summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoryyh <yyh@123.com>2016-01-26 18:21:34 +0800
committeryyh <yyh@123.com>2016-02-04 14:18:57 +0800
commita5878ae2f3aa82d6b9cc59d7434c2f80e28f7471 (patch)
tree209055077dc64bbf59146a59d7dddc3c17cb533c
parent1fd1b7a024b16dece3082bc94c760de9201dc4d7 (diff)
downloadmic-a5878ae2f3aa82d6b9cc59d7434c2f80e28f7471.tar.gz
mic-a5878ae2f3aa82d6b9cc59d7434c2f80e28f7471.tar.bz2
mic-a5878ae2f3aa82d6b9cc59d7434c2f80e28f7471.zip
Using argparse module to parse the cmd line
Conflicts: plugins/imager/loop_plugin.py
-rwxr-xr-xmic/cmd_chroot.py71
-rwxr-xr-xmic/cmd_create.py234
-rw-r--r--mic/creator.py398
-rwxr-xr-xmic/helpformat.py90
-rw-r--r--mic/utils/cmdln.py1586
-rw-r--r--packaging/mic.spec5
-rw-r--r--plugins/imager/fs_plugin.py19
-rw-r--r--plugins/imager/loop_plugin.py29
-rw-r--r--plugins/imager/qcow_plugin.py10
-rwxr-xr-xtools/mic394
10 files changed, 629 insertions, 2207 deletions
diff --git a/mic/cmd_chroot.py b/mic/cmd_chroot.py
new file mode 100755
index 0000000..d7d76ef
--- /dev/null
+++ b/mic/cmd_chroot.py
@@ -0,0 +1,71 @@
+#!/usr/bin/python -tt
+# vim: ai ts=4 sts=4 et sw=4
+#
+# Copyright (c) 2012 Intel, Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; version 2 of the License
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc., 59
+# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"""Implementation of subcmd: chroot
+"""
+
+import os
+import os, sys, re
+import pwd
+import argparse
+
+from mic import msger
+from mic.utils import misc, errors
+from mic.conf import configmgr
+from mic.plugin import pluginmgr
+
+def _root_confirm():
+ """Make sure command is called by root
+ There are a lot of commands needed to be run during creating images,
+ some of them must be run with root privilege like mount, kpartx"""
+ if os.geteuid() != 0:
+ msger.error('Root permission is required to continue, abort')
+
+def main(parser, args, argv):
+ """mic choot entry point."""
+
+ #args is argparser namespace, argv is the input cmd line
+ if args is None:
+ raise errors.Usage("Invalid arguments")
+
+ targetimage = args.imagefile
+ if not os.path.exists(targetimage):
+ raise errors.CreatorError("Cannot find the image: %s"
+ % targetimage)
+
+ _root_confirm()
+
+ configmgr.chroot['saveto'] = args.saveto
+
+ imagetype = misc.get_image_type(targetimage)
+ if imagetype in ("ext3fsimg", "ext4fsimg", "btrfsimg"):
+ imagetype = "loop"
+
+ chrootclass = None
+ for pname, pcls in pluginmgr.get_plugins('imager').iteritems():
+ if pname == imagetype and hasattr(pcls, "do_chroot"):
+ chrootclass = pcls
+ break
+
+ if not chrootclass:
+ raise errors.CreatorError("Cannot support image type: %s" \
+ % imagetype)
+
+ chrootclass.do_chroot(targetimage, args.cmd)
+
+
diff --git a/mic/cmd_create.py b/mic/cmd_create.py
new file mode 100755
index 0000000..ce574a1
--- /dev/null
+++ b/mic/cmd_create.py
@@ -0,0 +1,234 @@
+#!/usr/bin/python -tt
+# vim: ai ts=4 sts=4 et sw=4
+#
+# Copyright (c) 2012 Intel, Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; version 2 of the License
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc., 59
+# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"""Implementation of subcmd: create
+"""
+
+import os
+import os, sys, re
+import pwd
+import argparse
+
+from mic import msger
+from mic.utils import errors, rpmmisc
+from mic.conf import configmgr
+from mic.plugin import pluginmgr
+
+def main(parser, args, argv):
+ """mic create entry point."""
+ #args is argparser namespace, argv is the input cmd line
+ if args is None:
+ raise errors.Usage("Invalid arguments")
+
+ if not os.path.exists(args.ksfile):
+ raise errors.CreatorError("Can't find the file: %s" % args.ksfile)
+
+ if os.geteuid() != 0:
+ msger.error("Root permission is required, abort")
+
+ try:
+ w = pwd.getpwuid(os.geteuid())
+ except KeyError:
+ msger.warning("Might fail in compressing stage for undetermined user")
+
+ abspath = lambda pth: os.path.abspath(os.path.expanduser(pth))
+ if args.logfile:
+ logfile_abs_path = abspath(args.logfile)
+ if os.path.isdir(logfile_abs_path):
+ raise errors.Usage("logfile's path %s should be file"
+ % args.logfile)
+ configmgr.create['logfile'] = logfile_abs_path
+ configmgr.set_logfile()
+
+ if args.subcommand == "auto":
+ do_auto(parser, args.ksfile, argv)
+ return
+
+ #check the imager type
+ createrClass = None
+ for subcmd, klass in pluginmgr.get_plugins('imager').iteritems():
+ if subcmd == args.subcommand and hasattr(klass, 'do_create'):
+ createrClass = klass
+
+ if createrClass is None:
+ raise errors.CreatorError("Can't support subcommand %s" % args.subcommand)
+
+ if args.config:
+ configmgr.reset()
+ configmgr._siteconf = args.config
+
+ if args.outdir is not None:
+ configmgr.create['outdir'] = abspath(args.outdir)
+ if args.cachedir is not None:
+ configmgr.create['cachedir'] = abspath(args.cachedir)
+ os.environ['ZYPP_LOCKFILE_ROOT'] = configmgr.create['cachedir']
+
+ for cdir in ('outdir', 'cachedir'):
+ if os.path.exists(configmgr.create[cdir]) \
+ and not os.path.isdir(configmgr.create[cdir]):
+ raise errors.Usage('Invalid directory specified: %s' \
+ % configmgr.create[cdir])
+ if not os.path.exists(configmgr.create[cdir]):
+ os.makedirs(configmgr.create[cdir])
+ if os.getenv('SUDO_UID', '') and os.getenv('SUDO_GID', ''):
+ os.chown(configmgr.create[cdir],
+ int(os.getenv('SUDO_UID')),
+ int(os.getenv('SUDO_GID')))
+
+ if args.local_pkgs_path is not None:
+ if not os.path.exists(args.local_pkgs_path):
+ raise errors.Usage('Local pkgs directory: \'%s\' not exist' \
+ % args.local_pkgs_path)
+ configmgr.create['local_pkgs_path'] = args.local_pkgs_path
+
+ if args.release:
+ configmgr.create['release'] = args.release.rstrip('/')
+
+ if args.record_pkgs:
+ configmgr.create['record_pkgs'] = []
+ for infotype in args.record_pkgs.split(','):
+ if infotype not in ('name', 'content', 'license', 'vcs'):
+ raise errors.Usage('Invalid pkg recording: %s, valid ones:'
+ ' "name", "content", "license", "vcs"' \
+ % infotype)
+
+ configmgr.create['record_pkgs'].append(infotype)
+
+ if args.strict_mode:
+ configmgr.create['strict_mode'] = args.strict_mode
+ if args.arch is not None:
+ supported_arch = sorted(rpmmisc.archPolicies.keys(), reverse=True)
+ if args.arch in supported_arch:
+ configmgr.create['arch'] = args.arch
+ else:
+ raise errors.Usage('Invalid architecture: "%s".\n'
+ ' Supported architectures are: \n'
+ ' %s' % (args.arch,
+ ', '.join(supported_arch)))
+
+ if args.pkgmgr is not None:
+ configmgr.create['pkgmgr'] = args.pkgmgr
+
+ if args.runtime:
+ configmgr.set_runtime(args.runtime)
+
+ if args.pack_to is not None:
+ configmgr.create['pack_to'] = args.pack_to
+
+ if args.copy_kernel:
+ configmgr.create['copy_kernel'] = args.copy_kernel
+
+ if args.install_pkgs:
+ configmgr.create['install_pkgs'] = []
+ for pkgtype in args.install_pkgs.split(','):
+ if pkgtype not in ('source', 'debuginfo', 'debugsource'):
+ raise errors.Usage('Invalid parameter specified: "%s", '
+ 'valid values: source, debuginfo, '
+ 'debusource' % pkgtype)
+
+ configmgr.create['install_pkgs'].append(pkgtype)
+
+ if args.check_pkgs:
+ for pkg in args.check_pkgs.split(','):
+ configmgr.create['check_pkgs'].append(pkg)
+
+ if args.enabletmpfs:
+ configmgr.create['enabletmpfs'] = args.enabletmpfs
+
+ if args.repourl:
+ for item in args.repourl:
+ try:
+ key, val = item.split('=')
+ except:
+ continue
+ configmgr.create['repourl'][key] = val
+
+ if args.repo:
+ for optvalue in args.repo:
+ repo = {}
+ for item in optvalue.split(';'):
+ try:
+ key, val = item.split('=')
+ except:
+ continue
+ repo[key.strip()] = val.strip()
+ if 'name' in repo:
+ configmgr.create['extrarepos'][repo['name']] = repo
+
+ if args.ignore_ksrepo:
+ configmgr.create['ignore_ksrepo'] = args.ignore_ksrepo
+
+ creater = createrClass()
+ creater.do_create(args)
+
+def do_auto(parser, ksfile, argv):
+ """${cmd_name}: auto detect image type from magic header
+
+ Usage:
+ ${name} ${cmd_name} <ksfile>
+
+ ${cmd_option_list}
+ """
+ def parse_magic_line(re_str, pstr, ptype='mic'):
+ ptn = re.compile(re_str)
+ m = ptn.match(pstr)
+ if not m or not m.groups():
+ return None
+
+ inline_argv = m.group(1).strip()
+ if ptype == 'mic':
+ m2 = re.search('(?P<format>\w+)', inline_argv)
+ elif ptype == 'mic2':
+ m2 = re.search('(-f|--format(=)?)\s*(?P<format>\w+)',
+ inline_argv)
+ else:
+ return None
+
+ if m2:
+ cmdname = m2.group('format')
+ inline_argv = inline_argv.replace(m2.group(0), '')
+ return (cmdname, inline_argv)
+
+ return None
+
+ if not os.path.exists(ksfile):
+ raise errors.CreatorError("Can't find the file: %s" % ksfile)
+
+ with open(ksfile, 'r') as rf:
+ first_line = rf.readline()
+
+ mic_re = '^#\s*-\*-mic-options-\*-\s+(.*)\s+-\*-mic-options-\*-'
+ mic2_re = '^#\s*-\*-mic2-options-\*-\s+(.*)\s+-\*-mic2-options-\*-'
+
+ result = parse_magic_line(mic_re, first_line, 'mic') \
+ or parse_magic_line(mic2_re, first_line, 'mic2')
+ if not result:
+ raise errors.KsError("Invalid magic line in file: %s" % ksfile)
+
+ ksargv = ' '.join(result).split()
+
+ argv.remove("auto")
+ index = argv.index("create")
+ #insert the subcommand
+ argv.insert(index+1, ksargv[0])
+ options = argv + ksargv[1:]
+
+ args = parser.parse_args(options)
+
+ main(parser, args, options)
+
diff --git a/mic/creator.py b/mic/creator.py
deleted file mode 100644
index e7c6633..0000000
--- a/mic/creator.py
+++ /dev/null
@@ -1,398 +0,0 @@
-#!/usr/bin/python -tt
-#
-# Copyright (c) 2011 Intel, Inc.
-#
-# This program is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the Free
-# Software Foundation; version 2 of the License
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
-# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
-# for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc., 59
-# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-
-import os, sys, re
-import pwd
-from optparse import SUPPRESS_HELP
-
-from mic import msger
-from mic.utils import cmdln, errors, rpmmisc
-from mic.conf import configmgr
-from mic.plugin import pluginmgr
-
-
-class Creator(cmdln.Cmdln):
- """${name}: create an image
-
- Usage:
- ${name} SUBCOMMAND <ksfile> [OPTS]
-
- ${command_list}
- ${option_list}
- """
-
- name = 'mic create(cr)'
-
- def __init__(self, *args, **kwargs):
- cmdln.Cmdln.__init__(self, *args, **kwargs)
- self._subcmds = []
-
- # get cmds from pluginmgr
- # mix-in do_subcmd interface
- for subcmd, klass in pluginmgr.get_plugins('imager').iteritems():
- if not hasattr(klass, 'do_create'):
- msger.warning("Unsurpport subcmd: %s" % subcmd)
- continue
-
- func = getattr(klass, 'do_create')
- setattr(self.__class__, "do_"+subcmd, func)
- self._subcmds.append(subcmd)
-
- def get_optparser(self):
- optparser = cmdln.CmdlnOptionParser(self)
- optparser.add_option('-d', '--debug', action='store_true',
- dest='debug',
- help=SUPPRESS_HELP)
- optparser.add_option('-v', '--verbose', action='store_true',
- dest='verbose',
- help=SUPPRESS_HELP)
- optparser.add_option('', '--logfile', type='string', dest='logfile',
- default=None,
- help='Path of logfile')
- optparser.add_option('-c', '--config', type='string', dest='config',
- default=None,
- help='Specify config file for mic')
- optparser.add_option('-k', '--cachedir', type='string', action='store',
- dest='cachedir', default=None,
- help='Cache directory to store the downloaded')
- optparser.add_option('-o', '--outdir', type='string', action='store',
- dest='outdir', default=None,
- help='Output directory')
- optparser.add_option('-A', '--arch', type='string', dest='arch',
- default=None,
- help='Specify repo architecture')
- optparser.add_option('', '--release', type='string', dest='release',
- default=None, metavar='RID',
- help='Generate a release of RID with all necessary'
- ' files, when @BUILD_ID@ is contained in '
- 'kickstart file, it will be replaced by RID')
- optparser.add_option("", "--record-pkgs", type="string",
- dest="record_pkgs", default=None,
- help='Record the info of installed packages, '
- 'multiple values can be specified which '
- 'joined by ",", valid values: "name", '
- '"content", "license", "vcs"')
- optparser.add_option('', '--pkgmgr', type='string', dest='pkgmgr',
- default=None,
- help='Specify backend package manager')
- optparser.add_option('', '--local-pkgs-path', type='string',
- dest='local_pkgs_path', default=None,
- help='Path for local pkgs(rpms) to be installed')
- optparser.add_option('', '--runtime', type='string',
- dest='runtime', default=None,
- help='Specify runtime mode, avaiable: bootstrap')
- # --taring-to is alias to --pack-to
- optparser.add_option('', '--taring-to', type='string',
- dest='pack_to', default=None,
- help=SUPPRESS_HELP)
- optparser.add_option('', '--pack-to', type='string',
- dest='pack_to', default=None,
- help='Pack the images together into the specified'
- ' achive, extension supported: .zip, .tar, '
- '.tar.gz, .tar.bz2, etc. by default, .tar '
- 'will be used')
- optparser.add_option('', '--copy-kernel', action='store_true',
- dest='copy_kernel',
- help='Copy kernel files from image /boot directory'
- ' to the image output directory.')
- optparser.add_option('', '--install-pkgs', type='string', action='store',
- dest='install_pkgs', default=None,
- help='Specify what type of packages to be installed,'
- ' valid: source, debuginfo, debugsource')
- optparser.add_option('', '--check-pkgs', type='string', action='store',
- dest='check_pkgs', default=[],
- help='Check if given packages would be installed, '
- 'packages should be separated by comma')
- optparser.add_option('', '--tmpfs', action='store_true', dest='enabletmpfs',
- help='Setup tmpdir as tmpfs to accelerate, experimental'
- ' feature, use it if you have more than 4G memory')
- optparser.add_option('', '--repourl', action='append',
- dest='repourl', default=[],
- help=SUPPRESS_HELP)
- optparser.add_option('-R', '--repo', action='append',
- dest='repo', default=[],
- help=SUPPRESS_HELP)
- optparser.add_option('', '--ignore-ksrepo', action='store_true',
- dest='ignore_ksrepo', default=False,
- help=SUPPRESS_HELP)
- optparser.add_option('', '--strict-mode', action='store_true',
- dest='strict_mode', default=False,
- help='Abort creation of image, if there are some errors'
- ' during rpm installation. ')
- return optparser
-
- def preoptparse(self, argv):
- optparser = self.get_optparser()
-
- largs = []
- rargs = []
- while argv:
- arg = argv.pop(0)
-
- if arg in ('-h', '--help'):
- rargs.append(arg)
-
- elif optparser.has_option(arg):
- largs.append(arg)
-
- if optparser.get_option(arg).takes_value():
- try:
- largs.append(argv.pop(0))
- except IndexError:
- raise errors.Usage("option %s requires arguments" % arg)
-
- else:
- if arg.startswith("--"):
- if "=" in arg:
- opt = arg.split("=")[0]
- else:
- opt = None
- elif arg.startswith("-") and len(arg) > 2:
- opt = arg[0:2]
- else:
- opt = None
-
- if opt and optparser.has_option(opt):
- largs.append(arg)
- else:
- rargs.append(arg)
-
- return largs + rargs
-
- def postoptparse(self):
- abspath = lambda pth: os.path.abspath(os.path.expanduser(pth))
-
- if self.options.verbose:
- msger.set_loglevel('VERBOSE')
- if self.options.debug:
- msger.set_loglevel('DEBUG')
-
- if self.options.logfile:
- logfile_abs_path = abspath(self.options.logfile)
- if os.path.isdir(logfile_abs_path):
- raise errors.Usage("logfile's path %s should be file"
- % self.options.logfile)
- configmgr.create['logfile'] = logfile_abs_path
- configmgr.set_logfile()
-
- if self.options.config:
- configmgr.reset()
- configmgr._siteconf = self.options.config
-
- if self.options.outdir is not None:
- configmgr.create['outdir'] = abspath(self.options.outdir)
- if self.options.cachedir is not None:
- configmgr.create['cachedir'] = abspath(self.options.cachedir)
- os.environ['ZYPP_LOCKFILE_ROOT'] = configmgr.create['cachedir']
-
- for cdir in ('outdir', 'cachedir'):
- if os.path.exists(configmgr.create[cdir]) \
- and not os.path.isdir(configmgr.create[cdir]):
- raise errors.Usage('Invalid directory specified: %s' \
- % configmgr.create[cdir])
- if not os.path.exists(configmgr.create[cdir]):
- os.makedirs(configmgr.create[cdir])
- if os.getenv('SUDO_UID', '') and os.getenv('SUDO_GID', ''):
- os.chown(configmgr.create[cdir],
- int(os.getenv('SUDO_UID')),
- int(os.getenv('SUDO_GID')))
-
- if self.options.local_pkgs_path is not None:
- if not os.path.exists(self.options.local_pkgs_path):
- raise errors.Usage('Local pkgs directory: \'%s\' not exist' \
- % self.options.local_pkgs_path)
- configmgr.create['local_pkgs_path'] = self.options.local_pkgs_path
-
- if self.options.release:
- configmgr.create['release'] = self.options.release.rstrip('/')
-
- if self.options.record_pkgs:
- configmgr.create['record_pkgs'] = []
- for infotype in self.options.record_pkgs.split(','):
- if infotype not in ('name', 'content', 'license', 'vcs'):
- raise errors.Usage('Invalid pkg recording: %s, valid ones:'
- ' "name", "content", "license", "vcs"' \
- % infotype)
-
- configmgr.create['record_pkgs'].append(infotype)
-
- if self.options.strict_mode:
- configmgr.create['strict_mode'] = self.options.strict_mode
- if self.options.arch is not None:
- supported_arch = sorted(rpmmisc.archPolicies.keys(), reverse=True)
- if self.options.arch in supported_arch:
- configmgr.create['arch'] = self.options.arch
- else:
- raise errors.Usage('Invalid architecture: "%s".\n'
- ' Supported architectures are: \n'
- ' %s' % (self.options.arch,
- ', '.join(supported_arch)))
-
- if self.options.pkgmgr is not None:
- configmgr.create['pkgmgr'] = self.options.pkgmgr
-
- if self.options.runtime:
- configmgr.set_runtime(self.options.runtime)
-
- if self.options.pack_to is not None:
- configmgr.create['pack_to'] = self.options.pack_to
-
- if self.options.copy_kernel:
- configmgr.create['copy_kernel'] = self.options.copy_kernel
-
- if self.options.install_pkgs:
- configmgr.create['install_pkgs'] = []
- for pkgtype in self.options.install_pkgs.split(','):
- if pkgtype not in ('source', 'debuginfo', 'debugsource'):
- raise errors.Usage('Invalid parameter specified: "%s", '
- 'valid values: source, debuginfo, '
- 'debusource' % pkgtype)
-
- configmgr.create['install_pkgs'].append(pkgtype)
-
- if self.options.check_pkgs:
- for pkg in self.options.check_pkgs.split(','):
- configmgr.create['check_pkgs'].append(pkg)
-
- if self.options.enabletmpfs:
- configmgr.create['enabletmpfs'] = self.options.enabletmpfs
-
- if self.options.repourl:
- for item in self.options.repourl:
- try:
- key, val = item.split('=')
- except:
- continue
- configmgr.create['repourl'][key] = val
-
- if self.options.repo:
- for optvalue in self.options.repo:
- repo = {}
- for item in optvalue.split(';'):
- try:
- key, val = item.split('=')
- except:
- continue
- repo[key.strip()] = val.strip()
- if 'name' in repo:
- configmgr.create['extrarepos'][repo['name']] = repo
-
- if self.options.ignore_ksrepo:
- configmgr.create['ignore_ksrepo'] = self.options.ignore_ksrepo
-
- def main(self, argv=None):
- if argv is None:
- argv = sys.argv
- else:
- argv = argv[:] # don't modify caller's list
-
- self.optparser = self.get_optparser()
- if self.optparser:
- try:
- argv = self.preoptparse(argv)
- self.options, args = self.optparser.parse_args(argv)
-
- except cmdln.CmdlnUserError, ex:
- msg = "%s: %s\nTry '%s help' for info.\n"\
- % (self.name, ex, self.name)
- raise errors.Usage(msg)
-
- except cmdln.StopOptionProcessing, ex:
- return 0
- else:
- # optparser=None means no process for opts
- self.options, args = None, argv[1:]
-
- if not args:
- return self.emptyline()
-
- self.postoptparse()
-
- return self.cmd(args)
-
- def precmd(self, argv): # check help before cmd
-
- if '-h' in argv or '?' in argv or '--help' in argv or 'help' in argv:
- return argv
-
- if len(argv) == 1:
- return ['help', argv[0]]
-
- if os.geteuid() != 0:
- msger.error("Root permission is required, abort")
-
- try:
- w = pwd.getpwuid(os.geteuid())
- except KeyError:
- msger.warning("Might fail in compressing stage for undetermined user")
-
- return argv
-
- def do_auto(self, subcmd, opts, *args):
- """${cmd_name}: auto detect image type from magic header
-
- Usage:
- ${name} ${cmd_name} <ksfile>
-
- ${cmd_option_list}
- """
- def parse_magic_line(re_str, pstr, ptype='mic'):
- ptn = re.compile(re_str)
- m = ptn.match(pstr)
- if not m or not m.groups():
- return None
-
- inline_argv = m.group(1).strip()
- if ptype == 'mic':
- m2 = re.search('(?P<format>\w+)', inline_argv)
- elif ptype == 'mic2':
- m2 = re.search('(-f|--format(=)?)\s*(?P<format>\w+)',
- inline_argv)
- else:
- return None
-
- if m2:
- cmdname = m2.group('format')
- inline_argv = inline_argv.replace(m2.group(0), '')
- return (cmdname, inline_argv)
-
- return None
-
- if len(args) != 1:
- raise errors.Usage("Extra arguments given")
-
- if not os.path.exists(args[0]):
- raise errors.CreatorError("Can't find the file: %s" % args[0])
-
- with open(args[0], 'r') as rf:
- first_line = rf.readline()
-
- mic_re = '^#\s*-\*-mic-options-\*-\s+(.*)\s+-\*-mic-options-\*-'
- mic2_re = '^#\s*-\*-mic2-options-\*-\s+(.*)\s+-\*-mic2-options-\*-'
-
- result = parse_magic_line(mic_re, first_line, 'mic') \
- or parse_magic_line(mic2_re, first_line, 'mic2')
- if not result:
- raise errors.KsError("Invalid magic line in file: %s" % args[0])
-
- if result[0] not in self._subcmds:
- raise errors.KsError("Unsupport format '%s' in %s"
- % (result[0], args[0]))
-
- argv = ' '.join(result + args).split()
- self.main(argv)
-
diff --git a/mic/helpformat.py b/mic/helpformat.py
new file mode 100755
index 0000000..e17f716
--- /dev/null
+++ b/mic/helpformat.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+# vim: ai ts=4 sts=4 et sw=4
+#
+# Copyright (c) 2011 Intel, Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; version 2 of the License
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc., 59
+# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"""Local additions to commandline parsing."""
+
+import os
+import re
+import functools
+
+from argparse import RawDescriptionHelpFormatter, ArgumentTypeError
+
+class MICHelpFormatter(RawDescriptionHelpFormatter):
+ """Changed default argparse help output by request from cmdln lovers."""
+
+ def __init__(self, *args, **kwargs):
+ super(MICHelpFormatter, self).__init__(*args, **kwargs)
+ self._aliases = {}
+
+ def add_argument(self, action):
+ """Collect aliases."""
+
+ if action.choices:
+ for item, parser in action.choices.iteritems():
+ self._aliases[str(item)] = parser.get_default('alias')
+
+ return super(MICHelpFormatter, self).add_argument(action)
+
+ def format_help(self):
+ """
+ There is no safe and documented way in argparse to reformat
+ help output through APIs as almost all of them are private,
+ so this method just parses the output and changes it.
+ """
+ result = []
+ subcomm = False
+ for line in super(MICHelpFormatter, self).format_help().split('\n'):
+ if line.strip().startswith('{'):
+ continue
+ if line.startswith('optional arguments:'):
+ line = 'Global Options:'
+ if line.startswith('usage:'):
+ line = "Usage: mic [GLOBAL-OPTS] SUBCOMMAND [OPTS]"
+ if subcomm:
+ match = re.match("[ ]+([^ ]+)[ ]+(.+)", line)
+ if match:
+ name, help_text = match.group(1), match.group(2)
+ alias = self._aliases.get(name) or ''
+ if alias:
+ alias = "(%s)" % alias
+ line = " %-22s%s" % ("%s %s" % (name, alias), help_text)
+ if line.strip().startswith('subcommands:'):
+ line = 'Subcommands:'
+ subcomm = True
+ result.append(line)
+ return '\n'.join(result)
+
+def subparser(func):
+ """Convenient decorator for subparsers."""
+ @functools.wraps(func)
+ def wrapper(parser):
+ """
+ Create subparser
+ Set first line of function's docstring as a help
+ and the rest of the lines as a description.
+ Set attribute 'module' of subparser to 'cmd'+first part of function name
+ """
+ splitted = func.__doc__.split('\n')
+ name = func.__name__.split('_')[0]
+ subpar = parser.add_parser(name, help=splitted[0],
+ description='\n'.join(splitted[1:]),
+ formatter_class=RawDescriptionHelpFormatter)
+ subpar.set_defaults(module="cmd_%s" % name)
+ return func(subpar)
+ return wrapper
+
diff --git a/mic/utils/cmdln.py b/mic/utils/cmdln.py
deleted file mode 100644
index 3f4bb0f..0000000
--- a/mic/utils/cmdln.py
+++ /dev/null
@@ -1,1586 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2002-2007 ActiveState Software Inc.
-# License: MIT (see LICENSE.txt for license details)
-# Author: Trent Mick
-# Home: http://trentm.com/projects/cmdln/
-
-"""An improvement on Python's standard cmd.py module.
-
-As with cmd.py, this module provides "a simple framework for writing
-line-oriented command intepreters." This module provides a 'RawCmdln'
-class that fixes some design flaws in cmd.Cmd, making it more scalable
-and nicer to use for good 'cvs'- or 'svn'-style command line interfaces
-or simple shells. And it provides a 'Cmdln' class that add
-optparse-based option processing. Basically you use it like this:
-
- import cmdln
-
- class MySVN(cmdln.Cmdln):
- name = "svn"
-
- @cmdln.alias('stat', 'st')
- @cmdln.option('-v', '--verbose', action='store_true'
- help='print verbose information')
- def do_status(self, subcmd, opts, *paths):
- print "handle 'svn status' command"
-
- #...
-
- if __name__ == "__main__":
- shell = MySVN()
- retval = shell.main()
- sys.exit(retval)
-
-See the README.txt or <http://trentm.com/projects/cmdln/> for more
-details.
-"""
-
-__version_info__ = (1, 1, 2)
-__version__ = '.'.join(map(str, __version_info__))
-
-import os
-import sys
-import re
-import cmd
-import optparse
-from pprint import pprint
-import sys
-
-
-
-
-#---- globals
-
-LOOP_ALWAYS, LOOP_NEVER, LOOP_IF_EMPTY = range(3)
-
-# An unspecified optional argument when None is a meaningful value.
-_NOT_SPECIFIED = ("Not", "Specified")
-
-# Pattern to match a TypeError message from a call that
-# failed because of incorrect number of arguments (see
-# Python/getargs.c).
-_INCORRECT_NUM_ARGS_RE = re.compile(
- r"(takes [\w ]+ )(\d+)( arguments? \()(\d+)( given\))")
-
-
-
-#---- exceptions
-
-class CmdlnError(Exception):
- """A cmdln.py usage error."""
- def __init__(self, msg):
- self.msg = msg
- def __str__(self):
- return self.msg
-
-class CmdlnUserError(Exception):
- """An error by a user of a cmdln-based tool/shell."""
- pass
-
-
-
-#---- public methods and classes
-
-def alias(*aliases):
- """Decorator to add aliases for Cmdln.do_* command handlers.
-
- Example:
- class MyShell(cmdln.Cmdln):
- @cmdln.alias("!", "sh")
- def do_shell(self, argv):
- #...implement 'shell' command
- """
- def decorate(f):
- if not hasattr(f, "aliases"):
- f.aliases = []
- f.aliases += aliases
- return f
- return decorate
-
-
-class RawCmdln(cmd.Cmd):
- """An improved (on cmd.Cmd) framework for building multi-subcommand
- scripts (think "svn" & "cvs") and simple shells (think "pdb" and
- "gdb").
-
- A simple example:
-
- import cmdln
-
- class MySVN(cmdln.RawCmdln):
- name = "svn"
-
- @cmdln.aliases('stat', 'st')
- def do_status(self, argv):
- print "handle 'svn status' command"
-
- if __name__ == "__main__":
- shell = MySVN()
- retval = shell.main()
- sys.exit(retval)
-
- See <http://trentm.com/projects/cmdln> for more information.
- """
- name = None # if unset, defaults basename(sys.argv[0])
- prompt = None # if unset, defaults to self.name+"> "
- version = None # if set, default top-level options include --version
-
- # Default messages for some 'help' command error cases.
- # They are interpolated with one arg: the command.
- nohelp = "no help on '%s'"
- unknowncmd = "unknown command: '%s'"
-
- helpindent = '' # string with which to indent help output
-
- def __init__(self, completekey='tab',
- stdin=None, stdout=None, stderr=None):
- """Cmdln(completekey='tab', stdin=None, stdout=None, stderr=None)
-
- The optional argument 'completekey' is the readline name of a
- completion key; it defaults to the Tab key. If completekey is
- not None and the readline module is available, command completion
- is done automatically.
-
- The optional arguments 'stdin', 'stdout' and 'stderr' specify
- alternate input, output and error output file objects; if not
- specified, sys.* are used.
-
- If 'stdout' but not 'stderr' is specified, stdout is used for
- error output. This is to provide least surprise for users used
- to only the 'stdin' and 'stdout' options with cmd.Cmd.
- """
- import sys
- if self.name is None:
- self.name = os.path.basename(sys.argv[0])
- if self.prompt is None:
- self.prompt = self.name+"> "
- self._name_str = self._str(self.name)
- self._prompt_str = self._str(self.prompt)
- if stdin is not None:
- self.stdin = stdin
- else:
- self.stdin = sys.stdin
- if stdout is not None:
- self.stdout = stdout
- else:
- self.stdout = sys.stdout
- if stderr is not None:
- self.stderr = stderr
- elif stdout is not None:
- self.stderr = stdout
- else:
- self.stderr = sys.stderr
- self.cmdqueue = []
- self.completekey = completekey
- self.cmdlooping = False
-
- def get_optparser(self):
- """Hook for subclasses to set the option parser for the
- top-level command/shell.
-
- This option parser is used retrieved and used by `.main()' to
- handle top-level options.
-
- The default implements a single '-h|--help' option. Sub-classes
- can return None to have no options at the top-level. Typically
- an instance of CmdlnOptionParser should be returned.
- """
- version = (self.version is not None
- and "%s %s" % (self._name_str, self.version)
- or None)
- return CmdlnOptionParser(self, version=version)
-
- def postoptparse(self):
- """Hook method executed just after `.main()' parses top-level
- options.
-
- When called `self.options' holds the results of the option parse.
- """
- pass
-
- def main(self, argv=None, loop=LOOP_NEVER):
- """A possible mainline handler for a script, like so:
-
- import cmdln
- class MyCmd(cmdln.Cmdln):
- name = "mycmd"
- ...
-
- if __name__ == "__main__":
- MyCmd().main()
-
- By default this will use sys.argv to issue a single command to
- 'MyCmd', then exit. The 'loop' argument can be use to control
- interactive shell behaviour.
-
- Arguments:
- "argv" (optional, default sys.argv) is the command to run.
- It must be a sequence, where the first element is the
- command name and subsequent elements the args for that
- command.
- "loop" (optional, default LOOP_NEVER) is a constant
- indicating if a command loop should be started (i.e. an
- interactive shell). Valid values (constants on this module):
- LOOP_ALWAYS start loop and run "argv", if any
- LOOP_NEVER run "argv" (or .emptyline()) and exit
- LOOP_IF_EMPTY run "argv", if given, and exit;
- otherwise, start loop
- """
- if argv is None:
- import sys
- argv = sys.argv
- else:
- argv = argv[:] # don't modify caller's list
-
- self.optparser = self.get_optparser()
- if self.optparser: # i.e. optparser=None means don't process for opts
- try:
- self.options, args = self.optparser.parse_args(argv[1:])
- except CmdlnUserError, ex:
- msg = "%s: %s\nTry '%s help' for info.\n"\
- % (self.name, ex, self.name)
- self.stderr.write(self._str(msg))
- self.stderr.flush()
- return 1
- except StopOptionProcessing, ex:
- return 0
- else:
- self.options, args = None, argv[1:]
- self.postoptparse()
-
- if loop == LOOP_ALWAYS:
- if args:
- self.cmdqueue.append(args)
- return self.cmdloop()
- elif loop == LOOP_NEVER:
- if args:
- return self.cmd(args)
- else:
- return self.emptyline()
- elif loop == LOOP_IF_EMPTY:
- if args:
- return self.cmd(args)
- else:
- return self.cmdloop()
-
- def cmd(self, argv):
- """Run one command and exit.
-
- "argv" is the arglist for the command to run. argv[0] is the
- command to run. If argv is an empty list then the
- 'emptyline' handler is run.
-
- Returns the return value from the command handler.
- """
- assert isinstance(argv, (list, tuple)), \
- "'argv' is not a sequence: %r" % argv
- retval = None
- try:
- argv = self.precmd(argv)
- retval = self.onecmd(argv)
- self.postcmd(argv)
- except:
- if not self.cmdexc(argv):
- raise
- retval = 1
- return retval
-
- def _str(self, s):
- """Safely convert the given str/unicode to a string for printing."""
- try:
- return str(s)
- except UnicodeError:
- #XXX What is the proper encoding to use here? 'utf-8' seems
- # to work better than "getdefaultencoding" (usually
- # 'ascii'), on OS X at least.
- #import sys
- #return s.encode(sys.getdefaultencoding(), "replace")
- return s.encode("utf-8", "replace")
-
- def cmdloop(self, intro=None):
- """Repeatedly issue a prompt, accept input, parse into an argv, and
- dispatch (via .precmd(), .onecmd() and .postcmd()), passing them
- the argv. In other words, start a shell.
-
- "intro" (optional) is a introductory message to print when
- starting the command loop. This overrides the class
- "intro" attribute, if any.
- """
- self.cmdlooping = True
- self.preloop()
- if self.use_rawinput and self.completekey:
- try:
- import readline
- self.old_completer = readline.get_completer()
- readline.set_completer(self.complete)
- readline.parse_and_bind(self.completekey+": complete")
- except ImportError:
- pass
- try:
- if intro is None:
- intro = self.intro
- if intro:
- intro_str = self._str(intro)
- self.stdout.write(intro_str+'\n')
- self.stop = False
- retval = None
- while not self.stop:
- if self.cmdqueue:
- argv = self.cmdqueue.pop(0)
- assert isinstance(argv, (list, tuple)), \
- "item on 'cmdqueue' is not a sequence: %r" % argv
- else:
- if self.use_rawinput:
- try:
- line = raw_input(self._prompt_str)
- except EOFError:
- line = 'EOF'
- else:
- self.stdout.write(self._prompt_str)
- self.stdout.flush()
- line = self.stdin.readline()
- if not len(line):
- line = 'EOF'
- else:
- line = line[:-1] # chop '\n'
- argv = line2argv(line)
- try:
- argv = self.precmd(argv)
- retval = self.onecmd(argv)
- self.postcmd(argv)
- except:
- if not self.cmdexc(argv):
- raise
- retval = 1
- self.lastretval = retval
- self.postloop()
- finally:
- if self.use_rawinput and self.completekey:
- try:
- import readline
- readline.set_completer(self.old_completer)
- except ImportError:
- pass
- self.cmdlooping = False
- return retval
-
- def precmd(self, argv):
- """Hook method executed just before the command argv is
- interpreted, but after the input prompt is generated and issued.
-
- "argv" is the cmd to run.
-
- Returns an argv to run (i.e. this method can modify the command
- to run).
- """
- return argv
-
- def postcmd(self, argv):
- """Hook method executed just after a command dispatch is finished.
-
- "argv" is the command that was run.
- """
- pass
-
- def cmdexc(self, argv):
- """Called if an exception is raised in any of precmd(), onecmd(),
- or postcmd(). If True is returned, the exception is deemed to have
- been dealt with. Otherwise, the exception is re-raised.
-
- The default implementation handles CmdlnUserError's, which
- typically correspond to user error in calling commands (as
- opposed to programmer error in the design of the script using
- cmdln.py).
- """
- import sys
- type, exc, traceback = sys.exc_info()
- if isinstance(exc, CmdlnUserError):
- msg = "%s %s: %s\nTry '%s help %s' for info.\n"\
- % (self.name, argv[0], exc, self.name, argv[0])
- self.stderr.write(self._str(msg))
- self.stderr.flush()
- return True
-
- def onecmd(self, argv):
- if not argv:
- return self.emptyline()
- self.lastcmd = argv
- cmdname = self._get_canonical_cmd_name(argv[0])
- if cmdname:
- handler = self._get_cmd_handler(cmdname)
- if handler:
- return self._dispatch_cmd(handler, argv)
- return self.default(argv)
-
- def _dispatch_cmd(self, handler, argv):
- return handler(argv)
-
- def default(self, argv):
- """Hook called to handle a command for which there is no handler.
-
- "argv" is the command and arguments to run.
-
- The default implementation writes and error message to stderr
- and returns an error exit status.
-
- Returns a numeric command exit status.
- """
- errmsg = self._str(self.unknowncmd % (argv[0],))
- if self.cmdlooping:
- self.stderr.write(errmsg+"\n")
- else:
- self.stderr.write("%s: %s\nTry '%s help' for info.\n"
- % (self._name_str, errmsg, self._name_str))
- self.stderr.flush()
- return 1
-
- def parseline(self, line):
- # This is used by Cmd.complete (readline completer function) to
- # massage the current line buffer before completion processing.
- # We override to drop special '!' handling.
- line = line.strip()
- if not line:
- return None, None, line
- elif line[0] == '?':
- line = 'help ' + line[1:]
- i, n = 0, len(line)
- while i < n and line[i] in self.identchars: i = i+1
- cmd, arg = line[:i], line[i:].strip()
- return cmd, arg, line
-
- def helpdefault(self, cmd, known):
- """Hook called to handle help on a command for which there is no
- help handler.
-
- "cmd" is the command name on which help was requested.
- "known" is a boolean indicating if this command is known
- (i.e. if there is a handler for it).
-
- Returns a return code.
- """
- if known:
- msg = self._str(self.nohelp % (cmd,))
- if self.cmdlooping:
- self.stderr.write(msg + '\n')
- else:
- self.stderr.write("%s: %s\n" % (self.name, msg))
- else:
- msg = self.unknowncmd % (cmd,)
- if self.cmdlooping:
- self.stderr.write(msg + '\n')
- else:
- self.stderr.write("%s: %s\n"
- "Try '%s help' for info.\n"
- % (self.name, msg, self.name))
- self.stderr.flush()
- return 1
-
- def do_help(self, argv):
- """${cmd_name}: give detailed help on a specific sub-command
-
- Usage:
- ${name} help [COMMAND]
- """
- if len(argv) > 1: # asking for help on a particular command
- doc = None
- cmdname = self._get_canonical_cmd_name(argv[1]) or argv[1]
- if not cmdname:
- return self.helpdefault(argv[1], False)
- else:
- helpfunc = getattr(self, "help_"+cmdname, None)
- if helpfunc:
- doc = helpfunc()
- else:
- handler = self._get_cmd_handler(cmdname)
- if handler:
- doc = handler.__doc__
- if doc is None:
- return self.helpdefault(argv[1], handler != None)
- else: # bare "help" command
- doc = self.__class__.__doc__ # try class docstring
- if doc is None:
- # Try to provide some reasonable useful default help.
- if self.cmdlooping: prefix = ""
- else: prefix = self.name+' '
- doc = """Usage:
- %sCOMMAND [ARGS...]
- %shelp [COMMAND]
-
- ${option_list}
- ${command_list}
- ${help_list}
- """ % (prefix, prefix)
- cmdname = None
-
- if doc: # *do* have help content, massage and print that
- doc = self.help_reindent(doc)
- doc = self.help_preprocess(doc, cmdname)
- doc = doc.rstrip() + '\n' # trim down trailing space
- self.stdout.write(self._str(doc))
- self.stdout.flush()
- do_help.aliases = ["?"]
-
- def help_reindent(self, help, indent=None):
- """Hook to re-indent help strings before writing to stdout.
-
- "help" is the help content to re-indent
- "indent" is a string with which to indent each line of the
- help content after normalizing. If unspecified or None
- then the default is use: the 'self.helpindent' class
- attribute. By default this is the empty string, i.e.
- no indentation.
-
- By default, all common leading whitespace is removed and then
- the lot is indented by 'self.helpindent'. When calculating the
- common leading whitespace the first line is ignored -- hence
- help content for Conan can be written as follows and have the
- expected indentation:
-
- def do_crush(self, ...):
- '''${cmd_name}: crush your enemies, see them driven before you...
-
- c.f. Conan the Barbarian'''
- """
- if indent is None:
- indent = self.helpindent
- lines = help.splitlines(0)
- _dedentlines(lines, skip_first_line=True)
- lines = [(indent+line).rstrip() for line in lines]
- return '\n'.join(lines)
-
- def help_preprocess(self, help, cmdname):
- """Hook to preprocess a help string before writing to stdout.
-
- "help" is the help string to process.
- "cmdname" is the canonical sub-command name for which help
- is being given, or None if the help is not specific to a
- command.
-
- By default the following template variables are interpolated in
- help content. (Note: these are similar to Python 2.4's
- string.Template interpolation but not quite.)
-
- ${name}
- The tool's/shell's name, i.e. 'self.name'.
- ${option_list}
- A formatted table of options for this shell/tool.
- ${command_list}
- A formatted table of available sub-commands.
- ${help_list}
- A formatted table of additional help topics (i.e. 'help_*'
- methods with no matching 'do_*' method).
- ${cmd_name}
- The name (and aliases) for this sub-command formatted as:
- "NAME (ALIAS1, ALIAS2, ...)".
- ${cmd_usage}
- A formatted usage block inferred from the command function
- signature.
- ${cmd_option_list}
- A formatted table of options for this sub-command. (This is
- only available for commands using the optparse integration,
- i.e. using @cmdln.option decorators or manually setting the
- 'optparser' attribute on the 'do_*' method.)
-
- Returns the processed help.
- """
- preprocessors = {
- "${name}": self._help_preprocess_name,
- "${option_list}": self._help_preprocess_option_list,
- "${command_list}": self._help_preprocess_command_list,
- "${help_list}": self._help_preprocess_help_list,
- "${cmd_name}": self._help_preprocess_cmd_name,
- "${cmd_usage}": self._help_preprocess_cmd_usage,
- "${cmd_option_list}": self._help_preprocess_cmd_option_list,
- }
-
- for marker, preprocessor in preprocessors.items():
- if marker in help:
- help = preprocessor(help, cmdname)
- return help
-
- def _help_preprocess_name(self, help, cmdname=None):
- return help.replace("${name}", self.name)
-
- def _help_preprocess_option_list(self, help, cmdname=None):
- marker = "${option_list}"
- indent, indent_width = _get_indent(marker, help)
- suffix = _get_trailing_whitespace(marker, help)
-
- if self.optparser:
- # Setup formatting options and format.
- # - Indentation of 4 is better than optparse default of 2.
- # C.f. Damian Conway's discussion of this in Perl Best
- # Practices.
- self.optparser.formatter.indent_increment = 4
- self.optparser.formatter.current_indent = indent_width
- block = self.optparser.format_option_help() + '\n'
- else:
- block = ""
-
- help = help.replace(indent+marker+suffix, block, 1)
- return help
-
-
- def _help_preprocess_command_list(self, help, cmdname=None):
- marker = "${command_list}"
- indent, indent_width = _get_indent(marker, help)
- suffix = _get_trailing_whitespace(marker, help)
-
- # Find any aliases for commands.
- token2canonical = self._get_canonical_map()
- aliases = {}
- for token, cmdname in token2canonical.items():
- if token == cmdname: continue
- aliases.setdefault(cmdname, []).append(token)
-
- # Get the list of (non-hidden) commands and their
- # documentation, if any.
- cmdnames = {} # use a dict to strip duplicates
- for attr in self.get_names():
- if attr.startswith("do_"):
- cmdnames[attr[3:]] = True
- cmdnames = cmdnames.keys()
- cmdnames.sort()
- linedata = []
- for cmdname in cmdnames:
- if aliases.get(cmdname):
- a = aliases[cmdname]
- a.sort()
- cmdstr = "%s (%s)" % (cmdname, ", ".join(a))
- else:
- cmdstr = cmdname
- doc = None
- try:
- helpfunc = getattr(self, 'help_'+cmdname)
- except AttributeError:
- handler = self._get_cmd_handler(cmdname)
- if handler:
- doc = handler.__doc__
- else:
- doc = helpfunc()
-
- # Strip "${cmd_name}: " from the start of a command's doc. Best
- # practice dictates that command help strings begin with this, but
- # it isn't at all wanted for the command list.
- to_strip = "${cmd_name}:"
- if doc and doc.startswith(to_strip):
- #log.debug("stripping %r from start of %s's help string",
- # to_strip, cmdname)
- doc = doc[len(to_strip):].lstrip()
- linedata.append( (cmdstr, doc) )
-
- if linedata:
- subindent = indent + ' '*4
- lines = _format_linedata(linedata, subindent, indent_width+4)
- block = indent + "Commands:\n" \
- + '\n'.join(lines) + "\n\n"
- help = help.replace(indent+marker+suffix, block, 1)
- return help
-
- def _gen_names_and_attrs(self):
- # Inheritance says we have to look in class and
- # base classes; order is not important.
- names = []
- classes = [self.__class__]
- while classes:
- aclass = classes.pop(0)
- if aclass.__bases__:
- classes = classes + list(aclass.__bases__)
- for name in dir(aclass):
- yield (name, getattr(aclass, name))
-
- def _help_preprocess_help_list(self, help, cmdname=None):
- marker = "${help_list}"
- indent, indent_width = _get_indent(marker, help)
- suffix = _get_trailing_whitespace(marker, help)
-
- # Determine the additional help topics, if any.
- helpnames = {}
- token2cmdname = self._get_canonical_map()
- for attrname, attr in self._gen_names_and_attrs():
- if not attrname.startswith("help_"): continue
- helpname = attrname[5:]
- if helpname not in token2cmdname:
- helpnames[helpname] = attr
-
- if helpnames:
- linedata = [(n, a.__doc__ or "") for n, a in helpnames.items()]
- linedata.sort()
-
- subindent = indent + ' '*4
- lines = _format_linedata(linedata, subindent, indent_width+4)
- block = (indent
- + "Additional help topics (run `%s help TOPIC'):\n" % self.name
- + '\n'.join(lines)
- + "\n\n")
- else:
- block = ''
- help = help.replace(indent+marker+suffix, block, 1)
- return help
-
- def _help_preprocess_cmd_name(self, help, cmdname=None):
- marker = "${cmd_name}"
- handler = self._get_cmd_handler(cmdname)
- if not handler:
- raise CmdlnError("cannot preprocess '%s' into help string: "
- "could not find command handler for %r"
- % (marker, cmdname))
- s = cmdname
- if hasattr(handler, "aliases"):
- s += " (%s)" % (", ".join(handler.aliases))
- help = help.replace(marker, s)
- return help
-
- #TODO: this only makes sense as part of the Cmdln class.
- # Add hooks to add help preprocessing template vars and put
- # this one on that class.
- def _help_preprocess_cmd_usage(self, help, cmdname=None):
- marker = "${cmd_usage}"
- handler = self._get_cmd_handler(cmdname)
- if not handler:
- raise CmdlnError("cannot preprocess '%s' into help string: "
- "could not find command handler for %r"
- % (marker, cmdname))
- indent, indent_width = _get_indent(marker, help)
- suffix = _get_trailing_whitespace(marker, help)
-
- # Extract the introspection bits we need.
- func = handler.im_func
- if func.func_defaults:
- func_defaults = list(func.func_defaults)
- else:
- func_defaults = []
- co_argcount = func.func_code.co_argcount
- co_varnames = func.func_code.co_varnames
- co_flags = func.func_code.co_flags
- CO_FLAGS_ARGS = 4
- CO_FLAGS_KWARGS = 8
-
- # Adjust argcount for possible *args and **kwargs arguments.
- argcount = co_argcount
- if co_flags & CO_FLAGS_ARGS: argcount += 1
- if co_flags & CO_FLAGS_KWARGS: argcount += 1
-
- # Determine the usage string.
- usage = "%s %s" % (self.name, cmdname)
- if argcount <= 2: # handler ::= do_FOO(self, argv)
- usage += " [ARGS...]"
- elif argcount >= 3: # handler ::= do_FOO(self, subcmd, opts, ...)
- argnames = list(co_varnames[3:argcount])
- tail = ""
- if co_flags & CO_FLAGS_KWARGS:
- name = argnames.pop(-1)
- import warnings
- # There is no generally accepted mechanism for passing
- # keyword arguments from the command line. Could
- # *perhaps* consider: arg=value arg2=value2 ...
- warnings.warn("argument '**%s' on '%s.%s' command "
- "handler will never get values"
- % (name, self.__class__.__name__,
- func.func_name))
- if co_flags & CO_FLAGS_ARGS:
- name = argnames.pop(-1)
- tail = "[%s...]" % name.upper()
- while func_defaults:
- func_defaults.pop(-1)
- name = argnames.pop(-1)
- tail = "[%s%s%s]" % (name.upper(), (tail and ' ' or ''), tail)
- while argnames:
- name = argnames.pop(-1)
- tail = "%s %s" % (name.upper(), tail)
- usage += ' ' + tail
-
- block_lines = [
- self.helpindent + "Usage:",
- self.helpindent + ' '*4 + usage
- ]
- block = '\n'.join(block_lines) + '\n\n'
-
- help = help.replace(indent+marker+suffix, block, 1)
- return help
-
- #TODO: this only makes sense as part of the Cmdln class.
- # Add hooks to add help preprocessing template vars and put
- # this one on that class.
- def _help_preprocess_cmd_option_list(self, help, cmdname=None):
- marker = "${cmd_option_list}"
- handler = self._get_cmd_handler(cmdname)
- if not handler:
- raise CmdlnError("cannot preprocess '%s' into help string: "
- "could not find command handler for %r"
- % (marker, cmdname))
- indent, indent_width = _get_indent(marker, help)
- suffix = _get_trailing_whitespace(marker, help)
- if hasattr(handler, "optparser"):
- # Setup formatting options and format.
- # - Indentation of 4 is better than optparse default of 2.
- # C.f. Damian Conway's discussion of this in Perl Best
- # Practices.
- handler.optparser.formatter.indent_increment = 4
- handler.optparser.formatter.current_indent = indent_width
- block = handler.optparser.format_option_help() + '\n'
- else:
- block = ""
-
- help = help.replace(indent+marker+suffix, block, 1)
- return help
-
- def _get_canonical_cmd_name(self, token):
- map = self._get_canonical_map()
- return map.get(token, None)
-
- def _get_canonical_map(self):
- """Return a mapping of available command names and aliases to
- their canonical command name.
- """
- cacheattr = "_token2canonical"
- if not hasattr(self, cacheattr):
- # Get the list of commands and their aliases, if any.
- token2canonical = {}
- cmd2funcname = {} # use a dict to strip duplicates
- for attr in self.get_names():
- if attr.startswith("do_"): cmdname = attr[3:]
- elif attr.startswith("_do_"): cmdname = attr[4:]
- else:
- continue
- cmd2funcname[cmdname] = attr
- token2canonical[cmdname] = cmdname
- for cmdname, funcname in cmd2funcname.items(): # add aliases
- func = getattr(self, funcname)
- aliases = getattr(func, "aliases", [])
- for alias in aliases:
- if alias in cmd2funcname:
- import warnings
- warnings.warn("'%s' alias for '%s' command conflicts "
- "with '%s' handler"
- % (alias, cmdname, cmd2funcname[alias]))
- continue
- token2canonical[alias] = cmdname
- setattr(self, cacheattr, token2canonical)
- return getattr(self, cacheattr)
-
- def _get_cmd_handler(self, cmdname):
- handler = None
- try:
- handler = getattr(self, 'do_' + cmdname)
- except AttributeError:
- try:
- # Private command handlers begin with "_do_".
- handler = getattr(self, '_do_' + cmdname)
- except AttributeError:
- pass
- return handler
-
- def _do_EOF(self, argv):
- # Default EOF handler
- # Note: an actual EOF is redirected to this command.
- #TODO: separate name for this. Currently it is available from
- # command-line. Is that okay?
- self.stdout.write('\n')
- self.stdout.flush()
- self.stop = True
-
- def emptyline(self):
- # Different from cmd.Cmd: don't repeat the last command for an
- # emptyline.
- if self.cmdlooping:
- pass
- else:
- return self.do_help(["help"])
-
-
-#---- optparse.py extension to fix (IMO) some deficiencies
-#
-# See the class _OptionParserEx docstring for details.
-#
-
-class StopOptionProcessing(Exception):
- """Indicate that option *and argument* processing should stop
- cleanly. This is not an error condition. It is similar in spirit to
- StopIteration. This is raised by _OptionParserEx's default "help"
- and "version" option actions and can be raised by custom option
- callbacks too.
-
- Hence the typical CmdlnOptionParser (a subclass of _OptionParserEx)
- usage is:
-
- parser = CmdlnOptionParser(mycmd)
- parser.add_option("-f", "--force", dest="force")
- ...
- try:
- opts, args = parser.parse_args()
- except StopOptionProcessing:
- # normal termination, "--help" was probably given
- sys.exit(0)
- """
-
-class _OptionParserEx(optparse.OptionParser):
- """An optparse.OptionParser that uses exceptions instead of sys.exit.
-
- This class is an extension of optparse.OptionParser that differs
- as follows:
- - Correct (IMO) the default OptionParser error handling to never
- sys.exit(). Instead OptParseError exceptions are passed through.
- - Add the StopOptionProcessing exception (a la StopIteration) to
- indicate normal termination of option processing.
- See StopOptionProcessing's docstring for details.
-
- I'd also like to see the following in the core optparse.py, perhaps
- as a RawOptionParser which would serve as a base class for the more
- generally used OptionParser (that works as current):
- - Remove the implicit addition of the -h|--help and --version
- options. They can get in the way (e.g. if want '-?' and '-V' for
- these as well) and it is not hard to do:
- optparser.add_option("-h", "--help", action="help")
- optparser.add_option("--version", action="version")
- These are good practices, just not valid defaults if they can
- get in the way.
- """
- def error(self, msg):
- raise optparse.OptParseError(msg)
-
- def exit(self, status=0, msg=None):
- if status == 0:
- raise StopOptionProcessing(msg)
- else:
- #TODO: don't lose status info here
- raise optparse.OptParseError(msg)
-
-
-
-#---- optparse.py-based option processing support
-
-class CmdlnOptionParser(_OptionParserEx):
- """An optparse.OptionParser class more appropriate for top-level
- Cmdln options. For parsing of sub-command options, see
- SubCmdOptionParser.
-
- Changes:
- - disable_interspersed_args() by default, because a Cmdln instance
- has sub-commands which may themselves have options.
- - Redirect print_help() to the Cmdln.do_help() which is better
- equiped to handle the "help" action.
- - error() will raise a CmdlnUserError: OptionParse.error() is meant
- to be called for user errors. Raising a well-known error here can
- make error handling clearer.
- - Also see the changes in _OptionParserEx.
- """
- def __init__(self, cmdln, **kwargs):
- self.cmdln = cmdln
- kwargs["prog"] = self.cmdln.name
- _OptionParserEx.__init__(self, **kwargs)
- self.disable_interspersed_args()
-
- def print_help(self, file=None):
- self.cmdln.onecmd(["help"])
-
- def error(self, msg):
- raise CmdlnUserError(msg)
-
-
-class SubCmdOptionParser(_OptionParserEx):
- def set_cmdln_info(self, cmdln, subcmd):
- """Called by Cmdln to pass relevant info about itself needed
- for print_help().
- """
- self.cmdln = cmdln
- self.subcmd = subcmd
-
- def print_help(self, file=None):
- self.cmdln.onecmd(["help", self.subcmd])
-
- def error(self, msg):
- raise CmdlnUserError(msg)
-
-
-def option(*args, **kwargs):
- """Decorator to add an option to the optparser argument of a Cmdln
- subcommand.
-
- Example:
- class MyShell(cmdln.Cmdln):
- @cmdln.option("-f", "--force", help="force removal")
- def do_remove(self, subcmd, opts, *args):
- #...
- """
- #XXX Is there a possible optimization for many options to not have a
- # large stack depth here?
- def decorate(f):
- if not hasattr(f, "optparser"):
- f.optparser = SubCmdOptionParser()
- f.optparser.add_option(*args, **kwargs)
- return f
- return decorate
-
-
-class Cmdln(RawCmdln):
- """An improved (on cmd.Cmd) framework for building multi-subcommand
- scripts (think "svn" & "cvs") and simple shells (think "pdb" and
- "gdb").
-
- A simple example:
-
- import cmdln
-
- class MySVN(cmdln.Cmdln):
- name = "svn"
-
- @cmdln.aliases('stat', 'st')
- @cmdln.option('-v', '--verbose', action='store_true'
- help='print verbose information')
- def do_status(self, subcmd, opts, *paths):
- print "handle 'svn status' command"
-
- #...
-
- if __name__ == "__main__":
- shell = MySVN()
- retval = shell.main()
- sys.exit(retval)
-
- 'Cmdln' extends 'RawCmdln' by providing optparse option processing
- integration. See this class' _dispatch_cmd() docstring and
- <http://trentm.com/projects/cmdln> for more information.
- """
- def _dispatch_cmd(self, handler, argv):
- """Introspect sub-command handler signature to determine how to
- dispatch the command. The raw handler provided by the base
- 'RawCmdln' class is still supported:
-
- def do_foo(self, argv):
- # 'argv' is the vector of command line args, argv[0] is
- # the command name itself (i.e. "foo" or an alias)
- pass
-
- In addition, if the handler has more than 2 arguments option
- processing is automatically done (using optparse):
-
- @cmdln.option('-v', '--verbose', action='store_true')
- def do_bar(self, subcmd, opts, *args):
- # subcmd = <"bar" or an alias>
- # opts = <an optparse.Values instance>
- if opts.verbose:
- print "lots of debugging output..."
- # args = <tuple of arguments>
- for arg in args:
- bar(arg)
-
- TODO: explain that "*args" can be other signatures as well.
-
- The `cmdln.option` decorator corresponds to an `add_option()`
- method call on an `optparse.OptionParser` instance.
-
- You can declare a specific number of arguments:
-
- @cmdln.option('-v', '--verbose', action='store_true')
- def do_bar2(self, subcmd, opts, bar_one, bar_two):
- #...
-
- and an appropriate error message will be raised/printed if the
- command is called with a different number of args.
- """
- co_argcount = handler.im_func.func_code.co_argcount
- if co_argcount == 2: # handler ::= do_foo(self, argv)
- return handler(argv)
- elif co_argcount >= 3: # handler ::= do_foo(self, subcmd, opts, ...)
- try:
- optparser = handler.optparser
- except AttributeError:
- optparser = handler.im_func.optparser = SubCmdOptionParser()
- assert isinstance(optparser, SubCmdOptionParser)
- optparser.set_cmdln_info(self, argv[0])
- try:
- opts, args = optparser.parse_args(argv[1:])
- except StopOptionProcessing:
- #TODO: this doesn't really fly for a replacement of
- # optparse.py behaviour, does it?
- return 0 # Normal command termination
-
- try:
- return handler(argv[0], opts, *args)
- except TypeError, ex:
- # Some TypeError's are user errors:
- # do_foo() takes at least 4 arguments (3 given)
- # do_foo() takes at most 5 arguments (6 given)
- # do_foo() takes exactly 5 arguments (6 given)
- # Raise CmdlnUserError for these with a suitably
- # massaged error message.
- import sys
- tb = sys.exc_info()[2] # the traceback object
- if tb.tb_next is not None:
- # If the traceback is more than one level deep, then the
- # TypeError do *not* happen on the "handler(...)" call
- # above. In that we don't want to handle it specially
- # here: it would falsely mask deeper code errors.
- raise
- msg = ex.args[0]
- match = _INCORRECT_NUM_ARGS_RE.search(msg)
- if match:
- msg = list(match.groups())
- msg[1] = int(msg[1]) - 3
- if msg[1] == 1:
- msg[2] = msg[2].replace("arguments", "argument")
- msg[3] = int(msg[3]) - 3
- msg = ''.join(map(str, msg))
- raise CmdlnUserError(msg)
- else:
- raise
- else:
- raise CmdlnError("incorrect argcount for %s(): takes %d, must "
- "take 2 for 'argv' signature or 3+ for 'opts' "
- "signature" % (handler.__name__, co_argcount))
-
-
-
-#---- internal support functions
-
-def _format_linedata(linedata, indent, indent_width):
- """Format specific linedata into a pleasant layout.
-
- "linedata" is a list of 2-tuples of the form:
- (<item-display-string>, <item-docstring>)
- "indent" is a string to use for one level of indentation
- "indent_width" is a number of columns by which the
- formatted data will be indented when printed.
-
- The <item-display-string> column is held to 15 columns.
- """
- lines = []
- WIDTH = 78 - indent_width
- SPACING = 2
- NAME_WIDTH_LOWER_BOUND = 13
- NAME_WIDTH_UPPER_BOUND = 16
- NAME_WIDTH = max([len(s) for s,d in linedata])
- if NAME_WIDTH < NAME_WIDTH_LOWER_BOUND:
- NAME_WIDTH = NAME_WIDTH_LOWER_BOUND
- else:
- NAME_WIDTH = NAME_WIDTH_UPPER_BOUND
-
- DOC_WIDTH = WIDTH - NAME_WIDTH - SPACING
- for namestr, doc in linedata:
- line = indent + namestr
- if len(namestr) <= NAME_WIDTH:
- line += ' ' * (NAME_WIDTH + SPACING - len(namestr))
- else:
- lines.append(line)
- line = indent + ' ' * (NAME_WIDTH + SPACING)
- line += _summarize_doc(doc, DOC_WIDTH)
- lines.append(line.rstrip())
- return lines
-
-def _summarize_doc(doc, length=60):
- r"""Parse out a short one line summary from the given doclines.
-
- "doc" is the doc string to summarize.
- "length" is the max length for the summary
-
- >>> _summarize_doc("this function does this")
- 'this function does this'
- >>> _summarize_doc("this function does this", 10)
- 'this fu...'
- >>> _summarize_doc("this function does this\nand that")
- 'this function does this and that'
- >>> _summarize_doc("this function does this\n\nand that")
- 'this function does this'
- """
- import re
- if doc is None:
- return ""
- assert length > 3, "length <= 3 is absurdly short for a doc summary"
- doclines = doc.strip().splitlines(0)
- if not doclines:
- return ""
-
- summlines = []
- for i, line in enumerate(doclines):
- stripped = line.strip()
- if not stripped:
- break
- summlines.append(stripped)
- if len(''.join(summlines)) >= length:
- break
-
- summary = ' '.join(summlines)
- if len(summary) > length:
- summary = summary[:length-3] + "..."
- return summary
-
-
-def line2argv(line):
- r"""Parse the given line into an argument vector.
-
- "line" is the line of input to parse.
-
- This may get niggly when dealing with quoting and escaping. The
- current state of this parsing may not be completely thorough/correct
- in this respect.
-
- >>> from cmdln import line2argv
- >>> line2argv("foo")
- ['foo']
- >>> line2argv("foo bar")
- ['foo', 'bar']
- >>> line2argv("foo bar ")
- ['foo', 'bar']
- >>> line2argv(" foo bar")
- ['foo', 'bar']
-
- Quote handling:
-
- >>> line2argv("'foo bar'")
- ['foo bar']
- >>> line2argv('"foo bar"')
- ['foo bar']
- >>> line2argv(r'"foo\"bar"')
- ['foo"bar']
- >>> line2argv("'foo bar' spam")
- ['foo bar', 'spam']
- >>> line2argv("'foo 'bar spam")
- ['foo bar', 'spam']
-
- >>> line2argv('some\tsimple\ttests')
- ['some', 'simple', 'tests']
- >>> line2argv('a "more complex" test')
- ['a', 'more complex', 'test']
- >>> line2argv('a more="complex test of " quotes')
- ['a', 'more=complex test of ', 'quotes']
- >>> line2argv('a more" complex test of " quotes')
- ['a', 'more complex test of ', 'quotes']
- >>> line2argv('an "embedded \\"quote\\""')
- ['an', 'embedded "quote"']
-
- # Komodo bug 48027
- >>> line2argv('foo bar C:\\')
- ['foo', 'bar', 'C:\\']
-
- # Komodo change 127581
- >>> line2argv(r'"\test\slash" "foo bar" "foo\"bar"')
- ['\\test\\slash', 'foo bar', 'foo"bar']
-
- # Komodo change 127629
- >>> if sys.platform == "win32":
- ... line2argv(r'\foo\bar') == ['\\foo\\bar']
- ... line2argv(r'\\foo\\bar') == ['\\\\foo\\\\bar']
- ... line2argv('"foo') == ['foo']
- ... else:
- ... line2argv(r'\foo\bar') == ['foobar']
- ... line2argv(r'\\foo\\bar') == ['\\foo\\bar']
- ... try:
- ... line2argv('"foo')
- ... except ValueError, ex:
- ... "not terminated" in str(ex)
- True
- True
- True
- """
- import string
- line = line.strip()
- argv = []
- state = "default"
- arg = None # the current argument being parsed
- i = -1
- while 1:
- i += 1
- if i >= len(line): break
- ch = line[i]
-
- if ch == "\\" and i+1 < len(line):
- # escaped char always added to arg, regardless of state
- if arg is None: arg = ""
- if (sys.platform == "win32"
- or state in ("double-quoted", "single-quoted")
- ) and line[i+1] not in tuple('"\''):
- arg += ch
- i += 1
- arg += line[i]
- continue
-
- if state == "single-quoted":
- if ch == "'":
- state = "default"
- else:
- arg += ch
- elif state == "double-quoted":
- if ch == '"':
- state = "default"
- else:
- arg += ch
- elif state == "default":
- if ch == '"':
- if arg is None: arg = ""
- state = "double-quoted"
- elif ch == "'":
- if arg is None: arg = ""
- state = "single-quoted"
- elif ch in string.whitespace:
- if arg is not None:
- argv.append(arg)
- arg = None
- else:
- if arg is None: arg = ""
- arg += ch
- if arg is not None:
- argv.append(arg)
- if not sys.platform == "win32" and state != "default":
- raise ValueError("command line is not terminated: unfinished %s "
- "segment" % state)
- return argv
-
-
-def argv2line(argv):
- r"""Put together the given argument vector into a command line.
-
- "argv" is the argument vector to process.
-
- >>> from cmdln import argv2line
- >>> argv2line(['foo'])
- 'foo'
- >>> argv2line(['foo', 'bar'])
- 'foo bar'
- >>> argv2line(['foo', 'bar baz'])
- 'foo "bar baz"'
- >>> argv2line(['foo"bar'])
- 'foo"bar'
- >>> print argv2line(['foo" bar'])
- 'foo" bar'
- >>> print argv2line(["foo' bar"])
- "foo' bar"
- >>> argv2line(["foo'bar"])
- "foo'bar"
- """
- escapedArgs = []
- for arg in argv:
- if ' ' in arg and '"' not in arg:
- arg = '"'+arg+'"'
- elif ' ' in arg and "'" not in arg:
- arg = "'"+arg+"'"
- elif ' ' in arg:
- arg = arg.replace('"', r'\"')
- arg = '"'+arg+'"'
- escapedArgs.append(arg)
- return ' '.join(escapedArgs)
-
-
-# Recipe: dedent (0.1) in /Users/trentm/tm/recipes/cookbook
-def _dedentlines(lines, tabsize=8, skip_first_line=False):
- """_dedentlines(lines, tabsize=8, skip_first_line=False) -> dedented lines
-
- "lines" is a list of lines to dedent.
- "tabsize" is the tab width to use for indent width calculations.
- "skip_first_line" is a boolean indicating if the first line should
- be skipped for calculating the indent width and for dedenting.
- This is sometimes useful for docstrings and similar.
-
- Same as dedent() except operates on a sequence of lines. Note: the
- lines list is modified **in-place**.
- """
- DEBUG = False
- if DEBUG:
- print "dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\
- % (tabsize, skip_first_line)
- indents = []
- margin = None
- for i, line in enumerate(lines):
- if i == 0 and skip_first_line: continue
- indent = 0
- for ch in line:
- if ch == ' ':
- indent += 1
- elif ch == '\t':
- indent += tabsize - (indent % tabsize)
- elif ch in '\r\n':
- continue # skip all-whitespace lines
- else:
- break
- else:
- continue # skip all-whitespace lines
- if DEBUG: print "dedent: indent=%d: %r" % (indent, line)
- if margin is None:
- margin = indent
- else:
- margin = min(margin, indent)
- if DEBUG: print "dedent: margin=%r" % margin
-
- if margin is not None and margin > 0:
- for i, line in enumerate(lines):
- if i == 0 and skip_first_line: continue
- removed = 0
- for j, ch in enumerate(line):
- if ch == ' ':
- removed += 1
- elif ch == '\t':
- removed += tabsize - (removed % tabsize)
- elif ch in '\r\n':
- if DEBUG: print "dedent: %r: EOL -> strip up to EOL" % line
- lines[i] = lines[i][j:]
- break
- else:
- raise ValueError("unexpected non-whitespace char %r in "
- "line %r while removing %d-space margin"
- % (ch, line, margin))
- if DEBUG:
- print "dedent: %r: %r -> removed %d/%d"\
- % (line, ch, removed, margin)
- if removed == margin:
- lines[i] = lines[i][j+1:]
- break
- elif removed > margin:
- lines[i] = ' '*(removed-margin) + lines[i][j+1:]
- break
- return lines
-
-def _dedent(text, tabsize=8, skip_first_line=False):
- """_dedent(text, tabsize=8, skip_first_line=False) -> dedented text
-
- "text" is the text to dedent.
- "tabsize" is the tab width to use for indent width calculations.
- "skip_first_line" is a boolean indicating if the first line should
- be skipped for calculating the indent width and for dedenting.
- This is sometimes useful for docstrings and similar.
-
- textwrap.dedent(s), but don't expand tabs to spaces
- """
- lines = text.splitlines(1)
- _dedentlines(lines, tabsize=tabsize, skip_first_line=skip_first_line)
- return ''.join(lines)
-
-
-def _get_indent(marker, s, tab_width=8):
- """_get_indent(marker, s, tab_width=8) ->
- (<indentation-of-'marker'>, <indentation-width>)"""
- # Figure out how much the marker is indented.
- INDENT_CHARS = tuple(' \t')
- start = s.index(marker)
- i = start
- while i > 0:
- if s[i-1] not in INDENT_CHARS:
- break
- i -= 1
- indent = s[i:start]
- indent_width = 0
- for ch in indent:
- if ch == ' ':
- indent_width += 1
- elif ch == '\t':
- indent_width += tab_width - (indent_width % tab_width)
- return indent, indent_width
-
-def _get_trailing_whitespace(marker, s):
- """Return the whitespace content trailing the given 'marker' in string 's',
- up to and including a newline.
- """
- suffix = ''
- start = s.index(marker) + len(marker)
- i = start
- while i < len(s):
- if s[i] in ' \t':
- suffix += s[i]
- elif s[i] in '\r\n':
- suffix += s[i]
- if s[i] == '\r' and i+1 < len(s) and s[i+1] == '\n':
- suffix += s[i+1]
- break
- else:
- break
- i += 1
- return suffix
-
-
-
-#---- bash completion support
-# Note: This is still experimental. I expect to change this
-# significantly.
-#
-# To get Bash completion for a cmdln.Cmdln class, run the following
-# bash command:
-# $ complete -C 'python -m cmdln /path/to/script.py CmdlnClass' cmdname
-# For example:
-# $ complete -C 'python -m cmdln ~/bin/svn.py SVN' svn
-#
-#TODO: Simplify the above so don't have to given path to script (try to
-# find it on PATH, if possible). Could also make class name
-# optional if there is only one in the module (common case).
-
-if __name__ == "__main__" and len(sys.argv) == 6:
- def _log(s):
- return # no-op, comment out for debugging
- from os.path import expanduser
- fout = open(expanduser("~/tmp/bashcpln.log"), 'a')
- fout.write(str(s) + '\n')
- fout.close()
-
- # Recipe: module_from_path (1.0.1+)
- def _module_from_path(path):
- import imp, os, sys
- path = os.path.expanduser(path)
- dir = os.path.dirname(path) or os.curdir
- name = os.path.splitext(os.path.basename(path))[0]
- sys.path.insert(0, dir)
- try:
- iinfo = imp.find_module(name, [dir])
- return imp.load_module(name, *iinfo)
- finally:
- sys.path.remove(dir)
-
- def _get_bash_cplns(script_path, class_name, cmd_name,
- token, preceding_token):
- _log('--')
- _log('get_cplns(%r, %r, %r, %r, %r)'
- % (script_path, class_name, cmd_name, token, preceding_token))
- comp_line = os.environ["COMP_LINE"]
- comp_point = int(os.environ["COMP_POINT"])
- _log("COMP_LINE: %r" % comp_line)
- _log("COMP_POINT: %r" % comp_point)
-
- try:
- script = _module_from_path(script_path)
- except ImportError, ex:
- _log("error importing `%s': %s" % (script_path, ex))
- return []
- shell = getattr(script, class_name)()
- cmd_map = shell._get_canonical_map()
- del cmd_map["EOF"]
-
- # Determine if completing the sub-command name.
- parts = comp_line[:comp_point].split(None, 1)
- _log(parts)
- if len(parts) == 1 or not (' ' in parts[1] or '\t' in parts[1]):
- #TODO: if parts[1].startswith('-'): handle top-level opts
- _log("complete sub-command names")
- matches = {}
- for name, canon_name in cmd_map.items():
- if name.startswith(token):
- matches[name] = canon_name
- if not matches:
- return []
- elif len(matches) == 1:
- return matches.keys()
- elif len(set(matches.values())) == 1:
- return [matches.values()[0]]
- else:
- return matches.keys()
-
- # Otherwise, complete options for the given sub-command.
- #TODO: refine this so it does the right thing with option args
- if token.startswith('-'):
- cmd_name = comp_line.split(None, 2)[1]
- try:
- cmd_canon_name = cmd_map[cmd_name]
- except KeyError:
- return []
- handler = shell._get_cmd_handler(cmd_canon_name)
- optparser = getattr(handler, "optparser", None)
- if optparser is None:
- optparser = SubCmdOptionParser()
- opt_strs = []
- for option in optparser.option_list:
- for opt_str in option._short_opts + option._long_opts:
- if opt_str.startswith(token):
- opt_strs.append(opt_str)
- return opt_strs
-
- return []
-
- for cpln in _get_bash_cplns(*sys.argv[1:]):
- print cpln
-
diff --git a/packaging/mic.spec b/packaging/mic.spec
index 1c7ffac..3756dbf 100644
--- a/packaging/mic.spec
+++ b/packaging/mic.spec
@@ -1,4 +1,5 @@
%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
+%{!?python_version: %define python_version %(%{__python} -c "import sys; sys.stdout.write(sys.version[:3])")}
%define rc_version 0
@@ -22,6 +23,10 @@ Requires: python-urlgrabber >= 3.9.0
Requires: python-xml
%endif
+%if "%{?python_version}" < "2.7"
+Requires: python-argparse
+%endif
+
%if 0%{?tizen_version:1}
Requires: python-rpm
%else
diff --git a/plugins/imager/fs_plugin.py b/plugins/imager/fs_plugin.py
index faee633..d74530f 100644
--- a/plugins/imager/fs_plugin.py
+++ b/plugins/imager/fs_plugin.py
@@ -16,7 +16,7 @@
# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
from mic import chroot, msger, rt_util
-from mic.utils import cmdln, misc, errors, fs_related
+from mic.utils import misc, errors, fs_related
from mic.imager import fs
from mic.conf import configmgr
from mic.plugin import pluginmgr
@@ -26,12 +26,7 @@ class FsPlugin(ImagerPlugin):
name = 'fs'
@classmethod
- @cmdln.option("--include-src",
- dest = "include_src",
- action = "store_true",
- default = False,
- help = "Generate a image with source rpms included")
- def do_create(self, subcmd, opts, *args):
+ def do_create(self, args):
"""${cmd_name}: create fs image
Usage:
@@ -40,11 +35,11 @@ class FsPlugin(ImagerPlugin):
${cmd_option_list}
"""
- if len(args) != 1:
- raise errors.Usage("Extra arguments given")
+ if args is None:
+ raise errors.Usage("Invalid arguments.")
creatoropts = configmgr.create
- ksconf = args[0]
+ ksconf = args.ksfile
if creatoropts['runtime'] == 'bootstrap':
configmgr._ksconf = ksconf
@@ -83,7 +78,7 @@ class FsPlugin(ImagerPlugin):
','.join(backends.keys())))
creator = fs.FsImageCreator(creatoropts, pkgmgr)
- creator._include_src = opts.include_src
+ creator._include_src = args.include_src
if len(recording_pkgs) > 0:
creator._recording_pkgs = recording_pkgs
@@ -98,7 +93,7 @@ class FsPlugin(ImagerPlugin):
creator.mount(None, creatoropts["cachedir"])
creator.install()
#Download the source packages ###private options
- if opts.include_src:
+ if args.include_src:
installed_pkgs = creator.get_installed_packages()
msger.info('--------------------------------------------------')
msger.info('Generating the image with source rpms included ...')
diff --git a/plugins/imager/loop_plugin.py b/plugins/imager/loop_plugin.py
index f0f2771..1830230 100644
--- a/plugins/imager/loop_plugin.py
+++ b/plugins/imager/loop_plugin.py
@@ -20,7 +20,7 @@ import shutil
import tempfile
from mic import chroot, msger, rt_util
-from mic.utils import misc, fs_related, errors, cmdln
+from mic.utils import misc, fs_related, errors
from mic.conf import configmgr
from mic.plugin import pluginmgr
from mic.imager.loop import LoopImageCreator, load_mountpoints
@@ -30,16 +30,7 @@ class LoopPlugin(ImagerPlugin):
name = 'loop'
@classmethod
- @cmdln.option("--compress-disk-image", dest="compress_image",
- type='choice', choices=("gz", "bz2"), default=None,
- help="Same with --compress-image")
- # alias to compress-image for compatibility
- @cmdln.option("--compress-image", dest="compress_image",
- type='choice', choices=("gz", "bz2"), default=None,
- help="Compress all loop images with 'gz' or 'bz2'")
- @cmdln.option("--shrink", action='store_true', default=False,
- help="Whether to shrink loop images to minimal size")
- def do_create(self, subcmd, opts, *args):
+ def do_create(self, args):
"""${cmd_name}: create loop image
Usage:
@@ -48,16 +39,16 @@ class LoopPlugin(ImagerPlugin):
${cmd_option_list}
"""
- if len(args) != 1:
- raise errors.Usage("Extra arguments given")
+ if args is None:
+ raise errors.Usage("Invalid arguments")
creatoropts = configmgr.create
- ksconf = args[0]
+ ksconf = args.ksfile
if creatoropts['runtime'] == "bootstrap":
configmgr._ksconf = ksconf
rt_util.bootstrap_mic()
-
+
recording_pkgs = []
if len(creatoropts['record_pkgs']) > 0:
recording_pkgs = creatoropts['record_pkgs']
@@ -92,8 +83,8 @@ class LoopPlugin(ImagerPlugin):
creator = LoopImageCreator(creatoropts,
pkgmgr,
- opts.compress_image,
- opts.shrink)
+ args.compress_image,
+ args.shrink)
if len(recording_pkgs) > 0:
creator._recording_pkgs = recording_pkgs
@@ -228,8 +219,8 @@ class LoopPlugin(ImagerPlugin):
raise
try:
- if len(cmd) != 0:
- cmdline = ' '.join(cmd)
+ if cmd is not None:
+ cmdline = cmd
else:
cmdline = "/bin/bash"
envcmd = fs_related.find_binary_inchroot("env", extmnt)
diff --git a/plugins/imager/qcow_plugin.py b/plugins/imager/qcow_plugin.py
index 17b707c..8acf572 100644
--- a/plugins/imager/qcow_plugin.py
+++ b/plugins/imager/qcow_plugin.py
@@ -64,7 +64,7 @@ class QcowPlugin(ImagerPlugin):
name = 'qcow'
@classmethod
- def do_create(cls, subcmd, opts, *args):
+ def do_create(cls, args):
"""${cmd_name}: create qcow image
Usage:
@@ -72,11 +72,11 @@ class QcowPlugin(ImagerPlugin):
${cmd_option_list}
"""
- if len(args) != 1:
- raise errors.Usage("Extra arguments given")
+ if args is None:
+ raise errors.Usage("Invalid arguments")
creatoropts = configmgr.create
- ksconf = args[0]
+ ksconf = args.ksfile
if creatoropts['runtime'] == "bootstrap":
configmgr._ksconf = ksconf
@@ -110,7 +110,7 @@ class QcowPlugin(ImagerPlugin):
if not pkgmgr:
raise errors.CreatorError("Can't find backend: %s, "
- "available choices: %s" %
+ "available choices: %s" %
(creatoropts['pkgmgr'],
','.join(backends.keys())))
diff --git a/tools/mic b/tools/mic
index 871f9bd..7785bec 100755
--- a/tools/mic
+++ b/tools/mic
@@ -30,213 +30,233 @@ import os
import sys
import errno
-from mic import msger, creator, __version__ as VERSION
-from mic.utils import cmdln, misc, errors
+from argparse import ArgumentParser, SUPPRESS
+
+from mic import msger, __version__ as VERSION
+from mic.utils import misc, errors
from mic.conf import configmgr
from mic.plugin import pluginmgr
+from mic.helpformat import MICHelpFormatter, subparser
+
+@subparser
+def chroot_parser(parser):
+ """chroot into an image
-def optparser_setup(func):
- """Setup optparser for a function"""
- if not hasattr(func, "optparser"):
- func.optparser = cmdln.SubCmdOptionParser()
- func.optparser.disable_interspersed_args()
- return func
-
-
-class MicCmd(cmdln.Cmdln):
+ Examples:
+ mic chroot platform.img
+ mic chroot platform.img ls
"""
- Usage: mic SUBCOMMAND [OPTS] [ARGS...]
-
- mic Means the Image Creation tool
- Try 'mic help SUBCOMMAND' for help on a specific subcommand.
-
- ${command_list}
- global ${option_list}
- ${help_list}
+ parser.add_argument('imagefile', help='Path of image file')
+ parser.add_argument('-s', '--saveto', action = 'store', dest = 'saveto', default = None,
+ help = "Save the unpacked image to specified dir")
+ parser.add_argument('-c', '--cmd', dest = 'cmd', default = None,
+ help = "command which will be executed in chroot environment")
+ parser.set_defaults(alias="ch")
+ return parser
+
+@subparser
+def create_parser(parser):
+ """create an image
+ Examples:
+ $ mic -d -v create auto handset_blackbay.ks
+ $ mic -d -v cr loop handset_blackbay.ks --logfile=mic.log
"""
- name = 'mic'
- version = VERSION
- def print_version(self):
+ parent_parser = ArgumentParser(add_help=False)
+ parent_parser.add_argument('ksfile', help='Path of ksfile');
+ parent_parser.add_argument('--logfile', dest='logfile', default=None,
+ help='Path of logfile')
+ parent_parser.add_argument('-c', '--config', dest='config', default=None,
+ help='Specify config file for mic')
+ parent_parser.add_argument('-k', '--cachedir', action='store',
+ dest='cachedir', default=None,
+ help='Cache directory to store the downloaded')
+ parent_parser.add_argument('-o', '--outdir', action='store', dest='outdir',
+ default=None, help='Output directory')
+ parent_parser.add_argument('-A', '--arch', dest='arch', default=None,
+ help='Specify repo architecture')
+ parent_parser.add_argument('--release', dest='release', default=None, metavar='RID',
+ help='Generate a release of RID with all necessary'
+ ' files, when @BUILD_ID@ is contained in '
+ 'kickstart file, it will be replaced by RID')
+ parent_parser.add_argument("--record-pkgs", dest="record_pkgs", default=None,
+ help='Record the info of installed packages, '
+ 'multiple values can be specified which '
+ 'joined by ",", valid values: "name", '
+ '"content", "license", "vcs"')
+ parent_parser.add_argument('--pkgmgr', dest='pkgmgr', default=None,
+ help='Specify backend package manager')
+ parent_parser.add_argument('--local-pkgs-path', dest='local_pkgs_path', default=None,
+ help='Path for local pkgs(rpms) to be installed')
+ parent_parser.add_argument('--runtime', dest='runtime', default=None,
+ help='Specify runtime mode, avaiable: bootstrap')
+ # --taring-to is alias to --pack-to
+ parent_parser.add_argument('--taring-to', dest='pack_to', default=None,
+ help=SUPPRESS)
+ parent_parser.add_argument('--pack-to', dest='pack_to', default=None,
+ help='Pack the images together into the specified'
+ ' achive, extension supported: .zip, .tar, '
+ '.tar.gz, .tar.bz2, etc. by default, .tar '
+ 'will be used')
+ parent_parser.add_argument('--copy-kernel', action='store_true', dest='copy_kernel',
+ help='Copy kernel files from image /boot directory'
+ ' to the image output directory.')
+ parent_parser.add_argument('--install-pkgs', action='store', dest='install_pkgs', default=None,
+ help='Specify what type of packages to be installed,'
+ ' valid: source, debuginfo, debugsource')
+ parent_parser.add_argument('--check-pkgs', action='store', dest='check_pkgs', default=[],
+ help='Check if given packages would be installed, '
+ 'packages should be separated by comma')
+ parent_parser.add_argument('--tmpfs', action='store_true', dest='enabletmpfs',
+ help='Setup tmpdir as tmpfs to accelerate, experimental'
+ ' feature, use it if you have more than 4G memory')
+ parent_parser.add_argument('--repourl', action='append', dest='repourl', default=[],
+ help=SUPPRESS)
+ parent_parser.add_argument('-R', '--repo', action='append',
+ dest='repo', default=[],
+ help=SUPPRESS)
+ parent_parser.add_argument('--ignore-ksrepo', action='store_true',
+ dest='ignore_ksrepo', default=False,
+ help=SUPPRESS)
+ parent_parser.add_argument('--strict-mode', action='store_true',
+ dest='strict_mode', default=False,
+ help='Abort creation of image, if there are some errors'
+ ' during rpm installation. ')
+ parser.set_defaults(alias="cr")
+
+ subparsers = parser.add_subparsers(title='Subcommands', dest='subcommand')
+ fs_parser = subparsers.add_parser('fs', parents=[parent_parser],
+ help='auto detect image type from magic header')
+ fs_parser.add_argument("--include-src", dest = "include_src",action = "store_true",
+ default = False, help = "Generate a image with source rpms included")
+
+ auto_parser = subparsers.add_parser('auto', parents=[parent_parser], help='create fs image')
+ loop_parser = subparsers.add_parser('loop', parents=[parent_parser], help='create loop image')
+
+ loop_parser.add_argument("--compress-disk-image", dest="compress_image",
+ choices=("gz", "bz2", "lzo"), default=None,
+ help="Same with --compress-image")
+ # alias to compress-image for compatibility
+ loop_parser.add_argument("--compress-image", dest="compress_image",
+ choices=("gz", "bz2", "lzo"), default=None,
+ help="Compress all loop images with 'gz' or 'bz2' or 'lzo',"
+ "Note: if you want to use 'lzo', package 'lzop' is needed to"
+ "be installed manually.")
+ loop_parser.add_argument("--shrink", action='store_true', default=False,
+ help="Whether to shrink loop images to minimal size")
+
+ qcow_parser = subparsers.add_parser('qcow', parents=[parent_parser], help='create qcow image')
+
+ return parser
+
+def main(argv):
+ """Script entry point."""
+
+ def print_version():
"""log name, verion, hostname"""
-
- msger.raw("%s %s (%s)" % (self.name,
- self.version,
+
+ name = 'mic'
+ msger.raw("%s %s (%s)" % (name,
+ VERSION,
misc.get_hostname_distro_str()))
-
- def get_optparser(self):
- optparser = cmdln.CmdlnOptionParser(self, version=self.version)
- # hook optparse print_version here
- optparser.print_version = self.print_version
- optparser.add_option('-d', '--debug', action='store_true',
- dest='debug',
- help='print debug message')
- optparser.add_option('-v', '--verbose', action='store_true',
- dest='verbose',
- help='verbose information')
- optparser.add_option('-i', '--interactive', action='store_true',
- dest='interactive', default='True',
- help='interactive output')
- optparser.add_option('--non-interactive', action='store_false',
- dest='interactive', default='True',
- help='non-interactive output')
- return optparser
-
- def postoptparse(self):
- if self.options.interactive:
- msger.enable_interactive()
- else:
- msger.disable_interactive()
-
- if self.options.verbose:
- msger.set_loglevel('VERBOSE')
-
- if self.options.debug:
- try:
- import rpm
- rpm.setVerbosity(rpm.RPMLOG_NOTICE)
- except ImportError:
- pass
-
- msger.set_loglevel('DEBUG')
-
- self.print_version()
-
- def help_create(self):
- """Get help info from doc string.
- Fill symbols with real parameters
+
+ def has_parameter(arg, arglist):
"""
- crobj = creator.Creator()
- crobj.optparser = crobj.get_optparser()
- doc = crobj.__doc__
- doc = crobj.help_reindent(doc)
- doc = crobj.help_preprocess(doc, None)
- doc = doc.replace(crobj.name, "${cmd_name}", 1)
- doc = doc.rstrip() + '\n'
- return doc
-
- @cmdln.alias("cr")
- def do_create(self, argv):
- """Main for creating image"""
- crobj = creator.Creator()
- crobj.main(argv[1:])
-
- def _root_confirm(self):
- """Make sure command is called by root
- There are a lot of commands needed to be run during creating images,
- some of them must be run with root privilege like mount, kpartx"""
- if os.geteuid() != 0:
- msger.error('Root permission is required to continue, abort')
-
- @cmdln.alias("cv")
- @cmdln.option("-S", "--shell",
- action="store_true", dest="shell", default=False,
- help="Launch shell before packaging the converted image")
- def do_convert(self, _subcmd, opts, *args):
- """${cmd_name}: convert image format
-
- Usage:
- mic convert <imagefile> <destformat>
-
- ${cmd_option_list}
+ Helper function.
+ Check if argument requires parameter by analyzing
+ its action. Parameter is required only for 'store' and 'append' actions
"""
- if not args or len(args) != 2:
- # print help
- handler = self._get_cmd_handler('convert')
- if hasattr(handler, "optparser"):
- handler.optparser.print_help()
- raise errors.Usage("2 arguments and only 2 are required")
-
- (srcimg, destformat) = args
-
- if not os.path.exists(srcimg):
- raise errors.CreatorError("Cannot find the image: %s" % srcimg)
-
- self._root_confirm()
-
- configmgr.convert['shell'] = opts.shell
-
- srcformat = misc.get_image_type(srcimg)
- if srcformat == "ext3fsimg":
- srcformat = "loop"
-
- srcimager = None
- destimager = None
- for iname, icls in pluginmgr.get_plugins('imager').iteritems():
- if iname == srcformat and hasattr(icls, "do_unpack"):
- srcimager = icls
- if iname == destformat and hasattr(icls, "do_pack"):
- destimager = icls
-
- if (srcimager and destimager) is None:
- raise errors.CreatorError("Can't convert from %s to %s" \
- % (srcformat, destformat))
- maptab = {
- "loop": "img",
- }
- if destformat in maptab:
- imgname = os.path.splitext(os.path.basename(srcimg))[0]
- dstname = "{0}.{1}".format(imgname, maptab[destformat])
- if os.path.exists(dstname):
- if msger.ask("Converted image %s seems existed, "
- "remove and continue?" % dstname):
- os.unlink(dstname)
- else:
- raise errors.Abort("Canceled")
-
- destimager.do_pack(srcimager.do_unpack(srcimg))
-
- @cmdln.alias("ch")
- @cmdln.option('-s', '--saveto',
- action = 'store', dest = 'saveto', default = None,
- help = "Save the unpacked image to specified dir")
- @optparser_setup
- def do_chroot(self, _subcmd, opts, *args):
- """${cmd_name}: chroot into an image
-
- Usage:
- mic chroot [options] <imagefile> [command [arg]...]
-
- ${cmd_option_list}
- """
- if not args:
- # print help
- handler = self._get_cmd_handler('chroot')
- if hasattr(handler, "optparser"):
- handler.optparser.print_help()
- return 1
-
- targetimage = args[0]
- if not os.path.exists(targetimage):
- raise errors.CreatorError("Cannot find the image: %s"
- % targetimage)
-
- self._root_confirm()
+ if arg.startswith('-'):
+ for args in arglist:
+ if arg in (args['short'], args['long']):
+ if args.get('action') in (None, 'store', 'append'):
+ return True
+ return False
+
+ # Create top level parser
+ epilog = "Try 'mic SUBCOMMAND --help' for help on a specific subcommand."
+ description = "mic - the Image Creation tool"
+ parser = ArgumentParser(description=description, epilog=epilog,
+ formatter_class=MICHelpFormatter)
+
+ # List of global arguments
+ # The main purpose of this structure is to contain arguments
+ # of add_argument. This is used to do aliasing properly
+ # (see code under the comment 'replace aliases with real commands')
+ global_args = [{'short': '-V', 'long': '--version', 'action': 'version',
+ 'version': '%(prog)s ' + VERSION},
+ {'short': '-d', 'long': '--debug', 'action': 'store_true',
+ 'help': 'debug output'},
+ {'short': '-v', 'long': '--verbose', 'action': 'store_true',
+ 'help': 'verbose output'},
+ {'short': '-i', 'long': '--interactive', 'action': 'store_true',
+ 'dest': 'interactive', 'default': 'True', 'help': 'interactive output'},
+ {'short': '', 'long': '--non-interactive', 'action': 'store_false',
+ 'dest': 'interactive', 'default': 'True', 'help': 'non-interactive output'},
+ ]
+
+ for args in global_args:
+ parser_kwargs = {}
+ for key in ('action', 'help', 'version', 'default', 'dest'):
+ if key in args:
+ parser_kwargs[key] = args[key]
+
+ if args['short'] is '':
+ parser.add_argument(args['long'], **parser_kwargs)
+ else:
+ parser.add_argument(args['short'], args['long'], **parser_kwargs)
+
+ # hacked by the request of cmdln lovers
+ parser.format_usage = parser.format_help
+
+ # Create parsers for subcommands
+ subparsers = parser.add_subparsers(title='subcommands')
+
+ # collect aliases
+ aliases = {}
+ for name, obj in globals().iteritems():
+ if name.endswith('_parser') and callable(obj):
+ aliases[obj(subparsers).get_default('alias')] = name.split('_')[0]
+
+ # replace aliases with real commands
+ for i, arg in enumerate(argv[1:]):
+ if not arg.startswith('-'):
+ # argv[i] is previous argument to arg
+ if not has_parameter(argv[i], global_args) and arg in aliases:
+ argv[i+1] = aliases[arg]
+ break
+
+ # Parse arguments
+ args = parser.parse_args(argv[1:])
- configmgr.chroot['saveto'] = opts.saveto
+ if args.interactive:
+ msger.enable_interactive()
+ else:
+ msger.disable_interactive()
- imagetype = misc.get_image_type(targetimage)
- if imagetype in ("ext3fsimg", "ext4fsimg", "btrfsimg"):
- imagetype = "loop"
+ if args.verbose:
+ msger.set_loglevel('VERBOSE')
- chrootclass = None
- for pname, pcls in pluginmgr.get_plugins('imager').iteritems():
- if pname == imagetype and hasattr(pcls, "do_chroot"):
- chrootclass = pcls
- break
+ if args.debug:
+ try:
+ import rpm
+ rpm.setVerbosity(rpm.RPMLOG_NOTICE)
+ except ImportError:
+ pass
- if not chrootclass:
- raise errors.CreatorError("Cannot support image type: %s" \
- % imagetype)
+ msger.set_loglevel('DEBUG')
- chrootclass.do_chroot(targetimage, args[1:])
+ print_version()
+ # Import target module and call 'main' from it
+ module = __import__("mic.%s" % args.module, fromlist=[args.module])
+ return module.main(parser, args, argv[1:])
+
if __name__ == "__main__":
try:
- MIC = MicCmd()
- sys.exit(MIC.main())
+ sys.exit(main(sys.argv))
except KeyboardInterrupt:
msger.error('\n^C catched, program aborted.')
except IOError as ioerr: