.. -*- mode: rst -*- .. _development-option-parsing: ==================== Bcfg2 Option Parsing ==================== Bcfg2 uses an option parsing mechanism based on the Python :mod:`argparse` module. It does several very useful things that ``argparse`` does not: * Collects options from various places, which lets us easily specify per-plugin options, for example; * Automatically loads components (such as plugins); * Synthesizes option values from the command line, config files, and environment variables; * Can dynamically create commands with many subcommands (e.g., bcfg2-info and bcfg2-admin); and * Supports keeping documentation inline with the option declaration, which will make it easier to generate man pages. Collecting Options ================== One of the more important features of the option parser is its ability to automatically collect options from loaded components (e.g., Bcfg2 server plugins). Given the highly pluggable architecture of Bcfg2, this helps ensure two things: #. We do not have to specify all options in all places, or even in most places. Options are specified alongside the class(es) that use them. #. All options needed for a given script to run are guaranteed to be loaded, without the need to specify all components that script uses manually. For instance, assume a few plugins: * The ``Foo`` plugin takes one option, ``--foo`` * The ``Bar`` plugin takes two options, ``--bar`` and ``--force`` The plugins are used by the ``bcfg2-quux`` command, which itself takes two options: ``--plugins`` (which selects the plugins) and ``--test``. The options would be selected at runtime, so for instance these would be valid: .. code-block:: bash bcfg2-quux --plugins Foo --foo --test bcfg2-quux --plugins Foo,Bar --foo --bar --force bcfg2-quux --plugins Bar --force But this would not: bcfg2-quux --plugins Foo --bar The help message would reflect the options that are available to the default set of plugins. (For this reason, allowing component lists to be set in the config file is very useful; that way, usage messages reflect the components in the config file.) Components (in this example, the plugins) can be classes or modules. There is no required interface for an option component. They may *optionally* have: * An ``options`` attribute that is a list of :class:`Bcfg2.Options.Options.Option` objects or option groups. * A boolean ``parse_first`` attribute; if set to True, the options for the component are parsed before all other options. This is useful for, e.g., Django database settings, which must be parsed before plugins that use Django can be loaded. * A function or static method, ``options_parsed_hook``, that is called when all options have been parsed. (This will be called again if :func:`Bcfg2.Options.Parser.Parser.reparse` is called.) * A function or static method, ``component_parsed_hook``, that is called when early option parsing for a given component has completed. This is *only* called for components with ``parse_first`` set to True. It is passed a single argument: a :class:`argparse.Namespace` object containing the complete set of early options. Options are collected through two primary mechanisms: #. The :class:`Bcfg2.Options.Actions.ComponentAction` class. When a ComponentAction subclass is used as the action of an option, then options contained in the classes (or modules) given in the option value will be added to the parser. #. Modules that are not loaded via a :class:`Bcfg2.Options.Actions.ComponentAction` option may load options at runtime. Since it is preferred to add components instead of just options, loading options at runtime is generally best accomplished by creating a container object whose only purpose is to hold options. For instance: .. code-block:: python def foo(): # do stuff class _OptionContainer(object): options = [ Bcfg2.Options.BooleanOption("--foo", help="Enable foo")] @staticmethod def options_parsed_hook(): if Bcfg2.Options.setup.foo: foo() Bcfg2.Options.get_parser().add_component(_OptionContainer) The Bcfg2.Options module ======================== .. currentmodule:: Bcfg2.Options .. autodata:: setup Options ------- The base :class:`Bcfg2.Options.Option` object represents an option. Unlike options in :mod:`argparse`, an Option object does not need to be associated with an option parser; it exists on its own. .. autoclass:: Option .. autoclass:: PathOption .. autoclass:: BooleanOption .. autoclass:: PositionalArgument The Parser ---------- .. autoclass:: Parser .. autofunction:: get_parser .. autoexception:: OptionParserException Option Groups ------------- Options can be grouped in various meaningful ways. This uses a variety of :mod:`argparse` functionality behind the scenes. In all cases, options can be added to groups in-line by simply specifying them in the object group constructor: .. code-block:: python options = [ Bcfg2.Options.ExclusiveOptionGroup( Bcfg2.Options.Option(...), Bcfg2.Options.Option(...), required=True), ....] Nesting object groups is supported in theory, but barely tested. .. autoclass:: OptionGroup .. autoclass:: ExclusiveOptionGroup .. autoclass:: Subparser .. autoclass:: WildcardSectionGroup Subcommands ----------- This library makes it easier to work with programs that have a large number of subcommands (e.g., :ref:`bcfg2-info ` and :ref:`bcfg2-admin `). The normal implementation pattern is this: #. Define all of your subcommands as children of :class:`Bcfg2.Options.Subcommand`. #. Create a :class:`Bcfg2.Options.CommandRegistry` object that will be used to register all of the commands. Registering a command collect its options and adds it as a :class:`Bcfg2.Options.Subparser` option group to the main option parser. #. Register your commands with the :func:`Bcfg2.Options.CommandRegistry.register_commands` method of your ``CommandRegistry`` object. #. Add options from the :attr:`Bcfg2.Options.CommandRegistry.command_options` attribute to the option parser. #. Parse options, and run. :mod:`Bcfg2.Server.Admin` provides a fairly simple implementation, where the CLI class subclasses the command registry: .. code-block:: python class CLI(Bcfg2.Options.CommandRegistry): def __init__(self): Bcfg2.Options.CommandRegistry.__init__(self) self.register_commands(globals().values(), parent=AdminCmd) parser = Bcfg2.Options.get_parser( description="Manage a running Bcfg2 server", components=[self]) parser.add_options(self.subcommand_options) parser.parse() In this case, commands are collected from amongst all global variables (the most likely scenario), and they must be children of :class:`Bcfg2.Server.Admin.AdminCmd`, which itself subclasses :class:`Bcfg2.Options.Subcommand`. Commands are defined by subclassing :class:`Bcfg2.Options.Subcommand`. At a minimum, the :func:`Bcfg2.Options.Subcommand.run` method must be overridden, and a docstring written. .. autoclass:: Subcommand .. autoclass:: CommandRegistry Actions ------- Several custom argparse `actions `_ provide some of the option collection magic of :mod:`Bcfg2.Options`. .. autoclass:: ConfigFileAction .. autoclass:: ComponentAction .. autoclass:: PluginsAction Option Types ------------ :mod:`Bcfg2.Options` provides a number of useful types for use as the `type `_ keyword argument to the :class:`Bcfg2.Options.Option` constructor. .. autofunction:: Bcfg2.Options.Types.path .. autofunction:: Bcfg2.Options.Types.comma_list .. autofunction:: Bcfg2.Options.Types.colon_list .. autofunction:: Bcfg2.Options.Types.octal .. autofunction:: Bcfg2.Options.Types.username .. autofunction:: Bcfg2.Options.Types.groupname .. autofunction:: Bcfg2.Options.Types.timeout .. autofunction:: Bcfg2.Options.Types.size Common Options -------------- .. autoclass:: Common