diff options
| author | Laurence Miao <laurence.miao@gmail.com> | 2012-10-06 21:08:14 +0800 |
|---|---|---|
| committer | Laurence Miao <laurence.miao@gmail.com> | 2012-11-15 14:18:50 +0800 |
| commit | 5f564b24be4592c5212c492e6d67d9f4616229d2 (patch) | |
| tree | e5af1ff3d589701593e809fc8fcc90c1a94a1b72 /openstack | |
| parent | d6fa3847cc64f6815641cdb549dd100e1aab6043 (diff) | |
| download | oslo-5f564b24be4592c5212c492e6d67d9f4616229d2.tar.gz oslo-5f564b24be4592c5212c492e6d67d9f4616229d2.tar.xz oslo-5f564b24be4592c5212c492e6d67d9f4616229d2.zip | |
argparse support for cfg
* openstack/common/cfg.py
Optparse is fading out since python 2.7, this
patch will help openstack/common work on more
advanded version of python(argparse).
Now, disable_interspersed_args() has no effect.
Added new method add_cli_subparsers, return
argparse subparser, for usages such as
subcommand.
* tests/unit/test_cfg.py
SubcommandTestCase added.
Disabled test_disable_interspersed_args test entry
for happiness of tox, temporarily.
Modified test_help for port of argparse.
* tools/pip-requires
include argparse module for python 2.6
Change-Id: Ie5cbde1a92a92786d64dea0ddfcfbf288c29f960
Diffstat (limited to 'openstack')
| -rw-r--r-- | openstack/common/cfg.py | 288 |
1 files changed, 220 insertions, 68 deletions
diff --git a/openstack/common/cfg.py b/openstack/common/cfg.py index 1d67514..c256ca7 100644 --- a/openstack/common/cfg.py +++ b/openstack/common/cfg.py @@ -251,11 +251,11 @@ in order to support a common usage pattern in OpenStack: """ +import argparse import collections import copy import functools import glob -import optparse import os import string import sys @@ -561,17 +561,17 @@ class Opt(object): :param parser: the CLI option parser :param group: an optional OptGroup object """ - container = self._get_optparse_container(parser, group) - kwargs = self._get_optparse_kwargs(group) - prefix = self._get_optparse_prefix('', group) - self._add_to_optparse(container, self.name, self.short, kwargs, prefix, + container = self._get_argparse_container(parser, group) + kwargs = self._get_argparse_kwargs(group) + prefix = self._get_argparse_prefix('', group) + self._add_to_argparse(container, self.name, self.short, kwargs, prefix, self.deprecated_name) - def _add_to_optparse(self, container, name, short, kwargs, prefix='', + def _add_to_argparse(self, container, name, short, kwargs, prefix='', deprecated_name=None): - """Add an option to an optparse parser or group. + """Add an option to an argparse parser or group. - :param container: an optparse.OptionContainer object + :param container: an argparse._ArgumentGroup object :param name: the opt name :param short: the short opt name :param kwargs: the keyword arguments for add_option() @@ -581,30 +581,32 @@ class Opt(object): args = ['--' + prefix + name] if short: args += ['-' + short] + if deprecated_name: args += ['--' + prefix + deprecated_name] - for a in args: - if container.has_option(a): - raise DuplicateOptError(a) - container.add_option(*args, **kwargs) - def _get_optparse_container(self, parser, group): - """Returns an optparse.OptionContainer. + try: + container.add_argument(*args, **kwargs) + except(argparse.ArgumentError) as e: + raise DuplicateOptError(e) + + def _get_argparse_container(self, parser, group): + """Returns an argparse._ArgumentGroup. - :param parser: an optparse.OptionParser + :param parser: an argparse.ArgumentParser :param group: an (optional) OptGroup object - :returns: an optparse.OptionGroup if a group is given, else the parser + :returns: an argparse._ArgumentGroup if group is given, else parser """ if group is not None: - return group._get_optparse_group(parser) + return group._get_argparse_group(parser) else: return parser - def _get_optparse_kwargs(self, group, **kwargs): - """Build a dict of keyword arguments for optparse's add_option(). + def _get_argparse_kwargs(self, group, **kwargs): + """Build a dict of keyword arguments for argparse's add_argument(). Most opt types extend this method to customize the behaviour of the - options added to optparse. + options added to argparse. :param group: an optional group :param kwargs: optional keyword arguments to add to @@ -618,7 +620,7 @@ class Opt(object): 'help': self.help, }) return kwargs - def _get_optparse_prefix(self, prefix, group): + def _get_argparse_prefix(self, prefix, group): """Build a prefix for the CLI option name, if required. CLI options in a group are prefixed with the group's name in order @@ -671,21 +673,32 @@ class BoolOpt(Opt): def _add_to_cli(self, parser, group=None): """Extends the base class method to add the --nooptname option.""" super(BoolOpt, self)._add_to_cli(parser, group) - self._add_inverse_to_optparse(parser, group) + self._add_inverse_to_argparse(parser, group) - def _add_inverse_to_optparse(self, parser, group): + def _add_inverse_to_argparse(self, parser, group): """Add the --nooptname option to the option parser.""" - container = self._get_optparse_container(parser, group) - kwargs = self._get_optparse_kwargs(group, action='store_false') - prefix = self._get_optparse_prefix('no', group) + container = self._get_argparse_container(parser, group) + kwargs = self._get_argparse_kwargs(group, action='store_false') + prefix = self._get_argparse_prefix('no', group) kwargs["help"] = "The inverse of --" + self.name - self._add_to_optparse(container, self.name, None, kwargs, prefix, + self._add_to_argparse(container, self.name, None, kwargs, prefix, self.deprecated_name) - def _get_optparse_kwargs(self, group, action='store_true', **kwargs): - """Extends the base optparse keyword dict for boolean options.""" - return super(BoolOpt, - self)._get_optparse_kwargs(group, action=action, **kwargs) + def _get_argparse_kwargs(self, group, action='store_true', **kwargs): + """Extends the base argparse keyword dict for boolean options.""" + + kwargs = super(BoolOpt, self)._get_argparse_kwargs(group, **kwargs) + + # metavar has no effect for BoolOpt + if 'metavar' in kwargs: + del kwargs['metavar'] + + if action != 'store_true': + action = 'store_false' + + kwargs['action'] = action + + return kwargs class IntOpt(Opt): @@ -697,10 +710,10 @@ class IntOpt(Opt): return [int(v) for v in self._cparser_get_with_deprecated(cparser, section)] - def _get_optparse_kwargs(self, group, **kwargs): - """Extends the base optparse keyword dict for integer options.""" + def _get_argparse_kwargs(self, group, **kwargs): + """Extends the base argparse keyword dict for integer options.""" return super(IntOpt, - self)._get_optparse_kwargs(group, type='int', **kwargs) + self)._get_argparse_kwargs(group, type=int, **kwargs) class FloatOpt(Opt): @@ -712,10 +725,10 @@ class FloatOpt(Opt): return [float(v) for v in self._cparser_get_with_deprecated(cparser, section)] - def _get_optparse_kwargs(self, group, **kwargs): - """Extends the base optparse keyword dict for float options.""" - return super(FloatOpt, - self)._get_optparse_kwargs(group, type='float', **kwargs) + def _get_argparse_kwargs(self, group, **kwargs): + """Extends the base argparse keyword dict for float options.""" + return super(FloatOpt, self)._get_argparse_kwargs(group, + type=float, **kwargs) class ListOpt(Opt): @@ -724,24 +737,38 @@ class ListOpt(Opt): List opt values are simple string values separated by commas. The opt value is a list containing these strings. """ + class _StoreListAction(argparse._StoreAction): + """ + An argparse action for parsing an option value into a list. + """ + def __init__(self, + option_strings, + dest, + default=None, + required=False, + help=None, + metavar=None): + argparse._StoreAction.__init__(self, + option_strings=option_strings, + dest=dest, + default=default, + required=required, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, values.split(',')) def _get_from_config_parser(self, cparser, section): """Retrieve the opt value as a list from ConfigParser.""" return [v.split(',') for v in self._cparser_get_with_deprecated(cparser, section)] - def _get_optparse_kwargs(self, group, **kwargs): - """Extends the base optparse keyword dict for list options.""" - return super(ListOpt, - self)._get_optparse_kwargs(group, - type='string', - action='callback', - callback=self._parse_list, - **kwargs) - - def _parse_list(self, option, opt, value, parser): - """An optparse callback for parsing an option value into a list.""" - setattr(parser.values, self.dest, value.split(',')) + def _get_argparse_kwargs(self, group, **kwargs): + """Extends the base argparse keyword dict for list options.""" + return Opt._get_argparse_kwargs(self, + group, + action=ListOpt._StoreListAction, + **kwargs) class MultiStrOpt(Opt): @@ -752,10 +779,10 @@ class MultiStrOpt(Opt): """ multi = True - def _get_optparse_kwargs(self, group, **kwargs): - """Extends the base optparse keyword dict for multi str options.""" + def _get_argparse_kwargs(self, group, **kwargs): + """Extends the base argparse keyword dict for multi str options.""" return super(MultiStrOpt, - self)._get_optparse_kwargs(group, action='append') + self)._get_argparse_kwargs(group, action='append') def _cparser_get_with_deprecated(self, cparser, section): """If cannot find option as dest try deprecated_name alias.""" @@ -800,7 +827,7 @@ class OptGroup(object): self.help = help self._opts = {} # dict of dicts of (opt:, override:, default:) - self._optparse_group = None + self._argparse_group = None def _register_opt(self, opt): """Add an opt to this group. @@ -824,16 +851,16 @@ class OptGroup(object): if opt.dest in self._opts: del self._opts[opt.dest] - def _get_optparse_group(self, parser): - """Build an optparse.OptionGroup for this group.""" - if self._optparse_group is None: - self._optparse_group = optparse.OptionGroup(parser, self.title, - self.help) - return self._optparse_group + def _get_argparse_group(self, parser): + if self._argparse_group is None: + """Build an argparse._ArgumentGroup for this group.""" + self._argparse_group = parser.add_argument_group(self.title, + self.help) + return self._argparse_group def _clear(self): """Clear this group's option parsing state.""" - self._optparse_group = None + self._argparse_group = None class ParseError(iniparser.ParseError): @@ -912,6 +939,97 @@ class MultiConfigParser(object): raise KeyError +class _VersionAction(argparse.Action): + + def __init__(self, + option_strings, + version=None, + dest=argparse.SUPPRESS, + default=argparse.SUPPRESS, + help="show program's version number and exit"): + super(_VersionAction, self).__init__(option_strings=option_strings, + dest=dest, + default=default, + nargs=0, + help=help) + self.version = version + + def __call__(self, parser, namespace, values, option_string=None): + version = self.version + if version is None: + version = parser.version + formatter = parser._get_formatter() + formatter.add_text(version) + message = formatter.format_help() + if message: + parser._print_message(message, sys.stdout) + sys.exit(0) + + +class ConfigCliParser(argparse.ArgumentParser): + + def __init__(self, prog=None, usage=None, version=None, *args, **kwargs): + super(ConfigCliParser, self).__init__(prog=prog, usage=usage, + *args, **kwargs) + # to eliminate error: + # ValueError: unsupported format character 'p' (0x70) at index 1 + if usage is not None: + self.usage = usage.replace("%prog", self.prog) + + self.add_option('--version', + action=_VersionAction, + version=version) + self._optionals.title = 'Options' + + def add_option(self, *args, **kwargs): + self.add_argument(*args, **kwargs) + + def add_argument(self, *args, **kwargs): + try: + super(ConfigCliParser, self).add_argument(*args, **kwargs) + except(argparse.ArgumentError) as e: + raise DuplicateOptError(e) + + def add_subparsers(self, **kwargs): + return super(ConfigCliParser, self).add_subparsers(**kwargs) + + def parse_args(self, *args, **kwargs): + opts, args = super(ConfigCliParser, self).parse_known_args(*args, + **kwargs) + unknown_args = [] + for arg in args: + if 0 == arg.find("-"): + unknown_args.append(arg) + + if unknown_args: + msg = 'unrecognized arguments: %s' + self.error(msg % ' '.join(unknown_args)) + + return opts, args + + def print_usage(self, file=None): + ''' + overriding original print_usage method, only for + compatibility with optparse and happiness of unittest + ''' + print >>file, "Usage: " + self.usage + + def print_help(self, file=None): + ''' + overriding original print_usage method, only for + compatibility with optparse and happiness of unittest + unittest: HelpTestCase:test_print_help + ''' + msg = self.format_help() + if 0 == msg.find("usage:"): + msg = msg.replace("usage:", "Usage:") + print >>file, msg + + def print_version(self, file=None): + super(ConfigCliParser, self)._print_message(self.format_version(), + file) + + class ConfigOpts(collections.Mapping): """ @@ -928,6 +1046,10 @@ class ConfigOpts(collections.Mapping): self._groups = {} self._args = None + + # for subparser support, a root parser should be initialized earlier + # and conserved for later use + self._pre_init_parser = None self._oparser = None self._cparser = None self._cli_values = {} @@ -935,19 +1057,33 @@ class ConfigOpts(collections.Mapping): self._config_opts = [] self._disable_interspersed_args = False - def _setup(self, project, prog, version, usage, default_config_files): - """Initialize a ConfigOpts object for option parsing.""" + def _pre_setup(self, project, prog, version, usage, default_config_files): + """Initialize a ConfigCliParser object for option parsing.""" + if prog is None: prog = os.path.basename(sys.argv[0]) if default_config_files is None: default_config_files = find_config_files(project, prog) - self._oparser = optparse.OptionParser(prog=prog, - version=version, - usage=usage) - if self._disable_interspersed_args: - self._oparser.disable_interspersed_args() + # if _pre_init_parser does not exist, create one + if self._pre_init_parser is None: + self._oparser = ConfigCliParser(prog=prog, + version=version, + usage=usage) + # otherwise, use the pre-initialized parser with subparsers + # and re-initialize parser + else: + self._oparser = self._pre_init_parser + self._oparser.prog = prog + self._oparser.version = version + self._oparser.usage = usage + self._pre_init_parser = None + + return prog, default_config_files + + def _setup(self, project, prog, version, usage, default_config_files): + """Initialize a ConfigOpts object for option parsing.""" self._config_opts = [ MultiStrOpt('config-file', @@ -1017,8 +1153,15 @@ class ConfigOpts(collections.Mapping): :raises: SystemExit, ConfigFilesNotFoundError, ConfigFileParseError, RequiredOptError, DuplicateOptError """ + self.clear() + prog, default_config_files = self._pre_setup(project, + prog, + version, + usage, + default_config_files) + self._setup(project, prog, version, usage, default_config_files) self._cli_values, leftovers = self._parse_cli_opts(args) @@ -1055,6 +1198,13 @@ class ConfigOpts(collections.Mapping): """Return the number of options and option groups.""" return len(self._opts) + len(self._groups) + def add_cli_subparsers(self, **kwargs): + # only add subparsers to pre-initialized root parser + # to avoid cleared by self.clear() + if self._pre_init_parser is None: + self._pre_init_parser = ConfigCliParser() + return self._pre_init_parser.add_subparsers(**kwargs) + def reset(self): """Clear the object state and unset overrides and defaults.""" self._unset_defaults_and_overrides() @@ -1271,12 +1421,14 @@ class ConfigOpts(collections.Mapping): i.e. argument parsing is stopped at the first non-option argument. """ + # do nothing self._disable_interspersed_args = True def enable_interspersed_args(self): """Set parsing to not stop on the first non-option. This it the default behaviour.""" + # do nothing self._disable_interspersed_args = False def find_file(self, name): |
