diff options
Diffstat (limited to 'plac/doc/plac.txt')
-rw-r--r-- | plac/doc/plac.txt | 223 |
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 |