1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
|
# Copyright (C) 2011-2012 Patrick Totzke <patricktotzke@gmail.com>
# 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)
|