summaryrefslogtreecommitdiff
path: root/plac/doc/plac.txt
diff options
context:
space:
mode:
Diffstat (limited to 'plac/doc/plac.txt')
-rw-r--r--plac/doc/plac.txt223
1 files changed, 149 insertions, 74 deletions
diff --git a/plac/doc/plac.txt b/plac/doc/plac.txt
index c0e1252..ccbe8f7 100644
--- a/plac/doc/plac.txt
+++ b/plac/doc/plac.txt
@@ -3,9 +3,10 @@ Plac: Parsing the Command Line the Easy Way
:Author: Michele Simionato
:E-mail: michele.simionato@gmail.com
-:Requires: Python 2.3+
+:Date: June 2010
:Download page: http://pypi.python.org/pypi/plac
:Project page: http://micheles.googlecode.com/hg/plac/doc/plac.html
+:Requires: Python 2.3+
:Installation: ``easy_install -U plac``
:License: BSD license
@@ -20,14 +21,14 @@ getopt_ (from the stone age),
optparse_ (from Python 2.3) and argparse_ (from Python 2.7). All of
them are quite powerful and especially argparse_ is an industrial
strength solution; unfortunately, all of them feature a non-zero learning
-curve and a certain verbosity. They do not scale down well enough, at
+curve and a certain verbosity. They do not scale down well, at
least in my opinion.
It should not be necessary to stress the importance `scaling down`_;
-nevertheless most people are obsessed with features and concerned with
-the possibility of scaling up, whereas I think that we should be even
-more concerned with the issue of scaling down. This is an old meme in
-the computing world: programs should address the common cases simply,
+nevertheless, a lot of people are obsessed with features and concerned with
+the possibility of scaling up, forgetting the equally important
+issue of scaling down. This is an old meme in
+the computing world: programs should address the common cases simply and
simple things should be kept simple, while at the same keeping
difficult things possible. plac_ adhere as much as possible to this
philosophy and it is designed to handle well the simple cases, while
@@ -49,7 +50,7 @@ throw-away scripts for themselves, choosing the command line
interface because it is the quick and simple. Such users are not
interested in features, they are interested in a small learning curve:
they just want to be able to write a simple command line tool from a
-simple specification, not to build a command line parser by
+simple specification, not to build a command-line parser by
hand. Unfortunately, the modules in the standard library forces them
to go the hard way. They are designed to implement power user tools
and they have a non-trivial learning curve. On the contrary, plac_
@@ -61,7 +62,7 @@ Scripts with required arguments
Let me start with the simplest possible thing: a script that takes a
single argument and does something to it. It cannot get simpler
-than that, unless you consider a script without command line
+than that, unless you consider a script without command-line
arguments, where there is nothing to parse. Still, it is a use
case *extremely common*: I need to write scripts like that nearly
every day, I wrote hundreds of them in the last few years and I have
@@ -72,9 +73,9 @@ writing by hand for years:
:literal:
As you see the whole ``if __name__ == '__main__'`` block (nine lines)
-is essentially boilerplate that should not exists. Actually I think
+is essentially boilerplate that should not exist. Actually I think
the language should recognize the main function and pass to it the
-command line arguments automatically; unfortunaly this is unlikely to
+command-line arguments automatically; unfortunaly this is unlikely to
happen. I have been writing boilerplate like this in hundreds of
scripts for years, and every time I *hate* it. The purpose of using a
scripting language is convenience and trivial things should be
@@ -108,12 +109,13 @@ underlying argparse_ module) a nice usage message::
.. include:: example3.help
:literal:
+Moreover plac_ manages the case of missing arguments and of too many arguments.
This is only the tip of the iceberg: plac_ is able to do much more than that.
Scripts with default arguments
--------------------------------------------------
-The need to have suitable defaults for command line arguments is quite
+The need to have suitable defaults for command-line scripts is quite
common. For instance I have encountered this use case at work hundreds
of times:
@@ -121,10 +123,10 @@ of times:
:literal:
Here I want to perform a query on a database table, by extracting the
-today's data: it makes sense for ``today`` to be a default argument.
+most recent data: it makes sense for ``today`` to be a default argument.
If there is a most used table (in this example a table called ``'product'``)
it also makes sense to make it a default argument. Performing the parsing
-of the command lines arguments by hand takes 8 ugly lines of boilerplate
+of the command-line arguments by hand takes 8 ugly lines of boilerplate
(using argparse_ would require about the same number of lines).
With plac_ the entire ``__main__`` block reduces to the usual two lines::
@@ -150,15 +152,15 @@ Here is the usage message:
:literal:
The examples here should have made clear that *plac is able to figure out
-the command line arguments parser to use from the signature of the main
+the command-line arguments parser to use from the signature of the main
function*. This is the whole idea behind plac_: if the intent is clear,
let's the machine take care of the details.
-plac_ is inspired to the optionparse_ recipe, in the sense that it
-delivers the programmer from the burden of writing the parser, but is
-less of a hack: instead of extracting the parser from the docstring of
-the module, it extracts it from the signature of the ``main``
-function.
+plac_ is inspired to an old Python Cookbook recipe (optionparse_), in
+the sense that it delivers the programmer from the burden of writing
+the parser, but is less of a hack: instead of extracting the parser
+from the docstring of the module, it extracts it from the signature of
+the ``main`` function.
The idea comes from the `function annotations` concept, a new
feature of Python 3. An example is worth a thousand words, so here
@@ -180,13 +182,13 @@ I will show in the next paragraphs.
Scripts with options (and smart options)
-----------------------------------------
-It is surprising how few command line scripts with options I have
+It is surprising how few command-line scripts with options I have
written over the years (probably less than a hundred), compared to the
number of scripts with positional arguments I wrote (certainly more
than a thousand of them). Still, this use case cannot be neglected.
The standard library modules (all of them) are quite verbose when it
comes to specifying the options and frankly I have never used them
-directly. Instead, I have always relied on an old recipe of mine, the
+directly. Instead, I have always relied on the
optionparse_ recipe, which provides a convenient wrapper over
optionparse_. Alternatively, in the simplest cases, I have just
performed the parsing by hand. In plac_ the parser is inferred by the
@@ -199,7 +201,7 @@ Here the argument ``command`` has been annotated with the tuple
``("SQL query", 'option', 'c')``: the first string is the help string
which will appear in the usage message, the second string tells plac_
that ``command`` is an option and the third string that there is also
-a short form of the option ``-c``, the long form being ``--command=``.
+a short form of the option ``-c``, the long form being ``--command``.
The usage message is the following:
.. include:: example8.help
@@ -306,7 +308,7 @@ think to migrate to Python 3. I am pretty much sure most Pythonistas
are in the same situation. Therefore plac_ provides a way to work
with function annotations even in Python 2.X (including Python 2.3).
There is no magic involved; you just need to add the annotations
-by hand. For instance the annotate function declaration
+by hand. For instance the annotated function declaration
::
@@ -334,7 +336,7 @@ people with Python 2.4 available the simplest way is to use the
In the rest of this article I will assume that you are using Python 2.X with
X >= 4 and I will use the ``plac.annotations`` decorator. Notice however
-that plac_ runs even on Python 2.3.
+that the core features of plac_ run even on Python 2.3.
More features
--------------------------------------------------
@@ -350,7 +352,8 @@ general an annotation is a 6-tuple of the form
where ``help`` is the help message, ``kind`` is a string in the set {
``"flag"``, ``"option"``, ``"positional"``}, ``abbrev`` is a
-one-character string, ``type`` is a callable taking a string in input,
+one-character string or ``None``, ``type`` is a callable taking a
+string in input,
``choices`` is a discrete sequence of values and ``metavar`` is a string.
``type`` is used to automagically convert the command line arguments
@@ -366,22 +369,12 @@ the name in the usage message is the same as the argument name,
unless the argument has a default and in such a case is
equal to the stringified form of the default.
-Here is an example showing many of the features (taken from the
+Here is an example showing many of the features (copied from the
argparse_ documentation):
.. include:: example10.py
:literal:
-
-Often the main function of a script works by side effects and returns
-``None``; in this example instead I choose to return the number and to
-print it in the ``__main__`` block.
-
-Notice that *plac.call returns a list of strings*: in particular, it
-returns a single-element list if the main function returns a single
-non-None element (as in this example) or an empty list if the main
-function returns ``None``.
-
Here is the usage:
.. include:: example10.help
@@ -398,17 +391,11 @@ to the usage message. Here are a couple of examples of use::
usage: example10.py [-h] {add,mul} [n [n ...]]
example10.py: error: argument operator: invalid choice: 'ad' (choose from 'add', 'mul')
-If the main function returns a generic number of elements,
-the elements returned by ``plac.call`` are stringified by invoking
-``str`` on each of them.
-The reason is to simplify testing: a plac-based
-command-line interface can be tested by simply comparing lists of
-strings in input and lists of strings in output.
-For instance a doctest may look like this:
+``plac.call`` can also be used in doctests like this:
->>> import example10
+>>> import plac, example10
>>> plac.call(example10.main, ['add', '1', '2'])
-['3.0']
+3.0
``plac.call`` works for generators too:
@@ -416,15 +403,29 @@ For instance a doctest may look like this:
... for i in range(int(n)):
... yield i
>>> plac.call(main, ['3'])
-['0', '1', '2']
+[0, 1, 2]
-However, you should notice that ``plac.call`` is *eager*, not lazy:
-the generator is exhausted by the call. This behavior avoids mistakes like
-forgetting of applying ``list(result)`` to the result of a ``plac.call``.
+Internally ``plac.call`` tries to convert the output of the main function
+into a list, if possible. If the output is not iterable or it is a
+string, it is left unchanged, but if it is iterable it is converted.
+In particular, generator objects are exhausted by ``plac.call``.
-This behavior makes testing easier and supports the *yield-is-print*
-pattern: just replace the occurrences of ``print`` with ``yield`` in
-the main function and you will get an easy to test interface.
+This behavior avoids mistakes like forgetting of applying
+``list(result)`` to the result of ``plac.call``; moreover it makes
+errors visible early, and avoids mistakes in code like the following::
+
+ try:
+ result = plac.call(main, args)
+ except:
+ # do something
+
+Without the ``listify`` functionality, a main function returning a
+generator object would not raise any exception until the generator
+is iterated over.
+
+Moreover you can rely on type checks like ``isinstance(result, list)``
+for the output of ``plac.call``, to check if the output is an iterable
+or not.
A realistic example
---------------------------------------
@@ -438,20 +439,19 @@ string into a SqlSoup_ object:
:literal:
You can see the *yield-is-print* pattern here: instead of using
-``print`` in the main function, we use ``yield``, and we perform the
+``print`` in the main function, I use ``yield``, and I perform the
print in the ``__main__`` block. The advantage of the pattern is that
-the test becomes trivial: had we performed the printing in the main
-function, tje test would have involved redirecting ``sys.stdout`` to a
-``StringIO`` object and we know that redirecting ``sys.stdout`` is
-always ugly, especially in multithreaded situations.
+tests invoking ``plac.call`` and checking the result become trivial:
+had I performed the printing in the main function, the test would have
+involved an ugly hack like redirecting ``sys.stdout`` to a
+``StringIO`` object.
Here is the usage message:
.. include:: dbcli.help
:literal:
-I leave as an exercise for the reader to write doctests for this
-example.
+You can check for yourself that the script works.
Keyword arguments
---------------------------------------
@@ -489,6 +489,75 @@ positional arguments, excepted varargs and keywords. This limitation
is a consequence of the way the argument names are managed in function calls
by the Python language.
+Final example: a shelve interface
+----------------------------------------------------------
+
+Here is a less trivial example for the keyword arguments feature.
+The use case is the following: suppose we have stored the
+configuration parameters of a given application into a Python shelve
+and we need a command-line tool to edit the shelve.
+A possible implementation using plac_ could be the following:
+
+.. include:: ishelve.py
+ :literal:
+
+A few notes are in order:
+
+1. I have disabled the ordinary help provided by argparse_ and I have
+ implemented a custom help command.
+2. I have changed the prefix character used to recognize the options
+ to a dot.
+3. Keyword arguments recognition (in the ``**setters``) is used to make it
+ possible to store a value in the shelve with the syntax
+ ``param_name=param_value``.
+4. ``*params`` are used to retrieve parameters from the shelve and some
+ error checking is performed in the case of missing parameters
+5. A command to clear the shelve is implemented as a flag (``.clear``).
+6. A command to delete a given parameter is implemented as an option
+ (``.delete``).
+7. There is an option with default (``.filename=conf.shelve``) to store
+ the filename of the shelve.
+8. All things considered, the code looks like a poor man object oriented
+ interface implemented with a chain of elifs instead of methods. Of course,
+ plac_ can do better than that, but let me start from a low-level approach
+ first.
+
+If you run ``ishelve.py`` without arguments you get the following
+message::
+
+ $ python ishelve.py
+ no arguments passed, use .help to see the available commands
+
+If you run ``ishelve.py`` with the option ``.h`` (or any abbreviation
+of ``.help``) you get::
+
+ $ python ishelve.py .h
+ Commands: .help, .showall, .clear, .delete
+ <param> ...
+ <param=value> ...
+
+You can check by hand that the tool work::
+
+ $ python ishelve.py .clear # start from an empty shelve
+ cleared the shelve
+ $ python ishelve.py a=1 b=2
+ setting a=1
+ setting b=2
+ $ python ishelve.py .showall
+ b=2
+ a=1
+ $ python ishelve.py .del b # abbreviation for .delete
+ deleted b
+ $ python ishelve.py a
+ 1
+ $ python ishelve.py b
+ b: not found
+ $ python ishelve.py .cler # mispelled command
+ usage: ishelve.py [.help] [.showall] [.clear] [.delete DELETE]
+ [.filename /home/micheles/conf.shelve]
+ [params [params ...]] [setters [setters ...]]
+ ishelve.py: error: unrecognized arguments: .cler
+
plac vs argparse
---------------------------------------------
@@ -509,7 +578,8 @@ following assumes knowledge of argparse_):
- plac_ does not support "required options". As the argparse_
documentation puts it: *Required options are generally considered bad
form - normal users expect options to be optional. You should avoid
- the use of required options whenever possible.*
+ the use of required options whenever possible.* Notice that since
+ argparse_ supports them, plac_ can manage them too, but not directly.
- plac_ supports only regular boolean flags. argparse_ has the ability to
define generalized two-value flags with values different from ``True``
@@ -573,8 +643,8 @@ as option prefix you can add the line::
The first prefix char (``/``) is used
as the default for the recognition of options and flags;
the second prefix char (``-``) is kept to keep the ``-h/--help`` option
-working: however you can disable it and reimplement it, if you like.
-An example will be given in the `advanced usage document`_ .
+working: however you can disable it and reimplement it, if you like,
+as seen in the ``ishelve`` example.
It is possible to access directly the underlying ArgumentParser_ object, by
invoking the ``plac.parser_from`` utility function:
@@ -583,13 +653,11 @@ invoking the ``plac.parser_from`` utility function:
>>> def main(arg):
... pass
...
->>> print plac.parser_from(main)
-ArgumentParser(prog='', usage=None, description=None, version=None,
-formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error',
-add_help=True)
+>>> print(plac.parser_from(main)) #doctest: +ELLIPSIS
+ArgumentParser(prog=...)
Internally ``plac.call`` uses ``plac.parser_from`` and adds the parser
-as an attribute ``.p``. When ``plac.call(func)`` is
+to the main function as an attribute. When ``plac.call(func)`` is
invoked multiple time, the parser is re-used and not rebuilt from scratch again.
I use ``plac.parser_from`` in the unit tests of the module, but regular
@@ -633,9 +701,20 @@ future. The idea is to keep the module short: it is and it should
remain a little wrapper over argparse_. Actually I have thought about
contributing the core back to argparse_ if plac_ becomes successfull
and gains a reasonable number of users. For the moment it should be
-considered in alpha status, especially for what concerns the
-features described in the `advanced usage document`_, which are
-implemented in a separated module (``plac_ext.py``).
+considered in alpha status.
+
+Notice that even if plac_ has been designed to be simple to use for
+simple stuff, its power should not be underestimated; it is actually a
+quite advanced tool with a domain of applicability which far exceeds
+the realm of command-line arguments parsers.
+
+Version 0.5 of plac_ doubled the code base and the documentation: it is
+based on the idea of using plac_ to implement command-line interpreters,
+i.e. something akin to the ``cmd`` module in the standard library, only better.
+The new features of plac_ are described in the `advanced usage document`_ .
+They are implemented in a separated module (``plac_ext.py``), since
+they require Python 2.5 to work, whereas ``plac_core.py`` only requires
+Python 2.3.
Trivia: the story behind the name
-----------------------------------------
@@ -667,10 +746,6 @@ Having little imagination, I decided to rename everything again to plac,
an anagram of clap: since it is a non-existing English name, I hope nobody
will steal it from me!
-Version 0.5 of plac_ doubled the code base and the documentation: it is
-based on the idea of using plac_ to implement command-line interpreters,
-i.e. something like the ``cmd`` module in the standard library, only better.
-
That's all, I hope you will enjoy working with plac_!
.. _argparse: http://argparse.googlecode.com