Source code for Bcfg2.Options.OptionGroups

""" Option grouping classes """

import re
import copy
import fnmatch
from Bcfg2.Options import Option
from itertools import chain

__all__ = ["OptionGroup", "ExclusiveOptionGroup", "Subparser",
           "WildcardSectionGroup"]


class _OptionContainer(list):
    """ Parent class of all option groups """

    def list_options(self):
        """ Get a list of all options contained in this group,
        including options contained in option groups in this group,
        and so on. """
        return list(chain(*[o.list_options() for o in self]))

    def __repr__(self):
        return "%s(%s)" % (self.__class__.__name__, list.__repr__(self))

    def add_to_parser(self, parser):
        """ Add this option group to a :class:`Bcfg2.Options.Parser`
        object. """
        for opt in self:
            opt.add_to_parser(parser)


[docs]class OptionGroup(_OptionContainer): """ Generic option group that is used only to organize options. This uses :meth:`argparse.ArgumentParser.add_argument_group` behind the scenes. """ def __init__(self, *items, **kwargs): r""" :param \*args: Child options :type \*args: Bcfg2.Options.Option :param title: The title of the option group :type title: string :param description: A longer description of the option group :param description: string """ _OptionContainer.__init__(self, items) self.title = kwargs.pop('title') self.description = kwargs.pop('description', None)
[docs] def add_to_parser(self, parser): group = parser.add_argument_group(self.title, self.description) _OptionContainer.add_to_parser(self, group)
[docs]class ExclusiveOptionGroup(_OptionContainer): """ Option group that ensures that only one argument in the group is present. This uses :meth:`argparse.ArgumentParser.add_mutually_exclusive_group` behind the scenes.""" def __init__(self, *items, **kwargs): r""" :param \*args: Child options :type \*args: Bcfg2.Options.Option :param required: Exactly one argument in the group *must* be specified. :type required: boolean """ _OptionContainer.__init__(self, items) self.required = kwargs.pop('required', False)
[docs] def add_to_parser(self, parser): _OptionContainer.add_to_parser( self, parser.add_mutually_exclusive_group(required=self.required))
[docs]class Subparser(_OptionContainer): """ Option group that adds options in it to a subparser. This uses a lot of functionality tied to `argparse Sub-commands <http://docs.python.org/dev/library/argparse.html#sub-commands>`_. The subcommand string itself is stored in the :attr:`Bcfg2.Options.setup` namespace as ``subcommand``. This is commonly used with :class:`Bcfg2.Options.Subcommand` groups. """ _subparsers = dict() def __init__(self, *items, **kwargs): r""" :param \*args: Child options :type \*args: Bcfg2.Options.Option :param name: The name of the subparser. Required. :type name: string :param help: A help message for the subparser :param help: string """ self.name = kwargs.pop('name') self.help = kwargs.pop('help', None) _OptionContainer.__init__(self, items) def __repr__(self): return "%s %s(%s)" % (self.__class__.__name__, self.name, list.__repr__(self))
[docs] def add_to_parser(self, parser): if parser not in self._subparsers: self._subparsers[parser] = parser.add_subparsers(dest='subcommand') subparser = self._subparsers[parser].add_parser(self.name, help=self.help) _OptionContainer.add_to_parser(self, subparser)
[docs]class WildcardSectionGroup(_OptionContainer, Option): """WildcardSectionGroups contain options that may exist in several different sections of the config that match a glob. It works by creating options on the fly to match the sections described in the glob. For example, consider: .. code-block:: python options = [ Bcfg2.Options.WildcardSectionGroup( Bcfg2.Options.Option(cf=("myplugin:*", "number"), type=int), Bcfg2.Options.Option(cf=("myplugin:*", "description"))] If the config file contained ``[myplugin:foo]`` and ``[myplugin:bar]`` sections, then this would automagically create options for each of those. The end result would be: .. code-block:: python >>> Bcfg2.Options.setup Namespace(myplugin_bar_description='Bar description', myplugin_myplugin_bar_number=2, myplugin_myplugin_foo_description='Foo description', myplugin_myplugin_foo_number=1, myplugin_sections=['myplugin:foo', 'myplugin:bar']) All options must have the same section glob. The options are stored in an automatically-generated destination given by:: <prefix><section>_<destination> ``<destination>`` is the original `dest <http://docs.python.org/dev/library/argparse.html#dest>`_ of the option. ``<section>`` is the section that it's found in. ``<prefix>`` is automatically generated from the section glob. (This can be overridden with the constructor.) Both ``<section>`` and ``<prefix>`` have had all consecutive characters disallowed in Python variable names replaced with underscores. This group stores an additional option, the sections themselves, in an option given by ``<prefix>sections``. """ #: Regex to automatically get a destination for this option _dest_re = re.compile(r'(\A(_|[^A-Za-z])+)|((_|[^A-Za-z0-9])+)') def __init__(self, *items, **kwargs): r""" :param \*args: Child options :type \*args: Bcfg2.Options.Option :param prefix: The prefix to use for options generated by this option group. By default this is generated automatically from the config glob; see above for details. :type prefix: string :param dest: The destination for the list of known sections that match the glob. :param dest: string """ _OptionContainer.__init__(self, []) self._section_glob = items[0].cf[0] # get a default destination self._prefix = kwargs.get("prefix", self._dest_re.sub('_', self._section_glob)) Option.__init__(self, dest=kwargs.get('dest', self._prefix + "sections")) self.option_templates = items
[docs] def list_options(self): return [self] + _OptionContainer.list_options(self)
[docs] def from_config(self, cfp): sections = [] for section in cfp.sections(): if fnmatch.fnmatch(section, self._section_glob): sections.append(section) newopts = [] for opt_tmpl in self.option_templates: option = copy.deepcopy(opt_tmpl) option.cf = (section, option.cf[1]) option.dest = "%s%s_%s" % (self._prefix, self._dest_re.sub('_', section), option.dest) newopts.append(option) self.extend(newopts) for parser in self.parsers: parser.add_options(newopts) return sections
[docs] def add_to_parser(self, parser): Option.add_to_parser(self, parser) _OptionContainer.add_to_parser(self, parser)
def __eq__(self, other): return (_OptionContainer.__eq__(self, other) and self.option_templates == getattr(other, "option_templates", None)) def __repr__(self): if len(self) == 0: return "%s(%s)" % (self.__class__.__name__, ", ".join(".".join(o.cf) for o in self.option_templates)) else: return _OptionContainer.__repr__(self)