diff options
author | Michal Minar <miminar@redhat.com> | 2014-02-02 11:24:29 +0100 |
---|---|---|
committer | Michal Minar <miminar@redhat.com> | 2014-02-05 14:44:04 +0100 |
commit | e893f9c76ec881fc13072d47696fe89e9cecdeb5 (patch) | |
tree | c70899acb88e2e4f893b256b37e074f61f3e440d /lmi/scripts/common | |
parent | 3ce60a9be48da5e32d47f1cda810bded671d8b74 (diff) | |
download | openlmi-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.py | 89 | ||||
-rw-r--r-- | lmi/scripts/common/command/endpoint.py | 2 | ||||
-rw-r--r-- | lmi/scripts/common/command/meta.py | 2 | ||||
-rw-r--r-- | lmi/scripts/common/command/multiplexer.py | 16 |
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): |