diff --git a/commands.py b/commands.py index b672a00..ab41676 100644 --- a/commands.py +++ b/commands.py @@ -1,52 +1,46 @@ -from formatters import Printer from io import StringIO class BaseCommand(object): - def __init__(self, args): + """Commands can be used to chain the execution of multiple programs + together. You can chain multiple commands together using commands + + For example: + ls = RawCommand(["ls"]) + grep = RawCommand(["grep", "potato"]) + printer = Printer() + ls.chain(grep).chain(printer).call() + """ + + def __init__(self, args=None): self.cmd_args = args - def __call__(self, *args, **kwargs): - raise NotImplementedError( - "BaseCommands must be callable and return a generator") + self.prev_cmd = None + def call(self, *args, **kwargs): + """Implicitly calls any chained commands, returning a function + to make an input generator.""" + raise NotImplementedError("Must implement call") -class NoneCommand(BaseCommand): + def get_input_generator(self): + """Gets the input generator from the previous command, if it + exists. If it doesn't exist, we just return an empy list so that + when you iterate over it, it does nothing.""" + if self.prev_cmd is not None: + make_input_generator = self.prev_cmd.call() + input_generator = make_input_generator() + else: + input_generator = [] + return input_generator - 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=[]): - self.input_generator = input_generator - self.cmd = cmd - - def __call__(self, input_generator=[], *args, **kwargs): - import subprocess - try: - p = subprocess.Popen(self.cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) - def output_generator(): - input_str = b"" - for line in input_generator: - input_str += line + b'\n' - outs, errs = p.communicate(input_str) - if outs: - yield outs - - return output_generator() - except: - import traceback - traceback.print_exc() - return [] + def chain(self, cmd): + """Chains a command to another command, returning the other command""" + cmd.prev_cmd = self + return cmd registered_cmds = [] def register_cmd(cls): + """Decorator for putting all of the commands in one nice place.""" registered_cmds.append(cls.__name__) return cls diff --git a/example_cmd.py b/example_cmd.py index aa704c4..1b23ad3 100644 --- a/example_cmd.py +++ b/example_cmd.py @@ -1,19 +1,33 @@ from commands import BaseCommand, register_cmd + @register_cmd class example_cmd(BaseCommand): - def __call__(self, *args, **kwargs): + """Simple command that just returns 'example' and 'command'. Does + nothing at all with the input.""" + + def call(self, *args, **kwargs): def output_generator(): yield b'example' yield b'command' - return output_generator() + return output_generator @register_cmd class echo(BaseCommand): - def __call__(self,*args,**kwargs): - def output_generator(): - for line in self.cmd_args: - yield line.encode('utf-8') - return output_generator() + """Echoes anything from the command line arguments as well as input + from the previous command.""" + + def __init__(self, args): + super(echo, self).__init__() + self.args = args + + def call(self,*args,**kwargs): + input_generator = self.get_input_generator() + def output_generator(): + for args in self.args: + yield args.encode("utf-8") + for line in input_generator: + yield line + return output_generator diff --git a/formatters.py b/formatters.py index 66c62bf..8a516fb 100644 --- a/formatters.py +++ b/formatters.py @@ -1,23 +1,12 @@ -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") +from commands import BaseCommand -class Printer(Formatter): +class Printer(BaseCommand): """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): + def call(self): + input_generator = self.get_input_generator() for line in input_generator: print(str(line.decode('utf-8'))) return None diff --git a/main.py b/main.py index db8689b..957bc2a 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ -from commands import RawCommand, registered_cmds +from commands import registered_cmds +from raw_commands import RawCommand from formatters import Printer from example_cmd import example_cmd, echo @@ -46,10 +47,10 @@ def handle_input(prompt=""): else: cmds = parse_cmds(raw) cmds.append("Printer()") - mangled_input = "" - for cmd in reversed(cmds): - mangled_input += cmd + "(" - mangled_input += ")" * len(cmds) + 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 diff --git a/raw_commands.py b/raw_commands.py new file mode 100644 index 0000000..82f4c54 --- /dev/null +++ b/raw_commands.py @@ -0,0 +1,31 @@ +from formatters import Printer +from commands import BaseCommand + + +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): + super(RawCommand, self).__init__() + self.cmd = cmd + + def call(self, *args, **kwargs): + input_generator = self.get_input_generator() + import subprocess + try: + p = subprocess.Popen(self.cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + def make_output_generator(): + input_str = b"" + for line in input_generator: + input_str += line + b'\n' + outs, errs = p.communicate(input_str) + if outs: + yield outs + + return make_output_generator + except: + import traceback + traceback.print_exc() + return []