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