Metadata-Version: 2.1
Name: cs.cmdutils
Version: 20211208
Summary: a `BaseCommand` class for constructing command lines, some convenience functions for working with the `cmd` module, and some other command line related stuff
Home-page: https://bitbucket.org/cameron_simpson/css/commits/all
Author: Cameron Simpson
Author-email: cs@cskk.id.au
License: GNU General Public License v3 or later (GPLv3+)
Keywords: python2,python3
Platform: UNKNOWN
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 3
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Description-Content-Type: text/markdown

Convenience functions for working with the Cmd module,
the BaseCommand class for constructing command line programmes,
and other command line related stuff.

*Latest release 20211208*:
BaseCommand: better handle an unknown subcommand.

## Class `BaseCommand`

A base class for handling nestable command lines.

This class provides the basic parse and dispatch mechanisms
for command lines.
To implement a command line
one instantiates a subclass of `BaseCommand`:

    class MyCommand(BaseCommand):
        GETOPT_SPEC = 'ab:c'
        USAGE_FORMAT = r"""Usage: {cmd} [-a] [-b bvalue] [-c] [--] arguments...
          -a    Do it all.
          -b    But using bvalue.
          -c    The 'c' option!
        """
        ...

and provides either a `main` method if the command has no subcommands
or a suite of `cmd_`*subcommand* methods, one per subcommand.

Running a command is done by:

    MyCommand(argv).run()

or via the convenience method:

    MyCommand.run_argv(argv)

Modules which implement a command line mode generally look like this:

    ... imports etc ...
    ... other code ...
    class MyCommand(BaseCommand):
    ... other code ...
    if __name__ == '__main__':
        sys.exit(MyCommand.run_argv(sys.argv))

Instances have a `self.options` attribute on which optional
modes are set,
avoiding conflict with the attributes of `self`.

Subclasses with no subcommands
generally just implement a `main(argv)` method.

Subclasses with subcommands
should implement a `cmd_`*subcommand*`(argv)` method
for each subcommand.
If there is a paragraph in the method docstring
commencing with `Usage:`
then that paragraph is incorporated automatically
into the main usage message.
Example:

    def cmd_ls(self, argv):
        """ Usage: {cmd} [paths...]
              Emit a listing for the named paths.

            Further docstring non-usage information here.
        """
        ... do the "ls" subcommand ...

The subclass is customised by overriding the following methods:
* `apply_defaults()`:
  prepare the initial state of `self.options`
  before any command line options are applied.
* `apply_opt(opt,val)`:
  apply an individual getopt global command line option
  to `self.options`.
* `apply_opts(opts)`:
  apply the `opts` to `self.options`.
  `opts` is an `(option,value)` sequence
  as returned by `getopot.getopt`.
  The default implementation iterates over these and calls `apply_opt`.
* `cmd_`*subcmd*`(argv)`:
  if the command line options are followed by an argument
  whose value is *subcmd*,
  then the method `cmd_`*subcmd*`(subcmd_argv)`
  will be called where `subcmd_argv` contains the command line arguments
  following *subcmd*.
* `main(argv)`:
  if there are no command line aguments after the options
  or the first argument does not have a corresponding
  `cmd_`*subcmd* method
  then method `main(argv)`
  will be called where `argv` contains the command line arguments.
* `run_context()`:
  a context manager to provide setup or teardown actions
  to occur before and after the command implementation respectively,
  such as to open and close a database.

Editorial: why not arparse?
Primarily because when incorrectly invoked
an argparse command line prints the help/usage messgae
and aborts the whole programme with `SystemExit`.

### Method `BaseCommand.__init__(self, argv=None, *, cmd=None, **kw_options)`

Initialise the command line.
Raises `GetoptError` for unrecognised options.

Parameters:
* `argv`:
  optional command line arguments
  including the main command name if `cmd` is not specified.
  The default is `sys.argv`.
  The contents of `argv` are copied,
  permitting desctructive parsing of `argv`.
* `options`:
  a optional object for command state and context.
  If not specified a new `SimpleNamespace`
  is allocated for use as `options`,
  and prefilled with `.cmd` set to `cmd`
  and other values as set by `.apply_defaults()`
  if such a method is provided.
* `cmd`:
  optional command name for context;
  if this is not specified it is taken from `argv.pop(0)`.
Other keyword arguments are applied to `self.options`
as attributes.

The command line arguments are parsed according to
the optional `GETOPT_SPEC` class attribute (default `''`).
If `getopt_spec` is not empty
then `apply_opts(opts)` is called
to apply the supplied options to the state
where `opts` is the return from `getopt.getopt(argv,getopt_spec)`.

