diff --git a/it/LICENSE b/it/LICENSE new file mode 100644 index 0000000..fdddb29 --- /dev/null +++ b/it/LICENSE @@ -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 diff --git a/it/README.md b/it/README.md new file mode 100644 index 0000000..0c32f40 --- /dev/null +++ b/it/README.md @@ -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). diff --git a/it/it b/it/it new file mode 100755 index 0000000..de0f4b9 --- /dev/null +++ b/it/it @@ -0,0 +1,3 @@ +#!/bin/sh + +python3 src/it.py "$@" diff --git a/it/src/!.py b/it/src/!.py new file mode 100644 index 0000000..29cdb9a --- /dev/null +++ b/it/src/!.py @@ -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 diff --git a/it/src/Q.py b/it/src/Q.py new file mode 100644 index 0000000..8ebad32 --- /dev/null +++ b/it/src/Q.py @@ -0,0 +1,4 @@ +from sys import exit + +def main(buffer, argv): + return 0 diff --git a/it/src/buffer.py b/it/src/buffer.py new file mode 100644 index 0000000..18ac6b1 --- /dev/null +++ b/it/src/buffer.py @@ -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 + 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 = "" + # 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 diff --git a/it/src/config.py b/it/src/config.py new file mode 100644 index 0000000..32021ed --- /dev/null +++ b/it/src/config.py @@ -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 diff --git a/it/src/dot.py b/it/src/dot.py new file mode 100644 index 0000000..c45fe1a --- /dev/null +++ b/it/src/dot.py @@ -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 diff --git a/it/src/exit.py b/it/src/exit.py new file mode 100644 index 0000000..9080618 --- /dev/null +++ b/it/src/exit.py @@ -0,0 +1,4 @@ +import sys + +def main(buffer, command): + sys.exit(0) diff --git a/it/src/f.py b/it/src/f.py new file mode 100644 index 0000000..c8c9b96 --- /dev/null +++ b/it/src/f.py @@ -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 diff --git a/it/src/get_command.py b/it/src/get_command.py new file mode 100644 index 0000000..83c6fbf --- /dev/null +++ b/it/src/get_command.py @@ -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 diff --git a/it/src/hello_world.py b/it/src/hello_world.py new file mode 100644 index 0000000..4b1c040 --- /dev/null +++ b/it/src/hello_world.py @@ -0,0 +1,3 @@ +def main(buffer, command): + print("Hello, world!") + return buffer diff --git a/it/src/i.py b/it/src/i.py new file mode 100644 index 0000000..89ab7c0 --- /dev/null +++ b/it/src/i.py @@ -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 diff --git a/it/src/it.py b/it/src/it.py new file mode 100755 index 0000000..350ca84 --- /dev/null +++ b/it/src/it.py @@ -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) diff --git a/it/src/load_module.py b/it/src/load_module.py new file mode 100644 index 0000000..df02050 --- /dev/null +++ b/it/src/load_module.py @@ -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 diff --git a/it/src/p.py b/it/src/p.py new file mode 100644 index 0000000..34a6a21 --- /dev/null +++ b/it/src/p.py @@ -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 diff --git a/it/src/parse_command.py b/it/src/parse_command.py new file mode 100644 index 0000000..151e06d --- /dev/null +++ b/it/src/parse_command.py @@ -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()) diff --git a/it/src/q.py b/it/src/q.py new file mode 100644 index 0000000..f05cd12 --- /dev/null +++ b/it/src/q.py @@ -0,0 +1,8 @@ +from sys import exit + +def main(buffer, argv): + if not(buffer.saved): + print("?") + else: + buffer = 0 + return buffer diff --git a/it/src/saved.py b/it/src/saved.py new file mode 100644 index 0000000..47f12d6 --- /dev/null +++ b/it/src/saved.py @@ -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 diff --git a/it/src/version.py b/it/src/version.py new file mode 100644 index 0000000..8808343 --- /dev/null +++ b/it/src/version.py @@ -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