Merge remote-tracking branch 'it/main'
This commit is contained in:
commit
2eb1e26e8e
24
it/LICENSE
Normal file
24
it/LICENSE
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <https://unlicense.org>
|
67
it/README.md
Normal file
67
it/README.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# it
|
||||||
|
|
||||||
|
*UNIX ed for a new age...*
|
||||||
|
|
||||||
|
Just kidding, no need to use this over ed.
|
||||||
|
This is just my take on a line editor, heavily inspired by ed.
|
||||||
|
|
||||||
|
For disambiguation it's suggested that this project be referred to as "ited", pronounced however one likes (suggested: "iht-ehd").
|
||||||
|
|
||||||
|
## Included commands
|
||||||
|
|
||||||
|
### !
|
||||||
|
|
||||||
|
Passes arguments to Python `subprocess.run`.
|
||||||
|
|
||||||
|
### buffer
|
||||||
|
|
||||||
|
This is where code relating to the Buffer class is kept but calling this from within the editor will allow you to view the current buffer's attributes.
|
||||||
|
|
||||||
|
### dot
|
||||||
|
|
||||||
|
Change the current position within the buffer.
|
||||||
|
### exit
|
||||||
|
|
||||||
|
Will exit via sys.exit rather than telling main.py to break out of the main loop. Functionally identical to `Q`.
|
||||||
|
|
||||||
|
### f
|
||||||
|
|
||||||
|
Displays or changes the filename associated with the current buffer.
|
||||||
|
|
||||||
|
### it
|
||||||
|
|
||||||
|
Launches a new instance of `it`.
|
||||||
|
|
||||||
|
It should be noted that quitting a nested instance of `it` through the usual means (`exit`, `q`, or `Q`) will exit all nested instances of `it` as well.
|
||||||
|
To return to a higher instance of `it`, send an EOF character via your terminal. In xterm this is CTRL+d.
|
||||||
|
|
||||||
|
### load\_module
|
||||||
|
|
||||||
|
Manually loads a module, whether or not it was already loaded.
|
||||||
|
|
||||||
|
### parse\_command
|
||||||
|
|
||||||
|
Used by `it`'s main Python module but calling this from within the editor will allow you to test the command argument splitting.
|
||||||
|
|
||||||
|
### Q
|
||||||
|
|
||||||
|
Quits.
|
||||||
|
|
||||||
|
### q
|
||||||
|
|
||||||
|
Quits, unless your current buffer is unsaved.
|
||||||
|
|
||||||
|
### saved
|
||||||
|
|
||||||
|
With no arguments given, flips the buffer's "saved" boolean attribute.
|
||||||
|
Otherwise, sets the buffer's "saved" attribute to the first argument or errors if multiple arguments given.
|
||||||
|
|
||||||
|
### version
|
||||||
|
|
||||||
|
Prints the versions of given modules.
|
||||||
|
If no modules are specified, prints the version of module "it".
|
||||||
|
|
||||||
|
## Making new commands
|
||||||
|
|
||||||
|
Making new commands is very easy.
|
||||||
|
Read `buffer.py` and `it.py` (<100 LOC total).
|
11
it/src/!.py
Normal file
11
it/src/!.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import subprocess
|
||||||
|
|
||||||
|
def main(buffer, command):
|
||||||
|
if len(command) == 1:
|
||||||
|
print("?")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
print("[%d]" % subprocess.run(command[1:]).returncode)
|
||||||
|
except Exception as err:
|
||||||
|
print("%s" % str(err))
|
||||||
|
return buffer
|
4
it/src/Q.py
Normal file
4
it/src/Q.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from sys import exit
|
||||||
|
|
||||||
|
def main(buffer, argv):
|
||||||
|
return 0
|
74
it/src/buffer.py
Normal file
74
it/src/buffer.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
class Buffer: # all the information about the current file
|
||||||
|
def carat(self):
|
||||||
|
return self.index
|
||||||
|
def dollar(self):
|
||||||
|
return self.length() - 1 + self.index
|
||||||
|
|
||||||
|
# Rather than get the content as it's presented in the buffer object
|
||||||
|
# (as one big string), get it as a list.
|
||||||
|
def content_list(self):
|
||||||
|
return self.content.rstrip(self.delimiter).split(self.delimiter)
|
||||||
|
|
||||||
|
# Build a string with the same format as the buffer.content and set the
|
||||||
|
# buffer.content to that string, from the kind of list output by
|
||||||
|
# buffer.content_list().
|
||||||
|
# buffer.content_set_list(buffer.content_list()) is an expensive nop.
|
||||||
|
def content_set_list(self, content_as_list):
|
||||||
|
content = ""
|
||||||
|
for line in content_as_list:
|
||||||
|
content += line + self.delimiter
|
||||||
|
self.content = content
|
||||||
|
return None
|
||||||
|
|
||||||
|
# this is really bad because any module can call import_module_ just as
|
||||||
|
# easily as any other. so malicious modules that, say, take 'q' and
|
||||||
|
# make it upload a file to some external server before exiting would be
|
||||||
|
# super easy to make.
|
||||||
|
# the solution I see is OS-level permissions but this needs to be
|
||||||
|
# talked about like all the time if this tool gets popular
|
||||||
|
# [why would it?] lest some fool runs a zelda.sh script that changes
|
||||||
|
# ~/src/it/w.py and doesn't realize in time
|
||||||
|
def import_module_(self, name):
|
||||||
|
try:
|
||||||
|
self.modules[name] = importlib.import_module(name)
|
||||||
|
except (ModuleNotFoundError, TypeError) as err:
|
||||||
|
print(err)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def length(self):
|
||||||
|
return self.content.count(self.delimiter)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.content = "" # content of the file
|
||||||
|
self.delimiter = "\n" # line delimiter
|
||||||
|
self.env = {} # configuration dictionary
|
||||||
|
self.filename = "" # name of where we'll save the file
|
||||||
|
self.index = 0 # indexing of dot
|
||||||
|
self.dot = self.index - 1 # invalid position to start
|
||||||
|
self.modules = {} # see it.py
|
||||||
|
self.saved = 1 # is buffer saved?
|
||||||
|
|
||||||
|
def main(buffer, command):
|
||||||
|
if len(command) == 1:
|
||||||
|
command += list(vars(buffer))
|
||||||
|
for attrib in command[1:]:
|
||||||
|
if attrib in list(vars(buffer)):
|
||||||
|
val = vars(buffer)[attrib]
|
||||||
|
# so self.modules shows as <class 'dict'>
|
||||||
|
if not(type(val) is int or type(val) is str):
|
||||||
|
val = str(type(val))
|
||||||
|
# special case usually for self.delimiter
|
||||||
|
elif val == "\n":
|
||||||
|
val = "<newline>"
|
||||||
|
# only affects it if type(val) is int
|
||||||
|
else:
|
||||||
|
val = str(val)
|
||||||
|
print("%s:\t%s" % (attrib, val))
|
||||||
|
else:
|
||||||
|
print("No attribute: %s\n" % attrib, end='')
|
||||||
|
return buffer
|
10
it/src/config.py
Normal file
10
it/src/config.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
def main(buffer, command):
|
||||||
|
# hard-coded for consistency across multiple people's configs
|
||||||
|
config_delimiter = "\t"
|
||||||
|
for i in range(len(buffer.content_list())):
|
||||||
|
line = buffer.content_list()[i].split(config_delimiter)
|
||||||
|
if len(line) != 2:
|
||||||
|
print("%s: %d: Bad column quantity" % (command[0], i+buffer.index))
|
||||||
|
else:
|
||||||
|
buffer.env[line[0]] = line[1]
|
||||||
|
return buffer
|
20
it/src/dot.py
Normal file
20
it/src/dot.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
def main(buffer, command):
|
||||||
|
if len(command) == 2 and (command[1].isdigit() or command[1] in {"^","$"}):
|
||||||
|
if command[1].isdigit():
|
||||||
|
dot = int(command[1])
|
||||||
|
elif command[1] == "^":
|
||||||
|
dot = buffer.carat()
|
||||||
|
elif command[1] == "$":
|
||||||
|
dot = buffer.dollar()
|
||||||
|
elif len(command) == 3 and command[1] in {"+","-"} and command[2].isdigit():
|
||||||
|
if command[1] == "+":
|
||||||
|
dot = buffer.dot + int(command[2])
|
||||||
|
elif command[1] == "-":
|
||||||
|
dot = buffer.dot - int(command[2])
|
||||||
|
|
||||||
|
if not("dot" in locals()) or dot < buffer.carat() or dot > buffer.dollar():
|
||||||
|
print("?")
|
||||||
|
else:
|
||||||
|
buffer.dot = dot
|
||||||
|
|
||||||
|
return buffer
|
4
it/src/exit.py
Normal file
4
it/src/exit.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
def main(buffer, command):
|
||||||
|
sys.exit(0)
|
8
it/src/f.py
Normal file
8
it/src/f.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
def main(buffer, argv):
|
||||||
|
if len(argv) > 2:
|
||||||
|
print("?")
|
||||||
|
elif len(argv) == 1:
|
||||||
|
print(buffer.filename)
|
||||||
|
else:
|
||||||
|
buffer.filename = argv[1]
|
||||||
|
return buffer
|
13
it/src/get_command.py
Normal file
13
it/src/get_command.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from parse_command import parse_command
|
||||||
|
|
||||||
|
def get_command(prompt):
|
||||||
|
try:
|
||||||
|
return parse_command(input(prompt))
|
||||||
|
except KeyboardInterrupt: # bastard behavior from ed
|
||||||
|
pass
|
||||||
|
except EOFError:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def main(buffer, command):
|
||||||
|
print("Command returned as %s" % get_command("Input command (won't be executed): "))
|
||||||
|
return buffer
|
3
it/src/hello_world.py
Normal file
3
it/src/hello_world.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
def main(buffer, command):
|
||||||
|
print("Hello, world!")
|
||||||
|
return buffer
|
31
it/src/i.py
Normal file
31
it/src/i.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
def main(buffer, command):
|
||||||
|
if len(command) > 1:
|
||||||
|
print("?")
|
||||||
|
return buffer
|
||||||
|
|
||||||
|
i = []
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
line = input()
|
||||||
|
# unintuitive behavior from ed
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("?")
|
||||||
|
return buffer
|
||||||
|
except EOFError:
|
||||||
|
break
|
||||||
|
|
||||||
|
if line == ".":
|
||||||
|
break
|
||||||
|
|
||||||
|
i.append(line)
|
||||||
|
|
||||||
|
if buffer.dot < buffer.index:
|
||||||
|
buffer.content_set_list(i)
|
||||||
|
else:
|
||||||
|
buffer.content_set_list(
|
||||||
|
buffer.content_list()[:buffer.dot]
|
||||||
|
+ i
|
||||||
|
+ buffer.content_list()[buffer.dot:]
|
||||||
|
)
|
||||||
|
buffer.dot += len(i)
|
||||||
|
return buffer
|
38
it/src/it.py
Executable file
38
it/src/it.py
Executable file
@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
from buffer import Buffer
|
||||||
|
from get_command import get_command
|
||||||
|
|
||||||
|
def version():
|
||||||
|
print("it.py line editor; ALPHA 2021")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def main(buffer, supplied_command):
|
||||||
|
if supplied_command != [] and len(supplied_command) > 1:
|
||||||
|
command = supplied_command[1:]
|
||||||
|
else:
|
||||||
|
command = get_command("")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# EOFError in get_command(); ^D
|
||||||
|
if command == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
if command == []:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if command[0] in buffer.modules.keys() or buffer.import_module_(command[0]):
|
||||||
|
buffer = buffer.modules[command[0]].main(buffer, command)
|
||||||
|
if type(buffer) is int:
|
||||||
|
break
|
||||||
|
|
||||||
|
command = get_command("")
|
||||||
|
|
||||||
|
return buffer
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
buffer = main(Buffer(), [])
|
||||||
|
if type(buffer) is int:
|
||||||
|
sys.exit(buffer)
|
||||||
|
else:
|
||||||
|
sys.exit(0)
|
8
it/src/load_module.py
Normal file
8
it/src/load_module.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
def main(buffer, command):
|
||||||
|
if len(command) == 1:
|
||||||
|
print("?")
|
||||||
|
else:
|
||||||
|
for module in command[1:]:
|
||||||
|
if buffer.import_module_(module):
|
||||||
|
print("Loaded %s" % module)
|
||||||
|
return buffer
|
7
it/src/p.py
Normal file
7
it/src/p.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
def main(buffer, command):
|
||||||
|
if len(command) < 1 or len(command) > 2:
|
||||||
|
|
||||||
|
if buffer.dot < buffer.index:
|
||||||
|
buffer.content = i
|
||||||
|
buffer.dot += len(i)
|
||||||
|
return buffer
|
65
it/src/parse_command.py
Normal file
65
it/src/parse_command.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def parse_command(command):
|
||||||
|
casesensitive = True
|
||||||
|
escapes = {"\\"}
|
||||||
|
quotes = {"'", '"'}
|
||||||
|
spaces = {" ", "\t"}
|
||||||
|
|
||||||
|
command = command.strip()
|
||||||
|
|
||||||
|
in_quotes = 0
|
||||||
|
word = ""
|
||||||
|
parsed_command = []
|
||||||
|
|
||||||
|
for i in range(len(command)):
|
||||||
|
# if this char is a quote char
|
||||||
|
# , we're in quotes
|
||||||
|
if ((command[i] in quotes)
|
||||||
|
and in_quotes == 0
|
||||||
|
and (i == 0
|
||||||
|
or (i > 0 and not(command[i-1] in escapes)))):
|
||||||
|
in_quotes = command[i]
|
||||||
|
|
||||||
|
# if this char matches the char by which we're in quotes
|
||||||
|
# , we're not in quotes
|
||||||
|
elif (command[i] == in_quotes
|
||||||
|
and (i > 0 and not(command[i-1] in escapes))):
|
||||||
|
in_quotes = 0
|
||||||
|
|
||||||
|
# if this char is an arg delimiter
|
||||||
|
# and we're not in quotes
|
||||||
|
# and the last char isn't an escape
|
||||||
|
# , this word is an argument
|
||||||
|
elif (command[i] in spaces and in_quotes == 0
|
||||||
|
and (i > 0 and not(command[i-1] in escapes))):
|
||||||
|
parsed_command += [word]
|
||||||
|
word = ""
|
||||||
|
|
||||||
|
elif (not(command[i] in escapes) or (i == len(command) - 1)
|
||||||
|
or not(command[i+1] in spaces + quotes)):
|
||||||
|
word += command[i]
|
||||||
|
|
||||||
|
parsed_command += [word]
|
||||||
|
return [] if parsed_command == [''] else parsed_command
|
||||||
|
|
||||||
|
def main(*args):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
command = input()
|
||||||
|
except:
|
||||||
|
break
|
||||||
|
|
||||||
|
if command == ".":
|
||||||
|
break
|
||||||
|
|
||||||
|
command = parse_command(command)
|
||||||
|
|
||||||
|
for i in range(len(command)):
|
||||||
|
print("\t%d:\t%s" % (i, command[i]))
|
||||||
|
|
||||||
|
return 0 if len(args) != 2 else args[0]
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
8
it/src/q.py
Normal file
8
it/src/q.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from sys import exit
|
||||||
|
|
||||||
|
def main(buffer, argv):
|
||||||
|
if not(buffer.saved):
|
||||||
|
print("?")
|
||||||
|
else:
|
||||||
|
buffer = 0
|
||||||
|
return buffer
|
15
it/src/saved.py
Normal file
15
it/src/saved.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
def main(buffer, command):
|
||||||
|
if len(command) > 2:
|
||||||
|
print("?")
|
||||||
|
elif len(command) == 2:
|
||||||
|
if command[1].isdigit():
|
||||||
|
buffer.saved = int(command[1])
|
||||||
|
elif command[1].lower() in {"true","false"}:
|
||||||
|
buffer.saved = command[1].lower == "true"
|
||||||
|
else:
|
||||||
|
print("?")
|
||||||
|
else:
|
||||||
|
buffer.saved = not(buffer.saved)
|
||||||
|
print("Buffer marked as %sSAVED" % ("" if buffer.saved else "NOT "))
|
||||||
|
|
||||||
|
return buffer
|
16
it/src/version.py
Normal file
16
it/src/version.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
def version():
|
||||||
|
print("it.py version querying; ALPHA 2021")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def main(buffer, command):
|
||||||
|
if len(command) > 1:
|
||||||
|
fetching = command[1:]
|
||||||
|
else:
|
||||||
|
fetching = ["it"]
|
||||||
|
for module in fetching:
|
||||||
|
if module in list(buffer.modules) or buffer.import_module_(module):
|
||||||
|
try:
|
||||||
|
buffer.modules[module].version()
|
||||||
|
except AttributeError as err:
|
||||||
|
print(err)
|
||||||
|
return buffer
|
Loading…
Reference in New Issue
Block a user