summaryrefslogtreecommitdiffstats
path: root/lmi/scripts/common
diff options
context:
space:
mode:
authorMichal Minar <miminar@redhat.com>2014-02-02 11:24:29 +0100
committerMichal Minar <miminar@redhat.com>2014-02-05 14:44:04 +0100
commite893f9c76ec881fc13072d47696fe89e9cecdeb5 (patch)
treec70899acb88e2e4f893b256b37e074f61f3e440d /lmi/scripts/common
parent3ce60a9be48da5e32d47f1cda810bded671d8b74 (diff)
downloadopenlmi-scripts-e893f9c76ec881fc13072d47696fe89e9cecdeb5.tar.gz
openlmi-scripts-e893f9c76ec881fc13072d47696fe89e9cecdeb5.tar.xz
openlmi-scripts-e893f9c76ec881fc13072d47696fe89e9cecdeb5.zip
redone interactive mode
* Added support for changing to command namespace. Example: $ lmi lmi> :cd sw >sw> help ... >sw> :cd list >>list> pkgs >>list> :pwd /lmi/sw/list >>list> :cd / lmi> * Help and usage messages are now unindented. Resolves ticket #223
Diffstat (limited to 'lmi/scripts/common')
-rw-r--r--lmi/scripts/common/command/base.py89
-rw-r--r--lmi/scripts/common/command/endpoint.py2
-rw-r--r--lmi/scripts/common/command/meta.py2
-rw-r--r--lmi/scripts/common/command/multiplexer.py16
4 files changed, 62 insertions, 47 deletions
diff --git a/lmi/scripts/common/command/base.py b/lmi/scripts/common/command/base.py
index 5572ca8..72c1e94 100644
--- a/lmi/scripts/common/command/base.py
+++ b/lmi/scripts/common/command/base.py
@@ -136,45 +136,48 @@ class LmiBaseCommand(object):
return self._cmd_name
@property
- def cmd_full_name(self):
+ def cmd_name_parts(self):
"""
- Name of this subcommand with all prior commands included.
- It's the sequence of commands as given on command line up to this
- subcommand without any options present. In interactive mode
- this won't contain the name of binary (``sys.argv[0]``).
+ Convenience property calling :py:meth:`get_cmd_name_parts` to obtain
+ command path as a list of all preceding command names.
- :returns: Concatenation of all preceding commands with
- :py:attr:`cmd_name`.
- :rtype: string
- """
- return ' '.join(self.cmd_name_args)
-
- @property
- def cmd_name_args(self):
- """
- The same as :py:attr:`cmd_full_name`, except the result is a list of
- subcommands.
-
- :returns: List of command strings as given on command line up to this
- command.
:rtype: list
"""
- if self.parent is not None:
- return self.parent.cmd_name_args + [self.cmd_name]
- return [self._cmd_name]
-
- @property
- def docopt_cmd_name_args(self):
- """
- Arguments array for docopt parser. Similar to
- :py:meth:`LmiBaseCommand.cmd_name_args` except for the leading binary
- name, which is omitted here.
-
+ return self.get_cmd_name_parts()
+
+ def get_cmd_name_parts(self, all_parts=False, demand_own_usage=True,
+ for_docopt=False):
+ """
+ Get name of this command as a list composed of names of all preceding
+ commands since the top level one. When in interactive mode, only
+ commands following the active one will be present.
+
+ :param boolean full: Take no heed to the active command or interactive
+ mode. Return all command names since top level node inclusive. This
+ is overriden with *for_docopt* flag.
+ :param boolean demand_own_usage: Wether to continue the upward
+ traversal through command hieararchy past the active command until
+ the command with its own usage is found. This is the default behaviour.
+ :param boolean for_docopt: Docopt parser needs to be given arguments list
+ without the first item compared to command names in usage string
+ it receives. Thus this option causes skipping the first item that would
+ be otherwise included.
+ :returns: Command path. Returned list will always contain at least the
+ name of this command.
:rtype: list
"""
- if self.app.interactive_mode:
- return self.cmd_name_args
- return self.cmd_name_args[1:]
+ parts = [self.cmd_name]
+ cmd = self
+ own_usage = cmd.has_own_usage()
+ while ( cmd.parent is not None
+ and (all_parts or self.app.active_command not in (cmd, cmd.parent))
+ or (demand_own_usage and not own_usage)):
+ cmd = cmd.parent
+ parts.append(cmd.cmd_name)
+ own_usage = own_usage or cmd.has_own_usage()
+ if for_docopt and parts:
+ parts.pop()
+ return list(reversed(parts))
def get_usage(self, proper=False):
"""
@@ -196,14 +199,23 @@ class LmiBaseCommand(object):
while not cmd.has_own_usage() and cmd.parent is not None:
cmd = cmd.parent
if cmd.__doc__ is None:
- docstr = "Usage: %s\n" % self.cmd_full_name
+ docstr = "Usage: %s\n" % " ".join(self.cmd_name_parts)
else:
docstr = ( ( cmd.__doc__.rstrip()
- % {'cmd' : cmd.cmd_full_name }
- ) + "\n")
+ % {'cmd' : " ".join(cmd.cmd_name_parts)}
+ ))
+
match = RE_LSPACES.match(docstr)
- if match: # strip leading spaces
+ if match: # strip leading newlines
docstr = docstr[match.end(0):]
+
+ match = re.match(r'^ +', docstr)
+ if match: # unindent help message
+ re_lspaces = re.compile(r'^ {%s}' % match.end(0))
+ docstr = "\n".join(re_lspaces.sub('', l)
+ for l in docstr.splitlines())
+ docstr += "\n"
+
else:
# generate usage string from what is known, applies to nodes
# without own usage
@@ -213,7 +225,7 @@ class LmiBaseCommand(object):
hlp.append("")
hlp.append("Usage:")
hlp.append(" %s (--help | <command> [<args> ...])"
- % self.cmd_full_name)
+ % " ".join(self.cmd_name_parts))
hlp.append("")
hlp.append("Commands:")
cmd_max_len = max(len(c) for c in self.child_commands())
@@ -221,6 +233,7 @@ class LmiBaseCommand(object):
hlp.append((" %%-%ds %%s" % cmd_max_len)
% (name, cmd.get_description()))
docstr = "\n".join(hlp) + "\n"
+
return docstr
@abc.abstractmethod
diff --git a/lmi/scripts/common/command/endpoint.py b/lmi/scripts/common/command/endpoint.py
index c9c0b09..0097028 100644
--- a/lmi/scripts/common/command/endpoint.py
+++ b/lmi/scripts/common/command/endpoint.py
@@ -258,7 +258,7 @@ class LmiEndPointCommand(base.LmiBaseCommand):
.. _docopt: http://docopt.org/
"""
- full_args = self.cmd_name_args[1:] + args
+ full_args = self.get_cmd_name_parts(for_docopt=True) + args
options = docopt(self.get_usage(), full_args, help=False)
self._preprocess_options(options)
diff --git a/lmi/scripts/common/command/meta.py b/lmi/scripts/common/command/meta.py
index 6c2632f..8c740d5 100644
--- a/lmi/scripts/common/command/meta.py
+++ b/lmi/scripts/common/command/meta.py
@@ -644,7 +644,7 @@ class MultiplexerMetaClass(abc.ABCMeta):
'COMMANDS dictionary must be composed of'
' LmiCommandBase subclasses, failed class: "%s"'
% cmd.__name__)
- if not cmd.is_end_point():
+ if not cmd.is_end_point() and not cmd.has_own_usage():
cmd.__doc__ = dcl['__doc__']
def _new_child_commands(_cls):
""" Returns list of subcommands. """
diff --git a/lmi/scripts/common/command/multiplexer.py b/lmi/scripts/common/command/multiplexer.py
index 96e7027..f08422d 100644
--- a/lmi/scripts/common/command/multiplexer.py
+++ b/lmi/scripts/common/command/multiplexer.py
@@ -130,14 +130,16 @@ class LmiCommandMultiplexer(base.LmiBaseCommand):
"""
if not isinstance(args, (list, tuple)):
raise TypeError("args must be a list")
- full_args = self.cmd_name_args[1:] + args
- # check the --help ourselves (the default docopt behaviour checks
- # also for --version)
+ full_args = self.get_cmd_name_parts(for_docopt=True) + args
docopt_kwargs = {
- 'help' : False,
- # let's ignore options following first command for generated
- # usage string
- 'options_first' : not self.has_own_usage()
+ # check the --help ourselves (the default docopt behaviour checks
+ # also for --version)
+ 'help' : False,
+ # let's ignore options following first command for generated
+ # usage string and when a height of this branch is > 2
+ 'options_first' : not self.has_own_usage()
+ or any( not cmd.is_end_point()
+ for cmd in self.child_commands().values())
}
options = docopt(self.get_usage(), full_args, **docopt_kwargs)
if options.pop('--help', False):