After the option parse,
if the first command line argument *foo*
has a corresponding method `cmd_`*foo*
then that argument is removed from the start of `argv`
and `self.cmd_`*foo*`(argv,options,cmd=`*foo*`)` is called
and its value returned.
Otherwise `self.main(argv,options)` is called
and its value returned.

If the command implementation requires some setup or teardown
then this may be provided by the `run_context`
context manager method,
called with `cmd=`*subcmd* for subcommands
and with `cmd=None` for `main`.

### `BaseCommand.OPTIONS_CLASS`

### Method `BaseCommand.__init_subclass__()`

Update subclasses of `BaseCommand`.

Appends the usage message to the class docstring.

### Method `BaseCommand.apply_defaults(self)`

Stub `apply_defaults` method.

Subclasses can override this to set up the initial state of `self.options`.

### Method `BaseCommand.apply_opt(self, *a, **kw)`

Handle an individual global command line option.

This default implementation raises a `RuntimeError`.
It only fires if `getopt` actually gathered arguments
and would imply that a `GETOPT_SPEC` was supplied
without an `apply_opt` or `apply_opts` method to implement the options.

### Method `BaseCommand.apply_opts(self, opts)`

Apply command line options.

### Method `BaseCommand.apply_preargv(self, argv)`

Do any preparsing of `argv` before the subcommand/main-args.
Return the remaining arguments.

This default implementation returns `argv` unchanged.

### Method `BaseCommand.cmd_help(argv)`

Usage: {cmd} [subcommand-names...]
Print the full help for the named subcommands,
or for all subcommands if no names are specified.

### Method `BaseCommand.getopt_error_handler(cmd, options, e, usage, subcmd=None)`

