# Copyright (C) 2011-2012 Patrick Totzke # This file is released under the GNU GPL, version 3 or a later revision. # For further details see the COPYING file import argparse import glob import logging import os import re from ..settings.const import settings from ..helper import split_commandstring class Command: """base class for commands""" repeatable = False def __init__(self): self.prehook = None self.posthook = None self.undoable = False self.help = self.__doc__ def apply(self, ui): """code that gets executed when this command is applied""" pass class CommandCanceled(Exception): """ Exception triggered when an interactive command has been cancelled """ pass COMMANDS = { 'search': {}, 'envelope': {}, 'bufferlist': {}, 'taglist': {}, 'namedqueries': {}, 'thread': {}, 'global': {}, } def lookup_command(cmdname, mode): """ returns commandclass, argparser and forced parameters used to construct a command for `cmdname` when called in `mode`. :param cmdname: name of the command to look up :type cmdname: str :param mode: mode identifier :type mode: str :rtype: (:class:`Command`, :class:`~argparse.ArgumentParser`, dict(str->dict)) """ if cmdname in COMMANDS[mode]: return COMMANDS[mode][cmdname] elif cmdname in COMMANDS['global']: return COMMANDS['global'][cmdname] else: return None, None, None def lookup_parser(cmdname, mode): """ returns the :class:`CommandArgumentParser` used to construct a command for `cmdname` when called in `mode`. """ return lookup_command(cmdname, mode)[1] class CommandParseError(Exception): """could not parse commandline string""" pass class CommandArgumentParser(argparse.ArgumentParser): """ :class:`~argparse.ArgumentParser` that raises :class:`CommandParseError` instead of printing to `sys.stderr`""" def exit(self, message): raise CommandParseError(message) def error(self, message): raise CommandParseError(message) class registerCommand: """ Decorator used to register a :class:`Command` as handler for command `name` in `mode` so that it can be looked up later using :func:`lookup_command`. Consider this example that shows how a :class:`Command` class definition is decorated to register it as handler for 'save' in mode 'thread' and add boolean and string arguments:: .. code-block:: @registerCommand('thread', 'save', arguments=[ (['--all'], {'action': 'store_true', 'help':'save all'}), (['path'], {'nargs':'?', 'help':'path to save to'})], help='save attachment(s)') class SaveAttachmentCommand(Command): pass """ def __init__(self, mode, name, help=None, usage=None, forced=None, arguments=None): """ :param mode: mode identifier :type mode: str :param name: command name to register as :type name: str :param help: help string summarizing what this command does :type help: str :param usage: overides the auto generated usage string :type usage: str :param forced: keyword parameter used for commands constructor :type forced: dict (str->str) :param arguments: list of arguments given as pairs (args, kwargs) accepted by :meth:`argparse.ArgumentParser.add_argument`. :type arguments: list of (list of str, dict (str->str) """ self.mode = mode self.name = name self.help = help self.usage = usage self.forced = forced or {} self.arguments = arguments or [] def __call__(self, klass): helpstring = self.help or klass.__doc__ argparser = CommandArgumentParser(description=helpstring, usage=self.usage, prog=self.name, add_help=False) for args, kwargs in self.arguments: argparser.add_argument(*args, **kwargs) COMMANDS[self.mode][self.name] = (klass, argparser, self.forced) return klass def commandfactory(cmdline, mode='global'): """ parses `cmdline` and constructs a :class:`Command`. :param cmdline: command line to interpret :type cmdline: str :param mode: mode identifier :type mode: str """ # split commandname and parameters if not cmdline: return None logging.debug('mode:%s got commandline "%s"', mode, cmdline) # allow to shellescape without a space after '!' if cmdline.startswith('!'): cmdline = 'shellescape \'%s\'' % cmdline[1:] cmdline = re.sub(r'"(.*)"', r'"\\"\1\\""', cmdline) try: args = split_commandstring(cmdline) except ValueError as e: raise CommandParseError(str(e)) logging.debug('ARGS: %s', args) cmdname = args[0] args = args[1:] # unfold aliases # TODO: read from settingsmanager # get class, argparser and forced parameter (cmdclass, parser, forcedparms) = lookup_command(cmdname, mode) if cmdclass is None: msg = 'unknown command: %s' % cmdname logging.debug(msg) raise CommandParseError(msg) parms = vars(parser.parse_args(args)) parms.update(forcedparms) logging.debug('cmd parms %s', parms) # create Command cmd = cmdclass(**parms) # set pre and post command hooks get_hook = settings.get_hook cmd.prehook = get_hook('pre_%s_%s' % (mode, cmdname)) or \ get_hook('pre_global_%s' % cmdname) cmd.posthook = get_hook('post_%s_%s' % (mode, cmdname)) or \ get_hook('post_global_%s' % cmdname) return cmd pyfiles = glob.glob1(os.path.dirname(__file__), '*.py') __all__ = list(filename[:-3] for filename in pyfiles)