diff options
-rw-r--r-- | scripts/lib/wic/creator.py | 115 | ||||
-rw-r--r-- | scripts/lib/wic/plugins/imager/direct_plugin.py | 2 | ||||
-rw-r--r-- | scripts/lib/wic/utils/cmdln.py | 1604 |
3 files changed, 27 insertions, 1694 deletions
diff --git a/scripts/lib/wic/creator.py b/scripts/lib/wic/creator.py index 760848f320..5231297282 100644 --- a/scripts/lib/wic/creator.py +++ b/scripts/lib/wic/creator.py | |||
@@ -16,15 +16,15 @@ | |||
16 | # Temple Place - Suite 330, Boston, MA 02111-1307, USA. | 16 | # Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
17 | 17 | ||
18 | import os, sys | 18 | import os, sys |
19 | from optparse import SUPPRESS_HELP | 19 | from optparse import OptionParser, SUPPRESS_HELP |
20 | 20 | ||
21 | from wic import msger | 21 | from wic import msger |
22 | from wic.utils import cmdln, errors | 22 | from wic.utils import errors |
23 | from wic.conf import configmgr | 23 | from wic.conf import configmgr |
24 | from wic.plugin import pluginmgr | 24 | from wic.plugin import pluginmgr |
25 | 25 | ||
26 | 26 | ||
27 | class Creator(cmdln.Cmdln): | 27 | class Creator(object): |
28 | """${name}: create an image | 28 | """${name}: create an image |
29 | 29 | ||
30 | Usage: | 30 | Usage: |
@@ -37,8 +37,7 @@ class Creator(cmdln.Cmdln): | |||
37 | name = 'wic create(cr)' | 37 | name = 'wic create(cr)' |
38 | 38 | ||
39 | def __init__(self, *args, **kwargs): | 39 | def __init__(self, *args, **kwargs): |
40 | cmdln.Cmdln.__init__(self, *args, **kwargs) | 40 | self._subcmds = {} |
41 | self._subcmds = [] | ||
42 | 41 | ||
43 | # get cmds from pluginmgr | 42 | # get cmds from pluginmgr |
44 | # mix-in do_subcmd interface | 43 | # mix-in do_subcmd interface |
@@ -48,11 +47,10 @@ class Creator(cmdln.Cmdln): | |||
48 | continue | 47 | continue |
49 | 48 | ||
50 | func = getattr(klass, 'do_create') | 49 | func = getattr(klass, 'do_create') |
51 | setattr(self.__class__, "do_"+subcmd, func) | 50 | self._subcmds[subcmd] = func |
52 | self._subcmds.append(subcmd) | ||
53 | 51 | ||
54 | def get_optparser(self): | 52 | def get_optparser(self): |
55 | optparser = cmdln.CmdlnOptionParser(self) | 53 | optparser = OptionParser() |
56 | optparser.add_option('-d', '--debug', action='store_true', | 54 | optparser.add_option('-d', '--debug', action='store_true', |
57 | dest='debug', | 55 | dest='debug', |
58 | help=SUPPRESS_HELP) | 56 | help=SUPPRESS_HELP) |
@@ -73,69 +71,31 @@ class Creator(cmdln.Cmdln): | |||
73 | ' feature, use it if you have more than 4G memory') | 71 | ' feature, use it if you have more than 4G memory') |
74 | return optparser | 72 | return optparser |
75 | 73 | ||
76 | def preoptparse(self, argv): | 74 | def postoptparse(self, options): |
77 | optparser = self.get_optparser() | ||
78 | |||
79 | largs = [] | ||
80 | rargs = [] | ||
81 | while argv: | ||
82 | arg = argv.pop(0) | ||
83 | |||
84 | if arg in ('-h', '--help'): | ||
85 | rargs.append(arg) | ||
86 | |||
87 | elif optparser.has_option(arg): | ||
88 | largs.append(arg) | ||
89 | |||
90 | if optparser.get_option(arg).takes_value(): | ||
91 | try: | ||
92 | largs.append(argv.pop(0)) | ||
93 | except IndexError: | ||
94 | raise errors.Usage("option %s requires arguments" % arg) | ||
95 | |||
96 | else: | ||
97 | if arg.startswith("--"): | ||
98 | if "=" in arg: | ||
99 | opt = arg.split("=")[0] | ||
100 | else: | ||
101 | opt = None | ||
102 | elif arg.startswith("-") and len(arg) > 2: | ||
103 | opt = arg[0:2] | ||
104 | else: | ||
105 | opt = None | ||
106 | |||
107 | if opt and optparser.has_option(opt): | ||
108 | largs.append(arg) | ||
109 | else: | ||
110 | rargs.append(arg) | ||
111 | |||
112 | return largs + rargs | ||
113 | |||
114 | def postoptparse(self): | ||
115 | abspath = lambda pth: os.path.abspath(os.path.expanduser(pth)) | 75 | abspath = lambda pth: os.path.abspath(os.path.expanduser(pth)) |
116 | 76 | ||
117 | if self.options.verbose: | 77 | if options.verbose: |
118 | msger.set_loglevel('verbose') | 78 | msger.set_loglevel('verbose') |
119 | if self.options.debug: | 79 | if options.debug: |
120 | msger.set_loglevel('debug') | 80 | msger.set_loglevel('debug') |
121 | 81 | ||
122 | if self.options.logfile: | 82 | if options.logfile: |
123 | logfile_abs_path = abspath(self.options.logfile) | 83 | logfile_abs_path = abspath(options.logfile) |
124 | if os.path.isdir(logfile_abs_path): | 84 | if os.path.isdir(logfile_abs_path): |
125 | raise errors.Usage("logfile's path %s should be file" | 85 | raise errors.Usage("logfile's path %s should be file" |
126 | % self.options.logfile) | 86 | % options.logfile) |
127 | if not os.path.exists(os.path.dirname(logfile_abs_path)): | 87 | if not os.path.exists(os.path.dirname(logfile_abs_path)): |
128 | os.makedirs(os.path.dirname(logfile_abs_path)) | 88 | os.makedirs(os.path.dirname(logfile_abs_path)) |
129 | msger.set_interactive(False) | 89 | msger.set_interactive(False) |
130 | msger.set_logfile(logfile_abs_path) | 90 | msger.set_logfile(logfile_abs_path) |
131 | configmgr.create['logfile'] = self.options.logfile | 91 | configmgr.create['logfile'] = options.logfile |
132 | 92 | ||
133 | if self.options.config: | 93 | if options.config: |
134 | configmgr.reset() | 94 | configmgr.reset() |
135 | configmgr._siteconf = self.options.config | 95 | configmgr._siteconf = options.config |
136 | 96 | ||
137 | if self.options.outdir is not None: | 97 | if options.outdir is not None: |
138 | configmgr.create['outdir'] = abspath(self.options.outdir) | 98 | configmgr.create['outdir'] = abspath(options.outdir) |
139 | 99 | ||
140 | cdir = 'outdir' | 100 | cdir = 'outdir' |
141 | if os.path.exists(configmgr.create[cdir]) \ | 101 | if os.path.exists(configmgr.create[cdir]) \ |
@@ -143,8 +103,8 @@ class Creator(cmdln.Cmdln): | |||
143 | msger.error('Invalid directory specified: %s' \ | 103 | msger.error('Invalid directory specified: %s' \ |
144 | % configmgr.create[cdir]) | 104 | % configmgr.create[cdir]) |
145 | 105 | ||
146 | if self.options.enabletmpfs: | 106 | if options.enabletmpfs: |
147 | configmgr.create['enabletmpfs'] = self.options.enabletmpfs | 107 | configmgr.create['enabletmpfs'] = options.enabletmpfs |
148 | 108 | ||
149 | def main(self, argv=None): | 109 | def main(self, argv=None): |
150 | if argv is None: | 110 | if argv is None: |
@@ -152,36 +112,13 @@ class Creator(cmdln.Cmdln): | |||
152 | else: | 112 | else: |
153 | argv = argv[:] # don't modify caller's list | 113 | argv = argv[:] # don't modify caller's list |
154 | 114 | ||
155 | self.optparser = self.get_optparser() | 115 | pname = argv[0] |
156 | if self.optparser: | 116 | if pname not in self._subcmds: |
157 | try: | 117 | msger.error('Unknown plugin: %s' % pname) |
158 | argv = self.preoptparse(argv) | ||
159 | self.options, args = self.optparser.parse_args(argv) | ||
160 | |||
161 | except cmdln.CmdlnUserError, ex: | ||
162 | msg = "%s: %s\nTry '%s help' for info.\n"\ | ||
163 | % (self.name, ex, self.name) | ||
164 | msger.error(msg) | ||
165 | |||
166 | except cmdln.StopOptionProcessing, ex: | ||
167 | return 0 | ||
168 | else: | ||
169 | # optparser=None means no process for opts | ||
170 | self.options, args = None, argv[1:] | ||
171 | |||
172 | if not args: | ||
173 | return self.emptyline() | ||
174 | |||
175 | self.postoptparse() | ||
176 | |||
177 | return self.cmd(args) | ||
178 | 118 | ||
179 | def precmd(self, argv): # check help before cmd | 119 | optparser = self.get_optparser() |
180 | 120 | options, args = optparser.parse_args(argv) | |
181 | if '-h' in argv or '?' in argv or '--help' in argv or 'help' in argv: | ||
182 | return argv | ||
183 | 121 | ||
184 | if len(argv) == 1: | 122 | self.postoptparse(options) |
185 | return ['help', argv[0]] | ||
186 | 123 | ||
187 | return argv | 124 | return self._subcmds[pname](options, *args[1:]) |
diff --git a/scripts/lib/wic/plugins/imager/direct_plugin.py b/scripts/lib/wic/plugins/imager/direct_plugin.py index eb17e8d7b7..e9672fe274 100644 --- a/scripts/lib/wic/plugins/imager/direct_plugin.py +++ b/scripts/lib/wic/plugins/imager/direct_plugin.py | |||
@@ -56,7 +56,7 @@ class DirectPlugin(ImagerPlugin): | |||
56 | return krootfs_dir | 56 | return krootfs_dir |
57 | 57 | ||
58 | @classmethod | 58 | @classmethod |
59 | def do_create(cls, subcmd, opts, *args): | 59 | def do_create(cls, opts, *args): |
60 | """ | 60 | """ |
61 | Create direct image, called from creator as 'direct' cmd | 61 | Create direct image, called from creator as 'direct' cmd |
62 | """ | 62 | """ |
diff --git a/scripts/lib/wic/utils/cmdln.py b/scripts/lib/wic/utils/cmdln.py deleted file mode 100644 index 47654b934f..0000000000 --- a/scripts/lib/wic/utils/cmdln.py +++ /dev/null | |||
@@ -1,1604 +0,0 @@ | |||
1 | #!/usr/bin/env python | ||
2 | # Copyright (c) 2002-2007 ActiveState Software Inc. | ||
3 | # License: MIT (see LICENSE.txt for license details) | ||
4 | # Author: Trent Mick | ||
5 | # Home: http://trentm.com/projects/cmdln/ | ||
6 | |||
7 | """An improvement on Python's standard cmd.py module. | ||
8 | |||
9 | As with cmd.py, this module provides "a simple framework for writing | ||
10 | line-oriented command intepreters." This module provides a 'RawCmdln' | ||
11 | class that fixes some design flaws in cmd.Cmd, making it more scalable | ||
12 | and nicer to use for good 'cvs'- or 'svn'-style command line interfaces | ||
13 | or simple shells. And it provides a 'Cmdln' class that add | ||
14 | optparse-based option processing. Basically you use it like this: | ||
15 | |||
16 | import cmdln | ||
17 | |||
18 | class MySVN(cmdln.Cmdln): | ||
19 | name = "svn" | ||
20 | |||
21 | @cmdln.alias('stat', 'st') | ||
22 | @cmdln.option('-v', '--verbose', action='store_true' | ||
23 | help='print verbose information') | ||
24 | def do_status(self, subcmd, opts, *paths): | ||
25 | print "handle 'svn status' command" | ||
26 | |||
27 | #... | ||
28 | |||
29 | if __name__ == "__main__": | ||
30 | shell = MySVN() | ||
31 | retval = shell.main() | ||
32 | sys.exit(retval) | ||
33 | |||
34 | See the README.txt or <http://trentm.com/projects/cmdln/> for more | ||
35 | details. | ||
36 | """ | ||
37 | |||
38 | __version_info__ = (1, 1, 2) | ||
39 | __version__ = '.'.join(map(str, __version_info__)) | ||
40 | |||
41 | import os | ||
42 | import sys | ||
43 | import re | ||
44 | import cmd | ||
45 | import optparse | ||
46 | import sys | ||
47 | |||
48 | |||
49 | |||
50 | |||
51 | #---- globals | ||
52 | |||
53 | LOOP_ALWAYS, LOOP_NEVER, LOOP_IF_EMPTY = range(3) | ||
54 | |||
55 | # An unspecified optional argument when None is a meaningful value. | ||
56 | _NOT_SPECIFIED = ("Not", "Specified") | ||
57 | |||
58 | # Pattern to match a TypeError message from a call that | ||
59 | # failed because of incorrect number of arguments (see | ||
60 | # Python/getargs.c). | ||
61 | _INCORRECT_NUM_ARGS_RE = re.compile( | ||
62 | r"(takes [\w ]+ )(\d+)( arguments? \()(\d+)( given\))") | ||
63 | |||
64 | |||
65 | |||
66 | #---- exceptions | ||
67 | |||
68 | class CmdlnError(Exception): | ||
69 | """A cmdln.py usage error.""" | ||
70 | def __init__(self, msg): | ||
71 | self.msg = msg | ||
72 | def __str__(self): | ||
73 | return self.msg | ||
74 | |||
75 | class CmdlnUserError(Exception): | ||
76 | """An error by a user of a cmdln-based tool/shell.""" | ||
77 | pass | ||
78 | |||
79 | |||
80 | |||
81 | #---- public methods and classes | ||
82 | |||
83 | def alias(*aliases): | ||
84 | """Decorator to add aliases for Cmdln.do_* command handlers. | ||
85 | |||
86 | Example: | ||
87 | class MyShell(cmdln.Cmdln): | ||
88 | @cmdln.alias("!", "sh") | ||
89 | def do_shell(self, argv): | ||
90 | #...implement 'shell' command | ||
91 | """ | ||
92 | def decorate(f): | ||
93 | if not hasattr(f, "aliases"): | ||
94 | f.aliases = [] | ||
95 | f.aliases += aliases | ||
96 | return f | ||
97 | return decorate | ||
98 | |||
99 | |||
100 | class RawCmdln(cmd.Cmd): | ||
101 | """An improved (on cmd.Cmd) framework for building multi-subcommand | ||
102 | scripts (think "svn" & "cvs") and simple shells (think "pdb" and | ||
103 | "gdb"). | ||
104 | |||
105 | A simple example: | ||
106 | |||
107 | import cmdln | ||
108 | |||
109 | class MySVN(cmdln.RawCmdln): | ||
110 | name = "svn" | ||
111 | |||
112 | @cmdln.aliases('stat', 'st') | ||
113 | def do_status(self, argv): | ||
114 | print "handle 'svn status' command" | ||
115 | |||
116 | if __name__ == "__main__": | ||
117 | shell = MySVN() | ||
118 | retval = shell.main() | ||
119 | sys.exit(retval) | ||
120 | |||
121 | See <http://trentm.com/projects/cmdln> for more information. | ||
122 | """ | ||
123 | name = None # if unset, defaults basename(sys.argv[0]) | ||
124 | prompt = None # if unset, defaults to self.name+"> " | ||
125 | version = None # if set, default top-level options include --version | ||
126 | |||
127 | # Default messages for some 'help' command error cases. | ||
128 | # They are interpolated with one arg: the command. | ||
129 | nohelp = "no help on '%s'" | ||
130 | unknowncmd = "unknown command: '%s'" | ||
131 | |||
132 | helpindent = '' # string with which to indent help output | ||
133 | |||
134 | def __init__(self, completekey='tab', | ||
135 | stdin=None, stdout=None, stderr=None): | ||
136 | """Cmdln(completekey='tab', stdin=None, stdout=None, stderr=None) | ||
137 | |||
138 | The optional argument 'completekey' is the readline name of a | ||
139 | completion key; it defaults to the Tab key. If completekey is | ||
140 | not None and the readline module is available, command completion | ||
141 | is done automatically. | ||
142 | |||
143 | The optional arguments 'stdin', 'stdout' and 'stderr' specify | ||
144 | alternate input, output and error output file objects; if not | ||
145 | specified, sys.* are used. | ||
146 | |||
147 | If 'stdout' but not 'stderr' is specified, stdout is used for | ||
148 | error output. This is to provide least surprise for users used | ||
149 | to only the 'stdin' and 'stdout' options with cmd.Cmd. | ||
150 | """ | ||
151 | import sys | ||
152 | if self.name is None: | ||
153 | self.name = os.path.basename(sys.argv[0]) | ||
154 | if self.prompt is None: | ||
155 | self.prompt = self.name+"> " | ||
156 | self._name_str = self._str(self.name) | ||
157 | self._prompt_str = self._str(self.prompt) | ||
158 | if stdin is not None: | ||
159 | self.stdin = stdin | ||
160 | else: | ||
161 | self.stdin = sys.stdin | ||
162 | if stdout is not None: | ||
163 | self.stdout = stdout | ||
164 | else: | ||
165 | self.stdout = sys.stdout | ||
166 | if stderr is not None: | ||
167 | self.stderr = stderr | ||
168 | elif stdout is not None: | ||
169 | self.stderr = stdout | ||
170 | else: | ||
171 | self.stderr = sys.stderr | ||
172 | self.cmdqueue = [] | ||
173 | self.completekey = completekey | ||
174 | self.cmdlooping = False | ||
175 | |||
176 | def get_optparser(self): | ||
177 | """Hook for subclasses to set the option parser for the | ||
178 | top-level command/shell. | ||
179 | |||
180 | This option parser is used retrieved and used by `.main()' to | ||
181 | handle top-level options. | ||
182 | |||
183 | The default implements a single '-h|--help' option. Sub-classes | ||
184 | can return None to have no options at the top-level. Typically | ||
185 | an instance of CmdlnOptionParser should be returned. | ||
186 | """ | ||
187 | version = (self.version is not None | ||
188 | and "%s %s" % (self._name_str, self.version) | ||
189 | or None) | ||
190 | return CmdlnOptionParser(self, version=version) | ||
191 | |||
192 | def postoptparse(self): | ||
193 | """Hook method executed just after `.main()' parses top-level | ||
194 | options. | ||
195 | |||
196 | When called `self.options' holds the results of the option parse. | ||
197 | """ | ||
198 | pass | ||
199 | |||
200 | def main(self, argv=None, loop=LOOP_NEVER): | ||
201 | """A possible mainline handler for a script, like so: | ||
202 | |||
203 | import cmdln | ||
204 | class MyCmd(cmdln.Cmdln): | ||
205 | name = "mycmd" | ||
206 | ... | ||
207 | |||
208 | if __name__ == "__main__": | ||
209 | MyCmd().main() | ||
210 | |||
211 | By default this will use sys.argv to issue a single command to | ||
212 | 'MyCmd', then exit. The 'loop' argument can be use to control | ||
213 | interactive shell behaviour. | ||
214 | |||
215 | Arguments: | ||
216 | "argv" (optional, default sys.argv) is the command to run. | ||
217 | It must be a sequence, where the first element is the | ||
218 | command name and subsequent elements the args for that | ||
219 | command. | ||
220 | "loop" (optional, default LOOP_NEVER) is a constant | ||
221 | indicating if a command loop should be started (i.e. an | ||
222 | interactive shell). Valid values (constants on this module): | ||
223 | LOOP_ALWAYS start loop and run "argv", if any | ||
224 | LOOP_NEVER run "argv" (or .emptyline()) and exit | ||
225 | LOOP_IF_EMPTY run "argv", if given, and exit; | ||
226 | otherwise, start loop | ||
227 | """ | ||
228 | if argv is None: | ||
229 | import sys | ||
230 | argv = sys.argv | ||
231 | else: | ||
232 | argv = argv[:] # don't modify caller's list | ||
233 | |||
234 | self.optparser = self.get_optparser() | ||
235 | if self.optparser: # i.e. optparser=None means don't process for opts | ||
236 | try: | ||
237 | self.options, args = self.optparser.parse_args(argv[1:]) | ||
238 | except CmdlnUserError, ex: | ||
239 | msg = "%s: %s\nTry '%s help' for info.\n"\ | ||
240 | % (self.name, ex, self.name) | ||
241 | self.stderr.write(self._str(msg)) | ||
242 | self.stderr.flush() | ||
243 | return 1 | ||
244 | except StopOptionProcessing, ex: | ||
245 | return 0 | ||
246 | else: | ||
247 | self.options, args = None, argv[1:] | ||
248 | self.postoptparse() | ||
249 | |||
250 | if loop == LOOP_ALWAYS: | ||
251 | if args: | ||
252 | self.cmdqueue.append(args) | ||
253 | return self.cmdloop() | ||
254 | elif loop == LOOP_NEVER: | ||
255 | if args: | ||
256 | return self.cmd(args) | ||
257 | else: | ||
258 | return self.emptyline() | ||
259 | elif loop == LOOP_IF_EMPTY: | ||
260 | if args: | ||
261 | return self.cmd(args) | ||
262 | else: | ||
263 | return self.cmdloop() | ||
264 | |||
265 | def cmd(self, argv): | ||
266 | """Run one command and exit. | ||
267 | |||
268 | "argv" is the arglist for the command to run. argv[0] is the | ||
269 | command to run. If argv is an empty list then the | ||
270 | 'emptyline' handler is run. | ||
271 | |||
272 | Returns the return value from the command handler. | ||
273 | """ | ||
274 | assert isinstance(argv, (list, tuple)), \ | ||
275 | "'argv' is not a sequence: %r" % argv | ||
276 | retval = None | ||
277 | try: | ||
278 | argv = self.precmd(argv) | ||
279 | retval = self.onecmd(argv) | ||
280 | self.postcmd(argv) | ||
281 | except: | ||
282 | if not self.cmdexc(argv): | ||
283 | raise | ||
284 | retval = 1 | ||
285 | return retval | ||
286 | |||
287 | def _str(self, s): | ||
288 | """Safely convert the given str/unicode to a string for printing.""" | ||
289 | try: | ||
290 | return str(s) | ||
291 | except UnicodeError: | ||
292 | #XXX What is the proper encoding to use here? 'utf-8' seems | ||
293 | # to work better than "getdefaultencoding" (usually | ||
294 | # 'ascii'), on OS X at least. | ||
295 | #import sys | ||
296 | #return s.encode(sys.getdefaultencoding(), "replace") | ||
297 | return s.encode("utf-8", "replace") | ||
298 | |||
299 | def cmdloop(self, intro=None): | ||
300 | """Repeatedly issue a prompt, accept input, parse into an argv, and | ||
301 | dispatch (via .precmd(), .onecmd() and .postcmd()), passing them | ||
302 | the argv. In other words, start a shell. | ||
303 | |||
304 | "intro" (optional) is a introductory message to print when | ||
305 | starting the command loop. This overrides the class | ||
306 | "intro" attribute, if any. | ||
307 | """ | ||
308 | self.cmdlooping = True | ||
309 | self.preloop() | ||
310 | if self.use_rawinput and self.completekey: | ||
311 | try: | ||
312 | import readline | ||
313 | self.old_completer = readline.get_completer() | ||
314 | readline.set_completer(self.complete) | ||
315 | readline.parse_and_bind(self.completekey+": complete") | ||
316 | except ImportError: | ||
317 | pass | ||
318 | try: | ||
319 | if intro is None: | ||
320 | intro = self.intro | ||
321 | if intro: | ||
322 | intro_str = self._str(intro) | ||
323 | self.stdout.write(intro_str+'\n') | ||
324 | self.stop = False | ||
325 | retval = None | ||
326 | while not self.stop: | ||
327 | if self.cmdqueue: | ||
328 | argv = self.cmdqueue.pop(0) | ||
329 | assert isinstance(argv, (list, tuple)), \ | ||
330 | "item on 'cmdqueue' is not a sequence: %r" % argv | ||
331 | else: | ||
332 | if self.use_rawinput: | ||
333 | try: | ||
334 | line = raw_input(self._prompt_str) | ||
335 | except EOFError: | ||
336 | line = 'EOF' | ||
337 | else: | ||
338 | self.stdout.write(self._prompt_str) | ||
339 | self.stdout.flush() | ||
340 | line = self.stdin.readline() | ||
341 | if not len(line): | ||
342 | line = 'EOF' | ||
343 | else: | ||
344 | line = line[:-1] # chop '\n' | ||
345 | argv = line2argv(line) | ||
346 | try: | ||
347 | argv = self.precmd(argv) | ||
348 | retval = self.onecmd(argv) | ||
349 | self.postcmd(argv) | ||
350 | except: | ||
351 | if not self.cmdexc(argv): | ||
352 | raise | ||
353 | retval = 1 | ||
354 | self.lastretval = retval | ||
355 | self.postloop() | ||
356 | finally: | ||
357 | if self.use_rawinput and self.completekey: | ||
358 | try: | ||
359 | import readline | ||
360 | readline.set_completer(self.old_completer) | ||
361 | except ImportError: | ||
362 | pass | ||
363 | self.cmdlooping = False | ||
364 | return retval | ||
365 | |||
366 | def precmd(self, argv): | ||
367 | """Hook method executed just before the command argv is | ||
368 | interpreted, but after the input prompt is generated and issued. | ||
369 | |||
370 | "argv" is the cmd to run. | ||
371 | |||
372 | Returns an argv to run (i.e. this method can modify the command | ||
373 | to run). | ||
374 | """ | ||
375 | return argv | ||
376 | |||
377 | def postcmd(self, argv): | ||
378 | """Hook method executed just after a command dispatch is finished. | ||
379 | |||
380 | "argv" is the command that was run. | ||
381 | """ | ||
382 | pass | ||
383 | |||
384 | def cmdexc(self, argv): | ||
385 | """Called if an exception is raised in any of precmd(), onecmd(), | ||
386 | or postcmd(). If True is returned, the exception is deemed to have | ||
387 | been dealt with. Otherwise, the exception is re-raised. | ||
388 | |||
389 | The default implementation handles CmdlnUserError's, which | ||
390 | typically correspond to user error in calling commands (as | ||
391 | opposed to programmer error in the design of the script using | ||
392 | cmdln.py). | ||
393 | """ | ||
394 | import sys | ||
395 | type, exc, traceback = sys.exc_info() | ||
396 | if isinstance(exc, CmdlnUserError): | ||
397 | msg = "%s %s: %s\nTry '%s help %s' for info.\n"\ | ||
398 | % (self.name, argv[0], exc, self.name, argv[0]) | ||
399 | self.stderr.write(self._str(msg)) | ||
400 | self.stderr.flush() | ||
401 | return True | ||
402 | |||
403 | def onecmd(self, argv): | ||
404 | if not argv: | ||
405 | return self.emptyline() | ||
406 | self.lastcmd = argv | ||
407 | cmdname = self._get_canonical_cmd_name(argv[0]) | ||
408 | if cmdname: | ||
409 | handler = self._get_cmd_handler(cmdname) | ||
410 | if handler: | ||
411 | return self._dispatch_cmd(handler, argv) | ||
412 | return self.default(argv) | ||
413 | |||
414 | def _dispatch_cmd(self, handler, argv): | ||
415 | return handler(argv) | ||
416 | |||
417 | def default(self, argv): | ||
418 | """Hook called to handle a command for which there is no handler. | ||
419 | |||
420 | "argv" is the command and arguments to run. | ||
421 | |||
422 | The default implementation writes and error message to stderr | ||
423 | and returns an error exit status. | ||
424 | |||
425 | Returns a numeric command exit status. | ||
426 | """ | ||
427 | errmsg = self._str(self.unknowncmd % (argv[0],)) | ||
428 | if self.cmdlooping: | ||
429 | self.stderr.write(errmsg+"\n") | ||
430 | else: | ||
431 | self.stderr.write("%s: %s\nTry '%s help' for info.\n" | ||
432 | % (self._name_str, errmsg, self._name_str)) | ||
433 | self.stderr.flush() | ||
434 | return 1 | ||
435 | |||
436 | def parseline(self, line): | ||
437 | # This is used by Cmd.complete (readline completer function) to | ||
438 | # massage the current line buffer before completion processing. | ||
439 | # We override to drop special '!' handling. | ||
440 | line = line.strip() | ||
441 | if not line: | ||
442 | return None, None, line | ||
443 | elif line[0] == '?': | ||
444 | line = 'help ' + line[1:] | ||
445 | i, n = 0, len(line) | ||
446 | while i < n and line[i] in self.identchars: | ||
447 | i = i+1 | ||
448 | cmd, arg = line[:i], line[i:].strip() | ||
449 | return cmd, arg, line | ||
450 | |||
451 | def helpdefault(self, cmd, known): | ||
452 | """Hook called to handle help on a command for which there is no | ||
453 | help handler. | ||
454 | |||
455 | "cmd" is the command name on which help was requested. | ||
456 | "known" is a boolean indicating if this command is known | ||
457 | (i.e. if there is a handler for it). | ||
458 | |||
459 | Returns a return code. | ||
460 | """ | ||
461 | if known: | ||
462 | msg = self._str(self.nohelp % (cmd,)) | ||
463 | if self.cmdlooping: | ||
464 | self.stderr.write(msg + '\n') | ||
465 | else: | ||
466 | self.stderr.write("%s: %s\n" % (self.name, msg)) | ||
467 | else: | ||
468 | msg = self.unknowncmd % (cmd,) | ||
469 | if self.cmdlooping: | ||
470 | self.stderr.write(msg + '\n') | ||
471 | else: | ||
472 | self.stderr.write("%s: %s\n" | ||
473 | "Try '%s help' for info.\n" | ||
474 | % (self.name, msg, self.name)) | ||
475 | self.stderr.flush() | ||
476 | return 1 | ||
477 | |||
478 | def do_help(self, argv): | ||
479 | """${cmd_name}: give detailed help on a specific sub-command | ||
480 | |||
481 | Usage: | ||
482 | ${name} help [COMMAND] | ||
483 | """ | ||
484 | if len(argv) > 1: # asking for help on a particular command | ||
485 | doc = None | ||
486 | cmdname = self._get_canonical_cmd_name(argv[1]) or argv[1] | ||
487 | if not cmdname: | ||
488 | return self.helpdefault(argv[1], False) | ||
489 | else: | ||
490 | helpfunc = getattr(self, "help_"+cmdname, None) | ||
491 | if helpfunc: | ||
492 | doc = helpfunc() | ||
493 | else: | ||
494 | handler = self._get_cmd_handler(cmdname) | ||
495 | if handler: | ||
496 | doc = handler.__doc__ | ||
497 | if doc is None: | ||
498 | return self.helpdefault(argv[1], handler != None) | ||
499 | else: # bare "help" command | ||
500 | doc = self.__class__.__doc__ # try class docstring | ||
501 | if doc is None: | ||
502 | # Try to provide some reasonable useful default help. | ||
503 | if self.cmdlooping: | ||
504 | prefix = "" | ||
505 | else: | ||
506 | prefix = self.name+' ' | ||
507 | doc = """Usage: | ||
508 | %sCOMMAND [ARGS...] | ||
509 | %shelp [COMMAND] | ||
510 | |||
511 | ${option_list} | ||
512 | ${command_list} | ||
513 | ${help_list} | ||
514 | """ % (prefix, prefix) | ||
515 | cmdname = None | ||
516 | |||
517 | if doc: # *do* have help content, massage and print that | ||
518 | doc = self._help_reindent(doc) | ||
519 | doc = self._help_preprocess(doc, cmdname) | ||
520 | doc = doc.rstrip() + '\n' # trim down trailing space | ||
521 | self.stdout.write(self._str(doc)) | ||
522 | self.stdout.flush() | ||
523 | do_help.aliases = ["?"] | ||
524 | |||
525 | def _help_reindent(self, help, indent=None): | ||
526 | """Hook to re-indent help strings before writing to stdout. | ||
527 | |||
528 | "help" is the help content to re-indent | ||
529 | "indent" is a string with which to indent each line of the | ||
530 | help content after normalizing. If unspecified or None | ||
531 | then the default is use: the 'self.helpindent' class | ||
532 | attribute. By default this is the empty string, i.e. | ||
533 | no indentation. | ||
534 | |||
535 | By default, all common leading whitespace is removed and then | ||
536 | the lot is indented by 'self.helpindent'. When calculating the | ||
537 | common leading whitespace the first line is ignored -- hence | ||
538 | help content for Conan can be written as follows and have the | ||
539 | expected indentation: | ||
540 | |||
541 | def do_crush(self, ...): | ||
542 | '''${cmd_name}: crush your enemies, see them driven before you... | ||
543 | |||
544 | c.f. Conan the Barbarian''' | ||
545 | """ | ||
546 | if indent is None: | ||
547 | indent = self.helpindent | ||
548 | lines = help.splitlines(0) | ||
549 | _dedentlines(lines, skip_first_line=True) | ||
550 | lines = [(indent+line).rstrip() for line in lines] | ||
551 | return '\n'.join(lines) | ||
552 | |||
553 | def _help_preprocess(self, help, cmdname): | ||
554 | """Hook to preprocess a help string before writing to stdout. | ||
555 | |||
556 | "help" is the help string to process. | ||
557 | "cmdname" is the canonical sub-command name for which help | ||
558 | is being given, or None if the help is not specific to a | ||
559 | command. | ||
560 | |||
561 | By default the following template variables are interpolated in | ||
562 | help content. (Note: these are similar to Python 2.4's | ||
563 | string.Template interpolation but not quite.) | ||
564 | |||
565 | ${name} | ||
566 | The tool's/shell's name, i.e. 'self.name'. | ||
567 | ${option_list} | ||
568 | A formatted table of options for this shell/tool. | ||
569 | ${command_list} | ||
570 | A formatted table of available sub-commands. | ||
571 | ${help_list} | ||
572 | A formatted table of additional help topics (i.e. 'help_*' | ||
573 | methods with no matching 'do_*' method). | ||
574 | ${cmd_name} | ||
575 | The name (and aliases) for this sub-command formatted as: | ||
576 | "NAME (ALIAS1, ALIAS2, ...)". | ||
577 | ${cmd_usage} | ||
578 | A formatted usage block inferred from the command function | ||
579 | signature. | ||
580 | ${cmd_option_list} | ||
581 | A formatted table of options for this sub-command. (This is | ||
582 | only available for commands using the optparse integration, | ||
583 | i.e. using @cmdln.option decorators or manually setting the | ||
584 | 'optparser' attribute on the 'do_*' method.) | ||
585 | |||
586 | Returns the processed help. | ||
587 | """ | ||
588 | preprocessors = { | ||
589 | "${name}": self._help_preprocess_name, | ||
590 | "${option_list}": self._help_preprocess_option_list, | ||
591 | "${command_list}": self._help_preprocess_command_list, | ||
592 | "${help_list}": self._help_preprocess_help_list, | ||
593 | "${cmd_name}": self._help_preprocess_cmd_name, | ||
594 | "${cmd_usage}": self._help_preprocess_cmd_usage, | ||
595 | "${cmd_option_list}": self._help_preprocess_cmd_option_list, | ||
596 | } | ||
597 | |||
598 | for marker, preprocessor in preprocessors.items(): | ||
599 | if marker in help: | ||
600 | help = preprocessor(help, cmdname) | ||
601 | return help | ||
602 | |||
603 | def _help_preprocess_name(self, help, cmdname=None): | ||
604 | return help.replace("${name}", self.name) | ||
605 | |||
606 | def _help_preprocess_option_list(self, help, cmdname=None): | ||
607 | marker = "${option_list}" | ||
608 | indent, indent_width = _get_indent(marker, help) | ||
609 | suffix = _get_trailing_whitespace(marker, help) | ||
610 | |||
611 | if self.optparser: | ||
612 | # Setup formatting options and format. | ||
613 | # - Indentation of 4 is better than optparse default of 2. | ||
614 | # C.f. Damian Conway's discussion of this in Perl Best | ||
615 | # Practices. | ||
616 | self.optparser.formatter.indent_increment = 4 | ||
617 | self.optparser.formatter.current_indent = indent_width | ||
618 | block = self.optparser.format_option_help() + '\n' | ||
619 | else: | ||
620 | block = "" | ||
621 | |||
622 | help = help.replace(indent+marker+suffix, block, 1) | ||
623 | return help | ||
624 | |||
625 | |||
626 | def _help_preprocess_command_list(self, help, cmdname=None): | ||
627 | marker = "${command_list}" | ||
628 | indent, indent_width = _get_indent(marker, help) | ||
629 | suffix = _get_trailing_whitespace(marker, help) | ||
630 | |||
631 | # Find any aliases for commands. | ||
632 | token2canonical = self._get_canonical_map() | ||
633 | aliases = {} | ||
634 | for token, cmdname in token2canonical.items(): | ||
635 | if token == cmdname: | ||
636 | continue | ||
637 | aliases.setdefault(cmdname, []).append(token) | ||
638 | |||
639 | # Get the list of (non-hidden) commands and their | ||
640 | # documentation, if any. | ||
641 | cmdnames = {} # use a dict to strip duplicates | ||
642 | for attr in self.get_names(): | ||
643 | if attr.startswith("do_"): | ||
644 | cmdnames[attr[3:]] = True | ||
645 | cmdnames = cmdnames.keys() | ||
646 | cmdnames.sort() | ||
647 | linedata = [] | ||
648 | for cmdname in cmdnames: | ||
649 | if aliases.get(cmdname): | ||
650 | a = aliases[cmdname] | ||
651 | a.sort() | ||
652 | cmdstr = "%s (%s)" % (cmdname, ", ".join(a)) | ||
653 | else: | ||
654 | cmdstr = cmdname | ||
655 | doc = None | ||
656 | try: | ||
657 | helpfunc = getattr(self, 'help_'+cmdname) | ||
658 | except AttributeError: | ||
659 | handler = self._get_cmd_handler(cmdname) | ||
660 | if handler: | ||
661 | doc = handler.__doc__ | ||
662 | else: | ||
663 | doc = helpfunc() | ||
664 | |||
665 | # Strip "${cmd_name}: " from the start of a command's doc. Best | ||
666 | # practice dictates that command help strings begin with this, but | ||
667 | # it isn't at all wanted for the command list. | ||
668 | to_strip = "${cmd_name}:" | ||
669 | if doc and doc.startswith(to_strip): | ||
670 | #log.debug("stripping %r from start of %s's help string", | ||
671 | # to_strip, cmdname) | ||
672 | doc = doc[len(to_strip):].lstrip() | ||
673 | linedata.append( (cmdstr, doc) ) | ||
674 | |||
675 | if linedata: | ||
676 | subindent = indent + ' '*4 | ||
677 | lines = _format_linedata(linedata, subindent, indent_width+4) | ||
678 | block = indent + "Commands:\n" \ | ||
679 | + '\n'.join(lines) + "\n\n" | ||
680 | help = help.replace(indent+marker+suffix, block, 1) | ||
681 | return help | ||
682 | |||
683 | def _gen_names_and_attrs(self): | ||
684 | # Inheritance says we have to look in class and | ||
685 | # base classes; order is not important. | ||
686 | names = [] | ||
687 | classes = [self.__class__] | ||
688 | while classes: | ||
689 | aclass = classes.pop(0) | ||
690 | if aclass.__bases__: | ||
691 | classes = classes + list(aclass.__bases__) | ||
692 | for name in dir(aclass): | ||
693 | yield (name, getattr(aclass, name)) | ||
694 | |||
695 | def _help_preprocess_help_list(self, help, cmdname=None): | ||
696 | marker = "${help_list}" | ||
697 | indent, indent_width = _get_indent(marker, help) | ||
698 | suffix = _get_trailing_whitespace(marker, help) | ||
699 | |||
700 | # Determine the additional help topics, if any. | ||
701 | helpnames = {} | ||
702 | token2cmdname = self._get_canonical_map() | ||
703 | for attrname, attr in self._gen_names_and_attrs(): | ||
704 | if not attrname.startswith("help_"): | ||
705 | continue | ||
706 | helpname = attrname[5:] | ||
707 | if helpname not in token2cmdname: | ||
708 | helpnames[helpname] = attr | ||
709 | |||
710 | if helpnames: | ||
711 | linedata = [(n, a.__doc__ or "") for n, a in helpnames.items()] | ||
712 | linedata.sort() | ||
713 | |||
714 | subindent = indent + ' '*4 | ||
715 | lines = _format_linedata(linedata, subindent, indent_width+4) | ||
716 | block = (indent | ||
717 | + "Additional help topics (run `%s help TOPIC'):\n" % self.name | ||
718 | + '\n'.join(lines) | ||
719 | + "\n\n") | ||
720 | else: | ||
721 | block = '' | ||
722 | help = help.replace(indent+marker+suffix, block, 1) | ||
723 | return help | ||
724 | |||
725 | def _help_preprocess_cmd_name(self, help, cmdname=None): | ||
726 | marker = "${cmd_name}" | ||
727 | handler = self._get_cmd_handler(cmdname) | ||
728 | if not handler: | ||
729 | raise CmdlnError("cannot preprocess '%s' into help string: " | ||
730 | "could not find command handler for %r" | ||
731 | % (marker, cmdname)) | ||
732 | s = cmdname | ||
733 | if hasattr(handler, "aliases"): | ||
734 | s += " (%s)" % (", ".join(handler.aliases)) | ||
735 | help = help.replace(marker, s) | ||
736 | return help | ||
737 | |||
738 | #TODO: this only makes sense as part of the Cmdln class. | ||
739 | # Add hooks to add help preprocessing template vars and put | ||
740 | # this one on that class. | ||
741 | def _help_preprocess_cmd_usage(self, help, cmdname=None): | ||
742 | marker = "${cmd_usage}" | ||
743 | handler = self._get_cmd_handler(cmdname) | ||
744 | if not handler: | ||
745 | raise CmdlnError("cannot preprocess '%s' into help string: " | ||
746 | "could not find command handler for %r" | ||
747 | % (marker, cmdname)) | ||
748 | indent, indent_width = _get_indent(marker, help) | ||
749 | suffix = _get_trailing_whitespace(marker, help) | ||
750 | |||
751 | # Extract the introspection bits we need. | ||
752 | func = handler.im_func | ||
753 | if func.func_defaults: | ||
754 | func_defaults = list(func.func_defaults) | ||
755 | else: | ||
756 | func_defaults = [] | ||
757 | co_argcount = func.func_code.co_argcount | ||
758 | co_varnames = func.func_code.co_varnames | ||
759 | co_flags = func.func_code.co_flags | ||
760 | CO_FLAGS_ARGS = 4 | ||
761 | CO_FLAGS_KWARGS = 8 | ||
762 | |||
763 | # Adjust argcount for possible *args and **kwargs arguments. | ||
764 | argcount = co_argcount | ||
765 | if co_flags & CO_FLAGS_ARGS: | ||
766 | argcount += 1 | ||
767 | if co_flags & CO_FLAGS_KWARGS: | ||
768 | argcount += 1 | ||
769 | |||
770 | # Determine the usage string. | ||
771 | usage = "%s %s" % (self.name, cmdname) | ||
772 | if argcount <= 2: # handler ::= do_FOO(self, argv) | ||
773 | usage += " [ARGS...]" | ||
774 | elif argcount >= 3: # handler ::= do_FOO(self, subcmd, opts, ...) | ||
775 | argnames = list(co_varnames[3:argcount]) | ||
776 | tail = "" | ||
777 | if co_flags & CO_FLAGS_KWARGS: | ||
778 | name = argnames.pop(-1) | ||
779 | import warnings | ||
780 | # There is no generally accepted mechanism for passing | ||
781 | # keyword arguments from the command line. Could | ||
782 | # *perhaps* consider: arg=value arg2=value2 ... | ||
783 | warnings.warn("argument '**%s' on '%s.%s' command " | ||
784 | "handler will never get values" | ||
785 | % (name, self.__class__.__name__, | ||
786 | func.func_name)) | ||
787 | if co_flags & CO_FLAGS_ARGS: | ||
788 | name = argnames.pop(-1) | ||
789 | tail = "[%s...]" % name.upper() | ||
790 | while func_defaults: | ||
791 | func_defaults.pop(-1) | ||
792 | name = argnames.pop(-1) | ||
793 | tail = "[%s%s%s]" % (name.upper(), (tail and ' ' or ''), tail) | ||
794 | while argnames: | ||
795 | name = argnames.pop(-1) | ||
796 | tail = "%s %s" % (name.upper(), tail) | ||
797 | usage += ' ' + tail | ||
798 | |||
799 | block_lines = [ | ||
800 | self.helpindent + "Usage:", | ||
801 | self.helpindent + ' '*4 + usage | ||
802 | ] | ||
803 | block = '\n'.join(block_lines) + '\n\n' | ||
804 | |||
805 | help = help.replace(indent+marker+suffix, block, 1) | ||
806 | return help | ||
807 | |||
808 | #TODO: this only makes sense as part of the Cmdln class. | ||
809 | # Add hooks to add help preprocessing template vars and put | ||
810 | # this one on that class. | ||
811 | def _help_preprocess_cmd_option_list(self, help, cmdname=None): | ||
812 | marker = "${cmd_option_list}" | ||
813 | handler = self._get_cmd_handler(cmdname) | ||
814 | if not handler: | ||
815 | raise CmdlnError("cannot preprocess '%s' into help string: " | ||
816 | "could not find command handler for %r" | ||
817 | % (marker, cmdname)) | ||
818 | indent, indent_width = _get_indent(marker, help) | ||
819 | suffix = _get_trailing_whitespace(marker, help) | ||
820 | if hasattr(handler, "optparser"): | ||
821 | # Setup formatting options and format. | ||
822 | # - Indentation of 4 is better than optparse default of 2. | ||
823 | # C.f. Damian Conway's discussion of this in Perl Best | ||
824 | # Practices. | ||
825 | handler.optparser.formatter.indent_increment = 4 | ||
826 | handler.optparser.formatter.current_indent = indent_width | ||
827 | block = handler.optparser.format_option_help() + '\n' | ||
828 | else: | ||
829 | block = "" | ||
830 | |||
831 | help = help.replace(indent+marker+suffix, block, 1) | ||
832 | return help | ||
833 | |||
834 | def _get_canonical_cmd_name(self, token): | ||
835 | map = self._get_canonical_map() | ||
836 | return map.get(token, None) | ||
837 | |||
838 | def _get_canonical_map(self): | ||
839 | """Return a mapping of available command names and aliases to | ||
840 | their canonical command name. | ||
841 | """ | ||
842 | cacheattr = "_token2canonical" | ||
843 | if not hasattr(self, cacheattr): | ||
844 | # Get the list of commands and their aliases, if any. | ||
845 | token2canonical = {} | ||
846 | cmd2funcname = {} # use a dict to strip duplicates | ||
847 | for attr in self.get_names(): | ||
848 | if attr.startswith("do_"): | ||
849 | cmdname = attr[3:] | ||
850 | elif attr.startswith("_do_"): | ||
851 | cmdname = attr[4:] | ||
852 | else: | ||
853 | continue | ||
854 | cmd2funcname[cmdname] = attr | ||
855 | token2canonical[cmdname] = cmdname | ||
856 | for cmdname, funcname in cmd2funcname.items(): # add aliases | ||
857 | func = getattr(self, funcname) | ||
858 | aliases = getattr(func, "aliases", []) | ||
859 | for alias in aliases: | ||
860 | if alias in cmd2funcname: | ||
861 | import warnings | ||
862 | warnings.warn("'%s' alias for '%s' command conflicts " | ||
863 | "with '%s' handler" | ||
864 | % (alias, cmdname, cmd2funcname[alias])) | ||
865 | continue | ||
866 | token2canonical[alias] = cmdname | ||
867 | setattr(self, cacheattr, token2canonical) | ||
868 | return getattr(self, cacheattr) | ||
869 | |||
870 | def _get_cmd_handler(self, cmdname): | ||
871 | handler = None | ||
872 | try: | ||
873 | handler = getattr(self, 'do_' + cmdname) | ||
874 | except AttributeError: | ||
875 | try: | ||
876 | # Private command handlers begin with "_do_". | ||
877 | handler = getattr(self, '_do_' + cmdname) | ||
878 | except AttributeError: | ||
879 | pass | ||
880 | return handler | ||
881 | |||
882 | def _do_EOF(self, argv): | ||
883 | # Default EOF handler | ||
884 | # Note: an actual EOF is redirected to this command. | ||
885 | #TODO: separate name for this. Currently it is available from | ||
886 | # command-line. Is that okay? | ||
887 | self.stdout.write('\n') | ||
888 | self.stdout.flush() | ||
889 | self.stop = True | ||
890 | |||
891 | def emptyline(self): | ||
892 | # Different from cmd.Cmd: don't repeat the last command for an | ||
893 | # emptyline. | ||
894 | if self.cmdlooping: | ||
895 | pass | ||
896 | else: | ||
897 | return self.do_help(["help"]) | ||
898 | |||
899 | |||
900 | #---- optparse.py extension to fix (IMO) some deficiencies | ||
901 | # | ||
902 | # See the class _OptionParserEx docstring for details. | ||
903 | # | ||
904 | |||
905 | class StopOptionProcessing(Exception): | ||
906 | """Indicate that option *and argument* processing should stop | ||
907 | cleanly. This is not an error condition. It is similar in spirit to | ||
908 | StopIteration. This is raised by _OptionParserEx's default "help" | ||
909 | and "version" option actions and can be raised by custom option | ||
910 | callbacks too. | ||
911 | |||
912 | Hence the typical CmdlnOptionParser (a subclass of _OptionParserEx) | ||
913 | usage is: | ||
914 | |||
915 | parser = CmdlnOptionParser(mycmd) | ||
916 | parser.add_option("-f", "--force", dest="force") | ||
917 | ... | ||
918 | try: | ||
919 | opts, args = parser.parse_args() | ||
920 | except StopOptionProcessing: | ||
921 | # normal termination, "--help" was probably given | ||
922 | sys.exit(0) | ||
923 | """ | ||
924 | |||
925 | class _OptionParserEx(optparse.OptionParser): | ||
926 | """An optparse.OptionParser that uses exceptions instead of sys.exit. | ||
927 | |||
928 | This class is an extension of optparse.OptionParser that differs | ||
929 | as follows: | ||
930 | - Correct (IMO) the default OptionParser error handling to never | ||
931 | sys.exit(). Instead OptParseError exceptions are passed through. | ||
932 | - Add the StopOptionProcessing exception (a la StopIteration) to | ||
933 | indicate normal termination of option processing. | ||
934 | See StopOptionProcessing's docstring for details. | ||
935 | |||
936 | I'd also like to see the following in the core optparse.py, perhaps | ||
937 | as a RawOptionParser which would serve as a base class for the more | ||
938 | generally used OptionParser (that works as current): | ||
939 | - Remove the implicit addition of the -h|--help and --version | ||
940 | options. They can get in the way (e.g. if want '-?' and '-V' for | ||
941 | these as well) and it is not hard to do: | ||
942 | optparser.add_option("-h", "--help", action="help") | ||
943 | optparser.add_option("--version", action="version") | ||
944 | These are good practices, just not valid defaults if they can | ||
945 | get in the way. | ||
946 | """ | ||
947 | def error(self, msg): | ||
948 | raise optparse.OptParseError(msg) | ||
949 | |||
950 | def exit(self, status=0, msg=None): | ||
951 | if status == 0: | ||
952 | raise StopOptionProcessing(msg) | ||
953 | else: | ||
954 | #TODO: don't lose status info here | ||
955 | raise optparse.OptParseError(msg) | ||
956 | |||
957 | |||
958 | |||
959 | #---- optparse.py-based option processing support | ||
960 | |||
961 | class CmdlnOptionParser(_OptionParserEx): | ||
962 | """An optparse.OptionParser class more appropriate for top-level | ||
963 | Cmdln options. For parsing of sub-command options, see | ||
964 | SubCmdOptionParser. | ||
965 | |||
966 | Changes: | ||
967 | - disable_interspersed_args() by default, because a Cmdln instance | ||
968 | has sub-commands which may themselves have options. | ||
969 | - Redirect print_help() to the Cmdln.do_help() which is better | ||
970 | equiped to handle the "help" action. | ||
971 | - error() will raise a CmdlnUserError: OptionParse.error() is meant | ||
972 | to be called for user errors. Raising a well-known error here can | ||
973 | make error handling clearer. | ||
974 | - Also see the changes in _OptionParserEx. | ||
975 | """ | ||
976 | def __init__(self, cmdln, **kwargs): | ||
977 | self.cmdln = cmdln | ||
978 | kwargs["prog"] = self.cmdln.name | ||
979 | _OptionParserEx.__init__(self, **kwargs) | ||
980 | self.disable_interspersed_args() | ||
981 | |||
982 | def print_help(self, file=None): | ||
983 | self.cmdln.onecmd(["help"]) | ||
984 | |||
985 | def error(self, msg): | ||
986 | raise CmdlnUserError(msg) | ||
987 | |||
988 | |||
989 | class SubCmdOptionParser(_OptionParserEx): | ||
990 | def set_cmdln_info(self, cmdln, subcmd): | ||
991 | """Called by Cmdln to pass relevant info about itself needed | ||
992 | for print_help(). | ||
993 | """ | ||
994 | self.cmdln = cmdln | ||
995 | self.subcmd = subcmd | ||
996 | |||
997 | def print_help(self, file=None): | ||
998 | self.cmdln.onecmd(["help", self.subcmd]) | ||
999 | |||
1000 | def error(self, msg): | ||
1001 | raise CmdlnUserError(msg) | ||
1002 | |||
1003 | |||
1004 | def option(*args, **kwargs): | ||
1005 | """Decorator to add an option to the optparser argument of a Cmdln | ||
1006 | subcommand. | ||
1007 | |||
1008 | Example: | ||
1009 | class MyShell(cmdln.Cmdln): | ||
1010 | @cmdln.option("-f", "--force", help="force removal") | ||
1011 | def do_remove(self, subcmd, opts, *args): | ||
1012 | #... | ||
1013 | """ | ||
1014 | #XXX Is there a possible optimization for many options to not have a | ||
1015 | # large stack depth here? | ||
1016 | def decorate(f): | ||
1017 | if not hasattr(f, "optparser"): | ||
1018 | f.optparser = SubCmdOptionParser() | ||
1019 | f.optparser.add_option(*args, **kwargs) | ||
1020 | return f | ||
1021 | return decorate | ||
1022 | |||
1023 | |||
1024 | class Cmdln(RawCmdln): | ||
1025 | """An improved (on cmd.Cmd) framework for building multi-subcommand | ||
1026 | scripts (think "svn" & "cvs") and simple shells (think "pdb" and | ||
1027 | "gdb"). | ||
1028 | |||
1029 | A simple example: | ||
1030 | |||
1031 | import cmdln | ||
1032 | |||
1033 | class MySVN(cmdln.Cmdln): | ||
1034 | name = "svn" | ||
1035 | |||
1036 | @cmdln.aliases('stat', 'st') | ||
1037 | @cmdln.option('-v', '--verbose', action='store_true' | ||
1038 | help='print verbose information') | ||
1039 | def do_status(self, subcmd, opts, *paths): | ||
1040 | print "handle 'svn status' command" | ||
1041 | |||
1042 | #... | ||
1043 | |||
1044 | if __name__ == "__main__": | ||
1045 | shell = MySVN() | ||
1046 | retval = shell.main() | ||
1047 | sys.exit(retval) | ||
1048 | |||
1049 | 'Cmdln' extends 'RawCmdln' by providing optparse option processing | ||
1050 | integration. See this class' _dispatch_cmd() docstring and | ||
1051 | <http://trentm.com/projects/cmdln> for more information. | ||
1052 | """ | ||
1053 | def _dispatch_cmd(self, handler, argv): | ||
1054 | """Introspect sub-command handler signature to determine how to | ||
1055 | dispatch the command. The raw handler provided by the base | ||
1056 | 'RawCmdln' class is still supported: | ||
1057 | |||
1058 | def do_foo(self, argv): | ||
1059 | # 'argv' is the vector of command line args, argv[0] is | ||
1060 | # the command name itself (i.e. "foo" or an alias) | ||
1061 | pass | ||
1062 | |||
1063 | In addition, if the handler has more than 2 arguments option | ||
1064 | processing is automatically done (using optparse): | ||
1065 | |||
1066 | @cmdln.option('-v', '--verbose', action='store_true') | ||
1067 | def do_bar(self, subcmd, opts, *args): | ||
1068 | # subcmd = <"bar" or an alias> | ||
1069 | # opts = <an optparse.Values instance> | ||
1070 | if opts.verbose: | ||
1071 | print "lots of debugging output..." | ||
1072 | # args = <tuple of arguments> | ||
1073 | for arg in args: | ||
1074 | bar(arg) | ||
1075 | |||
1076 | TODO: explain that "*args" can be other signatures as well. | ||
1077 | |||
1078 | The `cmdln.option` decorator corresponds to an `add_option()` | ||
1079 | method call on an `optparse.OptionParser` instance. | ||
1080 | |||
1081 | You can declare a specific number of arguments: | ||
1082 | |||
1083 | @cmdln.option('-v', '--verbose', action='store_true') | ||
1084 | def do_bar2(self, subcmd, opts, bar_one, bar_two): | ||
1085 | #... | ||
1086 | |||
1087 | and an appropriate error message will be raised/printed if the | ||
1088 | command is called with a different number of args. | ||
1089 | """ | ||
1090 | co_argcount = handler.im_func.func_code.co_argcount | ||
1091 | if co_argcount == 2: # handler ::= do_foo(self, argv) | ||
1092 | return handler(argv) | ||
1093 | elif co_argcount >= 3: # handler ::= do_foo(self, subcmd, opts, ...) | ||
1094 | try: | ||
1095 | optparser = handler.optparser | ||
1096 | except AttributeError: | ||
1097 | optparser = handler.im_func.optparser = SubCmdOptionParser() | ||
1098 | assert isinstance(optparser, SubCmdOptionParser) | ||
1099 | optparser.set_cmdln_info(self, argv[0]) | ||
1100 | try: | ||
1101 | opts, args = optparser.parse_args(argv[1:]) | ||
1102 | except StopOptionProcessing: | ||
1103 | #TODO: this doesn't really fly for a replacement of | ||
1104 | # optparse.py behaviour, does it? | ||
1105 | return 0 # Normal command termination | ||
1106 | |||
1107 | try: | ||
1108 | return handler(argv[0], opts, *args) | ||
1109 | except TypeError, ex: | ||
1110 | # Some TypeError's are user errors: | ||
1111 | # do_foo() takes at least 4 arguments (3 given) | ||
1112 | # do_foo() takes at most 5 arguments (6 given) | ||
1113 | # do_foo() takes exactly 5 arguments (6 given) | ||
1114 | # Raise CmdlnUserError for these with a suitably | ||
1115 | # massaged error message. | ||
1116 | import sys | ||
1117 | tb = sys.exc_info()[2] # the traceback object | ||
1118 | if tb.tb_next is not None: | ||
1119 | # If the traceback is more than one level deep, then the | ||
1120 | # TypeError do *not* happen on the "handler(...)" call | ||
1121 | # above. In that we don't want to handle it specially | ||
1122 | # here: it would falsely mask deeper code errors. | ||
1123 | raise | ||
1124 | msg = ex.args[0] | ||
1125 | match = _INCORRECT_NUM_ARGS_RE.search(msg) | ||
1126 | if match: | ||
1127 | msg = list(match.groups()) | ||
1128 | msg[1] = int(msg[1]) - 3 | ||
1129 | if msg[1] == 1: | ||
1130 | msg[2] = msg[2].replace("arguments", "argument") | ||
1131 | msg[3] = int(msg[3]) - 3 | ||
1132 | msg = ''.join(map(str, msg)) | ||
1133 | raise CmdlnUserError(msg) | ||
1134 | else: | ||
1135 | raise | ||
1136 | else: | ||
1137 | raise CmdlnError("incorrect argcount for %s(): takes %d, must " | ||
1138 | "take 2 for 'argv' signature or 3+ for 'opts' " | ||
1139 | "signature" % (handler.__name__, co_argcount)) | ||
1140 | |||
1141 | |||
1142 | |||
1143 | #---- internal support functions | ||
1144 | |||
1145 | def _format_linedata(linedata, indent, indent_width): | ||
1146 | """Format specific linedata into a pleasant layout. | ||
1147 | |||
1148 | "linedata" is a list of 2-tuples of the form: | ||
1149 | (<item-display-string>, <item-docstring>) | ||
1150 | "indent" is a string to use for one level of indentation | ||
1151 | "indent_width" is a number of columns by which the | ||
1152 | formatted data will be indented when printed. | ||
1153 | |||
1154 | The <item-display-string> column is held to 15 columns. | ||
1155 | """ | ||
1156 | lines = [] | ||
1157 | WIDTH = 78 - indent_width | ||
1158 | SPACING = 2 | ||
1159 | NAME_WIDTH_LOWER_BOUND = 13 | ||
1160 | NAME_WIDTH_UPPER_BOUND = 16 | ||
1161 | NAME_WIDTH = max([len(s) for s,d in linedata]) | ||
1162 | if NAME_WIDTH < NAME_WIDTH_LOWER_BOUND: | ||
1163 | NAME_WIDTH = NAME_WIDTH_LOWER_BOUND | ||
1164 | else: | ||
1165 | NAME_WIDTH = NAME_WIDTH_UPPER_BOUND | ||
1166 | |||
1167 | DOC_WIDTH = WIDTH - NAME_WIDTH - SPACING | ||
1168 | for namestr, doc in linedata: | ||
1169 | line = indent + namestr | ||
1170 | if len(namestr) <= NAME_WIDTH: | ||
1171 | line += ' ' * (NAME_WIDTH + SPACING - len(namestr)) | ||
1172 | else: | ||
1173 | lines.append(line) | ||
1174 | line = indent + ' ' * (NAME_WIDTH + SPACING) | ||
1175 | line += _summarize_doc(doc, DOC_WIDTH) | ||
1176 | lines.append(line.rstrip()) | ||
1177 | return lines | ||
1178 | |||
1179 | def _summarize_doc(doc, length=60): | ||
1180 | r"""Parse out a short one line summary from the given doclines. | ||
1181 | |||
1182 | "doc" is the doc string to summarize. | ||
1183 | "length" is the max length for the summary | ||
1184 | |||
1185 | >>> _summarize_doc("this function does this") | ||
1186 | 'this function does this' | ||
1187 | >>> _summarize_doc("this function does this", 10) | ||
1188 | 'this fu...' | ||
1189 | >>> _summarize_doc("this function does this\nand that") | ||
1190 | 'this function does this and that' | ||
1191 | >>> _summarize_doc("this function does this\n\nand that") | ||
1192 | 'this function does this' | ||
1193 | """ | ||
1194 | import re | ||
1195 | if doc is None: | ||
1196 | return "" | ||
1197 | assert length > 3, "length <= 3 is absurdly short for a doc summary" | ||
1198 | doclines = doc.strip().splitlines(0) | ||
1199 | if not doclines: | ||
1200 | return "" | ||
1201 | |||
1202 | summlines = [] | ||
1203 | for i, line in enumerate(doclines): | ||
1204 | stripped = line.strip() | ||
1205 | if not stripped: | ||
1206 | break | ||
1207 | summlines.append(stripped) | ||
1208 | if len(''.join(summlines)) >= length: | ||
1209 | break | ||
1210 | |||
1211 | summary = ' '.join(summlines) | ||
1212 | if len(summary) > length: | ||
1213 | summary = summary[:length-3] + "..." | ||
1214 | return summary | ||
1215 | |||
1216 | |||
1217 | def line2argv(line): | ||
1218 | r"""Parse the given line into an argument vector. | ||
1219 | |||
1220 | "line" is the line of input to parse. | ||
1221 | |||
1222 | This may get niggly when dealing with quoting and escaping. The | ||
1223 | current state of this parsing may not be completely thorough/correct | ||
1224 | in this respect. | ||
1225 | |||
1226 | >>> from cmdln import line2argv | ||
1227 | >>> line2argv("foo") | ||
1228 | ['foo'] | ||
1229 | >>> line2argv("foo bar") | ||
1230 | ['foo', 'bar'] | ||
1231 | >>> line2argv("foo bar ") | ||
1232 | ['foo', 'bar'] | ||
1233 | >>> line2argv(" foo bar") | ||
1234 | ['foo', 'bar'] | ||
1235 | |||
1236 | Quote handling: | ||
1237 | |||
1238 | >>> line2argv("'foo bar'") | ||
1239 | ['foo bar'] | ||
1240 | >>> line2argv('"foo bar"') | ||
1241 | ['foo bar'] | ||
1242 | >>> line2argv(r'"foo\"bar"') | ||
1243 | ['foo"bar'] | ||
1244 | >>> line2argv("'foo bar' spam") | ||
1245 | ['foo bar', 'spam'] | ||
1246 | >>> line2argv("'foo 'bar spam") | ||
1247 | ['foo bar', 'spam'] | ||
1248 | |||
1249 | >>> line2argv('some\tsimple\ttests') | ||
1250 | ['some', 'simple', 'tests'] | ||
1251 | >>> line2argv('a "more complex" test') | ||
1252 | ['a', 'more complex', 'test'] | ||
1253 | >>> line2argv('a more="complex test of " quotes') | ||
1254 | ['a', 'more=complex test of ', 'quotes'] | ||
1255 | >>> line2argv('a more" complex test of " quotes') | ||
1256 | ['a', 'more complex test of ', 'quotes'] | ||
1257 | >>> line2argv('an "embedded \\"quote\\""') | ||
1258 | ['an', 'embedded "quote"'] | ||
1259 | |||
1260 | # Komodo bug 48027 | ||
1261 | >>> line2argv('foo bar C:\\') | ||
1262 | ['foo', 'bar', 'C:\\'] | ||
1263 | |||
1264 | # Komodo change 127581 | ||
1265 | >>> line2argv(r'"\test\slash" "foo bar" "foo\"bar"') | ||
1266 | ['\\test\\slash', 'foo bar', 'foo"bar'] | ||
1267 | |||
1268 | # Komodo change 127629 | ||
1269 | >>> if sys.platform == "win32": | ||
1270 | ... line2argv(r'\foo\bar') == ['\\foo\\bar'] | ||
1271 | ... line2argv(r'\\foo\\bar') == ['\\\\foo\\\\bar'] | ||
1272 | ... line2argv('"foo') == ['foo'] | ||
1273 | ... else: | ||
1274 | ... line2argv(r'\foo\bar') == ['foobar'] | ||
1275 | ... line2argv(r'\\foo\\bar') == ['\\foo\\bar'] | ||
1276 | ... try: | ||
1277 | ... line2argv('"foo') | ||
1278 | ... except ValueError, ex: | ||
1279 | ... "not terminated" in str(ex) | ||
1280 | True | ||
1281 | True | ||
1282 | True | ||
1283 | """ | ||
1284 | import string | ||
1285 | line = line.strip() | ||
1286 | argv = [] | ||
1287 | state = "default" | ||
1288 | arg = None # the current argument being parsed | ||
1289 | i = -1 | ||
1290 | while 1: | ||
1291 | i += 1 | ||
1292 | if i >= len(line): | ||
1293 | break | ||
1294 | ch = line[i] | ||
1295 | |||
1296 | if ch == "\\" and i+1 < len(line): | ||
1297 | # escaped char always added to arg, regardless of state | ||
1298 | if arg is None: | ||
1299 | arg = "" | ||
1300 | if (sys.platform == "win32" | ||
1301 | or state in ("double-quoted", "single-quoted") | ||
1302 | ) and line[i+1] not in tuple('"\''): | ||
1303 | arg += ch | ||
1304 | i += 1 | ||
1305 | arg += line[i] | ||
1306 | continue | ||
1307 | |||
1308 | if state == "single-quoted": | ||
1309 | if ch == "'": | ||
1310 | state = "default" | ||
1311 | else: | ||
1312 | arg += ch | ||
1313 | elif state == "double-quoted": | ||
1314 | if ch == '"': | ||
1315 | state = "default" | ||
1316 | else: | ||
1317 | arg += ch | ||
1318 | elif state == "default": | ||
1319 | if ch == '"': | ||
1320 | if arg is None: | ||
1321 | arg = "" | ||
1322 | state = "double-quoted" | ||
1323 | elif ch == "'": | ||
1324 | if arg is None: | ||
1325 | arg = "" | ||
1326 | state = "single-quoted" | ||
1327 | elif ch in string.whitespace: | ||
1328 | if arg is not None: | ||
1329 | argv.append(arg) | ||
1330 | arg = None | ||
1331 | else: | ||
1332 | if arg is None: | ||
1333 | arg = "" | ||
1334 | arg += ch | ||
1335 | if arg is not None: | ||
1336 | argv.append(arg) | ||
1337 | if not sys.platform == "win32" and state != "default": | ||
1338 | raise ValueError("command line is not terminated: unfinished %s " | ||
1339 | "segment" % state) | ||
1340 | return argv | ||
1341 | |||
1342 | |||
1343 | def argv2line(argv): | ||
1344 | r"""Put together the given argument vector into a command line. | ||
1345 | |||
1346 | "argv" is the argument vector to process. | ||
1347 | |||
1348 | >>> from cmdln import argv2line | ||
1349 | >>> argv2line(['foo']) | ||
1350 | 'foo' | ||
1351 | >>> argv2line(['foo', 'bar']) | ||
1352 | 'foo bar' | ||
1353 | >>> argv2line(['foo', 'bar baz']) | ||
1354 | 'foo "bar baz"' | ||
1355 | >>> argv2line(['foo"bar']) | ||
1356 | 'foo"bar' | ||
1357 | >>> print argv2line(['foo" bar']) | ||
1358 | 'foo" bar' | ||
1359 | >>> print argv2line(["foo' bar"]) | ||
1360 | "foo' bar" | ||
1361 | >>> argv2line(["foo'bar"]) | ||
1362 | "foo'bar" | ||
1363 | """ | ||
1364 | escapedArgs = [] | ||
1365 | for arg in argv: | ||
1366 | if ' ' in arg and '"' not in arg: | ||
1367 | arg = '"'+arg+'"' | ||
1368 | elif ' ' in arg and "'" not in arg: | ||
1369 | arg = "'"+arg+"'" | ||
1370 | elif ' ' in arg: | ||
1371 | arg = arg.replace('"', r'\"') | ||
1372 | arg = '"'+arg+'"' | ||
1373 | escapedArgs.append(arg) | ||
1374 | return ' '.join(escapedArgs) | ||
1375 | |||
1376 | |||
1377 | # Recipe: dedent (0.1) in /Users/trentm/tm/recipes/cookbook | ||
1378 | def _dedentlines(lines, tabsize=8, skip_first_line=False): | ||
1379 | """_dedentlines(lines, tabsize=8, skip_first_line=False) -> dedented lines | ||
1380 | |||
1381 | "lines" is a list of lines to dedent. | ||
1382 | "tabsize" is the tab width to use for indent width calculations. | ||
1383 | "skip_first_line" is a boolean indicating if the first line should | ||
1384 | be skipped for calculating the indent width and for dedenting. | ||
1385 | This is sometimes useful for docstrings and similar. | ||
1386 | |||
1387 | Same as dedent() except operates on a sequence of lines. Note: the | ||
1388 | lines list is modified **in-place**. | ||
1389 | """ | ||
1390 | DEBUG = False | ||
1391 | if DEBUG: | ||
1392 | print "dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\ | ||
1393 | % (tabsize, skip_first_line) | ||
1394 | indents = [] | ||
1395 | margin = None | ||
1396 | for i, line in enumerate(lines): | ||
1397 | if i == 0 and skip_first_line: | ||
1398 | continue | ||
1399 | indent = 0 | ||
1400 | for ch in line: | ||
1401 | if ch == ' ': | ||
1402 | indent += 1 | ||
1403 | elif ch == '\t': | ||
1404 | indent += tabsize - (indent % tabsize) | ||
1405 | elif ch in '\r\n': | ||
1406 | continue # skip all-whitespace lines | ||
1407 | else: | ||
1408 | break | ||
1409 | else: | ||
1410 | continue # skip all-whitespace lines | ||
1411 | if DEBUG: | ||
1412 | print "dedent: indent=%d: %r" % (indent, line) | ||
1413 | if margin is None: | ||
1414 | margin = indent | ||
1415 | else: | ||
1416 | margin = min(margin, indent) | ||
1417 | if DEBUG: | ||
1418 | print "dedent: margin=%r" % margin | ||
1419 | |||
1420 | if margin is not None and margin > 0: | ||
1421 | for i, line in enumerate(lines): | ||
1422 | if i == 0 and skip_first_line: | ||
1423 | continue | ||
1424 | removed = 0 | ||
1425 | for j, ch in enumerate(line): | ||
1426 | if ch == ' ': | ||
1427 | removed += 1 | ||
1428 | elif ch == '\t': | ||
1429 | removed += tabsize - (removed % tabsize) | ||
1430 | elif ch in '\r\n': | ||
1431 | if DEBUG: | ||
1432 | print "dedent: %r: EOL -> strip up to EOL" % line | ||
1433 | lines[i] = lines[i][j:] | ||
1434 | break | ||
1435 | else: | ||
1436 | raise ValueError("unexpected non-whitespace char %r in " | ||
1437 | "line %r while removing %d-space margin" | ||
1438 | % (ch, line, margin)) | ||
1439 | if DEBUG: | ||
1440 | print "dedent: %r: %r -> removed %d/%d"\ | ||
1441 | % (line, ch, removed, margin) | ||
1442 | if removed == margin: | ||
1443 | lines[i] = lines[i][j+1:] | ||
1444 | break | ||
1445 | elif removed > margin: | ||
1446 | lines[i] = ' '*(removed-margin) + lines[i][j+1:] | ||
1447 | break | ||
1448 | return lines | ||
1449 | |||
1450 | def _dedent(text, tabsize=8, skip_first_line=False): | ||
1451 | """_dedent(text, tabsize=8, skip_first_line=False) -> dedented text | ||
1452 | |||
1453 | "text" is the text to dedent. | ||
1454 | "tabsize" is the tab width to use for indent width calculations. | ||
1455 | "skip_first_line" is a boolean indicating if the first line should | ||
1456 | be skipped for calculating the indent width and for dedenting. | ||
1457 | This is sometimes useful for docstrings and similar. | ||
1458 | |||
1459 | textwrap.dedent(s), but don't expand tabs to spaces | ||
1460 | """ | ||
1461 | lines = text.splitlines(1) | ||
1462 | _dedentlines(lines, tabsize=tabsize, skip_first_line=skip_first_line) | ||
1463 | return ''.join(lines) | ||
1464 | |||
1465 | |||
1466 | def _get_indent(marker, s, tab_width=8): | ||
1467 | """_get_indent(marker, s, tab_width=8) -> | ||
1468 | (<indentation-of-'marker'>, <indentation-width>)""" | ||
1469 | # Figure out how much the marker is indented. | ||
1470 | INDENT_CHARS = tuple(' \t') | ||
1471 | start = s.index(marker) | ||
1472 | i = start | ||
1473 | while i > 0: | ||
1474 | if s[i-1] not in INDENT_CHARS: | ||
1475 | break | ||
1476 | i -= 1 | ||
1477 | indent = s[i:start] | ||
1478 | indent_width = 0 | ||
1479 | for ch in indent: | ||
1480 | if ch == ' ': | ||
1481 | indent_width += 1 | ||
1482 | elif ch == '\t': | ||
1483 | indent_width += tab_width - (indent_width % tab_width) | ||
1484 | return indent, indent_width | ||
1485 | |||
1486 | def _get_trailing_whitespace(marker, s): | ||
1487 | """Return the whitespace content trailing the given 'marker' in string 's', | ||
1488 | up to and including a newline. | ||
1489 | """ | ||
1490 | suffix = '' | ||
1491 | start = s.index(marker) + len(marker) | ||
1492 | i = start | ||
1493 | while i < len(s): | ||
1494 | if s[i] in ' \t': | ||
1495 | suffix += s[i] | ||
1496 | elif s[i] in '\r\n': | ||
1497 | suffix += s[i] | ||
1498 | if s[i] == '\r' and i+1 < len(s) and s[i+1] == '\n': | ||
1499 | suffix += s[i+1] | ||
1500 | break | ||
1501 | else: | ||
1502 | break | ||
1503 | i += 1 | ||
1504 | return suffix | ||
1505 | |||
1506 | |||
1507 | |||
1508 | #---- bash completion support | ||
1509 | # Note: This is still experimental. I expect to change this | ||
1510 | # significantly. | ||
1511 | # | ||
1512 | # To get Bash completion for a cmdln.Cmdln class, run the following | ||
1513 | # bash command: | ||
1514 | # $ complete -C 'python -m cmdln /path/to/script.py CmdlnClass' cmdname | ||
1515 | # For example: | ||
1516 | # $ complete -C 'python -m cmdln ~/bin/svn.py SVN' svn | ||
1517 | # | ||
1518 | #TODO: Simplify the above so don't have to given path to script (try to | ||
1519 | # find it on PATH, if possible). Could also make class name | ||
1520 | # optional if there is only one in the module (common case). | ||
1521 | |||
1522 | if __name__ == "__main__" and len(sys.argv) == 6: | ||
1523 | def _log(s): | ||
1524 | return # no-op, comment out for debugging | ||
1525 | from os.path import expanduser | ||
1526 | fout = open(expanduser("~/tmp/bashcpln.log"), 'a') | ||
1527 | fout.write(str(s) + '\n') | ||
1528 | fout.close() | ||
1529 | |||
1530 | # Recipe: module_from_path (1.0.1+) | ||
1531 | def _module_from_path(path): | ||
1532 | import imp, os, sys | ||
1533 | path = os.path.expanduser(path) | ||
1534 | dir = os.path.dirname(path) or os.curdir | ||
1535 | name = os.path.splitext(os.path.basename(path))[0] | ||
1536 | sys.path.insert(0, dir) | ||
1537 | try: | ||
1538 | iinfo = imp.find_module(name, [dir]) | ||
1539 | return imp.load_module(name, *iinfo) | ||
1540 | finally: | ||
1541 | sys.path.remove(dir) | ||
1542 | |||
1543 | def _get_bash_cplns(script_path, class_name, cmd_name, | ||
1544 | token, preceding_token): | ||
1545 | _log('--') | ||
1546 | _log('get_cplns(%r, %r, %r, %r, %r)' | ||
1547 | % (script_path, class_name, cmd_name, token, preceding_token)) | ||
1548 | comp_line = os.environ["COMP_LINE"] | ||
1549 | comp_point = int(os.environ["COMP_POINT"]) | ||
1550 | _log("COMP_LINE: %r" % comp_line) | ||
1551 | _log("COMP_POINT: %r" % comp_point) | ||
1552 | |||
1553 | try: | ||
1554 | script = _module_from_path(script_path) | ||
1555 | except ImportError, ex: | ||
1556 | _log("error importing `%s': %s" % (script_path, ex)) | ||
1557 | return [] | ||
1558 | shell = getattr(script, class_name)() | ||
1559 | cmd_map = shell._get_canonical_map() | ||
1560 | del cmd_map["EOF"] | ||
1561 | |||
1562 | # Determine if completing the sub-command name. | ||
1563 | parts = comp_line[:comp_point].split(None, 1) | ||
1564 | _log(parts) | ||
1565 | if len(parts) == 1 or not (' ' in parts[1] or '\t' in parts[1]): | ||
1566 | #TODO: if parts[1].startswith('-'): handle top-level opts | ||
1567 | _log("complete sub-command names") | ||
1568 | matches = {} | ||
1569 | for name, canon_name in cmd_map.items(): | ||
1570 | if name.startswith(token): | ||
1571 | matches[name] = canon_name | ||
1572 | if not matches: | ||
1573 | return [] | ||
1574 | elif len(matches) == 1: | ||
1575 | return matches.keys() | ||
1576 | elif len(set(matches.values())) == 1: | ||
1577 | return [matches.values()[0]] | ||
1578 | else: | ||
1579 | return matches.keys() | ||
1580 | |||
1581 | # Otherwise, complete options for the given sub-command. | ||
1582 | #TODO: refine this so it does the right thing with option args | ||
1583 | if token.startswith('-'): | ||
1584 | cmd_name = comp_line.split(None, 2)[1] | ||
1585 | try: | ||
1586 | cmd_canon_name = cmd_map[cmd_name] | ||
1587 | except KeyError: | ||
1588 | return [] | ||
1589 | handler = shell._get_cmd_handler(cmd_canon_name) | ||
1590 | optparser = getattr(handler, "optparser", None) | ||
1591 | if optparser is None: | ||
1592 | optparser = SubCmdOptionParser() | ||
1593 | opt_strs = [] | ||
1594 | for option in optparser.option_list: | ||
1595 | for opt_str in option._short_opts + option._long_opts: | ||
1596 | if opt_str.startswith(token): | ||
1597 | opt_strs.append(opt_str) | ||
1598 | return opt_strs | ||
1599 | |||
1600 | return [] | ||
1601 | |||
1602 | for cpln in _get_bash_cplns(*sys.argv[1:]): | ||
1603 | print cpln | ||
1604 | |||