Add a large chunk of the docs for command line interface
will have to finish subcommands later
This commit is contained in:
parent
8a4438c725
commit
af4c28b286
15 changed files with 371 additions and 19 deletions
111
docs/narr/cli/commands.rst
Normal file
111
docs/narr/cli/commands.rst
Normal 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
11
docs/narr/cli/index.rst
Normal file
|
@ -0,0 +1,11 @@
|
|||
|
||||
Command Line Interface
|
||||
======================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
overview
|
||||
commands
|
||||
subcommands
|
||||
scripts
|
21
docs/narr/cli/overview.rst
Normal file
21
docs/narr/cli/overview.rst
Normal 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
156
docs/narr/cli/scripts.rst
Normal 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")
|
5
docs/narr/cli/subcommands.rst
Normal file
5
docs/narr/cli/subcommands.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
|
||||
Subcommands
|
||||
===========
|
||||
|
||||
TODO
|
Loading…
Add table
Add a link
Reference in a new issue