Using argparse module to parse the cmd line
[tools/mic.git] / tools / mic
index 871f9bd..7785bec 100755 (executable)
--- 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: