1
0

Merge remote-tracking branch 'it/main'

This commit is contained in:
dtb 2023-09-11 22:49:08 -04:00
commit 2eb1e26e8e
20 changed files with 429 additions and 0 deletions

24
it/LICENSE Normal file
View 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
View 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).

3
it/it Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
python3 src/it.py "$@"

11
it/src/!.py Normal file
View 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
View File

@ -0,0 +1,4 @@
from sys import exit
def main(buffer, argv):
return 0

74
it/src/buffer.py Normal file
View 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
View 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
View 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
View File

@ -0,0 +1,4 @@
import sys
def main(buffer, command):
sys.exit(0)

8
it/src/f.py Normal file
View 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
View 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
View File

@ -0,0 +1,3 @@
def main(buffer, command):
print("Hello, world!")
return buffer

31
it/src/i.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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