diff options
author | Patrick Totzke <patricktotzke@gmail.com> | 2011-10-15 10:43:33 +0100 |
---|---|---|
committer | Patrick Totzke <patricktotzke@gmail.com> | 2011-10-15 10:43:33 +0100 |
commit | c4f6b77f34357db44c91ee1492bab1ead62dcd0f (patch) | |
tree | 9f583357fa5c6f3b614152417ff20872c567feaf /alot/commands | |
parent | df06f6019df34fabfc0748744e67d27d62a2a75d (diff) |
infrastructure for argparser in commandfactory
Diffstat (limited to 'alot/commands')
-rw-r--r-- | alot/commands/__init__.py | 251 | ||||
-rw-r--r-- | alot/commands/envelope.py | 4 | ||||
-rw-r--r-- | alot/commands/globals.py | 69 |
3 files changed, 182 insertions, 142 deletions
diff --git a/alot/commands/__init__.py b/alot/commands/__init__.py index c67ad2a2..0adb75f3 100644 --- a/alot/commands/__init__.py +++ b/alot/commands/__init__.py @@ -1,6 +1,9 @@ import os +import sys import glob import logging +import argparse +import cStringIO import alot.settings @@ -26,140 +29,162 @@ COMMANDS = { 'global': {}, } +def lookup_command(cmdname, mode): + """returns commandclass, argparser and forcedparams + for `cmdname` in `mode`""" + if cmdname in COMMANDS[mode]: + return COMMANDS[mode][cmdname] + elif cmdname in COMMANDS['global']: + return COMMANDS['global'][cmdname] + else: + return None, None, None -class registerCommand(object): - def __init__(self, mode, name, defaultparms): - self.mode = mode - self.name = name - self.defaultparms = defaultparms +def lookup_parser(cmdname, mode): + return lookup_command(cmdname, mode)[1] - def __call__(self, klass): - COMMANDS[self.mode][self.name] = (klass, self.defaultparms) - return klass +class CommandParseError(Exception): + pass -def register(klass): - COMMANDS['classes'] = klass - return klass +class CommandArgumentParser(argparse.ArgumentParser): + """ArgumentParser that raises `CommandParseError` + instead of printing to sys.stderr""" + def exit(self, message): + raise CommandParseError(message) + def error(self, message): + raise CommandParseError(message) -def commandfactory(cmdname, mode='global', **kwargs): - if cmdname in COMMANDS[mode]: - (cmdclass, parms) = COMMANDS[mode][cmdname] - elif cmdname in COMMANDS['global']: - (cmdclass, parms) = COMMANDS['global'][cmdname] - else: - logging.error('there is no command %s' % cmdname) - parms = parms.copy() - parms.update(kwargs) - for (key, value) in kwargs.items(): - if callable(value): - parms[key] = value() - else: - parms[key] = value - parms['prehook'] = alot.settings.hooks.get('pre_' + cmdname) - parms['posthook'] = alot.settings.hooks.get('post_' + cmdname) - - logging.debug('cmd parms %s' % parms) - return cmdclass(**parms) +class registerCommand(object): + def __init__(self, mode, name, forced={}, arguments=[]): + self.argparser = CommandArgumentParser(prog=name, add_help=False) + for args,kwargs in arguments: + self.argparser.add_argument(*args,**kwargs) + self.mode = mode + self.name = name + self.forced = forced + def __call__(self, klass): + COMMANDS[self.mode][self.name] = (klass, self.argparser, self.forced) + return klass -def interpret_commandline(cmdline, mode): - # TODO: use argparser here! +def commandfactory(cmdline, mode='global'): + # split commandname and parameters if not cmdline: return None logging.debug('mode:%s got commandline "%s"' % (mode, cmdline)) args = cmdline.split(' ', 1) - cmd = args[0] + cmdname = args[0] if args[1:]: - params = args[1] + argstring = args[1] else: - params = '' + argstring = '' # unfold aliases - if alot.settings.config.has_option('command-aliases', cmd): - cmd = alot.settings.config.get('command-aliases', cmd) + if alot.settings.config.has_option('command-aliases', cmdname): + cmdname = alot.settings.config.get('command-aliases', cmdname) # allow to shellescape without a space after '!' - if cmd.startswith('!'): - params = cmd[1:] + ' ' + params - cmd = 'shellescape' + if cmdname.startswith('!'): + argstring = cmdname[1:] + ' ' + argstring + cmdname = 'shellescape' + + # 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) + + logging.debug('PARSE: %s' % argstring) + #logging.debug(parser) + parms = vars(parser.parse_args(argstring.split())) + logging.debug('PARMS: %s' % parms) + logging.debug(parms) + + parms.update(forcedparms) + # still needed? + #for (key, value) in kwargs.items(): + # if callable(value): + # parms[key] = value() + # else: + # parms[key] = value - # check if this command makes sense in current mode - if cmd not in COMMANDS[mode] and cmd not in COMMANDS['global']: - logging.debug('unknown command: %s' % (cmd)) - return None + parms['prehook'] = alot.settings.hooks.get('pre_' + cmdname) + parms['posthook'] = alot.settings.hooks.get('post_' + cmdname) - if cmd == 'search': - return commandfactory(cmd, mode=mode, query=params) - if cmd in ['move', 'sendkey']: - return commandfactory(cmd, mode=mode, key=params) - elif cmd == 'compose': - h = {} - if params: - h = {'To': params} - return commandfactory(cmd, mode=mode, headers=h) - elif cmd == 'attach': - return commandfactory(cmd, mode=mode, path=params) - elif cmd == 'help': - return commandfactory(cmd, mode=mode, commandline=params) - elif cmd == 'forward': - return commandfactory(cmd, mode=mode, inline=(params == '--inline')) - elif cmd == 'prompt': - return commandfactory(cmd, mode=mode, startstring=params) - elif cmd == 'refine': - if mode == 'search': - return commandfactory(cmd, mode=mode, query=params) - elif mode == 'envelope': - return commandfactory(cmd, mode=mode, key=params) - - elif cmd == 'retag': - return commandfactory(cmd, mode=mode, tagsstring=params) - elif cmd == 'shellescape': - return commandfactory(cmd, mode=mode, commandstring=params) - elif cmd == 'set': - key, value = params.split(' ', 1) - return commandfactory(cmd, mode=mode, key=key, value=value) - elif cmd == 'toggletag': - return commandfactory(cmd, mode=mode, tags=params.split()) - elif cmd == 'fold': - return commandfactory(cmd, mode=mode, all=(params == '--all')) - elif cmd == 'unfold': - return commandfactory(cmd, mode=mode, all=(params == '--all')) - elif cmd == 'save': - args = params.split(' ') - allset = False - pathset = None - if args: - if args[0] == '--all': - allset = True - pathset = ' '.join(args[1:]) - else: - pathset = params - return commandfactory(cmd, mode=mode, all=allset, path=pathset) - elif cmd == 'edit': - filepath = os.path.expanduser(params) - if os.path.isfile(filepath): - return commandfactory(cmd, mode=mode, path=filepath) - elif cmd == 'print': - args = [a.strip() for a in params.split()] - return commandfactory(cmd, mode=mode, - whole_thread=('--thread' in args), - separately=('--separately' in args)) - elif cmd == 'pipeto': - return commandfactory(cmd, mode=mode, command=params) - - elif not params and cmd in ['exit', 'flush', 'pyshell', 'taglist', - 'bclose', 'compose', 'openfocussed', - 'closefocussed', 'bnext', 'bprevious', 'retag', - 'refresh', 'bufferlist', 'refineprompt', - 'reply', 'open', 'groupreply', 'bounce', - 'openthread', 'toggleheaders', 'send', - 'cancel', 'reedit', 'select', 'retagprompt']: - return commandfactory(cmd, mode=mode) - else: - return None + logging.debug('cmd parms %s' % parms) + return cmdclass(**parms) + + +#def interpret_commandline(cmdline, mode): +# +# elif cmd == 'compose': +# h = {} +# if params: +# h = {'To': params} +# return commandfactory(cmd, mode=mode, headers=h) +# elif cmd == 'attach': +# return commandfactory(cmd, mode=mode, path=params) +# elif cmd == 'help': +# return commandfactory(cmd, mode=mode, commandline=params) +# elif cmd == 'forward': +# return commandfactory(cmd, mode=mode, inline=(params == '--inline')) +# elif cmd == 'prompt': +# return commandfactory(cmd, mode=mode, startstring=params) +# elif cmd == 'refine': +# if mode == 'search': +# return commandfactory(cmd, mode=mode, query=params) +# elif mode == 'envelope': +# return commandfactory(cmd, mode=mode, key=params) +# +# elif cmd == 'retag': +# return commandfactory(cmd, mode=mode, tagsstring=params) +# elif cmd == 'shellescape': +# return commandfactory(cmd, mode=mode, commandstring=params) +# elif cmd == 'set': +# key, value = params.split(' ', 1) +# return commandfactory(cmd, mode=mode, key=key, value=value) +# elif cmd == 'toggletag': +# return commandfactory(cmd, mode=mode, tags=params.split()) +# elif cmd == 'fold': +# return commandfactory(cmd, mode=mode, all=(params == '--all')) +# elif cmd == 'unfold': +# return commandfactory(cmd, mode=mode, all=(params == '--all')) +# elif cmd == 'save': +# args = params.split(' ') +# allset = False +# pathset = None +# if args: +# if args[0] == '--all': +# allset = True +# pathset = ' '.join(args[1:]) +# else: +# pathset = params +# return commandfactory(cmd, mode=mode, all=allset, path=pathset) +# elif cmd == 'edit': +# filepath = os.path.expanduser(params) +# if os.path.isfile(filepath): +# return commandfactory(cmd, mode=mode, path=filepath) +# elif cmd == 'print': +# args = [a.strip() for a in params.split()] +# return commandfactory(cmd, mode=mode, +# whole_thread=('--thread' in args), +# separately=('--separately' in args)) +# elif cmd == 'pipeto': +# return commandfactory(cmd, mode=mode, command=params) +# +# elif not params and cmd in ['exit', 'flush', 'pyshell', 'taglist', +# 'bclose', 'compose', 'openfocussed', +# 'closefocussed', 'bnext', 'bprevious', 'retag', +# 'refresh', 'bufferlist', 'refineprompt', +# 'reply', 'open', 'groupreply', 'bounce', +# 'openthread', 'toggleheaders', 'send', +# 'cancel', 'reedit', 'select', 'retagprompt']: +# return commandfactory(cmd, mode=mode) +# else: +# return None __all__ = list(filename[:-3] for filename in glob.glob1(os.path.dirname(__file__), '*.py')) diff --git a/alot/commands/envelope.py b/alot/commands/envelope.py index f37195db..59d1159f 100644 --- a/alot/commands/envelope.py +++ b/alot/commands/envelope.py @@ -21,7 +21,9 @@ from alot.commands.globals import EnvelopeOpenCommand MODE = 'envelope' -@registerCommand(MODE, 'attach', {}) +@registerCommand(MODE, 'attach', arguments=[ + (['path'], {'help':'file(s) to attach'})] +) class EnvelopeAttachCommand(Command): def __init__(self, path=None, mail=None, **kwargs): Command.__init__(self, **kwargs) diff --git a/alot/commands/globals.py b/alot/commands/globals.py index 877071e3..a14c551f 100644 --- a/alot/commands/globals.py +++ b/alot/commands/globals.py @@ -23,7 +23,7 @@ from alot import commands MODE = 'global' -@registerCommand(MODE, 'exit', {}) +@registerCommand(MODE, 'exit') class ExitCommand(Command): """shuts the MUA down cleanly""" @defer.inlineCallbacks @@ -35,14 +35,16 @@ class ExitCommand(Command): ui.exit() -@registerCommand(MODE, 'search', {}) +@registerCommand(MODE, 'search', arguments=[ + (['query'], {'nargs':'*', 'default':'', 'help':'search string'})] +) class SearchCommand(Command): """open a new search buffer""" def __init__(self, query, **kwargs): """ :param query: initial querystring """ - self.query = query + self.query = ' '.join(query) Command.__init__(self, **kwargs) @defer.inlineCallbacks @@ -65,18 +67,20 @@ class SearchCommand(Command): ui.notify('empty query string') -@registerCommand(MODE, 'prompt', {}) +@registerCommand(MODE, 'prompt', arguments=[ + (['startwith'], {'nargs':'*', 'default':'', 'help':'initial content of commandprompt'})] +) class PromptCommand(Command): """starts commandprompt""" - def __init__(self, startstring=u'', **kwargs): - self.startstring = startstring + def __init__(self, startwith=[], **kwargs): + self.startwith = ' '.join(startwith) Command.__init__(self, **kwargs) def apply(self, ui): - ui.commandprompt(self.startstring) + ui.commandprompt(self.startwith) -@registerCommand(MODE, 'refresh', {}) +@registerCommand(MODE, 'refresh') class RefreshCommand(Command): """refreshes the current buffer""" def apply(self, ui): @@ -84,7 +88,7 @@ class RefreshCommand(Command): ui.update() -@registerCommand(MODE, 'shellescape', {}) +@registerCommand(MODE, 'shellescape') class ExternalCommand(Command): """calls external command""" def __init__(self, commandstring, path=None, spawn=False, refocus=True, @@ -153,7 +157,7 @@ class ExternalCommand(Command): ui.mainloop.screen.start() -@registerCommand(MODE, 'edit', {}) +@registerCommand(MODE, 'edit') class EditCommand(ExternalCommand): def __init__(self, path, spawn=None, **kwargs): self.path = path @@ -168,7 +172,7 @@ class EditCommand(ExternalCommand): **kwargs) -@registerCommand(MODE, 'pyshell', {}) +@registerCommand(MODE, 'pyshell') class PythonShellCommand(Command): """opens an interactive shell for introspection""" def apply(self, ui): @@ -177,7 +181,7 @@ class PythonShellCommand(Command): ui.mainloop.screen.start() -@registerCommand(MODE, 'bclose', {}) +@registerCommand(MODE, 'bclose') @registerCommand('bufferlist', 'closefocussed', {'focussed': True}) class BufferCloseCommand(Command): """close a buffer""" @@ -200,9 +204,9 @@ class BufferCloseCommand(Command): ui.buffer_focus(ui.current_buffer) -@registerCommand(MODE, 'bprevious', {'offset': -1}) -@registerCommand(MODE, 'bnext', {'offset': +1}) -@registerCommand('bufferlist', 'openfocussed', {}) # todo separate +@registerCommand(MODE, 'bprevious', forced={'offset': -1}) +@registerCommand(MODE, 'bnext', forced={'offset': +1}) +@registerCommand('bufferlist', 'openfocussed') # todo separate class BufferFocusCommand(Command): """focus a buffer""" def __init__(self, buffer=None, offset=0, **kwargs): @@ -225,7 +229,7 @@ class BufferFocusCommand(Command): ui.buffer_focus(self.buffer) -@registerCommand(MODE, 'bufferlist', {}) +@registerCommand(MODE, 'bufferlist') class OpenBufferlistCommand(Command): """open a bufferlist buffer""" def __init__(self, filtfun=None, **kwargs): @@ -240,7 +244,7 @@ class OpenBufferlistCommand(Command): ui.buffer_open(buffers.BufferlistBuffer(ui, self.filtfun)) -@registerCommand(MODE, 'taglist', {}) +@registerCommand(MODE, 'taglist') class TagListCommand(Command): """open a taglisat buffer""" def __init__(self, filtfun=None, **kwargs): @@ -255,7 +259,7 @@ class TagListCommand(Command): ui.buffer_focus(buf) -@registerCommand(MODE, 'flush', {}) +@registerCommand(MODE, 'flush') class FlushCommand(Command): """Flushes writes to the index. Retries until committed""" def apply(self, ui): @@ -272,19 +276,24 @@ class FlushCommand(Command): return -@registerCommand(MODE, 'help', {}) +@registerCommand(MODE, 'help', arguments=[ + (['commandname'], {'help':'command'})] +) class HelpCommand(Command): - def __init__(self, commandline='', **kwargs): + def __init__(self, commandname='', **kwargs): Command.__init__(self, **kwargs) - self.commandline = commandline.strip() + self.commandname = commandname def apply(self, ui): - if self.commandline: - cmd = self.commandline.split(' ', 1)[0] - # TODO: how to I access COMMANDS from below? - ui.notify('no help for \'%s\'' % cmd, priority='error') - titletext = 'help for %s' % cmd - body = urwid.Text('helpstring') + ui.logger.debug('HELP') + if self.commandname: + ui.logger.debug('HELP %s' % self.commandname) + parser = commands.lookup_parser(self.commandname, ui.mode) + if parser: + ui.notify(parser.format_help()) + else: + ui.notify('command %s not known in mode %s' % (self.commandname, + ui.mode)) return else: # get mappings @@ -397,12 +406,16 @@ class ComposeCommand(Command): ui.apply_command(commands.envelope.EnvelopeEditCommand(mail=self.mail)) -@registerCommand(MODE, 'move', {}) +@registerCommand(MODE, 'move', arguments=[ + (['key'], {'nargs':'+', 'help':'keypress to send'})] +) @registerCommand(MODE, 'cancel', {'key': 'cancel'}) @registerCommand(MODE, 'select', {'key': 'select'}) class SendKeypressCommand(Command): def __init__(self, key, **kwargs): Command.__init__(self, **kwargs) + if isinstance(key, list): + key = ' '.join(key) self.key = key def apply(self, ui): |