Source code for guacamole.ingredients.argparse

# encoding: utf-8
# This file is part of Guacamole.
#
# Copyright 2012-2015 Canonical Ltd.
# Written by:
#   Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
#
# Guacamole is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3,
# as published by the Free Software Foundation.
#
# Guacamole 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Guacamole.  If not, see <http://www.gnu.org/licenses/>.

"""
Ingredients for using arparse for parsing command line arguments.

This module contains two ingredients. The main one is the
:class:`ParserIngredient`. It is responsible for handling all of the command
line parsing and command argument registration. It is a part of the recipe
for the command class. Note that command dispatch is not handled by this
ingredient (see :class:`~guacamole.ingredients.cmdtree.CommandTreeIngredient`).

The second ingredient is :class:`AutocompleteIngredient` which relies on the
third-party argcomplete module to add support for automatic command line
completion to supported shells (bash).
"""

from __future__ import absolute_import, print_function, unicode_literals

import argparse

from guacamole.core import Ingredient
from guacamole.recipes import RecipeError
# from guacamole._argparse import LegacyHelpFormatter


[docs]class ParserIngredient(Ingredient): """ Ingredient for using argparse to parse command line arguments. This ingredient uses the following Ingredient methods: - ``build_early_parser()`` - ``preparse()`` - ``build_parser()`` - ``parse()`` The main parser is constructed in, unsurprisingly, the :meth:`build_parser()` method and stored in the context as ``parser``. Other ingredients can be added *after* the ``ParserIngredient`` and can extend the available arguments (on the root parser) by using standard argparse APIs such as ``parser.add_argument()`` or ``parser.add_argument_group()``. This parser is used to handle all of command line in the :meth:`parse()` method. While most users won't concern themselves with this design decision, there is also a second parser, called the *early parser*, that is used to *pre-parse* the command line arguments. This can be used as a way to optimize subsequent actions as, perhaps, knowing which commands are going to be invoked there will be no need to instantiate and prepare *all* of the commands in the command tree. Currently this feature is not used. To take advantage of this knowledge you can look at the ``context.early_args`` object which contains the result of parsing the command line with the *early parser*. The early parser is a simple parser consisting of ``--help``, ``--version`` (if applicable) and *rest*. The *rest* argument can be used as a hint as to what is coming next (e.g. if it matches a name of a command we know to exist) After parsing is done the results of parsing the command line are stored in the ``context.args`` attribute. This is commonly accessed by individual commands from their ``invoke()`` methods. """
[docs] def build_early_parser(self, context): """ Create the early argument parser. This method creates the early argparse argument parser. The early parser doesn't know about any of the sub-commands so it can be used much earlier during the start-up process (before commands are loaded and initialized). """ context.early_parser = self._create_early_parser(context)
[docs] def preparse(self, context): """ Parse a portion of command line arguments with the early parser. This method relies on ``context.argv`` and ``context.early_parser`` and produces ``context.early_args``. The ``context.early_args`` object is the return value from argparse. It is the dict/object like namespace object. """ context.early_args, unused = ( context.early_parser.parse_known_args(context.argv))
[docs] def build_parser(self, context): """ Create the final argument parser. This method creates the non-early (full) argparse argument parser. Unlike the early counterpart it is expected to have knowledge of the full command tree. This method relies on ``context.cmd_tree`` and produces ``context.parser``. Other ingredients can interact with the parser up until :meth:`parse()` is called. """ context.parser, context.max_level = self._create_parser(context)
[docs] def parse(self, context): """ Parse command line arguments. This method relies on ``context.argv`` and ``context.early_parser`` and produces ``context.args``. Note that ``.argv`` is modified by :meth:`preparse()` so it actually has _less_ things in it. The ``context.args`` object is the return value from argparse. It is the dict/object like namespace object. """ context.args = context.parser.parse_args(context.argv)
def _create_parser(self, context): cmd_name, cmd_obj, cmd_subcmds = context.cmd_tree parser = argparse.ArgumentParser( prog=cmd_name, **self._get_parser_kwargs(cmd_obj)) parser.add_argument("-h", "--help", action="help") self._maybe_add_version(parser, cmd_obj) max_level = self._add_command_to_parser( parser, cmd_name, cmd_obj, cmd_subcmds) return parser, max_level def _create_early_parser(self, context): early_parser = argparse.ArgumentParser(add_help=False) early_parser.add_argument( "rest", nargs="...", help=argparse.SUPPRESS) early_parser.add_argument( "-h", "--help", action="store_const", const=None) cmd_name, cmd_obj, cmd_subcmds = context.cmd_tree version = cmd_obj.get_cmd_version() if version is not None: early_parser.add_argument( "--version", action="store_const", const=None) return early_parser def _maybe_add_version(self, parser, command): version = command.get_cmd_version() if version is not None: # NOTE: help= is provided explicitly as argparse doesn't wrap # everything with _() correctly (depending on version) parser.add_argument( "--version", action="version", version=version, help="show program's version number and exit") def _get_parser_kwargs(self, command): return { 'usage': command.get_cmd_usage(), 'description': command.get_cmd_description(), 'epilog': command.get_cmd_epilog(), 'add_help': False, # formatter_class=LegacyHelpFormatter, } def _add_command_to_parser( self, parser, cmd_name, cmd_obj, cmd_subcmds, level=0 ): # Register this command cmd_obj.register_arguments(parser) parser.set_defaults(**{'command{}'.format(level): cmd_obj}) # Register sub-commands of this command (recursively) if not cmd_subcmds: return level subparsers = parser.add_subparsers( help="sub-command to pick") max_level = level for subcmd_name, subcmd_obj, subcmd_cmds in cmd_subcmds: sub_parser = subparsers.add_parser( subcmd_name, help=subcmd_obj.get_cmd_help(), **self._get_parser_kwargs(subcmd_obj)) sub_parser.add_argument("-h", "--help", action="help") max_level = max( max_level, self._add_command_to_parser( sub_parser, subcmd_name, subcmd_obj, subcmd_cmds, level + 1)) return max_level
[docs]class AutocompleteIngredient(Ingredient): """ Ingredient for adding shell auto-completion. .. warning:: This component is not widely tested due to difficulty of providing actual integration. It might be totally broken. .. note:: To effectively get tab completion you need to have the ``argcomplete`` package installed. In addition, a per-command initialization command has to be created and sourced by the shell. Look at argcomplete documentation for details. """
[docs] def parse(self, context): """ Optionally trigger argument completion in the invoking shell. This method is called to see if bash argument completion is requested and to honor the request, if needed. This causes the process to exit (early) without giving other ingredients a chance to initialize or shut down. Due to the way argcomple works, no other ingredient can print() anything to stdout prior to this point. """ try: import argcomplete except ImportError: return try: parser = context.parser except AttributeError: raise RecipeError( """ The context doesn't have the parser attribute. The auto-complete ingredient depends on having a parser object to generate completion data for she shell. In a typical application this requires that the AutocompleteIngredient and ParserIngredient are present and that the auto-complete ingredient precedes the parser. """) else: argcomplete.autocomplete(parser)