The `getopt_error_handler` method
is used to control the handling of `GetoptError`s raised
during the command line parse
or during the `main` or `cmd_`*subcmd*` calls.

This default handler issues a warning containing the exception text,
prints the usage message to standard error,
and returns `True` to indicate that the error has been handled.

The handler is called with these parameters:
* `cmd`: the command name
* `options`: the `options` object
* `e`: the `GetoptError` exception
* `usage`: the command usage or `None` if this was not provided
* `subcmd`: optional subcommand name;
  if not `None`, is the name of the subcommand which caused the error

It returns a true value if the exception is considered handled,
in which case the main `run` method returns 2.
It returns a false value if the exception is considered unhandled,
in which case the main `run` method reraises the `GetoptError`.

To let the exceptions out unhandled
this can be overridden with a method which just returns `False`.

Otherwise,
the handler may perform any suitable action
and return `True` to contain the exception
or `False` to cause the exception to be reraised.

### Method `BaseCommand.run(self, **kw_options)`

Run a command.
Returns the exit status of the command.
May raise `GetoptError` from subcommands.

Any keyword arguments are used to override `self.options` attributes
for the duration of the run,
for example to presupply a shared `RunState` from an outer context.

If the first command line argument *foo*
has a corresponding method `cmd_`*foo*
then that argument is removed from the start of `argv`
and `self.cmd_`*foo*`(cmd=`*foo*`)` is called
and its value returned.
Otherwise `self.main(argv)` is called
and its value returned.

If the command implementation requires some setup or teardown
then this may be provided by the `run_context`
context manager method,
called with `cmd=`*subcmd* for subcommands
and with `cmd=None` for `main`.

### Method `BaseCommand.run_argv(argv, **kw)`

Create an instance for `argv` and call its `.run()` method.

### Method `BaseCommand.run_context()`

Stub context manager which surrounds `main` or `cmd_`*subcmd*.

### Method `BaseCommand.subcommand_usage_text(subcmd, fulldoc=False, usage_format_mapping=None, short=False)`

Return the usage text for a subcommand.

Parameters:
* `subcmd`: the subcommand name
* `fulldoc`: if true (default `False`)
  return the full docstring with the Usage section expanded
  otherwise just return the Usage section.
* `short`: just include the first line of the usage message,
  intented for when there are many subcommands

It is an error to set both `fulldoc` and `short`.

### Method `BaseCommand.subcommands()`

Return a mapping of subcommand names to class attributes
for attributes which commence with `cls.SUBCOMMAND_METHOD_PREFIX`
by default `'cmd_'`.

### Method `BaseCommand.usage_text(*, cmd=None, format_mapping=None, subcmd=None, short=False)`

Compute the "Usage:" message for this class
from the top level `USAGE_FORMAT`
and the `'Usage:'`-containing docstrings
from its `cmd_*` methods.

Parameters:
* `cmd`: optional command name, default derived from the class name
* `format_mapping`: an optional format mapping for filling
  in format strings in the usage text
* `subcmd`: constrain the usage to a particular subcommand named `subcmd`;
  this is used to produce a shorter usage for subcommand usage failures

## Function `docmd(dofunc)`

Decorator for `cmd.Cmd` subclass methods
to supply some basic quality of service.

This decorator:
- wraps the function call in a `cs.pfx.Pfx` for context
- intercepts `getopt.GetoptError`s, issues a `warning`
  and runs `self.do_help` with the method name,
  then returns `None`
- intercepts other `Exception`s,
  issues an `exception` log message
  and returns `None`

The intended use is to decorate `cmd.Cmd` `do_`* methods:

    from cmd import Cmd
    from cs.cmdutils import docmd
    ...
    class MyCmd(Cmd):
        @docmd
        def do_something(...):
            ... do something ...

# Release Log



*Release 20211208*:
BaseCommand: better handle an unknown subcommand.

*Release 20210927*:
* Usage: show only the per subcommand usage for in-subcommand GetoptError.
* Usage: show terse usage when the subcommand cannot be recognised.
* Usage: support bare -h, -help, --help.

*Release 20210913*:
New BaseCommand.apply_preargv method to gather special arguments before subcommands.

*Release 20210906*:
* BaseCommand.cmd_help: bugfix obsolete parameter list.
* BaseCommand.SUBCOMMAND_ARGV_DEFAULT: support a single str value, turn into list.

*Release 20210809*:
Bugfix BaseCommand.cmd_help for modern API.

*Release 20210731*:
* BaseCommand.run: apply optional keyword arguments to self.options during the run.
* Look for self.SUBCOMMAND_ARGV_DEFAULT if no subcommand is supplied.
* Bugfix case for "main" method and no "cmd_*" methods.
* Bugfix BaseCommand.cmd_help.

*Release 20210420*:
* BaseCommand.getopt_error_handler: replace error print() with warning().
* Docstring improvements.

*Release 20210407.1*:
BaseCommand: bugfix for __init_subclass__ docstring update.

*Release 20210407*:
* BaseCommand.__init_subclass__: behave sanely if the subclass has no initial __doc__.
* BaseCommand: new .run_argv convenience method, obviates the "def main" boilerplate.

*Release 20210404*:
BaseCommand subclasses: automatically add the main usage message to the subclass docstring.

*Release 20210306*:
* BREAKING CHANGE: rework BaseCommand as a more normal class instantiated with argv and with most methods being instance methods, getting the former `options` parameter from self.options.
* BaseCommand: provide default `apply_opt` and `apply_opts` methods; subclasses will generally just override the former.

*Release 20210123*:
BaseCommand: propagate the format mapping (cmd, USAGE_KEYWORDS) to the subusage generation.

*Release 20201102*:
* BaseCommand.cmd_help: supply usage only for "all commands", full docstring for specified commands.
* BaseCommand: honour presupplied options.log_level.
* BaseCommand.usage_text: handle missing USAGE_FORMAT better.
* BaseCommand.run: provide options.upd.
* BaseCommand subclasses may now override BaseCommand.OPTIONS_CLASS (default SimpleNamespace) in order to provide convenience methods on the options.
* BaseCommand.run: separate variable for subcmd with dash translated to underscore to match method names.
* Minor fixes.

*Release 20200615*:
BaseCommand.usage_text: do not mention the "help" command if it is the only subcommand (it won't be available if there are no other subcommands).

*Release 20200521.1*:
Fix DISTINFO.install_requires.

*Release 20200521*:
* BaseCommand.run: support using BaseCommand subclasses as cmd_* names to make it easy to nest BaseCommands.
* BaseCommand: new hack_postopts_argv method called after parsing the main command line options, for inferring subcommands or the like.
* BaseCommand: extract "Usage:" paragraphs from subcommand method docstrings to build the main usage message.
* BaseCommand: new cmd_help default command.
* Assorted bugfixes and small improvements.

*Release 20200318*:
* BaseCommand.run: make argv optional, get additional usage keywords from self.USAGE_KEYWORDS.
* @BaseCommand.add_usage_to_docstring: honour cls.USAGE_KEYWORDS.
* BaseCommand: do not require GETOPT_SPEC for commands with no defined options.
* BaseCommand.run: call cs.logutils.setup_logging.

*Release 20200229*:
Improve subcommand selection logic, replace StackableValues with stackattrs, drop `cmd` from arguments passed to main/cmd_* methods (present in `options`).

*Release 20200210*:
* New BaseCommand.add_usage_to_docstring class method to be called after class setup, to append the usage message to the class docstring.
* BaseCommand.run: remove spurious Pfx(cmd), as logutils does this for us already.

*Release 20190729*:
BaseCommand: support for a USAGE_FORMAT usage message format string and a getopt_error_handler method.

*Release 20190619.1*:
Another niggling docstring formatting fix.

*Release 20190619*:
Minor documentation updates.

*Release 20190617.2*:
Lint.

*Release 20190617.1*:
Initial release with @docmd decorator and alpha quality BaseCommand command line assistance class.

