diff options
author | Michele Simionato <michele.simionato@gmail.com> | 2010-05-31 15:42:49 +0200 |
---|---|---|
committer | Michele Simionato <michele.simionato@gmail.com> | 2010-05-31 15:42:49 +0200 |
commit | baa78b50ebcccd789b89f083b36ab2e9a6a389d5 (patch) | |
tree | 7dbe43a5ead3ad2594513b2ee87a085595d1d166 | |
parent | 0ccbfa6c34e0a423e7d0788563988765eab6d44b (diff) | |
download | micheles-baa78b50ebcccd789b89f083b36ab2e9a6a389d5.tar.gz |
clap 0.2, based on function annotations
-rw-r--r-- | clap/README.txt | 52 | ||||
-rw-r--r-- | clap/article/annotations.py | 8 | ||||
-rw-r--r-- | clap/article/article.html | 410 | ||||
-rw-r--r-- | clap/article/article.pdf | 2418 | ||||
-rw-r--r-- | clap/article/article.txt | 442 | ||||
l--------- | clap/article/clap.py | 1 | ||||
-rw-r--r-- | clap/article/example1.py | 13 | ||||
-rw-r--r-- | clap/article/example10.py | 14 | ||||
-rw-r--r-- | clap/article/example11.py | 12 | ||||
-rw-r--r-- | clap/article/example2.py | 10 | ||||
-rw-r--r-- | clap/article/example3.py | 6 | ||||
-rw-r--r-- | clap/article/example4.py | 14 | ||||
-rw-r--r-- | clap/article/example4_.py | 8 | ||||
-rw-r--r-- | clap/article/example5.py | 8 | ||||
-rw-r--r-- | clap/article/example6.py | 12 | ||||
-rw-r--r-- | clap/article/example7.py | 9 | ||||
-rw-r--r-- | clap/article/example8.py | 7 | ||||
-rw-r--r-- | clap/article/example9.py | 7 | ||||
-rw-r--r-- | clap/clap.py | 265 | ||||
-rw-r--r-- | clap/doc.txt | 5 | ||||
-rw-r--r-- | clap/test_clap.py | 82 |
21 files changed, 3595 insertions, 208 deletions
diff --git a/clap/README.txt b/clap/README.txt new file mode 100644 index 0000000..06c9ce3 --- /dev/null +++ b/clap/README.txt @@ -0,0 +1,52 @@ +clap, the easiest command line arguments parser in the world +============================================================ + +:Author: Michele Simionato +:E-mail: michele.simionato@gmail.com +:Requires: Python 2.3+ +:Download page: http://pypi.python.org/pypi/clap +:Installation: ``easy_install clap`` +:License: BSD license + +Installation +------------- + +If you are lazy, just perform + +$ easy_install clap + +which will install just the module on your system. Notice that +Python 3 requires the easy_install version of the distribute_ project. + +If you prefer to install the full distribution from source, including +the documentation, download the tarball_, unpack it and run + +$ python setup.py install + +in the main directory, possibly as superuser. + +.. _tarball: http://pypi.python.org/pypi/clap +.. _distribute: http://packages.python.org/distribute/ + +Testing +-------- + +Run + +$ python test_clap.py + +or + +$ nosetests test_clap + +or + +$ py.test test_clap + +Documentation +-------------- + +You can choose between the `HTML version`_ and the `PDF version`_ . + +.. _HTML version: http://micheles.googlecode.com/hg/clap/documentation.html +.. _PDF version: http://micheles.googlecode.com/hg/clap/documentation.pdf diff --git a/clap/article/annotations.py b/clap/article/annotations.py new file mode 100644 index 0000000..c84cc04 --- /dev/null +++ b/clap/article/annotations.py @@ -0,0 +1,8 @@ +class Positional(object): + def __init__(self, help='', type=None, choices=None, metavar=None): + self.help = help + self.kind = 'positional' + self.abbrev = None + self.type = type + self.choices = choices + self.metavar = metavar diff --git a/clap/article/article.html b/clap/article/article.html new file mode 100644 index 0000000..65d0c68 --- /dev/null +++ b/clap/article/article.html @@ -0,0 +1,410 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta name="generator" content="Docutils 0.6: http://docutils.sourceforge.net/" /> +<title></title> +<style type="text/css"> + +.highlight { background: #f8f8f8; } +.highlight .c { color: #408080; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #BC7A00 } /* Comment.Preproc */ +.highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #408080; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #808080 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0040D0 } /* Generic.Traceback */ +.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #008000; font-weight: bold } /* Keyword.Type */ +.highlight .m { color: #666666 } /* Literal.Number */ +.highlight .s { color: #BA2121 } /* Literal.String */ +.highlight .na { color: #7D9029 } /* Name.Attribute */ +.highlight .nb { color: #008000 } /* Name.Builtin */ +.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.highlight .no { color: #880000 } /* Name.Constant */ +.highlight .nd { color: #AA22FF } /* Name.Decorator */ +.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #0000FF } /* Name.Function */ +.highlight .nl { color: #A0A000 } /* Name.Label */ +.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #19177C } /* Name.Variable */ +.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #666666 } /* Literal.Number.Float */ +.highlight .mh { color: #666666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666666 } /* Literal.Number.Oct */ +.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #BB6688 } /* Literal.String.Regex */ +.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #19177C } /* Name.Variable.Class */ +.highlight .vg { color: #19177C } /* Name.Variable.Global */ +.highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ + +</style> +</head> +<body> +<div class="document"> + + +<div class="section" id="the-easiest-command-line-arguments-parser-in-the-python-world"> +<h1>The easiest command line arguments parser in the Python world</h1> +<p>Today I want to announce to the general public the birth of my latest +project, which aims to be the easiest command line arguments +parser in the Python world: <a class="reference external" href="http://www.welton.it/articles/scalable_systems">clap</a>.</p> +<p><a class="reference external" href="http://www.welton.it/articles/scalable_systems">clap</a> is designed to be <a class="reference external" href="http://www.welton.it/articles/scalable_systems">downwardly scalable</a>, i.e. to be trivially simple +to use for trivial use cases, while being surprisingly scalable upwards +for less trivial use cases. Still, <a class="reference external" href="http://www.welton.it/articles/scalable_systems">clap</a> is not intended to +be an industrial strength command line parsing module. Its +capabilities are limited by design. If you need more power, by all means +use the parsing modules in the standard library. Still, I have been using +Python for 8 years and never once I had to use the full power of the +standard library modules.</p> +<p>Actually I am prettu much convinced that features provided by <tt class="docutils literal">clap</tt> +aee more than enough for 99.9% of the typical use cases of a scripter +working in a Unix-like environment. I am targetting here programmers, +sysadmins, scientists and in general people writing throw-away scripts +for themselves, choosing to use a command line interface because it is +the quick and simple. Such users are not interested in features, +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 +hand. Unfortunately, the current modules in the standard library +forces them to go the hard way. They are designed to implement +power user tools for programmers or system administrators, and +they have a non-trivial learning curve.</p> +<p>This is why, even if there is no want of command line arguments +parsers in Python world, sometime like <a class="reference external" href="http://www.welton.it/articles/scalable_systems">clap</a> was still lacking.</p> +<p>The standard library alone contains three different modules for the +parsing of command line options: <a class="reference external" href="http://docs.python.org/library/getopt.html">getopt</a> (from the stone age), +<a class="reference external" href="http://docs.python.org/library/optparse.html">optparse</a> (from Python 2.3) and <a class="reference external" href="http://argparse.googlecode.com">argparse</a> (from Python 2.7). All of +them are quite powerful and especially <a class="reference external" href="http://argparse.googlecode.com">argparse</a> is an industrial +strength solution; <a class="reference external" href="http://www.welton.it/articles/scalable_systems">clap</a> is just a nice and simple wrapper over +<a class="reference external" href="http://argparse.googlecode.com">argparse</a>, hiding most of the complexity while retaining most of +the power.</p> +</div> +<div class="section" id="the-importance-of-scaling-down"> +<h1>The importance of scaling down</h1> +<p>An ex-coworket of mine, David Welton, once wrote a nice article about +the importance of <a class="reference external" href="http://www.welton.it/articles/scalable_systems">scaling down</a>: most people are concerned with the +possibility of scaling up, but we should also be concerned with the +issue of scaling down: in other worlds, simple things should be kept +simple. To be concrete, let me start with the simplest possible +thing: a script that takes a single argument and does something to it. +It cannot get more trivial than that (discarding the possibility of +a script without command line arguments, where there is nothing to parse), +nevertheless it is a use case <em>extremely important</em>: +I need to write scripts like that nearly every day, I wrote hundreds +of them in the last few years and I have never been happy. Here is +a typical example:</p> +<blockquote> +<pre class="literal-block"> +def main(dsn): + "Do something with the database" + print(dsn) + +if __name__ == '__main__': + import sys + n = len(sys.argv[1:]) + if n == 0: + sys.exit('usage: python %s dsn' % sys.argv[0]) + elif n == 1: + main(sys.argv[1]) + else: + sys.exit('Unrecognized arguments: %s' % ' '.join(sys.argv[2:])) + +</pre> +</blockquote> +<p>As you see the whole <tt class="docutils literal">if __name__ == '__main__'</tt> block (10 lines +counting an empty line) is essentially boilerplate that should not exists. +Actually I think the Python language should recognize the +main function and perform trivial arguments parsing behind the +scenes; unfortunaly this is unlikely to happen. Therefore I have +been writing boilerplate like this for years, and every time +I <em>hate</em> having to check for the <tt class="docutils literal">IndexError</tt> by hand, and I hate having +to write always the same usage message. The purpose of using a +scripting language is convenience and trivial things should be +trivial. Unfortunately the standard library modules do not help +for this use case, which may be trivial, but it is still +incredibly common. Using <a class="reference external" href="http://docs.python.org/library/getopt.html">getopt</a> and <a class="reference external" href="http://docs.python.org/library/optparse.html">optparse</a> does not help, +since they are intended to manage options and not positional arguments; +the <a class="reference external" href="http://argparse.googlecode.com">argparse</a> module helps a bit and it is able to reduce the +boilerplate from 10 lines to 7 lines:</p> +<blockquote> +<pre class="literal-block"> +def main(dsn): + "Do something on the database" + print(dsn) + +if __name__ == '__main__': + import argparse + p = argparse.ArgumentParser() + p.add_argument('dsn') + arg = p.parse_args() + main(arg.dsn) + +</pre> +</blockquote> +<p>However saving a three lines does not justify introducing the external +dependency (most people will not switch Python 2.7, currenctly in beta +for many years). +Moreover, it just feels too complex to instantiate a class and to +define a parser by hand for such a trivial task.</p> +<p>The <a class="reference external" href="http://www.welton.it/articles/scalable_systems">clap</a> module works pretty well when scaling down, and it is able +to reduce the ten lines of boiler plate to two lines. With the +<a class="reference external" href="http://www.welton.it/articles/scalable_systems">clap</a> module all you need to write is</p> +<blockquote> +<pre class="literal-block"> +def main(dsn): + "Do something with the database" + print(dsn) + +if __name__ == '__main__': + import clap; clap.call(main) + +</pre> +</blockquote> +<p>As an additional bonus she <a class="reference external" href="http://www.welton.it/articles/scalable_systems">clap</a> module automatically recognizes the help flag:</p> +<blockquote> +# python example1.py -h</blockquote> +<p>This is only the tip of the iceberg, as you will see.</p> +</div> +<div class="section" id="optional-arguments"> +<h1>Optional arguments</h1> +<p>I have encountered this use case at work hundreds of times:</p> +<blockquote> +<pre class="literal-block"> +def main(dsn): + "Do something on the database" + print(dsn) + +if __name__ == '__main__': + import argparse + p = argparse.ArgumentParser() + p.add_argument('dsn') + arg = p.parse_args() + main(arg.dsn) + +</pre> +</blockquote> +<p>9 lines of boilerplate are removed.</p> +<p>Finally, often you want to pass a variable number of arguments to +your command line script. Here is an example, a script which runs +on the database a series of .sql scripts:</p> +<blockquote> +<pre class="literal-block"> +from datetime import datetime + +def main(dsn, *scripts): + "Run the given scripts on the database" + for script in scripts: + print('executing %s' % script) + +if __name__ == '__main__': + if len(sys.argv) < 2: + sys.exit('usage: python %s dsn script.sql ...' % sys.argv[0]) + main(sys.argv[1:]) + +</pre> +</blockquote> +<p>Using <a class="reference external" href="http://www.welton.it/articles/scalable_systems">clap</a>, you can just replace the <tt class="docutils literal">__main__</tt> block with the usual +<tt class="docutils literal">import clap; clap.call(main)</tt>. The importanting is that you get a +much nicer usage message:</p> +<pre class="literal-block"> +usage: example7.py [-h] dsn [scripts [scripts ...]] + +positional arguments: + dsn + scripts + +optional arguments: + -h, --help show this help message and exit +</pre> +</div> +<div class="section" id="options-and-flags"> +<h1>Options and flags</h1> +<p>It is surprising how little 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 certainly have written more than a thousand of them). +Still, this use case is quite common and 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 <a class="reference external" href="http://code.activestate.com/recipes/278844-parsing-the-command-line/">optionparse</a> recipe, which provides a +convenient wrapper over <a class="reference external" href="http://code.activestate.com/recipes/278844-parsing-the-command-line/">optionparse</a> or I have just performed the +parsing by hand in the simplest cases.</p> +<p><a class="reference external" href="http://www.welton.it/articles/scalable_systems">clap</a> is inspired to the <a class="reference external" href="http://code.activestate.com/recipes/278844-parsing-the-command-line/">optionparse</a> recipe, in the sense that +it delivers the programmer from the burden of writing the +parser for the options by hand, 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 <tt class="docutils literal">main</tt> function.</p> +<p>The idea is to leverage on the <cite>function annotations</cite> concept, a new +feature of Python 3. An example is worth a thousand words, so here +it is:</p> +<blockquote> +<pre class="literal-block"> +def main(command: ("SQL query", 'option-c'), dsn): + if command: + print('executing %s on %s' % (command, dsn)) + +if __name__ == '__main__': + import clap; clap.call(main) + +</pre> +</blockquote> +<p>As you see, the argument <tt class="docutils literal">command</tt> has been annotated with the +tuple <tt class="docutils literal">("SQL query", <span class="pre">'option-c')</span></tt>: the first string is the +help string which will appear in the usage message, whereas the +second string tells <a class="reference external" href="http://www.welton.it/articles/scalable_systems">clap</a> that <tt class="docutils literal">command</tt> is an option and that +it can be abbreviated with the letter <tt class="docutils literal">c</tt>. Of course, it also +possible to use the long option format, by prefixing the option +with <tt class="docutils literal"><span class="pre">--command=</span></tt>. The resulting usage message is the following:</p> +<pre class="literal-block"> +$ python3 example8.py -h +usage: example8.py [-h] [-c COMMAND] dsn + +positional arguments: + dsn + +optional arguments: + -h, --help show this help message and exit + -c COMMAND, --command COMMAND + SQL query +</pre> +<p>Here are two examples of usage:</p> +<pre class="literal-block"> +$ python3 example8.py -c"select * from table" dsn +executing select * from table on dsn + +$ python3 example8.py --command="select * from table" dsn +executing select * from table on dsn +</pre> +<p>Notice that if the option is not passed, the variable <tt class="docutils literal">command</tt> +will get the value <tt class="docutils literal">None</tt>.</p> +<p>Even positional argument can be annotated, but it makes no sense to +specify the "option-c" string, so you can skip it and write:</p> +<pre class="literal-block"> +def main(command: ("SQL query", 'option-c'), dsn: ("Database dsn",)): + ... +</pre> +<p>Alternatively, you can write <tt class="docutils literal">("Database dsn", None)</tt>. In both cases +the usage message now will show a nice help string on the right hand side +of the <tt class="docutils literal">dsn</tt> positional argument. varargs (starred-arguments) can also +be annotated:</p> +<pre class="literal-block"> +def main(dsn: ("Database dsn",), *scripts: ("SQL scripts",)): + ... +</pre> +<p>is a valid signature for <a class="reference external" href="http://www.welton.it/articles/scalable_systems">clap</a>, which will recognize the help strings +for both <tt class="docutils literal">dsn</tt> and <tt class="docutils literal">scripts</tt>.</p> +<p><a class="reference external" href="http://www.welton.it/articles/scalable_systems">clap</a> also recognizes flags, i.e. boolean options which are +True if they are passed to the command line and False if they are absent. +Here is an example:</p> +<pre class="literal-block"> +$ python3 example9.py -v dsn +connecting to dsn +</pre> +<p>For consistency with the way the usage message is printed, I suggest to +follow the FOP convention: in the <tt class="docutils literal">main</tt> function write first +the flag arguments, then the option arguments and finally the positional +arguments. This is a convention and you are not forced to use it, but +it makes sense to put the position arguments at the end, since they +may be default arguments and varargs. In this document I will always use +the FOP convention.</p> +</div> +<div class="section" id="clap-is-also-for-people-not-using-python-3"> +<h1>clap is also for people not using Python 3</h1> +<p>I do not use Python 3. At work we are just starting to think about +migrating to Python 2.6. I think it will take years before we even +think to migrate to Python 3. I am pretty much sure most Pythonistas +are in the same situation. Therefore <a class="reference external" href="http://www.welton.it/articles/scalable_systems">clap</a> 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</p> +<pre class="literal-block"> +def main(dsn: ("Database dsn",), *scripts: ("SQL scripts",)): +</pre> +<p>becomes:</p> +<pre class="literal-block"> +def main(dsn, *scripts): + ... +main.__annotations__ = dict( +dsn= ("Database dsn",), +scripts=("SQL scripts",)) +</pre> +<p>One should be careful to much the keys of the annotations dictionary +with the names of the arguments in the annotated function; for lazy +people with Python 2.4 available the simplest way is to use the +<tt class="docutils literal">clap.annotations</tt> decorator that performs the check for you.</p> +<pre class="literal-block"> +@annotations( +dsn= ("Database dsn",), +scripts=("SQL scripts",)) +def main(dsn, *scripts): + ... +</pre> +<p>In the rest of this article I will assume that you are using Python 2.X with +X >= 4 and I will use the <tt class="docutils literal">clap.annotations</tt> decorator.</p> +</div> +<div class="section" id="advanced-usage"> +<h1>Advanced usage</h1> +<p>One of the goals of clap is to have a learning curve of <em>minutes</em>, compared +to the learning curve of <em>hours</em> of <a class="reference external" href="http://argparse.googlecode.com">argparse</a>. That does not mean +that I have removed all the advanced features of <a class="reference external" href="http://argparse.googlecode.com">argparse</a>. Actually +a lot of <a class="reference external" href="http://argparse.googlecode.com">argparse</a> power persists in <a class="reference external" href="http://www.welton.it/articles/scalable_systems">clap</a>: in particular, the +<tt class="docutils literal">type</tt>, <tt class="docutils literal">choices</tt> and <tt class="docutils literal">metavar</tt> concepts are there. +Here is an example showing all of them:</p> +<blockquote> +<pre class="literal-block"> +import clap + +@clap.annotations( +operator=("The name of an operator", None, str, ['add', 'mul']), +numbers=("A number", None, float, None, "n")) +def main(operator, *numbers): + op = getattr(float, '__%s__' % operator) + result = dict(add=0.0, mul=1.0)[operator] + for n in numbers: + result = op(result, n) + print(result) + +if __name__ == '__main__': + clap.call(main) + +</pre> +</blockquote> +<p>Let me begin by discussing the <tt class="docutils literal">type</tt> feature: given any callable +taking a string in input a returning any Python object, it is possible +to automagically convert the parsed arguments with the callable, simply +by listing it in the annotation</p> +</div> +</div> +</body> +</html> diff --git a/clap/article/article.pdf b/clap/article/article.pdf new file mode 100644 index 0000000..cb3f3dc --- /dev/null +++ b/clap/article/article.pdf @@ -0,0 +1,2418 @@ +%PDF-1.3
+%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
+% 'BasicFonts': class PDFDictionary
+1 0 obj
+% The standard fonts dictionary
+<< /F1 2 0 R
+ /F2 3 0 R
+ /F3 14 0 R
+ /F4 16 0 R
+ /F5 17 0 R >>
+endobj
+% 'F1': class PDFType1Font
+2 0 obj
+% Font Helvetica
+<< /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font >>
+endobj
+% 'F2': class PDFType1Font
+3 0 obj
+% Font Helvetica-Bold
+<< /BaseFont /Helvetica-Bold
+ /Encoding /WinAnsiEncoding
+ /Name /F2
+ /Subtype /Type1
+ /Type /Font >>
+endobj
+% 'Annot.NUMBER1': class PDFDictionary
+4 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://docs.python.org/library/getopt.html) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 361.1228
+ 686.5936
+ 392.9767
+ 698.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER2': class PDFDictionary
+5 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://docs.python.org/library/optparse.html) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 493.6727
+ 686.5936
+ 531.3087
+ 698.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER3': class PDFDictionary
+6 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://argparse.googlecode.com) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 164.0504
+ 674.5936
+ 206.7572
+ 686.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER4': class PDFDictionary
+7 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://argparse.googlecode.com) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 493.1227
+ 674.5936
+ 532.1158
+ 686.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER5': class PDFDictionary
+8 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 95.71512
+ 632.5936
+ 114.0551
+ 644.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER6': class PDFDictionary
+9 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 120.9573
+ 632.5936
+ 143.4195
+ 644.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER7': class PDFDictionary
+10 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 227.1684
+ 632.5936
+ 320.7606
+ 644.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER8': class PDFDictionary
+11 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 337.6822
+ 620.5936
+ 359.0965
+ 632.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER9': class PDFDictionary
+12 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://argparse.googlecode.com) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 490.3427
+ 620.5936
+ 529.8027
+ 632.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER10': class PDFDictionary
+13 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 346.3284
+ 608.5936
+ 367.4652
+ 620.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'F3': class PDFType1Font
+14 0 obj
+% Font Courier
+<< /BaseFont /Courier
+ /Encoding /WinAnsiEncoding
+ /Name /F3
+ /Subtype /Type1
+ /Type /Font >>
+endobj
+% 'Annot.NUMBER11': class PDFDictionary
+15 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 471.0489
+ 407.5936
+ 529.8027
+ 419.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'F4': class PDFType1Font
+16 0 obj
+% Font Helvetica-Oblique
+<< /BaseFont /Helvetica-Oblique
+ /Encoding /WinAnsiEncoding
+ /Name /F4
+ /Subtype /Type1
+ /Type /Font >>
+endobj
+% 'F5': class PDFType1Font
+17 0 obj
+% Font Times-Roman
+<< /BaseFont /Times-Roman
+ /Encoding /WinAnsiEncoding
+ /Name /F5
+ /Subtype /Type1
+ /Type /Font >>
+endobj
+% 'Page1': class PDFPage
+18 0 obj
+% Page dictionary
+<< /Annots [ 4 0 R
+ 5 0 R
+ 6 0 R
+ 7 0 R
+ 8 0 R
+ 9 0 R
+ 10 0 R
+ 11 0 R
+ 12 0 R
+ 13 0 R
+ 15 0 R ]
+ /Contents 69 0 R
+ /MediaBox [ 0
+ 0
+ 595.2756
+ 841.8898 ]
+ /Parent 68 0 R
+ /Resources << /Font 1 0 R
+ /ProcSet [ /PDF
+ /Text
+ /ImageB
+ /ImageC
+ /ImageI ] >>
+ /Rotate 0
+ /Trans << >>
+ /Type /Page >>
+endobj
+% 'Annot.NUMBER12': class PDFDictionary
+19 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://docs.python.org/library/getopt.html) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 504.7827
+ 756.5936
+ 532.1006
+ 768.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER13': class PDFDictionary
+20 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://docs.python.org/library/optparse.html) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 82.40665
+ 744.5936
+ 124.3504
+ 756.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER14': class PDFDictionary
+21 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://argparse.googlecode.com) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 62.69291
+ 732.5936
+ 104.9329
+ 744.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER15': class PDFDictionary
+22 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 83.82606
+ 533.3936
+ 106.0692
+ 545.3936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER16': class PDFDictionary
+23 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 243.8829
+ 521.3936
+ 265.0029
+ 533.3936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER17': class PDFDictionary
+24 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 83.6329
+ 412.1936
+ 105.6829
+ 424.1936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER18': class PDFDictionary
+25 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://argparse.googlecode.com) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 421.9727
+ 412.1936
+ 465.1427
+ 424.1936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER19': class PDFDictionary
+26 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 211.6529
+ 262.9936
+ 232.7729
+ 274.9936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Page2': class PDFPage
+27 0 obj
+% Page dictionary
+<< /Annots [ 19 0 R
+ 20 0 R
+ 21 0 R
+ 22 0 R
+ 23 0 R
+ 24 0 R
+ 25 0 R
+ 26 0 R ]
+ /Contents 70 0 R
+ /MediaBox [ 0
+ 0
+ 595.2756
+ 841.8898 ]
+ /Parent 68 0 R
+ /Resources << /Font 1 0 R
+ /ProcSet [ /PDF
+ /Text
+ /ImageB
+ /ImageC
+ /ImageI ] >>
+ /Rotate 0
+ /Trans << >>
+ /Type /Page >>
+endobj
+% 'Annot.NUMBER20': class PDFDictionary
+28 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 85.47291
+ 665.3936
+ 106.5929
+ 677.3936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER21': class PDFDictionary
+29 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 62.69291
+ 450.9936
+ 84.20915
+ 462.9936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER22': class PDFDictionary
+30 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 97.95597
+ 257.7936
+ 116.296
+ 269.7936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER23': class PDFDictionary
+31 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 446.6187
+ 96.59362
+ 464.9587
+ 108.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Page3': class PDFPage
+32 0 obj
+% Page dictionary
+<< /Annots [ 28 0 R
+ 29 0 R
+ 30 0 R
+ 31 0 R ]
+ /Contents 71 0 R
+ /MediaBox [ 0
+ 0
+ 595.2756
+ 841.8898 ]
+ /Parent 68 0 R
+ /Resources << /Font 1 0 R
+ /ProcSet [ /PDF
+ /Text
+ /ImageB
+ /ImageC
+ /ImageI ] >>
+ /Rotate 0
+ /Trans << >>
+ /Type /Page >>
+endobj
+% 'Annot.NUMBER24': class PDFDictionary
+33 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://code.activestate.com/recipes/278844-parsing-the-command-line/) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 446.8103
+ 681.5936
+ 502.5727
+ 693.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER25': class PDFDictionary
+34 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://code.activestate.com/recipes/278844-parsing-the-command-line/) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 260.18
+ 669.5936
+ 312.43
+ 681.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER26': class PDFDictionary
+35 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 62.69291
+ 639.5936
+ 84.28901
+ 651.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER27': class PDFDictionary
+36 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://code.activestate.com/recipes/278844-parsing-the-command-line/) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 161.7834
+ 639.5936
+ 217.2895
+ 651.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER28': class PDFDictionary
+37 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 133.1479
+ 440.3936
+ 154.4129
+ 452.3936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Page4': class PDFPage
+38 0 obj
+% Page dictionary
+<< /Annots [ 33 0 R
+ 34 0 R
+ 35 0 R
+ 36 0 R
+ 37 0 R ]
+ /Contents 72 0 R
+ /MediaBox [ 0
+ 0
+ 595.2756
+ 841.8898 ]
+ /Parent 68 0 R
+ /Resources << /Font 1 0 R
+ /ProcSet [ /PDF
+ /Text
+ /ImageB
+ /ImageC
+ /ImageI ] >>
+ /Rotate 0
+ /Trans << >>
+ /Type /Page >>
+endobj
+% 'Annot.NUMBER29': class PDFDictionary
+39 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 185.8554
+ 711.3936
+ 207.616
+ 723.3936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER30': class PDFDictionary
+40 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 162.7329
+ 556.9936
+ 181.0729
+ 568.9936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER31': class PDFDictionary
+41 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 62.69291
+ 479.7936
+ 84.57878
+ 491.7936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER32': class PDFDictionary
+42 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 185.1229
+ 150.3936
+ 208.3329
+ 162.3936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Page5': class PDFPage
+43 0 obj
+% Page dictionary
+<< /Annots [ 39 0 R
+ 40 0 R
+ 41 0 R
+ 42 0 R ]
+ /Contents 73 0 R
+ /MediaBox [ 0
+ 0
+ 595.2756
+ 841.8898 ]
+ /Parent 68 0 R
+ /Resources << /Font 1 0 R
+ /ProcSet [ /PDF
+ /Text
+ /ImageB
+ /ImageC
+ /ImageI ] >>
+ /Rotate 0
+ /Trans << >>
+ /Type /Page >>
+endobj
+% 'Annot.NUMBER33': class PDFDictionary
+44 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://argparse.googlecode.com) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 62.69291
+ 455.1936
+ 102.1529
+ 467.1936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER34': class PDFDictionary
+45 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://argparse.googlecode.com) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 418.9683
+ 455.1936
+ 458.4283
+ 467.1936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER35': class PDFDictionary
+46 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://argparse.googlecode.com) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 62.69291
+ 443.1936
+ 106.4622
+ 455.1936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER36': class PDFDictionary
+47 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 188.85
+ 443.1936
+ 207.19
+ 455.1936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER37': class PDFDictionary
+48 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://argparse.googlecode.com) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 407.8229
+ 263.1936
+ 450.0629
+ 275.1936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Page6': class PDFPage
+49 0 obj
+% Page dictionary
+<< /Annots [ 44 0 R
+ 45 0 R
+ 46 0 R
+ 47 0 R
+ 48 0 R ]
+ /Contents 74 0 R
+ /MediaBox [ 0
+ 0
+ 595.2756
+ 841.8898 ]
+ /Parent 68 0 R
+ /Resources << /Font 1 0 R
+ /ProcSet [ /PDF
+ /Text
+ /ImageB
+ /ImageC
+ /ImageI ] >>
+ /Rotate 0
+ /Trans << >>
+ /Type /Page >>
+endobj
+% 'Annot.NUMBER38': class PDFDictionary
+50 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 62.69291
+ 475.0549
+ 84.7414
+ 487.0549 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER39': class PDFDictionary
+51 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://argparse.googlecode.com) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 136.4369
+ 475.0549
+ 179.6054
+ 487.0549 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER40': class PDFDictionary
+52 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://argparse.googlecode.com/svn/tags/r11/doc/ArgumentParser.html) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 62.69291
+ 463.0549
+ 138.3111
+ 475.0549 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER41': class PDFDictionary
+53 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 127.9872
+ 325.8549
+ 149.3819
+ 337.8549 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER42': class PDFDictionary
+54 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://argparse.googlecode.com) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 480.5388
+ 313.8549
+ 524.2427
+ 325.8549 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER43': class PDFDictionary
+55 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://argparse.googlecode.com/svn/tags/r11/doc/ArgumentParser.html) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 225.5228
+ 283.8549
+ 301.2571
+ 295.8549 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER44': class PDFDictionary
+56 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://www.welton.it/articles/scalable_systems) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 62.69291
+ 116.6549
+ 84.35423
+ 128.6549 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Page7': class PDFPage
+57 0 obj
+% Page dictionary
+<< /Annots [ 50 0 R
+ 51 0 R
+ 52 0 R
+ 53 0 R
+ 54 0 R
+ 55 0 R
+ 56 0 R ]
+ /Contents 75 0 R
+ /MediaBox [ 0
+ 0
+ 595.2756
+ 841.8898 ]
+ /Parent 68 0 R
+ /Resources << /Font 1 0 R
+ /ProcSet [ /PDF
+ /Text
+ /ImageB
+ /ImageC
+ /ImageI ] >>
+ /Rotate 0
+ /Trans << >>
+ /Type /Page >>
+endobj
+% 'Page8': class PDFPage
+58 0 obj
+% Page dictionary
+<< /Contents 76 0 R
+ /MediaBox [ 0
+ 0
+ 595.2756
+ 841.8898 ]
+ /Parent 68 0 R
+ /Resources << /Font 1 0 R
+ /ProcSet [ /PDF
+ /Text
+ /ImageB
+ /ImageC
+ /ImageI ] >>
+ /Rotate 0
+ /Trans << >>
+ /Type /Page >>
+endobj
+% 'R59': class PDFCatalog
+59 0 obj
+% Document Root
+<< /Outlines 61 0 R
+ /PageLabels 77 0 R
+ /PageMode /UseNone
+ /Pages 68 0 R
+ /Type /Catalog >>
+endobj
+% 'R60': class PDFInfo
+60 0 obj
+<< /Author ()
+ /CreationDate (D:20100531105058-01'00')
+ /Keywords ()
+ /Producer (ReportLab http://www.reportlab.com)
+ /Subject (\(unspecified\))
+ /Title (The Easiest Command Line Arguments Parser in the World) >>
+endobj
+% 'R61': class PDFOutlines
+61 0 obj
+<< /Count 6
+ /First 62 0 R
+ /Last 67 0 R
+ /Type /Outlines >>
+endobj
+% 'Outline.0': class OutlineEntryObject
+62 0 obj
+<< /Dest [ 18 0 R
+ /XYZ
+ 62.69291
+ 443.0236
+ 0 ]
+ /Next 63 0 R
+ /Parent 61 0 R
+ /Title (The importance of scaling down) >>
+endobj
+% 'Outline.1': class OutlineEntryObject
+63 0 obj
+<< /Dest [ 27 0 R
+ /XYZ
+ 62.69291
+ 247.4236
+ 0 ]
+ /Next 64 0 R
+ /Parent 61 0 R
+ /Prev 62 0 R
+ /Title (Positional default arguments) >>
+endobj
+% 'Outline.2': class OutlineEntryObject
+64 0 obj
+<< /Dest [ 38 0 R
+ /XYZ
+ 62.69291
+ 765.0236
+ 0 ]
+ /Next 65 0 R
+ /Parent 61 0 R
+ /Prev 63 0 R
+ /Title (Options and flags) >>
+endobj
+% 'Outline.3': class OutlineEntryObject
+65 0 obj
+<< /Dest [ 43 0 R
+ /XYZ
+ 62.69291
+ 209.8236
+ 0 ]
+ /Next 66 0 R
+ /Parent 61 0 R
+ /Prev 64 0 R
+ /Title (clap for people not using Python 3) >>
+endobj
+% 'Outline.4': class OutlineEntryObject
+66 0 obj
+<< /Dest [ 49 0 R
+ /XYZ
+ 62.69291
+ 502.6236
+ 0 ]
+ /Next 67 0 R
+ /Parent 61 0 R
+ /Prev 65 0 R
+ /Title (Advanced usage) >>
+endobj
+% 'Outline.5': class OutlineEntryObject
+67 0 obj
+<< /Dest [ 57 0 R
+ /XYZ
+ 62.69291
+ 510.4849
+ 0 ]
+ /Parent 61 0 R
+ /Prev 66 0 R
+ /Title (A few notes on the underlying implementation) >>
+endobj
+% 'R68': class PDFPages
+68 0 obj
+% page tree
+<< /Count 8
+ /Kids [ 18 0 R
+ 27 0 R
+ 32 0 R
+ 38 0 R
+ 43 0 R
+ 49 0 R
+ 57 0 R
+ 58 0 R ]
+ /Type /Pages >>
+endobj
+% 'R69': class PDFStream
+69 0 obj
+% page stream
+<< /Length 6154 >>
+stream
+1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 717.0236 cm
+q
+BT 1 0 0 1 0 33.64 Tm 3.214882 0 Td 24 TL /F2 20 Tf 0 0 0 rg (The Easiest Command Line Arguments Parser in) Tj T* 185.62 0 Td (the World) Tj T* -188.8349 0 Td ET
+Q
+Q
+q
+1 0 0 1 62.69291 647.0236 cm
+q
+BT 1 0 0 1 0 52.82 Tm .05061 Tw 12 TL /F1 10 Tf 0 0 0 rg (There is no want of command line arguments parsers in Python world. The standard library alone contains) Tj T* 0 Tw 1.273984 Tw (three different modules for the parsing of command line options: ) Tj 0 0 .501961 rg (getopt ) Tj 0 0 0 rg (\(from the stone age\), ) Tj 0 0 .501961 rg (optparse) Tj T* 0 Tw .46686 Tw 0 0 0 rg (\(from Python 2.3\) and ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (\(from Python 2.7\). All of them are quite powerful and especially ) Tj 0 0 .501961 rg (argparse) Tj T* 0 Tw .847485 Tw 0 0 0 rg (is an industrial strength solution; unfortunately, all of them have a non-zero learning curve and a certain) Tj T* 0 Tw (verbosity.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 557.0236 cm
+q
+BT 1 0 0 1 0 76.82 Tm 1.342209 Tw 12 TL /F1 10 Tf 0 0 0 rg (Enters ) Tj 0 0 .501961 rg (clap) Tj 0 0 0 rg (. ) Tj 0 0 .501961 rg (clap ) Tj 0 0 0 rg (is designed to be ) Tj 0 0 .501961 rg (downwardly scalable) Tj 0 0 0 rg (, i.e. to be trivially simple to use for trivial use) Tj T* 0 Tw .29436 Tw (cases, and to have a next-to-zero learning curve. Technically ) Tj 0 0 .501961 rg (clap ) Tj 0 0 0 rg (is just a simple wrapper over ) Tj 0 0 .501961 rg (argparse) Tj 0 0 0 rg (,) Tj T* 0 Tw .01686 Tw (hiding most of the complexity while retaining most of the power. ) Tj 0 0 .501961 rg (clap ) Tj 0 0 0 rg (is surprisingly scalable upwards even) Tj T* 0 Tw .569398 Tw (for non-trivial use cases, but it is not intended to be an industrial strength command line parsing module.) Tj T* 0 Tw .07104 Tw (Its capabilities are limited by design. If you need more power, by all means use the parsing modules in the) Tj T* 0 Tw .51237 Tw (standard library. Still, I have been using Python for 8 years and never once I had to use the full power of) Tj T* 0 Tw (the standard library modules.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 455.0236 cm
+q
+BT 1 0 0 1 0 88.82 Tm .536457 Tw 12 TL /F1 10 Tf 0 0 0 rg (Actually I am pretty much convinced that features provided by ) Tj /F3 10 Tf (clap ) Tj /F1 10 Tf (are more than enough for 99.9% of) Tj T* 0 Tw .60811 Tw (the typical use cases of a scripter working in a Unix-like environment. I am targetting here programmers,) Tj T* 0 Tw .337126 Tw (sys-admins, scientists and in general people writing throw-away scripts for themselves, choosing to use a) Tj T* 0 Tw .242339 Tw (command line interface because it is the quick and simple. Such users are not interested in features, they) Tj T* 0 Tw 2.177882 Tw (just want to be able to write a simple command line tool from a simple specification, not to build a) Tj T* 0 Tw .014985 Tw (command line parser by hand. Unfortunately, the current modules in the standard library forces them to go) Tj T* 0 Tw 4.664269 Tw (the hard way. They are designed to implement power user tools for programmers or system) Tj T* 0 Tw (administrators, and they have a non-trivial learning curve.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 422.0236 cm
+q
+BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (The importance of scaling down) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 320.0236 cm
+q
+BT 1 0 0 1 0 88.82 Tm .953735 Tw 12 TL /F1 10 Tf 0 0 0 rg (An ex-coworker of mine, David Welton, once wrote a nice article about the importance of ) Tj 0 0 .501961 rg (scaling down) Tj 0 0 0 rg (:) Tj T* 0 Tw 1.026457 Tw (most people are concerned with the possibility of scaling up, but we should also be concerned with the) Tj T* 0 Tw .754987 Tw (issue of scaling down: in other worlds, simple things should be kept simple. To be concrete, let me start) Tj T* 0 Tw .567765 Tw (with the simplest possible thing: a script that takes a single argument and does something to it. It cannot) Tj T* 0 Tw 1.314651 Tw (get more trivial than that \(discarding the possibility of a script without command line arguments, where) Tj T* 0 Tw .188935 Tw (there is nothing to parse\), nevertheless it is a use case ) Tj /F4 10 Tf (extremely common) Tj /F1 10 Tf (: I need to write scripts like that) Tj T* 0 Tw .539513 Tw (nearly every day, I wrote hundreds of them in the last few years and I have never been happy. Here is a) Tj T* 0 Tw (typical example of code I have been writing by hand for years:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 314.0236 cm
+Q
+q
+1 0 0 1 62.69291 144.8236 cm
+0 0 0 rg
+BT /F5 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm T* ET
+q
+1 0 0 1 20 0 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 448.6898 168 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 149.71 Tm /F3 10 Tf 12 TL (def main\(dsn\):) Tj T* ( "Do something with the database") Tj T* ( print\(dsn\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( import sys) Tj T* ( n = len\(sys.argv[1:]\)) Tj T* ( if n == 0:) Tj T* ( sys.exit\('usage: python %s dsn' % sys.argv[0]\)) Tj T* ( elif n == 1:) Tj T* ( main\(sys.argv[1]\)) Tj T* ( else:) Tj T* ( sys.exit\('Unrecognized arguments: %s' % ' '.join\(sys.argv[2:]\)\)) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 144.8236 cm
+Q
+q
+1 0 0 1 62.69291 78.82362 cm
+q
+BT 1 0 0 1 0 52.82 Tm .880651 Tw 12 TL /F1 10 Tf 0 0 0 rg (As you see the whole ) Tj /F3 10 Tf (if __name__ == '__main__' ) Tj /F1 10 Tf (block \(nine lines\) is essentially boilerplate that ) Tj T* 0 Tw 1.125318 Tw (should not exists. Actually I think the Python language should recognize the main function and perform ) Tj T* 0 Tw 1.385984 Tw (trivial arguments parsing behind the scenes; unfortunaly this is unlikely to happen. I have been writing ) Tj T* 0 Tw 1.767356 Tw (boilerplate like this in hundreds of scripts for years, and every time I ) Tj /F4 10 Tf (hate ) Tj /F1 10 Tf (it. The purpose of using a ) Tj T* 0 Tw 1.47229 Tw (scripting language is convenience and trivial things should be trivial. Unfortunately the standard library) Tj T* 0 Tw ET
+Q
+Q
+
+endstream
+
+endobj
+% 'R70': class PDFStream
+70 0 obj
+% page stream
+<< /Length 4633 >>
+stream
+1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 729.0236 cm
+q
+BT 1 0 0 1 0 28.82 Tm .482093 Tw 12 TL /F1 10 Tf 0 0 0 rg (modules do not help for this use case, which may be trivial, but it is still incredibly common. Using ) Tj 0 0 .501961 rg (getopt) Tj T* 0 Tw .253735 Tw 0 0 0 rg (and ) Tj 0 0 .501961 rg (optparse ) Tj 0 0 0 rg (does not help, since they are intended to manage options and not positional arguments; the) Tj T* 0 Tw 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (module helps a bit and it is able to reduce the boilerplate from nine lines to six lines:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 723.0236 cm
+Q
+q
+1 0 0 1 62.69291 589.8236 cm
+0 0 0 rg
+BT /F5 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm T* ET
+q
+1 0 0 1 20 0 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 448.6898 132 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 113.71 Tm /F3 10 Tf 12 TL (def main\(dsn\):) Tj T* ( "Do something on the database") Tj T* ( print\(dsn\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( import argparse) Tj T* ( p = argparse.ArgumentParser\(\)) Tj T* ( p.add_argument\('dsn'\)) Tj T* ( arg = p.parse_args\(\)) Tj T* ( main\(arg.dsn\)) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 589.8236 cm
+Q
+q
+1 0 0 1 62.69291 547.8236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 28.82 Tm /F1 10 Tf 12 TL 1.644269 Tw (However saving three lines does not justify introducing the external dependency: most people will not) Tj T* 0 Tw .276303 Tw (switch Python 2.7, which at the time of this writing is just about to be released, for many years. Moreover,) Tj T* 0 Tw (it just feels too complex to instantiate a class and to define a parser by hand for such a trivial task.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 517.8236 cm
+q
+BT 1 0 0 1 0 16.82 Tm 1.123145 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj 0 0 .501961 rg (clap ) Tj 0 0 0 rg (module is designed to manage well such use cases, and it is able to reduce the original nine) Tj T* 0 Tw (lines of boiler plate to two lines. With the ) Tj 0 0 .501961 rg (clap ) Tj 0 0 0 rg (module all you need to write is) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 511.8236 cm
+Q
+q
+1 0 0 1 62.69291 426.6236 cm
+0 0 0 rg
+BT /F5 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm T* ET
+q
+1 0 0 1 20 0 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 448.6898 84 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 65.71 Tm /F3 10 Tf 12 TL (def main\(dsn\):) Tj T* ( "Do something with the database") Tj T* ( print\(dsn\)) Tj T* ( ) Tj T* (if __name__ == '__main__':) Tj T* ( import clap; clap.call\(main\)) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 426.6236 cm
+Q
+q
+1 0 0 1 62.69291 396.6236 cm
+q
+BT 1 0 0 1 0 16.82 Tm .929986 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj 0 0 .501961 rg (clap ) Tj 0 0 0 rg (module provides for free \(actually the work is done by the underlying ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (module\) a nice) Tj T* 0 Tw (usage message:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 279.4236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 108 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 89.71 Tm /F3 10 Tf 12 TL ($ python example3.py -h) Tj T* (usage: example3.py [-h] dsn) Tj T* T* (positional arguments:) Tj T* ( dsn) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 259.4236 cm
+q
+BT 1 0 0 1 0 4.82 Tm 12 TL /F1 10 Tf 0 0 0 rg (This is only the tip of the iceberg: ) Tj 0 0 .501961 rg (clap ) Tj 0 0 0 rg (is able to do much more than that.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 226.4236 cm
+q
+BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Positional default arguments) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 208.4236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (I have encountered this use case at work hundreds of times:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 202.4236 cm
+Q
+q
+1 0 0 1 62.69291 76.86614 cm
+0 0 0 rg
+BT /F5 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm T* ET
+q
+1 0 0 1 20 0 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 442.6898 108 re B*
+Q
+q
+BT 1 0 0 1 0 89.71 Tm 12 TL /F3 10 Tf 0 0 0 rg (from datetime import datetime) Tj T* T* (def main\(dsn, table='product', today=datetime.today\(\)\):) Tj T* ( "Do something on the database") Tj T* ( print\(dsn, table, today\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( import sys) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+Q
+Q
+
+endstream
+
+endobj
+% 'R71': class PDFStream
+71 0 obj
+% page stream
+<< /Length 4390 >>
+stream
+1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 679.8236 cm
+0 0 0 rg
+BT /F5 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm T* ET
+q
+1 0 0 1 20 0 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 442.6898 84 re B*
+Q
+q
+BT 1 0 0 1 0 65.71 Tm 12 TL /F3 10 Tf 0 0 0 rg ( args = sys.argv[1:]) Tj T* ( if not args:) Tj T* ( sys.exit\('usage: python %s dsn' % sys.argv[0]\)) Tj T* ( elif len\(args\) ) Tj (>) Tj ( 2:) Tj T* ( sys.exit\('Unrecognized arguments: %s' % ' '.join\(argv[2:]\)\)) Tj T* ( main\(*args\)) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 679.8236 cm
+Q
+q
+1 0 0 1 62.69291 661.8236 cm
+q
+BT 1 0 0 1 0 4.82 Tm 12 TL /F1 10 Tf 0 0 0 rg (With ) Tj 0 0 .501961 rg (clap ) Tj 0 0 0 rg (the entire ) Tj /F3 10 Tf (__main__ ) Tj /F1 10 Tf (block reduces to the usual two lines:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 616.6236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 36 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 17.71 Tm /F3 10 Tf 12 TL (if __name__ == '__main__':) Tj T* ( import clap; clap.call\(main\)) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 596.6236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (In other words, six lines of boilerplate have been removed, and I have the usage message for free:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 467.4236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 120 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 101.71 Tm /F3 10 Tf 12 TL (usage: example4_.py [-h] dsn [table] [today]) Tj T* T* (positional arguments:) Tj T* ( dsn) Tj T* ( table) Tj T* ( today) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 435.4236 cm
+q
+BT 1 0 0 1 0 16.82 Tm .396235 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (clap ) Tj 0 0 0 rg (manages transparently even the case when you want to pass a variable number of arguments. Here) Tj T* 0 Tw (is an example, a script running on a database a series of ) Tj /F3 10 Tf (.sql ) Tj /F1 10 Tf (scripts:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 429.4236 cm
+Q
+q
+1 0 0 1 62.69291 272.2236 cm
+0 0 0 rg
+BT /F5 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm T* ET
+q
+1 0 0 1 20 0 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 448.6898 156 re B*
+Q
+q
+BT 1 0 0 1 0 137.71 Tm 12 TL /F3 10 Tf 0 0 0 rg (from datetime import datetime) Tj T* T* (def main\(dsn, *scripts\):) Tj T* ( "Run the given scripts on the database") Tj T* ( for script in scripts:) Tj T* ( print\('executing %s' % script\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( import sys) Tj T* ( if len\(sys.argv\) ) Tj (<) Tj ( 2:) Tj T* ( sys.exit\('usage: python %s dsn script.sql ...' % sys.argv[0]\)) Tj T* ( main\(sys.argv[1:]\)) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 272.2236 cm
+Q
+q
+1 0 0 1 62.69291 242.2236 cm
+q
+BT 1 0 0 1 0 16.82 Tm 6.923059 Tw 12 TL /F1 10 Tf 0 0 0 rg (Using ) Tj 0 0 .501961 rg (clap) Tj 0 0 0 rg (, you can just replace the ) Tj /F3 10 Tf (__main__ ) Tj /F1 10 Tf (block with the usual ) Tj /F3 10 Tf (import clap;) Tj T* 0 Tw (clap.call\(main\) ) Tj /F1 10 Tf (and you get the following usage message:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 125.0236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 108 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 89.71 Tm /F3 10 Tf 12 TL (usage: example7.py [-h] dsn [scripts [scripts ...]]) Tj T* T* (positional arguments:) Tj T* ( dsn) Tj T* ( scripts) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 81.02362 cm
+q
+BT 1 0 0 1 0 28.82 Tm .92881 Tw 12 TL /F1 10 Tf 0 0 0 rg (The examples here should have made clear that ) Tj /F4 10 Tf (clap is able to figure out the command line arguments) Tj T* 0 Tw .928488 Tw (parser to use from the signature of the main function) Tj /F1 10 Tf (. This is the whole idea behind ) Tj 0 0 .501961 rg (clap) Tj 0 0 0 rg (: if my intent is) Tj T* 0 Tw (clear, let's the machine takes care of the details.) Tj T* ET
+Q
+Q
+
+endstream
+
+endobj
+% 'R72': class PDFStream
+72 0 obj
+% page stream
+<< /Length 5415 >>
+stream
+1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 744.0236 cm
+q
+BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Options and flags) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 654.0236 cm
+q
+BT 1 0 0 1 0 76.82 Tm .046098 Tw 12 TL /F1 10 Tf 0 0 0 rg (It is surprising how few command line scripts with options I have written over the years \(probably less than) Tj T* 0 Tw 1.165984 Tw (a hundred\), compared to the number of scripts with positional arguments \(I certainly have written more) Tj T* 0 Tw 1.221163 Tw (than a thousand of them\). Still, this use case is quite common and cannot be neglected. The standard) Tj T* 0 Tw .446098 Tw (library modules \(all of them\) are quite verbose when it comes to specifying the options and frankly I have) Tj T* 0 Tw .732339 Tw (never used them directly. Instead, I have always relied on an old recipe of mine, the ) Tj 0 0 .501961 rg (optionparse ) Tj 0 0 0 rg (recipe,) Tj T* 0 Tw 1.32784 Tw (which provides a convenient wrapper over ) Tj 0 0 .501961 rg (optionparse) Tj 0 0 0 rg (. Alternatively, in the simplest cases, I have just) Tj T* 0 Tw (performed the parsing by hand, instead of manually building a suitable OptionParser.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 612.0236 cm
+q
+BT 1 0 0 1 0 28.82 Tm .476098 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (clap ) Tj 0 0 0 rg (is inspired to the ) Tj 0 0 .501961 rg (optionparse ) Tj 0 0 0 rg (recipe, in the sense that it delivers the programmer from the burden of) Tj T* 0 Tw .011488 Tw (writing the parser, but is less of a hack: instead of extracting the parser from the docstring of the module, it) Tj T* 0 Tw (extracts it from the signature of the ) Tj /F3 10 Tf (main ) Tj /F1 10 Tf (function.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 582.0236 cm
+q
+BT 1 0 0 1 0 16.82 Tm .319987 Tw 12 TL /F1 10 Tf 0 0 0 rg (The idea comes from the ) Tj /F4 10 Tf (function annotations ) Tj /F1 10 Tf (concept, a new feature of Python 3. An example is worth a) Tj T* 0 Tw (thousand words, so here it is:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 576.0236 cm
+Q
+q
+1 0 0 1 62.69291 478.8236 cm
+0 0 0 rg
+BT /F5 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm T* ET
+q
+1 0 0 1 20 0 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 448.6898 96 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 77.71 Tm /F3 10 Tf 12 TL (def main\(command: \("SQL query", 'option', 'c'\), dsn\):) Tj T* ( if command:) Tj T* ( print\('executing %s on %s' % \(command, dsn\)\)) Tj T* ( # ...) Tj T* T* (if __name__ == '__main__':) Tj T* ( import clap; clap.call\(main\)) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 478.8236 cm
+Q
+q
+1 0 0 1 62.69291 412.8236 cm
+q
+BT 1 0 0 1 0 52.82 Tm .789983 Tw 12 TL /F1 10 Tf 0 0 0 rg (As you see, the argument ) Tj /F3 10 Tf (command ) Tj /F1 10 Tf (has been annotated with the tuple ) Tj /F3 10 Tf (\("SQL query", 'option',) Tj T* 0 Tw .593876 Tw ('c'\)) Tj /F1 10 Tf (: the first string is the help string which will appear in the usage message, whereas the second and) Tj T* 0 Tw .144988 Tw (third strings tell ) Tj 0 0 .501961 rg (clap ) Tj 0 0 0 rg (that ) Tj /F3 10 Tf (command ) Tj /F1 10 Tf (is an option and that it can be abbreviated with the letter ) Tj /F3 10 Tf (c) Tj /F1 10 Tf (. Of course,) Tj T* 0 Tw 1.543735 Tw (it also possible to use the long option format, by prefixing the option with ) Tj /F3 10 Tf (--command=) Tj /F1 10 Tf (. The resulting) Tj T* 0 Tw (usage message is the following:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 271.6236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 132 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 113.71 Tm /F3 10 Tf 12 TL ($ python3 example8.py -h) Tj T* (usage: example8.py [-h] [-c COMMAND] dsn) Tj T* T* (positional arguments:) Tj T* ( dsn) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ( -c COMMAND, --command COMMAND) Tj T* ( SQL query) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 251.6236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (Here are two examples of usage:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 170.4236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 72 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 53.71 Tm /F3 10 Tf 12 TL ($ python3 example8.py -c"select * from table" dsn) Tj T* (executing select * from table on dsn) Tj T* T* ($ python3 example8.py --command="select * from table" dsn) Tj T* (executing select * from table on dsn) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 150.4236 cm
+q
+BT 1 0 0 1 0 4.82 Tm 12 TL /F1 10 Tf 0 0 0 rg (Notice that if the option is not passed, the variable ) Tj /F3 10 Tf (command ) Tj /F1 10 Tf (will get the value ) Tj /F3 10 Tf (None) Tj /F1 10 Tf (.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 132.4236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (Even positional argument can be annotated:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 87.22362 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 36 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 17.71 Tm /F3 10 Tf 12 TL (def main\(command: \("SQL query", 'option', 'c'\),) Tj T* ( dsn: \("Database dsn", 'positional', None\)\):) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+
+endstream
+
+endobj
+% 'R73': class PDFStream
+73 0 obj
+% page stream
+<< /Length 5431 >>
+stream
+1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 739.8236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 24 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 5.71 Tm /F3 10 Tf 12 TL ( ...) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 695.8236 cm
+q
+BT 1 0 0 1 0 28.82 Tm 3.203318 Tw 12 TL /F1 10 Tf 0 0 0 rg (Of course explicit is better than implicit, an no special cases are special enough, but sometimes) Tj T* 0 Tw .64061 Tw (practicality beats purity, so ) Tj 0 0 .501961 rg (clap ) Tj 0 0 0 rg (is smart enough to convert help messages into tuples internally; in other) Tj T* 0 Tw (words, you can just write "Database dsn" instead of ) Tj /F3 10 Tf (\("Database dsn", 'positional', None\)) Tj /F1 10 Tf (:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 650.6236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 36 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 17.71 Tm /F3 10 Tf 12 TL (def main\(command: \("SQL query", 'option', 'c'\), dsn: "Database dsn"\):) Tj T* ( ...) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 618.6236 cm
+q
+BT 1 0 0 1 0 16.82 Tm .171988 Tw 12 TL /F1 10 Tf 0 0 0 rg (In both cases the usage message will show a nice help string on the right hand side of the ) Tj /F3 10 Tf (dsn ) Tj /F1 10 Tf (positional) Tj T* 0 Tw (argument. varargs \(starred-arguments\) can also be annotated:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 573.4236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 36 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 17.71 Tm /F3 10 Tf 12 TL (def main\(dsn: "Database dsn", *scripts: "SQL scripts"\):) Tj T* ( ...) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 553.4236 cm
+q
+BT 1 0 0 1 0 4.82 Tm 12 TL /F1 10 Tf 0 0 0 rg (is a valid signature for ) Tj 0 0 .501961 rg (clap) Tj 0 0 0 rg (, which will recognize the help strings for both ) Tj /F3 10 Tf (dsn ) Tj /F1 10 Tf (and ) Tj /F3 10 Tf (scripts) Tj /F1 10 Tf (:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 496.2236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 48 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 29.71 Tm /F3 10 Tf 12 TL (positional arguments:) Tj T* ( dsn Database dsn) Tj T* ( scripts SQL scripts) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 464.2236 cm
+q
+BT 1 0 0 1 0 16.82 Tm .765868 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (clap ) Tj 0 0 0 rg (also recognizes flags, i.e. boolean options which are ) Tj /F3 10 Tf (True ) Tj /F1 10 Tf (if they are passed to the command line) Tj T* 0 Tw (and ) Tj /F3 10 Tf (False ) Tj /F1 10 Tf (if they are absent. Here is an example:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 419.0236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 36 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 17.71 Tm /F3 10 Tf 12 TL ($ python3 example9.py -v dsn) Tj T* (connecting to dsn) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 289.8236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 120 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 101.71 Tm /F3 10 Tf 12 TL ($ python3 example9.py -h) Tj T* (usage: example9.py [-h] [-v] dsn) Tj T* T* (positional arguments:) Tj T* ( dsn connection string) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ( -v, --verbose prints more info) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 221.8236 cm
+q
+BT 1 0 0 1 0 52.82 Tm 5.832651 Tw 12 TL /F1 10 Tf 0 0 0 rg (For consistency with the way the usage message is printed, I suggest you to follow the) Tj T* 0 Tw 1.686905 Tw (Flag-Option-Positional \(FOP\) convention: in the ) Tj /F3 10 Tf (main ) Tj /F1 10 Tf (function write first the flag arguments, then the) Tj T* 0 Tw .404692 Tw (option arguments and finally the positional arguments. This is just a convention and you are not forced to) Tj T* 0 Tw .478409 Tw (use it, but it makes sense to put the position arguments at the end, since they may be default arguments) Tj T* 0 Tw (and varargs.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 188.8236 cm
+q
+BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (clap for people not using Python 3) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 122.8236 cm
+q
+BT 1 0 0 1 0 52.82 Tm .12999 Tw 12 TL /F1 10 Tf 0 0 0 rg (I do not use Python 3. At work we are just starting to think about migrating to Python 2.6. I think it will take) Tj T* 0 Tw 1.269988 Tw (years before we even think to migrate to Python 3. I am pretty much sure most Pythonistas are in the) Tj T* 0 Tw 2.089984 Tw (same situation. Therefore ) Tj 0 0 .501961 rg (clap ) Tj 0 0 0 rg (provides a way to work with function annotations even in Python 2.X) Tj T* 0 Tw 1.352339 Tw (\(including Python 2.3\). There is no magic involved; you just need to add the annotations by hand. For) Tj T* 0 Tw (instance) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 89.62362 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 24 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 5.71 Tm /F3 10 Tf 12 TL (def main\(dsn: "Database dsn", *scripts: "SQL scripts"\):) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+
+endstream
+
+endobj
+% 'R74': class PDFStream
+74 0 obj
+% page stream
+<< /Length 5574 >>
+stream
+1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 753.0236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (becomes:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 671.8236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 72 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 53.71 Tm /F3 10 Tf 12 TL (def main\(dsn, *scripts\):) Tj T* ( ...) Tj T* (main.__annotations__ = dict\() Tj T* (dsn="Database dsn",) Tj T* (scripts="SQL scripts"\)) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 627.8236 cm
+q
+BT 1 0 0 1 0 28.82 Tm .412765 Tw 12 TL /F1 10 Tf 0 0 0 rg (One should be careful to much the keys of the annotations dictionary with the names of the arguments in) Tj T* 0 Tw 3.347485 Tw (the annotated function; for lazy people with Python 2.4 available the simplest way is to use the) Tj T* 0 Tw /F3 10 Tf (clap.annotations ) Tj /F1 10 Tf (decorator that performs the check for you.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 546.6236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 72 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 53.71 Tm /F3 10 Tf 12 TL (@annotations\() Tj T* (dsn="Database dsn",) Tj T* (scripts="SQL scripts"\)) Tj T* (def main\(dsn, *scripts\):) Tj T* ( ...) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 514.6236 cm
+q
+BT 1 0 0 1 0 16.82 Tm 1.422164 Tw 12 TL /F1 10 Tf 0 0 0 rg (In the rest of this article I will assume that you are using Python 2.X with ) Tj /F3 10 Tf (X >) Tj (= 4 ) Tj /F1 10 Tf (and I will use the) Tj T* 0 Tw /F3 10 Tf (clap.annotations ) Tj /F1 10 Tf (decorator.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 481.6236 cm
+q
+BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Advanced usage) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 427.6236 cm
+q
+BT 1 0 0 1 0 40.82 Tm .115703 Tw 12 TL /F1 10 Tf 0 0 0 rg (One of the goals of clap is to have a learning curve of ) Tj /F4 10 Tf (minutes) Tj /F1 10 Tf (, compared to the learning curve of ) Tj /F4 10 Tf (hours ) Tj /F1 10 Tf (of) Tj T* 0 Tw .196098 Tw 0 0 .501961 rg (argparse) Tj 0 0 0 rg (. That does not mean that I have removed all the advanced features of ) Tj 0 0 .501961 rg (argparse) Tj 0 0 0 rg (. Actually a lot of) Tj T* 0 Tw 1.529269 Tw 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (power persists in ) Tj 0 0 .501961 rg (clap) Tj 0 0 0 rg (: in particular, the ) Tj /F3 10 Tf (type) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (choices ) Tj /F1 10 Tf (and ) Tj /F3 10 Tf (metavar ) Tj /F1 10 Tf (concepts are there.) Tj T* 0 Tw (Until now, I have only showed simple annotations, but in general an annotation is a 5-tuple of the form) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 421.6236 cm
+Q
+q
+1 0 0 1 62.69291 409.6236 cm
+0 0 0 rg
+BT /F5 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm T* ET
+q
+1 0 0 1 20 0 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 5.71 Tm /F3 10 Tf 12 TL (\(help, kind, abbrev, type, choices, metavar\)) Tj T* ET
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 409.6236 cm
+Q
+q
+1 0 0 1 62.69291 367.6236 cm
+q
+BT 1 0 0 1 0 28.82 Tm 3.38811 Tw 12 TL /F1 10 Tf 0 0 0 rg (where ) Tj /F3 10 Tf (help ) Tj /F1 10 Tf (is the help message, ) Tj /F3 10 Tf (kind ) Tj /F1 10 Tf (is one of {"flag", "option ", "positional"}, ) Tj /F3 10 Tf (abbrev ) Tj /F1 10 Tf (is a) Tj T* 0 Tw 2.203735 Tw (one-character string, ) Tj /F3 10 Tf (type ) Tj /F1 10 Tf (is callable taking a string in input, choices is a sequence of values and) Tj T* 0 Tw /F3 10 Tf (metavar ) Tj /F1 10 Tf (is a string.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 337.6236 cm
+q
+BT 1 0 0 1 0 16.82 Tm .006654 Tw 12 TL /F3 10 Tf 0 0 0 rg (type ) Tj /F1 10 Tf (is used to automagically convert the arguments from string to any Python type; by default there is no) Tj T* 0 Tw (convertion i.e. ) Tj /F3 10 Tf (type=None) Tj /F1 10 Tf (.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 307.6236 cm
+q
+BT 1 0 0 1 0 16.82 Tm 2.904692 Tw 12 TL /F3 10 Tf 0 0 0 rg (choices ) Tj /F1 10 Tf (is used to restrict the number of the valid options; by default there is no restriction i.e.) Tj T* 0 Tw /F3 10 Tf (choices=None) Tj /F1 10 Tf (.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 277.6236 cm
+q
+BT 1 0 0 1 0 16.82 Tm 1.071751 Tw 12 TL /F3 10 Tf 0 0 0 rg (metavar ) Tj /F1 10 Tf (is used to change the argument name in the usage message \(and only there\); by default the) Tj T* 0 Tw (metavar is equal to the name of the argument.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 259.6236 cm
+q
+BT 1 0 0 1 0 4.82 Tm 12 TL /F1 10 Tf 0 0 0 rg (Here is an example showing all of such features \(shamelessly stolen from the ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (documentation\):) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 253.6236 cm
+Q
+q
+1 0 0 1 62.69291 84.21376 cm
+0 0 0 rg
+BT /F5 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm T* ET
+q
+1 0 0 1 20 0 cm
+q
+q
+.934933 0 0 .934933 0 0 cm
+q
+1 0 0 1 6.6 7.059329 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 480 180 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 161.71 Tm /F3 10 Tf 12 TL (import clap) Tj T* T* (@clap.annotations\() Tj T* (operator=\("The name of an operator", 'positional', None, str, ['add', 'mul']\),) Tj T* (numbers=\("A number", 'positional', None, float, None, "n"\)\)) Tj T* (def main\(operator, *numbers\):) Tj T* ( op = getattr\(float, '__%s__' % operator\)) Tj T* ( result = dict\(add=0.0, mul=1.0\)[operator]) Tj T* ( for n in numbers:) Tj T* ( result = op\(result, n\)) Tj T* ( print\(result\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( clap.call\(main\)) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 84.21376 cm
+Q
+
+endstream
+
+endobj
+% 'R75': class PDFStream
+75 0 obj
+% page stream
+<< /Length 5160 >>
+stream
+1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 753.0236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (Here is the usage for the script:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 635.8236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 108 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 89.71 Tm /F3 10 Tf 12 TL (usage: example10.py [-h] {add,mul} [n [n ...]]) Tj T* T* (positional arguments:) Tj T* ( {add,mul} The name of an operator) Tj T* ( n A number) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 615.8236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (Here are a couple of examples of use:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 522.4849 cm
+q
+q
+.87797 0 0 .87797 0 0 cm
+q
+1 0 0 1 6.6 7.517338 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 534 96 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 77.71 Tm /F3 10 Tf 12 TL ($ python example10.py add 1 2 3 4) Tj T* (10.0) Tj T* ($ python example10.py mul 1 2 3 4) Tj T* (24.0) Tj T* ($ python example10.py ad 1 2 3 4 # a mispelling error) Tj T* (usage: example10.py [-h] {add,mul} [n [n ...]]) Tj T* (example10.py: error: argument operator: invalid choice: 'ad' \(choose from 'add', 'mul'\)) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 489.4849 cm
+q
+BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (A few notes on the underlying implementation) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 423.4849 cm
+q
+BT 1 0 0 1 0 52.82 Tm .928488 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (clap ) Tj 0 0 0 rg (relies on a ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (for all of the heavy lifting work. It is possible to pass options to the underlying) Tj T* 0 Tw .03816 Tw 0 0 .501961 rg (ArgumentParser ) Tj 0 0 0 rg (object \(currently it accepts the default arguments ) Tj /F3 10 Tf (prog) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (usage) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (description) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (epilog) Tj /F1 10 Tf (,) Tj T* 0 Tw 18.21744 Tw /F3 10 Tf (version) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (parents) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (formatter_class) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (prefix_chars) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (fromfile_prefix_chars) Tj /F1 10 Tf (,) Tj T* 0 Tw 1.035976 Tw /F3 10 Tf (argument_default) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (conflict_handler) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (add_help) Tj /F1 10 Tf (\) simply by setting such attributes on the ) Tj /F3 10 Tf (main) Tj T* 0 Tw /F1 10 Tf (function. For instance) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 354.2849 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 60 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 41.71 Tm /F3 10 Tf 12 TL (def main\(...\):) Tj T* ( pass) Tj T* T* (main.add_help = False) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 298.2849 cm
+q
+BT 1 0 0 1 0 40.82 Tm 1.256457 Tw 12 TL /F1 10 Tf 0 0 0 rg (disable the recognition of the help flag ) Tj /F3 10 Tf (-h, --help) Tj /F1 10 Tf (. This is not particularly elegant, but I assume the) Tj T* 0 Tw .274751 Tw (typical user of ) Tj 0 0 .501961 rg (clap ) Tj 0 0 0 rg (will be happy with the default message and would not want to go at this level of detail;) Tj T* 0 Tw 1.463876 Tw (still it is possible if she wants to. I redirect the interested readers to the documentation of ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (to) Tj T* 0 Tw (understand the meaning of the various options.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 268.2849 cm
+q
+BT 1 0 0 1 0 16.82 Tm .154269 Tw 12 TL /F1 10 Tf 0 0 0 rg (If you want to access the underlying ) Tj 0 0 .501961 rg (ArgumentParser ) Tj 0 0 0 rg (object, you can use the ) Tj /F3 10 Tf (clap.parser_from ) Tj /F1 10 Tf (utility) Tj T* 0 Tw (function:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 151.0849 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 108 re B*
+Q
+q
+BT 1 0 0 1 0 89.71 Tm 12 TL /F3 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( import clap) Tj T* (>) Tj (>) Tj (>) Tj ( def main\(arg\):) Tj T* (... pass) Tj T* (...) Tj T* (>) Tj (>) Tj (>) Tj ( print clap.parser_from\(main\)) Tj T* (ArgumentParser\(prog='', usage=None, description=None, version=None,) Tj T* (formatter_class=) Tj (<) Tj (class 'argparse.HelpFormatter') Tj (>) Tj (, conflict_handler='error',) Tj T* (add_help=True\)) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 131.0849 cm
+q
+BT 1 0 0 1 0 4.82 Tm 12 TL /F1 10 Tf 0 0 0 rg (I use ) Tj /F3 10 Tf (clap.parser_from ) Tj /F1 10 Tf (in the unit tests of the module, but regular users should never need to use it.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 101.0849 cm
+q
+BT 1 0 0 1 0 16.82 Tm .541318 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (clap ) Tj 0 0 0 rg (uses an ) Tj /F3 10 Tf (Annotation ) Tj /F1 10 Tf (class to convert the raw annotations in the function signature into annotation) Tj T* 0 Tw (objects, i.e. objects with six attributes ) Tj /F3 10 Tf (help, kind, short, type, choices, metavar) Tj /F1 10 Tf (.) Tj T* ET
+Q
+Q
+
+endstream
+
+endobj
+% 'R76': class PDFStream
+76 0 obj
+% page stream
+<< /Length 3056 >>
+stream
+1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 717.0236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 40.82 Tm /F1 10 Tf 12 TL 1.682126 Tw (Advanced users can implement their own annotation objects. Since the special case of no annotation) Tj T* 0 Tw 1.088735 Tw (must be taken care of, the annotation factory must return a suitable default annotation object where no) Tj T* 0 Tw .18811 Tw (arguments are passed in input. Here is an example of how you could implement annotations for positional) Tj T* 0 Tw (arguments:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 711.0236 cm
+Q
+q
+1 0 0 1 62.69291 601.8236 cm
+0 0 0 rg
+BT /F5 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm T* ET
+q
+1 0 0 1 20 0 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 448.6898 108 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 89.71 Tm /F3 10 Tf 12 TL (class Positional\(object\):) Tj T* ( def __init__\(self, help='', type=None, choices=None, metavar=None\):) Tj T* ( self.help = help) Tj T* ( self.kind = 'positional') Tj T* ( self.abbrev = None) Tj T* ( self.type = type) Tj T* ( self.choices = choices) Tj T* ( self.metavar = metavar) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 601.8236 cm
+Q
+q
+1 0 0 1 62.69291 583.8236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (You can use such annotations objects as follows:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 577.8236 cm
+Q
+q
+1 0 0 1 62.69291 420.6236 cm
+0 0 0 rg
+BT /F5 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm T* ET
+q
+1 0 0 1 20 0 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 448.6898 156 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 137.71 Tm /F3 10 Tf 12 TL (import clap) Tj T* (from annotations import Positional) Tj T* T* (@clap.annotations\() Tj T* ( i=Positional\("This is an int", int\),) Tj T* ( n=Positional\("This is a float", float\),) Tj T* ( rest=Positional\("Other arguments"\)\)) Tj T* (def main\(i, n, *rest\):) Tj T* ( print\(i, n, rest\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( import clap; clap.call\(main\)) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 420.6236 cm
+Q
+q
+1 0 0 1 62.69291 402.6236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (Here is the usage message:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 273.4236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 120 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 101.71 Tm /F3 10 Tf 12 TL (usage: example11.py [-h] i n [rest [rest ...]]) Tj T* T* (positional arguments:) Tj T* ( i This is an int) Tj T* ( n This is a float) Tj T* ( rest Other arguments) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 253.4236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (You can go on and define Option and Flag classes, if you like.) Tj T* ET
+Q
+Q
+
+endstream
+
+endobj
+% 'R77': class PDFPageLabels
+77 0 obj
+% Document Root
+<< /Nums [ 0
+ 78 0 R
+ 1
+ 79 0 R
+ 2
+ 80 0 R
+ 3
+ 81 0 R
+ 4
+ 82 0 R
+ 5
+ 83 0 R
+ 6
+ 84 0 R
+ 7
+ 85 0 R ] >>
+endobj
+% 'R78': class PDFPageLabel
+78 0 obj
+% None
+<< /S /D
+ /St 1 >>
+endobj
+% 'R79': class PDFPageLabel
+79 0 obj
+% None
+<< /S /D
+ /St 2 >>
+endobj
+% 'R80': class PDFPageLabel
+80 0 obj
+% None
+<< /S /D
+ /St 3 >>
+endobj
+% 'R81': class PDFPageLabel
+81 0 obj
+% None
+<< /S /D
+ /St 4 >>
+endobj
+% 'R82': class PDFPageLabel
+82 0 obj
+% None
+<< /S /D
+ /St 5 >>
+endobj
+% 'R83': class PDFPageLabel
+83 0 obj
+% None
+<< /S /D
+ /St 6 >>
+endobj
+% 'R84': class PDFPageLabel
+84 0 obj
+% None
+<< /S /D
+ /St 7 >>
+endobj
+% 'R85': class PDFPageLabel
+85 0 obj
+% None
+<< /S /D
+ /St 8 >>
+endobj
+xref
+0 86
+0000000000 65535 f
+0000000113 00000 n
+0000000260 00000 n
+0000000425 00000 n
+0000000612 00000 n
+0000000872 00000 n
+0000001134 00000 n
+0000001382 00000 n
+0000001630 00000 n
+0000001894 00000 n
+0000002158 00000 n
+0000002423 00000 n
+0000002688 00000 n
+0000002938 00000 n
+0000003191 00000 n
+0000003366 00000 n
+0000003619 00000 n
+0000003801 00000 n
+0000003969 00000 n
+0000004369 00000 n
+0000004631 00000 n
+0000004895 00000 n
+0000005145 00000 n
+0000005411 00000 n
+0000005677 00000 n
+0000005942 00000 n
+0000006192 00000 n
+0000006443 00000 n
+0000006822 00000 n
+0000007088 00000 n
+0000007354 00000 n
+0000007619 00000 n
+0000007870 00000 n
+0000008213 00000 n
+0000008501 00000 n
+0000008785 00000 n
+0000009051 00000 n
+0000009339 00000 n
+0000009590 00000 n
+0000009942 00000 n
+0000010207 00000 n
+0000010473 00000 n
+0000010739 00000 n
+0000010990 00000 n
+0000011333 00000 n
+0000011583 00000 n
+0000011833 00000 n
+0000012083 00000 n
+0000012345 00000 n
+0000012580 00000 n
+0000012932 00000 n
+0000013197 00000 n
+0000013447 00000 n
+0000013734 00000 n
+0000014000 00000 n
+0000014250 00000 n
+0000014537 00000 n
+0000014788 00000 n
+0000015143 00000 n
+0000015424 00000 n
+0000015583 00000 n
+0000015849 00000 n
+0000015974 00000 n
+0000016165 00000 n
+0000016369 00000 n
+0000016562 00000 n
+0000016772 00000 n
+0000016962 00000 n
+0000017151 00000 n
+0000017322 00000 n
+0000023577 00000 n
+0000028311 00000 n
+0000032802 00000 n
+0000038318 00000 n
+0000043850 00000 n
+0000049525 00000 n
+0000054786 00000 n
+0000057947 00000 n
+0000058131 00000 n
+0000058208 00000 n
+0000058285 00000 n
+0000058362 00000 n
+0000058439 00000 n
+0000058516 00000 n
+0000058593 00000 n
+0000058670 00000 n
+trailer
+<< /ID
+ % ReportLab generated PDF document -- digest (http://www.reportlab.com)
+ [(\261\274,t\312\324D[\217\346f\222\0103;\021) (\261\274,t\312\324D[\217\346f\222\0103;\021)]
+
+ /Info 60 0 R
+ /Root 59 0 R
+ /Size 86 >>
+startxref
+58717
+%%EOF
diff --git a/clap/article/article.txt b/clap/article/article.txt new file mode 100644 index 0000000..79cb834 --- /dev/null +++ b/clap/article/article.txt @@ -0,0 +1,442 @@ +The Easiest Command Line Arguments Parser in the World +================================================================ + +There is no want of command line arguments parsers in Python +world. The standard library alone contains three different modules for +the parsing of command line options: 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 have a non-zero learning +curve and a certain verbosity. + +Enters clap_. clap_ is designed to be `downwardly scalable`_, i.e. to +be trivially simple to use for trivial use cases, and to have a +next-to-zero learning curve. Technically clap_ is just a simple +wrapper over argparse_, hiding most of the complexity while retaining +most of the power. clap_ is surprisingly scalable upwards even for +non-trivial use cases, but it is not intended to be an industrial +strength command line parsing module. Its capabilities are limited by +design. If you need more power, by all means use the parsing modules +in the standard library. Still, I have been using Python for 8 years +and never once I had to use the full power of the standard library +modules. + +Actually I am pretty much convinced that features provided by ``clap`` +are more than enough for 99.9% of the typical use cases of a scripter +working in a Unix-like environment. I am targetting here programmers, +sys-admins, scientists and in general people writing throw-away scripts +for themselves, choosing to use a command line interface because it is +the quick and simple. Such users are not interested in features, +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 +hand. Unfortunately, the current modules in the standard library +forces them to go the hard way. They are designed to implement +power user tools for programmers or system administrators, and +they have a non-trivial learning curve. + +The importance of scaling down +------------------------------------ + +An ex-coworker of mine, David Welton, once wrote a nice article about +the importance of `scaling down`_: most people are concerned with the +possibility of scaling up, but we should also be concerned with the +issue of scaling down: in other worlds, simple things should be kept +simple. To be concrete, let me start with the simplest possible +thing: a script that takes a single argument and does something to it. +It cannot get more trivial than that (discarding the possibility of +a script without command line arguments, where there is nothing to parse), +nevertheless 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 never been happy. Here is +a typical example of code I have been writing by hand for years: + + .. include:: example1.py + :literal: + +As you see the whole ``if __name__ == '__main__'`` block (nine lines) is +essentially boilerplate that should not exists. Actually I think the +Python language should recognize the main function and perform trivial +arguments parsing behind the scenes; 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 +trivial. Unfortunately the standard library modules do not help for +this use case, which may be trivial, but it is still incredibly +common. Using getopt_ and optparse_ does not help, since they are +intended to manage options and not positional arguments; the argparse_ +module helps a bit and it is able to reduce the boilerplate from nine +lines to six lines: + + .. include:: example2.py + :literal: + +However saving three lines does not justify introducing the external +dependency: most people will not switch Python 2.7, which at the time of +this writing is just about to be released, for many years. +Moreover, it just feels too complex to instantiate a class and to +define a parser by hand for such a trivial task. + +The clap_ module is designed to manage well such use cases, and it is able +to reduce the original nine lines of boiler plate to two lines. With the +clap_ module all you need to write is + + .. include:: example3.py + :literal: + +The clap_ module provides for free (actually the work is done by the +underlying argparse_ module) a nice usage message:: + + $ python example3.py -h + usage: example3.py [-h] dsn + + positional arguments: + dsn + + optional arguments: + -h, --help show this help message and exit + +This is only the tip of the iceberg: clap_ is able to do much more than that. + +Positional default arguments +-------------------------------------------------- + +I have encountered this use case at work hundreds of times: + + .. include:: example4.py + :literal: + +With clap_ the entire ``__main__`` block reduces to the usual two lines:: + + if __name__ == '__main__': + import clap; clap.call(main) + +In other words, six lines of boilerplate have been removed, and I have +the usage message for free:: + + usage: example4_.py [-h] dsn [table] [today] + + positional arguments: + dsn + table + today + + optional arguments: + -h, --help show this help message and exit + +clap_ manages transparently even the case when you want to pass a +variable number of arguments. Here is an example, a script running +on a database a series of ``.sql`` scripts: + + .. include:: example6.py + :literal: + +Using clap_, you can just replace the ``__main__`` block with the usual +``import clap; clap.call(main)`` and you get the following usage message:: + + usage: example7.py [-h] dsn [scripts [scripts ...]] + + positional arguments: + dsn + scripts + + optional arguments: + -h, --help show this help message and exit + +The examples here should have made clear that *clap is able to figure out +the command line arguments parser to use from the signature of the main +function*. This is the whole idea behind clap_: if my intent is clear, +let's the machine takes care of the details. + +Options and flags +--------------------------------------- + +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 certainly have written more than a thousand of them). +Still, this use case is quite common and 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 optionparse_ recipe, which provides a +convenient wrapper over optionparse_. Alternatively, in the simplest cases, +I have just performed the parsing by hand, instead of manually building +a suitable OptionParser. + +clap_ 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. + +The idea comes from the `function annotations` concept, a new +feature of Python 3. An example is worth a thousand words, so here +it is: + + .. include:: example8.py + :literal: + +As you see, 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, whereas the +second and third strings tell clap_ that ``command`` is an option and that +it can be abbreviated with the letter ``c``. Of course, it also +possible to use the long option format, by prefixing the option +with ``--command=``. The resulting usage message is the following:: + + $ python3 example8.py -h + usage: example8.py [-h] [-c COMMAND] dsn + + positional arguments: + dsn + + optional arguments: + -h, --help show this help message and exit + -c COMMAND, --command COMMAND + SQL query + +Here are two examples of usage:: + + $ python3 example8.py -c"select * from table" dsn + executing select * from table on dsn + + $ python3 example8.py --command="select * from table" dsn + executing select * from table on dsn + +Notice that if the option is not passed, the variable ``command`` +will get the value ``None``. + +Even positional argument can be annotated:: + + def main(command: ("SQL query", 'option', 'c'), + dsn: ("Database dsn", 'positional', None)): + ... + +Of course explicit is better than implicit, an no special cases are +special enough, but sometimes practicality beats purity, so clap_ is +smart enough to convert help messages into tuples internally; in other +words, you can just write "Database dsn" instead of ``("Database dsn", +'positional', None)``:: + + + def main(command: ("SQL query", 'option', 'c'), dsn: "Database dsn"): + ... + +In both cases +the usage message will show a nice help string on the right hand side +of the ``dsn`` positional argument. varargs (starred-arguments) can also +be annotated:: + + def main(dsn: "Database dsn", *scripts: "SQL scripts"): + ... + +is a valid signature for clap_, which will recognize the help strings +for both ``dsn`` and ``scripts``:: + + positional arguments: + dsn Database dsn + scripts SQL scripts + +clap_ also recognizes flags, i.e. boolean options which are +``True`` if they are passed to the command line and ``False`` +if they are absent. Here is an example:: + + $ python3 example9.py -v dsn + connecting to dsn + +:: + + $ python3 example9.py -h + usage: example9.py [-h] [-v] dsn + + positional arguments: + dsn connection string + + optional arguments: + -h, --help show this help message and exit + -v, --verbose prints more info + +For consistency with the way the usage message is printed, I suggest you to +follow the Flag-Option-Positional (FOP) convention: in the ``main`` +function write first the flag arguments, then the option arguments and +finally the positional arguments. This is just a convention and you are +not forced to use it, but +it makes sense to put the position arguments at the end, since they +may be default arguments and varargs. + +clap for people not using Python 3 +-------------------------------------------------- + +I do not use Python 3. At work we are just starting to think about +migrating to Python 2.6. I think it will take years before we even +think to migrate to Python 3. I am pretty much sure most Pythonistas +are in the same situation. Therefore clap_ 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 + +:: + + def main(dsn: "Database dsn", *scripts: "SQL scripts"): + +becomes:: + + def main(dsn, *scripts): + ... + main.__annotations__ = dict( + dsn="Database dsn", + scripts="SQL scripts") + +One should be careful to much the keys of the annotations dictionary +with the names of the arguments in the annotated function; for lazy +people with Python 2.4 available the simplest way is to use the +``clap.annotations`` decorator that performs the check for you. + +:: + + @annotations( + dsn="Database dsn", + scripts="SQL scripts") + def main(dsn, *scripts): + ... + +In the rest of this article I will assume that you are using Python 2.X with +``X >= 4`` and I will use the ``clap.annotations`` decorator. + +Advanced usage +-------------------------------------------------- + +One of the goals of clap is to have a learning curve of *minutes*, compared +to the learning curve of *hours* of argparse_. That does not mean +that I have removed all the advanced features of argparse_. Actually +a lot of argparse_ power persists in clap_: in particular, the +``type``, ``choices`` and ``metavar`` concepts are there. +Until now, I have only showed simple annotations, but in general +an annotation is a 5-tuple of the form + + ``(help, kind, abbrev, type, choices, metavar)`` + +where ``help`` is the help message, ``kind`` is one of {"flag", +"option ", "positional"}, ``abbrev`` is a one-character string, +``type`` is callable taking a string in input, choices is a sequence +of values and ``metavar`` is a string. + +``type`` is used to automagically convert the arguments from string +to any Python type; by default there is no convertion i.e. ``type=None``. + +``choices`` is used to restrict the number of the valid +options; by default there is no restriction i.e. ``choices=None``. + +``metavar`` is used to change the argument name in the usage +message (and only there); by default the metavar is equal to the name of the +argument. + +Here is an example showing all of such features (shamelessly stolen +from the argparse_ documentation): + + .. include:: example10.py + :literal: + +Here is the usage for the script:: + + usage: example10.py [-h] {add,mul} [n [n ...]] + + positional arguments: + {add,mul} The name of an operator + n A number + + optional arguments: + -h, --help show this help message and exit + +Here are a couple of examples of use:: + + $ python example10.py add 1 2 3 4 + 10.0 + $ python example10.py mul 1 2 3 4 + 24.0 + $ python example10.py ad 1 2 3 4 # a mispelling error + usage: example10.py [-h] {add,mul} [n [n ...]] + example10.py: error: argument operator: invalid choice: 'ad' (choose from 'add', 'mul') + +A few notes on the underlying implementation +---------------------------------------------------- + +clap_ relies on a argparse_ for all of the heavy lifting work. It is possible +to pass options to the underlying ArgumentParser_ object (currently it +accepts the default arguments ``description``, ``epilog``, ``prog``, ``usage``, +``add_help``, ``argument_default``, ``parents``, ``prefix_chars``, +``conflict_handler``, ``formatter_class``) simply by setting such attributes +on the ``main`` function. +For instance + +:: + + def main(...): + pass + + main.add_help = False + +disable the recognition of the help flag ``-h, --help``. This is not +particularly elegant, but I assume the typical user of clap_ will be +happy with the default message and would not want to go at this level +of detail; still it is possible if she wants to. I redirect the interested +readers to the documentation of argparse_ to understand the meaning of +the various options. + +If you want to access the underlying ArgumentParser_ object, you can +use the ``clap.parser_from`` utility function: + +>>> import clap +>>> def main(arg): +... pass +... +>>> print clap.parser_from(main) +ArgumentParser(prog='', usage=None, description=None, version=None, +formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', +add_help=True) + +I use ``clap.parser_from`` in the unit tests of the module, but regular +users should never need to use it. + +clap_ uses an ``Annotation`` class to convert the tuples +in the function signature into annotation objects, i.e. objects with +six attributes ``help, kind, short, type, choices, metavar``. + +Advanced users can implement their own annotation objects. +For instance, here is an example of how you could implement annotations for +positional arguments: + + .. include:: annotations.py + :literal: + +You can use such annotations objects as follows: + + .. include:: example11.py + :literal: + +Here is the usage message:: + + usage: example11.py [-h] i n [rest [rest ...]] + + positional arguments: + i This is an int + n This is a float + rest Other arguments + + optional arguments: + -h, --help show this help message and exit + +You can go on and define ``Option`` and ``Flag`` classes, if you like. +Using custom annotation objects you could do advanced things like extracting the +annotations from a configuration file or from a database, but I expect such +use cases to be quite rare: the default mechanism should work +pretty well for most users. + +.. _argparse: http://argparse.googlecode.com +.. _optparse: http://docs.python.org/library/optparse.html +.. _getopt: http://docs.python.org/library/getopt.html +.. _optionparse: http://code.activestate.com/recipes/278844-parsing-the-command-line/ +.. _clap: +.. _scaling down: http://www.welton.it/articles/scalable_systems +.. _downwardly scalable: http://www.welton.it/articles/scalable_systems +.. _ArgumentParser: http://argparse.googlecode.com/svn/tags/r11/doc/ArgumentParser.html diff --git a/clap/article/clap.py b/clap/article/clap.py new file mode 120000 index 0000000..f85cf0d --- /dev/null +++ b/clap/article/clap.py @@ -0,0 +1 @@ +../clap.py
\ No newline at end of file diff --git a/clap/article/example1.py b/clap/article/example1.py new file mode 100644 index 0000000..59d4ef1 --- /dev/null +++ b/clap/article/example1.py @@ -0,0 +1,13 @@ +def main(dsn): + "Do something with the database" + print(dsn) + +if __name__ == '__main__': + import sys + n = len(sys.argv[1:]) + if n == 0: + sys.exit('usage: python %s dsn' % sys.argv[0]) + elif n == 1: + main(sys.argv[1]) + else: + sys.exit('Unrecognized arguments: %s' % ' '.join(sys.argv[2:])) diff --git a/clap/article/example10.py b/clap/article/example10.py new file mode 100644 index 0000000..3797c3f --- /dev/null +++ b/clap/article/example10.py @@ -0,0 +1,14 @@ +import clap + +@clap.annotations( +operator=("The name of an operator", 'positional', None, str, ['add', 'mul']), +numbers=("A number", 'positional', None, float, None, "n")) +def main(operator, *numbers): + op = getattr(float, '__%s__' % operator) + result = dict(add=0.0, mul=1.0)[operator] + for n in numbers: + result = op(result, n) + print(result) + +if __name__ == '__main__': + clap.call(main) diff --git a/clap/article/example11.py b/clap/article/example11.py new file mode 100644 index 0000000..9c078b7 --- /dev/null +++ b/clap/article/example11.py @@ -0,0 +1,12 @@ +import clap +from annotations import Positional + +@clap.annotations( + i=Positional("This is an int", int), + n=Positional("This is a float", float), + rest=Positional("Other arguments")) +def main(i, n, *rest): + print(i, n, rest) + +if __name__ == '__main__': + import clap; clap.call(main) diff --git a/clap/article/example2.py b/clap/article/example2.py new file mode 100644 index 0000000..69981ee --- /dev/null +++ b/clap/article/example2.py @@ -0,0 +1,10 @@ +def main(dsn): + "Do something on the database" + print(dsn) + +if __name__ == '__main__': + import argparse + p = argparse.ArgumentParser() + p.add_argument('dsn') + arg = p.parse_args() + main(arg.dsn) diff --git a/clap/article/example3.py b/clap/article/example3.py new file mode 100644 index 0000000..94b756f --- /dev/null +++ b/clap/article/example3.py @@ -0,0 +1,6 @@ +def main(dsn): + "Do something with the database" + print(dsn) + +if __name__ == '__main__': + import clap; clap.call(main) diff --git a/clap/article/example4.py b/clap/article/example4.py new file mode 100644 index 0000000..08a7dd2 --- /dev/null +++ b/clap/article/example4.py @@ -0,0 +1,14 @@ +from datetime import datetime + +def main(dsn, table='product', today=datetime.today()): + "Do something on the database" + print(dsn, table, today) + +if __name__ == '__main__': + import sys + args = sys.argv[1:] + if not args: + sys.exit('usage: python %s dsn' % sys.argv[0]) + elif len(args) > 2: + sys.exit('Unrecognized arguments: %s' % ' '.join(argv[2:])) + main(*args) diff --git a/clap/article/example4_.py b/clap/article/example4_.py new file mode 100644 index 0000000..9ae2d46 --- /dev/null +++ b/clap/article/example4_.py @@ -0,0 +1,8 @@ +from datetime import datetime + +def main(dsn, table='product', today=datetime.today()): + "Do something on the database" + print(dsn, table, today) + +if __name__ == '__main__': + import clap; clap.call(main) diff --git a/clap/article/example5.py b/clap/article/example5.py new file mode 100644 index 0000000..59f88c7 --- /dev/null +++ b/clap/article/example5.py @@ -0,0 +1,8 @@ +from datetime import datetime + +def main(dsn, today=datetime.today()): + "Do something on the database" + print(dsn, today) + +if __name__ == '__main__': + import clap; clap.call(main) diff --git a/clap/article/example6.py b/clap/article/example6.py new file mode 100644 index 0000000..9c7b5fd --- /dev/null +++ b/clap/article/example6.py @@ -0,0 +1,12 @@ +from datetime import datetime + +def main(dsn, *scripts): + "Run the given scripts on the database" + for script in scripts: + print('executing %s' % script) + +if __name__ == '__main__': + import sys + if len(sys.argv) < 2: + sys.exit('usage: python %s dsn script.sql ...' % sys.argv[0]) + main(sys.argv[1:]) diff --git a/clap/article/example7.py b/clap/article/example7.py new file mode 100644 index 0000000..ab3f2f7 --- /dev/null +++ b/clap/article/example7.py @@ -0,0 +1,9 @@ +from datetime import datetime + +def main(dsn, *scripts): + "Run the given scripts on the database" + for script in scripts: + print('executing %s' % script) + +if __name__ == '__main__': + import clap; clap.call(main) diff --git a/clap/article/example8.py b/clap/article/example8.py new file mode 100644 index 0000000..2f675f5 --- /dev/null +++ b/clap/article/example8.py @@ -0,0 +1,7 @@ +def main(command: ("SQL query", 'option', 'c'), dsn): + if command: + print('executing %s on %s' % (command, dsn)) + # ... + +if __name__ == '__main__': + import clap; clap.call(main) diff --git a/clap/article/example9.py b/clap/article/example9.py new file mode 100644 index 0000000..6613345 --- /dev/null +++ b/clap/article/example9.py @@ -0,0 +1,7 @@ +def main(verbose: ('prints more info', 'flag', 'v'), dsn: 'connection string'): + if verbose: + print('connecting to %s' % dsn) + # ... + +if __name__ == '__main__': + import clap; clap.call(main) diff --git a/clap/clap.py b/clap/clap.py index 74d1d34..625092b 100644 --- a/clap/clap.py +++ b/clap/clap.py @@ -24,174 +24,121 @@ ## DAMAGE. """ -CLAP, the smart and simple Command Line Arguments Parser. +clap, the easiest Command Line Arguments Parser in the world. See clap/doc.html for the documentation. """ +__version__ = '0.2.0' -import optparse, re, sys, string +import re, sys, inspect, argparse -class RegexContainer(object): +if sys.version >= '3': + from inspect import getfullargspec +else: + class getfullargspec(object): + "A quick and dirty replacement for getfullargspec for Python 2.X" + def __init__(self, f): + self.args, self.varargs, self.keywords, self.defaults = \ + inspect.getargspec(f) + self.annotations = getattr(f, '__annotations__', {}) + +def annotations(**ann): """ - A regular expression container for named regexps. - All its regular attributes must be valid regular expression templates. - Notice that the termination character $ must be doubled in - order to avoid confusion with the template syntax. + Returns a decorator annotating a function with the given annotations. + This is a trick to support function annotations in Python 2.X. """ - def __init__(self): - self._dic = {} # a dictionary {name: interpolated regex pattern} - def __setattr__(self, name, value): - if not name.startswith('_'): # regular attribute - templ = string.Template('(?P<' + name + '>' + value + ')') - pattern = templ.substitute(self._dic) - object.__setattr__(self, name, re.compile(pattern)) - self._dic[name] = pattern - else: # private or special attribute - object.__setattr__(self, name, value) - -rx = RegexContainer() # a few regular expressions to parse the usage string - -rx.argument = r'[a-zA-Z]\w*' -rx.arguments = '(?:\s+$argument)*' -rx.ellipsis = r'\.\.\.' -rx.prog = r'%prog$arguments\s*$ellipsis?' - -rx.enddef = r'\n[ \t]*\n|$$' -rx.lines = r'.*?' -rx.short = r'\w' -rx.long = r'[-\w]+' -rx.default = r'[^:]*' -rx.help = r'.*' - -rx.usage = r'(?is)\s*usage:$lines$enddef' # case-insensitive multiline rx -rx.optiondef = r'\s*-$short\s*,\s*--$long\s*=\s*$default:\s*$help' -rx.flagdef = r'\s*-$short\s*,\s*--$long\s*:\s*$help' - -def parse_usage(txt): - "An utility to extract the expected arguments and the rest argument, if any" - match = rx.prog.match(txt) - if match is None: - ParsingError.raise_(txt) - expected_args = match.group('arguments').split() - if match.group('ellipsis'): - return expected_args[:-1], match.group('argument') - else: - return expected_args, '' - -class ParsingError(Exception): - @classmethod - def raise_(cls, usage): - raise cls("""Wrong format for the usage message.\n\n%s\n - It should be '%%prog arguments ... [options]""" % usage) - -def make_get_default_values(defaults): - # the purpose of this trick is to allow the idiom - # if not arg: OptionParser.exit() - def __nonzero__(self): - "True if at least one option is set to a non-trivial value" - for k, v in vars(self).iteritems(): - if v and v != defaults[k]: return True - return False - Values = type('Values', (optparse.Values, object), - dict(__nonzero__=__nonzero__)) - return lambda : Values(defaults) - -optionstring = None # singleton - -class OptionParser(object): + def annotate(f): + fas = getfullargspec(f) + args = fas.args + if fas.varargs: + args.append(fas.varargs) + for argname in ann: + if argname not in args: + raise NameError( + 'Annotating non-existing argument: %s' % argname) + f.__annotations__ = ann + return f + return annotate + +def is_annotation(obj): """ - There should be only one instance of it. - Attributes: all_options, expected_args, rest_arg, p + An object is an annotation object if it has the attributes + help, kind, abbrev, type, choices, metavar. """ - def __init__(self, doc): - "Populate the option parser." - global optionstring - assert doc is not None, \ - "Missing usage string (maybe __doc__ is None)" - optionstring = doc.replace('%prog', sys.argv[0]) - - # parse the doc - match = rx.usage.search(doc) - if not match: - raise ParsingError("Could not find the option definitions") - optlines = match.group("lines").splitlines() - prog = optlines[0] # first line - match = rx.prog.search(prog) - if not match: - ParsingError.raise_(prog) - self.expected_args, self.rest_arg = parse_usage(match.group()) - self.p = optparse.OptionParser(prog) - - # manage the default values - df = self.p.defaults - for a in self.expected_args: - df[a] = None - if self.rest_arg: - df[self.rest_arg] = [] - self.p.get_default_values = make_get_default_values(df) - - # parse the options - for line in optlines[1:]: - # check if the line is an option definition - match_option = rx.optiondef.match(line) - if match_option: - action = 'store' - short, long_, help, default=match_option.group( - "short", "long", "help", "default") - else: # check if the line is a flag definition - match_flag = rx.flagdef.match(line) - if match_flag: - action = 'store_true' - short, long_, help, default=match_flag.group( - "short", "long", "help") + (False,) - else: # cannot parse the definition correctly - continue - # add the options - long_ = long_.replace('-', '_') - self.p.add_option("-" + short, "--" + long_, - action=action, help=help, default=default) - # skip the help option, which destination is None - self.all_options = [o for o in self.p.option_list if o.dest] - - def parse_args(self, arglist=None): - """ - Parse the received arguments and returns an ``optparse.Values`` - object containing both the options and the positional arguments. - """ - option, args = self.p.parse_args(arglist) - n_expected_args = len(self.expected_args) - n_received_args = len(args) - if (n_received_args < n_expected_args) or ( - n_received_args > n_expected_args and not self.rest_arg): - raise ParsingError( - 'Received %d arguments %s, expected %d %s' % - (n_received_args, args, n_expected_args, self.expected_args)) - for name, value in zip(self.expected_args, args): - setattr(option, name, value) - if self.rest_arg: - setattr(option, self.rest_arg, args[n_expected_args:]) - return option + return (hasattr(obj, 'help') and hasattr(obj, 'kind') and + hasattr(obj, 'abbrev') and hasattr(obj, 'type') + and hasattr(obj, 'choices') and hasattr(obj, 'metavar')) + +class Annotation(object): + def __init__(self, help="", kind="positional", abbrev=None, type=str, + choices=None, metavar=None): + if kind == "positional": + assert abbrev is None, abbrev + else: + assert isinstance(abbrev, str) and len(abbrev) == 1, abbrev + self.help = help + self.kind = kind + self.abbrev = abbrev + self.type = type + self.choices = choices + self.metavar = metavar @classmethod - def exit(cls, msg=None): - exit(msg) - -def call(func, args=None, doc=None): + def from_(cls, obj): + "Helper to convert an object into an annotation, if needed" + if is_annotation(obj): + return obj # do nothing + elif hasattr(obj, '__iter__') and not isinstance(obj, str): + return cls(*obj) + return cls(obj) + +NONE = object() # sentinel use to signal the absence of a default + +valid_attrs = getfullargspec(argparse.ArgumentParser.__init__).args[1:] + +def parser_from(func): + # extract the ArgumentParser arguments from the attributes of func + attrs = dict([(n, v) for n, v in vars(func).items() if n in valid_attrs]) + p = argparse.ArgumentParser(**attrs) + f = p.argspec = getfullargspec(func) + defaults = f.defaults or () + n_args = len(f.args) + n_defaults = len(defaults) + alldefaults = (NONE,) * (n_args - n_defaults) + defaults + for name, default in zip(f.args, alldefaults): + a = Annotation.from_(f.annotations.get(name, ())) + if a.kind in ('option', 'flag'): + short = '-' + a.abbrev + long = '--' + name + elif default is NONE: # mandatory positional argument + p.add_argument(name, help=a.help, type=a.type, choices=a.choices, + metavar=a.metavar) + else: # regular default argument + p.add_argument(name, nargs='?', help=a.help, default=default, + type=a.type, choices=a.choices, metavar=a.metavar) + if a.kind == 'option': + if default is not NONE: + raise TypeError('Option %r does not want a default' % name) + p.add_argument(short, long, help=a.help, type=a.type, + choices=a.choices, metavar=a.metavar) + elif a.kind == 'flag': + if default is not NONE: + raise TypeError('Flag %r does not want a default' % name) + p.add_argument(short, long, action='store_true', help=a.help) + if f.varargs: + a = Annotation.from_(f.annotations.get(f.varargs, ())) + p.add_argument(f.varargs, nargs='*', help=a.help, default=[], + type=a.type, metavar=a.metavar) + return p + +def call(func, arglist=sys.argv[1:]): """ - Magically calls func by passing to it the command lines arguments, - parsed according to the docstring of func. + Parse the given arglist by using an argparser inferred from the + annotations of the given function (the main function of the script) + and call that function with the parsed arguments. The user can + provide a custom parse_annotation hook or replace the default one. """ - if args is None: - args = sys.argv[1:] - if doc is None: - doc = func.__doc__ - try: - arg = OptionParser(doc).parse_args(args) - except ParsingError, e: - print 'ParsingError:', e - OptionParser.exit() - return func(**vars(arg)) - -def exit(msg=None): - if msg is None: - msg = optionstring - raise SystemExit(msg) + p = parser_from(func) + argdict = vars(p.parse_args(arglist)) + args = [argdict[a] for a in p.argspec.args] + varargs = argdict.get(p.argspec.varargs, []) + func(*(args + varargs)) diff --git a/clap/doc.txt b/clap/doc.txt index 84d254a..c84d8da 100644 --- a/clap/doc.txt +++ b/clap/doc.txt @@ -75,3 +75,8 @@ Limitations ------------------------------------ The default values cannot contain a ':' (or it should be escaped). + + +.. Today I want to announce to the general public the birth of my latest +.. project, which aims to be the easiest command line arguments +.. parser in the Python world: clap_. diff --git a/clap/test_clap.py b/clap/test_clap.py index 8763ac6..04a313d 100644 --- a/clap/test_clap.py +++ b/clap/test_clap.py @@ -3,97 +3,81 @@ The tests are runnable with nose, with py.test, or even as standalone script """ import sys -from clap import OptionParser, ParsingError +import clap3 -def expect_error(err, func, *args, **kw): - """ - Calls a callable with the given arguments and expects a given - exception type to be raised, otherwise raises a RuntimeError. - """ +def expect_exit(func, *args, **kw): try: func(*args, **kw) - except Exception, e: - if str(e) != err: - raise e.__class__, e, sys.exc_info()[2] + except SystemExit: + pass else: - raise RuntimeError( - 'Exception %s expected, got none' % errclass.__name__) + raise RuntimeError('SystemExit expected, got none!') -p1 = OptionParser('''\ -usage: %prog args ... [options] --d, --delete=: delete a file -''') +def f1(delete: "d: delete a file", args=()): + pass def test_p1(): + p1 = clap3.parser_from(f1) arg = p1.parse_args(['-d', 'foo', 'arg1', 'arg2']) assert arg.delete == 'foo' assert arg.args == ['arg1', 'arg2'] arg = p1.parse_args([]) - assert arg.delete == '', arg.delete - assert arg.args == [], arg.args - assert not arg, arg + assert arg.delete is None, arg.delete + assert arg.args is (), arg.args -p2 = OptionParser(''' - usage: %prog arg1 args ... [options] --d, --delete=: delete a file -''') +def f2(arg1, delete: "d: delete a file", args=()): + pass def test_p2(): + p2 = clap3.parser_from(f2) arg = p2.parse_args(['-d', 'foo', 'arg1', 'arg2']) assert arg.delete == 'foo', arg.delete assert arg.arg1 == 'arg1', arg.arg1 assert arg.args == ['arg2'], arg.args arg = p2.parse_args(['arg1']) - assert arg.delete == '', arg.delete - assert arg.args == [], arg.args + assert arg.delete is None, arg.delete + assert arg.args is (), arg.args assert arg, arg - expect_error("Received 0 arguments [], expected 1 ['arg1']", - p2.parse_args, []) + expect_exit(p2.parse_args, []) -p3 = OptionParser('''\ -USAGE: %prog arg1 [options] --d, --delete=: delete a file -''') +def f3(arg1, delete: "d: delete a file"): + pass def test_p3(): + p3 = clap3.parser_from(f3) arg = p3.parse_args(['arg1']) - assert arg.delete == '', arg.delete + assert arg.delete is None, arg.delete assert arg.arg1 == 'arg1', arg.args - expect_error("Received 2 arguments ['arg1', 'arg2'], expected 1 ['arg1']", - p3.parse_args, ['arg1', 'arg2']) - - expect_error("Received 0 arguments [], expected 1 ['arg1']", - p3.parse_args, []) + expect_exit(p3.parse_args, ['arg1', 'arg2']) + expect_exit(p3.parse_args, []) -p4 = OptionParser('''\ -Usage: %prog [options] --c, --color=black: set default color --d, --delete=: delete the given file --a, --delete-all: delete all files -''') +def f4(delete: "d: delete a file", + delete_all: "a, delete all files", + color: "c: set default color"="black"): + pass def test_p4(): + p4 = clap3.parser_from(f4) arg = p4.parse_args(['-a']) assert arg.delete_all is True, arg.delete_all arg = p4.parse_args([]) - assert not arg, arg - + arg = p4.parse_args(['--color=black']) - assert not arg, arg + assert arg.color == 'black' arg = p4.parse_args(['--color=red']) - assert arg, arg + assert arg.color == 'red' if __name__ == '__main__': n = 0 - for name, test in globals().items(): + for name, test in list(globals().items()): if name.startswith('test_'): - print 'Running', name + print('Running', name) test() n +=1 - print 'Executed %d tests OK' % n + print('Executed %d tests OK' % n) |