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