~brenns10/subc

a8e965c3c2f24d31f0841f4d499129a1b7950f29 — Stephen Brennan 1 year, 8 months ago 6c68d0f
Add docstring updates and README.rst update
2 files changed, 78 insertions(+), 22 deletions(-)

M README.rst
M subc.py
M README.rst => README.rst +32 -3
@@ 36,11 36,14 @@ and running your application:
.. code:: python

    if __name__ == '__main__':
        MyCmd.main()
        MyCmd.main('description of app')

Advanced Use
------------

Intermediate Base Classes
^^^^^^^^^^^^^^^^^^^^^^^^^

You may find yourself wanting to create intermediate subclasses for your
application, in order to share common functionality. For example, you might
create a class for all commands which handle a single file as an argument:


@@ 65,8 68,34 @@ example, given the following class hierarchy:
The non-leaf commands (marked with an asterisk) will not be included as
executable commands. Only leaf classes will be executable.

``subc`` is a very simple library. If you have other advanced uses, read the
code.
Default Command
^^^^^^^^^^^^^^^

When the user does not provide any argument on the command-line, the default
action is to raise an Exception which states "you must select a sub-command".
You can provide a default command to run instead, via the ``default`` argument
to ``main()`` (or ``add_subcommands()``). For example:

.. code:: python

    if __name__ == '__main__':
        MyCmd.main('description', default='help')

The above code will run the ``help`` subcommand when no subcommand is specified.
Note that in this case, the default sub-command may not receive all of its
expected arguments.

Shortest Prefix Aliasing
^^^^^^^^^^^^^^^^^^^^^^^^

``subc`` has an optional feature which allows the user to specify a subcommand
by the shortest prefix which uniquely identifies the subcommand, or any longer
prefix thereof. As an example, imagine a git command with the following
sub-commands: clone, checkout, commit, cherry-pick. The shortest prefix aliasing
would allow you to run "git clone" by executing ``git cl``, since only "clone"
begins with "cl". You could also execute "git clone" with a longer prefix like
``git clo``. The feature can be enabled by setting ``shortest_prefix`` to true
in ``main()`` or ``add_subcommands()``.

License
-------

M subc.py => subc.py +46 -19
@@ 103,6 103,29 @@ class Command(ABC):
        default: t.Optional[str] = None,
        shortest_prefix: bool = False
    ) -> argparse.ArgumentParser:
        """
        Add all subcommands which are descendents of this class to parser.

        This call is required in order to setup an argument parser before
        parsing args and executing sub-command. Each sub-command must be a
        sub-class (or a further descendent) of this class. Only leaf subclasses
        are considered commands -- internal "nodes" in the hierarchy are skipped
        as they are assumed to be helpers.

        A default command to run may be set with 'default'. When the argument
        parser is called without a sub-command, this command will automatically
        execute (rather than simply raising an Exception).

        Shortest prefix sub-command matching allows the user to select a
        sub-command by using any string which is a prefix of exactly one
        command, e.g. "git cl" rather than "git clone".

        :param parser: Argument parser which is already created for this app
        :param default: Name of the command which should be executed if none is
          selected
        :param shortest_prefix: Enable shortest prefix command matching
        :returns: the modified parser (this can be ignored)
        """
        default_set = False
        subparsers = parser.add_subparsers(title='sub-command')
        subclasses = collections.deque(cls.__subclasses__())


@@ 139,20 162,6 @@ class Command(ABC):
        return parser

    @classmethod
    def parse_args(
            cls,
            description,
            default: t.Optional[str] = None,
            args: t.Optional[t.List[str]] = None,
            shortest_prefix: bool = False,
    ) -> argparse.Namespace:
        parser = argparse.ArgumentParser(description=description)
        cls.add_commands(
            parser, default=default, shortest_prefix=shortest_prefix,
        )
        return parser.parse_args(args)

    @classmethod
    def main(
            cls,
            description: str,


@@ 160,10 169,28 @@ class Command(ABC):
            args: t.Optional[t.List[str]] = None,
            shortest_prefix: bool = False,
    ) -> t.Any:
        ns = cls.parse_args(
            description,
            default=default,
            args=args,
            shortest_prefix=shortest_prefix
        """
        Parse arguments and run the selected sub-command.

        This helper function is expected to be the main, most useful API for
        subc, although you could directly call the add_commands() method.
        Creates an argument parser, adds every discovered sub-command, parses
        the arguments, and executes the selected sub-command, returning its
        return value.

        Custom arguments (rather than sys.argv) can be specified using "args".
        Details on the arguments "default" and "shortest_prefix" can be found
        in the docstring for add_commands().

        :param description: Description of the application (for help output)
        :param default: Default command name
        :param args: If specified, a list of args to use in place of sys.argv
        :param shortest_prefix: whether to enable prefix matching
        :returns: Return value of the selected command's run() method
        """
        parser = argparse.ArgumentParser(description=description)
        cls.add_commands(
            parser, default=default, shortest_prefix=shortest_prefix,
        )
        ns = parser.parse_args(args=args)
        return ns.func(ns)