3
0
Fork 0

Add a large chunk of the docs for command line interface

will have to finish subcommands later
This commit is contained in:
Lance Edgar 2023-11-22 21:40:26 -06:00
parent 8a4438c725
commit af4c28b286
15 changed files with 371 additions and 19 deletions

111
docs/narr/cli/commands.rst Normal file
View file

@ -0,0 +1,111 @@
Commands
========
Top-level :term:`commands<command>` are primarily a way to group
:term:`subcommands<subcommand>`.
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.)
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.:
.. code-block:: sh
wutta --help
To actually *do* anything you must also specify a subcommand, e.g.:
.. code-block:: sh
wutta make-appdir
Many subcommands may accept arguments of their own:
.. code-block:: sh
wutta make-appdir --path=/where/i/want/my/appdir
But top-level commands also accept global arguments. See the next
section for the full list of "global" command options. A complete example
then might be like:
.. code-block:: sh
wutta --config=/path/to/my/file.conf make-appdir --path=/where/i/want/my/appdir
Note that the top-level command will parse its global option args
first, and give only what's leftover to the subcommand. Therefore it
isn't strictly necessary to specify global options before the
subcommand:
.. code-block:: sh
wutta make-appdir --path=/where/i/want/my/appdir --config=/path/to/my/file.conf
``wutta`` command
-----------------
WuttJamaican comes with one top-level command named ``wutta``. Note
that the list of available subcommands is shown in the top-level
command help.
See :mod:`wuttjamaican.cmd` for more on the built-in ``wutta``
subcommands.
.. command-output:: wutta -h
:returncode: 1
Adding a New Command
--------------------
There is not much to this since top-level commands are mostly just a
grouping mechanism.
First create your :class:`~wuttjamaican.cmd.base.Command` class, and a
``main()`` function for it (e.g. in ``poser/commands.py``)::
import sys
from wuttjamaican.cmd import Command
class PoserCommand(Command):
name = 'poser'
description = 'my custom top-level command'
version = '0.1'
def poser_main(*args):
args = list(args) or sys.argv[1:]
cmd = PoserCommand()
cmd.run(*args)
Then register the :term:`entry point(s)<entry point>` in your
``setup.cfg``. The command name should not contain 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:
.. code-block:: sh
cd /path/to/venv
bin/poser --help
bin/wutta-poser --help

11
docs/narr/cli/index.rst Normal file
View file

@ -0,0 +1,11 @@
Command Line Interface
======================
.. toctree::
:maxdepth: 2
overview
commands
subcommands
scripts

View file

@ -0,0 +1,21 @@
Overview
========
The command line interface is an important part of app automation and
may be thought of in a couple ways:
First there is the :term:`ad hoc script` which is a single file and
can be placed anywhere, but is not installed as part of a package.
See :doc:`scripts`.
But the "real" command line interface uses :term:`commands<command>`
and :term:`subcommands<subcommand>`; these are installed as part of a
package.
Top-level commands are mostly just a way to group subcommands. Most
custom apps would define their own top-level command as well as
multiple subcommands. See :doc:`commands` for top-level details.
Subcommands on the other hand are the real workhorse since they define
the action logic. See :doc:`subcommands` for more about those.

156
docs/narr/cli/scripts.rst Normal file
View file

@ -0,0 +1,156 @@
Ad Hoc Scripts
==============
It can be useful to write :term:`ad hoc scripts<ad hoc script>` for
certain things, as opposed to a proper :term:`subcommand`. This is
especially true when first getting acquainted with the framework.
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.
Below we'll walk through creating a script.
Hello World
-----------
First to establish a baseline, here is a starting point script which
we'll name ``hello.py``::
print('hello world')
Run that like so:
.. code-block:: sh
$ python hello.py
hello world
Better Standards
----------------
Keeping it simple, but improving that script per recommended patterns::
def hello():
print('hello world')
if __name__ == '__main__':
hello()
Runs the same:
.. code-block:: sh
$ python hello.py
hello world
Configurability
---------------
If you have a :term:`config file` e.g. named ``my.conf``:
.. code-block:: ini
[hello]
name = George
Then you can make a :term:`config object` to access its values. Note
that this also gives you access to the :term:`app handler`::
from wuttjamaican.conf import make_config
def hello(config):
app = config.get_app()
print('hello', config.get('hello.name'))
print('from', app.appname)
if __name__ == '__main__':
config = make_config('my.conf')
hello(config)
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
before all packages are imported::
from wuttjamaican.conf import make_config
def hello(config):
# do extra imports here
from otherpkg import something
app = config.get_app()
print('hello', config.get('hello.name'))
print('from', app.appname)
something(config)
if __name__ == '__main__':
config = make_config('my.conf')
hello(config)
Output should now be different:
.. code-block:: sh
$ python hello.py
hello George
from wutta
Logging
-------
Logging behavior is determined by the config file(s). If they contain
no directives pertaining to the logging config then some default
behavior will be used.
In any case your script should not need to worry about that, but is
free to make logging calls. The configured logging behavior would
determine whether such messages are output to the console and/or file
etc.
There are 3 steps to logging:
* import the :mod:`python:logging` module
* call :func:`~python:logging.getLogger()` to get a logger
* call methods on the logger, e.g. :meth:`~python:logging.Logger.debug()`
Here is the script with logging incorporated::
# nb. it is always safe to import from standard library at the
# top of module, that will not interfere with make_config()
import logging
from wuttjamaican.conf import make_config
log = logging.getLogger(__name__)
log.debug("still at top of module")
def hello(config):
# do extra imports here
from otherpkg import something
log.debug("saying hello")
app = config.get_app()
print('hello', config.get('hello.name'))
print('from', app.appname)
log.debug("about to do something")
if something(config):
log.info("something seems to have worked")
else:
log.warn("oh no! something failed")
if __name__ == '__main__':
log.debug("entered the __main__ block")
config = make_config('my.conf')
log.debug("made config object: %s", config)
hello(config)
log.debug("all done")

View file

@ -0,0 +1,5 @@
Subcommands
===========
TODO