Source code for pypsi.commands.macro

#
# Copyright (c) 2015, Adam Meily <meily.adam@gmail.com>
# Pypsi - https://github.com/ameily/pypsi
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

from pypsi.plugins.block import BlockCommand
from pypsi.core import Command, PypsiArgParser, CommandShortCircuit
from pypsi.format import Table, Column, title_str
from pypsi.completers import command_completer
import sys


# something | macro | something
# =>
# something | cmd1 ; cmd2 | something

class Macro(Command):
    '''
    Recorded macro that executes statements sequentially. If the
    :class:`pypsi.plugins.variable.VariablePlugin` is registered, arguments
    passed in when the macro is called are translated into variables, ala Bash.
    Each statement may then reference the arguments via the variables ``$1-9``.
    The variable ``$0`` is the name of the macro. For example, the following
    statement would produce the corresponding variables:

    ``hello arg1 "arg 2" arg3``
    - ``$0`` = "hello"
    - ``$1`` = "arg1"
    - ``$2`` = "arg 2"
    - ``$3`` = "arg3"

    Once the macro has finished executing, the variables are argument variables
    are removed.
    '''

    def __init__(self, lines, **kwargs):
        super(Macro, self).__init__(**kwargs)
        self.lines = lines

    def run(self, shell, args):
        rc = None
        self.add_var_args(shell, args)

        for line in self.lines:
            rc = shell.execute(line)

        self.remove_var_args(shell)
        return rc

    def add_var_args(self, shell, args):
        if 'vars' in shell.ctx:
            shell.ctx.vars['0'] = self.name
            for i in range(0, 9):
                if i < len(args):
                    shell.ctx.vars[str(i+1)] = args[i]
                else:
                    shell.ctx.vars[str(i+1)] = ''

    def remove_var_args(self, shell):
        if 'vars' in shell.ctx:
            for i in range(0, 10):
                s = str(i)
                if s in shell.ctx.vars:
                    del shell.ctx.vars[s]


MacroCmdUsage = """%(prog)s -l
   or: %(prog)s NAME
   or: %(prog)s [-d] [-s] NAME"""


[docs]class MacroCommand(BlockCommand): ''' Record and execute command macros. Macros can consist of one or more command statements. Macros are comparable to Bash functions. Once a macro has been recorded, a new command is registered in the shell as an instance of a :class:`Macro`. This command requires the :class:`pypsi.plugins.block.BlockPlugin` plugin. ''' def __init__(self, name='macro', topic='shell', brief="manage registered macros", macros=None, **kwargs): self.parser = PypsiArgParser( prog=name, description=brief, usage=MacroCmdUsage ) self.parser.add_argument( '-l', '--list', help='list all macros', action='store_true' ) self.parser.add_argument( '-d', '--delete', help='delete macro', metavar='NAME', completer=self.complete_macros ) self.parser.add_argument( '-s', '--show', help='print macro body', metavar='NAME', completer=self.complete_macros ) self.parser.add_argument( 'name', help='macro name', nargs='?', metavar='NAME' ) super(MacroCommand, self).__init__( name=name, usage=self.parser.format_help(), brief=brief, topic=topic, **kwargs ) self.base_macros = macros or {} def complete_macros(self, shell, args, prefix): # returns a list of macro names in the current shell return list(shell.ctx.macros.keys()) def complete(self, shell, args, prefix): # The command_completer function takes in the parser, automatically # completes optional arguments (ex, '-v'/'--verbose') or sub-commands, # and complete any arguments' values by calling a callback function # with the same arguments as complete if the callback was defined # when the parser was created. return command_completer(self.parser, shell, args, prefix) def setup(self, shell): rc = 0 if 'macros' not in shell.ctx: shell.ctx.macros = {} for name in self.base_macros: rc = self.add_macro(shell, name, shell.ctx.macros[name]) return rc def run(self, shell, args): try: ns = self.parser.parse_args(args) except CommandShortCircuit as e: return e.code rc = 0 if ns.show: if ns.delete or ns.name: self.usage_error(shell, 'incompatible arguments: -s/--show and ', '-d/--delete' if ns.delete else 'NAME') return -1 if ns.list or ns.name: self.usage_error(shell, 'incompatible arguments: -s/--show and ', '-l/--list' if ns.list else 'NAME') return -1 if ns.show in shell.ctx.macros: print("macro ", ns.show, sep='') for line in shell.ctx.macros[ns.show]: print(" ", line, sep='') print("end") else: self.error(shell, "unknown macro ", ns.show) rc = -1 elif ns.delete: if ns.list or ns.name: self.usage_error(shell, 'incompatible arguments: -d/--delete and ', '-l/--list' if ns.list else 'NAME') return -1 if ns.delete in shell.ctx.macros: del shell.ctx.macros[ns.delete] # It gets registered as a command too. See line 230 in this # file and register() in shell.py del shell.commands[ns.delete] else: self.error(shell, "unknown macro ", ns.delete) rc = -1 elif ns.name: if ns.list: self.usage_error(shell, "list option does not take an argument") else: if (ns.name in shell.commands.keys() and ns.name not in shell.ctx.macros): self.error( shell, "A macro cannot have the same name as an ", "existing command." ) return -1 self.macro_name = ns.name self.begin_block(shell) if sys.stdin.isatty(): print("Beginning macro, use the '", shell.ctx.block.end_cmd, "' command to save.", sep='') shell.ctx.macro_orig_eof_is_sigint = shell.eof_is_sigint shell.eof_is_sigint = True elif ns.list: ''' Left justified table ''' print(title_str("Registered Macros", shell.width)) chunk_size = 3 tbl = Table( columns=(Column(''), Column(''), Column('', Column.Grow)), spacing=4, header=False, width=shell.width ) macro_names = list(shell.ctx.macros.keys()) for i in range(0, len(macro_names), chunk_size): chunk = macro_names[i:i+chunk_size] tbl.extend(chunk) tbl.write(sys.stdout) else: self.usage_error(shell, "missing required argument: NAME") rc = 1 return rc def end_block(self, shell, lines): self.add_macro(shell, self.macro_name, lines) self.macro_name = None shell.eof_is_sigint = shell.ctx.macro_orig_eof_is_sigint return 0 def cancel_block(self, shell): self.macro_name = None shell.eof_is_sigint = shell.ctx.macro_orig_eof_is_sigint def add_macro(self, shell, name, lines): shell.register( Macro(lines=lines, name=name, topic='__hidden__') ) shell.ctx.macros[name] = lines return 0