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
|
@ -4,3 +4,20 @@
|
|||
|
||||
.. automodule:: wuttjamaican.cmd
|
||||
:members:
|
||||
|
||||
The core framework is contained in :mod:`wuttjamaican.cmd.base`.
|
||||
|
||||
Note that :class:`~wuttjamaican.cmd.base.Command` serves as the base
|
||||
class for top-level :term:`commands<command>` but it also functions as
|
||||
the top-level ``wutta`` command.
|
||||
|
||||
Some :term:`subcommands<subcommand>` are available as well; these are
|
||||
registered under the ``wutta`` command.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
cmd.base
|
||||
cmd.date_organize
|
||||
cmd.make_appdir
|
||||
cmd.setup
|
||||
|
|
|
@ -9,10 +9,6 @@
|
|||
|
||||
app
|
||||
cmd
|
||||
cmd.base
|
||||
cmd.date_organize
|
||||
cmd.make_appdir
|
||||
cmd.setup
|
||||
conf
|
||||
db
|
||||
db.conf
|
||||
|
|
|
@ -6,9 +6,13 @@ Glossary
|
|||
.. glossary::
|
||||
:sorted:
|
||||
|
||||
ad hoc script
|
||||
Python script (text) file used for ad-hoc automation etc. See
|
||||
also :doc:`narr/cli/scripts`.
|
||||
|
||||
app
|
||||
Depending on context, may refer to the software application
|
||||
overall, or the :term:`app handler`.
|
||||
overall, or the :term:`app name`, or the :term:`app handler`.
|
||||
|
||||
app database
|
||||
The main database used by the :term:`app`. There is normally
|
||||
|
@ -26,15 +30,19 @@ Glossary
|
|||
:class:`~wuttjamaican.app.AppHandler`.
|
||||
|
||||
app name
|
||||
The code-friendly name for the :term:`app`
|
||||
(e.g. ``wutta_poser``). This is available on the :term:`config
|
||||
object` within Python as
|
||||
:attr:`~wuttjamaican.conf.WuttaConfig.appname`.
|
||||
Code-friendly name for the underlying app/config system
|
||||
(e.g. ``wutta_poser``).
|
||||
|
||||
This must usually be specified as part of the call to
|
||||
:func:`~wuttjamaican.conf.make_config()` and is then available on
|
||||
the :term:`config object`
|
||||
:attr:`~wuttjamaican.conf.WuttaConfig.appname` and the :term:`app
|
||||
handler` :attr:`~wuttjamaican.app.AppHandler.appname`.
|
||||
|
||||
See also the human-friendly :term:`app title`.
|
||||
|
||||
app title
|
||||
The human-friendly name for the :term:`app` (e.g. "Wutta Poser").
|
||||
Human-friendly name for the :term:`app` (e.g. "Wutta Poser").
|
||||
|
||||
See also the code-friendly :term:`app name`.
|
||||
|
||||
|
@ -42,7 +50,7 @@ Glossary
|
|||
A top-level command line interface for the app. Note that
|
||||
top-level commands don't really "do" anything per se, and are
|
||||
mostly a way to group :term:`subcommands<subcommand>`. See also
|
||||
:class:`~wuttjamaican.commands.base.Command`.
|
||||
:class:`~wuttjamaican.cmd.base.Command`.
|
||||
|
||||
config
|
||||
Depending on context, may refer to any of: :term:`config file`,
|
||||
|
@ -66,6 +74,17 @@ Glossary
|
|||
values obtained from the :term:`settings table` as opposed to
|
||||
:term:`config file`. See also :doc:`narr/config/settings`.
|
||||
|
||||
entry point
|
||||
This refers to a "setuptools-style" entry point specifically,
|
||||
which is a mechanism used to register "plugins" and the like.
|
||||
This lets the app / config discover features dynamically. Most
|
||||
notably used to register :term:`commands<command>` and
|
||||
:term:`subcommands<subcommand>`.
|
||||
|
||||
For more info see the `Python Packaging User Guide`_.
|
||||
|
||||
.. _Python Packaging User Guide: https://packaging.python.org/en/latest/specifications/entry-points/
|
||||
|
||||
settings table
|
||||
Table in the :term:`app database` which is used to store
|
||||
:term:`config settings<config setting>`.
|
||||
|
@ -74,4 +93,4 @@ Glossary
|
|||
A top-level :term:`command` may expose one or more subcommands,
|
||||
for the overall command line interface. Subcommands are the real
|
||||
workhorse; each can perform a different function. See also
|
||||
:class:`~wuttjamaican.commands.base.Subcommand`.
|
||||
:class:`~wuttjamaican.cmd.base.Subcommand`.
|
||||
|
|
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
|
|
@ -7,3 +7,4 @@ Documentation
|
|||
|
||||
install/index
|
||||
config/index
|
||||
cli/index
|
||||
|
|
|
@ -49,6 +49,20 @@ class AppHandler:
|
|||
self.config = config
|
||||
self.handlers = {}
|
||||
|
||||
@property
|
||||
def appname(self):
|
||||
"""
|
||||
The :term:`app name` for the current app. This is just an
|
||||
alias for :attr:`wuttjamaican.conf.WuttaConfig.appname`.
|
||||
|
||||
Note that this ``appname`` does not necessariy reflect what
|
||||
you think of as the name of your (e.g. custom) app. It is
|
||||
more fundamental than that; your Python package naming and the
|
||||
:term:`app title` are free to use a different name as their
|
||||
basis.
|
||||
"""
|
||||
return self.config.appname
|
||||
|
||||
def make_appdir(self, path, subfolders=None, **kwargs):
|
||||
"""
|
||||
Establish an :term:`app dir` at the given path.
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
"""
|
||||
WuttJamaican - command line interface
|
||||
|
||||
For convenience, from this ``wuttjamaican.cmd`` namespace you can
|
||||
For convenience, from the ``wuttjamaican.cmd`` namespace you can
|
||||
access the following:
|
||||
|
||||
* :class:`~wuttjamaican.cmd.base.Command`
|
||||
|
|
|
@ -315,9 +315,9 @@ also try: {self.name} <subcommand> -h
|
|||
version=f"%(prog)s {self.version}")
|
||||
|
||||
parser.add_argument('--stdout', metavar='PATH', type=argparse.FileType('w'),
|
||||
help="Optional path to which STDOUT should be written.")
|
||||
help="Optional path to which STDOUT should be written")
|
||||
parser.add_argument('--stderr', metavar='PATH', type=argparse.FileType('w'),
|
||||
help="Optional path to which STDERR should be written.")
|
||||
help="Optional path to which STDERR should be written")
|
||||
|
||||
def make_config(self, args):
|
||||
"""
|
||||
|
|
|
@ -33,7 +33,7 @@ from .base import Subcommand
|
|||
|
||||
class DateOrganize(Subcommand):
|
||||
"""
|
||||
Organize files in a given directory, according to date
|
||||
Organize files into subfolders according to date
|
||||
"""
|
||||
name = 'date-organize'
|
||||
description = __doc__.strip()
|
||||
|
|
|
@ -820,9 +820,9 @@ def make_config(
|
|||
extension_entry_points=None,
|
||||
**kwargs):
|
||||
"""
|
||||
Make a new config object (presumably for global use), initialized
|
||||
per the given parameters and (usually) further modified by all
|
||||
registered config extensions.
|
||||
Make a new config (usually :class:`WuttaConfig`) object,
|
||||
initialized per the given parameters and (usually) further
|
||||
modified by all registered config extensions.
|
||||
|
||||
This function really does 3 things:
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ class TestAppHandler(TestCase):
|
|||
def test_init(self):
|
||||
self.assertIs(self.app.config, self.config)
|
||||
self.assertEqual(self.app.handlers, {})
|
||||
self.assertEqual(self.app.appname, 'wuttatest')
|
||||
|
||||
def test_make_appdir(self):
|
||||
|
||||
|
|
Loading…
Reference in a new issue