diff --git a/docs/narr/cli/commands.rst b/docs/narr/cli/commands.rst index b67adbc..efde347 100644 --- a/docs/narr/cli/commands.rst +++ b/docs/narr/cli/commands.rst @@ -6,12 +6,19 @@ Top-level :term:`commands` are primarily a way to group :term:`subcommands`. +.. _running-commands: + Running a Command ----------------- Top-level commands are installed in such a way that they are available within the ``bin`` folder of the virtual environment. (Or the -``Scripts`` folder if on Windows.) +``Scripts`` folder if on Windows.) For instance: + +.. code-block:: sh + + cd /path/to/venv + bin/wutta --help This folder should be in the ``PATH`` when the virtual environment is activated, in which case you can just run the command by name, e.g.: @@ -64,6 +71,8 @@ subcommands. :returncode: 1 +.. _adding-commands: + Adding a New Command -------------------- @@ -87,8 +96,8 @@ First create your :class:`~wuttjamaican.cmd.base.Command` class, and a cmd.run(*args) Then register the :term:`entry point(s)` in your -``setup.cfg``. The command name should not contain spaces but may -include hyphens or underscore etc. +``setup.cfg``. The command name should *not* contain spaces but *may* +include hyphen or underscore. You can register more than one top-level command if needed; these could refer to the same ``main()`` function (in which case they @@ -109,3 +118,6 @@ available: cd /path/to/venv bin/poser --help bin/wutta-poser --help + +You will then likely want to add subcommand(s) for this to be useful; +see :ref:`adding-subcommands`. diff --git a/docs/narr/cli/scripts.rst b/docs/narr/cli/scripts.rst index fbbae50..3dfe877 100644 --- a/docs/narr/cli/scripts.rst +++ b/docs/narr/cli/scripts.rst @@ -10,6 +10,9 @@ A script is just a text file with Python code. To run it you generally must invoke the Python interpreter somehow and explicitly tell it the path to your script. +Note that a script is (usually) not installed as part of a package. +They can live anywhere. + Below we'll walk through creating a script. @@ -72,6 +75,14 @@ that this also gives you access to the :term:`app handler`:: config = make_config('my.conf') hello(config) +Output should now be different: + +.. code-block:: sh + + $ python hello.py + hello George + from wutta + You are likely to need more imports; it is generally wise to do those *within the function* as opposed to the top of the module. This is to ensure the :func:`~wuttjamaican.conf.make_config()` call happens @@ -94,14 +105,6 @@ before all packages are imported:: config = make_config('my.conf') hello(config) -Output should now be different: - -.. code-block:: sh - - $ python hello.py - hello George - from wutta - Logging ------- diff --git a/docs/narr/cli/subcommands.rst b/docs/narr/cli/subcommands.rst index 3586b2d..aa9fc44 100644 --- a/docs/narr/cli/subcommands.rst +++ b/docs/narr/cli/subcommands.rst @@ -2,4 +2,91 @@ Subcommands =========== -TODO +A top-level :term:`command` may have multiple +:term:`subcommands`. + +The top-level command is responsible for invoking the subcommand, but +the subcommand is responsible for performing some action(s). + +There is no restriction on what sort of action that might be, but for +sake of clarity it is best to make a distinct subcommand for each +"type" of action needed by the app. + + +Running a Subcommand +-------------------- + +You cannot run a subcommand directly; you must run a top-level command +and specify the subcommand as part of the command line arguments. See +:ref:`running-commands`. + +This restriction holds true even when running a subcommand "natively" +from within Python code. For more info see +:meth:`wuttjamaican.cmd.base.Subcommand.run()`. + + +Built-in Subcommands +-------------------- + +WuttJamaican comes with one top-level command named ``wutta`` as well +as a few subcommands under that. + +See :mod:`wuttjamaican.cmd` for more on the built-in ``wutta`` +subcommands. + + +.. _adding-subcommands: + +Adding a New Subcommand +----------------------- + +There are two steps for this: + +* define the subcommand +* register it under top-level command(s) + +First create a Subcommand class (e.g. by adding to +``poser/commands.py``):: + + from wuttjamaican.cmd import Subcommand + + class Hello(Subcommand): + """ + Say hello to the user + """ + name = 'hello' + description = __doc__.strip() + + def add_args(self): + self.parser.add_argument('--foo', default='bar', help="Foo value") + + def run(self, args): + print("hello, foo value is:", args.foo) + +You may notice there is nothing in that subcommand definition which +ties it to the ``poser`` top-level command. That is done by way of +another :term:`entry point` in your ``setup.cfg`` file. + +As with top-level commands, you can "alias" the same subcommand so +it appears under multiple top-level commands. Note that if the +top-level command name contains a hyphen, that must be replaced +with underscore for sake of the subcommand entry point: + +.. code-block:: ini + + [options.entry_points] + + poser.subcommands = + hello = poser.commands:Hello + + wutta_poser.subcommands = + hello = poser.commands:Hello + +Next time your ``poser`` package is installed, the subcommand will be +available, so you can e.g.: + +.. code-block:: sh + + cd /path/to/venv + bin/poser hello --help + bin/wutta-poser hello --help diff --git a/src/wuttjamaican/cmd/base.py b/src/wuttjamaican/cmd/base.py index 8fe9a1b..deeb763 100644 --- a/src/wuttjamaican/cmd/base.py +++ b/src/wuttjamaican/cmd/base.py @@ -72,51 +72,11 @@ class Command: :param subcommands: Optional dictionary to use for :attr:`subcommands`, instead of loading those via entry points. - The base class serves as the primary ``wutta`` command for + This base class also serves as the primary ``wutta`` command for WuttJamaican. Most apps will subclass this and register their own - top-level command, and create subcommands as needed. + top-level command, then create subcommands as needed. - To do that, first create your Command class, and a ``main()`` - entry point function for it (e.g. in ``poser/commands.py``):: - - import sys - from wuttjamaican.commands import Command - - class Poser(Command): - name = 'poser' - description = 'my custom top-level command' - version = '0.1' - - def poser_main(*args): - args = list(args) or sys.argv[1:] - cmd = Poser() - cmd.run(*args) - - Then register the ``main()`` entry point(s) in your ``setup.cfg``. - The command name should be "one word" (no spaces) but may include - hyphens or underscore etc. - - You can register more than one top-level command if needed; these - could refer to the same ``main()`` function (in which case they - are really aliases) or can use different functions: - - .. code-block:: ini - - [options.entry_points] - console_scripts = - poser = poser.commands:poser_main - wutta-poser = poser.commands:wutta_poser_main - - Next time your (``poser``) package is installed, the command will be - available, so you can e.g.: - - .. code-block:: sh - - cd /path/to/venv - bin/poser --help - bin/wutta-poser --help - - And see :class:`Subcommand` for info about adding those. + For more info see :doc:`/narr/cli/commands`. .. attribute:: name @@ -211,7 +171,7 @@ class Command: You could do this in Python:: - from wuttjamaican.commands import Command + from wuttjamaican.cmd import Command cmd = Command() assert cmd.name == 'wutta' @@ -341,13 +301,14 @@ class CommandArgumentParser(argparse.ArgumentParser): """ Custom argument parser for use with :class:`Command`. - This overrides some of the parsing logic which is specific to the + This is based on standard :class:`python:argparse.ArgumentParser` + but overrides some of the parsing logic which is specific to the primary command object, to separate command options from subcommand options. This is documented as FYI but you probably should not need to know about or try to use this yourself. It will be used automatically - by ``Command`` or a subclass thereof. + by :class:`Command` or a subclass thereof. """ def parse_args(self, args=None, namespace=None): @@ -365,63 +326,17 @@ class Subcommand: also define :meth:`add_args()` to expose options. Subcommands always belong to a top-level command - the association - is made by way of entry point registration, and the constructor - for this class. + is made by way of :term:`entry point` registration, and the + constructor for this class. :param command: Reference to top-level :class:`Command` object. Note that unlike :class:`Command`, the base ``Subcommand`` does not correspond to any real subcommand for WuttJamaican. (It's *only* a base class.) For a real example see - :class:`~wuttjamaican.commands.setup.Setup`. + :class:`~wuttjamaican.cmd.make_appdir.MakeAppDir`. - In your project you can define new subcommands for any top-level - command. For instance to add a ``hello`` subcommand to the - ``poser`` command example (cf. :class:`Command` docs): - - First create a Subcommand class (e.g. by adding to - ``poser/commands.py``):: - - from wuttjamaican.commands import Subcommand - - class Hello(Subcommand): - \""" - Say hello to the user - \""" - name = 'hello' - description = __doc__.strip() - - def add_args(self): - self.parser.add_argument('--foo', default='bar', help="Foo value") - - def run(self, args): - print("hello, foo value is:", args.foo) - - You may notice there is nothing in that subcommand definition - which ties it to the ``poser`` top-level command. That is done by - way of another entry point in your ``setup.cfg`` file. - - As with top-level commands, you can "alias" the same subcommand so - it appears under multiple top-level commands. Note that if the - top-level command name contains a hyphen, that must be replaced - with underscore for sake of the subcommand entry point: - - .. code-block:: ini - - [options.entry_points] - poser.subcommands = - hello = poser.commands:Hello - wutta_poser.subcommands = - hello = poser.commands:Hello - - Next time your (``poser``) package is installed, the subcommand - will be available, so you can e.g.: - - .. code-block:: sh - - cd /path/to/venv - bin/poser hello --help - bin/wutta-poser hello --help + For more info see :doc:`/narr/cli/subcommands`. .. attribute:: stdout @@ -509,9 +424,9 @@ class Subcommand: For a command line like ``bin/poser hello --foo=baz`` then, you might do this:: - from poser.commands import Poser + from poser.commands import PoserCommand - cmd = Poser() + cmd = PoserCommand() assert cmd.name == 'poser' cmd.run('hello', '--foo=baz') """