summaryrefslogtreecommitdiffstats
path: root/lmi/scripts/common
diff options
context:
space:
mode:
authorMichal Minar <miminar@redhat.com>2013-10-11 10:56:45 +0200
committerMichal Minar <miminar@redhat.com>2013-10-11 13:27:37 +0200
commitde3849421b442e7ac923608eae310b74b7cbbe95 (patch)
treea14cc95cabdc7171ec90c5fc1592c5650341c5d3 /lmi/scripts/common
parentd2c6c4421dc80e7361b072f7360cc03df23412b9 (diff)
downloadopenlmi-scripts-de3849421b442e7ac923608eae310b74b7cbbe95.tar.gz
openlmi-scripts-de3849421b442e7ac923608eae310b74b7cbbe95.tar.xz
openlmi-scripts-de3849421b442e7ac923608eae310b74b7cbbe95.zip
allow multiplexer to have a fallback command
Diffstat (limited to 'lmi/scripts/common')
-rw-r--r--lmi/scripts/common/command/base.py1
-rw-r--r--lmi/scripts/common/command/helper.py16
-rw-r--r--lmi/scripts/common/command/meta.py37
-rw-r--r--lmi/scripts/common/command/multiplexer.py26
-rw-r--r--lmi/scripts/common/command/session.py2
5 files changed, 72 insertions, 10 deletions
diff --git a/lmi/scripts/common/command/base.py b/lmi/scripts/common/command/base.py
index 21d790e..7949202 100644
--- a/lmi/scripts/common/command/base.py
+++ b/lmi/scripts/common/command/base.py
@@ -111,7 +111,6 @@ class LmiBaseCommand(object):
raise NotImplementedError("child_commands() method must be overriden"
" in a subclass")
-
def __init__(self, app, cmd_name, parent=None):
if not isinstance(cmd_name, basestring):
raise TypeError('cmd_name must be a string')
diff --git a/lmi/scripts/common/command/helper.py b/lmi/scripts/common/command/helper.py
index a13c99c..00d842f 100644
--- a/lmi/scripts/common/command/helper.py
+++ b/lmi/scripts/common/command/helper.py
@@ -69,7 +69,8 @@ def make_list_command(func,
props['transform_options'] = transform_func
return LmiLister.__metaclass__(name, (LmiLister, ), props)
-def register_subcommands(command_name, usage, command_map):
+def register_subcommands(command_name, usage, command_map,
+ fallback_command=None):
"""
Create a multiplexer command (a node in a tree of commands).
@@ -77,13 +78,18 @@ def register_subcommands(command_name, usage, command_map):
be given on a command line.
:param string usage: Usage string parseable by ``docopt``.
:param dictionary command_map: Dictionary of subcommands. Associates
- command names to their factories.
+ command names to their factories. It's assigned to ``COMMANDS``
+ property.
+ :param fallback_command: Command factory used when no command is given
+ on command line.
+ :type fallback_command: :py:class:`~.endpoint.LmiEndPointCommand`
:returns: Subclass of :py:class:`~.multiplexer.LmiCommandMultiplexer`.
:rtype: type
"""
- props = { 'COMMANDS' : command_map
- , 'OWN_USAGE' : True
- , '__doc__' : usage }
+ props = { 'COMMANDS' : command_map
+ , 'OWN_USAGE' : True
+ , '__doc__' : usage
+ , 'FALLBACK_COMMAND' : fallback_command }
return LmiCommandMultiplexer.__metaclass__(command_name,
(LmiCommandMultiplexer, ), props)
diff --git a/lmi/scripts/common/command/meta.py b/lmi/scripts/common/command/meta.py
index 8561f83..ad81f53 100644
--- a/lmi/scripts/common/command/meta.py
+++ b/lmi/scripts/common/command/meta.py
@@ -411,6 +411,38 @@ def _handle_opt_preprocess(name, dcl):
dcl['_preprocess_options'] = _new_preprocess_options
+def _handle_fallback_command(name, bases, dcl):
+ """
+ Process ``FALLBACK_COMMAND`` property of multiplexer command. It's turned
+ into a :py:meth:`~.multiplexer.LmiCommandMultiplexer.fallback_command`
+ class method. It needs to be called after the usage string is handled.
+
+ .. seealso::
+ :py:func:`_handle_usage`
+ """
+ fallback = dcl.pop('FALLBACK_COMMAND', None)
+ if fallback is not None:
+ if not issubclass(type(fallback), EndPointCommandMetaClass):
+ raise errors.LmiCommandInvalidProperty(dcl['__module__'], name,
+ "FALLBACK_COMMAND must be a command class"
+ " (subclass of LmiEndPointCommand) not %s" % repr(fallback))
+ if not fallback.has_own_usage():
+ usage_string = dcl.get('__doc__', None)
+ if not usage_string:
+ for base_cls in bases:
+ if not issubclass(base_cls, base.LmiBaseCommand):
+ continue
+ cmd = base_cls
+ while not cmd.has_own_usage() and cmd.parent is not None:
+ cmd = cmd.parent
+ usage_string = cmd.__doc__
+ if not usage_string:
+ errors.LmiCommandError(dcl['__module__'], name,
+ "missing usage string")
+ fallback.__doc__ = usage_string
+ fallback.has_own_usage = lambda cls: True
+ dcl['fallback_command'] = staticmethod(lambda: fallback)
+
class EndPointCommandMetaClass(abc.ABCMeta):
"""
End point command does not have any subcommands. It's a leaf of
@@ -582,6 +614,10 @@ class MultiplexerMetaClass(abc.ABCMeta):
Command names with assigned command classes. Each of them is a
direct subcommands of command with this property. Mandatory
property.
+
+ ``FALLBACK_COMMAND`` : :py:class:`~.endpoint.LmiEndPointCommand`
+ Command factory to use in case that no command is passed on command
+ line.
"""
def __new__(mcs, name, bases, dcl):
@@ -621,5 +657,6 @@ class MultiplexerMetaClass(abc.ABCMeta):
dcl['__module__'], name)
_handle_usage(name, dcl)
+ _handle_fallback_command(name, bases, dcl)
return super(MultiplexerMetaClass, mcs).__new__(mcs, name, bases, dcl)
diff --git a/lmi/scripts/common/command/multiplexer.py b/lmi/scripts/common/command/multiplexer.py
index 829d90f..8476999 100644
--- a/lmi/scripts/common/command/multiplexer.py
+++ b/lmi/scripts/common/command/multiplexer.py
@@ -57,9 +57,12 @@ class LmiCommandMultiplexer(base.LmiBaseCommand):
Where ``Subcmd1`` and ``Subcmd2`` are some other ``LmiBaseCommand``
subclasses. Documentation string must be parseable with ``docopt``.
- ``COMMANDS`` property will be translated to
- :py:meth:`LmiCommandMultiplexer.child_commands` class method by
- :py:class:`~.meta.MultiplexerMetaClass`.
+ Recognized properties:
+
+ ``COMMANDS`` : ``dictionary``
+ property will be translated to
+ :py:meth:`LmiCommandMultiplexer.child_commands` class method by
+ :py:class:`~.meta.MultiplexerMetaClass`.
Using metaclass: :py:class:`.meta.MultiplexerMetaClass`.
"""
@@ -78,6 +81,18 @@ class LmiCommandMultiplexer(base.LmiBaseCommand):
"""
raise NotImplementedError("child_commands must be implemented in"
" a subclass")
+
+ @classmethod
+ def fallback_command(cls):
+ """
+ This is overriden by :py:class:`~.meta.MultiplexerMetaClass` when
+ the ``FALLBACK_COMMAND`` gets processed.
+
+ :returns: Command factory invoked for missing command on command line.
+ :rtype: :py:class:`~.endpoint.LmiEndPointCommand`
+ """
+ return None
+
@classmethod
def is_end_point(cls):
return False
@@ -128,4 +143,9 @@ class LmiCommandMultiplexer(base.LmiBaseCommand):
if options.pop('--help', False):
self.app.stdout.write(self.get_usage())
return 0
+ if ( self.fallback_command() is not None
+ and (not args or args[0] not in self.child_commands())):
+ cmd_cls = self.fallback_command()
+ cmd = cmd_cls(self.app, self.cmd_name, self.parent)
+ return cmd.run(args)
return self.run_subcommand(args[0], args[1:])
diff --git a/lmi/scripts/common/command/session.py b/lmi/scripts/common/command/session.py
index adba8da..cecc387 100644
--- a/lmi/scripts/common/command/session.py
+++ b/lmi/scripts/common/command/session.py
@@ -139,7 +139,7 @@ class LmiSessionCommand(LmiEndPointCommand):
it just prints errors in a form of list.
:param session: Session object.
- :type session: :py:class:`lmi.scripts.common.session.Session`
+ :type session: :py:class:`lmi.scripts.common.command.session.Session`
:param dictionary results: Dictionary of form: ::
{ 'hostname' : (success_flag, result), ... }