From c9b1b2fba9542c4f5883e1bd120db24c97d1020d Mon Sep 17 00:00:00 2001 From: Ian Adam Naval Date: Fri, 20 Feb 2015 18:20:22 -0500 Subject: [PATCH] Initial commit --- .gitignore | 2 ++ commands.py | 52 ++++++++++++++++++++++++++++++++++++++ example.typescript | 39 ++++++++++++++++++++++++++++ example_cmd.py | 20 +++++++++++++++ formatters.py | 23 +++++++++++++++++ main.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 199 insertions(+) create mode 100644 .gitignore create mode 100644 commands.py create mode 100644 example.typescript create mode 100644 example_cmd.py create mode 100644 formatters.py create mode 100644 main.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d35cb3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +*.pyc diff --git a/commands.py b/commands.py new file mode 100644 index 0000000..ebf25be --- /dev/null +++ b/commands.py @@ -0,0 +1,52 @@ +from formatters import Printer +from io import StringIO + + +class BaseCommand(object): + + def __call__(self, *args, **kwargs): + raise NotImplementedError( + "BaseCommands must be callable and return a generator") + + +class NoneCommand(BaseCommand): + + def __call__(self, *args, **kwargs): + return [] + + + +class RawCommand(BaseCommand): + """Fallback raw command that just invokes an existing Unix utility program + with the builtin subprocess module. Each output object is just a tree node + whose data is a simple string.""" + + def __init__(self, cmd, input_generator=[], args=tuple()): + self.input_generator = input_generator + self.cmd = cmd + self.args = args + + def __call__(self, input_generator=[], *args, **kwargs): + import subprocess + try: + p = subprocess.Popen((self.cmd,) + self.args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + def output_generator(): + input_str = b"" + for line in input_generator: + input_str += line + outs, errs = p.communicate(input_str) + if outs: + yield outs + + return output_generator() + except: + import traceback + traceback.print_exc() + return [] + + +registered_cmds = [] + +def register_cmd(cls): + registered_cmds.append(cls.__name__) + return cls diff --git a/example.typescript b/example.typescript new file mode 100644 index 0000000..285edf7 --- /dev/null +++ b/example.typescript @@ -0,0 +1,39 @@ +Script started on Fri 20 Feb 2015 06:18:07 PM EST +%  +(B(Bblazer(B (B~/dev/mqp (B(B18:18 (B +(B(B [?1h=  +(B(Bblazer(B (B~/dev/mqp (B(B18:18 (B +(B➤(B scriptpython main.py[?1l>  +(B(Bblazer(B (B~/dev/mqp (B(B18:18 (B +(B➤(B python main.py +Augmented Unix Userland +>>> ls +[DEBUG]: evaluating Python: Printer()(RawCommand('ls')()) +commands.py +example_cmd.py +formatters.py +main.py +__pycache__ +typescript + +>>> ls | grep format +[DEBUG]: evaluating Python: Printer()(RawCommand('grep', args=('format',))(RawCommand('ls')())) +formatters.py + +>>> >ls  2 + 2 +4 +>>> >ls  )ou     grep    ls_outu put = ls() +>>> >ls  grep.args = (', py',) +>>> >grepped_outpu          p.args = ('format',) +>>> >grepped_output = ls  grep(ls_output) +>>> >g Printer()(grepped_output) +formatters.py + +>>> +%  +(B(Bblazer(B (B~/dev/mqp (B(B18:19 (B +(B➤(B [?1h=  +(B(Bblazer(B (B~/dev/mqp (B(B18:19 (B +(B➤(B  + +Script done on Fri 20 Feb 2015 06:19:26 PM EST diff --git a/example_cmd.py b/example_cmd.py new file mode 100644 index 0000000..970fd1f --- /dev/null +++ b/example_cmd.py @@ -0,0 +1,20 @@ +from commands import BaseCommand, register_cmd + +@register_cmd +class example_cmd(BaseCommand): + + def __call__(self, *args, **kwargs): + def input_generator(): + yield 'example' + yield 'command' + return input_generator + + +@register_cmd +class echo(BaseCommand): + + def __call__(self, *args, **kwargs): + def input_generator(): + for line in self.input_cmd(): + yield line + return input_generator diff --git a/formatters.py b/formatters.py new file mode 100644 index 0000000..86fa951 --- /dev/null +++ b/formatters.py @@ -0,0 +1,23 @@ +class Formatter(object): + """Formatters are callable objects who always accept a generator. + The generator represents the output stream of an executing program. + Formatters are special commands which produce no output themselves. + Instead, they always write to the standard out of the program. """ + + def __init__(self, *args, **kwargs): + # Always require no arguments + pass + + def __call__(self, input_generator): + raise NotImplementedError( + "You must extend a Formatter and implement the __call__ method") + + +class Printer(Formatter): + """Simple formatter which accepts any object from the input + generator and simply prints it as if it were a string.""" + + def __call__(self, input_generator): + for line in input_generator: + print(str(line.decode('utf-8'))) + return None diff --git a/main.py b/main.py new file mode 100644 index 0000000..3d49756 --- /dev/null +++ b/main.py @@ -0,0 +1,63 @@ +from commands import RawCommand, registered_cmds +from formatters import Printer +from example_cmd import example_cmd, echo + +import os +import os.path +for path in os.environ['PATH'].split(':'): + if os.path.exists(path): + binaries = os.listdir(path) + for binary in binaries: + 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: + if args: + return "RawCommand('{}', args={})".format(cmd_name, str(tuple(args))) + else: + return "RawCommand('{}')".format(cmd_name) + else: + return "{}()".format(cmd_name) + + +def parse_cmds(raw): + potential_cmds = raw.split('|') + cmds = [parse_cmd(cmd) for cmd in potential_cmds] + return cmds + + +def handle_input(prompt=""): + raw = input(prompt) + if raw[0] == '>': + return raw[1:] + else: + cmds = parse_cmds(raw) + cmds.append("Printer()") + mangled_input = "" + for cmd in reversed(cmds): + mangled_input += cmd + "(" + mangled_input += ")" * len(cmds) + print("[DEBUG]: evaluating Python: ", mangled_input) + return mangled_input + + +def main(): + import code + code.interact("Augmented Unix Userland", handle_input, globals()) + + +if __name__ == '__main__': + main()