Source code for guacamole.recipes.cmd

# 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/>.

"""
Recipe for using guacamole to run commands.

This module contains sock recipes for guacamole. Stock recipes allow
application developers to use use simple-to-understand design patterns to get
predictable runtime behiavior.

Currently, guacamole ships with two such recipes, for creating simple commands
and for creating hierarhical command groups. They are captured by the
:class:`Command` and :class:`Group` classes respectively.
"""

from __future__ import absolute_import, print_function, unicode_literals

import gettext
import inspect
import logging

from guacamole.ingredients import ansi
from guacamole.ingredients import argparse
from guacamole.ingredients import cmdtree
from guacamole.ingredients import crash
from guacamole.recipes import Recipe


__all__ = (
    'Command',
    'CommandRecipe',
)


_logger = logging.getLogger('guacamole')


[docs]class Command(object): """ A single-purpose command. Single purpose commands are the most commonly known tools in command line environments. Tools such as ``ls``, ``mkdir`` or ``vim`` all fall in this class. A command is essentially a named action that can be invoked from the terminal emulator or other command line environment specific to a given operating system. To create a new command simply create a custom class and override the :meth:`invoked()` method. Put all of your custom code there. If you want to interact with command line arguments then please also override the :meth:`register_arguments()` method. Have a look at example applications for details of how to do this. You can use them as a starting point for your own application as they are licensed very liberally. """ def __repr__(self): """Get the debugging representation of a command.""" return "<{}>".format(self.__class__.__name__)
[docs] def invoked(self, context): """ Callback called when the command gets invoked. :param context: The guacamole context object. :returns: The return value is returned by the executable. It should be an integer between 0 and 255. Other values are will likely won't work at all. The context argument can be used to access command line arguments and other information that guacamole provides. """ if not self.get_sub_commands(): _logger.warning( "Command %r doesn't override Command.invoked()", self)
[docs] def register_arguments(self, parser): """ Callback called to register command-specific arguments. :param parser: Argument parser (from :mod:`argparse`) specific to this command. """
[docs] def get_app_vendor(self): """ Get the name of the application vendor. The name should be a human readable name, like ``"Joe Developer"`` or ``"Big Corporation Ltd."`` .. note:: Application vendor name is looked up using the ``app_vendor`` attribute. """ try: return self.app_vendor except AttributeError: pass
[docs] def get_app_name(self): """ Get the name of the application. .. note:: Application name is looked up using the ``app_name`` attribute. Application name differs from executable name. The executable might be called ``my-app`` or ``myapp`` while the application might be called ``My Application``. """ try: return self.app_name except AttributeError: pass
[docs] def get_app_id(self): """ Get the identifier of the application. .. note:: Application identifier is looked up using the ``app_id`` attribute. The syntax of a valid command identifier is ``REVERSE-DNS-NAME:ID``. For example, ``"com.example.product:command"``. This identifier must not contain characters that are hostile to the file systems. It's best to stick to ASCII characters and digits. On *Mac OS X* this will be used as a directory name rooted in ``~/Library/Preferences/``. On Linux and other freedesktop.org-based systems this will be used as directory name rooted in ``$XDG_CONFIG_HOME`` and ``$XDG_CACHE_HOME``. On Windows it will be used as a directory name rooted in the per-user ``AppData`` folder. .. note:: If this method returns None then logging and configuration services are disabled. It is strongly recommended to implement this method and return a correct value as it enhances application behavior. """ try: return self.app_id except AttributeError: pass
[docs] def get_cmd_name(self): """ Get the name of the application executable. .. note:: If this method returns None then the executable name is guessed from ``sys.argv[0]``. """ try: return self.name except AttributeError: pass
[docs] def get_cmd_version(self): """ Get the version reported by this executable. .. note:: If this method returns None then the ``--version`` option is disabled. """ try: return self.version except AttributeError: pass
[docs] def get_cmd_usage(self): """ Get the usage string associated with this command. :returns: ``self.usage``, if defined :returns: None, otherwise The usage string typically contains the list of available, abbreviated options, mandatory arguments and other arguments. Its purpose is to quickly inform the user on the basic syntax used by the command. It is perfectly fine not to customize this method as the default is to compute an appropriate usage string out of all the arguments. Consider implementing this method in a customized way if your command has highly complicated syntax and you want to provide an alternative, more terse usage string instead. """ try: return self.usage except AttributeError: pass
[docs] def get_cmd_help(self): """ Get the single-line help of this command. :returns: ``self.help``, if defined :returns: The first line of the docstring, without the trailing dot, if present. :returns: None, otherwise """ try: return self.help except AttributeError: pass try: return get_localized_docstring( self, self.get_gettext_domain() ).splitlines()[0].rstrip('.').lower() except (AttributeError, IndexError, ValueError): pass
[docs] def get_cmd_description(self): """ Get the leading, multi-line description of this command. :returns: ``self.description``, if defined :returns: A substring of the class docstring between the first line (which is discarded) and the string ``@EPILOG@``, if present, or the end of the docstring, if any :returns: None, otherwise The description string will be displayed after the usage string but before any of the detailed argument descriptions. Please consider following good practice by keeping the description line short enough not to require scrolling but useful enough to provide additional information that cannot be inferred from the name of the command or other arguments. Stating the purpose of the command is highly recommended. """ try: return self.description except AttributeError: pass try: return '\n'.join( get_localized_docstring( self, self.get_gettext_domain() ).splitlines()[1:] ).split('@EPILOG@', 1)[0].strip() except (AttributeError, IndexError, ValueError): pass
[docs] def get_cmd_epilog(self): """ Get the trailing, multi-line description of this command. :returns: ``self.epilog``, if defined :returns: A substring of the class docstring between the string ``@EPILOG`` and the end of the docstring, if defined :returns: None, otherwise The epilog is similar to the description string but it is instead printed after the section containing detailed descriptions of all of the command line arguments. Please consider following good practice by providing additional details about how the command can be used, perhaps an example or a reference to means of finding additional documentation. """ try: return self.source.epilog except AttributeError: pass try: return '\n'.join( get_localized_docstring( self, self.get_gettext_domain() ).splitlines()[1:] ).split('@EPILOG@', 1)[1].strip() except (AttributeError, IndexError, ValueError): pass
[docs] def get_gettext_domain(self): """ Get the gettext translation domain associated with this command. The value returned will be used to select translations to global calls to gettext() and ngettext() everywhere in python. .. note:: If this method returns None then all i18n services are disabled. """ try: return self.gettext_domain except AttributeError: pass
[docs] def get_locale_dir(self): """ Get the path of the gettext translation catalogs for this command. This value is used to bind the domain returned by :meth:`get_gettext_domain()` to a specific directory. .. note:: If this method returns None then standard, system-wide locations are used (on compatibles systems). In practical terms, on Windows, you may need to use it to have access to localization data. """ try: return self.locale_dir except AttributeError: pass
[docs] def get_sub_commands(self): """ Get a list of sub-commands of this command. :returns: ``self.sub_commands``, if defined. This is a sequence of pairs ``(name, cls)`` where ``name`` is the name of the sub command and ``cls`` is a command class (not an object). The ``name`` can be None if the command has a version of :meth:`get_cmd_name()` that returns an useful value. :returns: An empty tuple otherwise Applications can create hierarchical commands by defining the ``sub_commands`` attribute. Many developers are familiar with nested commands, for example ``git commit`` is a sub-command of the ``git`` command. All commands can be nested this way. """ try: return self.sub_commands except AttributeError: return ()
[docs] def get_cmd_spices(self): """ Get a list of spices requested by this command. Feature flags are a mechanism that allows application developers to control ingredients (switch them on or off) as well as to control how some ingredients behave. :returns: ``self.spices``, if defined. This should be a set of strings. Each string represents as single flag. Ingredients should document the set of flags they understand and use. :returns: An empty set otherwise Some flags have a generic meaning, you can scope a flag to a given ingredient using the ``name:`` prefix where the name is the name of the ingredient. """ try: spices = self.spices except AttributeError: spices = set() return spices
[docs] def main(self, argv=None, exit=True): """ Shortcut for running a command. See :meth:`guacamole.recipes.Recipe.main()` for details. """ return CommandRecipe(self).main(argv, exit)
def get_localized_docstring(obj, domain): """Get a cleaned-up, localized copy of docstring of this class.""" if obj.__class__.__doc__ is not None: return inspect.cleandoc( gettext.dgettext(domain, obj.__class__.__doc__))
[docs]class CommandRecipe(Recipe): """A recipe for using commands.""" def __init__(self, command): """Initialize a recipe for working with a specific command.""" self.command = command
[docs] def get_ingredients(self): """Get a list of ingredients for guacamole.""" return [ ansi.ANSIIngredient(), cmdtree.CommandTreeBuilder(self.command), cmdtree.CommandTreeDispatcher(), argparse.AutocompleteIngredient(), argparse.ParserIngredient(), crash.VerboseCrashHandler(), ]