diff --git a/console.py b/console.py new file mode 100644 index 0000000..b08f42d --- /dev/null +++ b/console.py @@ -0,0 +1,81 @@ +import atexit +import code +import os +import readline +import shlex + +from commands import registered_cmds +import example_cmd + +DEFAULT_HISTORY_FILE = "~/.psh_history" + + +def parse_cmd(potential_cmd): + """Evaluates a potential command. If it exists in the list of + registered commands, we return a string that would call the + constructor for that command. If it does not exist, we wrap the + name of the command with the RawCommand class. + + :return: A string that when evaluated by Python's `eval` feature + would build an object of the correct type + """ + args = potential_cmd.strip().split(' ') + cmd_name = args[0] + if args: + args = args[1:] + if cmd_name not in registered_cmds: + return "RawCommand({})".format(shlex.split(potential_cmd)) + else: + return "{0}({1})".format(cmd_name,str(args)) + + +def parse_cmds(raw_input_line): + """Parses command objects out of a single | separated string""" + potential_cmds = raw_input_line.split('|') + cmds = [parse_cmd(cmd) for cmd in potential_cmds] + return cmds + + +class HistoryConsole(code.InteractiveConsole): + """Stolen from https://docs.python.org/2/library/readline.html + + Modified for the purposes of this project to handle special + bash-like parsing""" + + def __init__(self, locals=None, filename="", + histfile=os.path.expanduser(DEFAULT_HISTORY_FILE)): + code.InteractiveConsole.__init__(self, locals, filename) + self.init_history(histfile) + + def init_history(self, histfile): + readline.parse_and_bind("tab: complete") + if hasattr(readline, "read_history_file"): + try: + readline.read_history_file(histfile) + except IOError: + pass + atexit.register(self.save_history, histfile) + + def raw_input(self, prompt=""): + """Gets a single line of input for the REPL, and returns some valid + Python for the rest of the REPL to evaluate. It's the "R" in REPL. + If the line begins with ">", we just strip it and evaluate as valid + Python.""" + raw_input_line = input(prompt) + if raw_input_line == "": + return raw_input_line + if raw_input_line[0] == '>': + return raw_input_line[1:] + else: + cmds = parse_cmds(raw_input_line) + cmds.append("Printer()") + mangled_input = cmds[0] + for cmd in cmds[1:]: + mangled_input += ".chain(" + cmd + ")" + mangled_input += ".call()" + print("[DEBUG]: evaluating Python: ", mangled_input) + return mangled_input + + + def save_history(self, histfile): + readline.write_history_file(histfile) diff --git a/main.py b/main.py index 6b65cdd..a7bf3cc 100644 --- a/main.py +++ b/main.py @@ -1,11 +1,8 @@ -from commands import registered_cmds -from raw_commands import RawCommand -from formatters import Printer -from example_cmd import example_cmd, echo - import os import os.path -import shlex + +from formatters import * +from raw_commands import RawCommand # Load all of the commands in the path into the global namespace as raw # commands. @@ -17,54 +14,10 @@ for path in os.environ['PATH'].split(':'): globals()[binary] = RawCommand(binary) -def parse_cmd(potential_cmd): - """Evaluates a potential command. If it exists in the list of - registered commands, we return a string that would call the - constructor for that command. If it does not exist, we wrap the - name of the command with the RawCommand class. - - :return: A string that when evaluated by Python's `eval` feature - would build an object of the correct type - """ - args = potential_cmd.strip().split(' ') - cmd_name = args[0] - if args: - args = args[1:] - if cmd_name not in registered_cmds: - return "RawCommand({})".format(shlex.split(potential_cmd)) - else: - return "{0}({1})".format(cmd_name,str(args)) - - -def parse_cmds(raw_input_line): - """Parses command objects out of a single | separated string""" - potential_cmds = raw_input_line.split('|') - cmds = [parse_cmd(cmd) for cmd in potential_cmds] - return cmds - - -def handle_input(prompt=""): - """Gets a single line of input for the REPL, and returns some valid - Python for the rest of the REPL to evaluate. It's the "R" in REPL. - If the line begins with ">", we just strip it and evaluate as valid - Python.""" - raw_input_line = input(prompt) - if raw_input_line[0] == '>': - return raw_input_line[1:] - else: - cmds = parse_cmds(raw_input_line) - cmds.append("Printer()") - mangled_input = cmds[0] - for cmd in cmds[1:]: - mangled_input += ".chain(" + cmd + ")" - mangled_input += ".call()" - print("[DEBUG]: evaluating Python: ", mangled_input) - return mangled_input - - def main(): - import code - code.interact("Augmented Unix Userland", handle_input, globals()) + from console import HistoryConsole + console = HistoryConsole(globals()) + console.interact("Augmented Unix Userland") if __name__ == '__main__': diff --git a/tree.py b/tree.py new file mode 100644 index 0000000..8af2c09 --- /dev/null +++ b/tree.py @@ -0,0 +1,21 @@ +class TreeNode(object): + """A tree node holds some data and has iterable children.""" + + def __init__(self, data): + """Initializes a data. The data can be any type, but it usually + is an ordered dictionary.""" + self.parent = None + self.data = data + self.children = [] + + def add_child(self, child): + """Adds a child to the node.""" + child.parent = self + self.children.apppend(child) + + def children(self): + """Returns an iterable generator for all the children nodes.""" + def children_generator(): + for child in self.children: + yield child + return children_generator()