summaryrefslogtreecommitdiffstats
path: root/lmi/scripts/common/command/endpoint.py
diff options
context:
space:
mode:
Diffstat (limited to 'lmi/scripts/common/command/endpoint.py')
-rw-r--r--lmi/scripts/common/command/endpoint.py367
1 files changed, 0 insertions, 367 deletions
diff --git a/lmi/scripts/common/command/endpoint.py b/lmi/scripts/common/command/endpoint.py
deleted file mode 100644
index c744e34..0000000
--- a/lmi/scripts/common/command/endpoint.py
+++ /dev/null
@@ -1,367 +0,0 @@
-# Copyright (C) 2013-2014 Red Hat, Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# The views and conclusions contained in the software and documentation are
-# those of the authors and should not be interpreted as representing official
-# policies, either expressed or implied, of the FreeBSD Project.
-#
-# Authors: Michal Minar <miminar@redhat.com>
-#
-"""
-Defines base command class for all endpoint commands. Those having no children.
-"""
-import abc
-import inspect
-import re
-from docopt import docopt
-
-from lmi.scripts.common import errors
-from lmi.scripts.common import formatter
-from lmi.scripts.common import get_logger
-from lmi.scripts.common.formatter import command as fcmd
-from lmi.scripts.common.command import base
-from lmi.scripts.common.command import meta
-from lmi.scripts.common.command import util
-
-LOG = get_logger(__name__)
-
-def opt_name_sanitize(opt_name):
- """
- Make a function parameter name out of option name. This replaces any
- character not suitable for python identificator with ``'_'`` and
- make the whole string lowercase.
-
- :param string opt_name: Option name.
- :returns: Modified option name.
- :rtype: string
- """
- return re.sub(r'[^a-zA-Z0-9]+', '_', opt_name).lower()
-
-def options_dict2kwargs(options):
- """
- Convert option name from resulting ``docopt`` dictionary to a valid python
- identificator token used as function argument name.
-
- :param dictionary options: Dictionary returned by docopt call.
- :returns: New dictionary with keys passable to function as argument
- names.
- :rtype: dictionary
- """
- # (new_name, value) for each pair in options dictionary
- kwargs = {}
- # (new_name, name)
- orig_names = {}
- for name, value in options.items():
- for (reg, func) in (
- (util.RE_OPT_BRACKET_ARGUMENT, lambda m: m.group('name')),
- (util.RE_OPT_UPPER_ARGUMENT, lambda m: m.group('name')),
- (util.RE_OPT_SHORT_OPTION, lambda m: m.group(0)),
- (util.RE_OPT_LONG_OPTION, lambda m: m.group(0)),
- (util.RE_COMMAND_NAME, lambda m: m.group(0))):
- match = reg.match(name)
- if match:
- new_name = func(match)
- break
- else:
- raise errors.LmiError(
- 'Failed to convert argument "%s" to function option.' %
- name)
- if new_name == '--':
- continue # ignore double dash
- new_name = opt_name_sanitize(new_name)
- if new_name in kwargs:
- raise errors.LmiError('Option clash for "%s" and "%s", which both'
- ' translate to "%s".' % (name, orig_names[new_name], new_name))
- kwargs[new_name] = value
- orig_names[new_name] = name
- return kwargs
-
-class LmiEndPointCommand(base.LmiBaseCommand):
- """
- Base class for any leaf command.
-
- List of additional recognized properties:
-
- ``CALLABLE`` : ``tuple``
- Associated function. Will be wrapped in
- :py:meth:`LmiEndPointCommand.execute` method and will be accessible
- directly as a ``cmd.execute.dest`` property. It may be specified
- either as a string in form ``"<module_name>:<callable>"`` or as a
- reference to callable itself.
- ``ARG_ARRAY_SUFFIX`` : ``str``
- String appended to every option parsed by ``docopt`` having list as
- an associated value. It defaults to empty string. This modification
- is applied before calling
- :py:meth:`LmiEndPointCommand.verify_options` and
- :py:meth:`LmiEndPointCommand.transform_options`.
- ``FORMATTER`` : callable
- Default formatter factory for instances of given command. This
- factory accepts an output stream as the only parameter and returns
- an instance of :py:class:`~lmi.scripts.common.formatter.Formatter`.
-
- Using metaclass:
- :py:class:`.meta.EndPointCommandMetaClass`.
- """
- __metaclass__ = meta.EndPointCommandMetaClass
-
- def __init__(self, *args, **kwargs):
- super(LmiEndPointCommand, self).__init__(*args, **kwargs)
- self._formatter = None
- # saved options dictionary after call to transform_options()
- self._options = None
-
- @abc.abstractmethod
- def execute(self, *args, **kwargs):
- """
- Subclasses must override this method to pass given arguments to
- command library function. This function shall be specified in
- ``CALLABLE`` property.
- """
- raise NotImplementedError("execute method must be overriden"
- " in subclass")
-
- def formatter_factory(self):
- """
- Subclasses shall override this method to provide default formatter
- factory for printing output.
-
- :returns: Subclass of basic formatter.
- """
- return formatter.Formatter
-
- @classmethod
- def dest_pos_args_count(cls):
- """
- Number of positional arguments the associated function takes from
- command. These arguments are created by the command alone -- they do
- not belong to options in usage string. Function can take additional
- positional arguments that need to be covered by usage string.
-
- :rtype: integer
- """
- dest = getattr(cls.execute, "dest", cls.execute)
- abstract = dest == cls.execute and util.is_abstract_method(
- cls, 'execute', True)
- # if the destination function is not yet defined (abstract is True)
- # let's assume it's not a method => 0 positional arguments needed
- return 1 if not abstract and inspect.ismethod(dest) else 0
-
- def run_with_args(self, args, kwargs):
- """
- Process end-point arguments and exit.
-
- :param list args: Positional arguments to pass to associated
- function in command library.
- :param dictionary kwargs: Keyword arguments as a dictionary.
- :returns: Exit code of application.
- :rtype: integer
- """
- return self.execute(*args, **kwargs)
-
- @property
- def formatter(self):
- """
- Return instance of default formatter.
-
- :rtype: :py:class:`~lmi.scripts.common.formatter.Formatter`
- """
- if self._formatter is None:
- opts = self.format_options
- factory = self.formatter_factory()
- argspec = inspect.getargspec(
- factory.__init__ if type(factory) is type else factory)
- if not argspec.keywords:
- kwargs = {k: v for k, v in opts.items() if k in argspec.args}
- self._formatter = factory(self.app.stdout, **kwargs)
- return self._formatter
-
- def _make_end_point_args(self, options):
- """
- Creates a pair of positional and keyword arguments for a call to
- associated function from command line options. All keyword
- options not expected by target function are removed.
-
- :param dictionary options: Output of ``docopt`` parser.
- :returns: Positional and keyword arguments as a pair.
- :rtype: tuple
- """
- # if execute method does not have a *dest* attribute, then it's
- # itself a destination
- dest = getattr(self.execute, "dest", self.execute)
- argspec = inspect.getargspec(dest)
- kwargs = options_dict2kwargs(options)
- # number of positional arguments not covered by usage string
- pos_args_count = self.dest_pos_args_count()
- to_remove = []
- # if associated function takes keyword arguments in a single
- # dictionary (kwargs), we can pass all options
- if argspec.keywords is None:
- # otherwise we need to remove any unhandled
- for opt_name in kwargs:
- if opt_name not in argspec.args[pos_args_count:]:
- if opt_name not in self.cmd_name_parts:
- LOG().debug('Option "%s" not handled in function "%s",'
- ' ignoring.', opt_name, self.cmd_name)
- to_remove.append(opt_name)
- for opt_name in to_remove:
- # remove options unhandled by function
- del kwargs[opt_name]
- args = []
- for arg_name in argspec.args[pos_args_count:]:
- if arg_name not in kwargs:
- raise errors.LmiCommandError(
- self.__module__, self.__class__.__name__,
- 'registered command "%s" expects option "%s", which'
- ' is not covered in usage string'
- % (self.cmd_name, arg_name))
- args.append(kwargs.pop(arg_name))
- return args, kwargs
-
- def _preprocess_options(self, options):
- """
- This method may be overriden by
- :py:class:`~.meta.EndPointCommandMetaClass`
- as a result of processing ``ARG_ARRAY_SUFFIX`` and other properties
- modifying names of parsed options.
-
- This should not be overriden in command class's body.
-
- :param dictionary options: The result of ``docopt`` parser invocation
- which can be modified by this method.
- """
- pass
-
- def _parse_args(self, args):
- """
- Run ``docopt`` command line parser on given list of arguments.
- Removes all unrelated commands from created dictionary of options.
-
- :param list args: List of command line arguments just after the
- current command.
- :returns: Dictionary with parsed options. Please refer to
- docopt_ documentation for more informations.
- :rtype: dictionary
-
- .. _docopt: http://docopt.org/
- """
- full_args = self.get_cmd_name_parts(for_docopt=True) + args
- options = docopt(self.get_usage(), full_args, help=False)
- self._preprocess_options(options)
-
- # remove all command names from options
- cmd = self.parent
- while cmd is not None and not cmd.has_own_usage():
- cmd = cmd.parent
- if cmd is not None:
- for scn in cmd.child_commands():
- try:
- del options[scn]
- except KeyError:
- LOG().warn('Usage string of "%s.%s" command does not'
- ' contain registered command "%s" command.',
- cmd.__module__, cmd.__class__.__name__, scn)
- # remove also the root command name from options
- if cmd is not None and cmd.cmd_name in options:
- del options[cmd.cmd_name]
- return options
-
- def verify_options(self, options):
- """
- This method can be overriden in subclasses to check, whether the
- options given on command line are valid. If any flaw is discovered, an
- :py:exc:`~lmi.scripts.common.errors.LmiInvalidOptions` exception shall
- be raised. Any returned value is ignored.
-
- .. note::
- This is run before :py:meth:`transform_options()` method.
-
- :param dictionary options: Dictionary as returned by ``docopt`` parser.
- """
- pass
-
- def transform_options(self, options):
- """
- This method can be overriden in subclasses if options shall be somehow
- modified before passing them associated function.
-
- .. note::
- Run after :py:meth:`verify_options()` method.
-
- :param dictionary options: Dictionary as returned by ``docopt`` parser.
- """
- pass
-
- def produce_output(self, data):
- """
- This method can be use to render and print results with default
- formatter.
-
- :param data: Is an object expected by the
- :py:meth:`~lmi.scripts.common.formatter.Formatter.produce_output`
- method of formatter.
- """
- self.formatter.produce_output(data)
-
- def run(self, args):
- """
- Create options dictionary from input arguments, verify them,
- transform them, make positional and keyword arguments out of them and
- pass them to ``process_session()``.
-
- :param list args: List of command arguments.
- :returns: Exit code of application.
- :rtype: integer
- """
- options = self._parse_args(args)
- self.verify_options(options)
- self.transform_options(options)
- self._options = options.copy()
- args, kwargs = self._make_end_point_args(options)
- return self.run_with_args(args, kwargs)
-
- def _print_errors(self, error_list, new_line=True):
- """
- Print list of errors.
-
- :param list errors: Errors to print. Each error is a ``tuple``: ::
-
- (hostname, [error, error])
-
- Where ``error`` may be a test description or an instance of
- exception.
- :param new_line: Whether to print the new line before new error
- table is printed.
- """
- fmt = formatter.ErrorFormatter(self.app.stderr)
- if new_line:
- fmt.out.write('\n')
- if error_list:
- new_table_cmd = fcmd.NewTableCommand("There %s %d error%s" %
- ( 'were' if len(error_list) > 1 else 'was'
- , len(error_list)
- , 's' if len(error_list) > 1 else ''))
- fmt.produce_output((new_table_cmd, ))
- for hostname, host_errors in error_list:
- fmt.produce_output((fcmd.NewHostCommand(hostname), ))
- fmt.produce_output(host_errors)
-