cs.cmdutils


Namecs.cmdutils JSON
Version 20240422 PyPI version JSON
download
home_pageNone
SummaryConvenience functions for working with the Cmd module, the BaseCommand class for constructing command line programmes, and other command line related stuff.
upload_time2024-04-22 02:56:57
maintainerNone
docs_urlNone
authorNone
requires_pythonNone
licenseGNU General Public License v3 or later (GPLv3+)
keywords python2 python3
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            Convenience functions for working with the Cmd module,
the BaseCommand class for constructing command line programmes,
and other command line related stuff.

*Latest release 20240422*:
* BaseCommandOptions.popopts: return the dict from BaseCommand.popopts().
* BaseCommand.apply_preargv: apply the default options supported by self.options.
* BaseCommandOptions.update(mapping) method, useful for dropping subcommand-specific defaults onto the options ahead of the local popopts() call.

## 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()

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

    ... imports etc ...
    def main(argv=None, **run_kw):
        """ The command line mode.
        """
        return MyCommand(argv).run(**run_kw)
    ... other code ...
    class MyCommand(BaseCommand):
    ... other code ...
    if __name__ == '__main__':
        sys.exit(main(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)` instance method
for each subcommand.
If a subcommand is itself implemented using `BaseCommand`
then it can be a simple attribute:

    cmd_subthing = SubThingCommand

Returning to methods, 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_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 arguments 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`.
But also, I find the whole argparse `add_argument` thing cumbersome.

*Method `BaseCommand.__init__(self, argv=None, *, cmd=None, options=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`.
* `cmd`:
  optional keyword specifying the command name for context;
  if this is not specified it is taken from `argv.pop(0)`.
* `options`:
  an optional keyword providing object for command state and context.
  If not specified a new `self.Options` instance
  is allocated for use as `options`.
  The default `Options` class is `BaseCommandOptions`,
  a dataclass with some prefilled attributes and properties
  to aid use later.
Other keyword arguments are applied to `self.options`
as attributes.

The `cmd` and `argv` parameters have some fiddly semantics for convenience.
There are 3 basic ways to initialise:
* `BaseCommand()`: `argv` comes from `sys.argv`
  and the value for `cmd` is derived from `argv[0]`
* `BaseCommand(argv)`: `argv` is the complete command line
  including the command name and the value for `cmd` is
  derived from `argv[0]`
* `BaseCommand(argv, cmd=foo)`: `argv` is the command
  arguments _after_ the command name and `cmd` is set to
  `foo`

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`*

*Method `BaseCommand.__init_subclass__()`*:
Update subclasses of `BaseCommand`.

Appends the usage message to the class docstring.

*Method `BaseCommand.apply_opt(self, opt, val)`*:
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.

Subclasses can override this
but it is usually easier to override `apply_opt(opt,val)`.

*Method `BaseCommand.apply_preargv(self, argv)`*:
Do any preparsing of `argv` before the subcommand/main-args.
Return the remaining arguments.

This default implementation applies the default options
supported by `self.options` (an instance of `self.Options`
class).

*Method `BaseCommand.cmd_help(argv)`*:
Usage: {cmd} [-l] [subcommand-names...]
Print help for subcommands.
This outputs the full help for the named subcommands,
or the short help for all subcommands if no names are specified.
-l  Long help even if no subcommand-names provided.

*Method `BaseCommand.cmd_shell(self, argv)`*:
Usage: {cmd}
Run a command prompt via cmd.Cmd using this command's subcommands.

*Method `BaseCommand.cmdloop(intro=None)`*:
Use `cmd.Cmd` to run a command loop which calls the `cmd_`* methods.

*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.handle_signal(self, sig, frame, *, runstate: Optional[cs.resources.RunState] = <function <lambda> at 0x104df9f30>)`*:
The default signal handler, which cancels the default `RunState`.

*Method `BaseCommand.poparg(argv: List[str], *a, unpop_on_error=False)`*:
Pop the leading argument off `argv` and parse it.
Return the parsed argument.
Raises `getopt.GetoptError` on a missing or invalid argument.

This is expected to be used inside a `main` or `cmd_*`
command handler method or inside `apply_preargv`.

You can just use:

    value = argv.pop(0)

but this method provides conversion and valuation
and a richer failure mode.

Parameters:
* `argv`: the argument list, which is modified in place with `argv.pop(0)`
* the argument list `argv` may be followed by some help text
  and/or an argument parser function.
* `validate`: an optional function to validate the parsed value;
  this should return a true value if valid,
  or return a false value or raise a `ValueError` if invalid
* `unvalidated_message`: an optional message after `validate`
  for values failing the validation
* `unpop_on_error`: optional keyword parameter, default `False`;
  if true then push the argument back onto the front of `argv`
  if it fails to parse; `GetoptError` is still raised

Typical use inside a `main` or `cmd_*` method might look like:

    self.options.word = self.poparg(argv, int, "a count value")
    self.options.word = self.poparg(
        argv, int, "a count value",
       lambda count: count > 0, "count should be positive")

Because it raises `GetoptError` on a bad argument
the normal usage message failure mode follows automatically.

Demonstration:

    >>> argv = ['word', '3', 'nine', '4']
    >>> BaseCommand.poparg(argv, "word to process")
    'word'
    >>> BaseCommand.poparg(argv, int, "count value")
    3
    >>> BaseCommand.poparg(argv, float, "length")
    Traceback (most recent call last):
      ...
    getopt.GetoptError: length 'nine': float('nine'): could not convert string to float: 'nine'
    >>> BaseCommand.poparg(argv, float, "width", lambda width: width > 5)
    Traceback (most recent call last):
      ...
    getopt.GetoptError: width '4': invalid value
    >>> BaseCommand.poparg(argv, float, "length")
    Traceback (most recent call last):
      ...
    getopt.GetoptError: length: missing argument
    >>> argv = ['-5', 'zz']
    >>> BaseCommand.poparg(argv, float, "size", lambda f: f>0, "size should be >0")
    Traceback (most recent call last):
      ...
    getopt.GetoptError: size '-5': size should be >0
    >>> argv  # -5 was still consumed
    ['zz']
    >>> BaseCommand.poparg(argv, float, "size2", unpop_on_error=True)
    Traceback (most recent call last):
      ...
    getopt.GetoptError: size2 'zz': float('zz'): could not convert string to float: 'zz'
    >>> argv  # zz was pushed back
    ['zz']

*Method `BaseCommand.popopts(argv, attrfor=None, **opt_specs)`*:
Parse option switches from `argv`, a list of command line strings
with leading option switches.
Modify `argv` in place and return a dict mapping switch names to values.

The optional positional argument `attrfor`
may supply an object whose attributes may be set by the options,
for example:

    def cmd_foo(self, argv):
        self.popopts(argv, self.options, a='all', j_=('jobs', int))
        ... use self.options.jobs etc ...

The expected options are specified by the keyword parameters
in `opt_specs`:
* options not starting with a letter may be preceeded by an underscore
  to allow use in the parameter list, for example `_1='once'`
  for a `-1` option setting the `once` option name
* a single letter name specifies a short option
  and a multiletter name specifies a long option
* options requiring an argument have a trailing underscore
* options not requiring an argument normally imply a value
  of `True`; if their synonym commences with a dash they will
  imply a value of `False`, for example `n='dry_run',y='-dry_run'`

The `BaseCommandOptions` class provides a `popopts` method
which is a shim for this method with `attrfor=self` i.e.
the options object.
So common use in a command method usually looks like this:

    class SomeCommand(BaseCommand):

        def cmd_foo(self, argv):
            options = self.options
            # accept a -j or --jobs options
            options.popopts(argv, jobs=1, j='jobs')
            print("jobs =", options.jobs)

The `self.options` object is preprovided as an instance of
the `self.Options` class, which is `BaseCommandOptions` by
default. This presupplies support for some basic options
like `-v` for "verbose" and so forth, and a subcommand
need not describe these in a call to `self.options.popopts()`.

Example:

    >>> import os.path
    >>> from typing import Optional
    >>> @dataclass
    ... class DemoOptions(BaseCommandOptions):
    ...   all: bool = False
    ...   jobs: int = 1
    ...   number: int = 0
    ...   once: bool = False
    ...   path: Optional[str] = None
    ...   trace_exec: bool = False
    ...
    >>> options = DemoOptions()
    >>> argv = ['-1', '-v', '-y', '-j4', '--path=/foo', 'bah', '-x']
    >>> opt_dict = options.popopts(
    ...   argv,
    ...   _1='once',
    ...   a='all',
    ...   j_=('jobs',int),
    ...   x='-trace_exec',
    ...   y='-dry_run',
    ...   dry_run=None,
    ...   path_=(str, os.path.isabs, 'not an absolute path'),
    ...   verbose=None,
    ... )
    >>> opt_dict
    {'once': True, 'verbose': True, 'dry_run': False, 'jobs': 4, 'path': '/foo'}
    >>> options # doctest: +ELLIPSIS
    DemoOptions(cmd=None, dry_run=False, force=False, quiet=False, runstate_signals=(...), verbose=True, all=False, jobs=4, number=0, once=True, path='/foo', trace_exec=False)

*Method `BaseCommand.repl(self, *argv, banner=None, local=None)`*:
Run an interactive Python prompt with some predefined local names.
Aka REPL (Read Evaluate Print Loop).

Parameters:
* `argv`: any notional command line arguments
* `banner`: optional banner string
* `local`: optional local names mapping

The default `local` mapping is a `dict` containing:
* `argv`: from `argv`
* `options`: from `self.options`
* `self`: from `self`
* the attributes of `options`
* the attributes of `self`

This is not presented automatically as a subcommand, but
commands wishing such a command should provide something
like this:

    def cmd_repl(self, argv):
        """ Usage: {cmd}
              Run an interactive Python prompt with some predefined local names.
        """
        return self.repl(*argv)

*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 `Upd` 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.

*Method `BaseCommand.run_context(*a, upd: Optional[cs.upd.Upd] = <function uses_upd.<locals>.<lambda> at 0x104cea3b0>, **kw)`*:
The context manager which surrounds `main` or `cmd_`*subcmd*.

This default does several things, and subclasses should
override it like this:

    @contextmanager
    def run_context(self):
      with super().run_context():
        try:
          ... subclass context setup ...
            yield
        finally:
          ... any unconditional cleanup ...

*Method `BaseCommand.subcommand_usage_text(subcmd, usage_format_mapping=None, short=False)`*:
Return the usage text for a subcommand.

Parameters:
* `subcmd`: the subcommand name
* `short`: just include the first line of the usage message,
  intented for when there are many subcommands

*Method `BaseCommand.subcommands()`*:
Return a mapping of subcommand names to subcommand specifications
for class 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 of 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

## Class `BaseCommandCmd(cmd.Cmd)`

A `cmd.Cmd` subclass used to provide interactive use of a
command's subcommands.

The `BaseCommand.cmdloop()` class method instantiates an
instance of this cand calls its `.cmdloop()` method
i.e. `cmd.Cmd.cmdloop`.

## Class `BaseCommandOptions(cs.threads.HasThreadState)`

A base class for the `BaseCommand` `options` object.

This is the default class for the `self.options` object
available during `BaseCommand.run()`,
and available as the `BaseCommand.Options` attribute.

Any keyword arguments are applied as field updates to the instance.

It comes prefilled with:
* `.dry_run=False`
* `.force=False`
* `.quiet=False`
* `.verbose=False`
and a `.doit` property which is the inverse of `.dry_run`.

It is recommended that if ``BaseCommand` subclasses use a
different type for their `Options` that it should be a
subclass of `BaseCommandOptions`.
Since `BaseCommandOptions` is a data class, this typically looks like:

    @dataclass
    class Options(BaseCommand.Options):
        ... optional extra fields etc ...

*Method `BaseCommandOptions.__call__(self, **updates)`*:
Calling the options object returns a context manager whose
value is a shallow copy of the options with any `suboptions` applied.

Example showing the semantics:

    >>> from cs.cmdutils import BaseCommandOptions
    >>> @dataclass
    ... class DemoOptions(BaseCommandOptions):
    ...   x: int = 0
    ...
    >>> options = DemoOptions(x=1)
    >>> assert options.x == 1
    >>> assert not options.verbose
    >>> with options(verbose=True) as subopts:
    ...     assert options is not subopts
    ...     assert options.x == 1
    ...     assert not options.verbose
    ...     assert subopts.x == 1
    ...     assert subopts.verbose
    ...
    >>> assert options.x == 1
    >>> assert not options.verbose

*Method `BaseCommandOptions.copy(self, **updates)`*:
Return a new instance of `BaseCommandOptions` (well, `type(self)`)
which is a shallow copy of the public attributes from `self.__dict__`.

Any keyword arguments are applied as attribute updates to the copy.

*Property `BaseCommandOptions.doit`*:
I usually use a `doit` flag,
the inverse of `dry_run`.

*`BaseCommandOptions.perthread_state`*

*Method `BaseCommandOptions.popopts(self, argv, **opt_specs)`*:
Convenience method to appply `BaseCommand.popopts` to the options (`self`).

Example for a `BaseCommand` `cmd_foo` method:

    def cmd_foo(self, argv):
        self.options.popopts(
            c_='config',
            l='long',
            x='trace',
        )
        if self.options.dry_run:
            print("dry run!")

The class attribute `COMMON_OPT_SPECS` is a mapping of
options which are always supported. `BaseCommandOptions`
has: `COMMON_OPT_SPECS={'n': 'dry_run', 'q': 'quiet', 'v': 'verbose'}`.

A subclass with more common options might extend this like so,
from `cs.hashindex`:

    COMMON_OPT_SPECS = dict(
        e='ssh_exe',
        h_='hashname',
        H_='hashindex_exe',
        **BaseCommand.Options.COMMON_OPT_SPECS,
    )

*Method `BaseCommandOptions.update(self, **updates)`*:
Modify the options in place with the mapping `updates`.
It would be more normal to call the options in a `with` statement
as shown for `__call__`.

## 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 ...

## Function `uses_cmd_options(func, cls=<class 'cs.cmdutils.BaseCommandOptions'>, options_param_name='options')`

A decorator to provide a default parameter containing the
prevailing `BaseCommandOptions` instance as the `options` keyword
argument, using the `cs.deco.default_params` decorator factory.

This allows functions to utilitse global options set by a
command such as `options.dry_run` or `options.verbose` without
the tedious plumbing through the entire call stack.

Parameters:
* `cls`: the `BaseCommandOptions` or `BaseCommand` class,
  default `BaseCommandOptions`. If a `BaseCommand` subclass is
  provided its `cls.Options` class is used.
* `options_param_name`: the parameter name to provide, default `options`

Examples:

    @uses_cmd_options
    def f(x,*,options):
        """ Run directly from the prevailing options. """
        if options.verbose:
            print("doing f with x =", x)
        ....

    @uses_cmd_options
    def f(x,*,verbose=None,options):
        """ Get defaults from the prevailing options. """
        if verbose is None:
            verbose = options.verbose
        if verbose:
            print("doing f with x =", x)
        ....

# Release Log



*Release 20240422*:
* BaseCommandOptions.popopts: return the dict from BaseCommand.popopts().
* BaseCommand.apply_preargv: apply the default options supported by self.options.
* BaseCommandOptions.update(mapping) method, useful for dropping subcommand-specific defaults onto the options ahead of the local popopts() call.

*Release 20240412*:
* BaseCommand.run_context: do not store .upd and .runstate on the options (it confuses options in subcommands and we have @uses_runstate and @uses_upd forthis anyway these days).
* BaseCommand.run_context: catch SIGQUIT, present the default handler as BaseCommand.handle_signal.

*Release 20240316*:
* New @uses_cmd_options decorator to provide an "options" parameter being the prevailing BaseCommandOptions instance.
* BaseCommandOptions.popopts: get common options from BaseCommandOptions.COMMON_OPT_SPECS.

*Release 20240211*:
* Include the first sentence of the subcommand description in the short help.
* BaseCommandOptions: move the runstate_signals into this directly.
* BaseCommand: move the run() options stuff into run_context() and have it work on a copy of the original options.
* BaseCommandCmd: implement get_names(), provide docstrings for the do_* attributes, thus help.
* BaseCommand.run_context: make runstate and upd keyword only parameters.

*Release 20240201*:
* BaseCommand.run: catch CancellationError and return 1.
* BaseCommandCmd.__getattr__: recognise EOF, exit and quit to terminate the cmdloop.

*Release 20231129*:
BaseCommandOptions: define a runstate field.

*Release 20230703*:
Small internal changes.

*Release 20230612*:
* BaseCommand.cmdloop: fix intro parameter.
* Other small fixes.

*Release 20230407*:
* BaseCommand: use @uses_runstate when preparing the command, store as self.options.runstate.
* Make BaseCommandOptions a data class.
* Drop any pretence at python 2 support, we're long past that.
* BaseCommand: new cmdloop method to run a cmd.Cmd instance to run subcommand interactively.
* BaseCommand: rename shell to repl, add cmd_shell to call cmdloop().
* Drop BaseCommand.apply_defaults in favour of the Options dataclass.
* BaseCommand: do setup_logging before initiating the Options instance.

*Release 20230212*:
* BaseCommand.run_context: update RunState support.
* BaseCommand.run_context: always be having an self.options.upd.

*Release 20230211*:
BaseCommand: new shell() method to present an interactive Python prompt for use by subclasses cmd_shell method if desired.

*Release 20221228*:
Move a lot of the context logic from BaseCommand.run to BaseCommand.run_context, which now must be correctly overridden in subclasses.

*Release 20220918*:
* BaseCommand.run_context: expand default signals to include SIGHUP, expose as BaseCommand.DEFAULT_SIGNALS.
* BaseCommand.run: pass in the subclass handle_signal method if present.

*Release 20220626*:
* BaseCommand.poparg: fix positional argument handling.
* BaseCommand.poparg: new unpop_on_error=False parameter to support pushing a bad argument back onto the front of the argument list.

*Release 20220606*:
BaseCommand.run: remove the Upd bodge, too annoying, now fixed in cs.upd I believe.

*Release 20220605*:
* BaseCommand: new popopts(argv,...) compact getopt wrapper.
* BaseCommand: new poparg(argv,...) compact validating argument consumer.
* BaseCommand: drop run_argv, provided no utility.
* BaseCommand.run: get the RunState signal list from self.options.runstate_signals.
* BaseCommand.apply_opts: support multiple individual options raising GetoptError, as I hate commands which abort at the first bad option.
* Assorted other small things.

*Release 20220429*:
* BaseCommand: fold dots in argv[0] into underscores, supports subcommands like "setup.py".
* BaseCommand: new popargv(argv[,help_text[,parse[,validate[,unvalidated_message]]]]) helper class method.
* BaseCommand: accept dashed-form of the underscored_form subcommand name.
* BaseCommand: new self.options.runstate_signals=SIGINT,SIGTERM specifying singals to catch-and-cancel, shuffle run() context managers.

*Release 20220318*:
BaseCommand.__init__: handle main() method in the New Scheme.

*Release 20220315*:
_BaseSubCommand.__init__: hook in the class USAGE_KEYWORDS for methods.

*Release 20220311*:
BaseCommand: big refactor of subcommand internals and make the "cmd_foo=FooCommand" implementation work properly.

*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.


            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "cs.cmdutils",
    "maintainer": null,
    "docs_url": null,
    "requires_python": null,
    "maintainer_email": null,
    "keywords": "python2, python3",
    "author": null,
    "author_email": "Cameron Simpson <cs@cskk.id.au>",
    "download_url": "https://files.pythonhosted.org/packages/07/c1/404cb89a74e50f75723271d78fb5e0c8ecc8657e9e6a447b5a53954cbb98/cs.cmdutils-20240422.tar.gz",
    "platform": null,
    "description": "Convenience functions for working with the Cmd module,\nthe BaseCommand class for constructing command line programmes,\nand other command line related stuff.\n\n*Latest release 20240422*:\n* BaseCommandOptions.popopts: return the dict from BaseCommand.popopts().\n* BaseCommand.apply_preargv: apply the default options supported by self.options.\n* BaseCommandOptions.update(mapping) method, useful for dropping subcommand-specific defaults onto the options ahead of the local popopts() call.\n\n## Class `BaseCommand`\n\nA base class for handling nestable command lines.\n\nThis class provides the basic parse and dispatch mechanisms\nfor command lines.\nTo implement a command line\none instantiates a subclass of `BaseCommand`:\n\n    class MyCommand(BaseCommand):\n        GETOPT_SPEC = 'ab:c'\n        USAGE_FORMAT = r\"\"\"Usage: {cmd} [-a] [-b bvalue] [-c] [--] arguments...\n          -a    Do it all.\n          -b    But using bvalue.\n          -c    The 'c' option!\n        \"\"\"\n        ...\n\nand provides either a `main` method if the command has no subcommands\nor a suite of `cmd_`*subcommand* methods, one per subcommand.\n\nRunning a command is done by:\n\n    MyCommand(argv).run()\n\nModules which implement a command line mode generally look like this:\n\n    ... imports etc ...\n    def main(argv=None, **run_kw):\n        \"\"\" The command line mode.\n        \"\"\"\n        return MyCommand(argv).run(**run_kw)\n    ... other code ...\n    class MyCommand(BaseCommand):\n    ... other code ...\n    if __name__ == '__main__':\n        sys.exit(main(sys.argv))\n\nInstances have a `self.options` attribute on which optional\nmodes are set,\navoiding conflict with the attributes of `self`.\n\nSubclasses with no subcommands\ngenerally just implement a `main(argv)` method.\n\nSubclasses with subcommands\nshould implement a `cmd_`*subcommand*`(argv)` instance method\nfor each subcommand.\nIf a subcommand is itself implemented using `BaseCommand`\nthen it can be a simple attribute:\n\n    cmd_subthing = SubThingCommand\n\nReturning to methods, if there is a paragraph in the method docstring\ncommencing with `Usage:`\nthen that paragraph is incorporated automatically\ninto the main usage message.\nExample:\n\n    def cmd_ls(self, argv):\n        \"\"\" Usage: {cmd} [paths...]\n              Emit a listing for the named paths.\n\n            Further docstring non-usage information here.\n        \"\"\"\n        ... do the \"ls\" subcommand ...\n\nThe subclass is customised by overriding the following methods:\n* `apply_opt(opt,val)`:\n  apply an individual getopt global command line option\n  to `self.options`.\n* `apply_opts(opts)`:\n  apply the `opts` to `self.options`.\n  `opts` is an `(option,value)` sequence\n  as returned by `getopot.getopt`.\n  The default implementation iterates over these and calls `apply_opt`.\n* `cmd_`*subcmd*`(argv)`:\n  if the command line options are followed by an argument\n  whose value is *subcmd*,\n  then the method `cmd_`*subcmd*`(subcmd_argv)`\n  will be called where `subcmd_argv` contains the command line arguments\n  following *subcmd*.\n* `main(argv)`:\n  if there are no command line arguments after the options\n  or the first argument does not have a corresponding\n  `cmd_`*subcmd* method\n  then method `main(argv)`\n  will be called where `argv` contains the command line arguments.\n* `run_context()`:\n  a context manager to provide setup or teardown actions\n  to occur before and after the command implementation respectively,\n  such as to open and close a database.\n\nEditorial: why not arparse?\nPrimarily because when incorrectly invoked\nan argparse command line prints the help/usage messgae\nand aborts the whole programme with `SystemExit`.\nBut also, I find the whole argparse `add_argument` thing cumbersome.\n\n*Method `BaseCommand.__init__(self, argv=None, *, cmd=None, options=None, **kw_options)`*:\nInitialise the command line.\nRaises `GetoptError` for unrecognised options.\n\nParameters:\n* `argv`:\n  optional command line arguments\n  including the main command name if `cmd` is not specified.\n  The default is `sys.argv`.\n  The contents of `argv` are copied,\n  permitting desctructive parsing of `argv`.\n* `cmd`:\n  optional keyword specifying the command name for context;\n  if this is not specified it is taken from `argv.pop(0)`.\n* `options`:\n  an optional keyword providing object for command state and context.\n  If not specified a new `self.Options` instance\n  is allocated for use as `options`.\n  The default `Options` class is `BaseCommandOptions`,\n  a dataclass with some prefilled attributes and properties\n  to aid use later.\nOther keyword arguments are applied to `self.options`\nas attributes.\n\nThe `cmd` and `argv` parameters have some fiddly semantics for convenience.\nThere are 3 basic ways to initialise:\n* `BaseCommand()`: `argv` comes from `sys.argv`\n  and the value for `cmd` is derived from `argv[0]`\n* `BaseCommand(argv)`: `argv` is the complete command line\n  including the command name and the value for `cmd` is\n  derived from `argv[0]`\n* `BaseCommand(argv, cmd=foo)`: `argv` is the command\n  arguments _after_ the command name and `cmd` is set to\n  `foo`\n\nThe command line arguments are parsed according to\nthe optional `GETOPT_SPEC` class attribute (default `''`).\nIf `getopt_spec` is not empty\nthen `apply_opts(opts)` is called\nto apply the supplied options to the state\nwhere `opts` is the return from `getopt.getopt(argv,getopt_spec)`.\n\nAfter the option parse,\nif the first command line argument *foo*\nhas a corresponding method `cmd_`*foo*\nthen that argument is removed from the start of `argv`\nand `self.cmd_`*foo*`(argv,options,cmd=`*foo*`)` is called\nand its value returned.\nOtherwise `self.main(argv,options)` is called\nand its value returned.\n\nIf the command implementation requires some setup or teardown\nthen this may be provided by the `run_context`\ncontext manager method,\ncalled with `cmd=`*subcmd* for subcommands\nand with `cmd=None` for `main`.\n\n*`BaseCommand.Options`*\n\n*Method `BaseCommand.__init_subclass__()`*:\nUpdate subclasses of `BaseCommand`.\n\nAppends the usage message to the class docstring.\n\n*Method `BaseCommand.apply_opt(self, opt, val)`*:\nHandle an individual global command line option.\n\nThis default implementation raises a `RuntimeError`.\nIt only fires if `getopt` actually gathered arguments\nand would imply that a `GETOPT_SPEC` was supplied\nwithout an `apply_opt` or `apply_opts` method to implement the options.\n\n*Method `BaseCommand.apply_opts(self, opts)`*:\nApply command line options.\n\nSubclasses can override this\nbut it is usually easier to override `apply_opt(opt,val)`.\n\n*Method `BaseCommand.apply_preargv(self, argv)`*:\nDo any preparsing of `argv` before the subcommand/main-args.\nReturn the remaining arguments.\n\nThis default implementation applies the default options\nsupported by `self.options` (an instance of `self.Options`\nclass).\n\n*Method `BaseCommand.cmd_help(argv)`*:\nUsage: {cmd} [-l] [subcommand-names...]\nPrint help for subcommands.\nThis outputs the full help for the named subcommands,\nor the short help for all subcommands if no names are specified.\n-l  Long help even if no subcommand-names provided.\n\n*Method `BaseCommand.cmd_shell(self, argv)`*:\nUsage: {cmd}\nRun a command prompt via cmd.Cmd using this command's subcommands.\n\n*Method `BaseCommand.cmdloop(intro=None)`*:\nUse `cmd.Cmd` to run a command loop which calls the `cmd_`* methods.\n\n*Method `BaseCommand.getopt_error_handler(cmd, options, e, usage, subcmd=None)`*:\nThe `getopt_error_handler` method\nis used to control the handling of `GetoptError`s raised\nduring the command line parse\nor during the `main` or `cmd_`*subcmd*` calls.\n\nThis default handler issues a warning containing the exception text,\nprints the usage message to standard error,\nand returns `True` to indicate that the error has been handled.\n\nThe handler is called with these parameters:\n* `cmd`: the command name\n* `options`: the `options` object\n* `e`: the `GetoptError` exception\n* `usage`: the command usage or `None` if this was not provided\n* `subcmd`: optional subcommand name;\n  if not `None`, is the name of the subcommand which caused the error\n\nIt returns a true value if the exception is considered handled,\nin which case the main `run` method returns 2.\nIt returns a false value if the exception is considered unhandled,\nin which case the main `run` method reraises the `GetoptError`.\n\nTo let the exceptions out unhandled\nthis can be overridden with a method which just returns `False`.\n\nOtherwise,\nthe handler may perform any suitable action\nand return `True` to contain the exception\nor `False` to cause the exception to be reraised.\n\n*Method `BaseCommand.handle_signal(self, sig, frame, *, runstate: Optional[cs.resources.RunState] = <function <lambda> at 0x104df9f30>)`*:\nThe default signal handler, which cancels the default `RunState`.\n\n*Method `BaseCommand.poparg(argv: List[str], *a, unpop_on_error=False)`*:\nPop the leading argument off `argv` and parse it.\nReturn the parsed argument.\nRaises `getopt.GetoptError` on a missing or invalid argument.\n\nThis is expected to be used inside a `main` or `cmd_*`\ncommand handler method or inside `apply_preargv`.\n\nYou can just use:\n\n    value = argv.pop(0)\n\nbut this method provides conversion and valuation\nand a richer failure mode.\n\nParameters:\n* `argv`: the argument list, which is modified in place with `argv.pop(0)`\n* the argument list `argv` may be followed by some help text\n  and/or an argument parser function.\n* `validate`: an optional function to validate the parsed value;\n  this should return a true value if valid,\n  or return a false value or raise a `ValueError` if invalid\n* `unvalidated_message`: an optional message after `validate`\n  for values failing the validation\n* `unpop_on_error`: optional keyword parameter, default `False`;\n  if true then push the argument back onto the front of `argv`\n  if it fails to parse; `GetoptError` is still raised\n\nTypical use inside a `main` or `cmd_*` method might look like:\n\n    self.options.word = self.poparg(argv, int, \"a count value\")\n    self.options.word = self.poparg(\n        argv, int, \"a count value\",\n       lambda count: count > 0, \"count should be positive\")\n\nBecause it raises `GetoptError` on a bad argument\nthe normal usage message failure mode follows automatically.\n\nDemonstration:\n\n    >>> argv = ['word', '3', 'nine', '4']\n    >>> BaseCommand.poparg(argv, \"word to process\")\n    'word'\n    >>> BaseCommand.poparg(argv, int, \"count value\")\n    3\n    >>> BaseCommand.poparg(argv, float, \"length\")\n    Traceback (most recent call last):\n      ...\n    getopt.GetoptError: length 'nine': float('nine'): could not convert string to float: 'nine'\n    >>> BaseCommand.poparg(argv, float, \"width\", lambda width: width > 5)\n    Traceback (most recent call last):\n      ...\n    getopt.GetoptError: width '4': invalid value\n    >>> BaseCommand.poparg(argv, float, \"length\")\n    Traceback (most recent call last):\n      ...\n    getopt.GetoptError: length: missing argument\n    >>> argv = ['-5', 'zz']\n    >>> BaseCommand.poparg(argv, float, \"size\", lambda f: f>0, \"size should be >0\")\n    Traceback (most recent call last):\n      ...\n    getopt.GetoptError: size '-5': size should be >0\n    >>> argv  # -5 was still consumed\n    ['zz']\n    >>> BaseCommand.poparg(argv, float, \"size2\", unpop_on_error=True)\n    Traceback (most recent call last):\n      ...\n    getopt.GetoptError: size2 'zz': float('zz'): could not convert string to float: 'zz'\n    >>> argv  # zz was pushed back\n    ['zz']\n\n*Method `BaseCommand.popopts(argv, attrfor=None, **opt_specs)`*:\nParse option switches from `argv`, a list of command line strings\nwith leading option switches.\nModify `argv` in place and return a dict mapping switch names to values.\n\nThe optional positional argument `attrfor`\nmay supply an object whose attributes may be set by the options,\nfor example:\n\n    def cmd_foo(self, argv):\n        self.popopts(argv, self.options, a='all', j_=('jobs', int))\n        ... use self.options.jobs etc ...\n\nThe expected options are specified by the keyword parameters\nin `opt_specs`:\n* options not starting with a letter may be preceeded by an underscore\n  to allow use in the parameter list, for example `_1='once'`\n  for a `-1` option setting the `once` option name\n* a single letter name specifies a short option\n  and a multiletter name specifies a long option\n* options requiring an argument have a trailing underscore\n* options not requiring an argument normally imply a value\n  of `True`; if their synonym commences with a dash they will\n  imply a value of `False`, for example `n='dry_run',y='-dry_run'`\n\nThe `BaseCommandOptions` class provides a `popopts` method\nwhich is a shim for this method with `attrfor=self` i.e.\nthe options object.\nSo common use in a command method usually looks like this:\n\n    class SomeCommand(BaseCommand):\n\n        def cmd_foo(self, argv):\n            options = self.options\n            # accept a -j or --jobs options\n            options.popopts(argv, jobs=1, j='jobs')\n            print(\"jobs =\", options.jobs)\n\nThe `self.options` object is preprovided as an instance of\nthe `self.Options` class, which is `BaseCommandOptions` by\ndefault. This presupplies support for some basic options\nlike `-v` for \"verbose\" and so forth, and a subcommand\nneed not describe these in a call to `self.options.popopts()`.\n\nExample:\n\n    >>> import os.path\n    >>> from typing import Optional\n    >>> @dataclass\n    ... class DemoOptions(BaseCommandOptions):\n    ...   all: bool = False\n    ...   jobs: int = 1\n    ...   number: int = 0\n    ...   once: bool = False\n    ...   path: Optional[str] = None\n    ...   trace_exec: bool = False\n    ...\n    >>> options = DemoOptions()\n    >>> argv = ['-1', '-v', '-y', '-j4', '--path=/foo', 'bah', '-x']\n    >>> opt_dict = options.popopts(\n    ...   argv,\n    ...   _1='once',\n    ...   a='all',\n    ...   j_=('jobs',int),\n    ...   x='-trace_exec',\n    ...   y='-dry_run',\n    ...   dry_run=None,\n    ...   path_=(str, os.path.isabs, 'not an absolute path'),\n    ...   verbose=None,\n    ... )\n    >>> opt_dict\n    {'once': True, 'verbose': True, 'dry_run': False, 'jobs': 4, 'path': '/foo'}\n    >>> options # doctest: +ELLIPSIS\n    DemoOptions(cmd=None, dry_run=False, force=False, quiet=False, runstate_signals=(...), verbose=True, all=False, jobs=4, number=0, once=True, path='/foo', trace_exec=False)\n\n*Method `BaseCommand.repl(self, *argv, banner=None, local=None)`*:\nRun an interactive Python prompt with some predefined local names.\nAka REPL (Read Evaluate Print Loop).\n\nParameters:\n* `argv`: any notional command line arguments\n* `banner`: optional banner string\n* `local`: optional local names mapping\n\nThe default `local` mapping is a `dict` containing:\n* `argv`: from `argv`\n* `options`: from `self.options`\n* `self`: from `self`\n* the attributes of `options`\n* the attributes of `self`\n\nThis is not presented automatically as a subcommand, but\ncommands wishing such a command should provide something\nlike this:\n\n    def cmd_repl(self, argv):\n        \"\"\" Usage: {cmd}\n              Run an interactive Python prompt with some predefined local names.\n        \"\"\"\n        return self.repl(*argv)\n\n*Method `BaseCommand.run(self, **kw_options)`*:\nRun a command.\nReturns the exit status of the command.\nMay raise `GetoptError` from subcommands.\n\nAny keyword arguments are used to override `self.options` attributes\nfor the duration of the run,\nfor example to presupply a shared `Upd` from an outer context.\n\nIf the first command line argument *foo*\nhas a corresponding method `cmd_`*foo*\nthen that argument is removed from the start of `argv`\nand `self.cmd_`*foo*`(cmd=`*foo*`)` is called\nand its value returned.\nOtherwise `self.main(argv)` is called\nand its value returned.\n\nIf the command implementation requires some setup or teardown\nthen this may be provided by the `run_context()`\ncontext manager method.\n\n*Method `BaseCommand.run_context(*a, upd: Optional[cs.upd.Upd] = <function uses_upd.<locals>.<lambda> at 0x104cea3b0>, **kw)`*:\nThe context manager which surrounds `main` or `cmd_`*subcmd*.\n\nThis default does several things, and subclasses should\noverride it like this:\n\n    @contextmanager\n    def run_context(self):\n      with super().run_context():\n        try:\n          ... subclass context setup ...\n            yield\n        finally:\n          ... any unconditional cleanup ...\n\n*Method `BaseCommand.subcommand_usage_text(subcmd, usage_format_mapping=None, short=False)`*:\nReturn the usage text for a subcommand.\n\nParameters:\n* `subcmd`: the subcommand name\n* `short`: just include the first line of the usage message,\n  intented for when there are many subcommands\n\n*Method `BaseCommand.subcommands()`*:\nReturn a mapping of subcommand names to subcommand specifications\nfor class attributes which commence with `cls.SUBCOMMAND_METHOD_PREFIX`\nby default `'cmd_'`.\n\n*Method `BaseCommand.usage_text(*, cmd=None, format_mapping=None, subcmd=None, short=False)`*:\nCompute the \"Usage:\" message for this class\nfrom the top level `USAGE_FORMAT`\nand the `'Usage:'`-containing docstrings of its `cmd_*` methods.\n\nParameters:\n* `cmd`: optional command name, default derived from the class name\n* `format_mapping`: an optional format mapping for filling\n  in format strings in the usage text\n* `subcmd`: constrain the usage to a particular subcommand named `subcmd`;\n  this is used to produce a shorter usage for subcommand usage failures\n\n## Class `BaseCommandCmd(cmd.Cmd)`\n\nA `cmd.Cmd` subclass used to provide interactive use of a\ncommand's subcommands.\n\nThe `BaseCommand.cmdloop()` class method instantiates an\ninstance of this cand calls its `.cmdloop()` method\ni.e. `cmd.Cmd.cmdloop`.\n\n## Class `BaseCommandOptions(cs.threads.HasThreadState)`\n\nA base class for the `BaseCommand` `options` object.\n\nThis is the default class for the `self.options` object\navailable during `BaseCommand.run()`,\nand available as the `BaseCommand.Options` attribute.\n\nAny keyword arguments are applied as field updates to the instance.\n\nIt comes prefilled with:\n* `.dry_run=False`\n* `.force=False`\n* `.quiet=False`\n* `.verbose=False`\nand a `.doit` property which is the inverse of `.dry_run`.\n\nIt is recommended that if ``BaseCommand` subclasses use a\ndifferent type for their `Options` that it should be a\nsubclass of `BaseCommandOptions`.\nSince `BaseCommandOptions` is a data class, this typically looks like:\n\n    @dataclass\n    class Options(BaseCommand.Options):\n        ... optional extra fields etc ...\n\n*Method `BaseCommandOptions.__call__(self, **updates)`*:\nCalling the options object returns a context manager whose\nvalue is a shallow copy of the options with any `suboptions` applied.\n\nExample showing the semantics:\n\n    >>> from cs.cmdutils import BaseCommandOptions\n    >>> @dataclass\n    ... class DemoOptions(BaseCommandOptions):\n    ...   x: int = 0\n    ...\n    >>> options = DemoOptions(x=1)\n    >>> assert options.x == 1\n    >>> assert not options.verbose\n    >>> with options(verbose=True) as subopts:\n    ...     assert options is not subopts\n    ...     assert options.x == 1\n    ...     assert not options.verbose\n    ...     assert subopts.x == 1\n    ...     assert subopts.verbose\n    ...\n    >>> assert options.x == 1\n    >>> assert not options.verbose\n\n*Method `BaseCommandOptions.copy(self, **updates)`*:\nReturn a new instance of `BaseCommandOptions` (well, `type(self)`)\nwhich is a shallow copy of the public attributes from `self.__dict__`.\n\nAny keyword arguments are applied as attribute updates to the copy.\n\n*Property `BaseCommandOptions.doit`*:\nI usually use a `doit` flag,\nthe inverse of `dry_run`.\n\n*`BaseCommandOptions.perthread_state`*\n\n*Method `BaseCommandOptions.popopts(self, argv, **opt_specs)`*:\nConvenience method to appply `BaseCommand.popopts` to the options (`self`).\n\nExample for a `BaseCommand` `cmd_foo` method:\n\n    def cmd_foo(self, argv):\n        self.options.popopts(\n            c_='config',\n            l='long',\n            x='trace',\n        )\n        if self.options.dry_run:\n            print(\"dry run!\")\n\nThe class attribute `COMMON_OPT_SPECS` is a mapping of\noptions which are always supported. `BaseCommandOptions`\nhas: `COMMON_OPT_SPECS={'n': 'dry_run', 'q': 'quiet', 'v': 'verbose'}`.\n\nA subclass with more common options might extend this like so,\nfrom `cs.hashindex`:\n\n    COMMON_OPT_SPECS = dict(\n        e='ssh_exe',\n        h_='hashname',\n        H_='hashindex_exe',\n        **BaseCommand.Options.COMMON_OPT_SPECS,\n    )\n\n*Method `BaseCommandOptions.update(self, **updates)`*:\nModify the options in place with the mapping `updates`.\nIt would be more normal to call the options in a `with` statement\nas shown for `__call__`.\n\n## Function `docmd(dofunc)`\n\nDecorator for `cmd.Cmd` subclass methods\nto supply some basic quality of service.\n\nThis decorator:\n- wraps the function call in a `cs.pfx.Pfx` for context\n- intercepts `getopt.GetoptError`s, issues a `warning`\n  and runs `self.do_help` with the method name,\n  then returns `None`\n- intercepts other `Exception`s,\n  issues an `exception` log message\n  and returns `None`\n\nThe intended use is to decorate `cmd.Cmd` `do_`* methods:\n\n    from cmd import Cmd\n    from cs.cmdutils import docmd\n    ...\n    class MyCmd(Cmd):\n        @docmd\n        def do_something(...):\n            ... do something ...\n\n## Function `uses_cmd_options(func, cls=<class 'cs.cmdutils.BaseCommandOptions'>, options_param_name='options')`\n\nA decorator to provide a default parameter containing the\nprevailing `BaseCommandOptions` instance as the `options` keyword\nargument, using the `cs.deco.default_params` decorator factory.\n\nThis allows functions to utilitse global options set by a\ncommand such as `options.dry_run` or `options.verbose` without\nthe tedious plumbing through the entire call stack.\n\nParameters:\n* `cls`: the `BaseCommandOptions` or `BaseCommand` class,\n  default `BaseCommandOptions`. If a `BaseCommand` subclass is\n  provided its `cls.Options` class is used.\n* `options_param_name`: the parameter name to provide, default `options`\n\nExamples:\n\n    @uses_cmd_options\n    def f(x,*,options):\n        \"\"\" Run directly from the prevailing options. \"\"\"\n        if options.verbose:\n            print(\"doing f with x =\", x)\n        ....\n\n    @uses_cmd_options\n    def f(x,*,verbose=None,options):\n        \"\"\" Get defaults from the prevailing options. \"\"\"\n        if verbose is None:\n            verbose = options.verbose\n        if verbose:\n            print(\"doing f with x =\", x)\n        ....\n\n# Release Log\n\n\n\n*Release 20240422*:\n* BaseCommandOptions.popopts: return the dict from BaseCommand.popopts().\n* BaseCommand.apply_preargv: apply the default options supported by self.options.\n* BaseCommandOptions.update(mapping) method, useful for dropping subcommand-specific defaults onto the options ahead of the local popopts() call.\n\n*Release 20240412*:\n* BaseCommand.run_context: do not store .upd and .runstate on the options (it confuses options in subcommands and we have @uses_runstate and @uses_upd forthis anyway these days).\n* BaseCommand.run_context: catch SIGQUIT, present the default handler as BaseCommand.handle_signal.\n\n*Release 20240316*:\n* New @uses_cmd_options decorator to provide an \"options\" parameter being the prevailing BaseCommandOptions instance.\n* BaseCommandOptions.popopts: get common options from BaseCommandOptions.COMMON_OPT_SPECS.\n\n*Release 20240211*:\n* Include the first sentence of the subcommand description in the short help.\n* BaseCommandOptions: move the runstate_signals into this directly.\n* BaseCommand: move the run() options stuff into run_context() and have it work on a copy of the original options.\n* BaseCommandCmd: implement get_names(), provide docstrings for the do_* attributes, thus help.\n* BaseCommand.run_context: make runstate and upd keyword only parameters.\n\n*Release 20240201*:\n* BaseCommand.run: catch CancellationError and return 1.\n* BaseCommandCmd.__getattr__: recognise EOF, exit and quit to terminate the cmdloop.\n\n*Release 20231129*:\nBaseCommandOptions: define a runstate field.\n\n*Release 20230703*:\nSmall internal changes.\n\n*Release 20230612*:\n* BaseCommand.cmdloop: fix intro parameter.\n* Other small fixes.\n\n*Release 20230407*:\n* BaseCommand: use @uses_runstate when preparing the command, store as self.options.runstate.\n* Make BaseCommandOptions a data class.\n* Drop any pretence at python 2 support, we're long past that.\n* BaseCommand: new cmdloop method to run a cmd.Cmd instance to run subcommand interactively.\n* BaseCommand: rename shell to repl, add cmd_shell to call cmdloop().\n* Drop BaseCommand.apply_defaults in favour of the Options dataclass.\n* BaseCommand: do setup_logging before initiating the Options instance.\n\n*Release 20230212*:\n* BaseCommand.run_context: update RunState support.\n* BaseCommand.run_context: always be having an self.options.upd.\n\n*Release 20230211*:\nBaseCommand: new shell() method to present an interactive Python prompt for use by subclasses cmd_shell method if desired.\n\n*Release 20221228*:\nMove a lot of the context logic from BaseCommand.run to BaseCommand.run_context, which now must be correctly overridden in subclasses.\n\n*Release 20220918*:\n* BaseCommand.run_context: expand default signals to include SIGHUP, expose as BaseCommand.DEFAULT_SIGNALS.\n* BaseCommand.run: pass in the subclass handle_signal method if present.\n\n*Release 20220626*:\n* BaseCommand.poparg: fix positional argument handling.\n* BaseCommand.poparg: new unpop_on_error=False parameter to support pushing a bad argument back onto the front of the argument list.\n\n*Release 20220606*:\nBaseCommand.run: remove the Upd bodge, too annoying, now fixed in cs.upd I believe.\n\n*Release 20220605*:\n* BaseCommand: new popopts(argv,...) compact getopt wrapper.\n* BaseCommand: new poparg(argv,...) compact validating argument consumer.\n* BaseCommand: drop run_argv, provided no utility.\n* BaseCommand.run: get the RunState signal list from self.options.runstate_signals.\n* BaseCommand.apply_opts: support multiple individual options raising GetoptError, as I hate commands which abort at the first bad option.\n* Assorted other small things.\n\n*Release 20220429*:\n* BaseCommand: fold dots in argv[0] into underscores, supports subcommands like \"setup.py\".\n* BaseCommand: new popargv(argv[,help_text[,parse[,validate[,unvalidated_message]]]]) helper class method.\n* BaseCommand: accept dashed-form of the underscored_form subcommand name.\n* BaseCommand: new self.options.runstate_signals=SIGINT,SIGTERM specifying singals to catch-and-cancel, shuffle run() context managers.\n\n*Release 20220318*:\nBaseCommand.__init__: handle main() method in the New Scheme.\n\n*Release 20220315*:\n_BaseSubCommand.__init__: hook in the class USAGE_KEYWORDS for methods.\n\n*Release 20220311*:\nBaseCommand: big refactor of subcommand internals and make the \"cmd_foo=FooCommand\" implementation work properly.\n\n*Release 20211208*:\nBaseCommand: better handle an unknown subcommand.\n\n*Release 20210927*:\n* Usage: show only the per subcommand usage for in-subcommand GetoptError.\n* Usage: show terse usage when the subcommand cannot be recognised.\n* Usage: support bare -h, -help, --help.\n\n*Release 20210913*:\nNew BaseCommand.apply_preargv method to gather special arguments before subcommands.\n\n*Release 20210906*:\n* BaseCommand.cmd_help: bugfix obsolete parameter list.\n* BaseCommand.SUBCOMMAND_ARGV_DEFAULT: support a single str value, turn into list.\n\n*Release 20210809*:\nBugfix BaseCommand.cmd_help for modern API.\n\n*Release 20210731*:\n* BaseCommand.run: apply optional keyword arguments to self.options during the run.\n* Look for self.SUBCOMMAND_ARGV_DEFAULT if no subcommand is supplied.\n* Bugfix case for \"main\" method and no \"cmd_*\" methods.\n* Bugfix BaseCommand.cmd_help.\n\n*Release 20210420*:\n* BaseCommand.getopt_error_handler: replace error print() with warning().\n* Docstring improvements.\n\n*Release 20210407.1*:\nBaseCommand: bugfix for __init_subclass__ docstring update.\n\n*Release 20210407*:\n* BaseCommand.__init_subclass__: behave sanely if the subclass has no initial __doc__.\n* BaseCommand: new .run_argv convenience method, obviates the \"def main\" boilerplate.\n\n*Release 20210404*:\nBaseCommand subclasses: automatically add the main usage message to the subclass docstring.\n\n*Release 20210306*:\n* 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.\n* BaseCommand: provide default `apply_opt` and `apply_opts` methods; subclasses will generally just override the former.\n\n*Release 20210123*:\nBaseCommand: propagate the format mapping (cmd, USAGE_KEYWORDS) to the subusage generation.\n\n*Release 20201102*:\n* BaseCommand.cmd_help: supply usage only for \"all commands\", full docstring for specified commands.\n* BaseCommand: honour presupplied options.log_level.\n* BaseCommand.usage_text: handle missing USAGE_FORMAT better.\n* BaseCommand.run: provide options.upd.\n* BaseCommand subclasses may now override BaseCommand.OPTIONS_CLASS (default SimpleNamespace) in order to provide convenience methods on the options.\n* BaseCommand.run: separate variable for subcmd with dash translated to underscore to match method names.\n* Minor fixes.\n\n*Release 20200615*:\nBaseCommand.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).\n\n*Release 20200521.1*:\nFix DISTINFO.install_requires.\n\n*Release 20200521*:\n* BaseCommand.run: support using BaseCommand subclasses as cmd_* names to make it easy to nest BaseCommands.\n* BaseCommand: new hack_postopts_argv method called after parsing the main command line options, for inferring subcommands or the like.\n* BaseCommand: extract \"Usage:\" paragraphs from subcommand method docstrings to build the main usage message.\n* BaseCommand: new cmd_help default command.\n* Assorted bugfixes and small improvements.\n\n*Release 20200318*:\n* BaseCommand.run: make argv optional, get additional usage keywords from self.USAGE_KEYWORDS.\n* @BaseCommand.add_usage_to_docstring: honour cls.USAGE_KEYWORDS.\n* BaseCommand: do not require GETOPT_SPEC for commands with no defined options.\n* BaseCommand.run: call cs.logutils.setup_logging.\n\n*Release 20200229*:\nImprove subcommand selection logic, replace StackableValues with stackattrs, drop `cmd` from arguments passed to main/cmd_* methods (present in `options`).\n\n*Release 20200210*:\n* New BaseCommand.add_usage_to_docstring class method to be called after class setup, to append the usage message to the class docstring.\n* BaseCommand.run: remove spurious Pfx(cmd), as logutils does this for us already.\n\n*Release 20190729*:\nBaseCommand: support for a USAGE_FORMAT usage message format string and a getopt_error_handler method.\n\n*Release 20190619.1*:\nAnother niggling docstring formatting fix.\n\n*Release 20190619*:\nMinor documentation updates.\n\n*Release 20190617.2*:\nLint.\n\n*Release 20190617.1*:\nInitial release with @docmd decorator and alpha quality BaseCommand command line assistance class.\n\n",
    "bugtrack_url": null,
    "license": "GNU General Public License v3 or later (GPLv3+)",
    "summary": "Convenience functions for working with the Cmd module, the BaseCommand class for constructing command line programmes, and other command line related stuff.",
    "version": "20240422",
    "project_urls": {
        "URL": "https://bitbucket.org/cameron_simpson/css/commits/all"
    },
    "split_keywords": [
        "python2",
        " python3"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "801f1aa595dc779a299465e15cd1e3ce328589b3a458319c4ba47495bf7c636f",
                "md5": "a3cf6b6544b765d0ff24112f8ca88e2e",
                "sha256": "9b29a408041c1a6c907b2ee6b54d42a7bb0e82810fb4412d7bc24c3bf928c0d4"
            },
            "downloads": -1,
            "filename": "cs.cmdutils-20240422-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "a3cf6b6544b765d0ff24112f8ca88e2e",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 25778,
            "upload_time": "2024-04-22T02:56:54",
            "upload_time_iso_8601": "2024-04-22T02:56:54.629805Z",
            "url": "https://files.pythonhosted.org/packages/80/1f/1aa595dc779a299465e15cd1e3ce328589b3a458319c4ba47495bf7c636f/cs.cmdutils-20240422-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "07c1404cb89a74e50f75723271d78fb5e0c8ecc8657e9e6a447b5a53954cbb98",
                "md5": "ffc96c12848df1554a2823780840bd65",
                "sha256": "b979004301322f0e64798ae77d8bef6278322d42c102d5cad10ffcc628eacd88"
            },
            "downloads": -1,
            "filename": "cs.cmdutils-20240422.tar.gz",
            "has_sig": false,
            "md5_digest": "ffc96c12848df1554a2823780840bd65",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 49858,
            "upload_time": "2024-04-22T02:56:57",
            "upload_time_iso_8601": "2024-04-22T02:56:57.400316Z",
            "url": "https://files.pythonhosted.org/packages/07/c1/404cb89a74e50f75723271d78fb5e0c8ecc8657e9e6a447b5a53954cbb98/cs.cmdutils-20240422.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-04-22 02:56:57",
    "github": false,
    "gitlab": false,
    "bitbucket": true,
    "codeberg": false,
    "bitbucket_user": "cameron_simpson",
    "bitbucket_project": "css",
    "lcname": "cs.cmdutils"
}
        
Elapsed time: 0.29087s