From 68e5691ddde6246cd22c7cc6e0b189f2b343e661 Mon Sep 17 00:00:00 2001 From: Michele Simionato Date: Mon, 13 Jun 2011 09:57:28 +0200 Subject: Removed the plac project, which now has its own page --- plac/CHANGES.txt | 47 - plac/MANIFEST.in | 1 - plac/Makefile | 10 - plac/README.txt | 58 - plac/df.css | 400 -- plac/doc/annotations.py | 9 - plac/doc/cmd_ex.py | 6 - plac/doc/dbcli.help | 13 - plac/doc/dbcli.py | 29 - plac/doc/example0.py | 6 - plac/doc/example1.py | 14 - plac/doc/example10.help | 10 - plac/doc/example10.py | 20 - plac/doc/example11.help | 9 - plac/doc/example11.py | 13 - plac/doc/example12.help | 9 - plac/doc/example12.py | 18 - plac/doc/example13.help | 13 - plac/doc/example13.py | 28 - plac/doc/example2.py | 12 - plac/doc/example3.help | 9 - plac/doc/example3.py | 8 - plac/doc/example4.py | 15 - plac/doc/example5.help | 11 - plac/doc/example5.py | 9 - plac/doc/example6.help | 8 - plac/doc/example6.py | 6 - plac/doc/example7.help | 10 - plac/doc/example7.py | 11 - plac/doc/example7_.help | 10 - plac/doc/example7_.py | 11 - plac/doc/example8.help | 9 - plac/doc/example8.py | 8 - plac/doc/example8_.help | 9 - plac/doc/example8_.py | 6 - plac/doc/example9.help | 8 - plac/doc/example9.py | 9 - plac/doc/importer1.py | 20 - plac/doc/importer2.py | 22 - plac/doc/importer3.py | 20 - plac/doc/importer_ui.py | 30 - plac/doc/ishelve.help | 18 - plac/doc/ishelve.plac | 7 - plac/doc/ishelve.placet | 13 - plac/doc/ishelve.py | 54 - plac/doc/ishelve2.help | 8 - plac/doc/ishelve2.plac | 4 - plac/doc/ishelve2.placet | 9 - plac/doc/ishelve2.py | 43 - plac/doc/ishelve3.py | 5 - plac/doc/picalculator.py | 63 - plac/doc/plac.el | 77 - plac/doc/plac.html | 3026 +-------- plac/doc/plac.pdf | 13201 ------------------------------------- plac/doc/plac.txt | 2 - plac/doc/plac_adv.txt | 1266 ---- plac/doc/plac_core.txt | 793 --- plac/doc/read_stdin.py | 13 - plac/doc/server_ex.py | 9 - plac/doc/shelve_interpreter.help | 13 - plac/doc/shelve_interpreter.py | 19 - plac/doc/sql_interface.py | 29 - plac/doc/test_ishelve.py | 12 - plac/doc/test_ishelve_more.py | 11 - plac/doc/test_pi.py | 11 - plac/doc/test_plac.py | 231 - plac/doc/test_runp.py | 24 - plac/doc/test_server.py | 38 - plac/doc/vcs.help | 12 - plac/doc/vcs.py | 30 - plac/plac.py | 40 - plac/plac_core.py | 312 - plac/plac_ext.py | 1063 --- plac/plac_runner.py | 65 - plac/setup.py | 50 - 75 files changed, 1 insertion(+), 21544 deletions(-) delete mode 100644 plac/CHANGES.txt delete mode 100644 plac/MANIFEST.in delete mode 100644 plac/Makefile delete mode 100644 plac/README.txt delete mode 100644 plac/df.css delete mode 100644 plac/doc/annotations.py delete mode 100644 plac/doc/cmd_ex.py delete mode 100644 plac/doc/dbcli.help delete mode 100644 plac/doc/dbcli.py delete mode 100644 plac/doc/example0.py delete mode 100644 plac/doc/example1.py delete mode 100644 plac/doc/example10.help delete mode 100644 plac/doc/example10.py delete mode 100644 plac/doc/example11.help delete mode 100644 plac/doc/example11.py delete mode 100644 plac/doc/example12.help delete mode 100644 plac/doc/example12.py delete mode 100644 plac/doc/example13.help delete mode 100644 plac/doc/example13.py delete mode 100644 plac/doc/example2.py delete mode 100644 plac/doc/example3.help delete mode 100644 plac/doc/example3.py delete mode 100644 plac/doc/example4.py delete mode 100644 plac/doc/example5.help delete mode 100644 plac/doc/example5.py delete mode 100644 plac/doc/example6.help delete mode 100644 plac/doc/example6.py delete mode 100644 plac/doc/example7.help delete mode 100644 plac/doc/example7.py delete mode 100644 plac/doc/example7_.help delete mode 100644 plac/doc/example7_.py delete mode 100644 plac/doc/example8.help delete mode 100644 plac/doc/example8.py delete mode 100644 plac/doc/example8_.help delete mode 100644 plac/doc/example8_.py delete mode 100644 plac/doc/example9.help delete mode 100644 plac/doc/example9.py delete mode 100644 plac/doc/importer1.py delete mode 100644 plac/doc/importer2.py delete mode 100644 plac/doc/importer3.py delete mode 100644 plac/doc/importer_ui.py delete mode 100644 plac/doc/ishelve.help delete mode 100644 plac/doc/ishelve.plac delete mode 100644 plac/doc/ishelve.placet delete mode 100644 plac/doc/ishelve.py delete mode 100644 plac/doc/ishelve2.help delete mode 100644 plac/doc/ishelve2.plac delete mode 100644 plac/doc/ishelve2.placet delete mode 100644 plac/doc/ishelve2.py delete mode 100644 plac/doc/ishelve3.py delete mode 100644 plac/doc/picalculator.py delete mode 100644 plac/doc/plac.el delete mode 100644 plac/doc/plac.pdf delete mode 100644 plac/doc/plac.txt delete mode 100644 plac/doc/plac_adv.txt delete mode 100644 plac/doc/plac_core.txt delete mode 100644 plac/doc/read_stdin.py delete mode 100644 plac/doc/server_ex.py delete mode 100644 plac/doc/shelve_interpreter.help delete mode 100644 plac/doc/shelve_interpreter.py delete mode 100644 plac/doc/sql_interface.py delete mode 100644 plac/doc/test_ishelve.py delete mode 100644 plac/doc/test_ishelve_more.py delete mode 100644 plac/doc/test_pi.py delete mode 100644 plac/doc/test_plac.py delete mode 100644 plac/doc/test_runp.py delete mode 100644 plac/doc/test_server.py delete mode 100644 plac/doc/vcs.help delete mode 100644 plac/doc/vcs.py delete mode 100644 plac/plac.py delete mode 100644 plac/plac_core.py delete mode 100644 plac/plac_ext.py delete mode 100644 plac/plac_runner.py delete mode 100644 plac/setup.py diff --git a/plac/CHANGES.txt b/plac/CHANGES.txt deleted file mode 100644 index f5560e6..0000000 --- a/plac/CHANGES.txt +++ /dev/null @@ -1,47 +0,0 @@ -HISTORY ----------- - -0.9.0 Default values are now displayed in the help message by default; - removed .help and introduced help as an alias for --help; - added a reference to Argh (2011-06-05) -0.8.1 Removed a stray newline in the output of plac, as signaled - by Daniele Pighin; fixed a bug in the doctest method raising - non-existing exceptions; turned the notification messages into - unicode strings; removed an ugly SystemExit message - for invalid commands, signaled by Tuk Bredsdorff (2011-04-11) -0.8.0 Added a monitor framework and a TkMonitor (2011-02-16) -0.7.6 Fixed the error propagation in ``Interpreter.__exit__``. - Added a note about commandline and marrow.script in the documentation - (2011-01-13) -0.7.5 Fixed a bug with the help of subcommands, signaled by Paul Jacobson; - added the ability to save the output of a command into a file; postponed - the import of the readline module to avoid buffering issues; fixed a - bug with the traceback when in multiprocessing mode (2011-01-01) -0.7.4 Fixed the plac_runner switches -i and -s; fixed a bug with multiline - output and issue with nosetest (2010-09-04) -0.7.3 Put the documentation in a single document; added runp (2010-08-31) -0.7.2 Interpreter.call does not start an interpreter automagically anymore; - better documented and added tests for the metavar concept (2010-08-31) -0.7.1 A few bug fixes (2010-08-11) -0.7.0 Improved and documented the support for parallel programming; - added an asynchronous server; added plac.Interpreter.call (2010-08-07) -0.6.1 Fixed the history file location; added the ability to pass a split - function; added two forgotten files; added a reference to cmd2 by - Catherine Devlin (2010-07-12) -0.6.0 Improved the interactive experience with full readline support and - custom help. Added support for long running command, via threads and - processes (2010-07-11) -0.5.0 Gigantic release. Introduced smart options, added an Interpreter class - and the command container concept. Made the split plac/plac_core/plac_ext - and added a plac runner, able to run scripts, batch files and doctests. - Removed the default formatter class (2010-06-20) -0.4.3 Fixed the installation procedure to automatically download argparse - if needed (2010-06-11) -0.4.2 Added missing .help files, made the tests generative and added a - note about Clap in the documentation (2010-06-04) -0.4.1 Changed the default formatter class and fixed a bug in the - display of the default arguments. Added more stringent tests. (2010-06-03) -0.4.0 abbrev is now optional. Added a note about CLIArgs and opterate. - Added keyword arguments recognition. ``plac.call`` now returns the - the output of the main function. (2010-06-03) -0.3.0 Initial version. (2010-06-02) diff --git a/plac/MANIFEST.in b/plac/MANIFEST.in deleted file mode 100644 index 0f93421..0000000 --- a/plac/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include *.txt doc/*.py doc/*.help doc/*.txt doc/*.html doc/*.pdf diff --git a/plac/Makefile b/plac/Makefile deleted file mode 100644 index c1e79ab..0000000 --- a/plac/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -default: - make doc/plac.pdf -doc/plac.pdf: doc/plac.txt doc/plac_core.txt doc/plac_adv.txt - cd doc; rst2pdf --footer=###Page### plac.txt; rst2html --stylesheet=$(HOME)/md/gcodedev/df.css plac.txt plac.html -upload: - python3.2 setup.py register sdist upload -2: - python setup.py build; sudo python setup.py install; sudo rm -rf dist -3: - python3.2 setup.py build; sudo python3.2 setup.py install; sudo rm -rf dist diff --git a/plac/README.txt b/plac/README.txt deleted file mode 100644 index 989a43d..0000000 --- a/plac/README.txt +++ /dev/null @@ -1,58 +0,0 @@ -Installation -------------- - -If you are lazy, just perform - -:: - - $ easy_install -U plac - -which will install the module on your system (and possibly argparse -too, if it is not already installed). 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/plac -.. _distribute: http://packages.python.org/distribute/ - -Testing --------- - -Run - -:: - - $ python doc/test_plac.py - -or - -:: - - $ nosetests doc - -or - -:: - - $ py.test doc - -Some tests will fail if sqlalchemy is not installed. -Run an ``easy_install -U sqlalchemy`` or just ignore them. - -Documentation --------------- - -The source code and the documentation are hosted on Google code. -Here is the full documentation in HTML and PDF form: - -http://plac.googlecode.com/hg/doc/plac.html - -http://plac.googlecode.com/hg/doc/plac.pdf diff --git a/plac/df.css b/plac/df.css deleted file mode 100644 index afa58bb..0000000 --- a/plac/df.css +++ /dev/null @@ -1,400 +0,0 @@ -.first { - margin-top: 0 } - -.last { - margin-bottom: 0 } - -a.toc-backref { - text-decoration: none ; - color: black } - -dd { - margin-bottom: 0.5em } - -div.abstract { - margin: 2em 5em } - -div.abstract p.topic-title { - font-weight: bold ; - text-align: center } - -div.attention, div.caution, div.danger, div.error, div.hint, -div.important, div.note, div.tip, div.warning { - margin: 2em ; - border: medium outset ; - padding: 1em } - -div.attention p.admonition-title, div.caution p.admonition-title, -div.danger p.admonition-title, div.error p.admonition-title, -div.warning p.admonition-title { - color: red ; - font-weight: bold ; - font-family: sans-serif } - -div.hint p.admonition-title, div.important p.admonition-title, -div.note p.admonition-title, div.tip p.admonition-title { - font-weight: bold ; - font-family: sans-serif } - -div.dedication { - margin: 2em 5em ; - text-align: center ; - font-style: italic } - -div.dedication p.topic-title { - font-weight: bold ; - font-style: normal } - -div.figure { - margin-left: 2em } - -div.footer, div.header { - font-size: smaller } - -div.system-messages { - margin: 5em } - -div.system-messages h1 { - color: red } - -div.system-message { - border: medium outset ; - padding: 1em } - -div.system-message p.system-message-title { - color: red ; - font-weight: bold } - -div.topic { - margin: 2em } - -hr { - width: 75% } - -ol.simple, ul.simple { - margin-bottom: 1em } - -ol.arabic { - list-style: decimal } - -ol.loweralpha { - list-style: lower-alpha } - -ol.upperalpha { - list-style: upper-alpha } - -ol.lowerroman { - list-style: lower-roman } - -ol.upperroman { - list-style: upper-roman } - -p.caption { - font-style: italic } - -p.credits { - font-style: italic ; - font-size: smaller } - -p.label { - white-space: nowrap } - -p.topic-title { - font-weight: bold } - -pre.address { - margin-bottom: 0 ; - margin-top: 0 ; - font-family: serif ; - font-size: 100% } - -pre.line-block { - font-family: serif ; - font-size: 100% } - -pre.literal-block, pre.doctest-block { - background-color: #eeeeee } - -span.classifier { - font-family: sans-serif ; - font-style: oblique } - -span.classifier-delimiter { - font-family: sans-serif ; - font-weight: bold } - -span.interpreted { - font-family: sans-serif } - -span.option-argument { - font-style: italic } - -span.pre { - white-space: pre } - -span.problematic { - color: red } - -table { - margin-top: 0.5em ; - margin-bottom: 0.5em } - -table.citation { - border-left: solid thin gray ; - padding-left: 0.5ex } - -table.docinfo { - margin: 2em 4em } - -table.footnote { - border-left: solid thin black ; - padding-left: 0.5ex } - -td, th { - padding-left: 0.5em ; - padding-right: 0.5em ; - vertical-align: top } - -th.docinfo-name, th.field-name { - font-weight: bold ; - text-align: left ; - white-space: nowrap } - -h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { - font-size: 100% } - -tt { - background-color: #eeeeee } - -ul.auto-toc { - list-style-type: none } - - -/* -Additional styles for "modern"-style of DocFactory. - -:Author: Gunnar Schwant -:Contact: g.schwant@gmx.de -*/ - -.first { - font-size: 10pt } - -.last { - font-size: 10pt } - -a { - text-decoration: none } - -a.reference { - color: #00009F } - -a:hover { - background-color: #00009F ; - color: white } - -body { - font-family: arial,helvetica,univers ; - font-size: 10pt ; - padding-top: 0.6cm ; - margin-left:0.5cm ; - margin-right:0.5cm ; - margin-bottom:0.5cm } - -dd { - font-size: 10pt ; - padding-top: 0.1cm -} - -dt { - font-size: 10pt ; - font-weight: bold ; - background-color: #6FC7FB ; - padding-left: 0.1cm ; - padding-top: 0.1cm ; - padding-bottom: 0.1cm } - -div.abstract { - font-size: 10pt } - -div.abstract p.topic-title { - font-size: 10pt } - -div.attention, div.caution, div.danger, div.error, div.hint, -div.important, div.note, div.tip, div.warning { - font-size: 10pt } - -div.attention p.admonition-title, div.caution p.admonition-title, -div.danger p.admonition-title, div.error p.admonition-title, -div.warning p.admonition-title, div.hint p.admonition-title, -div.important p.admonition-title, div.note p.admonition-title, -div.tip p.admonition-title { - margin-top: 0em ; - font-size: 12pt ; - font-family: arial,helvetica,univers } - -div.dedication { - font-size: 10pt } - -div.dedication p.topic-title { - font-size: 10pt } - -div.figure { - font-size: 10pt } - -div.footer, div.header { - font-size: 8pt } - -div.system-messages { - font-size: 10pt } - -div.system-messages h1 { - font-size: 12pt } - -div.system-message { - font-size: 10pt } - -div.system-message p.system-message-title { - font-size: 10pt } - -div.topic { - font-size: 10pt } - -h1, h2, h3, h4, h5, h6 { - padding-top: 0.5cm ; - page-break-after: avoid ; - font-family: arial,helvetica,univers } - -h1 { - font-size: 18pt } - -h1.title { - color: white ; - background-color: #00009F ; - padding-top: 0cm } - -h2 { - font-size: 16pt } - -h2.subtitle { - padding-top: 0cm } - -h3 { - font-size: 14pt } - -h4 { - font-size: 12pt } - -h5, h6 { - font-size: 10pt } - -hr { - width: 100%; - page-break-after: always } - -li { - padding-top: 1mm ; - padding-bottom: 1mm } - -ol.simple, ul.simple { - font-size: 10pt } - -ol.arabic { - font-size: 10pt } - -ol.loweralpha { - font-size: 10pt } - -ol.upperalpha { - font-size: 10pt } - -ol.lowerroman { - font-size: 10pt } - -ol.upperroman { - font-size: 10pt } - -p.caption { - font-size: 10pt } - -p.credits { - font-style: italic ; - font-size: 8pt } - -p.label { - font-size: 10pt } - -p.topic-title { - font-size: 10pt } - -pre.address { - font-family: arial,helvetica,univers ; - font-size: 10pt } - -pre.line-block { - font-size: 10pt } - -pre.literal-block, pre.doctest-block { - border-width: 1pt ; - border-style: solid ; - border-color: #999999 ; - color: #0000C0 ; - background-color: #ffffe0 ; - font-size: 9pt } - -span.classifier { - font-size: 10pt ; - font-family: arial,helvetica,univers } - -span.classifier-delimiter { - font-size: 10pt ; - font-family: arial,helvetica,univers } - -span.field-argument { - font-size: 10pt } - -span.interpreted { - font-size: 10pt ; - font-family: arial,helvetica,univers } - -span.option-argument { - font-size: 10pt } - -span.problematic { - font-size: 10pt } - -table { - font-size: 10pt ; - border-collapse: collapse ; - border-width: 1.5pt ; - border-color: #003366 } - -table.citation { - font-size: 10pt } - -table.docinfo { - font-size: 10pt } - -table.footnote { - font-size: 8pt ; - text-align: left } - -table.table { - width: 100% } - -th { - border-width: 1.5pt } - -td { - border-width: 1pt } - -td, th { - font-size: 10pt ; - border-style: thin ; - border-color: #003366 } - -td.docinfo-name, th.field-name { - font-size: 10pt } - -h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { - font-size: 10pt } diff --git a/plac/doc/annotations.py b/plac/doc/annotations.py deleted file mode 100644 index 0de2140..0000000 --- a/plac/doc/annotations.py +++ /dev/null @@ -1,9 +0,0 @@ -# annotations.py -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/plac/doc/cmd_ex.py b/plac/doc/cmd_ex.py deleted file mode 100644 index 4af18c4..0000000 --- a/plac/doc/cmd_ex.py +++ /dev/null @@ -1,6 +0,0 @@ -# cmd_ext.py -from plac_ext import cmd_interface -import ishelve2 - -if __name__ == '__main__': - cmd_interface(ishelve2.main()).cmdloop() diff --git a/plac/doc/dbcli.help b/plac/doc/dbcli.help deleted file mode 100644 index 8c0c18a..0000000 --- a/plac/doc/dbcli.help +++ /dev/null @@ -1,13 +0,0 @@ -usage: dbcli.py [-h] [-H] [-c SQL] [-d |] db [scripts [scripts ...]] - -A script to run queries and SQL scripts on a database - -positional arguments: - db Connection string - scripts SQL scripts - -optional arguments: - -h, --help show this help message and exit - -H, --header Header - -c SQL, --sqlcmd SQL SQL command - -d |, --delimiter | Column separator diff --git a/plac/doc/dbcli.py b/plac/doc/dbcli.py deleted file mode 100644 index d5db392..0000000 --- a/plac/doc/dbcli.py +++ /dev/null @@ -1,29 +0,0 @@ -# dbcli.py -import plac -from sqlalchemy.ext.sqlsoup import SqlSoup - -@plac.annotations( - db=("Connection string", 'positional', None, SqlSoup), - header=("Header", 'flag', 'H'), - sqlcmd=("SQL command", 'option', 'c', str, None, "SQL"), - delimiter=("Column separator", 'option', 'd'), - scripts="SQL scripts", - ) -def main(db, header, sqlcmd, delimiter="|", *scripts): - "A script to run queries and SQL scripts on a database" - yield 'Working on %s' % db.bind.url - - if sqlcmd: - result = db.bind.execute(sqlcmd) - if header: # print the header - yield delimiter.join(result.keys()) - for row in result: # print the rows - yield delimiter.join(map(str, row)) - - for script in scripts: - db.bind.execute(file(script).read()) - yield 'executed %s' % script - -if __name__ == '__main__': - for output in plac.call(main): - print(output) diff --git a/plac/doc/example0.py b/plac/doc/example0.py deleted file mode 100644 index af345c3..0000000 --- a/plac/doc/example0.py +++ /dev/null @@ -1,6 +0,0 @@ -def main(arg: "required argument"): - "do something with arg" - print('Got %s' % arg) - -if __name__ == '__main__': - import plac; plac.call(main) # passes sys.argv[1:] to main diff --git a/plac/doc/example1.py b/plac/doc/example1.py deleted file mode 100644 index f024090..0000000 --- a/plac/doc/example1.py +++ /dev/null @@ -1,14 +0,0 @@ -# example1.py -def main(dsn): - "Do something with the database" - # ... - -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/plac/doc/example10.help b/plac/doc/example10.help deleted file mode 100644 index 8ac3e9f..0000000 --- a/plac/doc/example10.help +++ /dev/null @@ -1,10 +0,0 @@ -usage: example10.py [-h] {add,mul} [n [n ...]] - -A script to add and multiply numbers - -positional arguments: - {add,mul} The name of an operator - n A number - -optional arguments: - -h, --help show this help message and exit diff --git a/plac/doc/example10.py b/plac/doc/example10.py deleted file mode 100644 index 02bbdc6..0000000 --- a/plac/doc/example10.py +++ /dev/null @@ -1,20 +0,0 @@ -# example10.py -import plac - -@plac.annotations( -operator=("The name of an operator", 'positional', None, str, ['add', 'mul']), -numbers=("A number", 'positional', None, float, None, "n")) -def main(operator, *numbers): - "A script to add and multiply numbers" - if operator == 'mul': - op = float.__mul__ - result = 1.0 - else: # operator == 'add' - op = float.__add__ - result = 0.0 - for n in numbers: - result = op(result, n) - return result - -if __name__ == '__main__': - print(plac.call(main)) diff --git a/plac/doc/example11.help b/plac/doc/example11.help deleted file mode 100644 index 4d36924..0000000 --- a/plac/doc/example11.help +++ /dev/null @@ -1,9 +0,0 @@ -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 diff --git a/plac/doc/example11.py b/plac/doc/example11.py deleted file mode 100644 index 07fbd40..0000000 --- a/plac/doc/example11.py +++ /dev/null @@ -1,13 +0,0 @@ -# example11.py -import plac -from annotations import Positional - -@plac.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 plac; plac.call(main) diff --git a/plac/doc/example12.help b/plac/doc/example12.help deleted file mode 100644 index fec1715..0000000 --- a/plac/doc/example12.help +++ /dev/null @@ -1,9 +0,0 @@ -usage: example12.py [-h] [-opt OPT] [args [args ...]] [kw [kw ...]] - -positional arguments: - args default arguments - kw keyword arguments - -optional arguments: - -h, --help show this help message and exit - -opt OPT some option diff --git a/plac/doc/example12.py b/plac/doc/example12.py deleted file mode 100644 index 56c65a9..0000000 --- a/plac/doc/example12.py +++ /dev/null @@ -1,18 +0,0 @@ -# example12.py -import plac - -@plac.annotations( - opt=('some option', 'option'), - args='default arguments', - kw='keyword arguments') -def main(opt, *args, **kw): - if opt: - yield 'opt=%s' % opt - if args: - yield 'args=%s' % str(args) - if kw: - yield 'kw=%s' % kw - -if __name__ == '__main__': - for output in plac.call(main): - print(output) diff --git a/plac/doc/example13.help b/plac/doc/example13.help deleted file mode 100644 index c931651..0000000 --- a/plac/doc/example13.help +++ /dev/null @@ -1,13 +0,0 @@ -usage: example13.py [-h] {status,commit,checkout,help} ... - -A Fake Version Control System - -optional arguments: - -h, --help show this help message and exit - -subcommands: - {status,commit,checkout,help} - checkout - commit - status - help diff --git a/plac/doc/example13.py b/plac/doc/example13.py deleted file mode 100644 index 9982fdd..0000000 --- a/plac/doc/example13.py +++ /dev/null @@ -1,28 +0,0 @@ -import plac - -class FVCS(object): - "A Fake Version Control System" - commands = 'checkout', 'commit', 'status', 'help' - add_help = True - - @plac.annotations( - name=('a recognized command', 'positional', None, str, commands)) - def help(self, name): - print(plac.parser_from(self).help_cmd(name)) - - @plac.annotations( - url=('url of the source code', 'positional')) - def checkout(self, url): - print('checkout', url) - - def commit(self): - print('commit') - - @plac.annotations(quiet=('summary information', 'flag')) - def status(self, quiet): - print('status', quiet) - -main = FVCS() - -if __name__ == '__main__': - plac.call(main) diff --git a/plac/doc/example2.py b/plac/doc/example2.py deleted file mode 100644 index 56aa23c..0000000 --- a/plac/doc/example2.py +++ /dev/null @@ -1,12 +0,0 @@ -# example2.py -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/plac/doc/example3.help b/plac/doc/example3.help deleted file mode 100644 index dd4d96a..0000000 --- a/plac/doc/example3.help +++ /dev/null @@ -1,9 +0,0 @@ -usage: example3.py [-h] dsn - -Do something with the database - -positional arguments: - dsn - -optional arguments: - -h, --help show this help message and exit diff --git a/plac/doc/example3.py b/plac/doc/example3.py deleted file mode 100644 index 979b1a4..0000000 --- a/plac/doc/example3.py +++ /dev/null @@ -1,8 +0,0 @@ -# example3.py -def main(dsn): - "Do something with the database" - print(dsn) - # ... - -if __name__ == '__main__': - import plac; plac.call(main) diff --git a/plac/doc/example4.py b/plac/doc/example4.py deleted file mode 100644 index 2e76b65..0000000 --- a/plac/doc/example4.py +++ /dev/null @@ -1,15 +0,0 @@ -# example4.py -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/plac/doc/example5.help b/plac/doc/example5.help deleted file mode 100644 index ff57733..0000000 --- a/plac/doc/example5.help +++ /dev/null @@ -1,11 +0,0 @@ -usage: example5.py [-h] dsn [table] [today] - -Do something on the database - -positional arguments: - dsn - table [product] - today [YYYY-MM-DD] - -optional arguments: - -h, --help show this help message and exit diff --git a/plac/doc/example5.py b/plac/doc/example5.py deleted file mode 100644 index a2de37c..0000000 --- a/plac/doc/example5.py +++ /dev/null @@ -1,9 +0,0 @@ -# example5.py -from datetime import date - -def main(dsn, table='product', today=date.today()): - "Do something on the database" - print(dsn, table, today) - -if __name__ == '__main__': - import plac; plac.call(main) diff --git a/plac/doc/example6.help b/plac/doc/example6.help deleted file mode 100644 index c950e78..0000000 --- a/plac/doc/example6.help +++ /dev/null @@ -1,8 +0,0 @@ -usage: example6.py [-h] [-command COMMAND] dsn - -positional arguments: - dsn - -optional arguments: - -h, --help show this help message and exit - -command COMMAND SQL query diff --git a/plac/doc/example6.py b/plac/doc/example6.py deleted file mode 100644 index 045839b..0000000 --- a/plac/doc/example6.py +++ /dev/null @@ -1,6 +0,0 @@ -# example6.py -def main(dsn, command: ("SQL query", 'option')): - print('executing %r on %s' % (command, dsn)) - -if __name__ == '__main__': - import plac; plac.call(main) diff --git a/plac/doc/example7.help b/plac/doc/example7.help deleted file mode 100644 index 0834858..0000000 --- a/plac/doc/example7.help +++ /dev/null @@ -1,10 +0,0 @@ -usage: example7.py [-h] dsn [scripts [scripts ...]] - -Run the given scripts on the database - -positional arguments: - dsn - scripts - -optional arguments: - -h, --help show this help message and exit diff --git a/plac/doc/example7.py b/plac/doc/example7.py deleted file mode 100644 index 475799d..0000000 --- a/plac/doc/example7.py +++ /dev/null @@ -1,11 +0,0 @@ -# example7.py -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 plac; plac.call(main) diff --git a/plac/doc/example7_.help b/plac/doc/example7_.help deleted file mode 100644 index 6f0f4b8..0000000 --- a/plac/doc/example7_.help +++ /dev/null @@ -1,10 +0,0 @@ -usage: example7_.py [-h] dsn [scripts [scripts ...]] - -Run the given scripts on the database - -positional arguments: - dsn Database dsn - scripts SQL scripts - -optional arguments: - -h, --help show this help message and exit diff --git a/plac/doc/example7_.py b/plac/doc/example7_.py deleted file mode 100644 index 550a524..0000000 --- a/plac/doc/example7_.py +++ /dev/null @@ -1,11 +0,0 @@ -# example7_.py -from datetime import datetime - -def main(dsn: "Database dsn", *scripts: "SQL scripts"): - "Run the given scripts on the database" - for script in scripts: - print('executing %s' % script) - # ... - -if __name__ == '__main__': - import plac; plac.call(main) diff --git a/plac/doc/example8.help b/plac/doc/example8.help deleted file mode 100644 index a566258..0000000 --- a/plac/doc/example8.help +++ /dev/null @@ -1,9 +0,0 @@ -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 diff --git a/plac/doc/example8.py b/plac/doc/example8.py deleted file mode 100644 index 1dad399..0000000 --- a/plac/doc/example8.py +++ /dev/null @@ -1,8 +0,0 @@ -# example8.py -def main(command: ("SQL query", 'option', 'c'), dsn): - if command: - print('executing %s on %s' % (command, dsn)) - # ... - -if __name__ == '__main__': - import plac; plac.call(main) diff --git a/plac/doc/example8_.help b/plac/doc/example8_.help deleted file mode 100644 index 872b05a..0000000 --- a/plac/doc/example8_.help +++ /dev/null @@ -1,9 +0,0 @@ -usage: example8_.py [-h] [-command select * from table] dsn - -positional arguments: - dsn - -optional arguments: - -h, --help show this help message and exit - -command select * from table - SQL query diff --git a/plac/doc/example8_.py b/plac/doc/example8_.py deleted file mode 100644 index cdea364..0000000 --- a/plac/doc/example8_.py +++ /dev/null @@ -1,6 +0,0 @@ -# example8_.py -def main(dsn, command: ("SQL query", 'option')='select * from table'): - print('executing %r on %s' % (command, dsn)) - -if __name__ == '__main__': - import plac; plac.call(main) diff --git a/plac/doc/example9.help b/plac/doc/example9.help deleted file mode 100644 index cda79b3..0000000 --- a/plac/doc/example9.help +++ /dev/null @@ -1,8 +0,0 @@ -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 diff --git a/plac/doc/example9.py b/plac/doc/example9.py deleted file mode 100644 index 8e39eff..0000000 --- a/plac/doc/example9.py +++ /dev/null @@ -1,9 +0,0 @@ -# example9.py - -def main(verbose: ('prints more info', 'flag', 'v'), dsn: 'connection string'): - if verbose: - print('connecting to %s' % dsn) - # ... - -if __name__ == '__main__': - import plac; plac.call(main) diff --git a/plac/doc/importer1.py b/plac/doc/importer1.py deleted file mode 100644 index 233f910..0000000 --- a/plac/doc/importer1.py +++ /dev/null @@ -1,20 +0,0 @@ -import time -import plac - -class FakeImporter(object): - "A fake importer with an import_file command" - commands = ['import_file'] - def __init__(self, dsn): - self.dsn = dsn - def import_file(self, fname): - "Import a file into the database" - try: - for n in range(10000): - time.sleep(.01) - if n % 100 == 99: - yield 'Imported %d lines' % (n+1) - finally: - print('closing the file') - -if __name__ == '__main__': - plac.Interpreter.call(FakeImporter) diff --git a/plac/doc/importer2.py b/plac/doc/importer2.py deleted file mode 100644 index 61a99b1..0000000 --- a/plac/doc/importer2.py +++ /dev/null @@ -1,22 +0,0 @@ -import time -import plac - -class FakeImporter(object): - "A fake importer with an import_file command" - thcommands = ['import_file'] - def __init__(self, dsn): - self.dsn = dsn - def import_file(self, fname): - "Import a file into the database" - try: - for n in range(10000): - time.sleep(.02) - if n % 100 == 99: # every two seconds - yield 'Imported %d lines' % (n+1) - if n % 10 == 9: # every 0.2 seconds - yield # go back and check the TOBEKILLED status - finally: - print('closing the file') - -if __name__ == '__main__': - plac.Interpreter.call(FakeImporter) diff --git a/plac/doc/importer3.py b/plac/doc/importer3.py deleted file mode 100644 index 2a47e99..0000000 --- a/plac/doc/importer3.py +++ /dev/null @@ -1,20 +0,0 @@ -import time -import plac - -class FakeImporter(object): - "A fake importer with an import_file command" - mpcommands = ['import_file'] - def __init__(self, dsn): - self.dsn = dsn - def import_file(self, fname): - "Import a file into the database" - try: - for n in range(10000): - time.sleep(.02) - if n % 100 == 99: - yield 'Imported %d lines' % (n+1) - finally: - print('closing the file') - -if __name__ == '__main__': - plac.Interpreter.call(FakeImporter) diff --git a/plac/doc/importer_ui.py b/plac/doc/importer_ui.py deleted file mode 100644 index 0a5cc9c..0000000 --- a/plac/doc/importer_ui.py +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import with_statement -from Tkinter import * -from importer3 import FakeImporter - -def taskwidget(root, task, tick=500): - "A Label widget showing the output of a task every 500 ms" - sv = StringVar(root) - lb = Label(root, textvariable=sv) - def show_outlist(): - try: - out = task.outlist[-1] - except IndexError: # no output yet - out = '' - sv.set('%s %s' % (task, out)) - root.after(tick, show_outlist) - root.after(0, show_outlist) - return lb - -def monitor(tasks): - root = Tk() - for task in tasks: - task.run() - taskwidget(root, task).pack() - root.mainloop() - -if __name__ == '__main__': - import plac - with plac.Interpreter(plac.call(FakeImporter)) as i: - tasks = [i.submit('import_file f1'), i.submit('import_file f2')] - monitor(tasks) diff --git a/plac/doc/ishelve.help b/plac/doc/ishelve.help deleted file mode 100644 index d68a099..0000000 --- a/plac/doc/ishelve.help +++ /dev/null @@ -1,18 +0,0 @@ -usage: ishelve.py [.help] [.showall] [.clear] [.delete DELETE] - [.filename /home/micheles/conf.shelve] - [params [params ...]] [setters [setters ...]] - -Simple interface to a shelve - -positional arguments: - params names of the parameters in the shelve - setters setters param=value - -optional arguments: - .help show help - .showall show all parameters in the shelve - .clear clear the shelve - .delete DELETE delete an element - .filename /home/micheles/conf.shelve - filename of the shelve - diff --git a/plac/doc/ishelve.plac b/plac/doc/ishelve.plac deleted file mode 100644 index ef378b3..0000000 --- a/plac/doc/ishelve.plac +++ /dev/null @@ -1,7 +0,0 @@ -#!ishelve.py -.clear -a=1 b=2 -.show -.del a -.dl b -.show diff --git a/plac/doc/ishelve.placet b/plac/doc/ishelve.placet deleted file mode 100644 index 2637223..0000000 --- a/plac/doc/ishelve.placet +++ /dev/null @@ -1,13 +0,0 @@ -#!ishelve.py -i> .clear # start from a clean state -cleared the shelve -i> a=1 -setting a=1 -i> a -1 -i> .del a -deleted a -i> a -a: not found -i> .cler # spelling error -.cler: not found diff --git a/plac/doc/ishelve.py b/plac/doc/ishelve.py deleted file mode 100644 index 4f22ba2..0000000 --- a/plac/doc/ishelve.py +++ /dev/null @@ -1,54 +0,0 @@ -# ishelve.py -import os, shelve, plac - -DEFAULT_SHELVE = os.path.expanduser('~/conf.shelve') - -@plac.annotations( - help=('show help', 'flag'), - showall=('show all parameters in the shelve', 'flag'), - clear=('clear the shelve', 'flag'), - delete=('delete an element', 'option'), - filename=('filename of the shelve', 'option'), - params='names of the parameters in the shelve', - setters='setters param=value') -def main(help, showall, clear, delete, filename=DEFAULT_SHELVE, - *params, **setters): - "A simple interface to a shelve. Use .help to see the available commands." - sh = shelve.open(filename) - try: - if not any([help, showall, clear, delete, params, setters]): - yield 'no arguments passed, use .help to see the available commands' - elif help: # custom help - yield 'Commands: .help, .showall, .clear, .delete' - yield ' ...' - yield ' ...' - elif showall: - for param, name in sh.items(): - yield '%s=%s' % (param, name) - elif clear: - sh.clear() - yield 'cleared the shelve' - elif delete: - try: - del sh[delete] - except KeyError: - yield '%s: not found' % delete - else: - yield 'deleted %s' % delete - for param in params: - try: - yield sh[param] - except KeyError: - yield '%s: not found' % param - for param, value in setters.items(): - sh[param] = value - yield 'setting %s=%s' % (param, value) - finally: - sh.close() - -main.add_help = False # there is a custom help, remove the default one -main.prefix_chars = '.' # use dot-prefixed commands - -if __name__ == '__main__': - for output in plac.call(main): - print(output) diff --git a/plac/doc/ishelve2.help b/plac/doc/ishelve2.help deleted file mode 100644 index ade74c0..0000000 --- a/plac/doc/ishelve2.help +++ /dev/null @@ -1,8 +0,0 @@ -usage: ishelve2.py [-h] [-configfile CONFIGFILE] - -A minimal interface over a shelve object. - -optional arguments: - -h, --help show this help message and exit - -configfile CONFIGFILE - path name of the shelve diff --git a/plac/doc/ishelve2.plac b/plac/doc/ishelve2.plac deleted file mode 100644 index 5ca0064..0000000 --- a/plac/doc/ishelve2.plac +++ /dev/null @@ -1,4 +0,0 @@ -#!ishelve2.py:ShelveInterface -c ~/conf.shelve -set a 1 -del a -del a # intentional error diff --git a/plac/doc/ishelve2.placet b/plac/doc/ishelve2.placet deleted file mode 100644 index fdc89c4..0000000 --- a/plac/doc/ishelve2.placet +++ /dev/null @@ -1,9 +0,0 @@ -#!ishelve2.py:ShelveInterface -configfile=~/test.shelve -i> del -deleting everything -i> set a 1 -setting a=1 -i> set b 2 -setting b=2 -i> show a -a = 1 diff --git a/plac/doc/ishelve2.py b/plac/doc/ishelve2.py deleted file mode 100644 index dbc5d68..0000000 --- a/plac/doc/ishelve2.py +++ /dev/null @@ -1,43 +0,0 @@ -# ishelve2.py -import shelve, os, sys, plac - -class ShelveInterface(object): - "A minimal interface over a shelve object." - commands = 'set', 'show', 'showall', 'delete' - @plac.annotations( - configfile=('path name of the shelve', 'option')) - def __init__(self, configfile): - self.configfile = configfile or '~/conf.shelve' - self.fname = os.path.expanduser(self.configfile) - self.__doc__ += '\nOperating on %s.\nUse help to see '\ - 'the available commands.\n' % self.fname - def __enter__(self): - self.sh = shelve.open(self.fname) - return self - def __exit__(self, etype, exc, tb): - self.sh.close() - def set(self, name, value): - "set name value" - yield 'setting %s=%s' % (name, value) - self.sh[name] = value - def show(self, *names): - "show given parameters" - for name in names: - yield '%s = %s' % (name, self.sh[name]) # no error checking - def showall(self): - "show all parameters" - for name in self.sh: - yield '%s = %s' % (name, self.sh[name]) - def delete(self, name=None): - "delete given parameter (or everything)" - if name is None: - yield 'deleting everything' - self.sh.clear() - else: - yield 'deleting %s' % name - del self.sh[name] # no error checking - -main = ShelveInterface # useful for the tests - -if __name__ == '__main__': - plac.Interpreter.call(ShelveInterface) diff --git a/plac/doc/ishelve3.py b/plac/doc/ishelve3.py deleted file mode 100644 index c37178b..0000000 --- a/plac/doc/ishelve3.py +++ /dev/null @@ -1,5 +0,0 @@ -# ishelve3.py -from ishelve2 import ShelveInterface as main - -if __name__ == '__main__': - import plac; plac.Interpreter.call(main) diff --git a/plac/doc/picalculator.py b/plac/doc/picalculator.py deleted file mode 100644 index 75df2b2..0000000 --- a/plac/doc/picalculator.py +++ /dev/null @@ -1,63 +0,0 @@ -from __future__ import with_statement -from random import random -import multiprocessing -import plac - -class PiCalculator(object): - """Compute pi in parallel with threads or processes""" - - @plac.annotations( - npoints=('number of integration points', 'positional', None, int), - mode=('sequential|parallel|threaded', 'option', 'm', str, 'SPT')) - def __init__(self, npoints, mode='S'): - self.npoints = npoints - if mode == 'P': - self.mpcommands = ['calc_pi'] - elif mode == 'T': - self.thcommands = ['calc_pi'] - elif mode == 'S': - self.commands = ['calc_pi'] - self.n_cpu = multiprocessing.cpu_count() - - def submit_tasks(self): - self.i = plac.Interpreter(self).__enter__() - return [self.i.submit('calc_pi %d' % (self.npoints / self.n_cpu)) - for _ in range(self.n_cpu)] - - def close(self): - self.i.close() - - @plac.annotations( - npoints=('npoints', 'positional', None, int)) - def calc_pi(self, npoints): - counts = 0 - for j in xrange(npoints): - n, r = divmod(j, 1000000) - if r == 0: - yield '%dM iterations' % n - x, y = random(), random() - if x*x + y*y < 1: - counts += 1 - yield (4.0 * counts)/npoints - - def run(self): - tasks = self.i.tasks() - for t in tasks: - t.run() - try: - total = 0 - for task in tasks: - total += task.result - except: # the task was killed - print tasks - return - return total / self.n_cpu - -if __name__ == '__main__': - pc = plac.call(PiCalculator) - pc.submit_tasks() - try: - import time; t0 = time.time() - print '%f in %f seconds ' % (pc.run(), time.time() - t0) - finally: - pc.close() diff --git a/plac/doc/plac.el b/plac/doc/plac.el deleted file mode 100644 index 0eddcbb..0000000 --- a/plac/doc/plac.el +++ /dev/null @@ -1,77 +0,0 @@ -;;; Emacs-plac integration: add the following to your .emacs - -(define-generic-mode 'plac-mode - '("#") ; comment chars - '(); highlighted commands - nil - '(".plac\\'"); file extensions - nil) - -(add-hook 'plac-mode-hook (lambda () (local-set-key [f4] 'plac-start))) -(add-hook 'plac-mode-hook (lambda () (local-set-key [f5] 'plac-send))) -(add-hook 'plac-mode-hook (lambda () (local-set-key [f6] 'plac-stop))) - -(defconst terminator 59); ASCII code for the semicolon -(defvar *plac-process* nil) - -(defun plac-start () - "Start an inferior plac process by inferring the script to use from the - shebang line" - (interactive) - (let ((shebang-line - (save-excursion - (goto-line 1) (end-of-line) - (buffer-substring-no-properties 3 (point))))) - (if *plac-process* (princ "plac already started") - (setq *plac-process* - (start-process - "plac" "*plac*" "plac_runner.py" "-m" shebang-line)))) - (display-buffer "*plac*")) - -;(defun plac-send () -; "Send the current region to the inferior plac process" -; (interactive) -; (save-excursion (set-buffer "*plac*") (erase-buffer)) -; (process-send-region *plac-process* (region-beginning) (region-end))) - -(defun current-paragraph-beg-end () - "Returns the extrema of the current paragraph, delimited by semicolons" - (interactive) - (save-excursion - (let ((beg (save-excursion (goto-line 2) (point))); skip the shebang - (end (point-max))) - ;; go backward - (while (> (point) beg) - (goto-char (1- (point))) - (if (= terminator (following-char)) - (setq beg (point)))) - (if (= terminator (following-char)) - (setq beg (1+ beg))) - ;; go forward - (while (< (point) end) - (goto-char (1+ (point))) - (if (= 59 (following-char)) - (setq end (point)))) - (if (= 59 (following-char)) - (setq end (1+ end))) - (list beg end)))) - -(defun plac-send () - "Send the current region to the inferior plac process" - (interactive) - (save-excursion (set-buffer "*plac*") (erase-buffer)) - (let ((p (apply 'buffer-substring-no-properties (current-paragraph-beg-end)))) - (message p) - (process-send-string *plac-process* (concat p "\n")))) - ;(switch-to-buffer-other-window "*plac*"))) - ;(save-excursion (set-buffer "*plac*") - ; (set-window-start (selected-window) 1 nil)))) - -(defun plac-stop () - "Stop the inferior plac process by sending to it an EOF" - (interactive) - (process-send-eof *plac-process*) - (setq *plac-process* nil) - "killed *plac-process*") - -(provide 'plac) diff --git a/plac/doc/plac.html b/plac/doc/plac.html index a0c738c..d53ecf8 100644 --- a/plac/doc/plac.html +++ b/plac/doc/plac.html @@ -5,3034 +5,10 @@ - -
- - -
-

Plac: Parsing the Command Line the Easy Way

- --- - - - - - - - - - - - - - - - - - -
Author:Michele Simionato
E-mail:michele.simionato@gmail.com
Date:June 2011
Download page:http://pypi.python.org/pypi/plac
Project page:http://plac.googlecode.com/hg/doc/plac.html
Requires:Python 2.3+
Installation:easy_install -U plac
License:BSD license
- -
-

The importance of scaling down

-

There is no want of command line arguments parsers in the Python -world. The standard library alone contains three different modules: -getopt (from the stone age), -optparse (from Python 2.3) and argparse (from Python 2.7). All of -them are quite powerful and especially argparse is an industrial -strength solution; unfortunately, all of them feature a non-zero learning -curve and a certain verbosity. They do not scale down well, at -least in my opinion.

-

It should not be necessary to stress the importance scaling down; -nevertheless, a lot of people are obsessed with features and concerned with -the possibility of scaling up, forgetting the equally important -issue of scaling down. This is an old meme in -the computing world: programs should address the common cases simply and -simple things should be kept simple, while at the same keeping -difficult things possible. plac adhere as much as possible to this -philosophy and it is designed to handle well the simple cases, while -retaining the ability to handle complex cases by relying on the -underlying power of argparse.

-

Technically plac is just a simple wrapper over argparse which hides -most of its complexity by using a declarative interface: the argument -parser is inferred rather than written down by imperatively. Still, plac is -surprisingly scalable upwards, even without using the underlying -argparse. I have been using Python for 8 years and in my experience -it is extremely unlikely that you will ever need to go beyond the -features provided by the declarative interface of plac: they should -be more than enough for 99.9% of the use cases.

-

plac is targetting especially unsophisticated users, -programmers, sys-admins, scientists and in general people writing -throw-away scripts for themselves, choosing the command line -interface because it is the quick and simple. Such users are not -interested in features, they are interested in a small learning curve: -they just want to be able to write a simple command line tool from a -simple specification, not to build a command-line parser by -hand. Unfortunately, the modules in the standard library forces them -to go the hard way. They are designed to implement power user tools -and they have a non-trivial learning curve. On the contrary, plac -is designed to be simple to use and extremely concise, as the examples -below will show.

-
-
-

Scripts with required arguments

-

Let me start with the simplest possible thing: a script that takes a -single argument and does something to it. It cannot get simpler -than that, unless you consider a script without command-line -arguments, where there is nothing to parse. Still, it is a use -case extremely common: I need to write scripts like that nearly -every day, I wrote hundreds of them in the last few years and I have -never been happy. Here is a typical example of code I have been -writing by hand for years:

-
-# example1.py
-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:]))
-
-
-

As you see the whole if __name__ == '__main__' block (nine lines) -is essentially boilerplate that should not exist. Actually I think -the language should recognize the main function and pass to it the -command-line arguments automatically; unfortunaly this is unlikely to -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 does not help for this -incredibly common use case. 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:

-
-# example2.py
-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)
-
-
-

However, it just feels too complex to instantiate a class and to -define a parser by hand for such a trivial task.

-

The plac 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 -plac module all you need to write is

-
-# example3.py
-def main(dsn):
-    "Do something with the database"
-    print(dsn)
-    # ...
- 
-if __name__ == '__main__':
-    import plac; plac.call(main)
-
-
-

The plac 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
-
-Do something with the database
-
-positional arguments:
-  dsn
-
-optional arguments:
-  -h, --help  show this help message and exit
-
-
-

Moreover plac manages the case of missing arguments and of too many arguments. -This is only the tip of the iceberg: plac is able to do much more than that.

-
-
-

Scripts with default arguments

-

The need to have suitable defaults for command-line scripts is quite -common. For instance I have encountered this use case at work hundreds -of times:

-
-# example4.py
-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)
-
-
-

Here I want to perform a query on a database table, by extracting the -most recent data: it makes sense for today to be a default argument. -If there is a most used table (in this example a table called 'product') -it also makes sense to make it a default argument. Performing the parsing -of the command-line arguments by hand takes 8 ugly lines of boilerplate -(using argparse would require about the same number of lines). -With plac the entire __main__ block reduces to the usual two lines:

-
-if __name__ == '__main__':
-    import plac; plac.call(main)
-
-

In other words, six lines of boilerplate have been removed, and we get -the usage message for free:

-
-usage: example5.py [-h] dsn [table] [today]
-
-Do something on the database
-
-positional arguments:
-  dsn
-  table       [product]
-  today       [YYYY-MM-DD]
-
-optional arguments:
-  -h, --help  show this help message and exit
-
-
-

Notice that by default plac prints the string representation -of the default values (with square brackets) in the usage message. -plac 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:

-
-# example7.py
-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 plac; plac.call(main)
-
-
-

Here is the usage message:

-
-usage: example7.py [-h] dsn [scripts [scripts ...]]
-
-Run the given scripts on the database
-
-positional arguments:
-  dsn
-  scripts
-
-optional arguments:
-  -h, --help  show this help message and exit
-
-
-

The examples here should have made clear that plac is able to figure out -the command-line arguments parser to use from the signature of the main -function. This is the whole idea behind plac: if the intent is clear, -let's the machine take care of the details.

-

plac is inspired to an old Python Cookbook recipe of mine (optionparse), in -the sense that it delivers the programmer from the burden of writing -the parser, but is less of a hack: instead of extracting the parser -from the docstring of the module, it extracts it from the signature of -the main function.

-

The idea comes from the function annotations concept, a new -feature of Python 3. An example is worth a thousand words, so here -it is:

-
-# example7_.py
-from datetime import datetime
-
-def main(dsn: "Database dsn", *scripts: "SQL scripts"):
-    "Run the given scripts on the database"
-    for script in scripts:
-        print('executing %s' % script)
-        # ...
-
-if __name__ == '__main__':
-    import plac; plac.call(main)
-
-
-

Here the arguments of the main function have been annotated with -strings which are intented to be used in the help message:

-
-usage: example7_.py [-h] dsn [scripts [scripts ...]]
-
-Run the given scripts on the database
-
-positional arguments:
-  dsn         Database dsn
-  scripts     SQL scripts
-
-optional arguments:
-  -h, --help  show this help message and exit
-
-
-

plac is able to recognize much more complex annotations, as -I will show in the next paragraphs.

-
-
-

Scripts with options (and smart options)

-

It is surprising how few command-line scripts with options I have -written over the years (probably less than a hundred), compared to the -number of scripts with positional arguments I wrote (certainly more -than a thousand of them). Still, this use case cannot be neglected. -The standard library modules (all of them) are quite verbose when it -comes to specifying the options and frankly I have never used them -directly. Instead, I have always relied on the -optionparse recipe, which provides a convenient wrapper over -optionparse. Alternatively, in the simplest cases, I have just -performed the parsing by hand. In plac the parser is inferred by the -function annotations. Here is an example:

-
-# example8.py
-def main(command: ("SQL query", 'option', 'c'), dsn):
-    if command:
-        print('executing %s on %s' % (command, dsn))
-        # ...
-
-if __name__ == '__main__':
-    import plac; plac.call(main)
-
-
-

Here the argument command has been annotated with the tuple -("SQL query", 'option', 'c'): the first string is the help string -which will appear in the usage message, the second string tells plac -that command is an option and the third string that there is also -a short form of the option -c, the long form being --command. -The usage message is the following:

-
-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
-
-

The third argument in the function annotation can be omitted: in such -case it will be assumed to be None. The consequence is that -the usual dichotomy between long and short options (GNU-style options) -disappears: we get smart options, which have the single character prefix -of short options and behave like both long and short options, since -they can be abbreviated. Here is an example featuring smart options:

-
-# example6.py
-def main(dsn, command: ("SQL query", 'option')):
-    print('executing %r on %s' % (command, dsn))
-
-if __name__ == '__main__':
-    import plac; plac.call(main)
-
-
-
-usage: example6.py [-h] [-command COMMAND] dsn
-
-positional arguments:
-  dsn
-
-optional arguments:
-  -h, --help        show this help message and exit
-  -command COMMAND  SQL query
-
-
-

The following are all valid invocations ot the script:

-
-$ python3 example6.py -c "select" dsn
-executing 'select' on dsn
-$ python3 example6.py -com "select" dsn
-executing 'select' on dsn
-$ python3 example6.py -command="select" dsn
-executing 'select' on dsn
-
-

Notice that the form -command=SQL is recognized only for the full -option, not for its abbreviations:

-
-$ python3 example6.py -com="select" dsn
-usage: example6.py [-h] [-command COMMAND] dsn
-example6.py: error: unrecognized arguments: -com=select
-
-

If the option is not passed, the variable command -will get the value None. However, it is possible to specify a non-trivial -default. Here is an example:

-
-# example8_.py
-def main(dsn, command: ("SQL query", 'option')='select * from table'):
-    print('executing %r on %s' % (command, dsn))
-
-if __name__ == '__main__':
-    import plac; plac.call(main)
-
-
-

Notice that the default value appears in the help message:

-
-usage: example8_.py [-h] [-command select * from table] dsn
-
-positional arguments:
-  dsn
-
-optional arguments:
-  -h, --help            show this help message and exit
-  -command select * from table
-                        SQL query
-
-
-

When you run the script and you do not pass the -command option, the -default query will be executed:

-
-$ python3 example8_.py dsn
-executing 'select * from table' on dsn
-
-
-
-

Scripts with flags

-

plac is able to recognize 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:

-
-# example9.py
-
-def main(verbose: ('prints more info', 'flag', 'v'), dsn: 'connection string'):
-    if verbose:
-        print('connecting to %s' % dsn)
-    # ...
-
-if __name__ == '__main__':
-    import plac; plac.call(main)
-
-
-
-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
-
-
-
-$ python3 example9.py -v dsn
-connecting to dsn
-
-

Notice that it is an error trying to specify a default for flags: the -default value for a flag is always False. If you feel the need to -implement non-boolean flags, you should use an option with two -choices, as explained in the "more features" section.

-

For consistency with the way the usage message is printed, I suggest -you to follow the Flag-Option-Required-Default (FORD) convention: in -the main function write first the flag arguments, then the option -arguments, then the required arguments and finally the default -arguments. This is just a convention and you are not forced to use it, -except for the default arguments (including the varargs) which must -stay at the end as it is required by the Python syntax.

-

I also suggests to specify a one-character abbreviation for flags: in -this way you can use the GNU-style composition of flags (i.e. -zxvf -is an abbreviation of -z -x -v -f). I usually do not provide -the one-character abbreviation for options, since it does not make sense -to compose them.

-
-
-

plac for Python 2.X users

-

I do not use Python 3. At work we are just starting to think about -migrating to Python 2.6. It will take years before we -think to migrate to Python 3. I am pretty much sure most Pythonistas -are in the same situation. Therefore plac provides a way to work -with function annotations even in Python 2.X (including Python 2.3). -There is no magic involved; you just need to add the annotations -by hand. For instance the annotated function declaration

-
-def main(dsn: "Database dsn", *scripts: "SQL scripts"):
-    ...
-
-

is equivalent to the following code:

-
-def main(dsn, *scripts):
-    ...
-main.__annotations__ = dict(
-    dsn="Database dsn",
-    scripts="SQL scripts")
-
-

One should be careful to match the keys of the annotation 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 -plac.annotations decorator that performs the check for you:

-
-@plac.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 plac.annotations decorator. Notice however -that the core features of plac run even on Python 2.3.

-
-
-

More features

-

One of the goals of plac is to have a learning curve of minutes for -its core features, compared to the learning curve of hours of -argparse. In order to reach this goal, I have not sacrificed all -the features of argparse. Actually a lot of argparse power persists -in plac. Until now, I have only showed simple annotations, but in -general an annotation is a 6-tuple of the form

-
-(help, kind, abbrev, type, choices, metavar)
-

where help is the help message, kind is a string in the set { -"flag", "option", "positional"}, abbrev is a -one-character string or None, type is a callable taking a -string in input, -choices is a discrete sequence of values and metavar is a string.

-

type is used to automagically convert the command line arguments -from the string type to any Python type; by default there is no -conversion and type=None.

-

choices is used to restrict the number of the valid -options; by default there is no restriction i.e. choices=None.

-

metavar has two meanings. For a positional argument it is used to -change the argument name in the usage message (and only there). By -default the metavar is None and the name in the usage message is -the same as the argument name. For an option -the metavar is used differently in the usage message, which has -now the form [--option-name METAVAR]. If the metavar is None, -then it is equal to the uppercased name of the argument, unless the -argument has a default and in such a case is equal to the stringified -form of the default.

-

Here is an example showing many of the features (copied from the -argparse documentation):

-
-# example10.py
-import plac
-
-@plac.annotations(
-operator=("The name of an operator", 'positional', None, str, ['add', 'mul']),
-numbers=("A number", 'positional', None, float, None, "n"))
-def main(operator, *numbers):
-    "A script to add and multiply numbers"
-    if operator == 'mul':
-        op = float.__mul__
-        result = 1.0
-    else: # operator == 'add'
-        op = float.__add__
-        result = 0.0
-    for n in numbers:
-        result = op(result, n)
-    return result
-
-if __name__ == '__main__':
-    print(plac.call(main))
-
-
-

Here is the usage:

-
-usage: example10.py [-h] {add,mul} [n [n ...]]
-
-A script to add and multiply numbers
-
-positional arguments:
-  {add,mul}   The name of an operator
-  n           A number
-
-optional arguments:
-  -h, --help  show this help message and exit
-
-
-

Notice that the docstring of the main function has been automatically added -to the usage message. 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')
-
-

plac.call can also be used in doctests like this:

-
->>> import plac, example10
->>> plac.call(example10.main, ['add', '1', '2'])
-3.0
-
-

plac.call works for generators too:

-
->>> def main(n):
-...     for i in range(int(n)):
-...         yield i
->>> plac.call(main, ['3'])
-[0, 1, 2]
-
-

Internally plac.call tries to convert the output of the main function -into a list, if possible. If the output is not iterable or it is a -string, it is left unchanged, but if it is iterable it is converted. -In particular, generator objects are exhausted by plac.call.

-

This behavior avoids mistakes like forgetting of applying -list(result) to the result of plac.call; moreover it makes -errors visible early, and avoids mistakes in code like the following:

-
-try:
-    result = plac.call(main, args)
-except:
-   # do something
-
-

Without the "listify" functionality, a main function returning a -generator object would not raise any exception until the generator -is iterated over.

-

If you are a fan of lazyness, you can still have it by setting the eager -flag to False, as in the following example:

-
-for line in plac.call(main, args, eager=False):
-    print(line)
-
-

If main returns a generator object this example will print each -line as soon as available, whereas the default behaviour is to print -all the lines together and the end of the computation.

-
-
-

A realistic example

-

Here is a more realistic script using most of the features of plac to -run SQL queries on a database by relying on SQLAlchemy. Notice the usage -of the type feature to automagically convert a SQLAlchemy connection -string into a SqlSoup object:

-
-# dbcli.py
-import plac
-from sqlalchemy.ext.sqlsoup import SqlSoup
-
-@plac.annotations(
-    db=("Connection string", 'positional', None, SqlSoup),
-    header=("Header", 'flag', 'H'),
-    sqlcmd=("SQL command", 'option', 'c', str, None, "SQL"),
-    delimiter=("Column separator", 'option', 'd'),
-    scripts="SQL scripts",
-    )
-def main(db, header, sqlcmd, delimiter="|", *scripts):
-    "A script to run queries and SQL scripts on a database"
-    yield 'Working on %s' % db.bind.url
-
-    if sqlcmd:
-        result = db.bind.execute(sqlcmd)
-        if header: # print the header
-            yield delimiter.join(result.keys())
-        for row in result: # print the rows
-            yield delimiter.join(map(str, row))
-
-    for script in scripts:
-        db.bind.execute(file(script).read())
-        yield 'executed %s' % script
-
-if __name__ == '__main__':
-    for output in plac.call(main):
-        print(output)
-
-
-

You can see the yield-is-print pattern here: instead of using -print in the main function, I use yield, and I perform the -print in the __main__ block. The advantage of the pattern is that -tests invoking plac.call and checking the result become trivial: -had I performed the printing in the main function, the test would have -involved an ugly hack like redirecting sys.stdout to a -StringIO object.

-

Here is the usage message:

-
-usage: dbcli.py [-h] [-H] [-c SQL] [-d |] db [scripts [scripts ...]]
-
-A script to run queries and SQL scripts on a database
-
-positional arguments:
-  db                    Connection string
-  scripts               SQL scripts
-
-optional arguments:
-  -h, --help            show this help message and exit
-  -H, --header          Header
-  -c SQL, --sqlcmd SQL  SQL command
-  -d |, --delimiter |   Column separator
-
-
-

You can check for yourself that the script works.

-
-
-

Keyword arguments

-

Starting from release 0.4, plac supports keyword arguments. In -practice that means that if your main function has keyword arguments, -plac treats specially arguments of the form "name=value" in the -command line. Here is an example:

-
-# example12.py
-import plac
-
-@plac.annotations(
-   opt=('some option', 'option'),
-   args='default arguments',
-   kw='keyword arguments')
-def main(opt, *args, **kw):
-   if opt:
-      yield 'opt=%s' % opt
-   if args:
-      yield 'args=%s' % str(args)
-   if kw:
-      yield 'kw=%s' % kw
-
-if __name__ == '__main__':
-    for output in plac.call(main):
-       print(output)
-
-
-

Here is the generated usage message:

-
-usage: example12.py [-h] [-opt OPT] [args [args ...]] [kw [kw ...]]
-
-positional arguments:
-  args        default arguments
-  kw          keyword arguments
-
-optional arguments:
-  -h, --help  show this help message and exit
-  -opt OPT    some option
-
-
-

Here is how you call the script:

-
-$ python example12.py -o X a1 a2 name=value
-opt=X
-args=('a1', 'a2')
-kw={'name': 'value'}
-
-

When using keyword arguments, one must be careful to use names which -are not alreay taken; for instance in this examples the name opt -is taken:

-
-$ python example12.py 1 2 kw1=1 kw2=2 opt=0
-usage: example12.py [-h] [-o OPT] [args [args ...]] [kw [kw ...]]
-example12.py: error: colliding keyword arguments: opt
-
-

The names taken are the names of the flags, of the options, and of the -positional arguments, excepted varargs and keywords. This limitation -is a consequence of the way the argument names are managed in function calls -by the Python language.

-
-
-

Final example: a shelve interface

-

Here is a less trivial example for the keyword arguments feature. -The use case is the following: suppose we have stored the -configuration parameters of a given application into a Python shelve -and we need a command-line tool to edit the shelve. -A possible implementation using plac could be the following:

-
-# ishelve.py
-import os, shelve, plac
-
-DEFAULT_SHELVE = os.path.expanduser('~/conf.shelve')
-
-@plac.annotations(
-    help=('show help', 'flag'),
-    showall=('show all parameters in the shelve', 'flag'),
-    clear=('clear the shelve', 'flag'),
-    delete=('delete an element', 'option'),
-    filename=('filename of the shelve', 'option'),
-    params='names of the parameters in the shelve',
-    setters='setters param=value')
-def main(help, showall, clear, delete, filename=DEFAULT_SHELVE,
-         *params, **setters):
-    "A simple interface to a shelve. Use .help to see the available commands."
-    sh = shelve.open(filename)
-    try:
-        if not any([help, showall, clear, delete, params, setters]):
-            yield 'no arguments passed, use .help to see the available commands'
-        elif help: # custom help
-            yield 'Commands: .help, .showall, .clear, .delete'
-            yield '<param> ...'
-            yield '<param=value> ...'
-        elif showall:
-            for param, name in sh.items():
-                yield '%s=%s' % (param, name)
-        elif clear:
-            sh.clear()
-            yield 'cleared the shelve'
-        elif delete:
-            try:
-                del sh[delete]
-            except KeyError:
-                yield '%s: not found' % delete
-            else:
-                yield 'deleted %s' % delete
-        for param in params:
-            try:
-                yield sh[param]
-            except KeyError:
-                yield '%s: not found' % param           
-        for param, value in setters.items():
-            sh[param] = value
-            yield 'setting %s=%s' % (param, value)
-    finally:
-        sh.close()
-
-main.add_help = False # there is a custom help, remove the default one
-main.prefix_chars = '.' # use dot-prefixed commands
-
-if __name__ == '__main__':
-    for output in plac.call(main):
-        print(output)
-
-
-

A few notes are in order:

-
    -
  1. I have disabled the ordinary help provided by argparse and I have -implemented a custom help command.
  2. -
  3. I have changed the prefix character used to recognize the options -to a dot.
  4. -
  5. Keyword arguments recognition (in the **setters) is used to make it -possible to store a value in the shelve with the syntax -param_name=param_value.
  6. -
  7. *params are used to retrieve parameters from the shelve and some -error checking is performed in the case of missing parameters
  8. -
  9. A command to clear the shelve is implemented as a flag (.clear).
  10. -
  11. A command to delete a given parameter is implemented as an option -(.delete).
  12. -
  13. There is an option with default (.filename=conf.shelve) to store -the filename of the shelve.
  14. -
  15. All things considered, the code looks like a poor man object oriented -interface implemented with a chain of elifs instead of methods. Of course, -plac can do better than that, but let me start from a low-level approach -first.
  16. -
-

If you run ishelve.py without arguments you get the following -message:

-
-$ python ishelve.py
-no arguments passed, use .help to see the available commands
-
-

If you run ishelve.py with the option .h (or any abbreviation -of .help) you get:

-
-$ python ishelve.py .h
-Commands: .help, .showall, .clear, .delete
-<param> ...
-<param=value> ...
-
-

You can check by hand that the tool work:

-
-$ python ishelve.py .clear # start from an empty shelve
-cleared the shelve
-$ python ishelve.py a=1 b=2
-setting a=1
-setting b=2
-$ python ishelve.py .showall
-b=2
-a=1
-$ python ishelve.py .del b # abbreviation for .delete
-deleted b
-$ python ishelve.py a
-1
-$ python ishelve.py b
-b: not found
-$ python ishelve.py .cler # mispelled command
-usage: ishelve.py [.help] [.showall] [.clear] [.delete DELETE]
-                  [.filename /home/micheles/conf.shelve]
-                  [params [params ...]] [setters [setters ...]]
-ishelve.py: error: unrecognized arguments: .cler
-
-
-
-

plac vs argparse

-

plac is opinionated and by design it does not try to make available -all of the features of argparse in an easy way. In particular you -should be aware of the following limitations/differences (the -following assumes knowledge of argparse):

-
    -
  • plac does not support the destination concept: the destination -coincides with the name of the argument, always. This restriction -has some drawbacks. For instance, suppose you want to define a long -option called --yield. In this case the destination would be yield, -which is a Python keyword, and since you cannot introduce an -argument with that name in a function definition, it is impossible -to implement it. Your choices are to change the name of the long -option, or to use argparse with a suitable destination.
  • -
  • plac does not support "required options". As the argparse -documentation puts it: Required options are generally considered bad -form - normal users expect options to be optional. You should avoid -the use of required options whenever possible. Notice that since -argparse supports them, plac can manage them too, but not directly.
  • -
  • plac supports only regular boolean flags. argparse has the ability to -define generalized two-value flags with values different from True -and False. An earlier version of plac had this feature too, but -since you can use options with two choices instead, and in any case -the conversion from {True, False} to any couple of values -can be trivially implemented with a ternary operator -(value1 if flag else value2), I have removed it (KISS rules!).
  • -
  • plac does not support nargs options directly (it uses them internally, -though, to implement flag recognition). The reason it that all the use -cases of interest to me are covered by plac and did not feel the need -to increase the learning curve by adding direct support for nargs.
  • -
  • plac does support subparsers, but you must read the advanced usage -document to see how it works.
  • -
  • plac does not support actions directly. This also -looks like a feature too advanced for the goals of plac. Notice however -that the ability to define your own annotation objects (again, see -the advanced usage document) may mitigate the need for custom actions.
  • -
-

plac can leverage directly on many argparse features.

-

For instance, you can make invisible an argument in the usage message -simply by using '==SUPPRESS==' as help string (or -argparse.SUPPRESS). Similarly, you can use argparse.FileType -directly.

-

It is also possible to pass options to the underlying -argparse.ArgumentParser object (currently it accepts the default -arguments description, epilog, prog, usage, -add_help, argument_default, parents, prefix_chars, -fromfile_prefix_chars, conflict_handler, formatter_class). -It is enough to set such attributes on the main function. For -instance

-
-def main(...):
-    pass
-
-main.add_help = False
-
-

disables the recognition of the help flag -h, --help. This -mechanism does not look particularly elegant, but it works well -enough. I assume that the typical user of plac will be happy with -the defaults and would not want to change them; still it is possible -if she wants to.

-

For instance, by setting the description attribute, it is possible -to add a comment to the usage message (by default the docstring of the -main function is used as description).

-

It is also possible to change the option prefix; for -instance if your script must run under Windows and you want to use "/" -as option prefix you can add the line:

-
-main.prefix_chars='/-'
-
-

The first prefix char (/) is used -as the default for the recognition of options and flags; -the second prefix char (-) is kept to keep the -h/--help option -working: however you can disable it and reimplement it, if you like, -as seen in the ishelve example.

-

It is possible to access directly the underlying ArgumentParser object, by -invoking the plac.parser_from utility function:

-
->>> import plac
->>> def main(arg):
-...     pass
-...
->>> print(plac.parser_from(main)) #doctest: +ELLIPSIS
-ArgumentParser(prog=...)
-
-

Internally plac.call uses plac.parser_from and adds the parser -to the main function as an attribute. When plac.call(func) is -invoked multiple time, the parser is re-used and not rebuilt from scratch again.

-

I use plac.parser_from in the unit tests of the module, but regular -users should not need to use it, unless they want to access all -of the features of argparse directly without calling the main function.

-

Interested readers should read the documentation of argparse to -understand the meaning of the other options. If there is a set of -options that you use very often, you may consider writing a decorator -adding such options to the main function for you. For simplicity, -plac does not perform any magic except the addition of the .p -attribute.

-
-
-

plac vs the rest of the world

-

Originally plac boasted about being "the easiest command-line -arguments parser in the world". Since then, people started pointing -out to me various projects which are based on the same idea -(extracting the parser from the main function signature) and are -arguably even easier than plac:

- -

Luckily for me none of such projects had the idea of using -function annotations and argparse; as a consequence, they are -no match for the capabilities of plac.

-

Of course, there are tons of other libraries to parse the command -line. For instance Clap by Matthew Frazier which appeared on PyPI -just the day before plac; Clap is fine but it is certainly not -easier than plac.

-

plac can also be used as a replacement of the cmd module in the standard -library and as such it shares many features with the module cmd2 by -Catherine Devlin. However, this is completely coincidental, since I became -aware of the cmd2 module only after writing plac.

-

Command-line argument parsers keep coming out; between the newcomers I -will notice marrow.script by Alice Bevan-McGregor, which is quite -similar to plac in spirit, but does not rely on argparse at all. -Argh by Andrey Mikhaylenko is also worth mentioning: it is also based -on argparse, it came after plac and I must give credit to the author -for the choice of the name, much funnier than plac!

-
-
-

The future

-

Currently the core of plac is around 200 lines of code, not counting blanks, -comments and docstrings. I do not plan to extend the core much in the -future. The idea is to keep the module short: it is and it should -remain a little wrapper over argparse. Actually I have thought about -contributing the core back to argparse if plac becomes successfull -and gains a reasonable number of users. For the moment it should be -considered in alpha status.

-

Notice that even if plac has been designed to be simple to use for -simple stuff, its power should not be underestimated; it is actually a -quite advanced tool with a domain of applicability which far exceeds -the realm of command-line arguments parsers.

-

Version 0.5 of plac doubled the code base and the documentation: it is -based on the idea of using plac to implement command-line interpreters, -i.e. something akin to the cmd module in the standard library, only better. -The new features of plac are described in the advanced usage document . -They are implemented in a separated module (plac_ext.py), since -they require Python 2.5 to work, whereas plac_core.py only requires -Python 2.3.

-
-
-

Trivia: the story behind the name

-

The plac project started very humbly: I just wanted to make -easy_installable my old optionparse recipe, and to publish it on PyPI. -The original name of plac was optionparser and the idea behind it was -to build an OptionParser object from the docstring of the module. -However, before doing that, I decided to check out the argparse module, -since I knew it was going into Python 2.7 and Python 2.7 was coming out. -Soon enough I realized two things:

-
    -
  1. the single greatest idea of argparse was unifying the positional arguments -and the options in a single namespace object;
  2. -
  3. parsing the docstring was so old-fashioned, considering the existence -of functions annotations in Python 3.
  4. -
-

Putting together these two observations with the original idea of inferring the -parser I decided to build an ArgumentParser object from function -annotations. The optionparser name was ruled out, since I was -now using argparse; a name like argparse_plus was also ruled out, -since the typical usage was completely different from the argparse usage.

-

I made a research on PyPI and the name clap (Command Line Arguments Parser) -was not taken, so I renamed everything to clap. After two days -a Clap module appeared on PyPI <expletives deleted>!

-

Having little imagination, I decided to rename everything again to plac, -an anagram of clap: since it is a non-existing English name, I hope nobody -will steal it from me!

-

That concludes the section about the basic usage of plac. You are now ready to -read about the advanced usage.

-
-
-
-

Advanced usages of plac

-
-

Introduction

-

One of the design goals of plac is to make it dead easy to write a -scriptable and testable interface for an application. You can use -plac whenever you have an API with strings in input and strings in -output, and that includes a huge domain of applications.

-

A string-oriented interface is a scriptable interface by -construction. That means that you can define a command language for -your application and that it is possible to write scripts which are -interpretable by plac and can be run as batch scripts.

-

Actually, at the most general level, you can see plac as a generic tool to -write domain specific languages (DSL). With plac you -can test your application interactively as well as with batch -scripts, and even with the analogous of Python doctests for your -defined language.

-

You can easily replace the cmd module of the standard library and -you could easily write an application like twill with plac. Or you -could use it to script your building procedure. plac also supports -parallel execution of multiple commands and can be used as -task manager and monitor. It is also quite easy to build a GUI -or a Web application on top of plac. When speaking of things -you can do with plac, your imagination is the only limit!

-
-
-

From scripts to interactive applications

-

Command-line scripts have many advantages, but they are no substitute -for interactive applications. -In particular, if you have a script with a large startup time which must be run -multiple times, it is best to turn it into an interactive application, -so that the startup is performed only once. plac provides an -Interpreter class just for this purpose.

-

The Interpreter class wraps the main function of a script and -provides an .interact method to start an interactive interpreter -reading commands from the console.

-

For instance, you can define an interactive interpreter on top of the -ishelve script introduced in the basic documentation as -follows:

-
-# shelve_interpreter.py
-import plac, ishelve
-
-@plac.annotations(
-    interactive=('start interactive interface', 'flag'),
-    subcommands='the commands of the underlying ishelve interpreter')
-def main(interactive, *subcommands):
-    """
-    This script works both interactively and non-interactively.
-    Use .help to see the internal commands.
-    """
-    if interactive:
-        plac.Interpreter(ishelve.main).interact()
-    else:
-        for out in plac.call(ishelve.main, subcommands):
-            print(out)
-
-if __name__ == '__main__':
-    plac.call(main)
-
-
-

A trick has been used here: the ishelve command-line interface has been -hidden inside an external interface. They are distinct: for instance -the external interface recognizes the -h/--help flag whereas the -internal interface only recognizes the .help command:

-
-$ python shelve_interpreter.py -h
-
-
-usage: shelve_interpreter.py [-h] [-interactive]
-                             [subcommands [subcommands ...]]
-
-    This script works both interactively and non-interactively.
-    Use .help to see the internal commands.
-    
-
-positional arguments:
-  subcommands   the commands of the underlying ishelve interpreter
-
-optional arguments:
-  -h, --help    show this help message and exit
-  -interactive  start interactive interface
-
-
-

Thanks to this ingenuous trick, the script can be run both interactively -and non-interactively:

-
-$ python shelve_interpreter.py .clear # non-interactive use
-cleared the shelve
-
-

Here is an usage session:

-
-$ python shelve_interpreter.py -i # interactive use
-A simple interface to a shelve. Use .help to see the available commands.
-i> .help
-Commands: .help, .showall, .clear, .delete
-<param> ...
-<param=value> ...
-i> a=1
-setting a=1
-i> a
-1
-i> b=2
-setting b=2
-i> a b
-1
-2
-i> .del a
-deleted a
-i> a
-a: not found
-i> .show
-b=2
-i> [CTRL-D]
-
-

The .interact method -reads commands from the console and send them to the -underlying interpreter, until the user send a CTRL-D -command (CTRL-Z in Windows). There is a default -argument prompt='i> ' which -can be used to change the prompt. The text displayed at the beginning -of the interactive session is the docstring of the main function. -plac also understands command abbreviations: in this example -del is an abbreviation for delete. In case of ambiguous -abbreviations plac raises a NameError.

-

Finally I must notice that the plac.Interpreter is available only if you -are using a recent version of Python (>= 2.5), because it is a context -manager object which uses extended generators internally.

-
-
-

Testing a plac application

-

You can conveniently test your application in interactive mode. -However manual testing is a poor substitute for automatic testing.

-

In principle, one could write automatic tests for the -ishelve application by using plac.call directly:

-
-# test_ishelve.py
-import plac, ishelve
-
-def test():
-    assert plac.call(ishelve.main, ['.clear']) == ['cleared the shelve']
-    assert plac.call(ishelve.main, ['a=1']) == ['setting a=1']
-    assert plac.call(ishelve.main, ['a']) == ['1']
-    assert plac.call(ishelve.main, ['.delete=a']) == ['deleted a']
-    assert plac.call(ishelve.main, ['a']) == ['a: not found']
-
-if __name__ == '__main__':
-    test()
-
-
-

However, using plac.call is not especially nice. The big -issue is that plac.call responds to invalid input by printing an -error message on stderr and by raising a SystemExit: this is -certainly not a nice thing to do in a test.

-

As a consequence of this behavior it is impossible to test for invalid -commands, unless you wrap the SystemExit exception by -hand each time (a possibly you do something with the error message in -stderr too). Luckily, plac offers a better testing support through -the check method of Interpreter objects:

-
-# test_ishelve_more.py
-from __future__ import with_statement
-import plac, ishelve
-
-def test():
-    with plac.Interpreter(ishelve.main) as i:
-        i.check('.clear', 'cleared the shelve')
-        i.check('a=1', 'setting a=1')
-        i.check('a', '1')
-        i.check('.delete=a', 'deleted a')
-        i.check('a', 'a: not found')
-
-
-

The method .check(given_input, expected_output) works on strings -and raises an AssertionError if the output produced by the -interpreter is different from the expected output for the given input. -Notice that AssertionError is catched by tools like py.test and -nosetests and actually plac tests are intended to be run with -such tools.

-

Interpreters offer a minor syntactic advantage with respect to calling -plac.call directly, but they offer a major semantic advantage when things -go wrong (read exceptions): an Interpreter object internally invokes -something like plac.call, but it wraps all exceptions, so that i.check -is guaranteed not to raise any exception except AssertionError.

-

Even the SystemExit exception is captured and you can write your test as

-
-i.check('-cler', 'SystemExit: unrecognized arguments: -cler')
-

without risk of exiting from the Python interpreter.

-

There is a second advantage of interpreters: if the main function contains some -initialization code and finalization code -(__enter__ and __exit__ functions) they will be run only -once at the beginning and at the end of the interpreter loop. -plac.call instead ignores the initialization/finalization code.

-
-
-

Plac easy tests

-

Writing your tests in terms of Interpreter.check is certainly an -improvement over writing them in terms of plac.call, but they -are still too low-level for my taste. The Interpreter class provides -support for doctest-style tests, a.k.a. plac easy tests.

-

By using plac easy tests you can cut and paste your interactive session and -turn it into a runnable automatics test. -Consider for instance the following file ishelve.placet (the .placet -extension is a mnemonic for plac easy tests):

-
-#!ishelve.py
-i> .clear # start from a clean state
-cleared the shelve
-i> a=1
-setting a=1
-i> a
-1
-i> .del a
-deleted a
-i> a
-a: not found
-i> .cler # spelling error
-.cler: not found
-
-
-

Notice the precence of the shebang line containing the name of the -plac tool to test (a plac tool is just a Python module with a -function called main). The shebang is ignored by the interpreter -(it looks like a comment to it) but it is there so that external -tools (say a test runner) can infer the plac interpreter -to use to test the file.

-

You can test ishelve.placet file by calling the -.doctest method of the interpreter, as in this example:

-
-$ python -c"import plac, ishelve
-plac.Interpreter(ishelve.main).doctest(open('ishelve.placet'), verbose=True)"
-
-

Internally Interpreter.doctests invokes something like Interpreter.check -multiple times inside the same context and compare the output with the -expected output: if even a check fails, the whole test fail.

-

You should realize tha the easy tests supported by plac are not -unittests: they are functional tests. They model then user interaction and the -order of the operations generally matters. The single subtests in a -.placet file are not independent and it makes sense to exit -immediately at the first failure.

-

The support for doctests in plac comes nearly for free, thanks to the -shlex module in the standard library, which is able to parse simple -languages as the ones you can implement with plac. In particular, -thanks to shlex, plac is able to recognize comments (the default -comment character is #), escape sequences and more. Look at the -shlex documentation if you need to customize how the language is -interpreted. For more flexibility, it is even possible to pass to the -interpreter a custom split function with signature split(line, -commentchar).

-

In addition, I have implemented from scratch some support for line number -recognition, so that if a test fail you get the line number of the -failing command. This is especially useful if your tests are -stored in external files, even if plac easy tests does not need to be in -a file: you can just pass to the .doctest method a list of -strings corresponding to the lines of the file.

-

At the present plac does not use any code from the doctest -module, but the situation may change in the future (it would be nice -if plac could reuse doctests directives like ELLIPSIS).

-

It is straighforward to integrate your .placet tests with standard -testing tools. For instance, you can integrate your doctests with nose -or py.test as follow:

-
-import os, shlex, plac
-
-def test_doct():
-   """
-   Find all the doctests in the current directory and run them with the
-   corresponding plac interpreter (the shebang rules!)
-   """
-   placets = [f for f in os.listdir('.') if f.endswith('.placet')]
-   for placet in placets:
-       lines = list(open(placet))
-       assert lines[0].startswith('#!'), 'Missing or incorrect shebang line!'
-       firstline = lines[0][2:] # strip the shebang
-       main = plac.import_main(*shlex.split(firstline))
-       yield plac.Interpreter(main).doctest, lines[1:]
-
-

Here you should notice that usage of plac.import_main, an utility -which is able to import the main function of the script specified in -the shebang line. You can use both the full path name of the -tool, or a relative path name. In this case the runner look at the -environment variable PLACPATH and it searches -the plac tool in the directories specified there (PLACPATH is just -a string containing directory names separated by colons). If the variable -PLACPATH is not defined, it just looks in the current directory. -If the plac tool is not found, an ImportError is raised.

-
-
-

Plac batch scripts

-

It is pretty easy to realize that an interactive interpreter can -also be used to run batch scripts: instead of reading the commands from -the console, it is enough to read the commands from a file. -plac interpreters provide an .execute method to perform just that.

-

There is just a subtle point to notice: whereas in an interactive loop -one wants to manage all exceptions, a batch script should not in the -background in case of unexpected errors. The implementation of -Interpreter.execute makes sure that any error raised by -plac.call internally is re-raised. In other words, plac -interpreters wrap the errors, but does not eat them: the errors are -always accessible and can be re-raised on demand.

-

The exception is the case of invalid commands, which are skipped. -Consider for instance the following batch file, which contains a -mispelled command (.dl instead of .del):

-
-#!ishelve.py
-.clear 
-a=1 b=2
-.show
-.del a
-.dl b
-.show
-
-
-

If you execute the batch file, the interpreter will print a .dl: not found -at the .dl line and will continue:

-
-$ python -c "import plac, ishelve
-plac.Interpreter(ishelve.main).execute(open('ishelve.plac'), verbose=True)"
-i> .clear
-cleared the shelve
-i> a=1 b=2
-setting a=1
-setting b=2
-i> .show
-b=2
-a=1
-i> .del a
-deleted a
-i> .dl b
-2
-.dl: not found
-i> .show
-b=2
-
-

The verbose flag is there to show the lines which are being interpreted -(prefixed by i>). This is done on purpose, so that you can cut and paste -the output of the batch script and turn it into a .placet test -(cool, isn't it?).

-
-
-

Implementing subcommands

-

When I discussed the ishelve implementation in the basic -documentation, I said that it looked like a poor man implementation -of an object system as a chain of elifs; I also said that plac was -able to do much better than that. Here I will substantiate my claim.

-

plac is actually able to infer a set of subparsers from a -generic container of commands. This is useful if you want to -implement subcommands (a familiar example of a command-line -application featuring subcommands is subversion).

-

Technically a container of commands is any object with a .commands attribute -listing a set of functions or methods which are valid commands. A command -container may have initialization/finalization hooks (__enter__/__exit__) -and dispatch hooks (__missing__, invoked for invalid command names). -Moreover, only when using command containers plac is able to provide -automatic autocompletion of commands.

-

The shelve interface can be rewritten in an object-oriented way as follows:

-
-# ishelve2.py
-import shelve, os, sys, plac
-
-class ShelveInterface(object):
-    "A minimal interface over a shelve object."
-    commands = 'set', 'show', 'showall', 'delete'
-    @plac.annotations(
-        configfile=('path name of the shelve', 'option'))
-    def __init__(self, configfile):
-        self.configfile = configfile or '~/conf.shelve'
-        self.fname = os.path.expanduser(self.configfile)
-        self.__doc__ += '\nOperating on %s.\nUse help to see '\
-            'the available commands.\n'  % self.fname
-    def __enter__(self):
-        self.sh = shelve.open(self.fname)
-        return self
-    def __exit__(self, etype, exc, tb):
-        self.sh.close()
-    def set(self, name, value):
-        "set name value"
-        yield 'setting %s=%s' % (name, value)
-        self.sh[name] = value
-    def show(self, *names):
-        "show given parameters"
-        for name in names:
-            yield '%s = %s' % (name, self.sh[name]) # no error checking
-    def showall(self):
-        "show all parameters"
-        for name in self.sh:
-            yield '%s = %s' % (name, self.sh[name])
-    def delete(self, name=None):
-        "delete given parameter (or everything)"
-        if name is None:
-            yield 'deleting everything'
-            self.sh.clear()
-        else:
-            yield 'deleting %s' % name
-            del self.sh[name] # no error checking
-
-main = ShelveInterface # useful for the tests
-
-if __name__ == '__main__':
-    plac.Interpreter.call(ShelveInterface)
-
-
-

plac.Interpreter objects wrap context manager objects -consistently. In other words, if you wrap an object with -__enter__ and __exit__ methods, they are invoked in the right -order (__enter__ before the interpreter loop starts and -__exit__ after the interpreter loop ends, both in the regular and -in the exceptional case). In our example, the methods __enter__ -and __exit__ make sure the the shelve is opened and closed -correctly even in the case of exceptions. Notice that I have not -implemented any error checking in the show and delete methods -on purpose, to verify that plac works correctly in the presence of -exceptions.

-

When working with command containers, plac automatically adds two -special commands to the set of provided commands: help -and .last_tb. The help command is the easier to understand: -when invoked without arguments it displays the list of available commands -with the same formatting of the cmd module; when invoked with the name of -a command it displays the usage message for that command. -The .last_tb command is useful when debugging: in case of errors, -it allows you to display the traceback of the last executed command.

-

Here is a session of usage on an Unix-like operating system:

-
-$ python ishelve2.py
-A minimal interface over a shelve object.
-Operating on /home/micheles/conf.shelve.
-Use help to see the available commands.
-i> help
-
-special commands
-================
-last_tb
-
-custom commands
-===============
-delete  set  show  showall
-
-i> delete
-deleting everything
-i> set a pippo
-setting a=pippo
-i> set b lippo
-setting b=lippo
-i> showall
-b = lippo
-a = pippo
-i> show a b
-a = pippo
-b = lippo
-i> del a
-deleting a
-i> showall
-b = lippo
-i> delete a
-deleting a
-KeyError: 'a'
-i> .last_tb
- File "/usr/local/lib/python2.6/dist-packages/plac-0.6.0-py2.6.egg/plac_ext.py", line 190, in _wrap
-    for value in genobj:
-  File "./ishelve2.py", line 37, in delete
-    del self.sh[name] # no error checking
-  File "/usr/lib/python2.6/shelve.py", line 136, in __delitem__
-    del self.dict[key]
-i>
-
-

Notice that in interactive mode the traceback is hidden, unless -you pass the verbose flag to the Interpreter.interact method.

-

CHANGED IN VERSION 0.9: if you have an old version of plac the -help command must be prefixed with a dot, i.e. you must write -.help. The old behavior was more consistent in my opinion, since -it made it clear that the help command was special and threated -differently from the regular commands. However many users complained against -the dot, so I changed it to make them happy. Starting from release 0.9 -the help command is just an alias for --help, in the -sense that there is a preprocessor step replacing help with --help -in the argument list. -Notice that if you implement a custom help command in the commander class -the preprocessor will be automatically disabled: passing help -will call the custom help command, just as you would expect.

-
-
-

plac.Interpreter.call

-

At the core of plac there is the call function which invokes -a callable with the list of arguments passed at the command-line -(sys.argv[1:]). Thanks to plac.call you can launch your module -by simply adding the lines:

-
-if __name__ == '__main__':
-    plac.call(main)
-
-

Everything works fine if main is a simple callable performing some -action; however, in many cases, one has a main "function" which -is a actually a factory returning a command container object. For -instance, in my second shelve example the main function is the class -ShelveInterface, and the two lines needed to run the module are -a bit ugly:

-
-if __name__ == '__main__':
-   plac.Interpreter(plac.call(ShelveInterface)).interact()
-
-

Moreover, now the program runs, but only in interactive mode, i.e. -it is not possible to run it as a script. Instead, it would be nice -to be able to specify the command to execute on the command-line -and have the interpreter start, execute the command and finish -properly (I mean by calling __enter__ and __exit__) -without needing user input. Then the script could be called from -a batch shell script working in the background. -In order to provide such functionality plac.Interpreter provides -a classmethod named .call which takes the factory, instantiates -it with the arguments read from the command line, wraps the resulting -container object as an interpreter and runs it with the rest arguments -found in the command line. Here is the code to turn the ShelveInterface -into a script

-
-# ishelve3.py
-from ishelve2 import ShelveInterface as main
-
-if __name__ == '__main__':
-    import plac; plac.Interpreter.call(main)
-
-
-

and here are a few examples of usage:

-
-$ python ishelve3.py -h
-usage: ishelve3.py [-h] [-i] [-configfile CONFIGFILE] [args [args ...]]
-
-positional arguments:
-  args
-
-optional arguments:
-  -h, --help            show this help message and exit
-  -i, --interact        start interactive interpreter
-  -configfile CONFIGFILE
-                        path name of the shelve
-
-$ python ishelve3.py set a 1
-setting a=1
-$ python ishelve3.py show a
-a = 1
-
-

If you pass the -i flag in the command line, then the -script will enter in interactive mode and ask the user -for the commands to execute:

-
-$ python ishelve3.py -i
-A minimal interface over a shelve object.
-Operating on /home/micheles/conf.shelve.
-Use help to see the available commands.
-
-i>
-
-

In a sense, I have closed the circle: at the beginning of this -document I discussed how to turn a script into an interactive -application (the shelve_interpreter.py example), whereas here I -have show how to turn an interactive application into a script.

-

The complete signature of plac.Interpreter.call is the following:

-
-call(factory, arglist=sys.argv[1:],
-     commentchar='#', split=shlex.split,
-     stdin=sys.stdin, prompt='i> ', verbose=False)
-
-

The factory must have a fixed number of positional arguments (no -default arguments, no varargs, no kwargs), otherwise a TypeError -is raised: the reason is that we want to be able to distinguish the -command-line arguments needed to instantiate the factory from the rest -arguments that must be sent to the corresponding interpreter object. -It is also possible to specify a list of arguments different from -sys.argv[1:] (useful in tests), the character to be recognized as -a comment, the splitting function, the input source and the prompt to -use while in interactive mode, and a verbose flag.

-
-
-

Readline support

-

Starting from release 0.6 plac offers full readline support. That -means that if your Python was compiled with readline support you get -autocompletion and persistent command history for free. By default -all commands are autocomplete in a case sensitive way. If you want to -add new words to the autocompletion set, or you want to change the -location of the .history file, or to change the case sensitivity, -the way to go is to pass a plac.ReadlineInput object to the -interpreter. Here is an example, assuming you want to build a -database interface understanding SQL commands:

-
-import os, plac
-from sqlalchemy.ext.sqlsoup import SqlSoup
-
-SQLKEYWORDS = set(['help', 'select', 'from', 
-                   'inner', 'join', 'outer', 'left', 'right']
-                  ) # and many others
-DBTABLES = set(['table1', 'table2']) # you can read them from the db schema
-
-COMPLETIONS = SQLKEYWORDS | DBTABLES
-
-class SqlInterface(object):
-    commands = ['SELECT']
-    def __init__(self, dsn):
-        self.soup = SqlSoup(dsn)
-    def SELECT(self, argstring):
-        sql = 'SELECT ' + argstring
-        for row in self.soup.bind.execute(sql):
-            yield str(row) # the formatting can be much improved
-
-rl_input = plac.ReadlineInput(
-    COMPLETIONS, histfile=os.path.expanduser('~/.sql_interface.history'), 
-    case_sensitive=False)
-
-def split_on_first_space(line, commentchar):
-    return line.strip().split(' ', 1) # ignoring comments
-    
-if __name__ == '__main__':
-    plac.Interpreter.call(SqlInterface, split=split_on_first_space,
-                          stdin=rl_input, prompt='sql> ')
-
-
-

Here is an example of usage:

-
-$ python sql_interface.py <some dsn>
-sql> SELECT a.* FROM TABLE1 AS a INNER JOIN TABLE2 AS b ON a.id = b.id
-...
-
-

You can check that entering just sel and pressing TAB the readline library -completes the SELECT keyword for you and makes it upper case; idem for -FROM, INNER, JOIN and even for the names of the tables. An -obvious improvement is to read the names of the tables by introspecting -the database: actually you can even read the names of the views and of -the columns, and have full autocompletion. All the entered commands -and recorded and saved in the file ~/.sql_interface.history when -exiting from the command-line interface.

-

If the readline library is not available, my suggestion is to use the -rlwrap tool which provides similar features, at least on Unix-like -platforms. plac should also work fine on Windows with the pyreadline -library (I do not use Windows, so this part is very little tested: I -tried it only once and it worked, but your mileage may vary). -For people worried about licenses, I will notice that plac uses the -readline library only if available, it does not include it and it does -not rely on it in any fundamental way, so that the plac licence does -not need to be the GPL (actually it is a BSD -do-whatever-you-want-with-it licence).

-

The interactive mode of plac can be used as a replacement of the -cmd module in the standard library. It is actually better than cmd: -for instance, the help command is more powerful, since it -provides information about the arguments accepted by the given command:

-
-i> help set
-usage:  set name value
-
-set name value
-
-positional arguments:
-  name
-  value
-
-i> help delete
-usage:  delete [name]
-
-delete given parameter (or everything)
-
-positional arguments:
-  name        [None]
-
-i> help show
-usage:  show [names [names ...]]
-
-show given parameters
-
-positional arguments:
-  names
-
-

As you can imagine, the help message is provided by the underlying argparse -subparser (there is a subparser for each command). plac commands accept -options, flags, varargs, keyword arguments, arguments with defaults, -arguments with a fixed number of choices, type conversion and all the -features provided of argparse which should be reimplemented from scratch -using plac.

-

Moreover at the moment plac also understands command abbreviations. -However, this feature may disappear in -future releases. It was meaningful in the past, when plac did not support -readline.

-

Notice that if an abbreviation is ambiguous, plac warns you:

-
-i> sh
-NameError: Ambiguous command 'sh': matching ['showall', 'show']
-
-
-
-

The plac runner

-

The distribution of plac includes a runner script named plac_runner.py, -which will be installed in a suitable directory in your system by distutils -(say in \usr\local\bin\plac_runner.py in a Unix-like operative system). -The runner provides many facilities to run .plac scripts and -.placet files, as well as Python modules containg a main -object, which can be a function, a command container object or -even a command container class.

-

For instance, suppose you want to execute a script containing commands -defined in the ishelve2 module like the following one:

-
-#!ishelve2.py:ShelveInterface -c ~/conf.shelve
-set a 1
-del a
-del a # intentional error
-
-
-

The first line of the .plac script contains the name of the -python module containing the plac interpreter and the arguments -which must be passed to its main function in order to be able -to instantiate an interpreter object. In this case I appended -:ShelveInterface to the name of the module to specify the -object that must be imported: if not specified, by default the -object named 'main' is imported. -The other lines contains commands. -You can run the script as follows:

-
-$ plac_runner.py --batch ishelve2.plac
-setting a=1
-deleting a
-Traceback (most recent call last):
-  ...
-_bsddb.DBNotFoundError: (-30988, 'DB_NOTFOUND: No matching key/data pair found')
-
-

The last command intentionally contained an error, to show that the -plac runner does not eat the traceback.

-

The runner can also be used to run Python modules in interactive -mode and non-interactive mode. If you put this alias in your bashrc

-
-alias plac="plac_runner.py"
-

(or you define a suitable plac.bat script in Windows) you can -run the ishelve2.py script in interactive mode as -follows:

-
-$ plac -i ishelve2.py:ShelveInterface
-A minimal interface over a shelve object.
-Operating on /home/micheles/conf.shelve.
-.help to see the available commands.
-
-i> del
-deleting everything
-i> set a 1
-setting a=1
-i> set b 2
-setting b=2
-i> show b
-b = 2
-
-

Now you can cut and paste the interactive session an turns into into -a .placet file like the following:

-
-#!ishelve2.py:ShelveInterface -configfile=~/test.shelve
-i> del
-deleting everything
-i> set a 1
-setting a=1
-i> set b 2
-setting b=2
-i> show a
-a = 1
-
-
-

Notice that the first line specifies a test database -~/test.shelve, to avoid clobbering your default shelve. If you -mispell the arguments in the first line plac will give you an -argparse error message (just try).

-

You can run placets following the shebang convention directly with -the plac runner:

-
-$ plac --test ishelve2.placet
-run 1 plac test(s)
-
-

If you want to see the output of the tests, pass the -v/--verbose flag. -Notice that he runner ignore the extension, so you can actually use any -extension your like, but it relies on the first line of the file to invoke -the corresponding plac tool with the given arguments.

-

The plac runner does not provide any test discovery facility, -but you can use standard Unix tools to help. For instance, you can -run all the .placet files into a directory and its subdirectories -as follows:

-
-$ find . -name \*.placet | xargs plac_runner.py -t
-
-

The plac runner expects the main function of your script to -return a plac tool, i.e. a function or an object with a .commands -attribute. It this is not the case the runner gracefully exits.

-

It also works in non-interactive mode, if you call it as

-
-$ plac module.py args ...
-

Here is an example:

-
-$ plac ishelve.py a=1
-setting a=1
-$ plac ishelve.py .show
-a=1
-
-

Notice that in non-interactive mode the runner just invokes plac.call -on the main object of the Python module.

-
-
-

A non class-based example

-

plac does not force you to use classes to define command containers. -Even a simple function can be a valid command container, it is -enough to add to it a .commands attribute and possibly -__enter__ and/or __exit__ attributes.

-

In particular, a Python module is a perfect container of commands. As an -example, consider the following module implementing a fake Version -Control System:

-
-"A Fake Version Control System"
-
-import plac
-
-commands = 'checkout', 'commit', 'status'
-
-@plac.annotations(url='url of the source code')
-def checkout(url):
-    "A fake checkout command"
-    return ('checkout ', url)
-
-@plac.annotations(message=('commit message', 'option'))
-def commit(message):
-    "A fake commit command"
-    return ('commit ', message)
-
-@plac.annotations(quiet=('summary information', 'flag', 'q'))
-def status(quiet):
-    "A fake status command"
-    return ('status ', quiet)
-
-def __missing__(name):
-    return 'Command %r does not exist' % name
-
-def __exit__(etype, exc, tb):
-    "Will be called automatically at the end of the call/cmdloop"
-    if etype in (None, GeneratorExit): # success
-        print('ok')
-
-main = __import__(__name__) # the module imports itself!
-
-
-

Notice that I have defined both an __exit__ hook and a __missing__ -hook, invoked for non-existing commands. -The real trick here is the line main = __import__(__name__), which -define main to be an alias for the current module.

-

The vcs module does not contain an if __name__ == '__main__' -block, but you can still run it through the plac runner -(try plac vcs.py -h):

-
-usage: plac_runner.py vcs.py [-h] {status,commit,checkout} ...
-
-A Fake Version Control System
-
-optional arguments:
-  -h, --help            show this help message and exit
-
-subcommands:
-  {status,commit,checkout}
-    checkout            A fake checkout command
-    commit              A fake commit command
-    status              A fake status command
-
-
-

You can get help for the subcommands by postponing -h after the -name of the command:

-
-$ plac vcs.py status -h
-usage: vcs.py status [-h] [-q]
-
-A fake status command
-
-optional arguments:
-  -h, --help   show this help message and exit
-  -q, --quiet  summary information
-
-

Notice how the docstring of the command is automatically shown in -usage message, as well as the documentation for the sub flag -q.

-

Here is an example of a non-interactive session:

-
-$ plac vcs.py check url
-checkout
-url
-$ plac vcs.py st -q
-status
-True
-$ plac vcs.py co
-commit
-None
-
-

and here is an interactive session:

-
-$ plac -i vcs.py
-usage: plac_runner.py vcs.py [-h] {status,commit,checkout} ...
-i> check url
-checkout
-url
-i> st -q
-status
-True
-i> co
-commit
-None
-i> sto
-Command 'sto' does not exist
-i> [CTRL-D]
-ok
-
-

Notice the invocation of the __missing__ hook for non-existing commands. -Notice also that the __exit__ hook gets called only in interactive -mode.

-

If the commands are completely independent, a module is a good fit for -a method container. In other situations, it is best to use a custom -class.

-
-
-

Writing your own plac runner

-

The runner included in the plac distribution is intentionally kept -small (around 50 lines of code) so that you can study it and write -your own runner if want to. If you need to go to such level -of detail, you should know that the most important method of -the Interpreter class is the .send method, which takes -strings in input and returns a four-tuple with attributes -.str, .etype, .exc and .tb:

-
    -
  • .str is the output of the command, if successful (a string);
  • -
  • .etype is the class of the exception, if the command fail;
  • -
  • .exc is the exception instance;
  • -
  • .tb is the traceback.
  • -
-

Moreover the __str__ representation of the output object is redefined -to return the output string if the command was successful or the error -message if the command failed (actually it returns the error message -preceded by the name of the exception class).

-

For instance, if you send a mispelled option to -the interpreter a SystemExit will be trapped:

-
->>> import plac
->>> from ishelve import ishelve
->>> with plac.Interpreter(ishelve) as i:
-...     print(i.send('.cler'))
-...
-SystemExit: unrecognized arguments: .cler
-
-

It is important to invoke the .send method inside the context manager, -otherwise you will get a RuntimeError.

-

For instance, suppose you want to implement a graphical runner for a -plac-based interpreter with two text widgets: one to enter the commands -and one to display the results. Suppose you want to display the errors -with tracebacks in red. You will need to code something like that -(pseudocode follows):

-
-input_widget = WidgetReadingInput()
-output_widget = WidgetDisplayingOutput()
-
-def send(interpreter, line):
-    out = interpreter.send(line)
-    if out.tb: # there was an error
-        output_widget.display(out.tb, color='red')
-    else:
-        output_widget.display(out.str)
-
-main = plac.import_main(tool_path) # get the main object
-
-with plac.Interpreter(main) as i:
-   def callback(event):
-      if event.user_pressed_ENTER():
-           send(i, input_widget.last_line)
-   input_widget.addcallback(callback)
-   gui_mainloop.start()
-
-

You can adapt the pseudocode to your GUI toolkit of choice and you can -also change the file associations in such a way that clicking on a -plac tool file the graphical user interface starts.

-

An example of GUI program built on top of plac is given later on, in the -paragraph Managing the output of concurrent commands (using Tkinter -for simplicity and portability).

-

There is a final caveat: since the plac interpreter loop is -implemented via extended generators, plac interpreters are single threaded: you -will get an error if you .send commands from separated threads. -You can circumvent the problem by using a queue. If EXIT is a sentinel -value to signal exiting from the interpreter look, you can write code -like this:

-
-with interpreter:
-    for input_value in iter(input_queue.get, EXIT):
-        output_queue.put(interpreter.send(input_value))
-
-

The same trick also work for processes; you could run the interpreter -loop in a separate process and send commands to it via the Queue -class provided by the multiprocessing module.

-
-
-

Long running commands

-

As we saw, by default a plac interpreter blocks until -the command terminates. This is an issue, in the sense that it makes -the interactive experience quite painful for long running commands. An -example is better than a thousand words, so consider the following -fake importer:

-
-import time
-import plac
-
-class FakeImporter(object):
-    "A fake importer with an import_file command"
-    commands = ['import_file']
-    def __init__(self, dsn):
-        self.dsn = dsn
-    def import_file(self, fname):
-        "Import a file into the database"
-        try:
-            for n in range(10000):
-                time.sleep(.01)
-                if n % 100 == 99:
-                    yield 'Imported %d lines' % (n+1)
-        finally:
-            print('closing the file')
-
-if __name__ == '__main__':
-    plac.Interpreter.call(FakeImporter)
-
-
-

If you run the import_file command, you will have to wait for 200 seconds -before entering a new command:

-
-$ python importer1.py dsn -i
-A fake importer with an import_file command
-i> import_file file1
-... <wait 3+ minutes>
-Imported 100 lines
-Imported 200 lines
-Imported 300 lines
-...
-Imported 10000 lines
-closing the file
-
-

Being unable to enter any other command is quite annoying: in such situation one -would like to run the long running commands in the background, to keep -the interface responsive. plac provides two ways to reach this goal: threads -and processes.

-
-
-

Threaded commands

-

The most familiar way to execute a task in the background (even if not -necessarily the best way) is to run it into a separated thread. In our -example it is sufficient to replace the line

-
-commands = ['import_file']
-

with

-
-thcommands = ['import_file']
-

to tell to the plac interpreter that the command import_file should be -run into a separated thread. Here is an example session:

-
-i> import_file file1
-<ThreadedTask 1 [import_file file1] RUNNING>
-
-

The import task started in a separated thread. You can see the -progress of the task by using the special command .output:

-
-i> .output 1
-<ThreadedTask 1 [import_file file1] RUNNING>
-Imported 100 lines
-Imported 200 lines
-
-

If you look after a while, you will get more lines of output:

-
-i> .output 1
-<ThreadedTask 1 [import_file file1] RUNNING>
-Imported 100 lines
-Imported 200 lines
-Imported 300 lines
-Imported 400 lines
-
-

If you look after a time long enough, the task will be finished:

-
-i> .output 1
-<ThreadedTask 1 [import_file file1] FINISHED>
-
-

It is possible to store the output of a task into a file, to be read -later (this is useful for tasks with a large output):

-
-i> .output 1 /tmp/out.txt
-saved output of 1 into /tmp/out.txt
-
-

You can even skip the number argument: then .output will the return -the output of the last launched command (the special commands like .output -do not count).

-

You can launch many tasks one after the other:

-
-i> import_file file2
-<ThreadedTask 5 [import_file file2] RUNNING>
-i> import_file file3
-<ThreadedTask 6 [import_file file3] RUNNING>
-
-

The .list command displays all the running tasks:

-
-i> .list
-<ThreadedTask 5 [import_file file2] RUNNING>
-<ThreadedTask 6 [import_file file3] RUNNING>
-
-

It is even possible to kill a task:

-
-i> .kill 5
-<ThreadedTask 5 [import_file file2] TOBEKILLED>
-# wait a bit ...
-closing the file
-i> .output 5
-<ThreadedTask 5 [import_file file2] KILLED>
-
-

You should notice that since at the Python level it is impossible to kill -a thread, the .kill commands works by setting the status of the task to -TOBEKILLED. Internally the generator corresponding to the command -is executed in the thread and the status is checked at each iteration: -when the status become TOBEKILLED a GeneratorExit exception is -raised and the thread terminates (softly, so that the finally clause -is honored). In our example the generator is yielding -back control once every 100 iterations, i.e. every two seconds (not much). -In order to get a responsive interface it is a good idea to yield more -often, for instance every 10 iterations (i.e. 5 times per second), -as in the following code:

-
-import time
-import plac
-
-class FakeImporter(object):
-    "A fake importer with an import_file command"
-    thcommands = ['import_file']
-    def __init__(self, dsn):
-        self.dsn = dsn
-    def import_file(self, fname):
-        "Import a file into the database"
-        try:
-            for n in range(10000):
-                time.sleep(.02)
-                if n % 100 == 99: # every two seconds
-                    yield 'Imported %d lines' % (n+1)
-                if n % 10 == 9: # every 0.2 seconds
-                    yield # go back and check the TOBEKILLED status
-        finally:
-            print('closing the file')
-
-if __name__ == '__main__':
-    plac.Interpreter.call(FakeImporter)
-
-
-
-
-

Running commands as external processes

-

Threads are not loved much in the Python world and actually most people -prefer to use processes instead. For this reason plac provides the -option to execute long running commands as external processes. Unfortunately -the current implementation only works in Unix-like operating systems -(including Mac OS X) because it relies on fork via the multiprocessing -module.

-

In our example, to enable the feature it is sufficient to replace the line

-
-thcommands = ['import_file']
-

with

-
-mpcommands = ['import_file'].
-

The user experience is exactly the same as with threads and you will not see any -difference at the user interface level:

-
-i> import_file file3
-<MPTask 1 [import_file file3] SUBMITTED>
-i> .kill 1
-<MPTask 1 [import_file file3] RUNNING>
-closing the file
-i> .o 1
-<MPTask 1 [import_file file3] KILLED>
-Imported 100 lines
-Imported 200 lines
-i>
-
-

Still, using processes is quite different than using threads: in -particular, when using processes you can only yield pickleable values -and you cannot re-raise an exception first raised in a different -process, because traceback objects are not pickleable. Moreover, -you cannot rely on automatic sharing of your objects.

-

On the plus side, when using processes you do not need to worry about -killing a command: they are killed immediately using a SIGTERM signal, -and there is not a TOBEKILLED mechanism. Moreover, the killing is -guaranteed to be soft: internally a command receiving a SIGTERM raises -a TerminatedProcess exception which is trapped in the generator -loop, so that the command is closed properly.

-

Using processes allows to take full advantage of multicore machines -and it is safer than using threads, so it is the recommended approach -unless you are working on Windows.

-
-
-

Managing the output of concurrent commands

-

plac acts as a command-line task launcher and can be used as the base -to build a GUI-based task launcher and task monitor. To this aim the -interpreter class provides a .submit method which returns a task -object and a .tasks method returning the list of all the tasks -submitted to the interpreter. The submit method does not start the task -and thus it is nonblocking. -Each task has an .outlist attribute which is a list -storing the value yielded by the generator underlying the task (the -None values are skipped though): the .outlist grows as the -task runs and more values are yielded. Accessing the .outlist is -nonblocking and can be done freely. -Finally there is a .result -property which waits for the task to finish and returns the last yielded -value or raises an exception. The code below provides an example of -how you could implement a GUI over the importer example:

-
-from __future__ import with_statement
-from Tkinter import *
-from importer3 import FakeImporter
-
-def taskwidget(root, task, tick=500):
-    "A Label widget showing the output of a task every 500 ms"
-    sv = StringVar(root)
-    lb = Label(root, textvariable=sv)
-    def show_outlist():
-        try:
-            out = task.outlist[-1]
-        except IndexError: # no output yet
-            out = ''
-        sv.set('%s %s' % (task, out))
-        root.after(tick, show_outlist)
-    root.after(0, show_outlist)
-    return lb
-
-def monitor(tasks):
-    root = Tk()
-    for task in tasks:
-        task.run()
-        taskwidget(root, task).pack()
-    root.mainloop()
-
-if __name__ == '__main__':
-    import plac
-    with plac.Interpreter(plac.call(FakeImporter)) as i:
-        tasks = [i.submit('import_file f1'), i.submit('import_file f2')]
-        monitor(tasks)
-
-
-
-
-

Monitor support

-

Starting from release 0.8 plac provides builtin support for monitoring the -output of concurrent commands, at least for platforms where multiprocessing -is fully supported. You can define your own monitor -class, simply by inheriting from plac.Monitor and by -overriding the methods add_listener(self, no), -del_listener(self, taskno), notify_listener(self, taskno, msg), -schedule(self, seconds, func, arg) and run(self). -Then, you can a monitor object to any plac.Interpreter object -by simply calling the add_monitor method. -For convenience, -plac comes with a very simple TkMonitor based on Tkinter -(I chose Tkinter because it is easy to use and it is -in the standard library, but you can use any GUI): you can just -look at how the TkMonitor is implemented in plac_tk.py -and adapt it. Here is an example of usage of the TkMonitor:

-
-from __future__ import with_statement
-import plac
-
-class Hello(object):
-    mpcommands = ['hello']
-    def hello(self):
-        yield 'hello'
-
-if __name__ == '__main__':
-    i = plac.Interpreter(Hello())
-    i.add_monitor(plac.TkMonitor('tkmon'))
-    with i:
-        i.interact()
-        
-
-
-

Try to give the hello command from the interactive interpreter: -each time a new text widget will be added displaying the output -of the command. Notice that if Tkinter is not installed correctly -on your system the TkMonitor class will not be available.

-
-
-

Parallel computing with plac

-

plac is certainly not intended as a tool for parallel computing, but -still you can use it to launch a set of commands and to collect the -results, similarly to the MapReduce pattern popularized by -Google. In order to give an example, I will consider the "Hello -World" of parallel computing, i.e. the computation of pi with -independent processes. There is a huge number of algorithms to -compute pi; here I will describe a trivial one chosen for simplicity, -not per efficienty. The trick is to consider the first quadrant of a -circle with radius 1 and to extract a number of points (x, y) with -x and y random variables in the interval [0,1]. The -probability of extracting a number inside the quadrant (i.e. with -x^2 + y^2 < 1) is proportional to the area of the quadrant -(i.e. pi/4). The value of pi therefore can be extracted by -multiplying by 4 the ratio between the number of points in the -quadrant versus the total number of points N, for N large:

-
-def calc_pi(N):
-    inside = 0
-    for j in xrange(N):
-        x, y = random(), random()
-        if x*x + y*y < 1:
-            inside += 1
-    return (4.0 * inside) / N
-
-

The algorithm is trivially parallelizable: if you have n CPUs, you can -compute pi n times with N/n iterations, sum the results and divide the total -by n. I have a Macbook with two cores, therefore I would expect a speedup -factor of 2 with respect to a sequential computation. Moreover, I would -expect a threaded computation to be even slower than a sequential -computation, due to the GIL and the scheduling overhead.

-

Here is a script implementing the algorithm and working in three different -modes (parallel mode, threaded mode and sequential mode) depending on a -mode option:

-
-from __future__ import with_statement
-from random import random
-import multiprocessing
-import plac
-
-class PiCalculator(object):
-    """Compute pi in parallel with threads or processes"""
-    
-    @plac.annotations(
-        npoints=('number of integration points', 'positional', None, int),
-        mode=('sequential|parallel|threaded', 'option', 'm', str, 'SPT'))
-    def __init__(self, npoints, mode='S'):
-        self.npoints = npoints
-        if mode == 'P':
-            self.mpcommands = ['calc_pi']
-        elif mode == 'T':
-            self.thcommands = ['calc_pi']
-        elif mode == 'S':
-            self.commands = ['calc_pi']
-        self.n_cpu = multiprocessing.cpu_count()
-    
-    def submit_tasks(self):
-        self.i = plac.Interpreter(self).__enter__()            
-        return [self.i.submit('calc_pi %d' % (self.npoints / self.n_cpu))
-                for _ in range(self.n_cpu)]
-
-    def close(self):
-        self.i.close()
-
-    @plac.annotations(
-        npoints=('npoints', 'positional', None, int))
-    def calc_pi(self, npoints):
-        counts = 0
-        for j in xrange(npoints):
-            n, r = divmod(j, 1000000)
-            if r == 0:
-                yield '%dM iterations' % n
-            x, y = random(), random()
-            if x*x + y*y < 1:
-                counts += 1
-        yield (4.0 * counts)/npoints
-
-    def run(self):
-        tasks = self.i.tasks()
-        for t in tasks:
-            t.run()
-        try:
-            total = 0
-            for task in tasks:
-                total += task.result
-        except: # the task was killed
-            print tasks
-            return
-        return total / self.n_cpu
-
-if __name__ == '__main__':
-    pc = plac.call(PiCalculator)
-    pc.submit_tasks()
-    try:
-        import time; t0 = time.time()
-        print '%f in %f seconds ' % (pc.run(), time.time() - t0)
-    finally:
-        pc.close()
-
-
-

Notice the submit_tasks method, which instantiates and initializes a -plac.Interpreter object and submits a number of commands corresponding -to the number of available CPUs. The calc_pi command yield a log -message every million of interactions, just to monitor the progress of -the computation. The run method starts all the submitted commands -in parallel and sums the results. It returns the average value of pi -after the slowest CPU has finished its job (if the CPUs are equal and -equally busy they should finish more or less at the same time).

-

Here are the results on my old Macbook with Ubuntu 10.04 and Python 2.6, -for 10 million of iterations:

-
-$ python picalculator.py -mP 10000000 # two processes
-3.141904 in 5.744545 seconds
-$ python picalculator.py -mT 10000000 # two threads
-3.141272 in 13.875645 seconds
-$ python picalculator.py -mS 10000000 # sequential
-3.141586 in 11.353841 seconds
-
-

As you see using processes one gets a 2x speedup indeed, where the threaded -mode is some 20% slower than the sequential mode.

-

Since the pattern submit a bunch of tasks, starts them and collect the -results is so common, plac provides an utility function -runp(genseq, mode='p', monitors=(), start=True) to start -a bunch a generators and return a list of task objects. By default -runp use processes, but you can use threads by passing mode='t'. -If you do not wont to start the tasks, you can say so (start=False). -With runp the parallel pi calculation becomes a one-liner:

-
-sum(task.result for task in plac.runp(calc_pi(N) for i in range(ncpus)))/ncpus
-
-

The file test_runp in the doc directory of the plac distribution -shows another couple of examples of usage, including how to show the -results of the running computation on a TkMonitor.

-
-
-

The plac server

-

A command-line oriented interface can be easily converted into a -socket-based interface. Starting from release 0.7 plac features -a builtin server which is able to accept commands from multiple -clients and to execute them. The server works by instantiating -a separate interpreter for each client, so that if a client interpreter -dies for any reason the other interpreters keep working. -To avoid external dependencies the server is based on the asynchat -module in the standard library, but it would not be difficult to -replace the server with a different one (for instance, a Twisted server). -Since asynchat-based servers are asynchronous, any blocking command -in the interpreter should be run in a separated process or thread. -The default port for the plac server is 2199, and the command to -signal end-of-connection is EOF. -For instance, here is how you could manage remote import on a database -(say a SQLite db):

-
-import plac
-from importer2 import FakeImporter
-
-def main(port=2199):
-    main = FakeImporter('dsn')
-    plac.Interpreter(main).start_server(port)
-    
-if __name__ == '__main__':
-   plac.call(main)
-
-
-

You can connect to the server with telnet on port 2199, as follows:

-
-$ telnet localhost 2199
-Trying ::1...
-Trying 127.0.0.1...
-Connected to localhost.
-Escape character is '^]'.
-i> import_file f1
-i> .list
-<ThreadedTask 1 [import_file f1] RUNNING>
-i> .out
-Imported 100 lines
-Imported 200 lines
-i> EOF
-Connection closed by foreign host.
-
-
-
-

Summary

-

Once plac claimed to be the easiest command-line arguments parser -in the world. Having read this document you may think that it is not -so easy after all. But it is a false impression. Actually the -rules are quite simple:

-
    -
  1. if you want to implement a command-line script, use plac.call;
  2. -
  3. if you want to implement a command interpreter, use plac.Interpreter:
      -
    • for an interactive interpreter, call the .interact method;
    • -
    • for an batch interpreter, call the .execute method;
    • -
    -
  4. -
  5. for testing call the Interpreter.check method in the appropriate context -or use the Interpreter.doctest feature;
  6. -
  7. if you need to go at a lower level, you may need to call the -Interpreter.send method which returns a (finished) Task object.
  8. -
  9. long running command can be executed in the background as threads or -processes: just declare them in the lists thcommands and mpcommands -respectively.
  10. -
  11. the .start_server method starts an asynchronous server on the -given port number (default 2199)
  12. -
-

Moreover, remember that plac_runner.py is your friend.

-
-
-
-

Appendix: custom annotation objects

-

Internally plac 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:

-
-# annotations.py
-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
-
-
-

You can use such annotations objects as follows:

-
-# example11.py
-import plac
-from annotations import Positional
-
-@plac.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 plac; plac.call(main)
-
-
-

Here is the usage message you get:

-
-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
+The documentation of plac has been moved here.
 
-
-

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.

-
-
-
diff --git a/plac/doc/plac.pdf b/plac/doc/plac.pdf deleted file mode 100644 index 527b867..0000000 --- a/plac/doc/plac.pdf +++ /dev/null @@ -1,13201 +0,0 @@ -%PDF-1.4 -%“Œ‹ž 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 7 0 R - /F4 96 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 (mailto:michele.simionato@gmail.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 153.7323 - 705.7736 - 526.5827 - 717.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER2': class PDFDictionary -5 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 153.7323 - 675.7736 - 526.5827 - 687.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER3': class PDFDictionary -6 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://plac.googlecode.com/hg/doc/plac.html) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 153.7323 - 648.7736 - 526.5827 - 660.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'F3': class PDFType1Font -7 0 obj -% Font Courier -<< /BaseFont /Courier - /Encoding /WinAnsiEncoding - /Name /F3 - /Subtype /Type1 - /Type /Font >> -endobj -% 'Page1': class PDFPage -8 0 obj -% Page dictionary -<< /Annots [ 4 0 R - 5 0 R - 6 0 R ] - /Contents 351 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER4': class LinkAnnotation -9 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 8 0 R - /XYZ - 62.69291 - 765.0236 - 0 ] - /Rect [ 62.69291 - 723.7736 - 286.0929 - 735.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER5': class LinkAnnotation -10 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 8 0 R - /XYZ - 62.69291 - 765.0236 - 0 ] - /Rect [ 527.0227 - 723.7736 - 532.5827 - 735.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER6': class LinkAnnotation -11 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 97 0 R - /XYZ - 62.69291 - 765.0236 - 0 ] - /Rect [ 82.69291 - 705.7736 - 223.8629 - 717.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER7': class LinkAnnotation -12 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 97 0 R - /XYZ - 62.69291 - 765.0236 - 0 ] - /Rect [ 527.0227 - 705.7736 - 532.5827 - 717.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER8': class LinkAnnotation -13 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 97 0 R - /XYZ - 62.69291 - 411.0236 - 0 ] - /Rect [ 82.69291 - 687.7736 - 223.2929 - 699.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER9': class LinkAnnotation -14 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 97 0 R - /XYZ - 62.69291 - 411.0236 - 0 ] - /Rect [ 527.0227 - 687.7736 - 532.5827 - 699.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER10': class LinkAnnotation -15 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 112 0 R - /XYZ - 62.69291 - 765.0236 - 0 ] - /Rect [ 82.69291 - 669.7736 - 216.6329 - 681.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER11': class LinkAnnotation -16 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 112 0 R - /XYZ - 62.69291 - 765.0236 - 0 ] - /Rect [ 527.0227 - 669.7736 - 532.5827 - 681.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER12': class LinkAnnotation -17 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 122 0 R - /XYZ - 62.69291 - 707.8236 - 0 ] - /Rect [ 82.69291 - 651.7736 - 257.7529 - 663.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER13': class LinkAnnotation -18 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 122 0 R - /XYZ - 62.69291 - 707.8236 - 0 ] - /Rect [ 527.0227 - 651.7736 - 532.5827 - 663.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER14': class LinkAnnotation -19 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 126 0 R - /XYZ - 62.69291 - 715.8236 - 0 ] - /Rect [ 82.69291 - 633.7736 - 157.7129 - 645.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER15': class LinkAnnotation -20 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 126 0 R - /XYZ - 62.69291 - 715.8236 - 0 ] - /Rect [ 527.0227 - 633.7736 - 532.5827 - 645.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER16': class LinkAnnotation -21 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 126 0 R - /XYZ - 62.69291 - 216.5299 - 0 ] - /Rect [ 82.69291 - 615.7736 - 194.4129 - 627.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER17': class LinkAnnotation -22 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 126 0 R - /XYZ - 62.69291 - 216.5299 - 0 ] - /Rect [ 527.0227 - 615.7736 - 532.5827 - 627.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER18': class LinkAnnotation -23 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 134 0 R - /XYZ - 62.69291 - 457.4236 - 0 ] - /Rect [ 82.69291 - 597.7736 - 144.3829 - 609.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER19': class LinkAnnotation -24 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 134 0 R - /XYZ - 62.69291 - 457.4236 - 0 ] - /Rect [ 521.4627 - 597.7736 - 532.5827 - 609.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER20': class LinkAnnotation -25 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 139 0 R - /XYZ - 62.69291 - 478.6236 - 0 ] - /Rect [ 82.69291 - 579.7736 - 166.6029 - 591.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER21': class LinkAnnotation -26 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 139 0 R - /XYZ - 62.69291 - 478.6236 - 0 ] - /Rect [ 521.4627 - 579.7736 - 532.5827 - 591.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER22': class LinkAnnotation -27 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 142 0 R - /XYZ - 62.69291 - 420.6236 - 0 ] - /Rect [ 82.69291 - 561.7736 - 171.6129 - 573.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER23': class LinkAnnotation -28 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 142 0 R - /XYZ - 62.69291 - 420.6236 - 0 ] - /Rect [ 521.4627 - 561.7736 - 532.5827 - 573.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER24': class LinkAnnotation -29 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 144 0 R - /XYZ - 62.69291 - 409.4236 - 0 ] - /Rect [ 82.69291 - 543.7736 - 228.8629 - 555.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER25': class LinkAnnotation -30 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 144 0 R - /XYZ - 62.69291 - 409.4236 - 0 ] - /Rect [ 521.4627 - 543.7736 - 532.5827 - 555.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER26': class LinkAnnotation -31 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 159 0 R - /XYZ - 62.69291 - 357.4236 - 0 ] - /Rect [ 82.69291 - 525.7736 - 156.0529 - 537.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER27': class LinkAnnotation -32 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 159 0 R - /XYZ - 62.69291 - 357.4236 - 0 ] - /Rect [ 521.4627 - 525.7736 - 532.5827 - 537.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER28': class LinkAnnotation -33 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 214 0 R - /XYZ - 62.69291 - 705.0236 - 0 ] - /Rect [ 82.69291 - 507.7736 - 204.4129 - 519.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER29': class LinkAnnotation -34 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 214 0 R - /XYZ - 62.69291 - 705.0236 - 0 ] - /Rect [ 521.4627 - 507.7736 - 532.5827 - 519.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER30': class LinkAnnotation -35 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 214 0 R - /XYZ - 62.69291 - 411.0236 - 0 ] - /Rect [ 82.69291 - 489.7736 - 128.2729 - 501.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER31': class LinkAnnotation -36 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 214 0 R - /XYZ - 62.69291 - 411.0236 - 0 ] - /Rect [ 521.4627 - 489.7736 - 532.5827 - 501.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER32': class LinkAnnotation -37 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 214 0 R - /XYZ - 62.69291 - 207.0236 - 0 ] - /Rect [ 82.69291 - 471.7736 - 228.3129 - 483.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER33': class LinkAnnotation -38 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 214 0 R - /XYZ - 62.69291 - 207.0236 - 0 ] - /Rect [ 521.4627 - 471.7736 - 532.5827 - 483.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER34': class LinkAnnotation -39 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 231 0 R - /XYZ - 62.69291 - 585.0236 - 0 ] - /Rect [ 62.69291 - 453.7736 - 182.7329 - 465.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER35': class LinkAnnotation -40 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 231 0 R - /XYZ - 62.69291 - 585.0236 - 0 ] - /Rect [ 521.4627 - 453.7736 - 532.5827 - 465.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER36': class LinkAnnotation -41 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 231 0 R - /XYZ - 62.69291 - 552.0236 - 0 ] - /Rect [ 82.69291 - 435.7736 - 134.9429 - 447.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER37': class LinkAnnotation -42 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 231 0 R - /XYZ - 62.69291 - 552.0236 - 0 ] - /Rect [ 521.4627 - 435.7736 - 532.5827 - 447.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER38': class LinkAnnotation -43 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 231 0 R - /XYZ - 62.69291 - 330.0236 - 0 ] - /Rect [ 82.69291 - 417.7736 - 252.7429 - 429.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER39': class LinkAnnotation -44 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 231 0 R - /XYZ - 62.69291 - 330.0236 - 0 ] - /Rect [ 521.4627 - 417.7736 - 532.5827 - 429.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER40': class LinkAnnotation -45 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 234 0 R - /XYZ - 62.69291 - 533.8236 - 0 ] - /Rect [ 82.69291 - 399.7736 - 195.5229 - 411.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER41': class LinkAnnotation -46 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 234 0 R - /XYZ - 62.69291 - 533.8236 - 0 ] - /Rect [ 521.4627 - 399.7736 - 532.5827 - 411.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER42': class LinkAnnotation -47 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 237 0 R - /XYZ - 62.69291 - 497.8236 - 0 ] - /Rect [ 82.69291 - 381.7736 - 149.9429 - 393.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER43': class LinkAnnotation -48 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 237 0 R - /XYZ - 62.69291 - 497.8236 - 0 ] - /Rect [ 521.4627 - 381.7736 - 532.5827 - 393.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER44': class LinkAnnotation -49 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 247 0 R - /XYZ - 62.69291 - 171.0642 - 0 ] - /Rect [ 82.69291 - 363.7736 - 161.0529 - 375.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER45': class LinkAnnotation -50 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 247 0 R - /XYZ - 62.69291 - 171.0642 - 0 ] - /Rect [ 521.4627 - 363.7736 - 532.5827 - 375.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER46': class LinkAnnotation -51 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 253 0 R - /XYZ - 62.69291 - 256.6236 - 0 ] - /Rect [ 82.69291 - 345.7736 - 210.5129 - 357.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER47': class LinkAnnotation -52 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 253 0 R - /XYZ - 62.69291 - 256.6236 - 0 ] - /Rect [ 521.4627 - 345.7736 - 532.5827 - 357.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER48': class LinkAnnotation -53 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 260 0 R - /XYZ - 62.69291 - 765.0236 - 0 ] - /Rect [ 82.69291 - 327.7736 - 167.7229 - 339.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER49': class LinkAnnotation -54 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 260 0 R - /XYZ - 62.69291 - 765.0236 - 0 ] - /Rect [ 521.4627 - 327.7736 - 532.5827 - 339.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER50': class LinkAnnotation -55 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 262 0 R - /XYZ - 62.69291 - 468.6236 - 0 ] - /Rect [ 82.69291 - 309.7736 - 158.2829 - 321.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER51': class LinkAnnotation -56 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 262 0 R - /XYZ - 62.69291 - 468.6236 - 0 ] - /Rect [ 521.4627 - 309.7736 - 532.5827 - 321.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER52': class LinkAnnotation -57 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 279 0 R - /XYZ - 62.69291 - 566.6236 - 0 ] - /Rect [ 82.69291 - 291.7736 - 152.7229 - 303.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER53': class LinkAnnotation -58 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 279 0 R - /XYZ - 62.69291 - 566.6236 - 0 ] - /Rect [ 521.4627 - 291.7736 - 532.5827 - 303.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER54': class LinkAnnotation -59 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 283 0 R - /XYZ - 62.69291 - 729.0236 - 0 ] - /Rect [ 82.69291 - 273.7736 - 205.5229 - 285.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER55': class LinkAnnotation -60 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 283 0 R - /XYZ - 62.69291 - 729.0236 - 0 ] - /Rect [ 521.4627 - 273.7736 - 532.5827 - 285.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER56': class LinkAnnotation -61 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 286 0 R - /XYZ - 62.69291 - 729.0236 - 0 ] - /Rect [ 82.69291 - 255.7736 - 209.9529 - 267.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER57': class LinkAnnotation -62 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 286 0 R - /XYZ - 62.69291 - 729.0236 - 0 ] - /Rect [ 521.4627 - 255.7736 - 532.5827 - 267.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER58': class LinkAnnotation -63 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 290 0 R - /XYZ - 62.69291 - 555.8236 - 0 ] - /Rect [ 82.69291 - 237.7736 - 192.7429 - 249.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER59': class LinkAnnotation -64 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 290 0 R - /XYZ - 62.69291 - 555.8236 - 0 ] - /Rect [ 521.4627 - 237.7736 - 532.5827 - 249.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER60': class LinkAnnotation -65 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 293 0 R - /XYZ - 62.69291 - 659.8236 - 0 ] - /Rect [ 82.69291 - 219.7736 - 177.1729 - 231.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER61': class LinkAnnotation -66 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 293 0 R - /XYZ - 62.69291 - 659.8236 - 0 ] - /Rect [ 521.4627 - 219.7736 - 532.5827 - 231.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER62': class LinkAnnotation -67 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 298 0 R - /XYZ - 62.69291 - 765.0236 - 0 ] - /Rect [ 82.69291 - 201.7736 - 271.6529 - 213.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER63': class LinkAnnotation -68 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 298 0 R - /XYZ - 62.69291 - 765.0236 - 0 ] - /Rect [ 521.4627 - 201.7736 - 532.5827 - 213.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER64': class LinkAnnotation -69 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 298 0 R - /XYZ - 62.69291 - 297.8236 - 0 ] - /Rect [ 82.69291 - 183.7736 - 286.6829 - 195.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER65': class LinkAnnotation -70 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 298 0 R - /XYZ - 62.69291 - 297.8236 - 0 ] - /Rect [ 521.4627 - 183.7736 - 532.5827 - 195.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER66': class LinkAnnotation -71 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 300 0 R - /XYZ - 62.69291 - 427.8236 - 0 ] - /Rect [ 82.69291 - 165.7736 - 152.1629 - 177.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER67': class LinkAnnotation -72 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 300 0 R - /XYZ - 62.69291 - 427.8236 - 0 ] - /Rect [ 521.4627 - 165.7736 - 532.5827 - 177.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER68': class LinkAnnotation -73 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 302 0 R - /XYZ - 62.69291 - 717.0236 - 0 ] - /Rect [ 82.69291 - 147.7736 - 206.6229 - 159.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER69': class LinkAnnotation -74 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 302 0 R - /XYZ - 62.69291 - 717.0236 - 0 ] - /Rect [ 521.4627 - 147.7736 - 532.5827 - 159.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER70': class LinkAnnotation -75 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 306 0 R - /XYZ - 62.69291 - 553.2159 - 0 ] - /Rect [ 82.69291 - 129.7736 - 151.6029 - 141.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER71': class LinkAnnotation -76 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 306 0 R - /XYZ - 62.69291 - 553.2159 - 0 ] - /Rect [ 521.4627 - 129.7736 - 532.5827 - 141.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER72': class LinkAnnotation -77 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 309 0 R - /XYZ - 62.69291 - 765.0236 - 0 ] - /Rect [ 82.69291 - 111.7736 - 125.4729 - 123.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER73': class LinkAnnotation -78 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 309 0 R - /XYZ - 62.69291 - 765.0236 - 0 ] - /Rect [ 521.4627 - 111.7736 - 532.5827 - 123.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER74': class LinkAnnotation -79 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 309 0 R - /XYZ - 62.69291 - 454.6772 - 0 ] - /Rect [ 82.69291 - 93.77362 - 246.1129 - 105.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER75': class LinkAnnotation -80 0 obj -<< /Border [ 0 - 0 - 0 ] - /Contents () - /Dest [ 309 0 R - /XYZ - 62.69291 - 454.6772 - 0 ] - /Rect [ 521.4627 - 93.77362 - 532.5827 - 105.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page2': class PDFPage -81 0 obj -% Page dictionary -<< /Annots [ 9 0 R - 10 0 R - 11 0 R - 12 0 R - 13 0 R - 14 0 R - 15 0 R - 16 0 R - 17 0 R - 18 0 R - 19 0 R - 20 0 R - 21 0 R - 22 0 R - 23 0 R - 24 0 R - 25 0 R - 26 0 R - 27 0 R - 28 0 R - 29 0 R - 30 0 R - 31 0 R - 32 0 R - 33 0 R - 34 0 R - 35 0 R - 36 0 R - 37 0 R - 38 0 R - 39 0 R - 40 0 R - 41 0 R - 42 0 R - 43 0 R - 44 0 R - 45 0 R - 46 0 R - 47 0 R - 48 0 R - 49 0 R - 50 0 R - 51 0 R - 52 0 R - 53 0 R - 54 0 R - 55 0 R - 56 0 R - 57 0 R - 58 0 R - 59 0 R - 60 0 R - 61 0 R - 62 0 R - 63 0 R - 64 0 R - 65 0 R - 66 0 R - 67 0 R - 68 0 R - 69 0 R - 70 0 R - 71 0 R - 72 0 R - 73 0 R - 74 0 R - 75 0 R - 76 0 R - 77 0 R - 78 0 R - 79 0 R - 80 0 R ] - /Contents 352 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER76': class PDFDictionary -82 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://docs.python.org/library/getopt.html) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 214.8914 - 717.7736 - 246.5585 - 729.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER77': class PDFDictionary -83 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://docs.python.org/library/optparse.html) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 346.507 - 717.7736 - 389.2842 - 729.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER78': class PDFDictionary -84 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 493.1227 - 717.7736 - 531.4956 - 729.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER79': class PDFDictionary -85 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 346.384 - 705.7736 - 388.8477 - 717.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER80': class PDFDictionary -86 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://www.welton.it/articles/scalable_systems) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 311.5097 - 663.7736 - 371.5115 - 675.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER81': class PDFDictionary -87 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 62.69291 - 615.7736 - 84.90623 - 627.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER82': class PDFDictionary -88 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 453.4229 - 603.7736 - 492.8829 - 615.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER83': class PDFDictionary -89 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 116.9711 - 585.7736 - 139.5794 - 597.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER84': class PDFDictionary -90 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 277.9887 - 585.7736 - 321.7169 - 597.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER85': class PDFDictionary -91 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 504.0394 - 573.7736 - 525.3627 - 585.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER86': class PDFDictionary -92 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 351.0408 - 561.7736 - 390.5008 - 573.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER87': class PDFDictionary -93 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 247.8817 - 537.7736 - 266.2217 - 549.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER88': class PDFDictionary -94 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 62.69291 - 507.7736 - 85.3538 - 519.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER89': class PDFDictionary -95 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 124.2211 - 435.7736 - 146.9252 - 447.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'F4': class PDFType1Font -96 0 obj -% Font Helvetica-Oblique -<< /BaseFont /Helvetica-Oblique - /Encoding /WinAnsiEncoding - /Name /F4 - /Subtype /Type1 - /Type /Font >> -endobj -% 'Page3': class PDFPage -97 0 obj -% Page dictionary -<< /Annots [ 82 0 R - 83 0 R - 84 0 R - 85 0 R - 86 0 R - 87 0 R - 88 0 R - 89 0 R - 90 0 R - 91 0 R - 92 0 R - 93 0 R - 94 0 R - 95 0 R ] - /Contents 353 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER90': class PDFDictionary -98 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://docs.python.org/library/getopt.html) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 325.341 - 717.7736 - 356.6198 - 729.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER91': class PDFDictionary -99 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://docs.python.org/library/optparse.html) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 376.7786 - 717.7736 - 419.1674 - 729.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER92': class PDFDictionary -100 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 365.694 - 705.7736 - 408.8281 - 717.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER93': class PDFDictionary -101 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 83.82606 - 478.5736 - 106.0692 - 490.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER94': class PDFDictionary -102 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 243.8829 - 466.5736 - 265.0029 - 478.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER95': class PDFDictionary -103 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 83.6329 - 329.3736 - 105.6829 - 341.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER96': class PDFDictionary -104 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 421.9727 - 329.3736 - 465.1427 - 341.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER97': class PDFDictionary -105 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 107.8707 - 134.9736 - 129.1584 - 146.9736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER98': class PDFDictionary -106 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 117.7229 - 122.9736 - 138.8429 - 134.9736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page4': class PDFPage -107 0 obj -% Page dictionary -<< /Annots [ 98 0 R - 99 0 R - 100 0 R - 101 0 R - 102 0 R - 103 0 R - 104 0 R - 105 0 R - 106 0 R ] - /Contents 354 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER99': class PDFDictionary -108 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 321.4303 - 460.5736 - 363.754 - 472.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER100': class PDFDictionary -109 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 126.0429 - 448.5736 - 147.1629 - 460.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER101': class PDFDictionary -110 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 162.6654 - 210.1736 - 184.5985 - 222.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER102': class PDFDictionary -111 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 155.8754 - 198.1736 - 177.1129 - 210.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page5': class PDFPage -112 0 obj -% Page dictionary -<< /Annots [ 108 0 R - 109 0 R - 110 0 R - 111 0 R ] - /Contents 355 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER103': class PDFDictionary -113 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 446.1627 - 499.3736 - 464.5027 - 511.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER104': class PDFDictionary -114 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 62.69291 - 469.3736 - 84.53568 - 481.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER105': class PDFDictionary -115 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://code.activestate.com/recipes/278844-parsing-the-command-line/) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 331.3133 - 469.3736 - 383.5633 - 481.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page6': class PDFPage -116 0 obj -% Page dictionary -<< /Annots [ 113 0 R - 114 0 R - 115 0 R ] - /Contents 356 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER106': class PDFDictionary -117 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 62.69291 - 720.5736 - 83.81291 - 732.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER107': class PDFDictionary -118 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://code.activestate.com/recipes/278844-parsing-the-command-line/) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 240.1228 - 624.5736 - 297.7099 - 636.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER108': class PDFDictionary -119 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://code.activestate.com/recipes/278844-parsing-the-command-line/) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 62.69291 - 612.5736 - 114.9429 - 624.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER109': class PDFDictionary -120 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 496.4721 - 612.5736 - 518.6827 - 624.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER110': class PDFDictionary -121 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 494.1558 - 451.3736 - 515.9027 - 463.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page7': class PDFPage -122 0 obj -% Page dictionary -<< /Annots [ 117 0 R - 118 0 R - 119 0 R - 120 0 R - 121 0 R ] - /Contents 357 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Page8': class PDFPage -123 0 obj -% Page dictionary -<< /Contents 358 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER111': class PDFDictionary -124 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 62.69291 - 680.5736 - 84.62846 - 692.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER112': class PDFDictionary -125 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 110.2829 - 157.2799 - 132.8629 - 169.2799 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page9': class PDFPage -126 0 obj -% Page dictionary -<< /Annots [ 124 0 R - 125 0 R ] - /Contents 359 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER113': class PDFDictionary -127 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 392.5829 - 470.1736 - 413.7029 - 482.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER114': class PDFDictionary -128 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 157.3904 - 422.1736 - 179.9938 - 434.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER115': class PDFDictionary -129 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 184.0634 - 410.1736 - 223.5234 - 422.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER116': class PDFDictionary -130 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 62.69291 - 398.1736 - 102.1529 - 410.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER117': class PDFDictionary -131 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 192.7997 - 398.1736 - 237.9391 - 410.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER118': class PDFDictionary -132 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 324.4372 - 398.1736 - 342.7772 - 410.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER119': class PDFDictionary -133 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 360.0429 - 170.1736 - 402.2829 - 182.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page10': class PDFPage -134 0 obj -% Page dictionary -<< /Annots [ 127 0 R - 128 0 R - 129 0 R - 130 0 R - 131 0 R - 132 0 R - 133 0 R ] - /Contents 360 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Page11': class PDFPage -135 0 obj -% Page dictionary -<< /Contents 361 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER120': class PDFDictionary -136 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 338.1568 - 443.3736 - 360.5113 - 455.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER121': class PDFDictionary -137 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://www.sqlalchemy.org/) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 110.6843 - 431.3736 - 169.0343 - 443.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER122': class PDFDictionary -138 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://www.sqlalchemy.org/docs/reference/ext/sqlsoup.html) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 168.3029 - 419.3736 - 208.8829 - 431.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page12': class PDFPage -139 0 obj -% Page dictionary -<< /Annots [ 136 0 R - 137 0 R - 138 0 R ] - /Contents 362 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER123': class PDFDictionary -140 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 185.0709 - 385.3736 - 208.0228 - 397.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER124': class PDFDictionary -141 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 220.5998 - 373.3736 - 243.819 - 385.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page13': class PDFPage -142 0 obj -% Page dictionary -<< /Annots [ 140 0 R - 141 0 R ] - /Contents 363 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER125': class PDFDictionary -143 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 374.4929 - 350.1736 - 395.6129 - 362.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page14': class PDFPage -144 0 obj -% Page dictionary -<< /Annots [ 143 0 R ] - /Contents 364 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER126': class PDFDictionary -145 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 304.0655 - 312.0481 - 348.3808 - 324.0481 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER127': class PDFDictionary -146 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 293.7749 - 138.0481 - 316.2402 - 150.0481 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page15': class PDFPage -147 0 obj -% Page dictionary -<< /Annots [ 145 0 R - 146 0 R ] - /Contents 365 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER128': class PDFDictionary -148 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 62.69291 - 322.1736 - 84.8789 - 334.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER129': class PDFDictionary -149 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 466.5307 - 322.1736 - 509.8367 - 334.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER130': class PDFDictionary -150 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 124.3929 - 298.1736 - 163.8529 - 310.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER131': class PDFDictionary -151 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 85.69291 - 217.1736 - 127.9329 - 229.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER132': class PDFDictionary -152 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 85.69291 - 199.1736 - 107.9337 - 211.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER133': class PDFDictionary -153 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 308.5389 - 199.1736 - 351.8997 - 211.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER134': class PDFDictionary -154 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 380.6856 - 175.1736 - 423.7999 - 187.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER135': class PDFDictionary -155 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 494.4684 - 175.1736 - 516.4627 - 187.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER136': class PDFDictionary -156 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 85.69291 - 145.1736 - 108.3529 - 157.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER137': class PDFDictionary -157 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 277.2428 - 145.1736 - 321.0228 - 157.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER138': class PDFDictionary -158 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 404.5839 - 133.1736 - 426.0657 - 145.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page16': class PDFPage -159 0 obj -% Page dictionary -<< /Annots [ 148 0 R - 149 0 R - 150 0 R - 151 0 R - 152 0 R - 153 0 R - 154 0 R - 155 0 R - 156 0 R - 157 0 R - 158 0 R ] - /Contents 366 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER139': class PDFDictionary -160 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 85.69291 - 750.7736 - 108.61 - 762.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER140': class PDFDictionary -161 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 459.2622 - 738.7736 - 481.289 - 750.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER141': class PDFDictionary -162 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 85.69291 - 708.7736 - 108.9242 - 720.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER142': class PDFDictionary -163 0 obj -<< /A << /S /URI - /Type /Action - /URI (file:///home/micheles/Dropbox/md/gcodedev/plac/doc/in-writing) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 340.9248 - 708.7736 - 470.1087 - 720.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER143': class PDFDictionary -164 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 85.69291 - 678.7736 - 107.9247 - 690.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER144': class PDFDictionary -165 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 85.69291 - 666.7736 - 104.0329 - 678.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER145': class PDFDictionary -166 0 obj -<< /A << /S /URI - /Type /Action - /URI (file:///home/micheles/Dropbox/md/gcodedev/plac/doc/in-writing) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 489.2227 - 666.7736 - 532.176 - 678.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER146': class PDFDictionary -167 0 obj -<< /A << /S /URI - /Type /Action - /URI (file:///home/micheles/Dropbox/md/gcodedev/plac/doc/in-writing) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 85.69291 - 654.7736 - 159.6229 - 666.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER147': class PDFDictionary -168 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 62.69291 - 639.7736 - 83.81291 - 651.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER148': class PDFDictionary -169 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 219.4229 - 639.7736 - 261.6629 - 651.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER149': class PDFDictionary -170 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com/svn/tags/r11/doc/other-utilities.html?highlight=filetype#FileType) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 455.2227 - 609.7736 - 534.3667 - 621.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER150': class PDFDictionary -171 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 325.7268 - 442.5736 - 347.4138 - 454.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER151': class PDFDictionary -172 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com/svn/tags/r11/doc/ArgumentParser.html) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 327.2261 - 275.3736 - 410.5152 - 287.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER152': class PDFDictionary -173 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 275.5829 - 96.17362 - 317.8229 - 108.1736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page17': class PDFPage -174 0 obj -% Page dictionary -<< /Annots [ 160 0 R - 161 0 R - 162 0 R - 163 0 R - 164 0 R - 165 0 R - 166 0 R - 167 0 R - 168 0 R - 169 0 R - 170 0 R - 171 0 R - 172 0 R - 173 0 R ] - /Contents 367 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER153': class PDFDictionary -175 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 307.9178 - 753.7736 - 351.5999 - 765.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER154': class PDFDictionary -176 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 329.8034 - 729.7736 - 352.1804 - 741.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER155': class PDFDictionary -177 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 109.0098 - 669.7736 - 131.9967 - 681.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER156': class PDFDictionary -178 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 397.2929 - 645.7736 - 415.6329 - 657.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER157': class PDFDictionary -179 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/opterator) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 85.69291 - 624.7736 - 128.4929 - 636.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER158': class PDFDictionary -180 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/CLIArgs) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 85.69291 - 606.7736 - 124.5929 - 618.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER159': class PDFDictionary -181 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/commandline) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 85.69291 - 588.7736 - 147.9329 - 600.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER160': class PDFDictionary -182 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 464.3898 - 573.7736 - 503.8498 - 585.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER161': class PDFDictionary -183 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 305.0429 - 561.7736 - 323.3829 - 573.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER162': class PDFDictionary -184 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/Clap/0.7) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 455.0104 - 543.7736 - 479.9015 - 555.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER163': class PDFDictionary -185 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 303.707 - 531.7736 - 322.047 - 543.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER164': class PDFDictionary -186 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/Clap/0.7) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 328.8186 - 531.7736 - 353.3701 - 543.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER165': class PDFDictionary -187 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 62.69291 - 519.7736 - 81.03291 - 531.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER166': class PDFDictionary -188 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 62.69291 - 501.7736 - 84.4354 - 513.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER167': class PDFDictionary -189 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://docs.python.org/library/cmd.html) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 275.6978 - 501.7736 - 297.9903 - 513.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER168': class PDFDictionary -190 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://packages.python.org/cmd2/) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 203.5285 - 489.7736 - 231.1357 - 501.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER169': class PDFDictionary -191 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://packages.python.org/cmd2/) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 164.4129 - 477.7736 - 191.6429 - 489.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER170': class PDFDictionary -192 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 302.7929 - 477.7736 - 321.1329 - 489.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER171': class PDFDictionary -193 0 obj -<< /A << /S /URI - /Type /Action - /URI (https://github.com/pulp/marrow.script) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 458.7927 - 459.7736 - 522.0227 - 471.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER172': class PDFDictionary -194 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 276.5607 - 447.7736 - 297.9876 - 459.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER173': class PDFDictionary -195 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 427.9754 - 447.7736 - 470.5222 - 459.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER174': class PDFDictionary -196 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://packages.python.org/argh) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 497.8158 - 447.7736 - 522.0227 - 459.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER175': class PDFDictionary -197 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 357.7489 - 435.7736 - 397.2089 - 447.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER176': class PDFDictionary -198 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 462.971 - 435.7736 - 484.6916 - 447.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER177': class PDFDictionary -199 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 156.6051 - 375.7736 - 177.8606 - 387.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER178': class PDFDictionary -200 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 186.6535 - 351.7736 - 226.1135 - 363.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER179': class PDFDictionary -201 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 493.1227 - 351.7736 - 532.4646 - 363.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER180': class PDFDictionary -202 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 72.7804 - 339.7736 - 96.20788 - 351.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER181': class PDFDictionary -203 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 149.2229 - 309.7736 - 171.2704 - 321.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER182': class PDFDictionary -204 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 128.0309 - 267.7736 - 149.4369 - 279.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER183': class PDFDictionary -205 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 502.8367 - 267.7736 - 524.2427 - 279.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER184': class PDFDictionary -206 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 187.4797 - 243.7736 - 209.0991 - 255.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER185': class PDFDictionary -207 0 obj -<< /A << /S /URI - /Type /Action - /URI (file:///home/micheles/Dropbox/md/gcodedev/plac/doc/in-writing) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 301.6965 - 243.7736 - 426.0446 - 255.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER186': class PDFDictionary -208 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 83.6829 - 171.7736 - 105.7829 - 183.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER187': class PDFDictionary -209 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://code.activestate.com/recipes/278844-parsing-the-command-line/) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 446.5627 - 171.7736 - 502.5727 - 183.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER188': class PDFDictionary -210 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 275.6828 - 159.7736 - 297.3688 - 171.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER189': class PDFDictionary -211 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://docs.python.org/library/optparse.html?highlight=optionparser#optparse.OptionParser) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 77.19665 - 147.7736 - 139.4904 - 159.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER190': class PDFDictionary -212 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 96.54131 - 135.7736 - 139.0255 - 147.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER191': class PDFDictionary -213 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 203.5016 - 102.7736 - 245.8453 - 114.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page18': class PDFPage -214 0 obj -% Page dictionary -<< /Annots [ 175 0 R - 176 0 R - 177 0 R - 178 0 R - 179 0 R - 180 0 R - 181 0 R - 182 0 R - 183 0 R - 184 0 R - 185 0 R - 186 0 R - 187 0 R - 188 0 R - 189 0 R - 190 0 R - 191 0 R - 192 0 R - 193 0 R - 194 0 R - 195 0 R - 196 0 R - 197 0 R - 198 0 R - 199 0 R - 200 0 R - 201 0 R - 202 0 R - 203 0 R - 204 0 R - 205 0 R - 206 0 R - 207 0 R - 208 0 R - 209 0 R - 210 0 R - 211 0 R - 212 0 R - 213 0 R ] - /Contents 368 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER192': class PDFDictionary -215 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 - 711.7736 - 138.7898 - 723.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER193': class PDFDictionary -216 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 114.6649 - 699.7736 - 154.1249 - 711.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER194': class PDFDictionary -217 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 191.6329 - 687.7736 - 233.8729 - 699.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER195': class PDFDictionary -218 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/Clap/0.7) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 263.3429 - 657.7736 - 286.6829 - 669.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER196': class PDFDictionary -219 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 298.1928 - 609.7736 - 316.5328 - 621.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER197': class PDFDictionary -220 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 185.4471 - 516.7736 - 207.1062 - 528.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER198': class PDFDictionary -221 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 177.6784 - 504.7736 - 199.6123 - 516.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER199': class PDFDictionary -222 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 62.69291 - 450.7736 - 83.81291 - 462.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER200': class PDFDictionary -223 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 278.4678 - 432.7736 - 300.0328 - 444.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER201': class PDFDictionary -224 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 117.3573 - 420.7736 - 138.5845 - 432.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER202': class PDFDictionary -225 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://twill.idyll.org/) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 82.74466 - 378.7736 - 104.4564 - 390.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER203': class PDFDictionary -226 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 127.2882 - 378.7736 - 145.6282 - 390.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER204': class PDFDictionary -227 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 410.1674 - 378.7736 - 433.5592 - 390.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER205': class PDFDictionary -228 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 265.9578 - 354.7736 - 284.2978 - 366.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER206': class PDFDictionary -229 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 487.7492 - 354.7736 - 506.0892 - 366.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER207': class PDFDictionary -230 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://plac.googlecode.com/hg/doc/plac.html) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 62.69291 - 198.7736 - 157.1829 - 210.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page19': class PDFPage -231 0 obj -% Page dictionary -<< /Annots [ 215 0 R - 216 0 R - 217 0 R - 218 0 R - 219 0 R - 220 0 R - 221 0 R - 222 0 R - 223 0 R - 224 0 R - 225 0 R - 226 0 R - 227 0 R - 228 0 R - 229 0 R - 230 0 R ] - /Contents 369 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Page20': class PDFPage -232 0 obj -% Page dictionary -<< /Contents 370 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER208': class PDFDictionary -233 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 383.9329 - 576.5736 - 405.0529 - 588.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page21': class PDFPage -234 0 obj -% Page dictionary -<< /Annots [ 233 0 R ] - /Contents 371 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER209': class PDFDictionary -235 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 370.6785 - 199.3736 - 392.4956 - 211.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER210': class PDFDictionary -236 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 455.8742 - 199.3736 - 477.6913 - 211.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page22': class PDFPage -237 0 obj -% Page dictionary -<< /Annots [ 235 0 R - 236 0 R ] - /Contents 372 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER211': class PDFDictionary -238 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 185.9351 - 624.9893 - 207.4695 - 636.9893 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER212': class PDFDictionary -239 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://docs.python.org/library/shlex.html) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 369.8905 - 624.9893 - 396.425 - 636.9893 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER213': class PDFDictionary -240 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 408.8916 - 612.9893 - 427.2316 - 624.9893 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER214': class PDFDictionary -241 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://docs.python.org/library/shlex.html) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 62.69291 - 600.9893 - 86.03291 - 612.9893 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER215': class PDFDictionary -242 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 92.4689 - 600.9893 - 114.4649 - 612.9893 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER216': class PDFDictionary -243 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://docs.python.org/library/shlex.html) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 149.3204 - 588.9893 - 176.9472 - 600.9893 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER217': class PDFDictionary -244 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 129.6923 - 492.9893 - 151.4655 - 504.9893 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER218': class PDFDictionary -245 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 173.8529 - 480.9893 - 194.9729 - 492.9893 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER219': class PDFDictionary -246 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 460.388 - 123.8142 - 482.0127 - 135.8142 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page23': class PDFPage -247 0 obj -% Page dictionary -<< /Annots [ 238 0 R - 239 0 R - 240 0 R - 241 0 R - 242 0 R - 243 0 R - 244 0 R - 245 0 R - 246 0 R ] - /Contents 373 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER220': class PDFDictionary -248 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 95.32996 - 717.7736 - 116.857 - 729.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER221': class PDFDictionary -249 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://plac.googlecode.com/hg/doc/plac.html) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 316.3528 - 221.3736 - 409.2453 - 233.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER222': class PDFDictionary -250 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 419.1694 - 209.3736 - 440.4061 - 221.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER223': class PDFDictionary -251 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 62.69291 - 179.3736 - 84.70395 - 191.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER224': class PDFDictionary -252 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 293.2359 - 101.3736 - 316.4697 - 113.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page24': class PDFPage -253 0 obj -% Page dictionary -<< /Annots [ 248 0 R - 249 0 R - 250 0 R - 251 0 R - 252 0 R ] - /Contents 374 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER225': class PDFDictionary -254 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 431.7904 - 118.5736 - 453.7245 - 130.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page25': class PDFPage -255 0 obj -% Page dictionary -<< /Annots [ 254 0 R ] - /Contents 375 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER226': class PDFDictionary -256 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 255.5885 - 753.7736 - 278.2757 - 765.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER227': class PDFDictionary -257 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://docs.python.org/library/cmd.html) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 513.6927 - 729.7736 - 532.1846 - 741.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER228': class PDFDictionary -258 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 323.3 - 226.8179 - 344.4661 - 238.8179 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page26': class PDFPage -259 0 obj -% Page dictionary -<< /Annots [ 256 0 R - 257 0 R - 258 0 R ] - /Contents 376 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Page27': class PDFPage -260 0 obj -% Page dictionary -<< /Contents 377 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER229': class PDFDictionary -261 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 179.0529 - 433.3736 - 201.1953 - 445.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page28': class PDFPage -262 0 obj -% Page dictionary -<< /Annots [ 261 0 R ] - /Contents 378 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER230': class PDFDictionary -263 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://freshmeat.net/projects/rlwrap/) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 377.8504 - 481.3736 - 409.861 - 493.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER231': class PDFDictionary -264 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 242.4466 - 469.3736 - 263.7922 - 481.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER232': class PDFDictionary -265 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://ipython.scipy.org/moin/PyReadline/Intro) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 456.2271 - 469.3736 - 505.3627 - 481.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER233': class PDFDictionary -266 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 363.1739 - 445.3736 - 386.5004 - 457.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER234': class PDFDictionary -267 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 479.7508 - 433.3736 - 501.4627 - 445.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER235': class PDFDictionary -268 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://docs.python.org/library/cmd.html) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 366.9454 - 403.3736 - 388.8033 - 415.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER236': class PDFDictionary -269 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://docs.python.org/library/cmd.html) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 172.4855 - 391.3736 - 191.3755 - 403.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page29': class PDFPage -270 0 obj -% Page dictionary -<< /Annots [ 263 0 R - 264 0 R - 265 0 R - 266 0 R - 267 0 R - 268 0 R - 269 0 R ] - /Contents 379 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER237': class PDFDictionary -271 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 390.8027 - 708.5736 - 435.0027 - 720.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER238': class PDFDictionary -272 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 213.451 - 696.5736 - 237.5255 - 708.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER239': class PDFDictionary -273 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 114.9429 - 672.5736 - 157.1829 - 684.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER240': class PDFDictionary -274 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 385.0429 - 672.5736 - 403.3829 - 684.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER241': class PDFDictionary -275 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 350.6129 - 642.5736 - 371.7329 - 654.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER242': class PDFDictionary -276 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 256.6729 - 624.5736 - 277.7929 - 636.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER243': class PDFDictionary -277 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 149.5469 - 531.3736 - 172.1982 - 543.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER244': class PDFDictionary -278 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://docs.python.org/distutils/) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 224.3178 - 519.3736 - 260.8853 - 531.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page30': class PDFPage -279 0 obj -% Page dictionary -<< /Annots [ 271 0 R - 272 0 R - 273 0 R - 274 0 R - 275 0 R - 276 0 R - 277 0 R - 278 0 R ] - /Contents 380 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER245': class PDFDictionary -280 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://argparse.googlecode.com) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 381.1529 - 415.3736 - 423.3929 - 427.3736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page31': class PDFPage -281 0 obj -% Page dictionary -<< /Annots [ 280 0 R ] - /Contents 381 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER246': class PDFDictionary -282 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 62.69291 - 693.7736 - 84.72012 - 705.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page32': class PDFPage -283 0 obj -% Page dictionary -<< /Annots [ 282 0 R ] - /Contents 382 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Page33': class PDFPage -284 0 obj -% Page dictionary -<< /Contents 383 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER247': class PDFDictionary -285 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 182.479 - 693.7736 - 203.7662 - 705.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page34': class PDFPage -286 0 obj -% Page dictionary -<< /Annots [ 285 0 R ] - /Contents 384 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER248': class PDFDictionary -287 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 255.1228 - 723.7736 - 276.5028 - 735.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER249': class PDFDictionary -288 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://docs.python.org/library/multiprocessing.html) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 295.0229 - 568.5736 - 367.2629 - 580.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER250': class PDFDictionary -289 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 179.1295 - 520.5736 - 201.6839 - 532.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page35': class PDFPage -290 0 obj -% Page dictionary -<< /Annots [ 287 0 R - 288 0 R - 289 0 R ] - /Contents 385 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER251': class PDFDictionary -291 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 414.8874 - 684.5736 - 436.9487 - 696.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER252': class PDFDictionary -292 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 122.7054 - 540.5736 - 145.2085 - 552.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page36': class PDFPage -293 0 obj -% Page dictionary -<< /Annots [ 291 0 R - 292 0 R ] - /Contents 386 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Page37': class PDFPage -294 0 obj -% Page dictionary -<< /Contents 387 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER253': class PDFDictionary -295 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 183.3657 - 717.7736 - 207.8364 - 729.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER254': class PDFDictionary -296 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://docs.python.org/library/multiprocessing.html) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 254.9929 - 693.7736 - 327.2329 - 705.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER255': class PDFDictionary -297 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 62.69291 - 262.5736 - 85.70846 - 274.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page38': class PDFPage -298 0 obj -% Page dictionary -<< /Annots [ 295 0 R - 296 0 R - 297 0 R ] - /Contents 388 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER256': class PDFDictionary -299 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 176.9371 - 392.5736 - 198.5507 - 404.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page39': class PDFPage -300 0 obj -% Page dictionary -<< /Annots [ 299 0 R ] - /Contents 389 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER257': class PDFDictionary -301 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 62.69291 - 681.7736 - 84.98766 - 693.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page40': class PDFPage -302 0 obj -% Page dictionary -<< /Annots [ 301 0 R ] - /Contents 390 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Page41': class PDFPage -303 0 obj -% Page dictionary -<< /Contents 391 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER258': class PDFDictionary -304 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 473.5049 - 678.5736 - 494.7927 - 690.5736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER259': class PDFDictionary -305 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 172.4311 - 433.9659 - 194.7087 - 445.9659 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page42': class PDFPage -306 0 obj -% Page dictionary -<< /Annots [ 304 0 R - 305 0 R ] - /Contents 392 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Annot.NUMBER260': class PDFDictionary -307 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 91.57623 - 729.7736 - 114.8995 - 741.7736 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Annot.NUMBER261': class PDFDictionary -308 0 obj -<< /A << /S /URI - /Type /Action - /URI (http://pypi.python.org/pypi/plac) >> - /Border [ 0 - 0 - 0 ] - /Rect [ 106.6216 - 419.4272 - 128.3202 - 431.4272 ] - /Subtype /Link - /Type /Annot >> -endobj -% 'Page43': class PDFPage -309 0 obj -% Page dictionary -<< /Annots [ 307 0 R - 308 0 R ] - /Contents 393 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'Page44': class PDFPage -310 0 obj -% Page dictionary -<< /Contents 394 0 R - /MediaBox [ 0 - 0 - 595.2756 - 841.8898 ] - /Parent 350 0 R - /Resources << /Font 1 0 R - /ProcSet [ /PDF - /Text - /ImageB - /ImageC - /ImageI ] >> - /Rotate 0 - /Trans << >> - /Type /Page >> -endobj -% 'R311': class PDFCatalog -311 0 obj -% Document Root -<< /Outlines 313 0 R - /PageLabels 395 0 R - /PageMode /UseNone - /Pages 350 0 R - /Type /Catalog >> -endobj -% 'R312': class PDFInfo -312 0 obj -<< /Author () - /CreationDate (D:20110605055423-01'00') - /Creator (\(unspecified\)) - /Keywords () - /Producer (ReportLab PDF Library - www.reportlab.com) - /Subject (\(unspecified\)) - /Title () >> -endobj -% 'R313': class PDFOutlines -313 0 obj -<< /Count 38 - /First 314 0 R - /Last 329 0 R - /Type /Outlines >> -endobj -% 'Outline.0': class OutlineEntryObject -314 0 obj -<< /Count 14 - /Dest [ 8 0 R - /XYZ - 62.69291 - 765.0236 - 0 ] - /First 315 0 R - /Last 328 0 R - /Next 329 0 R - /Parent 313 0 R - /Title (Plac: Parsing the Command Line the Easy Way) >> -endobj -% 'Outline.37.0': class OutlineEntryObject -315 0 obj -<< /Dest [ 97 0 R - /XYZ - 62.69291 - 765.0236 - 0 ] - /Next 316 0 R - /Parent 314 0 R - /Title (The importance of scaling down) >> -endobj -% 'Outline.37.1': class OutlineEntryObject -316 0 obj -<< /Dest [ 97 0 R - /XYZ - 62.69291 - 411.0236 - 0 ] - /Next 317 0 R - /Parent 314 0 R - /Prev 315 0 R - /Title (Scripts with required arguments) >> -endobj -% 'Outline.37.2': class OutlineEntryObject -317 0 obj -<< /Dest [ 112 0 R - /XYZ - 62.69291 - 765.0236 - 0 ] - /Next 318 0 R - /Parent 314 0 R - /Prev 316 0 R - /Title (Scripts with default arguments) >> -endobj -% 'Outline.37.3': class OutlineEntryObject -318 0 obj -<< /Dest [ 122 0 R - /XYZ - 62.69291 - 707.8236 - 0 ] - /Next 319 0 R - /Parent 314 0 R - /Prev 317 0 R - /Title (Scripts with options \(and smart options\)) >> -endobj -% 'Outline.37.4': class OutlineEntryObject -319 0 obj -<< /Dest [ 126 0 R - /XYZ - 62.69291 - 715.8236 - 0 ] - /Next 320 0 R - /Parent 314 0 R - /Prev 318 0 R - /Title (Scripts with flags) >> -endobj -% 'Outline.37.5': class OutlineEntryObject -320 0 obj -<< /Dest [ 126 0 R - /XYZ - 62.69291 - 216.5299 - 0 ] - /Next 321 0 R - /Parent 314 0 R - /Prev 319 0 R - /Title (plac for Python 2.X users) >> -endobj -% 'Outline.37.6': class OutlineEntryObject -321 0 obj -<< /Dest [ 134 0 R - /XYZ - 62.69291 - 457.4236 - 0 ] - /Next 322 0 R - /Parent 314 0 R - /Prev 320 0 R - /Title (More features) >> -endobj -% 'Outline.37.7': class OutlineEntryObject -322 0 obj -<< /Dest [ 139 0 R - /XYZ - 62.69291 - 478.6236 - 0 ] - /Next 323 0 R - /Parent 314 0 R - /Prev 321 0 R - /Title (A realistic example) >> -endobj -% 'Outline.37.8': class OutlineEntryObject -323 0 obj -<< /Dest [ 142 0 R - /XYZ - 62.69291 - 420.6236 - 0 ] - /Next 324 0 R - /Parent 314 0 R - /Prev 322 0 R - /Title (Keyword arguments) >> -endobj -% 'Outline.37.9': class OutlineEntryObject -324 0 obj -<< /Dest [ 144 0 R - /XYZ - 62.69291 - 409.4236 - 0 ] - /Next 325 0 R - /Parent 314 0 R - /Prev 323 0 R - /Title (Final example: a shelve interface) >> -endobj -% 'Outline.37.10': class OutlineEntryObject -325 0 obj -<< /Dest [ 159 0 R - /XYZ - 62.69291 - 357.4236 - 0 ] - /Next 326 0 R - /Parent 314 0 R - /Prev 324 0 R - /Title (plac vs argparse) >> -endobj -% 'Outline.37.11': class OutlineEntryObject -326 0 obj -<< /Dest [ 214 0 R - /XYZ - 62.69291 - 705.0236 - 0 ] - /Next 327 0 R - /Parent 314 0 R - /Prev 325 0 R - /Title (plac vs the rest of the world) >> -endobj -% 'Outline.37.12': class OutlineEntryObject -327 0 obj -<< /Dest [ 214 0 R - /XYZ - 62.69291 - 411.0236 - 0 ] - /Next 328 0 R - /Parent 314 0 R - /Prev 326 0 R - /Title (The future) >> -endobj -% 'Outline.37.13': class OutlineEntryObject -328 0 obj -<< /Dest [ 214 0 R - /XYZ - 62.69291 - 207.0236 - 0 ] - /Parent 314 0 R - /Prev 327 0 R - /Title (Trivia: the story behind the name) >> -endobj -% 'Outline.1': class OutlineEntryObject -329 0 obj -<< /Count 20 - /Dest [ 231 0 R - /XYZ - 62.69291 - 585.0236 - 0 ] - /First 330 0 R - /Last 349 0 R - /Parent 313 0 R - /Prev 314 0 R - /Title (Advanced usages of plac) >> -endobj -% 'Outline.38.0': class OutlineEntryObject -330 0 obj -<< /Dest [ 231 0 R - /XYZ - 62.69291 - 552.0236 - 0 ] - /Next 331 0 R - /Parent 329 0 R - /Title (Introduction) >> -endobj -% 'Outline.38.1': class OutlineEntryObject -331 0 obj -<< /Dest [ 231 0 R - /XYZ - 62.69291 - 330.0236 - 0 ] - /Next 332 0 R - /Parent 329 0 R - /Prev 330 0 R - /Title (From scripts to interactive applications) >> -endobj -% 'Outline.38.2': class OutlineEntryObject -332 0 obj -<< /Dest [ 234 0 R - /XYZ - 62.69291 - 533.8236 - 0 ] - /Next 333 0 R - /Parent 329 0 R - /Prev 331 0 R - /Title (Testing a plac application) >> -endobj -% 'Outline.38.3': class OutlineEntryObject -333 0 obj -<< /Dest [ 237 0 R - /XYZ - 62.69291 - 497.8236 - 0 ] - /Next 334 0 R - /Parent 329 0 R - /Prev 332 0 R - /Title (Plac easy tests) >> -endobj -% 'Outline.38.4': class OutlineEntryObject -334 0 obj -<< /Dest [ 247 0 R - /XYZ - 62.69291 - 171.0642 - 0 ] - /Next 335 0 R - /Parent 329 0 R - /Prev 333 0 R - /Title (Plac batch scripts) >> -endobj -% 'Outline.38.5': class OutlineEntryObject -335 0 obj -<< /Dest [ 253 0 R - /XYZ - 62.69291 - 256.6236 - 0 ] - /Next 336 0 R - /Parent 329 0 R - /Prev 334 0 R - /Title (Implementing subcommands) >> -endobj -% 'Outline.38.6': class OutlineEntryObject -336 0 obj -<< /Dest [ 260 0 R - /XYZ - 62.69291 - 765.0236 - 0 ] - /Next 337 0 R - /Parent 329 0 R - /Prev 335 0 R - /Title (plac.Interpreter.call) >> -endobj -% 'Outline.38.7': class OutlineEntryObject -337 0 obj -<< /Dest [ 262 0 R - /XYZ - 62.69291 - 468.6236 - 0 ] - /Next 338 0 R - /Parent 329 0 R - /Prev 336 0 R - /Title (Readline support) >> -endobj -% 'Outline.38.8': class OutlineEntryObject -338 0 obj -<< /Dest [ 279 0 R - /XYZ - 62.69291 - 566.6236 - 0 ] - /Next 339 0 R - /Parent 329 0 R - /Prev 337 0 R - /Title (The plac runner) >> -endobj -% 'Outline.38.9': class OutlineEntryObject -339 0 obj -<< /Dest [ 283 0 R - /XYZ - 62.69291 - 729.0236 - 0 ] - /Next 340 0 R - /Parent 329 0 R - /Prev 338 0 R - /Title (A non class-based example) >> -endobj -% 'Outline.38.10': class OutlineEntryObject -340 0 obj -<< /Dest [ 286 0 R - /XYZ - 62.69291 - 729.0236 - 0 ] - /Next 341 0 R - /Parent 329 0 R - /Prev 339 0 R - /Title (Writing your own plac runner) >> -endobj -% 'Outline.38.11': class OutlineEntryObject -341 0 obj -<< /Dest [ 290 0 R - /XYZ - 62.69291 - 555.8236 - 0 ] - /Next 342 0 R - /Parent 329 0 R - /Prev 340 0 R - /Title (Long running commands) >> -endobj -% 'Outline.38.12': class OutlineEntryObject -342 0 obj -<< /Dest [ 293 0 R - /XYZ - 62.69291 - 659.8236 - 0 ] - /Next 343 0 R - /Parent 329 0 R - /Prev 341 0 R - /Title (Threaded commands) >> -endobj -% 'Outline.38.13': class OutlineEntryObject -343 0 obj -<< /Dest [ 298 0 R - /XYZ - 62.69291 - 765.0236 - 0 ] - /Next 344 0 R - /Parent 329 0 R - /Prev 342 0 R - /Title (Running commands as external processes) >> -endobj -% 'Outline.38.14': class OutlineEntryObject -344 0 obj -<< /Dest [ 298 0 R - /XYZ - 62.69291 - 297.8236 - 0 ] - /Next 345 0 R - /Parent 329 0 R - /Prev 343 0 R - /Title (Managing the output of concurrent commands) >> -endobj -% 'Outline.38.15': class OutlineEntryObject -345 0 obj -<< /Dest [ 300 0 R - /XYZ - 62.69291 - 427.8236 - 0 ] - /Next 346 0 R - /Parent 329 0 R - /Prev 344 0 R - /Title (Monitor support) >> -endobj -% 'Outline.38.16': class OutlineEntryObject -346 0 obj -<< /Dest [ 302 0 R - /XYZ - 62.69291 - 717.0236 - 0 ] - /Next 347 0 R - /Parent 329 0 R - /Prev 345 0 R - /Title (Parallel computing with plac) >> -endobj -% 'Outline.38.17': class OutlineEntryObject -347 0 obj -<< /Dest [ 306 0 R - /XYZ - 62.69291 - 553.2159 - 0 ] - /Next 348 0 R - /Parent 329 0 R - /Prev 346 0 R - /Title (The plac server) >> -endobj -% 'Outline.38.18': class OutlineEntryObject -348 0 obj -<< /Dest [ 309 0 R - /XYZ - 62.69291 - 765.0236 - 0 ] - /Next 349 0 R - /Parent 329 0 R - /Prev 347 0 R - /Title (Summary) >> -endobj -% 'Outline.38.19': class OutlineEntryObject -349 0 obj -<< /Dest [ 309 0 R - /XYZ - 62.69291 - 454.6772 - 0 ] - /Parent 329 0 R - /Prev 348 0 R - /Title (Appendix: custom annotation objects) >> -endobj -% 'R350': class PDFPages -350 0 obj -% page tree -<< /Count 44 - /Kids [ 8 0 R - 81 0 R - 97 0 R - 107 0 R - 112 0 R - 116 0 R - 122 0 R - 123 0 R - 126 0 R - 134 0 R - 135 0 R - 139 0 R - 142 0 R - 144 0 R - 147 0 R - 159 0 R - 174 0 R - 214 0 R - 231 0 R - 232 0 R - 234 0 R - 237 0 R - 247 0 R - 253 0 R - 255 0 R - 259 0 R - 260 0 R - 262 0 R - 270 0 R - 279 0 R - 281 0 R - 283 0 R - 284 0 R - 286 0 R - 290 0 R - 293 0 R - 294 0 R - 298 0 R - 300 0 R - 302 0 R - 303 0 R - 306 0 R - 309 0 R - 310 0 R ] - /Type /Pages >> -endobj -% 'R351': class PDFStream -351 0 obj -% page stream -<< /Length 2933 >> -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 3.5 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Plac: Parsing the Command Line the Easy Way) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 732.0236 cm -Q -q -1 0 0 1 62.69291 717.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 36.93937 0 Td (Author:) Tj T* -36.93937 0 Td ET -Q -Q -q -1 0 0 1 91.03937 3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Michele Simionato) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 702.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 39.69937 0 Td (E-mail:) Tj T* -39.69937 0 Td ET -Q -Q -q -1 0 0 1 91.03937 3 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (michele.simionato@gmail.com) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 687.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 48.03937 0 Td (Date:) Tj T* -48.03937 0 Td ET -Q -Q -q -1 0 0 1 91.03937 3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (June 2011) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 660.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 3 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F2 10 Tf 12 TL 25.25937 0 Td (Download) Tj T* 21.11 0 Td (page:) Tj T* -46.36937 0 Td ET -Q -Q -q -1 0 0 1 91.03937 15 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (http://pypi.python.org/pypi/plac) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 645.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 9.68937 0 Td (Project page:) Tj T* -9.68937 0 Td ET -Q -Q -q -1 0 0 1 91.03937 3 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (http://plac.googlecode.com/hg/doc/plac.html) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 630.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 26.91937 0 Td (Requires:) Tj T* -26.91937 0 Td ET -Q -Q -q -1 0 0 1 91.03937 3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Python 2.3+) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 615.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 16.91937 0 Td (Installation:) Tj T* -16.91937 0 Td ET -Q -Q -q -1 0 0 1 91.03937 3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F3 10 Tf 12 TL (easy_install -U plac) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 600.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 32.46937 0 Td (License:) Tj T* -32.46937 0 Td ET -Q -Q -q -1 0 0 1 91.03937 3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (BSD license) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 238.1649 0 Td (1) Tj T* -238.1649 0 Td ET -Q -Q - -endstream -endobj -% 'R352': class PDFStream -352 0 obj -% page stream -<< /Length 9629 >> -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 3.5 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Contents) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 90.02362 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 0 633 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (Plac: Parsing the Command Line the Easy Way) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 633 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 66.44 0 Td (1) Tj T* -66.44 0 Td ET -Q -Q -q -1 0 0 1 0 615 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (The importance of scaling down) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 615 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 66.44 0 Td (3) Tj T* -66.44 0 Td ET -Q -Q -q -1 0 0 1 0 597 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Scripts with required arguments) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 597 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 66.44 0 Td (3) Tj T* -66.44 0 Td ET -Q -Q -q -1 0 0 1 0 579 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Scripts with default arguments) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 579 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 66.44 0 Td (5) Tj T* -66.44 0 Td ET -Q -Q -q -1 0 0 1 0 561 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Scripts with options \(and smart options\)) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 561 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 66.44 0 Td (7) Tj T* -66.44 0 Td ET -Q -Q -q -1 0 0 1 0 543 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Scripts with flags) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 543 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 66.44 0 Td (9) Tj T* -66.44 0 Td ET -Q -Q -q -1 0 0 1 0 525 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (plac for Python 2.X users) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 525 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 66.44 0 Td (9) Tj T* -66.44 0 Td ET -Q -Q -q -1 0 0 1 0 507 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (More features) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 507 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (10) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 489 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (A realistic example) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 489 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (12) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 471 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Keyword arguments) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 471 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (13) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 453 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Final example: a shelve interface) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 453 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (14) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 435 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (plac vs argparse) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 435 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (16) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 417 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (plac vs the rest of the world) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 417 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (18) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 399 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (The future) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 399 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (18) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 381 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Trivia: the story behind the name) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 381 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (18) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 363 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (Advanced usages of plac) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 363 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F2 10 Tf 12 TL 60.88 0 Td (19) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 345 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Introduction) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 345 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (19) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 327 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (From scripts to interactive applications) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 327 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (19) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 309 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Testing a plac application) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 309 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (21) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 291 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Plac easy tests) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 291 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (22) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 273 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Plac batch scripts) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 273 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (23) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 255 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Implementing subcommands) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 255 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (24) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 237 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (plac.Interpreter.call) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 237 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (27) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 219 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Readline support) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 219 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (28) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 201 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (The plac runner) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 201 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (30) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 183 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (A non class-based example) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 183 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (32) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 165 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Writing your own plac runner) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 165 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (34) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 147 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Long running commands) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 147 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (35) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 129 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Threaded commands) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 129 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (36) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 111 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Running commands as external processes) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 111 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (38) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 93 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Managing the output of concurrent commands) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 93 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (38) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 75 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Monitor support) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 75 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (39) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 57 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Parallel computing with plac) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 57 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (40) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 39 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (The plac server) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 39 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (42) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 21 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Summary) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 21 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (43) Tj T* -60.88 0 Td ET -Q -Q -q -1 0 0 1 0 3 cm -q -BT 1 0 0 1 20 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (Appendix: custom annotation objects) Tj T* ET -Q -Q -q -1 0 0 1 397.8898 3 cm -q -0 0 .501961 rg -0 0 .501961 RG -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 60.88 0 Td (43) Tj T* -60.88 0 Td ET -Q -Q -q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 238.1649 0 Td (2) Tj T* -238.1649 0 Td ET -Q -Q - -endstream -endobj -% 'R353': class PDFStream -353 0 obj -% page stream -<< /Length 5956 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 747.0236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (The importance of scaling down) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 681.0236 cm -q -BT 1 0 0 1 0 50 Tm 1.50936 Tw 12 TL /F1 10 Tf 0 0 0 rg (There is no want of command line arguments parsers in the Python world. The standard library alone) Tj T* 0 Tw 1.087126 Tw (contains three different modules: ) Tj 0 0 .501961 rg (getopt ) Tj 0 0 0 rg (\(from the stone age\), ) Tj 0 0 .501961 rg (optparse ) Tj 0 0 0 rg (\(from Python 2.3\) and ) Tj 0 0 .501961 rg (argparse) Tj T* 0 Tw .223735 Tw 0 0 0 rg (\(from Python 2.7\). All of them are quite powerful and especially ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (is an industrial strength solution;) Tj T* 0 Tw 1.40311 Tw (unfortunately, all of them feature a non-zero learning curve and a certain verbosity. They do not scale) Tj T* 0 Tw (down well, at least in my opinion.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 603.0236 cm -q -BT 1 0 0 1 0 62 Tm 2.20186 Tw 12 TL /F1 10 Tf 0 0 0 rg (It should not be necessary to stress the importance ) Tj 0 0 .501961 rg (scaling down) Tj 0 0 0 rg (; nevertheless, a lot of people are) Tj T* 0 Tw .968555 Tw (obsessed with features and concerned with the possibility of scaling up, forgetting the equally important) Tj T* 0 Tw .048221 Tw (issue of scaling down. This is an old meme in the computing world: programs should address the common) Tj T* 0 Tw .36311 Tw (cases simply and simple things should be kept simple, while at the same keeping difficult things possible.) Tj T* 0 Tw 1.09332 Tw 0 0 .501961 rg (plac ) Tj 0 0 0 rg (adhere as much as possible to this philosophy and it is designed to handle well the simple cases,) Tj T* 0 Tw (while retaining the ability to handle complex cases by relying on the underlying power of ) Tj 0 0 .501961 rg (argparse) Tj 0 0 0 rg (.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 525.0236 cm -q -BT 1 0 0 1 0 62 Tm 1.488221 Tw 12 TL /F1 10 Tf 0 0 0 rg (Technically ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is just a simple wrapper over ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (which hides most of its complexity by using a) Tj T* 0 Tw .203318 Tw (declarative interface: the argument parser is inferred rather than written down by imperatively. Still, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is) Tj T* 0 Tw .125984 Tw (surprisingly scalable upwards, even without using the underlying ) Tj 0 0 .501961 rg (argparse) Tj 0 0 0 rg (. I have been using Python for 8) Tj T* 0 Tw 1.618876 Tw (years and in my experience it is extremely unlikely that you will ever need to go beyond the features) Tj T* 0 Tw 1.776457 Tw (provided by the declarative interface of ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (: they should be more than enough for 99.9% of the use) Tj T* 0 Tw (cases.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 423.0236 cm -q -BT 1 0 0 1 0 86 Tm 1.540888 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is targetting especially unsophisticated users, programmers, sys-admins, scientists and in general) Tj T* 0 Tw .81284 Tw (people writing throw-away scripts for themselves, choosing the command line interface because it is the) Tj T* 0 Tw .471751 Tw (quick and simple. Such users are not interested in features, they are interested in a small learning curve:) Tj T* 0 Tw .984988 Tw (they just want to be able to write a simple command line tool from a simple specification, not to build a) Tj T* 0 Tw 1.127318 Tw (command-line parser by hand. Unfortunately, the modules in the standard library forces them to go the) Tj T* 0 Tw .014104 Tw (hard way. They are designed to implement power user tools and they have a non-trivial learning curve. On) Tj T* 0 Tw 1.584104 Tw (the contrary, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is designed to be simple to use and extremely concise, as the examples below will) Tj T* 0 Tw (show.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 393.0236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Scripts with required arguments) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 327.0236 cm -q -BT 1 0 0 1 0 50 Tm .352209 Tw 12 TL /F1 10 Tf 0 0 0 rg (Let me start with the simplest possible thing: a script that takes a single argument and does something to) Tj T* 0 Tw 1.053984 Tw (it. It cannot get simpler than that, unless you consider a script without command-line arguments, where) Tj T* 0 Tw .735488 Tw (there is nothing to parse. Still, it is a use case ) Tj /F4 10 Tf (extremely common) Tj /F1 10 Tf (: I need to write scripts like that nearly) Tj T* 0 Tw .486655 Tw (every day, I wrote hundreds of them in the last few years and I have never been happy. Here is a typical) Tj T* 0 Tw (example of code I have been writing by hand for years:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 125.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 192 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 170 Tm /F3 10 Tf 12 TL (# example1.py) Tj T* (def main\(dsn\):) Tj T* ( "Do something with the database") Tj T* ( print\(dsn\)) Tj T* ( # ...) 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 -1 0 0 1 62.69291 93.82362 cm -q -BT 1 0 0 1 0 14 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.972927 Tw (should not exist. Actually I think the language should recognize the main function and pass to it the) Tj T* 0 Tw ET -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 238.1649 0 Td (3) Tj T* -238.1649 0 Td ET -Q -Q - -endstream -endobj -% 'R354': class PDFStream -354 0 obj -% page stream -<< /Length 4163 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 693.0236 cm -q -BT 1 0 0 1 0 62 Tm 3.309147 Tw 12 TL /F1 10 Tf 0 0 0 rg (command-line arguments automatically; 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 .69881 Tw (does not help for this incredibly common use case. Using ) Tj 0 0 .501961 rg (getopt ) Tj 0 0 0 rg (and ) Tj 0 0 .501961 rg (optparse ) Tj 0 0 0 rg (does not help, since they) Tj T* 0 Tw .894104 Tw (are intended to manage options and not positional arguments; the ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (module helps a bit and it is) Tj T* 0 Tw (able to reduce the boilerplate from nine lines to six lines:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 527.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 156 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 134 Tm /F3 10 Tf 12 TL (# example2.py) Tj T* (def main\(dsn\):) Tj T* ( "Do something on the database") Tj T* ( print\(dsn\)) Tj T* ( # ...) 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 -1 0 0 1 62.69291 495.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .817488 Tw (However, it just feels too complex to instantiate a class and to define a parser by hand for such a trivial) Tj T* 0 Tw (task.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 465.8236 cm -q -BT 1 0 0 1 0 14 Tm 1.123145 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj 0 0 .501961 rg (plac ) 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 (plac ) Tj 0 0 0 rg (module all you need to write is) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 348.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 108 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 86 Tm /F3 10 Tf 12 TL (# example3.py) Tj T* (def main\(dsn\):) Tj T* ( "Do something with the database") Tj T* ( print\(dsn\)) Tj T* ( # ...) Tj T* ( ) Tj T* (if __name__ == '__main__':) Tj T* ( import plac; plac.call\(main\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 316.6236 cm -q -BT 1 0 0 1 0 14 Tm .929986 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj 0 0 .501961 rg (plac ) 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 283.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 24 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F3 10 Tf 12 TL ($ python example3.py -h) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 154.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 120 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 98 Tm /F3 10 Tf 12 TL (usage: example3.py [-h] dsn) Tj T* T* (Do something with the database) 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 122.2236 cm -q -BT 1 0 0 1 0 14 Tm .167765 Tw 12 TL /F1 10 Tf 0 0 0 rg (Moreover ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (manages the case of missing arguments and of too many arguments. This is only the tip of) Tj T* 0 Tw (the iceberg: ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is able to do much more than that.) Tj T* ET -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 238.1649 0 Td (4) Tj T* -238.1649 0 Td ET -Q -Q - -endstream -endobj -% 'R355': class PDFStream -355 0 obj -% page stream -<< /Length 4211 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 747.0236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Scripts with default arguments) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 717.0236 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL 2.609984 Tw (The need to have suitable defaults for command-line scripts is quite common. For instance I have) Tj T* 0 Tw (encountered this use case at work hundreds of times:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 515.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 192 re B* -Q -q -BT 1 0 0 1 0 170 Tm 12 TL /F3 10 Tf 0 0 0 rg (# example4.py) Tj T* (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* ( 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 -1 0 0 1 62.69291 447.8236 cm -q -BT 1 0 0 1 0 50 Tm .038488 Tw 12 TL /F1 10 Tf 0 0 0 rg (Here I want to perform a query on a database table, by extracting the most recent data: it makes sense for) Tj T* 0 Tw .299988 Tw /F3 10 Tf (today ) Tj /F1 10 Tf (to be a default argument. If there is a most used table \(in this example a table called ) Tj /F3 10 Tf ('product') Tj /F1 10 Tf (\)) Tj T* 0 Tw 3.313984 Tw (it also makes sense to make it a default argument. Performing the parsing of the command-line) Tj T* 0 Tw .083735 Tw (arguments by hand takes 8 ugly lines of boilerplate \(using ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (would require about the same number) Tj T* 0 Tw (of lines\). With ) Tj 0 0 .501961 rg (plac ) 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 402.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 14 Tm /F3 10 Tf 12 TL (if __name__ == '__main__':) Tj T* ( import plac; plac.call\(main\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 382.6236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (In other words, six lines of boilerplate have been removed, and we get the usage message for free:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 229.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 144 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 122 Tm /F3 10 Tf 12 TL (usage: example5.py [-h] dsn [table] [today]) Tj T* T* (Do something on the database) Tj T* T* (positional arguments:) Tj T* ( dsn) Tj T* ( table [product]) Tj T* ( today [YYYY-MM-DD]) 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 185.4236 cm -q -BT 1 0 0 1 0 26 Tm .81311 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that by default ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (prints the string representation of the default values \(with square brackets\) in) Tj T* 0 Tw .117485 Tw (the usage message. ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (manages transparently even the case when you want to pass a variable number) Tj T* 0 Tw (of arguments. Here is an example, a script running on a database a series of SQL scripts:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 92.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 84 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 62 Tm /F3 10 Tf 12 TL (# example7.py) Tj T* (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* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 238.1649 0 Td (5) Tj T* -238.1649 0 Td ET -Q -Q - -endstream -endobj -% 'R356': class PDFStream -356 0 obj -% page stream -<< /Length 3873 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 691.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 50 Tm /F3 10 Tf 12 TL ( print\('executing %s' % script\)) Tj T* ( # ...) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac; plac.call\(main\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 671.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is the usage message:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 530.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 110 Tm /F3 10 Tf 12 TL (usage: example7.py [-h] dsn [scripts [scripts ...]]) Tj T* T* (Run the given scripts on the database) 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 486.6236 cm -q -BT 1 0 0 1 0 26 Tm .952485 Tw 12 TL /F1 10 Tf 0 0 0 rg (The examples here should have made clear that ) Tj /F4 10 Tf (plac is able to figure out the command-line arguments) Tj T* 0 Tw .899988 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 (plac) Tj 0 0 0 rg (: if the intent is) Tj T* 0 Tw (clear, let's the machine take care of the details.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 444.6236 cm -q -BT 1 0 0 1 0 26 Tm .722765 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is inspired to an old Python Cookbook recipe of mine \() Tj 0 0 .501961 rg (optionparse) Tj 0 0 0 rg (\), in the sense that it delivers the) Tj T* 0 Tw .847209 Tw (programmer from the burden of writing the parser, but is less of a hack: instead of extracting the parser) Tj T* 0 Tw (from the docstring of the module, it 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 414.6236 cm -q -BT 1 0 0 1 0 14 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 261.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 144 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 122 Tm /F3 10 Tf 12 TL (# example7_.py) Tj T* (from datetime import datetime) Tj T* T* (def main\(dsn: "Database dsn", *scripts: "SQL scripts"\):) Tj T* ( "Run the given scripts on the database") Tj T* ( for script in scripts:) Tj T* ( print\('executing %s' % script\)) Tj T* ( # ...) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac; plac.call\(main\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 229.4236 cm -q -BT 1 0 0 1 0 14 Tm .17528 Tw 12 TL /F1 10 Tf 0 0 0 rg (Here the arguments of the ) Tj /F3 10 Tf (main ) Tj /F1 10 Tf (function have been annotated with strings which are intented to be used) Tj T* 0 Tw (in the help message:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 100.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 120 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 98 Tm /F3 10 Tf 12 TL (usage: example7_.py [-h] dsn [scripts [scripts ...]]) Tj T* T* (Run the given scripts on the database) Tj T* T* (positional arguments:) Tj T* ( dsn Database dsn) Tj T* ( scripts SQL scripts) Tj T* T* (optional arguments:) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 238.1649 0 Td (6) Tj T* -238.1649 0 Td ET -Q -Q - -endstream -endobj -% 'R357': class PDFStream -357 0 obj -% page stream -<< /Length 5179 >> -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 2 Tm /F3 10 Tf 12 TL ( -h, --help show this help message and exit) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 719.8236 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is able to recognize much more complex annotations, as I will show in the next paragraphs.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 689.8236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Scripts with options \(and smart options\)) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 599.8236 cm -q -BT 1 0 0 1 0 74 Tm .016457 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.02311 Tw (a hundred\), compared to the number of scripts with positional arguments I wrote \(certainly more than a) Tj T* 0 Tw .177045 Tw (thousand of them\). Still, this use case cannot be neglected. The standard library modules \(all of them\) are) Tj T* 0 Tw 2.30686 Tw (quite verbose when it comes to specifying the options and frankly I have never used them directly.) Tj T* 0 Tw 2.557126 Tw (Instead, I have always relied on the ) Tj 0 0 .501961 rg (optionparse ) Tj 0 0 0 rg (recipe, which provides a convenient wrapper over) Tj T* 0 Tw 1.09061 Tw 0 0 .501961 rg (optionparse) Tj 0 0 0 rg (. Alternatively, in the simplest cases, I have just performed the parsing by hand. In ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (the) Tj T* 0 Tw (parser is inferred by the function annotations. Here is an example:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 482.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 108 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 86 Tm /F3 10 Tf 12 TL (# example8.py) Tj T* (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 plac; plac.call\(main\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 426.6236 cm -q -BT 1 0 0 1 0 38 Tm .929213 Tw 12 TL /F1 10 Tf 0 0 0 rg (Here the argument ) Tj /F3 10 Tf (command ) Tj /F1 10 Tf (has been annotated with the tuple ) Tj /F3 10 Tf (\("SQL query", 'option', 'c'\)) Tj /F1 10 Tf (:) Tj T* 0 Tw .62683 Tw (the first string is the help string which will appear in the usage message, the second string tells ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (that) Tj T* 0 Tw .931894 Tw /F3 10 Tf (command ) Tj /F1 10 Tf (is an option and the third string that there is also a short form of the option ) Tj /F3 10 Tf (-c) Tj /F1 10 Tf (, the long form) Tj T* 0 Tw (being ) Tj /F3 10 Tf (--command) Tj /F1 10 Tf (. The usage message is the following:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 297.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 98 Tm /F3 10 Tf 12 TL (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 277.4236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here are two examples of usage:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 196.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 72 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 50 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 140.2236 cm -q -BT 1 0 0 1 0 38 Tm .268935 Tw 12 TL /F1 10 Tf 0 0 0 rg (The third argument in the function annotation can be omitted: in such case it will be assumed to be ) Tj /F3 10 Tf (None) Tj /F1 10 Tf (.) Tj T* 0 Tw 2.839213 Tw (The consequence is that the usual dichotomy between long and short options \(GNU-style options\)) Tj T* 0 Tw .396235 Tw (disappears: we get ) Tj /F4 10 Tf (smart options) Tj /F1 10 Tf (, which have the single character prefix of short options and behave like) Tj T* 0 Tw (both long and short options, since they can be abbreviated. Here is an example featuring smart options:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 95.02362 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 14 Tm /F3 10 Tf 12 TL (# example6.py) Tj T* (def main\(dsn, command: \("SQL query", 'option'\)\):) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 238.1649 0 Td (7) Tj T* -238.1649 0 Td ET -Q -Q - -endstream -endobj -% 'R358': class PDFStream -358 0 obj -% page stream -<< /Length 4058 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 703.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 60 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 38 Tm /F3 10 Tf 12 TL ( print\('executing %r on %s' % \(command, dsn\)\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac; plac.call\(main\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 586.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 108 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 86 Tm /F3 10 Tf 12 TL (usage: example6.py [-h] [-command 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* ( -command COMMAND SQL query) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 566.6236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (The following are all valid invocations ot the script:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 473.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 84 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 62 Tm /F3 10 Tf 12 TL ($ python3 example6.py -c "select" dsn) Tj T* (executing 'select' on dsn) Tj T* ($ python3 example6.py -com "select" dsn) Tj T* (executing 'select' on dsn) Tj T* ($ python3 example6.py -command="select" dsn) Tj T* (executing 'select' on dsn) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 453.4236 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (Notice that the form ) Tj /F3 10 Tf (-command=SQL ) Tj /F1 10 Tf (is recognized only for the full option, not for its abbreviations:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 396.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 26 Tm /F3 10 Tf 12 TL ($ python3 example6.py -com="select" dsn) Tj T* (usage: example6.py [-h] [-command COMMAND] dsn) Tj T* (example6.py: error: unrecognized arguments: -com=select) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 364.2236 cm -q -BT 1 0 0 1 0 14 Tm 1.724987 Tw 12 TL /F1 10 Tf 0 0 0 rg (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 (. However, it is possible to) Tj T* 0 Tw (specify a non-trivial default. Here is an example:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 271.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 84 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 62 Tm /F3 10 Tf 12 TL (# example8_.py) Tj T* (def main\(dsn, command: \("SQL query", 'option'\)='select * from table'\):) Tj T* ( print\('executing %r on %s' % \(command, dsn\)\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac; plac.call\(main\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 251.0236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Notice that the default value appears in the help message:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 121.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 98 Tm /F3 10 Tf 12 TL (usage: example8_.py [-h] [-command select * from table] 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* ( -command select * from table) Tj T* ( SQL query) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 101.8236 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (When you run the script and you do not pass the ) Tj /F3 10 Tf (-command ) Tj /F1 10 Tf (option, the default query will be executed:) Tj T* ET -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 238.1649 0 Td (8) Tj T* -238.1649 0 Td ET -Q -Q - -endstream -endobj -% 'R359': class PDFStream -359 0 obj -% page stream -<< /Length 4742 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 727.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 36 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL ($ python3 example8_.py dsn) Tj T* (executing 'select * from table' on dsn) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 697.8236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Scripts with flags) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 667.8236 cm -q -BT 1 0 0 1 0 14 Tm .815542 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is able to recognize flags, i.e. boolean options which are ) Tj /F3 10 Tf (True ) Tj /F1 10 Tf (if they are passed to the command) Tj T* 0 Tw (line 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 542.9299 cm -q -q -.96447 0 0 .96447 0 0 cm -q -1 0 0 1 6.6 6.843137 cm -q -.662745 .662745 .662745 RG -.5 w -.960784 .960784 .862745 rg -n -6 -6 486 120 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 98 Tm /F3 10 Tf 12 TL (# example9.py) Tj T* T* (def main\(verbose: \('prints more info', 'flag', 'v'\), dsn: 'connection string'\):) Tj T* ( if verbose:) Tj T* ( print\('connecting to %s' % dsn\)) Tj T* ( # ...) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac; plac.call\(main\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 425.7299 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 86 Tm /F3 10 Tf 12 TL (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 380.5299 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 14 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 336.5299 cm -q -BT 1 0 0 1 0 26 Tm .31408 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that it is an error trying to specify a default for flags: the default value for a flag is always ) Tj /F3 10 Tf (False) Tj /F1 10 Tf (. If) Tj T* 0 Tw 2.652485 Tw (you feel the need to implement non-boolean flags, you should use an option with two choices, as) Tj T* 0 Tw (explained in the "more features" section.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 270.5299 cm -q -BT 1 0 0 1 0 50 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.895433 Tw (Flag-Option-Required-Default \(FORD\) convention: in the ) Tj /F3 10 Tf (main ) Tj /F1 10 Tf (function write first the flag arguments,) Tj T* 0 Tw .881235 Tw (then the option arguments, then the required arguments and finally the default arguments. This is just a) Tj T* 0 Tw .110574 Tw (convention and you are not forced to use it, except for the default arguments \(including the varargs\) which) Tj T* 0 Tw (must stay at the end as it is required by the Python syntax.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 228.5299 cm -q -BT 1 0 0 1 0 26 Tm .897045 Tw 12 TL /F1 10 Tf 0 0 0 rg (I also suggests to specify a one-character abbreviation for flags: in this way you can use the GNU-style) Tj T* 0 Tw 2.034431 Tw (composition of flags \(i.e. ) Tj /F3 10 Tf (-zxvf ) Tj /F1 10 Tf (is an abbreviation of ) Tj /F3 10 Tf (-z -x -v -f) Tj /F1 10 Tf (\). I usually do not provide the) Tj T* 0 Tw (one-character abbreviation for options, since it does not make sense to compose them.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 198.5299 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (plac for Python 2.X users) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 132.5299 cm -q -BT 1 0 0 1 0 50 Tm .211807 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. It will take years) Tj T* 0 Tw .304724 Tw (before we think to migrate to Python 3. I am pretty much sure most Pythonistas are in the same situation.) Tj T* 0 Tw 1.459984 Tw (Therefore ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (provides a way to work with function annotations even in Python 2.X \(including Python) Tj T* 0 Tw 2.692339 Tw (2.3\). There is no magic involved; you just need to add the annotations by hand. For instance the) Tj T* 0 Tw (annotated function declaration) Tj T* ET -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 238.1649 0 Td (9) Tj T* -238.1649 0 Td ET -Q -Q - -endstream -endobj -% 'R360': class PDFStream -360 0 obj -% page stream -<< /Length 6254 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 727.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 36 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 14 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 707.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (is equivalent to the following code:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 626.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 50 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 582.6236 cm -q -BT 1 0 0 1 0 26 Tm .536098 Tw 12 TL /F1 10 Tf 0 0 0 rg (One should be careful to match the keys of the annotation 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 (plac.annotations ) Tj /F1 10 Tf (decorator that performs the check for you:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 501.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 50 Tm /F3 10 Tf 12 TL (@plac.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 469.4236 cm -q -BT 1 0 0 1 0 14 Tm 1.846077 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 X >) Tj (= 4 and I will use the) Tj T* 0 Tw /F3 10 Tf (plac.annotations ) Tj /F1 10 Tf (decorator. Notice however that the core features of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (run even on Python 2.3.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 439.4236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (More features) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 385.4236 cm -q -BT 1 0 0 1 0 38 Tm 1.483488 Tw 12 TL /F1 10 Tf 0 0 0 rg (One of the goals of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is to have a learning curve of ) Tj /F4 10 Tf (minutes ) Tj /F1 10 Tf (for its core features, compared to the) Tj T* 0 Tw 1.152093 Tw (learning curve of ) Tj /F4 10 Tf (hours ) Tj /F1 10 Tf (of ) Tj 0 0 .501961 rg (argparse) Tj 0 0 0 rg (. In order to reach this goal, I have ) Tj /F4 10 Tf (not ) Tj /F1 10 Tf (sacrificed all the features of) Tj T* 0 Tw 2.89936 Tw 0 0 .501961 rg (argparse) Tj 0 0 0 rg (. Actually a lot of ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (power persists in ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (. Until now, I have only showed simple) Tj T* 0 Tw (annotations, but in general an annotation is a 6-tuple of the form) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 379.4236 cm -Q -q -1 0 0 1 62.69291 367.4236 cm -0 0 0 rg -BT /F1 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 2 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 367.4236 cm -Q -q -1 0 0 1 62.69291 325.4236 cm -q -BT 1 0 0 1 0 26 Tm 1.068735 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 a string in the set { ) Tj /F3 10 Tf ("flag") Tj /F1 10 Tf (, ) Tj /F3 10 Tf ("option") Tj /F1 10 Tf (, ) Tj /F3 10 Tf ("positional") Tj /F1 10 Tf (},) Tj T* 0 Tw 1.579431 Tw /F3 10 Tf (abbrev ) Tj /F1 10 Tf (is a one-character string or ) Tj /F3 10 Tf (None) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (type ) Tj /F1 10 Tf (is a callable taking a string in input, ) Tj /F3 10 Tf (choices ) Tj /F1 10 Tf (is a) Tj T* 0 Tw (discrete sequence of values and ) Tj /F3 10 Tf (metavar ) Tj /F1 10 Tf (is a string.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 295.4236 cm -q -BT 1 0 0 1 0 14 Tm 1.05061 Tw 12 TL /F3 10 Tf 0 0 0 rg (type ) Tj /F1 10 Tf (is used to automagically convert the command line arguments from the string type to any Python) Tj T* 0 Tw (type; by default there is no conversion and ) Tj /F3 10 Tf (type=None) Tj /F1 10 Tf (.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 265.4236 cm -q -BT 1 0 0 1 0 14 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 187.4236 cm -q -BT 1 0 0 1 0 62 Tm 1.171163 Tw 12 TL /F3 10 Tf 0 0 0 rg (metavar ) Tj /F1 10 Tf (has two meanings. For a positional argument it is used to change the argument name in the) Tj T* 0 Tw .352209 Tw (usage message \(and only there\). By default the metavar is ) Tj /F3 10 Tf (None ) Tj /F1 10 Tf (and the name in the usage message is) Tj T* 0 Tw .752339 Tw (the same as the argument name. For an option the ) Tj /F3 10 Tf (metavar ) Tj /F1 10 Tf (is used differently in the usage message,) Tj T* 0 Tw .802927 Tw (which has now the form ) Tj /F3 10 Tf ([--option-name METAVAR]) Tj /F1 10 Tf (. If the ) Tj /F3 10 Tf (metavar ) Tj /F1 10 Tf (is ) Tj /F3 10 Tf (None) Tj /F1 10 Tf (, then it is equal to the) Tj T* 0 Tw .50683 Tw (uppercased name of the argument, unless the argument has a default and in such a case is equal to the) Tj T* 0 Tw (stringified form of the default.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 169.4236 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (Here is an example showing many of the features \(copied 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 100.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 60 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 38 Tm /F3 10 Tf 12 TL (# example10.py) Tj T* (import plac) Tj T* T* (@plac.annotations\() Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (10) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R361': class PDFStream -361 0 obj -% page stream -<< /Length 3696 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 564.6467 cm -q -q -.976496 0 0 .976496 0 0 cm -q -1 0 0 1 6.6 6.758862 cm -q -.662745 .662745 .662745 RG -.5 w -.960784 .960784 .862745 rg -n -6 -6 480 204 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 182 Tm /F3 10 Tf 12 TL (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* ( "A script to add and multiply numbers") Tj T* ( if operator == 'mul':) Tj T* ( op = float.__mul__) Tj T* ( result = 1.0) Tj T* ( else: # operator == 'add') Tj T* ( op = float.__add__) Tj T* ( result = 0.0) Tj T* ( for n in numbers:) Tj T* ( result = op\(result, n\)) Tj T* ( return result) Tj T* T* (if __name__ == '__main__':) Tj T* ( print\(plac.call\(main\)\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 544.6467 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is the usage:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 403.4467 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 110 Tm /F3 10 Tf 12 TL (usage: example10.py [-h] {add,mul} [n [n ...]]) Tj T* T* (A script to add and multiply numbers) 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 371.4467 cm -q -BT 1 0 0 1 0 14 Tm .15186 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that the docstring of the ) Tj /F3 10 Tf (main ) Tj /F1 10 Tf (function has been automatically added to the usage message. Here) Tj T* 0 Tw (are a couple of examples of use:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 278.108 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 74 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 258.108 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (plac.call ) Tj /F1 10 Tf (can also be used in doctests like this:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 200.908 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 -BT 1 0 0 1 0 26 Tm 12 TL /F3 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( import plac, example10) Tj T* (>) Tj (>) Tj (>) Tj ( plac.call\(example10.main, ['add', '1', '2']\)) Tj T* (3.0) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 180.908 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (plac.call ) Tj /F1 10 Tf (works for generators too:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 99.70797 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 -BT 1 0 0 1 0 50 Tm 12 TL /F3 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( def main\(n\):) Tj T* (... for i in range\(int\(n\)\):) Tj T* (... yield i) Tj T* (>) Tj (>) Tj (>) Tj ( plac.call\(main, ['3']\)) Tj T* ([0, 1, 2]) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (11) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R362': class PDFStream -362 0 obj -% page stream -<< /Length 4537 >> -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 26 Tm .158409 Tw 12 TL /F1 10 Tf 0 0 0 rg (Internally ) Tj /F3 10 Tf (plac.call ) Tj /F1 10 Tf (tries to convert the output of the main function into a list, if possible. If the output is) Tj T* 0 Tw .725703 Tw (not iterable or it is a string, it is left unchanged, but if it is iterable it is converted. In particular, generator) Tj T* 0 Tw (objects are exhausted by ) Tj /F3 10 Tf (plac.call) Tj /F1 10 Tf (.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 699.0236 cm -q -BT 1 0 0 1 0 14 Tm 1.450751 Tw 12 TL /F1 10 Tf 0 0 0 rg (This behavior avoids mistakes like forgetting of applying ) Tj /F3 10 Tf (list\(result\) ) Tj /F1 10 Tf (to the result of ) Tj /F3 10 Tf (plac.call) Tj /F1 10 Tf (;) Tj T* 0 Tw (moreover it makes errors visible early, and avoids mistakes in code like the following:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 629.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 60 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 38 Tm /F3 10 Tf 12 TL (try:) Tj T* ( result = plac.call\(main, args\)) Tj T* (except:) Tj T* ( # do something) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 597.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL 3.122126 Tw (Without the "listify" functionality, a main function returning a generator object would not raise any) Tj T* 0 Tw (exception until the generator is iterated over.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 567.8236 cm -q -BT 1 0 0 1 0 14 Tm .647262 Tw 12 TL /F1 10 Tf 0 0 0 rg (If you are a fan of lazyness, you can still have it by setting the ) Tj /F3 10 Tf (eager ) Tj /F1 10 Tf (flag to ) Tj /F3 10 Tf (False) Tj /F1 10 Tf (, as in the following) Tj T* 0 Tw (example:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 522.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 14 Tm /F3 10 Tf 12 TL (for line in plac.call\(main, args, eager=False\):) Tj T* ( print\(line\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 490.6236 cm -q -BT 1 0 0 1 0 14 Tm 1.35528 Tw 12 TL /F1 10 Tf 0 0 0 rg (If ) Tj /F3 10 Tf (main ) Tj /F1 10 Tf (returns a generator object this example will print each line as soon as available, whereas the) Tj T* 0 Tw (default behaviour is to print all the lines together and the end of the computation.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 460.6236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (A realistic example) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 418.6236 cm -q -BT 1 0 0 1 0 26 Tm 1.234488 Tw 12 TL /F1 10 Tf 0 0 0 rg (Here is a more realistic script using most of the features of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (to run SQL queries on a database by) Tj T* 0 Tw .930697 Tw (relying on ) Tj 0 0 .501961 rg (SQLAlchemy) Tj 0 0 0 rg (. Notice the usage of the ) Tj /F3 10 Tf (type ) Tj /F1 10 Tf (feature to automagically convert a SQLAlchemy) Tj T* 0 Tw (connection string into a ) Tj 0 0 .501961 rg (SqlSoup ) Tj 0 0 0 rg (object:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 97.42362 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 312 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 290 Tm /F3 10 Tf 12 TL (# dbcli.py) Tj T* (import plac) Tj T* (from sqlalchemy.ext.sqlsoup import SqlSoup) Tj T* T* (@plac.annotations\() Tj T* ( db=\("Connection string", 'positional', None, SqlSoup\),) Tj T* ( header=\("Header", 'flag', 'H'\),) Tj T* ( sqlcmd=\("SQL command", 'option', 'c', str, None, "SQL"\),) Tj T* ( delimiter=\("Column separator", 'option', 'd'\),) Tj T* ( scripts="SQL scripts",) Tj T* ( \)) Tj T* (def main\(db, header, sqlcmd, delimiter="|", *scripts\):) Tj T* ( "A script to run queries and SQL scripts on a database") Tj T* ( yield 'Working on %s' % db.bind.url) Tj T* T* ( if sqlcmd:) Tj T* ( result = db.bind.execute\(sqlcmd\)) Tj T* ( if header: # print the header) Tj T* ( yield delimiter.join\(result.keys\(\)\)) Tj T* ( for row in result: # print the rows) Tj T* ( yield delimiter.join\(map\(str, row\)\)) Tj T* T* ( for script in scripts:) Tj T* ( db.bind.execute\(file\(script\).read\(\)\)) Tj T* ( yield 'executed %s' % script) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (12) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R363': class PDFStream -363 0 obj -% page stream -<< /Length 3815 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 703.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 60 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 38 Tm /F3 10 Tf 12 TL T* (if __name__ == '__main__':) Tj T* ( for output in plac.call\(main\):) Tj T* ( print\(output\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 647.8236 cm -q -BT 1 0 0 1 0 38 Tm .049987 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can see the ) Tj /F4 10 Tf (yield-is-print ) Tj /F1 10 Tf (pattern here: instead of using ) Tj /F3 10 Tf (print ) Tj /F1 10 Tf (in the main function, I use ) Tj /F3 10 Tf (yield) Tj /F1 10 Tf (, and) Tj T* 0 Tw 3.55061 Tw (I perform the print in the ) Tj /F3 10 Tf (__main__ ) Tj /F1 10 Tf (block. The advantage of the pattern is that tests invoking) Tj T* 0 Tw .52936 Tw /F3 10 Tf (plac.call ) Tj /F1 10 Tf (and checking the result become trivial: had I performed the printing in the main function, the) Tj T* 0 Tw (test would have involved an ugly hack like redirecting ) Tj /F3 10 Tf (sys.stdout ) Tj /F1 10 Tf (to a ) Tj /F3 10 Tf (StringIO ) Tj /F1 10 Tf (object.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 629.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is the usage message:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 452.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 168 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 146 Tm /F3 10 Tf 12 TL (usage: dbcli.py [-h] [-H] [-c SQL] [-d |] db [scripts [scripts ...]]) Tj T* T* (A script to run queries and SQL scripts on a database) Tj T* T* (positional arguments:) Tj T* ( db Connection string) Tj T* ( scripts SQL scripts) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ( -H, --header Header) Tj T* ( -c SQL, --sqlcmd SQL SQL command) Tj T* ( -d |, --delimiter | Column separator) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 432.6236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (You can check for yourself that the script works.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 402.6236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Keyword arguments) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 360.6236 cm -q -BT 1 0 0 1 0 26 Tm 1.831984 Tw 12 TL /F1 10 Tf 0 0 0 rg (Starting from release 0.4, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (supports keyword arguments. In practice that means that if your main) Tj T* 0 Tw 2.099213 Tw (function has keyword arguments, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (treats specially arguments of the form ) Tj /F3 10 Tf ("name=value" ) Tj /F1 10 Tf (in the) Tj T* 0 Tw (command line. Here is an example:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 123.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 228 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 206 Tm /F3 10 Tf 12 TL (# example12.py) Tj T* (import plac) Tj T* T* (@plac.annotations\() Tj T* ( opt=\('some option', 'option'\),) Tj T* ( args='default arguments',) Tj T* ( kw='keyword arguments'\)) Tj T* (def main\(opt, *args, **kw\):) Tj T* ( if opt:) Tj T* ( yield 'opt=%s' % opt) Tj T* ( if args:) Tj T* ( yield 'args=%s' % str\(args\)) Tj T* ( if kw:) Tj T* ( yield 'kw=%s' % kw) Tj T* T* (if __name__ == '__main__':) Tj T* ( for output in plac.call\(main\):) Tj T* ( print\(output\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 103.4236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is the generated usage message:) Tj T* ET -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (13) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R364': class PDFStream -364 0 obj -% page stream -<< /Length 4073 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 643.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 98 Tm /F3 10 Tf 12 TL (usage: example12.py [-h] [-opt OPT] [args [args ...]] [kw [kw ...]]) Tj T* T* (positional arguments:) Tj T* ( args default arguments) Tj T* ( kw keyword arguments) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ( -opt OPT some option) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 623.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is how you call the script:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 554.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 60 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 38 Tm /F3 10 Tf 12 TL ($ python example12.py -o X a1 a2 name=value) Tj T* (opt=X) Tj T* (args=\('a1', 'a2'\)) Tj T* (kw={'name': 'value'}) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 522.6236 cm -q -BT 1 0 0 1 0 14 Tm 2.133735 Tw 12 TL /F1 10 Tf 0 0 0 rg (When using keyword arguments, one must be careful to use names which are not alreay taken; for) Tj T* 0 Tw (instance in this examples the name ) Tj /F3 10 Tf (opt ) Tj /F1 10 Tf (is taken:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 465.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 48 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 26 Tm /F3 10 Tf 12 TL ($ python example12.py 1 2 kw1=1 kw2=2 opt=0) Tj T* (usage: example12.py [-h] [-o OPT] [args [args ...]] [kw [kw ...]]) Tj T* (example12.py: error: colliding keyword arguments: opt) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 421.4236 cm -q -0 0 0 rg -BT 1 0 0 1 0 26 Tm /F1 10 Tf 12 TL 1.024104 Tw (The names taken are the names of the flags, of the options, and of the positional arguments, excepted) Tj T* 0 Tw .60561 Tw (varargs and keywords. This limitation is a consequence of the way the argument names are managed in) Tj T* 0 Tw (function calls by the Python language.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 391.4236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Final example: a shelve interface) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 349.4236 cm -q -BT 1 0 0 1 0 26 Tm .603516 Tw 12 TL /F1 10 Tf 0 0 0 rg (Here is a less trivial example for the keyword arguments feature. The use case is the following: suppose) Tj T* 0 Tw .82881 Tw (we have stored the configuration parameters of a given application into a Python shelve and we need a) Tj T* 0 Tw (command-line tool to edit the shelve. A possible implementation using ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (could be the following:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 105.8928 cm -q -q -.976496 0 0 .976496 0 0 cm -q -1 0 0 1 6.6 6.758862 cm -q -.662745 .662745 .662745 RG -.5 w -.960784 .960784 .862745 rg -n -6 -6 480 240 re B* -Q -q -BT 1 0 0 1 0 218 Tm 12 TL /F3 10 Tf 0 0 0 rg (# ishelve.py) Tj T* (import os, shelve, plac) Tj T* T* (DEFAULT_SHELVE = os.path.expanduser\('~/conf.shelve'\)) Tj T* T* (@plac.annotations\() Tj T* ( help=\('show help', 'flag'\),) Tj T* ( showall=\('show all parameters in the shelve', 'flag'\),) Tj T* ( clear=\('clear the shelve', 'flag'\),) Tj T* ( delete=\('delete an element', 'option'\),) Tj T* ( filename=\('filename of the shelve', 'option'\),) Tj T* ( params='names of the parameters in the shelve',) Tj T* ( setters='setters param=value'\)) Tj T* (def main\(help, showall, clear, delete, filename=DEFAULT_SHELVE,) Tj T* ( *params, **setters\):) Tj T* ( "A simple interface to a shelve. Use .help to see the available commands.") Tj T* ( sh = shelve.open\(filename\)) Tj T* ( try:) Tj T* ( if not any\([help, showall, clear, delete, params, setters]\):) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (14) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R365': class PDFStream -365 0 obj -% page stream -<< /Length 6068 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 352.2981 cm -q -q -.952737 0 0 .952737 0 0 cm -q -1 0 0 1 6.6 6.927412 cm -q -.662745 .662745 .662745 RG -.5 w -.960784 .960784 .862745 rg -n -6 -6 492 432 re B* -Q -q -BT 1 0 0 1 0 410 Tm 12 TL /F3 10 Tf 0 0 0 rg ( yield 'no arguments passed, use .help to see the available commands') Tj T* ( elif help: # custom help) Tj T* ( yield 'Commands: .help, .showall, .clear, .delete') Tj T* ( yield ') Tj (<) Tj (param) Tj (>) Tj ( ...') Tj T* ( yield ') Tj (<) Tj (param=value) Tj (>) Tj ( ...') Tj T* ( elif showall:) Tj T* ( for param, name in sh.items\(\):) Tj T* ( yield '%s=%s' % \(param, name\)) Tj T* ( elif clear:) Tj T* ( sh.clear\(\)) Tj T* ( yield 'cleared the shelve') Tj T* ( elif delete:) Tj T* ( try:) Tj T* ( del sh[delete]) Tj T* ( except KeyError:) Tj T* ( yield '%s: not found' % delete) Tj T* ( else:) Tj T* ( yield 'deleted %s' % delete) Tj T* ( for param in params:) Tj T* ( try:) Tj T* ( yield sh[param]) Tj T* ( except KeyError:) Tj T* ( yield '%s: not found' % param ) Tj T* ( for param, value in setters.items\(\):) Tj T* ( sh[param] = value) Tj T* ( yield 'setting %s=%s' % \(param, value\)) Tj T* ( finally:) Tj T* ( sh.close\(\)) Tj T* T* (main.add_help = False # there is a custom help, remove the default one) Tj T* (main.prefix_chars = '.' # use dot-prefixed commands) Tj T* T* (if __name__ == '__main__':) Tj T* ( for output in plac.call\(main\):) Tj T* ( print\(output\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 332.2981 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (A few notes are in order:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 326.2981 cm -Q -q -1 0 0 1 62.69291 326.2981 cm -Q -q -1 0 0 1 62.69291 302.2981 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 9 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (1.) Tj T* -5.66 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 14 Tm 2.075318 Tw 12 TL /F1 10 Tf 0 0 0 rg (I have disabled the ordinary help provided by ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (and I have implemented a custom help) Tj T* 0 Tw (command.) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 296.2981 cm -Q -q -1 0 0 1 62.69291 284.2981 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 -3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (2.) Tj T* -5.66 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (I have changed the prefix character used to recognize the options to a dot.) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 278.2981 cm -Q -q -1 0 0 1 62.69291 254.2981 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 9 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (3.) Tj T* -5.66 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 14 Tm .864985 Tw 12 TL /F1 10 Tf 0 0 0 rg (Keyword arguments recognition \(in the ) Tj /F3 10 Tf (**setters) Tj /F1 10 Tf (\) is used to make it possible to store a value in) Tj T* 0 Tw (the shelve with the syntax ) Tj /F3 10 Tf (param_name=param_value) Tj /F1 10 Tf (.) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 248.2981 cm -Q -q -1 0 0 1 62.69291 224.2981 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 9 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (4.) Tj T* -5.66 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 14 Tm .649318 Tw 12 TL /F3 10 Tf 0 0 0 rg (*params ) Tj /F1 10 Tf (are used to retrieve parameters from the shelve and some error checking is performed in) Tj T* 0 Tw (the case of missing parameters) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 218.2981 cm -Q -q -1 0 0 1 62.69291 206.2981 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 -3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (5.) Tj T* -5.66 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (A command to clear the shelve is implemented as a flag \() Tj /F3 10 Tf (.clear) Tj /F1 10 Tf (\).) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 200.2981 cm -Q -q -1 0 0 1 62.69291 188.2981 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 -3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (6.) Tj T* -5.66 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (A command to delete a given parameter is implemented as an option \() Tj /F3 10 Tf (.delete) Tj /F1 10 Tf (\).) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 182.2981 cm -Q -q -1 0 0 1 62.69291 170.2981 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 -3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (7.) Tj T* -5.66 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (There is an option with default \() Tj /F3 10 Tf (.filename=conf.shelve) Tj /F1 10 Tf (\) to store the filename of the shelve.) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 164.2981 cm -Q -q -1 0 0 1 62.69291 128.2981 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 21 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (8.) Tj T* -5.66 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 26 Tm 1.001984 Tw 12 TL /F1 10 Tf 0 0 0 rg (All things considered, the code looks like a poor man object oriented interface implemented with a) Tj T* 0 Tw 1.345251 Tw (chain of elifs instead of methods. Of course, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (can do better than that, but let me start from a) Tj T* 0 Tw (low-level approach first.) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 128.2981 cm -Q -q -1 0 0 1 62.69291 110.2981 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (If you run ) Tj /F3 10 Tf (ishelve.py ) Tj /F1 10 Tf (without arguments you get the following message:) Tj T* ET -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (15) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R366': class PDFStream -366 0 obj -% page stream -<< /Length 5938 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 727.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 36 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL ($ python ishelve.py) Tj T* (no arguments passed, use .help to see the available commands) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 707.8236 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (If you run ) Tj /F3 10 Tf (ishelve.py ) Tj /F1 10 Tf (with the option ) Tj /F3 10 Tf (.h ) Tj /F1 10 Tf (\(or any abbreviation of ) Tj /F3 10 Tf (.help) Tj /F1 10 Tf (\) you get:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 638.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 60 re B* -Q -q -BT 1 0 0 1 0 38 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ python ishelve.py .h) Tj T* (Commands: .help, .showall, .clear, .delete) Tj T* (<) Tj (param) Tj (>) Tj ( ...) Tj T* (<) Tj (param=value) Tj (>) Tj ( ...) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 618.6236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (You can check by hand that the tool work:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 369.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 240 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 218 Tm /F3 10 Tf 12 TL ($ python ishelve.py .clear # start from an empty shelve) Tj T* (cleared the shelve) Tj T* ($ python ishelve.py a=1 b=2) Tj T* (setting a=1) Tj T* (setting b=2) Tj T* ($ python ishelve.py .showall) Tj T* (b=2) Tj T* (a=1) Tj T* ($ python ishelve.py .del b # abbreviation for .delete) Tj T* (deleted b) Tj T* ($ python ishelve.py a) Tj T* (1) Tj T* ($ python ishelve.py b) Tj T* (b: not found) Tj T* ($ python ishelve.py .cler # mispelled command) Tj T* (usage: ishelve.py [.help] [.showall] [.clear] [.delete DELETE]) Tj T* ( [.filename /home/micheles/conf.shelve]) Tj T* ( [params [params ...]] [setters [setters ...]]) Tj T* (ishelve.py: error: unrecognized arguments: .cler) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 339.4236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (plac vs argparse) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 297.4236 cm -q -BT 1 0 0 1 0 26 Tm 1.065988 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is opinionated and by design it does not try to make available all of the features of ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (in an) Tj T* 0 Tw .177126 Tw (easy way. In particular you should be aware of the following limitations/differences \(the following assumes) Tj T* 0 Tw (knowledge of ) Tj 0 0 .501961 rg (argparse) Tj 0 0 0 rg (\):) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 291.4236 cm -Q -q -1 0 0 1 62.69291 291.4236 cm -Q -q -1 0 0 1 62.69291 219.4236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 57 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 62 Tm 2.69784 Tw 12 TL /F1 10 Tf 0 0 0 rg (plac does not support the destination concept: the destination coincides with the name of the) Tj T* 0 Tw .359983 Tw (argument, always. This restriction has some drawbacks. For instance, suppose you want to define a) Tj T* 0 Tw 2.758651 Tw (long option called ) Tj /F3 10 Tf (--yield) Tj /F1 10 Tf (. In this case the destination would be ) Tj /F3 10 Tf (yield) Tj /F1 10 Tf (, which is a Python) Tj T* 0 Tw 1.181235 Tw (keyword, and since you cannot introduce an argument with that name in a function definition, it is) Tj T* 0 Tw 2.12528 Tw (impossible to implement it. Your choices are to change the name of the long option, or to use) Tj T* 0 Tw 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (with a suitable destination.) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 213.4236 cm -Q -q -1 0 0 1 62.69291 165.4236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 33 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 38 Tm 1.120751 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (does not support "required options". As the ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (documentation puts it: ) Tj /F4 10 Tf (Required options) Tj T* 0 Tw 1.075318 Tw (are generally considered bad form - normal users expect options to be optional. You should avoid) Tj T* 0 Tw .874269 Tw (the use of required options whenever possible. ) Tj /F1 10 Tf (Notice that since ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (supports them, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (can) Tj T* 0 Tw (manage them too, but not directly.) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 159.4236 cm -Q -q -1 0 0 1 62.69291 99.42362 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 45 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 50 Tm 1.539982 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (supports only regular boolean flags. ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (has the ability to define generalized two-value) Tj T* 0 Tw .361751 Tw (flags with values different from ) Tj /F3 10 Tf (True ) Tj /F1 10 Tf (and ) Tj /F3 10 Tf (False) Tj /F1 10 Tf (. An earlier version of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (had this feature too, but) Tj T* 0 Tw .814985 Tw (since you can use options with two choices instead, and in any case the conversion from ) Tj /F3 10 Tf ({True,) Tj T* 0 Tw .901984 Tw (False} ) Tj /F1 10 Tf (to any couple of values can be trivially implemented with a ternary operator \() Tj /F3 10 Tf (value1 if) Tj T* 0 Tw (flag else value2) Tj /F1 10 Tf (\), I have removed it \(KISS rules!\).) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 93.42362 cm -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (16) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R367': class PDFStream -367 0 obj -% page stream -<< /Length 7614 >> -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 -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 21 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 26 Tm 1.797126 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (does not support ) Tj /F3 10 Tf (nargs ) Tj /F1 10 Tf (options directly \(it uses them internally, though, to implement flag) Tj T* 0 Tw .90683 Tw (recognition\). The reason it that all the use cases of interest to me are covered by ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (and did not) Tj T* 0 Tw (feel the need to increase the learning curve by adding direct support for ) Tj /F3 10 Tf (nargs) Tj /F1 10 Tf (.) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 723.0236 cm -Q -q -1 0 0 1 62.69291 699.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 9 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 14 Tm 2.111318 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (does support subparsers, but you must read the ) Tj 0 0 .501961 rg (advanced usage document ) Tj 0 0 0 rg (to see how it) Tj T* 0 Tw (works.) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 693.0236 cm -Q -q -1 0 0 1 62.69291 657.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 21 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 26 Tm 1.111751 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (does not support actions directly. This also looks like a feature too advanced for the goals of) Tj T* 0 Tw .406651 Tw 0 0 .501961 rg (plac) Tj 0 0 0 rg (. Notice however that the ability to define your own annotation objects \(again, see the ) Tj 0 0 .501961 rg (advanced) Tj T* 0 Tw (usage document) Tj 0 0 0 rg (\) may mitigate the need for custom actions.) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 657.0236 cm -Q -q -1 0 0 1 62.69291 639.0236 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (can leverage directly on many ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (features.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 597.0236 cm -q -BT 1 0 0 1 0 26 Tm 5.575697 Tw 12 TL /F1 10 Tf 0 0 0 rg (For instance, you can make invisible an argument in the usage message simply by using) Tj T* 0 Tw 1.435976 Tw /F3 10 Tf ('==SUPPRESS==' ) Tj /F1 10 Tf (as help string \(or ) Tj /F3 10 Tf (argparse.SUPPRESS) Tj /F1 10 Tf (\). Similarly, you can use ) Tj 0 0 .501961 rg (argparse.FileType) Tj T* 0 Tw 0 0 0 rg (directly.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 543.0236 cm -q -BT 1 0 0 1 0 38 Tm 1.639213 Tw 12 TL /F1 10 Tf 0 0 0 rg (It is also possible to pass options to the underlying ) Tj /F3 10 Tf (argparse.ArgumentParser ) Tj /F1 10 Tf (object \(currently it) Tj T* 0 Tw .285529 Tw (accepts the default arguments ) Tj /F3 10 Tf (description) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (epilog) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (prog) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (usage) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (add_help) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (argument_default) Tj /F1 10 Tf (,) Tj T* 0 Tw 1.439953 Tw /F3 10 Tf (parents) 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 /F3 10 Tf (conflict_handler) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (formatter_class) Tj /F1 10 Tf (\). It) Tj T* 0 Tw (is enough to set such attributes on the ) Tj /F3 10 Tf (main ) Tj /F1 10 Tf (function. For instance) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 473.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 60 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 38 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 429.8236 cm -q -BT 1 0 0 1 0 26 Tm .239318 Tw 12 TL /F1 10 Tf 0 0 0 rg (disables the recognition of the help flag ) Tj /F3 10 Tf (-h, --help) Tj /F1 10 Tf (. This mechanism does not look particularly elegant,) Tj T* 0 Tw .566988 Tw (but it works well enough. I assume that the typical user of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (will be happy with the defaults and would) Tj T* 0 Tw (not want to change them; still it is possible if she wants to.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 399.8236 cm -q -BT 1 0 0 1 0 14 Tm 2.391235 Tw 12 TL /F1 10 Tf 0 0 0 rg (For instance, by setting the ) Tj /F3 10 Tf (description ) Tj /F1 10 Tf (attribute, it is possible to add a comment to the usage) Tj T* 0 Tw (message \(by default the docstring of the ) Tj /F3 10 Tf (main ) Tj /F1 10 Tf (function is used as description\).) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 369.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .392619 Tw (It is also possible to change the option prefix; for instance if your script must run under Windows and you) Tj T* 0 Tw (want to use "/" as option prefix you can add the line:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 336.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 24 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F3 10 Tf 12 TL (main.prefix_chars='/-') Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 292.6236 cm -q -BT 1 0 0 1 0 26 Tm .924198 Tw 12 TL /F1 10 Tf 0 0 0 rg (The first prefix char \() Tj /F3 10 Tf (/) Tj /F1 10 Tf (\) is used as the default for the recognition of options and flags; the second prefix) Tj T* 0 Tw .26832 Tw (char \() Tj /F3 10 Tf (-) Tj /F1 10 Tf (\) is kept to keep the ) Tj /F3 10 Tf (-h/--help ) Tj /F1 10 Tf (option working: however you can disable it and reimplement it, if) Tj T* 0 Tw (you like, as seen in the ) Tj /F3 10 Tf (ishelve ) Tj /F1 10 Tf (example.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 262.6236 cm -q -BT 1 0 0 1 0 14 Tm 7.709147 Tw 12 TL /F1 10 Tf 0 0 0 rg (It is possible to access directly the underlying ) Tj 0 0 .501961 rg (ArgumentParser ) Tj 0 0 0 rg (object, by invoking the) Tj T* 0 Tw /F3 10 Tf (plac.parser_from ) Tj /F1 10 Tf (utility function:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 169.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 84 re B* -Q -q -BT 1 0 0 1 0 62 Tm 12 TL /F3 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( import plac) Tj T* (>) Tj (>) Tj (>) Tj ( def main\(arg\):) Tj T* (... pass) Tj T* (...) Tj T* (>) Tj (>) Tj (>) Tj ( print\(plac.parser_from\(main\)\) #doctest: +ELLIPSIS) Tj T* (ArgumentParser\(prog=...\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 125.4236 cm -q -BT 1 0 0 1 0 26 Tm 2.646905 Tw 12 TL /F1 10 Tf 0 0 0 rg (Internally ) Tj /F3 10 Tf (plac.call ) Tj /F1 10 Tf (uses ) Tj /F3 10 Tf (plac.parser_from ) Tj /F1 10 Tf (and adds the parser to the main function as an) Tj T* 0 Tw .982126 Tw (attribute. When ) Tj /F3 10 Tf (plac.call\(func\) ) Tj /F1 10 Tf (is invoked multiple time, the parser is re-used and not rebuilt from) Tj T* 0 Tw (scratch again.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 95.42362 cm -q -BT 1 0 0 1 0 14 Tm .982765 Tw 12 TL /F1 10 Tf 0 0 0 rg (I use ) Tj /F3 10 Tf (plac.parser_from ) Tj /F1 10 Tf (in the unit tests of the module, but regular users should not need to use it,) Tj T* 0 Tw (unless they want to access ) Tj /F4 10 Tf (all ) Tj /F1 10 Tf (of the features of ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (directly without calling the main function.) Tj T* ET -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (17) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R368': class PDFStream -368 0 obj -% page stream -<< /Length 8496 >> -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 38 Tm 1.442126 Tw 12 TL /F1 10 Tf 0 0 0 rg (Interested readers should read the documentation of ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (to understand the meaning of the other) Tj T* 0 Tw .771567 Tw (options. If there is a set of options that you use very often, you may consider writing a decorator adding) Tj T* 0 Tw 1.257045 Tw (such options to the ) Tj /F3 10 Tf (main ) Tj /F1 10 Tf (function for you. For simplicity, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (does not perform any magic except the) Tj T* 0 Tw (addition of the ) Tj /F3 10 Tf (.p ) Tj /F1 10 Tf (attribute.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 687.0236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (plac vs the rest of the world) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 645.0236 cm -q -BT 1 0 0 1 0 26 Tm 1.866905 Tw 12 TL /F1 10 Tf 0 0 0 rg (Originally ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (boasted about being "the easiest command-line arguments parser in the world". Since) Tj T* 0 Tw .306457 Tw (then, people started pointing out to me various projects which are based on the same idea \(extracting the) Tj T* 0 Tw (parser from the main function signature\) and are arguably even easier than ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 639.0236 cm -Q -q -1 0 0 1 62.69291 639.0236 cm -Q -q -1 0 0 1 62.69291 627.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 -3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (opterator ) Tj 0 0 0 rg (by Dusty Phillips) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 621.0236 cm -Q -q -1 0 0 1 62.69291 609.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 -3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (CLIArgs ) Tj 0 0 0 rg (by Pavel Panchekha) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 603.0236 cm -Q -q -1 0 0 1 62.69291 591.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 -3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (commandline ) Tj 0 0 0 rg (by David Laban) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 591.0236 cm -Q -q -1 0 0 1 62.69291 561.0236 cm -q -BT 1 0 0 1 0 14 Tm 2.136457 Tw 12 TL /F1 10 Tf 0 0 0 rg (Luckily for me none of such projects had the idea of using function annotations and ) Tj 0 0 .501961 rg (argparse) Tj 0 0 0 rg (; as a) Tj T* 0 Tw (consequence, they are no match for the capabilities of ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 519.0236 cm -q -BT 1 0 0 1 0 26 Tm 1.551163 Tw 12 TL /F1 10 Tf 0 0 0 rg (Of course, there are tons of other libraries to parse the command line. For instance ) Tj 0 0 .501961 rg (Clap ) Tj 0 0 0 rg (by Matthew) Tj T* 0 Tw 1.211567 Tw (Frazier which appeared on PyPI just the day before ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (; ) Tj 0 0 .501961 rg (Clap ) Tj 0 0 0 rg (is fine but it is certainly not easier than) Tj T* 0 Tw 0 0 .501961 rg (plac) Tj 0 0 0 rg (.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 477.0236 cm -q -BT 1 0 0 1 0 26 Tm .622488 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (can also be used as a replacement of the ) Tj 0 0 .501961 rg (cmd ) Tj 0 0 0 rg (module in the standard library and as such it shares) Tj T* 0 Tw .377126 Tw (many features with the module ) Tj 0 0 .501961 rg (cmd2 ) Tj 0 0 0 rg (by Catherine Devlin. However, this is completely coincidental, since) Tj T* 0 Tw (I became aware of the ) Tj 0 0 .501961 rg (cmd2 ) Tj 0 0 0 rg (module only after writing ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 423.0236 cm -q -BT 1 0 0 1 0 38 Tm .449982 Tw 12 TL /F1 10 Tf 0 0 0 rg (Command-line argument parsers keep coming out; between the newcomers I will notice ) Tj 0 0 .501961 rg (marrow.script ) Tj 0 0 0 rg (by) Tj T* 0 Tw .30683 Tw (Alice Bevan-McGregor, which is quite similar to ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (in spirit, but does not rely on ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (at all. ) Tj 0 0 .501961 rg (Argh ) Tj 0 0 0 rg (by) Tj T* 0 Tw .600542 Tw (Andrey Mikhaylenko is also worth mentioning: it is also based on ) Tj 0 0 .501961 rg (argparse) Tj 0 0 0 rg (, it came after ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (and I must) Tj T* 0 Tw (give credit to the author for the choice of the name, much funnier than plac!) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 393.0236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (The future) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 327.0236 cm -q -BT 1 0 0 1 0 50 Tm .135542 Tw 12 TL /F1 10 Tf 0 0 0 rg (Currently the core of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is around 200 lines of code, not counting blanks, comments and docstrings. I do) Tj T* 0 Tw .968626 Tw (not plan to extend the core much in the future. The idea is to keep the module short: it is and it should) Tj T* 0 Tw .11811 Tw (remain a little wrapper over ) Tj 0 0 .501961 rg (argparse) Tj 0 0 0 rg (. Actually I have thought about contributing the core back to ) Tj 0 0 .501961 rg (argparse) Tj T* 0 Tw 2.307485 Tw 0 0 0 rg (if ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (becomes successfull and gains a reasonable number of users. For the moment it should be) Tj T* 0 Tw (considered in alpha status.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 285.0236 cm -q -BT 1 0 0 1 0 26 Tm .927488 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that even if ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (has been designed to be simple to use for simple stuff, its power should not be) Tj T* 0 Tw 1.02186 Tw (underestimated; it is actually a quite advanced tool with a domain of applicability which far exceeds the) Tj T* 0 Tw (realm of command-line arguments parsers.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 219.0236 cm -q -BT 1 0 0 1 0 50 Tm .285988 Tw 12 TL /F1 10 Tf 0 0 0 rg (Version 0.5 of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (doubled the code base and the documentation: it is based on the idea of using ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (to) Tj T* 0 Tw .408555 Tw (implement command-line interpreters, i.e. something akin to the ) Tj /F3 10 Tf (cmd ) Tj /F1 10 Tf (module in the standard library, only) Tj T* 0 Tw .49936 Tw (better. The new features of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (are described in the ) Tj 0 0 .501961 rg (advanced usage document ) Tj 0 0 0 rg (. They are implemented) Tj T* 0 Tw .313828 Tw (in a separated module \() Tj /F3 10 Tf (plac_ext.py) Tj /F1 10 Tf (\), since they require Python 2.5 to work, whereas ) Tj /F3 10 Tf (plac_core.py) Tj T* 0 Tw /F1 10 Tf (only requires Python 2.3.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 189.0236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Trivia: the story behind the name) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 123.0236 cm -q -BT 1 0 0 1 0 50 Tm .979984 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (project started very humbly: I just wanted to make easy_installable my old ) Tj 0 0 .501961 rg (optionparse ) Tj 0 0 0 rg (recipe,) Tj T* 0 Tw .565988 Tw (and to publish it on PyPI. The original name of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (was optionparser and the idea behind it was to build) Tj T* 0 Tw .603735 Tw (an ) Tj 0 0 .501961 rg (OptionParser ) Tj 0 0 0 rg (object from the docstring of the module. However, before doing that, I decided to check) Tj T* 0 Tw .244198 Tw (out the ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (module, since I knew it was going into Python 2.7 and Python 2.7 was coming out. Soon) Tj T* 0 Tw (enough I realized two things:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 117.0236 cm -Q -q -1 0 0 1 62.69291 117.0236 cm -Q -q -1 0 0 1 62.69291 93.02362 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 9 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (1.) Tj T* -5.66 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 14 Tm .103735 Tw 12 TL /F1 10 Tf 0 0 0 rg (the single greatest idea of ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (was unifying the positional arguments and the options in a single) Tj T* 0 Tw (namespace object;) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 88.86614 cm -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (18) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R369': class PDFStream -369 0 obj -% page stream -<< /Length 6685 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 741.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 9 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (2.) Tj T* -5.66 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL 1.66748 Tw (parsing the docstring was so old-fashioned, considering the existence of functions annotations in) Tj T* 0 Tw (Python 3.) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 741.0236 cm -Q -q -1 0 0 1 62.69291 687.0236 cm -q -BT 1 0 0 1 0 38 Tm .600574 Tw 12 TL /F1 10 Tf 0 0 0 rg (Putting together these two observations with the original idea of inferring the parser I decided to build an) Tj T* 0 Tw .516905 Tw 0 0 .501961 rg (ArgumentParser ) Tj 0 0 0 rg (object from function annotations. The ) Tj /F3 10 Tf (optionparser ) Tj /F1 10 Tf (name was ruled out, since I was) Tj T* 0 Tw 2.085984 Tw (now using ) Tj 0 0 .501961 rg (argparse) Tj 0 0 0 rg (; a name like ) Tj /F3 10 Tf (argparse_plus ) Tj /F1 10 Tf (was also ruled out, since the typical usage was) Tj T* 0 Tw (completely different from the ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (usage.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 657.0236 cm -q -BT 1 0 0 1 0 14 Tm 1.093876 Tw 12 TL /F1 10 Tf 0 0 0 rg (I made a research on PyPI and the name ) Tj /F4 10 Tf (clap ) Tj /F1 10 Tf (\(Command Line Arguments Parser\) was not taken, so I) Tj T* 0 Tw (renamed everything to clap. After two days a ) Tj 0 0 .501961 rg (Clap ) Tj 0 0 0 rg (module appeared on PyPI <) Tj (expletives deleted) Tj (>) Tj (!) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 627.0236 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .877209 Tw (Having little imagination, I decided to rename everything again to plac, an anagram of clap: since it is a) Tj T* 0 Tw (non-existing English name, I hope nobody will steal it from me!) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 597.0236 cm -q -BT 1 0 0 1 0 14 Tm .225542 Tw 12 TL /F1 10 Tf 0 0 0 rg (That concludes the section about the basic usage of ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (. You are now ready to read about the advanced) Tj T* 0 Tw (usage.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 564.0236 cm -q -BT 1 0 0 1 0 3.5 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Advanced usages of plac) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 534.0236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Introduction) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 492.0236 cm -q -BT 1 0 0 1 0 26 Tm .539036 Tw 12 TL /F1 10 Tf 0 0 0 rg (One of the design goals of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is to make it dead easy to write a scriptable and testable interface for an) Tj T* 0 Tw .813876 Tw (application. You can use ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (whenever you have an API with strings in input and strings in output, and) Tj T* 0 Tw (that includes a ) Tj /F4 10 Tf (huge ) Tj /F1 10 Tf (domain of applications.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 450.0236 cm -q -BT 1 0 0 1 0 26 Tm 1.756651 Tw 12 TL /F1 10 Tf 0 0 0 rg (A string-oriented interface is a scriptable interface by construction. That means that you can define a) Tj T* 0 Tw .918735 Tw (command language for your application and that it is possible to write scripts which are interpretable by) Tj T* 0 Tw 0 0 .501961 rg (plac ) Tj 0 0 0 rg (and can be run as batch scripts.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 408.0236 cm -q -BT 1 0 0 1 0 26 Tm .444987 Tw 12 TL /F1 10 Tf 0 0 0 rg (Actually, at the most general level, you can see ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (as a generic tool to write domain specific languages) Tj T* 0 Tw .107209 Tw (\(DSL\). With ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (you can test your application interactively as well as with batch scripts, and even with the) Tj T* 0 Tw (analogous of Python doctests for your defined language.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 342.0236 cm -q -BT 1 0 0 1 0 50 Tm .694104 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can easily replace the ) Tj /F3 10 Tf (cmd ) Tj /F1 10 Tf (module of the standard library and you could easily write an application) Tj T* 0 Tw 2.271751 Tw (like ) Tj 0 0 .501961 rg (twill ) Tj 0 0 0 rg (with ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (. Or you could use it to script your building procedure. ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (also supports parallel) Tj T* 0 Tw .907765 Tw (execution of multiple commands and can be used as task manager and monitor. It is also quite easy to) Tj T* 0 Tw 1.483488 Tw (build a GUI or a Web application on top of ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (. When speaking of things you can do with ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (, your) Tj T* 0 Tw (imagination is the only limit!) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 312.0236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (From scripts to interactive applications) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 258.0236 cm -q -BT 1 0 0 1 0 38 Tm 1.300751 Tw 12 TL /F1 10 Tf 0 0 0 rg (Command-line scripts have many advantages, but they are no substitute for interactive applications. In) Tj T* 0 Tw .088171 Tw (particular, if you have a script with a large startup time which must be run multiple times, it is best to turn it) Tj T* 0 Tw 4.582126 Tw (into an interactive application, so that the startup is performed only once. ) Tj /F3 10 Tf (plac ) Tj /F1 10 Tf (provides an) Tj T* 0 Tw /F3 10 Tf (Interpreter ) Tj /F1 10 Tf (class just for this purpose.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 228.0236 cm -q -BT 1 0 0 1 0 14 Tm 1.293984 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F3 10 Tf (Interpreter ) Tj /F1 10 Tf (class wraps the main function of a script and provides an ) Tj /F3 10 Tf (.interact ) Tj /F1 10 Tf (method to) Tj T* 0 Tw (start an interactive interpreter reading commands from the console.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 198.0236 cm -q -BT 1 0 0 1 0 14 Tm 1.49436 Tw 12 TL /F1 10 Tf 0 0 0 rg (For instance, you can define an interactive interpreter on top of the ) Tj /F3 10 Tf (ishelve ) Tj /F1 10 Tf (script introduced in the) Tj T* 0 Tw 0 0 .501961 rg (basic documentation ) Tj 0 0 0 rg (as follows:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 92.82362 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 96 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 74 Tm /F3 10 Tf 12 TL (# shelve_interpreter.py) Tj T* (import plac, ishelve) Tj T* T* (@plac.annotations\() Tj T* ( interactive=\('start interactive interface', 'flag'\),) Tj T* ( subcommands='the commands of the underlying ishelve interpreter'\)) Tj T* (def main\(interactive, *subcommands\):) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (19) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R370': class PDFStream -370 0 obj -% page stream -<< /Length 3712 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 607.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 156 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 134 Tm /F3 10 Tf 12 TL ( """) Tj T* ( This script works both interactively and non-interactively.) Tj T* ( Use .help to see the internal commands.) Tj T* ( """) Tj T* ( if interactive:) Tj T* ( plac.Interpreter\(ishelve.main\).interact\(\)) Tj T* ( else:) Tj T* ( for out in plac.call\(ishelve.main, subcommands\):) Tj T* ( print\(out\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( plac.call\(main\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 563.8236 cm -q -BT 1 0 0 1 0 26 Tm 2.200651 Tw 12 TL /F1 10 Tf 0 0 0 rg (A trick has been used here: the ishelve command-line interface has been hidden inside an external) Tj T* 0 Tw .917674 Tw (interface. They are distinct: for instance the external interface recognizes the ) Tj /F3 10 Tf (-h/--help ) Tj /F1 10 Tf (flag whereas) Tj T* 0 Tw (the internal interface only recognizes the ) Tj /F3 10 Tf (.help ) Tj /F1 10 Tf (command:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 530.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 24 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F3 10 Tf 12 TL ($ python shelve_interpreter.py -h) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 353.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 168 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 146 Tm /F3 10 Tf 12 TL (usage: shelve_interpreter.py [-h] [-interactive]) Tj T* ( [subcommands [subcommands ...]]) Tj T* T* ( This script works both interactively and non-interactively.) Tj T* ( Use .help to see the internal commands.) Tj T* ( ) Tj T* T* (positional arguments:) Tj T* ( subcommands the commands of the underlying ishelve interpreter) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ( -interactive start interactive interface) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 333.4236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Thanks to this ingenuous trick, the script can be run both interactively and non-interactively:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 288.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 36 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL ($ python shelve_interpreter.py .clear # non-interactive use) Tj T* (cleared the shelve) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 268.2236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is an usage session:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 91.02362 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 168 re B* -Q -q -BT 1 0 0 1 0 146 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ python shelve_interpreter.py -i # interactive use) Tj T* (A simple interface to a shelve. Use .help to see the available commands.) Tj T* (i) Tj (>) Tj ( .help) Tj T* (Commands: .help, .showall, .clear, .delete) Tj T* (<) Tj (param) Tj (>) Tj ( ...) Tj T* (<) Tj (param=value) Tj (>) Tj ( ...) Tj T* (i) Tj (>) Tj ( a=1) Tj T* (setting a=1) Tj T* (i) Tj (>) Tj ( a) Tj T* (1) Tj T* (i) Tj (>) Tj ( b=2) Tj T* (setting b=2) Tj T* (i) Tj (>) Tj ( a b) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (20) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R371': class PDFStream -371 0 obj -% page stream -<< /Length 4861 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 643.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 -BT 1 0 0 1 0 98 Tm 12 TL /F3 10 Tf 0 0 0 rg (1) Tj T* (2) Tj T* (i) Tj (>) Tj ( .del a) Tj T* (deleted a) Tj T* (i) Tj (>) Tj ( a) Tj T* (a: not found) Tj T* (i) Tj (>) Tj ( .show) Tj T* (b=2) Tj T* (i) Tj (>) Tj ( [CTRL-D]) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 575.8236 cm -q -BT 1 0 0 1 0 50 Tm .256412 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F3 10 Tf (.interact ) Tj /F1 10 Tf (method reads commands from the console and send them to the underlying interpreter,) Tj T* 0 Tw .065984 Tw (until the user send a CTRL-D command \(CTRL-Z in Windows\). There is a default argument ) Tj /F3 10 Tf (prompt='i) Tj (>) Tj T* 0 Tw .41832 Tw (' ) Tj /F1 10 Tf (which can be used to change the prompt. The text displayed at the beginning of the interactive session) Tj T* 0 Tw 1.407126 Tw (is the docstring of the main function. ) Tj /F3 10 Tf (plac ) Tj /F1 10 Tf (also understands command abbreviations: in this example) Tj T* 0 Tw /F3 10 Tf (del ) Tj /F1 10 Tf (is an abbreviation for ) Tj /F3 10 Tf (delete) Tj /F1 10 Tf (. In case of ambiguous abbreviations ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (raises a ) Tj /F3 10 Tf (NameError) Tj /F1 10 Tf (.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 545.8236 cm -q -BT 1 0 0 1 0 14 Tm .847045 Tw 12 TL /F1 10 Tf 0 0 0 rg (Finally I must notice that the ) Tj /F3 10 Tf (plac.Interpreter ) Tj /F1 10 Tf (is available only if you are using a recent version of) Tj T* 0 Tw (Python \() Tj (>) Tj (= 2.5\), because it is a context manager object which uses extended generators internally.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 515.8236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Testing a plac application) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 485.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL 3.034269 Tw (You can conveniently test your application in interactive mode. However manual testing is a poor) Tj T* 0 Tw (substitute for automatic testing.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 467.8236 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (In principle, one could write automatic tests for the ) Tj /F3 10 Tf (ishelve ) Tj /F1 10 Tf (application by using ) Tj /F3 10 Tf (plac.call ) Tj /F1 10 Tf (directly:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 302.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 156 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 134 Tm /F3 10 Tf 12 TL (# test_ishelve.py) Tj T* (import plac, ishelve) Tj T* T* (def test\(\):) Tj T* ( assert plac.call\(ishelve.main, ['.clear']\) == ['cleared the shelve']) Tj T* ( assert plac.call\(ishelve.main, ['a=1']\) == ['setting a=1']) Tj T* ( assert plac.call\(ishelve.main, ['a']\) == ['1']) Tj T* ( assert plac.call\(ishelve.main, ['.delete=a']\) == ['deleted a']) Tj T* ( assert plac.call\(ishelve.main, ['a']\) == ['a: not found']) Tj T* T* (if __name__ == '__main__':) Tj T* ( test\(\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 258.6236 cm -q -BT 1 0 0 1 0 26 Tm .390651 Tw 12 TL /F1 10 Tf 0 0 0 rg (However, using ) Tj /F3 10 Tf (plac.call ) Tj /F1 10 Tf (is not especially nice. The big issue is that ) Tj /F3 10 Tf (plac.call ) Tj /F1 10 Tf (responds to invalid) Tj T* 0 Tw 1.249987 Tw (input by printing an error message on stderr and by raising a ) Tj /F3 10 Tf (SystemExit) Tj /F1 10 Tf (: this is certainly not a nice) Tj T* 0 Tw (thing to do in a test.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 216.6236 cm -q -BT 1 0 0 1 0 26 Tm 1.616457 Tw 12 TL /F1 10 Tf 0 0 0 rg (As a consequence of this behavior it is impossible to test for invalid commands, unless you wrap the) Tj T* 0 Tw .259985 Tw /F3 10 Tf (SystemExit ) Tj /F1 10 Tf (exception by hand each time \(a possibly you do something with the error message in stderr) Tj T* 0 Tw (too\). Luckily, ) Tj /F3 10 Tf (plac ) Tj /F1 10 Tf (offers a better testing support through the ) Tj /F3 10 Tf (check ) Tj /F1 10 Tf (method of ) Tj /F3 10 Tf (Interpreter ) Tj /F1 10 Tf (objects:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 99.42362 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 86 Tm /F3 10 Tf 12 TL (# test_ishelve_more.py) Tj T* (from __future__ import with_statement) Tj T* (import plac, ishelve) Tj T* T* (def test\(\):) Tj T* ( with plac.Interpreter\(ishelve.main\) as i:) Tj T* ( i.check\('.clear', 'cleared the shelve'\)) Tj T* ( i.check\('a=1', 'setting a=1'\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (21) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R372': class PDFStream -372 0 obj -% page stream -<< /Length 5767 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 715.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 48 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 26 Tm /F3 10 Tf 12 TL ( i.check\('a', '1'\)) Tj T* ( i.check\('.delete=a', 'deleted a'\)) Tj T* ( i.check\('a', 'a: not found'\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 659.8236 cm -q -BT 1 0 0 1 0 38 Tm 6.299974 Tw 12 TL /F1 10 Tf 0 0 0 rg (The method ) Tj /F3 10 Tf (.check\(given_input, expected_output\) ) Tj /F1 10 Tf (works on strings and raises an) Tj T* 0 Tw .971318 Tw /F3 10 Tf (AssertionError ) Tj /F1 10 Tf (if the output produced by the interpreter is different from the expected output for the) Tj T* 0 Tw 2.186905 Tw (given input. Notice that ) Tj /F3 10 Tf (AssertionError ) Tj /F1 10 Tf (is catched by tools like ) Tj /F3 10 Tf (py.test ) Tj /F1 10 Tf (and ) Tj /F3 10 Tf (nosetests ) Tj /F1 10 Tf (and) Tj T* 0 Tw (actually ) Tj /F3 10 Tf (plac ) Tj /F1 10 Tf (tests are intended to be run with such tools.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 605.8236 cm -q -BT 1 0 0 1 0 38 Tm .239984 Tw 12 TL /F1 10 Tf 0 0 0 rg (Interpreters offer a minor syntactic advantage with respect to calling ) Tj /F3 10 Tf (plac.call ) Tj /F1 10 Tf (directly, but they offer a) Tj T* 0 Tw .96748 Tw /F4 10 Tf (major ) Tj /F1 10 Tf (semantic advantage when things go wrong \(read exceptions\): an ) Tj /F3 10 Tf (Interpreter ) Tj /F1 10 Tf (object internally) Tj T* 0 Tw 1.181318 Tw (invokes something like ) Tj /F3 10 Tf (plac.call) Tj /F1 10 Tf (, but it wraps all exceptions, so that ) Tj /F3 10 Tf (i.check ) Tj /F1 10 Tf (is guaranteed not to) Tj T* 0 Tw (raise any exception except ) Tj /F3 10 Tf (AssertionError) Tj /F1 10 Tf (.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 587.8236 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (Even the ) Tj /F3 10 Tf (SystemExit ) Tj /F1 10 Tf (exception is captured and you can write your test as) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 581.8236 cm -Q -q -1 0 0 1 62.69291 569.8236 cm -0 0 0 rg -BT /F1 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 2 Tm /F3 10 Tf 12 TL (i.check\('-cler', 'SystemExit: unrecognized arguments: -cler'\)) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 569.8236 cm -Q -q -1 0 0 1 62.69291 551.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (without risk of exiting from the Python interpreter.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 509.8236 cm -q -BT 1 0 0 1 0 26 Tm 1.422651 Tw 12 TL /F1 10 Tf 0 0 0 rg (There is a second advantage of interpreters: if the main function contains some initialization code and) Tj T* 0 Tw .454651 Tw (finalization code \() Tj /F3 10 Tf (__enter__ ) Tj /F1 10 Tf (and ) Tj /F3 10 Tf (__exit__ ) Tj /F1 10 Tf (functions\) they will be run only once at the beginning and) Tj T* 0 Tw (at the end of the interpreter loop. ) Tj /F3 10 Tf (plac.call ) Tj /F1 10 Tf (instead ignores the initialization/finalization code.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 479.8236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Plac easy tests) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 437.8236 cm -q -BT 1 0 0 1 0 26 Tm 1.517126 Tw 12 TL /F1 10 Tf 0 0 0 rg (Writing your tests in terms of ) Tj /F3 10 Tf (Interpreter.check ) Tj /F1 10 Tf (is certainly an improvement over writing them in) Tj T* 0 Tw 1.807318 Tw (terms of ) Tj /F3 10 Tf (plac.call) Tj /F1 10 Tf (, but they are still too low-level for my taste. The ) Tj /F3 10 Tf (Interpreter ) Tj /F1 10 Tf (class provides) Tj T* 0 Tw (support for doctest-style tests, a.k.a. ) Tj /F4 10 Tf (plac easy tests) Tj /F1 10 Tf (.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 395.8236 cm -q -BT 1 0 0 1 0 26 Tm 2.142209 Tw 12 TL /F1 10 Tf 0 0 0 rg (By using plac easy tests you can cut and paste your interactive session and turn it into a runnable) Tj T* 0 Tw .519213 Tw (automatics test. Consider for instance the following file ) Tj /F3 10 Tf (ishelve.placet ) Tj /F1 10 Tf (\(the ) Tj /F3 10 Tf (.placet ) Tj /F1 10 Tf (extension is a) Tj T* 0 Tw (mnemonic for plac easy tests\):) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 218.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 168 re B* -Q -q -BT 1 0 0 1 0 146 Tm 12 TL /F3 10 Tf 0 0 0 rg (#!ishelve.py) Tj T* (i) Tj (>) Tj ( .clear # start from a clean state) Tj T* (cleared the shelve) Tj T* (i) Tj (>) Tj ( a=1) Tj T* (setting a=1) Tj T* (i) Tj (>) Tj ( a) Tj T* (1) Tj T* (i) Tj (>) Tj ( .del a) Tj T* (deleted a) Tj T* (i) Tj (>) Tj ( a) Tj T* (a: not found) Tj T* (i) Tj (>) Tj ( .cler # spelling error) Tj T* (.cler: not found) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 162.6236 cm -q -BT 1 0 0 1 0 38 Tm .697132 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice the precence of the shebang line containing the name of the ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (tool to test \(a ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (tool is just a) Tj T* 0 Tw 1.511751 Tw (Python module with a function called ) Tj /F3 10 Tf (main) Tj /F1 10 Tf (\). The shebang is ignored by the interpreter \(it looks like a) Tj T* 0 Tw .487608 Tw (comment to it\) but it is there so that external tools \(say a test runner\) can infer the plac interpreter to use) Tj T* 0 Tw (to test the file.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 132.6236 cm -q -BT 1 0 0 1 0 14 Tm 2.419984 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can test ) Tj /F3 10 Tf (ishelve.placet ) Tj /F1 10 Tf (file by calling the ) Tj /F3 10 Tf (.doctest ) Tj /F1 10 Tf (method of the interpreter, as in this) Tj T* 0 Tw (example:) Tj T* ET -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (22) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R373': class PDFStream -373 0 obj -% page stream -<< /Length 6371 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 728.2393 cm -q -q -.988825 0 0 .988825 0 0 cm -q -1 0 0 1 6.6 6.674587 cm -q -.662745 .662745 .662745 RG -.5 w -.960784 .960784 .862745 rg -n -6 -6 474 36 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL ($ python -c"import plac, ishelve) Tj T* (plac.Interpreter\(ishelve.main\).doctest\(open\('ishelve.placet'\), verbose=True\)") Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 684.2393 cm -q -BT 1 0 0 1 0 26 Tm 4.007109 Tw 12 TL /F1 10 Tf 0 0 0 rg (Internally ) Tj /F3 10 Tf (Interpreter.doctests ) Tj /F1 10 Tf (invokes something like ) Tj /F3 10 Tf (Interpreter.check ) Tj /F1 10 Tf (multiple times) Tj T* 0 Tw .226654 Tw (inside the same context and compare the output with the expected output: if even a check fails, the whole) Tj T* 0 Tw (test fail.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 642.2393 cm -q -BT 1 0 0 1 0 26 Tm .175868 Tw 12 TL /F1 10 Tf 0 0 0 rg (You should realize tha the easy tests supported by ) Tj /F3 10 Tf (plac ) Tj /F1 10 Tf (are ) Tj /F4 10 Tf (not ) Tj /F1 10 Tf (unittests: they are functional tests. They) Tj T* 0 Tw 1.22936 Tw (model then user interaction and the order of the operations generally matters. The single subtests in a) Tj T* 0 Tw /F3 10 Tf (.placet ) Tj /F1 10 Tf (file are not independent and it makes sense to exit immediately at the first failure.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 564.2393 cm -q -BT 1 0 0 1 0 62 Tm .414431 Tw 12 TL /F1 10 Tf 0 0 0 rg (The support for doctests in ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (comes nearly for free, thanks to the ) Tj 0 0 .501961 rg (shlex ) Tj 0 0 0 rg (module in the standard library,) Tj T* 0 Tw .352765 Tw (which is able to parse simple languages as the ones you can implement with ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (. In particular, thanks to) Tj T* 0 Tw .875984 Tw 0 0 .501961 rg (shlex) Tj 0 0 0 rg (, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is able to recognize comments \(the default comment character is ) Tj /F3 10 Tf (#) Tj /F1 10 Tf (\), escape sequences and) Tj T* 0 Tw 1.50686 Tw (more. Look at the ) Tj 0 0 .501961 rg (shlex ) Tj 0 0 0 rg (documentation if you need to customize how the language is interpreted. For) Tj T* 0 Tw 2.794985 Tw (more flexibility, it is even possible to pass to the interpreter a custom split function with signature) Tj T* 0 Tw /F3 10 Tf (split\(line, commentchar\)) Tj /F1 10 Tf (.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 510.2393 cm -q -BT 1 0 0 1 0 38 Tm .136654 Tw 12 TL /F1 10 Tf 0 0 0 rg (In addition, I have implemented from scratch some support for line number recognition, so that if a test fail) Tj T* 0 Tw .042093 Tw (you get the line number of the failing command. This is especially useful if your tests are stored in external) Tj T* 0 Tw .610898 Tw (files, even if plac easy tests does not need to be in a file: you can just pass to the ) Tj /F3 10 Tf (.doctest ) Tj /F1 10 Tf (method a) Tj T* 0 Tw (list of strings corresponding to the lines of the file.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 480.2393 cm -q -BT 1 0 0 1 0 14 Tm .653145 Tw 12 TL /F1 10 Tf 0 0 0 rg (At the present ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (does not use any code from the doctest module, but the situation may change in the) Tj T* 0 Tw (future \(it would be nice if ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (could reuse doctests directives like ELLIPSIS\).) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 450.2393 cm -q -BT 1 0 0 1 0 14 Tm 1.447318 Tw 12 TL /F1 10 Tf 0 0 0 rg (It is straighforward to integrate your ) Tj /F3 10 Tf (.placet ) Tj /F1 10 Tf (tests with standard testing tools. For instance, you can) Tj T* 0 Tw (integrate your doctests with ) Tj /F3 10 Tf (nose ) Tj /F1 10 Tf (or ) Tj /F3 10 Tf (py.test ) Tj /F1 10 Tf (as follow:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 263.0642 cm -q -q -.988825 0 0 .988825 0 0 cm -q -1 0 0 1 6.6 6.674587 cm -q -.662745 .662745 .662745 RG -.5 w -.960784 .960784 .862745 rg -n -6 -6 474 180 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 158 Tm /F3 10 Tf 12 TL (import os, shlex, plac) Tj T* T* (def test_doct\(\):) Tj T* ( """) Tj T* ( Find all the doctests in the current directory and run them with the) Tj T* ( corresponding plac interpreter \(the shebang rules!\)) Tj T* ( """) Tj T* ( placets = [f for f in os.listdir\('.'\) if f.endswith\('.placet'\)]) Tj T* ( for placet in placets:) Tj T* ( lines = list\(open\(placet\)\)) Tj T* ( assert lines[0].startswith\('#!'\), 'Missing or incorrect shebang line!') Tj T* ( firstline = lines[0][2:] # strip the shebang) Tj T* ( main = plac.import_main\(*shlex.split\(firstline\)\)) Tj T* ( yield plac.Interpreter\(main\).doctest, lines[1:]) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 183.0642 cm -q -BT 1 0 0 1 0 62 Tm 1.44811 Tw 12 TL /F1 10 Tf 0 0 0 rg (Here you should notice that usage of ) Tj /F3 10 Tf (plac.import_main) Tj /F1 10 Tf (, an utility which is able to import the main) Tj T* 0 Tw .775703 Tw (function of the script specified in the shebang line. You can use both the full path name of the tool, or a) Tj T* 0 Tw .87686 Tw (relative path name. In this case the runner look at the environment variable ) Tj /F3 10 Tf (PLACPATH ) Tj /F1 10 Tf (and it searches) Tj T* 0 Tw 1.900651 Tw (the plac tool in the directories specified there \() Tj /F3 10 Tf (PLACPATH ) Tj /F1 10 Tf (is just a string containing directory names) Tj T* 0 Tw .56332 Tw (separated by colons\). If the variable ) Tj /F3 10 Tf (PLACPATH ) Tj /F1 10 Tf (is not defined, it just looks in the current directory. If the) Tj T* 0 Tw (plac tool is not found, an ) Tj /F3 10 Tf (ImportError ) Tj /F1 10 Tf (is raised.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 153.0642 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Plac batch scripts) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 111.0642 cm -q -BT 1 0 0 1 0 26 Tm .772093 Tw 12 TL /F1 10 Tf 0 0 0 rg (It is pretty easy to realize that an interactive interpreter can also be used to run batch scripts: instead of) Tj T* 0 Tw .504692 Tw (reading the commands from the console, it is enough to read the commands from a file. ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (interpreters) Tj T* 0 Tw (provide an ) Tj /F3 10 Tf (.execute ) Tj /F1 10 Tf (method to perform just that.) Tj T* ET -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (23) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R374': class PDFStream -374 0 obj -% page stream -<< /Length 4903 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 705.0236 cm -q -BT 1 0 0 1 0 50 Tm .098935 Tw 12 TL /F1 10 Tf 0 0 0 rg (There is just a subtle point to notice: whereas in an interactive loop one wants to manage all exceptions, a) Tj T* 0 Tw 3.866412 Tw (batch script should not in the background in case of unexpected errors. The implementation of) Tj T* 0 Tw .103059 Tw /F3 10 Tf (Interpreter.execute ) Tj /F1 10 Tf (makes sure that any error raised by ) Tj /F3 10 Tf (plac.call ) Tj /F1 10 Tf (internally is re-raised. In other) Tj T* 0 Tw .407045 Tw (words, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (interpreters ) Tj /F4 10 Tf (wrap the errors, but does not eat them) Tj /F1 10 Tf (: the errors are always accessible and can) Tj T* 0 Tw (be re-raised on demand.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 675.0236 cm -q -BT 1 0 0 1 0 14 Tm 1.239318 Tw 12 TL /F1 10 Tf 0 0 0 rg (The exception is the case of invalid commands, which are skipped. Consider for instance the following) Tj T* 0 Tw (batch file, which contains a mispelled command \() Tj /F3 10 Tf (.dl ) Tj /F1 10 Tf (instead of ) Tj /F3 10 Tf (.del) Tj /F1 10 Tf (\):) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 569.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 96 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 74 Tm /F3 10 Tf 12 TL (#!ishelve.py) Tj T* (.clear ) Tj T* (a=1 b=2) Tj T* (.show) Tj T* (.del a) Tj T* (.dl b) Tj T* (.show) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 537.8236 cm -q -BT 1 0 0 1 0 14 Tm 1.939461 Tw 12 TL /F1 10 Tf 0 0 0 rg (If you execute the batch file, the interpreter will print a ) Tj /F3 10 Tf (.dl: not found ) Tj /F1 10 Tf (at the ) Tj /F3 10 Tf (.dl ) Tj /F1 10 Tf (line and will) Tj T* 0 Tw (continue:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 312.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 216 re B* -Q -q -BT 1 0 0 1 0 194 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ python -c "import plac, ishelve) Tj T* (plac.Interpreter\(ishelve.main\).execute\(open\('ishelve.plac'\), verbose=True\)") Tj T* (i) Tj (>) Tj ( .clear) Tj T* (cleared the shelve) Tj T* (i) Tj (>) Tj ( a=1 b=2) Tj T* (setting a=1) Tj T* (setting b=2) Tj T* (i) Tj (>) Tj ( .show) Tj T* (b=2) Tj T* (a=1) Tj T* (i) Tj (>) Tj ( .del a) Tj T* (deleted a) Tj T* (i) Tj (>) Tj ( .dl b) Tj T* (2) Tj T* (.dl: not found) Tj T* (i) Tj (>) Tj ( .show) Tj T* (b=2) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 268.6236 cm -q -BT 1 0 0 1 0 26 Tm .159988 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F3 10 Tf (verbose ) Tj /F1 10 Tf (flag is there to show the lines which are being interpreted \(prefixed by ) Tj /F3 10 Tf (i) Tj (>) Tj /F1 10 Tf (\). This is done on) Tj T* 0 Tw 1.359988 Tw (purpose, so that you can cut and paste the output of the batch script and turn it into a ) Tj /F3 10 Tf (.placet ) Tj /F1 10 Tf (test) Tj T* 0 Tw (\(cool, isn't it?\).) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 238.6236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Implementing subcommands) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 196.6236 cm -q -BT 1 0 0 1 0 26 Tm 1.182485 Tw 12 TL /F1 10 Tf 0 0 0 rg (When I discussed the ) Tj /F3 10 Tf (ishelve ) Tj /F1 10 Tf (implementation in the ) Tj 0 0 .501961 rg (basic documentation) Tj 0 0 0 rg (, I said that it looked like a) Tj T* 0 Tw .116655 Tw (poor man implementation of an object system as a chain of elifs; I also said that ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (was able to do much) Tj T* 0 Tw (better than that. Here I will substantiate my claim.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 154.6236 cm -q -BT 1 0 0 1 0 26 Tm .89104 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is actually able to infer a set of subparsers from a generic container of commands. This is useful if) Tj T* 0 Tw 3.125814 Tw (you want to implement ) Tj /F4 10 Tf (subcommands ) Tj /F1 10 Tf (\(a familiar example of a command-line application featuring) Tj T* 0 Tw (subcommands is subversion\).) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 100.6236 cm -q -BT 1 0 0 1 0 38 Tm .015868 Tw 12 TL /F1 10 Tf 0 0 0 rg (Technically a container of commands is any object with a ) Tj /F3 10 Tf (.commands ) Tj /F1 10 Tf (attribute listing a set of functions or ) Tj T* 0 Tw 2.550888 Tw (methods which are valid commands. A command container may have initialization/finalization hooks ) Tj T* 0 Tw 2.55664 Tw (\() Tj /F3 10 Tf (__enter__/__exit__) Tj /F1 10 Tf (\) and dispatch hooks \() Tj /F3 10 Tf (__missing__) Tj /F1 10 Tf (, invoked for invalid command names\). ) Tj T* 0 Tw 2.113828 Tw (Moreover, only when using command containers ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is able to provide automatic autocompletion of) Tj T* 0 Tw ET -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (24) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R375': class PDFStream -375 0 obj -% page stream -<< /Length 3734 >> -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 2 Tm /F1 10 Tf 12 TL (commands.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 735.0236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (The shelve interface can be rewritten in an object-oriented way as follows:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 197.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 528 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 506 Tm /F3 10 Tf 12 TL (# ishelve2.py) Tj T* (import shelve, os, sys, plac) Tj T* T* (class ShelveInterface\(object\):) Tj T* ( "A minimal interface over a shelve object.") Tj T* ( commands = 'set', 'show', 'showall', 'delete') Tj T* ( @plac.annotations\() Tj T* ( configfile=\('path name of the shelve', 'option'\)\)) Tj T* ( def __init__\(self, configfile\):) Tj T* ( self.configfile = configfile or '~/conf.shelve') Tj T* ( self.fname = os.path.expanduser\(self.configfile\)) Tj T* ( self.__doc__ += '\\nOperating on %s.\\nUse help to see '\\) Tj T* ( 'the available commands.\\n' % self.fname) Tj T* ( def __enter__\(self\):) Tj T* ( self.sh = shelve.open\(self.fname\)) Tj T* ( return self) Tj T* ( def __exit__\(self, etype, exc, tb\):) Tj T* ( self.sh.close\(\)) Tj T* ( def set\(self, name, value\):) Tj T* ( "set name value") Tj T* ( yield 'setting %s=%s' % \(name, value\)) Tj T* ( self.sh[name] = value) Tj T* ( def show\(self, *names\):) Tj T* ( "show given parameters") Tj T* ( for name in names:) Tj T* ( yield '%s = %s' % \(name, self.sh[name]\) # no error checking) Tj T* ( def showall\(self\):) Tj T* ( "show all parameters") Tj T* ( for name in self.sh:) Tj T* ( yield '%s = %s' % \(name, self.sh[name]\)) Tj T* ( def delete\(self, name=None\):) Tj T* ( "delete given parameter \(or everything\)") Tj T* ( if name is None:) Tj T* ( yield 'deleting everything') Tj T* ( self.sh.clear\(\)) Tj T* ( else:) Tj T* ( yield 'deleting %s' % name) Tj T* ( del self.sh[name] # no error checking) Tj T* T* (main = ShelveInterface # useful for the tests) Tj T* T* (if __name__ == '__main__':) Tj T* ( plac.Interpreter.call\(ShelveInterface\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 105.8236 cm -q -BT 1 0 0 1 0 74 Tm .885366 Tw 12 TL /F3 10 Tf 0 0 0 rg (plac.Interpreter ) Tj /F1 10 Tf (objects wrap context manager objects consistently. In other words, if you wrap an) Tj T* 0 Tw 2.323828 Tw (object with ) Tj /F3 10 Tf (__enter__ ) Tj /F1 10 Tf (and ) Tj /F3 10 Tf (__exit__ ) Tj /F1 10 Tf (methods, they are invoked in the right order \() Tj /F3 10 Tf (__enter__) Tj T* 0 Tw .23528 Tw /F1 10 Tf (before the interpreter loop starts and ) Tj /F3 10 Tf (__exit__ ) Tj /F1 10 Tf (after the interpreter loop ends, both in the regular and in) Tj T* 0 Tw 1.916412 Tw (the exceptional case\). In our example, the methods ) Tj /F3 10 Tf (__enter__ ) Tj /F1 10 Tf (and ) Tj /F3 10 Tf (__exit__ ) Tj /F1 10 Tf (make sure the the) Tj T* 0 Tw .339398 Tw (shelve is opened and closed correctly even in the case of exceptions. Notice that I have not implemented) Tj T* 0 Tw .814104 Tw (any error checking in the ) Tj /F3 10 Tf (show ) Tj /F1 10 Tf (and ) Tj /F3 10 Tf (delete ) Tj /F1 10 Tf (methods on purpose, to verify that ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (works correctly in) Tj T* 0 Tw (the presence of exceptions.) Tj T* ET -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (25) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R376': class PDFStream -376 0 obj -% page stream -<< /Length 4389 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 693.0236 cm -q -BT 1 0 0 1 0 62 Tm 1.567126 Tw 12 TL /F1 10 Tf 0 0 0 rg (When working with command containers, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (automatically adds two special commands to the set of) Tj T* 0 Tw 2.099213 Tw (provided commands: ) Tj /F3 10 Tf (help ) Tj /F1 10 Tf (and ) Tj /F3 10 Tf (.last_tb) Tj /F1 10 Tf (. The ) Tj /F3 10 Tf (help ) Tj /F1 10 Tf (command is the easier to understand: when) Tj T* 0 Tw .39811 Tw (invoked without arguments it displays the list of available commands with the same formatting of the ) Tj 0 0 .501961 rg (cmd) Tj T* 0 Tw 1.19561 Tw 0 0 0 rg (module; when invoked with the name of a command it displays the usage message for that command.) Tj T* 0 Tw 2.33686 Tw (The ) Tj /F3 10 Tf (.last_tb ) Tj /F1 10 Tf (command is useful when debugging: in case of errors, it allows you to display the) Tj T* 0 Tw (traceback of the last executed command.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 675.0236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is a session of usage on an Unix-like operating system:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 276.0679 cm -q -q -.773863 0 0 .773863 0 0 cm -q -1 0 0 1 6.6 8.528639 cm -q -.662745 .662745 .662745 RG -.5 w -.960784 .960784 .862745 rg -n -6 -6 606 504 re B* -Q -q -BT 1 0 0 1 0 482 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ python ishelve2.py) Tj T* (A minimal interface over a shelve object.) Tj T* (Operating on /home/micheles/conf.shelve.) Tj T* (Use help to see the available commands.) Tj T* (i) Tj (>) Tj ( help) Tj T* T* (special commands) Tj T* (================) Tj T* (last_tb) Tj T* T* (custom commands) Tj T* (===============) Tj T* (delete set show showall) Tj T* T* (i) Tj (>) Tj ( delete) Tj T* (deleting everything) Tj T* (i) Tj (>) Tj ( set a pippo) Tj T* (setting a=pippo) Tj T* (i) Tj (>) Tj ( set b lippo) Tj T* (setting b=lippo) Tj T* (i) Tj (>) Tj ( showall) Tj T* (b = lippo) Tj T* (a = pippo) Tj T* (i) Tj (>) Tj ( show a b) Tj T* (a = pippo) Tj T* (b = lippo) Tj T* (i) Tj (>) Tj ( del a) Tj T* (deleting a) Tj T* (i) Tj (>) Tj ( showall) Tj T* (b = lippo) Tj T* (i) Tj (>) Tj ( delete a) Tj T* (deleting a) Tj T* (KeyError: 'a') Tj T* (i) Tj (>) Tj ( .last_tb) Tj T* ( File "/usr/local/lib/python2.6/dist-packages/plac-0.6.0-py2.6.egg/plac_ext.py", line 190, in _wrap) Tj T* ( for value in genobj:) Tj T* ( File "./ishelve2.py", line 37, in delete) Tj T* ( del self.sh[name] # no error checking) Tj T* ( File "/usr/lib/python2.6/shelve.py", line 136, in __delitem__) Tj T* ( del self.dict[key]) Tj T* (i) Tj (>) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 244.0679 cm -q -BT 1 0 0 1 0 14 Tm 2.571235 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that in interactive mode the traceback is hidden, unless you pass the ) Tj /F3 10 Tf (verbose ) Tj /F1 10 Tf (flag to the) Tj T* 0 Tw /F3 10 Tf (Interpreter.interact ) Tj /F1 10 Tf (method.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 142.0679 cm -q -BT 1 0 0 1 0 86 Tm .046098 Tw 12 TL /F1 10 Tf 0 0 0 rg (CHANGED IN VERSION 0.9: if you have an old version of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (the ) Tj /F3 10 Tf (help ) Tj /F1 10 Tf (command must be prefixed with) Tj T* 0 Tw 1.096303 Tw (a dot, i.e. you must write ) Tj /F3 10 Tf (.help) Tj /F1 10 Tf (. The old behavior was more consistent in my opinion, since it made it) Tj T* 0 Tw .416412 Tw (clear that the ) Tj /F3 10 Tf (help ) Tj /F1 10 Tf (command was special and threated differently from the regular commands. However) Tj T* 0 Tw .011654 Tw (many users complained against the dot, so I changed it to make them happy. Starting from release 0.9 the) Tj T* 0 Tw .077209 Tw /F3 10 Tf (help ) Tj /F1 10 Tf (command is just an alias for ) Tj /F3 10 Tf (--help) Tj /F1 10 Tf (, in the sense that there is a preprocessor step replacing ) Tj /F3 10 Tf (help) Tj T* 0 Tw 2.914985 Tw /F1 10 Tf (with ) Tj /F3 10 Tf (--help ) Tj /F1 10 Tf (in the argument list. Notice that if you implement a custom ) Tj /F3 10 Tf (help ) Tj /F1 10 Tf (command in the) Tj T* 0 Tw .299269 Tw (commander class the preprocessor will be automatically disabled: passing ) Tj /F3 10 Tf (help ) Tj /F1 10 Tf (will call the custom help) Tj T* 0 Tw (command, just as you would expect.) Tj T* ET -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (26) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R377': class PDFStream -377 0 obj -% page stream -<< /Length 4852 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 747.0236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (plac.Interpreter.call) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 705.0236 cm -q -BT 1 0 0 1 0 26 Tm .10104 Tw 12 TL /F1 10 Tf 0 0 0 rg (At the core of ) Tj /F3 10 Tf (plac ) Tj /F1 10 Tf (there is the ) Tj /F3 10 Tf (call ) Tj /F1 10 Tf (function which invokes a callable with the list of arguments passed) Tj T* 0 Tw 1.238443 Tw (at the command-line \() Tj /F3 10 Tf (sys.argv[1:]) Tj /F1 10 Tf (\). Thanks to ) Tj /F3 10 Tf (plac.call ) Tj /F1 10 Tf (you can launch your module by simply) Tj T* 0 Tw (adding the lines:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 659.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 36 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL (if __name__ == '__main__':) Tj T* ( plac.call\(main\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 603.8236 cm -q -BT 1 0 0 1 0 38 Tm .50436 Tw 12 TL /F1 10 Tf 0 0 0 rg (Everything works fine if ) Tj /F3 10 Tf (main ) Tj /F1 10 Tf (is a simple callable performing some action; however, in many cases, one) Tj T* 0 Tw .087633 Tw (has a ) Tj /F3 10 Tf (main ) Tj /F1 10 Tf ("function" which is a actually a factory returning a command container object. For instance, in) Tj T* 0 Tw .573318 Tw (my second shelve example the main function is the class ) Tj /F3 10 Tf (ShelveInterface) Tj /F1 10 Tf (, and the two lines needed) Tj T* 0 Tw (to run the module are a bit ugly:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 558.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 14 Tm /F3 10 Tf 12 TL (if __name__ == '__main__':) Tj T* ( plac.Interpreter\(plac.call\(ShelveInterface\)\).interact\(\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 454.6236 cm -q -BT 1 0 0 1 0 86 Tm .873988 Tw 12 TL /F1 10 Tf 0 0 0 rg (Moreover, now the program runs, but only in interactive mode, i.e. it is not possible to run it as a script.) Tj T* 0 Tw .097882 Tw (Instead, it would be nice to be able to specify the command to execute on the command-line and have the) Tj T* 0 Tw 4.08229 Tw (interpreter start, execute the command and finish properly \(I mean by calling ) Tj /F3 10 Tf (__enter__ ) Tj /F1 10 Tf (and) Tj T* 0 Tw .100574 Tw /F3 10 Tf (__exit__) Tj /F1 10 Tf (\) without needing user input. Then the script could be called from a batch shell script working in) Tj T* 0 Tw 2.26816 Tw (the background. In order to provide such functionality ) Tj /F3 10 Tf (plac.Interpreter ) Tj /F1 10 Tf (provides a classmethod) Tj T* 0 Tw 1.173318 Tw (named ) Tj /F3 10 Tf (.call ) Tj /F1 10 Tf (which takes the factory, instantiates it with the arguments read from the command line,) Tj T* 0 Tw 1.517045 Tw (wraps the resulting container object as an interpreter and runs it with the rest arguments found in the) Tj T* 0 Tw (command line. Here is the code to turn the ) Tj /F3 10 Tf (ShelveInterface ) Tj /F1 10 Tf (into a script) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 373.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 50 Tm /F3 10 Tf 12 TL (# ishelve3.py) Tj T* (from ishelve2 import ShelveInterface as main) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac; plac.Interpreter.call\(main\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 353.4236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (and here are a few examples of usage:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 140.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 204 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 182 Tm /F3 10 Tf 12 TL ($ python ishelve3.py -h) Tj T* (usage: ishelve3.py [-h] [-i] [-configfile CONFIGFILE] [args [args ...]]) Tj T* T* (positional arguments:) Tj T* ( args) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ( -i, --interact start interactive interpreter) Tj T* ( -configfile CONFIGFILE) Tj T* ( path name of the shelve) Tj T* T* ($ python ishelve3.py set a 1) Tj T* (setting a=1) Tj T* ($ python ishelve3.py show a) Tj T* (a = 1) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 108.2236 cm -q -BT 1 0 0 1 0 14 Tm .079989 Tw 12 TL /F1 10 Tf 0 0 0 rg (If you pass the ) Tj /F3 10 Tf (-i ) Tj /F1 10 Tf (flag in the command line, then the script will enter in interactive mode and ask the user) Tj T* 0 Tw (for the commands to execute:) Tj T* ET -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (27) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R378': class PDFStream -378 0 obj -% page stream -<< /Length 4660 >> -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 -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 84 re B* -Q -q -BT 1 0 0 1 0 62 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ python ishelve3.py -i) Tj T* (A minimal interface over a shelve object.) Tj T* (Operating on /home/micheles/conf.shelve.) Tj T* (Use help to see the available commands.) Tj T* T* (i) Tj (>) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 635.8236 cm -q -BT 1 0 0 1 0 26 Tm .221417 Tw 12 TL /F1 10 Tf 0 0 0 rg (In a sense, I have closed the circle: at the beginning of this document I discussed how to turn a script into) Tj T* 0 Tw .784147 Tw (an interactive application \(the ) Tj /F3 10 Tf (shelve_interpreter.py ) Tj /F1 10 Tf (example\), whereas here I have show how to) Tj T* 0 Tw (turn an interactive application into a script.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 617.8236 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (The complete signature of ) Tj /F3 10 Tf (plac.Interpreter.call ) Tj /F1 10 Tf (is the following:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 560.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 48 re B* -Q -q -BT 1 0 0 1 0 26 Tm 12 TL /F3 10 Tf 0 0 0 rg (call\(factory, arglist=sys.argv[1:],) Tj T* ( commentchar='#', split=shlex.split,) Tj T* ( stdin=sys.stdin, prompt='i) Tj (>) Tj ( ', verbose=False\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 480.6236 cm -q -BT 1 0 0 1 0 62 Tm 1.756651 Tw 12 TL /F1 10 Tf 0 0 0 rg (The factory must have a fixed number of positional arguments \(no default arguments, no varargs, no) Tj T* 0 Tw 1.87881 Tw (kwargs\), otherwise a ) Tj /F3 10 Tf (TypeError ) Tj /F1 10 Tf (is raised: the reason is that we want to be able to distinguish the) Tj T* 0 Tw .829984 Tw (command-line arguments needed to instantiate the factory from the rest arguments that must be sent to) Tj T* 0 Tw 2.609984 Tw (the corresponding interpreter object. It is also possible to specify a list of arguments different from) Tj T* 0 Tw .513318 Tw /F3 10 Tf (sys.argv[1:] ) Tj /F1 10 Tf (\(useful in tests\), the character to be recognized as a comment, the splitting function, the) Tj T* 0 Tw (input source and the prompt to use while in interactive mode, and a verbose flag.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 450.6236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Readline support) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 372.6236 cm -q -BT 1 0 0 1 0 62 Tm 1.022485 Tw 12 TL /F1 10 Tf 0 0 0 rg (Starting from release 0.6 ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (offers full readline support. That means that if your Python was compiled) Tj T* 0 Tw 2.120697 Tw (with readline support you get autocompletion and persistent command history for free. By default all) Tj T* 0 Tw .144104 Tw (commands are autocomplete in a case sensitive way. If you want to add new words to the autocompletion) Tj T* 0 Tw .116488 Tw (set, or you want to change the location of the ) Tj /F3 10 Tf (.history ) Tj /F1 10 Tf (file, or to change the case sensitivity, the way to) Tj T* 0 Tw .18436 Tw (go is to pass a ) Tj /F3 10 Tf (plac.ReadlineInput ) Tj /F1 10 Tf (object to the interpreter. Here is an example, assuming you want) Tj T* 0 Tw (to build a database interface understanding SQL commands:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 99.42362 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 264 re B* -Q -q -BT 1 0 0 1 0 242 Tm 12 TL /F3 10 Tf 0 0 0 rg (import os, plac) Tj T* (from sqlalchemy.ext.sqlsoup import SqlSoup) Tj T* T* (SQLKEYWORDS = set\(['help', 'select', 'from', ) Tj T* ( 'inner', 'join', 'outer', 'left', 'right']) Tj T* ( \) # and many others) Tj T* (DBTABLES = set\(['table1', 'table2']\) # you can read them from the db schema) Tj T* T* (COMPLETIONS = SQLKEYWORDS | DBTABLES) Tj T* T* (class SqlInterface\(object\):) Tj T* ( commands = ['SELECT']) Tj T* ( def __init__\(self, dsn\):) Tj T* ( self.soup = SqlSoup\(dsn\)) Tj T* ( def SELECT\(self, argstring\):) Tj T* ( sql = 'SELECT ' + argstring) Tj T* ( for row in self.soup.bind.execute\(sql\):) Tj T* ( yield str\(row\) # the formatting can be much improved) Tj T* T* (rl_input = plac.ReadlineInput\() Tj T* ( COMPLETIONS, histfile=os.path.expanduser\('~/.sql_interface.history'\), ) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (28) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R379': class PDFStream -379 0 obj -% page stream -<< /Length 4519 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 655.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 -BT 1 0 0 1 0 86 Tm 12 TL /F3 10 Tf 0 0 0 rg ( case_sensitive=False\)) Tj T* T* (def split_on_first_space\(line, commentchar\):) Tj T* ( return line.strip\(\).split\(' ', 1\) # ignoring comments) Tj T* ( ) Tj T* (if __name__ == '__main__':) Tj T* ( plac.Interpreter.call\(SqlInterface, split=split_on_first_space,) Tj T* ( stdin=rl_input, prompt='sql) Tj (>) Tj ( '\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 635.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is an example of usage:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 578.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 48 re B* -Q -q -BT 1 0 0 1 0 26 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ python sql_interface.py ) Tj (<) Tj (some dsn) Tj (>) Tj T* (sql) Tj (>) Tj ( SELECT a.* FROM TABLE1 AS a INNER JOIN TABLE2 AS b ON a.id = b.id) Tj T* (...) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 498.6236 cm -q -BT 1 0 0 1 0 62 Tm 1.951318 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can check that entering just ) Tj /F3 10 Tf (sel ) Tj /F1 10 Tf (and pressing TAB the readline library completes the ) Tj /F3 10 Tf (SELECT) Tj T* 0 Tw .797356 Tw /F1 10 Tf (keyword for you and makes it upper case; idem for ) Tj /F3 10 Tf (FROM) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (INNER) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (JOIN ) Tj /F1 10 Tf (and even for the names of the) Tj T* 0 Tw .256235 Tw (tables. An obvious improvement is to read the names of the tables by introspecting the database: actually) Tj T* 0 Tw 1.616654 Tw (you can even read the names of the views and of the columns, and have full autocompletion. All the) Tj T* 0 Tw 2.047251 Tw (entered commands and recorded and saved in the file ) Tj /F3 10 Tf (~/.sql_interface.history ) Tj /F1 10 Tf (when exiting) Tj T* 0 Tw (from the command-line interface.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 420.6236 cm -q -BT 1 0 0 1 0 62 Tm 2.010574 Tw 12 TL /F1 10 Tf 0 0 0 rg (If the readline library is not available, my suggestion is to use the ) Tj 0 0 .501961 rg (rlwrap ) Tj 0 0 0 rg (tool which provides similar) Tj T* 0 Tw .22561 Tw (features, at least on Unix-like platforms. ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (should also work fine on Windows with the ) Tj 0 0 .501961 rg (pyreadline ) Tj 0 0 0 rg (library) Tj T* 0 Tw .389989 Tw (\(I do not use Windows, so this part is very little tested: I tried it only once and it worked, but your mileage) Tj T* 0 Tw 2.206457 Tw (may vary\). For people worried about licenses, I will notice that ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (uses the readline library only if) Tj T* 0 Tw .591894 Tw (available, it does not include it and it does not rely on it in any fundamental way, so that the ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (licence) Tj T* 0 Tw (does not need to be the GPL \(actually it is a BSD do-whatever-you-want-with-it licence\).) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 378.6236 cm -q -BT 1 0 0 1 0 26 Tm .187882 Tw 12 TL /F1 10 Tf 0 0 0 rg (The interactive mode of ) Tj /F3 10 Tf (plac ) Tj /F1 10 Tf (can be used as a replacement of the ) Tj 0 0 .501961 rg (cmd ) Tj 0 0 0 rg (module in the standard library. It) Tj T* 0 Tw 3.130651 Tw (is actually better than ) Tj 0 0 .501961 rg (cmd) Tj 0 0 0 rg (: for instance, the ) Tj /F3 10 Tf (help ) Tj /F1 10 Tf (command is more powerful, since it provides) Tj T* 0 Tw (information about the arguments accepted by the given command:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 93.42362 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 276 re B* -Q -q -BT 1 0 0 1 0 254 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( help set) Tj T* (usage: set name value) Tj T* T* (set name value) Tj T* T* (positional arguments:) Tj T* ( name) Tj T* ( value) Tj T* T* (i) Tj (>) Tj ( help delete) Tj T* (usage: delete [name]) Tj T* T* (delete given parameter \(or everything\)) Tj T* T* (positional arguments:) Tj T* ( name [None]) Tj T* T* (i) Tj (>) Tj ( help show) Tj T* (usage: show [names [names ...]]) Tj T* T* (show given parameters) Tj T* T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (29) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R380': class PDFStream -380 0 obj -% page stream -<< /Length 5976 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 727.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 36 re B* -Q -q -BT 1 0 0 1 0 14 Tm 12 TL /F3 10 Tf 0 0 0 rg (positional arguments:) Tj T* ( names) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 671.8236 cm -q -BT 1 0 0 1 0 38 Tm 1.959985 Tw 12 TL /F1 10 Tf 0 0 0 rg (As you can imagine, the help message is provided by the underlying ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (subparser \(there is a) Tj T* 0 Tw 2.954524 Tw (subparser for each command\). ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (commands accept options, flags, varargs, keyword arguments,) Tj T* 0 Tw .719318 Tw (arguments with defaults, arguments with a fixed number of choices, type conversion and all the features) Tj T* 0 Tw (provided of ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (which should be reimplemented from scratch using ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 641.8236 cm -q -BT 1 0 0 1 0 14 Tm 1.78248 Tw 12 TL /F1 10 Tf 0 0 0 rg (Moreover at the moment ) Tj /F3 10 Tf (plac ) Tj /F1 10 Tf (also understands command abbreviations. However, this feature may) Tj T* 0 Tw (disappear in future releases. It was meaningful in the past, when ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (did not support readline.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 623.8236 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (Notice that if an abbreviation is ambiguous, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (warns you:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 578.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 -BT 1 0 0 1 0 14 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( sh) Tj T* (NameError: Ambiguous command 'sh': matching ['showall', 'show']) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 548.6236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (The plac runner) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 482.6236 cm -q -BT 1 0 0 1 0 50 Tm 1.531318 Tw 12 TL /F1 10 Tf 0 0 0 rg (The distribution of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (includes a runner script named ) Tj /F3 10 Tf (plac_runner.py) Tj /F1 10 Tf (, which will be installed in a) Tj T* 0 Tw .44748 Tw (suitable directory in your system by ) Tj 0 0 .501961 rg (distutils ) Tj 0 0 0 rg (\(say in ) Tj /F3 10 Tf (\\usr\\local\\bin\\plac_runner.py ) Tj /F1 10 Tf (in a Unix-like) Tj T* 0 Tw .680651 Tw (operative system\). The runner provides many facilities to run ) Tj /F3 10 Tf (.plac ) Tj /F1 10 Tf (scripts and ) Tj /F3 10 Tf (.placet ) Tj /F1 10 Tf (files, as well) Tj T* 0 Tw 1.47311 Tw (as Python modules containg a ) Tj /F3 10 Tf (main ) Tj /F1 10 Tf (object, which can be a function, a command container object or) Tj T* 0 Tw (even a command container class.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 452.6236 cm -q -BT 1 0 0 1 0 14 Tm 1.994269 Tw 12 TL /F1 10 Tf 0 0 0 rg (For instance, suppose you want to execute a script containing commands defined in the ) Tj /F3 10 Tf (ishelve2) Tj T* 0 Tw /F1 10 Tf (module like the following one:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 383.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 60 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 38 Tm /F3 10 Tf 12 TL (#!ishelve2.py:ShelveInterface -c ~/conf.shelve) Tj T* (set a 1) Tj T* (del a) Tj T* (del a # intentional error) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 315.4236 cm -q -BT 1 0 0 1 0 50 Tm .575868 Tw 12 TL /F1 10 Tf 0 0 0 rg (The first line of the ) Tj /F3 10 Tf (.plac ) Tj /F1 10 Tf (script contains the name of the python module containing the plac interpreter) Tj T* 0 Tw 2.327209 Tw (and the arguments which must be passed to its main function in order to be able to instantiate an) Tj T* 0 Tw .202485 Tw (interpreter object. In this case I appended ) Tj /F3 10 Tf (:ShelveInterface ) Tj /F1 10 Tf (to the name of the module to specify the) Tj T* 0 Tw 1.030574 Tw (object that must be imported: if not specified, by default the object named 'main' is imported. The other) Tj T* 0 Tw (lines contains commands. You can run the script as follows:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 226.2505 cm -q -q -.952737 0 0 .952737 0 0 cm -q -1 0 0 1 6.6 6.927412 cm -q -.662745 .662745 .662745 RG -.5 w -.960784 .960784 .862745 rg -n -6 -6 492 84 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 62 Tm /F3 10 Tf 12 TL ($ plac_runner.py --batch ishelve2.plac) Tj T* (setting a=1) Tj T* (deleting a) Tj T* (Traceback \(most recent call last\):) Tj T* ( ...) Tj T* (_bsddb.DBNotFoundError: \(-30988, 'DB_NOTFOUND: No matching key/data pair found'\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 194.2505 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL 2.79186 Tw (The last command intentionally contained an error, to show that the plac runner does not eat the) Tj T* 0 Tw (traceback.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 164.2505 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .437633 Tw (The runner can also be used to run Python modules in interactive mode and non-interactive mode. If you) Tj T* 0 Tw (put this alias in your bashrc) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 158.2505 cm -Q -q -1 0 0 1 62.69291 146.2505 cm -0 0 0 rg -BT /F1 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 2 Tm /F3 10 Tf 12 TL (alias plac="plac_runner.py") Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 146.2505 cm -Q -q -1 0 0 1 62.69291 116.2505 cm -q -BT 1 0 0 1 0 14 Tm 2.955318 Tw 12 TL /F1 10 Tf 0 0 0 rg (\(or you define a suitable ) Tj /F3 10 Tf (plac.bat ) Tj /F1 10 Tf (script in Windows\) you can run the ) Tj /F3 10 Tf (ishelve2.py ) Tj /F1 10 Tf (script in) Tj T* 0 Tw (interactive mode as follows:) Tj T* ET -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (30) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R381': class PDFStream -381 0 obj -% page stream -<< /Length 4819 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 595.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 168 re B* -Q -q -BT 1 0 0 1 0 146 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ plac -i ishelve2.py:ShelveInterface) Tj T* (A minimal interface over a shelve object.) Tj T* (Operating on /home/micheles/conf.shelve.) Tj T* (.help to see the available commands.) Tj T* T* (i) Tj (>) Tj ( del) Tj T* (deleting everything) Tj T* (i) Tj (>) Tj ( set a 1) Tj T* (setting a=1) Tj T* (i) Tj (>) Tj ( set b 2) Tj T* (setting b=2) Tj T* (i) Tj (>) Tj ( show b) Tj T* (b = 2) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 575.8236 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (Now you can cut and paste the interactive session an turns into into a ) Tj /F3 10 Tf (.placet ) Tj /F1 10 Tf (file like the following:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 446.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 120 re B* -Q -q -BT 1 0 0 1 0 98 Tm 12 TL /F3 10 Tf 0 0 0 rg (#!ishelve2.py:ShelveInterface -configfile=~/test.shelve) Tj T* (i) Tj (>) Tj ( del) Tj T* (deleting everything) Tj T* (i) Tj (>) Tj ( set a 1) Tj T* (setting a=1) Tj T* (i) Tj (>) Tj ( set b 2) Tj T* (setting b=2) Tj T* (i) Tj (>) Tj ( show a) Tj T* (a = 1) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 414.6236 cm -q -BT 1 0 0 1 0 14 Tm 2.145697 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that the first line specifies a test database ) Tj /F3 10 Tf (~/test.shelve) Tj /F1 10 Tf (, to avoid clobbering your default) Tj T* 0 Tw (shelve. If you mispell the arguments in the first line plac will give you an ) Tj 0 0 .501961 rg (argparse ) Tj 0 0 0 rg (error message \(just try\).) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 396.6236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (You can run placets following the shebang convention directly with the plac runner:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 351.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 14 Tm /F3 10 Tf 12 TL ($ plac --test ishelve2.placet) Tj T* (run 1 plac test\(s\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 307.4236 cm -q -BT 1 0 0 1 0 26 Tm .32104 Tw 12 TL /F1 10 Tf 0 0 0 rg (If you want to see the output of the tests, pass the ) Tj /F3 10 Tf (-v/--verbose ) Tj /F1 10 Tf (flag. Notice that he runner ignore the) Tj T* 0 Tw .24856 Tw (extension, so you can actually use any extension your like, but ) Tj /F4 10 Tf (it relies on the first line of the file to invoke) Tj T* 0 Tw (the corresponding plac tool with the given arguments) Tj /F1 10 Tf (.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 277.4236 cm -q -BT 1 0 0 1 0 14 Tm .537209 Tw 12 TL /F1 10 Tf 0 0 0 rg (The plac runner does not provide any test discovery facility, but you can use standard Unix tools to help.) Tj T* 0 Tw (For instance, you can run all the ) Tj /F3 10 Tf (.placet ) Tj /F1 10 Tf (files into a directory and its subdirectories as follows:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 244.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 24 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F3 10 Tf 12 TL ($ find . -name \\*.placet | xargs plac_runner.py -t) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 212.2236 cm -q -BT 1 0 0 1 0 14 Tm .760988 Tw 12 TL /F1 10 Tf 0 0 0 rg (The plac runner expects the main function of your script to return a plac tool, i.e. a function or an object) Tj T* 0 Tw (with a ) Tj /F3 10 Tf (.commands ) Tj /F1 10 Tf (attribute. It this is not the case the runner gracefully exits.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 194.2236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (It also works in non-interactive mode, if you call it as) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 188.2236 cm -Q -q -1 0 0 1 62.69291 176.2236 cm -0 0 0 rg -BT /F1 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 2 Tm /F3 10 Tf 12 TL ($ plac module.py args ...) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 176.2236 cm -Q -q -1 0 0 1 62.69291 158.2236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is an example:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 89.02362 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 38 Tm /F3 10 Tf 12 TL ($ plac ishelve.py a=1) Tj T* (setting a=1) Tj T* ($ plac ishelve.py .show) Tj T* (a=1) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (31) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R382': class PDFStream -382 0 obj -% page stream -<< /Length 3928 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 741.0236 cm -q -BT 1 0 0 1 0 14 Tm .01561 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that in non-interactive mode the runner just invokes ) Tj /F3 10 Tf (plac.call ) Tj /F1 10 Tf (on the ) Tj /F3 10 Tf (main ) Tj /F1 10 Tf (object of the Python) Tj T* 0 Tw (module.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 711.0236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (A non class-based example) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 669.0236 cm -q -BT 1 0 0 1 0 26 Tm .907209 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (does not force you to use classes to define command containers. Even a simple function can be a) Tj T* 0 Tw 1.796651 Tw (valid command container, it is enough to add to it a ) Tj /F3 10 Tf (.commands ) Tj /F1 10 Tf (attribute and possibly ) Tj /F3 10 Tf (__enter__) Tj T* 0 Tw /F1 10 Tf (and/or ) Tj /F3 10 Tf (__exit__ ) Tj /F1 10 Tf (attributes.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 639.0236 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .327485 Tw (In particular, a Python module is a perfect container of commands. As an example, consider the following) Tj T* 0 Tw (module implementing a fake Version Control System:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 257.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 372 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 350 Tm /F3 10 Tf 12 TL ("A Fake Version Control System") Tj T* T* (import plac) Tj T* T* (commands = 'checkout', 'commit', 'status') Tj T* T* (@plac.annotations\(url='url of the source code'\)) Tj T* (def checkout\(url\):) Tj T* ( "A fake checkout command") Tj T* ( return \('checkout ', url\)) Tj T* T* (@plac.annotations\(message=\('commit message', 'option'\)\)) Tj T* (def commit\(message\):) Tj T* ( "A fake commit command") Tj T* ( return \('commit ', message\)) Tj T* T* (@plac.annotations\(quiet=\('summary information', 'flag', 'q'\)\)) Tj T* (def status\(quiet\):) Tj T* ( "A fake status command") Tj T* ( return \('status ', quiet\)) Tj T* T* (def __missing__\(name\):) Tj T* ( return 'Command %r does not exist' % name) Tj T* T* (def __exit__\(etype, exc, tb\):) Tj T* ( "Will be called automatically at the end of the call/cmdloop") Tj T* ( if etype in \(None, GeneratorExit\): # success) Tj T* ( print\('ok'\)) Tj T* T* (main = __import__\(__name__\) # the module imports itself!) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 213.8236 cm -q -BT 1 0 0 1 0 26 Tm .431318 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that I have defined both an ) Tj /F3 10 Tf (__exit__ ) Tj /F1 10 Tf (hook and a ) Tj /F3 10 Tf (__missing__ ) Tj /F1 10 Tf (hook, invoked for non-existing) Tj T* 0 Tw .592651 Tw (commands. The real trick here is the line ) Tj /F3 10 Tf (main = __import__\(__name__\)) Tj /F1 10 Tf (, which define ) Tj /F3 10 Tf (main ) Tj /F1 10 Tf (to be) Tj T* 0 Tw (an alias for the current module.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 183.8236 cm -q -BT 1 0 0 1 0 14 Tm 1.259986 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F3 10 Tf (vcs ) Tj /F1 10 Tf (module does not contain an ) Tj /F3 10 Tf (if __name__ == '__main__' ) Tj /F1 10 Tf (block, but you can still run it) Tj T* 0 Tw (through the plac runner \(try ) Tj /F3 10 Tf (plac vcs.py -h) Tj /F1 10 Tf (\):) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 90.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 84 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 62 Tm /F3 10 Tf 12 TL (usage: plac_runner.py vcs.py [-h] {status,commit,checkout} ...) Tj T* T* (A Fake Version Control System) 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 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (32) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R383': class PDFStream -383 0 obj -% page stream -<< /Length 3250 >> -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 -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 84 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 62 Tm /F3 10 Tf 12 TL T* (subcommands:) Tj T* ( {status,commit,checkout}) Tj T* ( checkout A fake checkout command) Tj T* ( commit A fake commit command) Tj T* ( status A fake status command) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 659.8236 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (You can get help for the subcommands by postponing ) Tj /F3 10 Tf (-h ) Tj /F1 10 Tf (after the name of the command:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 542.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 108 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 86 Tm /F3 10 Tf 12 TL ($ plac vcs.py status -h) Tj T* (usage: vcs.py status [-h] [-q]) Tj T* T* (A fake status command) Tj T* T* (optional arguments:) Tj T* ( -h, --help show this help message and exit) Tj T* ( -q, --quiet summary information) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 510.6236 cm -q -BT 1 0 0 1 0 14 Tm 2.064985 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice how the docstring of the command is automatically shown in usage message, as well as the) Tj T* 0 Tw (documentation for the sub flag ) Tj /F3 10 Tf (-q) Tj /F1 10 Tf (.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 492.6236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is an example of a non-interactive session:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 363.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 98 Tm /F3 10 Tf 12 TL ($ plac vcs.py check url) Tj T* (checkout) Tj T* (url) Tj T* ($ plac vcs.py st -q) Tj T* (status) Tj T* (True) Tj T* ($ plac vcs.py co) Tj T* (commit) Tj T* (None) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 343.4236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (and here is an interactive session:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 142.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 192 re B* -Q -q -BT 1 0 0 1 0 170 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ plac -i vcs.py) Tj T* (usage: plac_runner.py vcs.py [-h] {status,commit,checkout} ...) Tj T* (i) Tj (>) Tj ( check url) Tj T* (checkout) Tj T* (url) Tj T* (i) Tj (>) Tj ( st -q) Tj T* (status) Tj T* (True) Tj T* (i) Tj (>) Tj ( co) Tj T* (commit) Tj T* (None) Tj T* (i) Tj (>) Tj ( sto) Tj T* (Command 'sto' does not exist) Tj T* (i) Tj (>) Tj ( [CTRL-D]) Tj T* (ok) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 110.2236 cm -q -BT 1 0 0 1 0 14 Tm 2.986905 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice the invocation of the ) Tj /F3 10 Tf (__missing__ ) Tj /F1 10 Tf (hook for non-existing commands. Notice also that the) Tj T* 0 Tw /F3 10 Tf (__exit__ ) Tj /F1 10 Tf (hook gets called only in interactive mode.) Tj T* ET -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (33) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R384': class PDFStream -384 0 obj -% page stream -<< /Length 5749 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 741.0236 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL 1.614104 Tw (If the commands are completely independent, a module is a good fit for a method container. In other) Tj T* 0 Tw (situations, it is best to use a custom class.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 711.0236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Writing your own plac runner) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 657.0236 cm -q -BT 1 0 0 1 0 38 Tm .167209 Tw 12 TL /F1 10 Tf 0 0 0 rg (The runner included in the ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (distribution is intentionally kept small \(around 50 lines of code\) so that you) Tj T* 0 Tw .081294 Tw (can study it and write your own runner if want to. If you need to go to such level of detail, you should know) Tj T* 0 Tw .42061 Tw (that the most important method of the ) Tj /F3 10 Tf (Interpreter ) Tj /F1 10 Tf (class is the ) Tj /F3 10 Tf (.send ) Tj /F1 10 Tf (method, which takes strings in) Tj T* 0 Tw (input and returns a four-tuple with attributes ) Tj /F3 10 Tf (.str) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (.etype) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (.exc ) Tj /F1 10 Tf (and ) Tj /F3 10 Tf (.tb) Tj /F1 10 Tf (:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 651.0236 cm -Q -q -1 0 0 1 62.69291 651.0236 cm -Q -q -1 0 0 1 62.69291 639.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 -3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (.str ) Tj /F1 10 Tf (is the output of the command, if successful \(a string\);) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 633.0236 cm -Q -q -1 0 0 1 62.69291 621.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 -3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (.etype ) Tj /F1 10 Tf (is the class of the exception, if the command fail;) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 615.0236 cm -Q -q -1 0 0 1 62.69291 603.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 -3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (.exc ) Tj /F1 10 Tf (is the exception instance;) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 597.0236 cm -Q -q -1 0 0 1 62.69291 585.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 -3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (.tb ) Tj /F1 10 Tf (is the traceback.) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 585.0236 cm -Q -q -1 0 0 1 62.69291 543.0236 cm -q -BT 1 0 0 1 0 26 Tm .937485 Tw 12 TL /F1 10 Tf 0 0 0 rg (Moreover the ) Tj /F3 10 Tf (__str__ ) Tj /F1 10 Tf (representation of the output object is redefined to return the output string if the) Tj T* 0 Tw 2.686651 Tw (command was successful or the error message if the command failed \(actually it returns the error) Tj T* 0 Tw (message preceded by the name of the exception class\).) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 525.0236 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (For instance, if you send a mispelled option to the interpreter a ) Tj /F3 10 Tf (SystemExit ) Tj /F1 10 Tf (will be trapped:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 431.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 84 re B* -Q -q -BT 1 0 0 1 0 62 Tm 12 TL /F3 10 Tf 0 0 0 rg (>) Tj (>) Tj (>) Tj ( import plac) Tj T* (>) Tj (>) Tj (>) Tj ( from ishelve import ishelve) Tj T* (>) Tj (>) Tj (>) Tj ( with plac.Interpreter\(ishelve\) as i:) Tj T* (... print\(i.send\('.cler'\)\)) Tj T* (...) Tj T* (SystemExit: unrecognized arguments: .cler) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 399.8236 cm -q -BT 1 0 0 1 0 14 Tm 2.90561 Tw 12 TL /F1 10 Tf 0 0 0 rg (It is important to invoke the ) Tj /F3 10 Tf (.send ) Tj /F1 10 Tf (method inside the context manager, otherwise you will get a) Tj T* 0 Tw /F3 10 Tf (RuntimeError) Tj /F1 10 Tf (.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 357.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 26 Tm /F1 10 Tf 12 TL .29311 Tw (For instance, suppose you want to implement a graphical runner for a plac-based interpreter with two text) Tj T* 0 Tw 1.548221 Tw (widgets: one to enter the commands and one to display the results. Suppose you want to display the) Tj T* 0 Tw (errors with tracebacks in red. You will need to code something like that \(pseudocode follows\):) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 120.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 228 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 206 Tm /F3 10 Tf 12 TL (input_widget = WidgetReadingInput\(\)) Tj T* (output_widget = WidgetDisplayingOutput\(\)) Tj T* T* (def send\(interpreter, line\):) Tj T* ( out = interpreter.send\(line\)) Tj T* ( if out.tb: # there was an error) Tj T* ( output_widget.display\(out.tb, color='red'\)) Tj T* ( else:) Tj T* ( output_widget.display\(out.str\)) Tj T* T* (main = plac.import_main\(tool_path\) # get the main object) Tj T* T* (with plac.Interpreter\(main\) as i:) Tj T* ( def callback\(event\):) Tj T* ( if event.user_pressed_ENTER\(\):) Tj T* ( send\(i, input_widget.last_line\)) Tj T* ( input_widget.addcallback\(callback\)) Tj T* ( gui_mainloop.start\(\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (34) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R385': class PDFStream -385 0 obj -% page stream -<< /Length 4478 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 741.0236 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .102765 Tw (You can adapt the pseudocode to your GUI toolkit of choice and you can also change the file associations) Tj T* 0 Tw (in such a way that clicking on a plac tool file the graphical user interface starts.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 711.0236 cm -q -BT 1 0 0 1 0 14 Tm .259988 Tw 12 TL /F1 10 Tf 0 0 0 rg (An example of GUI program built on top of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is given later on, in the paragraph ) Tj /F4 10 Tf (Managing the output of) Tj T* 0 Tw (concurrent commands ) Tj /F1 10 Tf (\(using Tkinter for simplicity and portability\).) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 657.0236 cm -q -BT 1 0 0 1 0 38 Tm 2.090651 Tw 12 TL /F1 10 Tf 0 0 0 rg (There is a final ) Tj /F4 10 Tf (caveat) Tj /F1 10 Tf (: since the plac interpreter loop is implemented via extended generators, plac) Tj T* 0 Tw .988651 Tw (interpreters are single threaded: you will get an error if you ) Tj /F3 10 Tf (.send ) Tj /F1 10 Tf (commands from separated threads.) Tj T* 0 Tw .947882 Tw (You can circumvent the problem by using a queue. If EXIT is a sentinel value to signal exiting from the) Tj T* 0 Tw (interpreter look, you can write code like this:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 599.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 48 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 26 Tm /F3 10 Tf 12 TL (with interpreter:) Tj T* ( for input_value in iter\(input_queue.get, EXIT\):) Tj T* ( output_queue.put\(interpreter.send\(input_value\)\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 567.8236 cm -q -BT 1 0 0 1 0 14 Tm .106098 Tw 12 TL /F1 10 Tf 0 0 0 rg (The same trick also work for processes; you could run the interpreter loop in a separate process and send) Tj T* 0 Tw (commands to it via the Queue class provided by the ) Tj 0 0 .501961 rg (multiprocessing ) Tj 0 0 0 rg (module.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 537.8236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Long running commands) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 495.8236 cm -q -BT 1 0 0 1 0 26 Tm 1.434431 Tw 12 TL /F1 10 Tf 0 0 0 rg (As we saw, by default a ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (interpreter blocks until the command terminates. This is an issue, in the) Tj T* 0 Tw 1.201318 Tw (sense that it makes the interactive experience quite painful for long running commands. An example is) Tj T* 0 Tw (better than a thousand words, so consider the following fake importer:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 234.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 252 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 230 Tm /F3 10 Tf 12 TL (import time) Tj T* (import plac) Tj T* T* (class FakeImporter\(object\):) Tj T* ( "A fake importer with an import_file command") Tj T* ( commands = ['import_file']) Tj T* ( def __init__\(self, dsn\):) Tj T* ( self.dsn = dsn) Tj T* ( def import_file\(self, fname\):) Tj T* ( "Import a file into the database") Tj T* ( try:) Tj T* ( for n in range\(10000\):) Tj T* ( time.sleep\(.01\)) Tj T* ( if n % 100 == 99:) Tj T* ( yield 'Imported %d lines' % \(n+1\)) Tj T* ( finally:) Tj T* ( print\('closing the file'\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( plac.Interpreter.call\(FakeImporter\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 202.6236 cm -q -BT 1 0 0 1 0 14 Tm 1.466457 Tw 12 TL /F1 10 Tf 0 0 0 rg (If you run the ) Tj /F3 10 Tf (import_file ) Tj /F1 10 Tf (command, you will have to wait for 200 seconds before entering a new) Tj T* 0 Tw (command:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 97.42362 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 96 re B* -Q -q -BT 1 0 0 1 0 74 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ python importer1.py dsn -i) Tj T* (A fake importer with an import_file command) Tj T* (i) Tj (>) Tj ( import_file file1) Tj T* (... ) Tj (<) Tj (wait 3+ minutes) Tj (>) Tj T* (Imported 100 lines) Tj T* (Imported 200 lines) Tj T* (Imported 300 lines) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (35) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R386': class PDFStream -386 0 obj -% page stream -<< /Length 5336 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 715.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 48 re B* -Q -q -BT 1 0 0 1 0 26 Tm 12 TL /F3 10 Tf 0 0 0 rg (...) Tj T* (Imported 10000 lines) Tj T* (closing the file) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 671.8236 cm -q -BT 1 0 0 1 0 26 Tm .96832 Tw 12 TL /F1 10 Tf 0 0 0 rg (Being unable to enter any other command is quite annoying: in such situation one would like to run the) Tj T* 0 Tw .941318 Tw (long running commands in the background, to keep the interface responsive. ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (provides two ways to) Tj T* 0 Tw (reach this goal: threads and processes.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 641.8236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Threaded commands) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 611.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .317988 Tw (The most familiar way to execute a task in the background \(even if not necessarily the best way\) is to run) Tj T* 0 Tw (it into a separated thread. In our example it is sufficient to replace the line) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 605.8236 cm -Q -q -1 0 0 1 62.69291 593.8236 cm -0 0 0 rg -BT /F1 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 2 Tm /F3 10 Tf 12 TL (commands = ['import_file']) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 593.8236 cm -Q -q -1 0 0 1 62.69291 575.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (with) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 569.8236 cm -Q -q -1 0 0 1 62.69291 557.8236 cm -0 0 0 rg -BT /F1 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 2 Tm /F3 10 Tf 12 TL (thcommands = ['import_file']) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 557.8236 cm -Q -q -1 0 0 1 62.69291 527.8236 cm -q -BT 1 0 0 1 0 14 Tm 1.38311 Tw 12 TL /F1 10 Tf 0 0 0 rg (to tell to the ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (interpreter that the command ) Tj /F3 10 Tf (import_file ) Tj /F1 10 Tf (should be run into a separated thread.) Tj T* 0 Tw (Here is an example session:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 482.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 -BT 1 0 0 1 0 14 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( import_file file1) Tj T* (<) Tj (ThreadedTask 1 [import_file file1] RUNNING) Tj (>) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 450.6236 cm -q -BT 1 0 0 1 0 14 Tm .595777 Tw 12 TL /F1 10 Tf 0 0 0 rg (The import task started in a separated thread. You can see the progress of the task by using the special) Tj T* 0 Tw (command ) Tj /F3 10 Tf (.output) Tj /F1 10 Tf (:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 381.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 60 re B* -Q -q -BT 1 0 0 1 0 38 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( .output 1) Tj T* (<) Tj (ThreadedTask 1 [import_file file1] RUNNING) Tj (>) Tj T* (Imported 100 lines) Tj T* (Imported 200 lines) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 361.4236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (If you look after a while, you will get more lines of output:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 268.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 84 re B* -Q -q -BT 1 0 0 1 0 62 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( .output 1) Tj T* (<) Tj (ThreadedTask 1 [import_file file1] RUNNING) Tj (>) Tj T* (Imported 100 lines) Tj T* (Imported 200 lines) Tj T* (Imported 300 lines) Tj T* (Imported 400 lines) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 248.2236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (If you look after a time long enough, the task will be finished:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 203.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 -BT 1 0 0 1 0 14 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( .output 1) Tj T* (<) Tj (ThreadedTask 1 [import_file file1] FINISHED) Tj (>) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 171.0236 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .819573 Tw (It is possible to store the output of a task into a file, to be read later \(this is useful for tasks with a large) Tj T* 0 Tw (output\):) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 125.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 36 re B* -Q -q -BT 1 0 0 1 0 14 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( .output 1 /tmp/out.txt) Tj T* (saved output of 1 into /tmp/out.txt) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 93.82362 cm -q -BT 1 0 0 1 0 14 Tm 1.045868 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can even skip the number argument: then ) Tj /F3 10 Tf (.output ) Tj /F1 10 Tf (will the return the output of the last launched) Tj T* 0 Tw (command \(the special commands like .output do not count\).) Tj T* ET -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (36) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R387': class PDFStream -387 0 obj -% page stream -<< /Length 4197 >> -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 2 Tm /F1 10 Tf 12 TL (You can launch many tasks one after the other:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 683.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 60 re B* -Q -q -BT 1 0 0 1 0 38 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( import_file file2) Tj T* (<) Tj (ThreadedTask 5 [import_file file2] RUNNING) Tj (>) Tj T* (i) Tj (>) Tj ( import_file file3) Tj T* (<) Tj (ThreadedTask 6 [import_file file3] RUNNING) Tj (>) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 663.8236 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F3 10 Tf (.list ) Tj /F1 10 Tf (command displays all the running tasks:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 606.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 48 re B* -Q -q -BT 1 0 0 1 0 26 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( .list) Tj T* (<) Tj (ThreadedTask 5 [import_file file2] RUNNING) Tj (>) Tj T* (<) Tj (ThreadedTask 6 [import_file file3] RUNNING) Tj (>) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 586.6236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (It is even possible to kill a task:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 493.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 84 re B* -Q -q -BT 1 0 0 1 0 62 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( .kill 5) Tj T* (<) Tj (ThreadedTask 5 [import_file file2] TOBEKILLED) Tj (>) Tj T* (# wait a bit ...) Tj T* (closing the file) Tj T* (i) Tj (>) Tj ( .output 5) Tj T* (<) Tj (ThreadedTask 5 [import_file file2] KILLED) Tj (>) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 401.4236 cm -q -BT 1 0 0 1 0 74 Tm 1.100542 Tw 12 TL /F1 10 Tf 0 0 0 rg (You should notice that since at the Python level it is impossible to kill a thread, the ) Tj /F3 10 Tf (.kill ) Tj /F1 10 Tf (commands) Tj T* 0 Tw 1.793984 Tw (works by setting the status of the task to ) Tj /F3 10 Tf (TOBEKILLED) Tj /F1 10 Tf (. Internally the generator corresponding to the) Tj T* 0 Tw .632927 Tw (command is executed in the thread and the status is checked at each iteration: when the status become) Tj T* 0 Tw 2.578443 Tw /F3 10 Tf (TOBEKILLED ) Tj /F1 10 Tf (a ) Tj /F3 10 Tf (GeneratorExit ) Tj /F1 10 Tf (exception is raised and the thread terminates \(softly, so that the) Tj T* 0 Tw 2.328651 Tw /F3 10 Tf (finally ) Tj /F1 10 Tf (clause is honored\). In our example the generator is yielding back control once every 100) Tj T* 0 Tw 1.152619 Tw (iterations, i.e. every two seconds \(not much\). In order to get a responsive interface it is a good idea to) Tj T* 0 Tw (yield more often, for instance every 10 iterations \(i.e. 5 times per second\), as in the following code:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 116.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 276 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 254 Tm /F3 10 Tf 12 TL (import time) Tj T* (import plac) Tj T* T* (class FakeImporter\(object\):) Tj T* ( "A fake importer with an import_file command") Tj T* ( thcommands = ['import_file']) Tj T* ( def __init__\(self, dsn\):) Tj T* ( self.dsn = dsn) Tj T* ( def import_file\(self, fname\):) Tj T* ( "Import a file into the database") Tj T* ( try:) Tj T* ( for n in range\(10000\):) Tj T* ( time.sleep\(.02\)) Tj T* ( if n % 100 == 99: # every two seconds) Tj T* ( yield 'Imported %d lines' % \(n+1\)) Tj T* ( if n % 10 == 9: # every 0.2 seconds) Tj T* ( yield # go back and check the TOBEKILLED status) Tj T* ( finally:) Tj T* ( print\('closing the file'\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( plac.Interpreter.call\(FakeImporter\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (37) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R388': class PDFStream -388 0 obj -% page stream -<< /Length 5986 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 747.0236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Running commands as external processes) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 693.0236 cm -q -BT 1 0 0 1 0 38 Tm 2.30686 Tw 12 TL /F1 10 Tf 0 0 0 rg (Threads are not loved much in the Python world and actually most people prefer to use processes) Tj T* 0 Tw 3.350697 Tw (instead. For this reason ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (provides the option to execute long running commands as external) Tj T* 0 Tw .632706 Tw (processes. Unfortunately the current implementation only works in Unix-like operating systems \(including) Tj T* 0 Tw (Mac OS X\) because it relies on fork via the ) Tj 0 0 .501961 rg (multiprocessing ) Tj 0 0 0 rg (module.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 675.0236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (In our example, to enable the feature it is sufficient to replace the line) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 669.0236 cm -Q -q -1 0 0 1 62.69291 657.0236 cm -0 0 0 rg -BT /F1 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 2 Tm /F3 10 Tf 12 TL (thcommands = ['import_file']) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 657.0236 cm -Q -q -1 0 0 1 62.69291 639.0236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (with) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 633.0236 cm -Q -q -1 0 0 1 62.69291 621.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -BT 1 0 0 1 0 2 Tm T* ET -q -1 0 0 1 20 0 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F3 10 Tf 0 0 0 rg (mpcommands = ['import_file']) Tj /F1 10 Tf (.) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 621.0236 cm -Q -q -1 0 0 1 62.69291 591.0236 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .772619 Tw (The user experience is exactly the same as with threads and you will not see any difference at the user) Tj T* 0 Tw (interface level:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 449.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 132 re B* -Q -q -BT 1 0 0 1 0 110 Tm 12 TL /F3 10 Tf 0 0 0 rg (i) Tj (>) Tj ( import_file file3) Tj T* (<) Tj (MPTask 1 [import_file file3] SUBMITTED) Tj (>) Tj T* (i) Tj (>) Tj ( .kill 1) Tj T* (<) Tj (MPTask 1 [import_file file3] RUNNING) Tj (>) Tj T* (closing the file) Tj T* (i) Tj (>) Tj ( .o 1) Tj T* (<) Tj (MPTask 1 [import_file file3] KILLED) Tj (>) Tj T* (Imported 100 lines) Tj T* (Imported 200 lines) Tj T* (i) Tj (>) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 393.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 38 Tm /F1 10 Tf 12 TL 1.201318 Tw (Still, using processes is quite different than using threads: in particular, when using processes you can) Tj T* 0 Tw 2.313318 Tw (only yield pickleable values and you cannot re-raise an exception first raised in a different process,) Tj T* 0 Tw 1.445697 Tw (because traceback objects are not pickleable. Moreover, you cannot rely on automatic sharing of your) Tj T* 0 Tw (objects.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 339.8236 cm -q -BT 1 0 0 1 0 38 Tm .128935 Tw 12 TL /F1 10 Tf 0 0 0 rg (On the plus side, when using processes you do not need to worry about killing a command: they are killed) Tj T* 0 Tw .466412 Tw (immediately using a SIGTERM signal, and there is not a ) Tj /F3 10 Tf (TOBEKILLED ) Tj /F1 10 Tf (mechanism. Moreover, the killing) Tj T* 0 Tw 1.50229 Tw (is guaranteed to be soft: internally a command receiving a SIGTERM raises a ) Tj /F3 10 Tf (TerminatedProcess) Tj T* 0 Tw /F1 10 Tf (exception which is trapped in the generator loop, so that the command is closed properly.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 309.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .242927 Tw (Using processes allows to take full advantage of multicore machines and it is safer than using threads, so) Tj T* 0 Tw (it is the recommended approach unless you are working on Windows.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 279.8236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Managing the output of concurrent commands) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 165.8236 cm -q -BT 1 0 0 1 0 98 Tm 1.895542 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (acts as a command-line task launcher and can be used as the base to build a GUI-based task) Tj T* 0 Tw .38561 Tw (launcher and task monitor. To this aim the interpreter class provides a ) Tj /F3 10 Tf (.submit ) Tj /F1 10 Tf (method which returns a) Tj T* 0 Tw 1.792339 Tw (task object and a ) Tj /F3 10 Tf (.tasks ) Tj /F1 10 Tf (method returning the list of all the tasks submitted to the interpreter. The) Tj T* 0 Tw .373516 Tw /F3 10 Tf (submit ) Tj /F1 10 Tf (method does not start the task and thus it is nonblocking. Each task has an ) Tj /F3 10 Tf (.outlist ) Tj /F1 10 Tf (attribute) Tj T* 0 Tw .106098 Tw (which is a list storing the value yielded by the generator underlying the task \(the ) Tj /F3 10 Tf (None ) Tj /F1 10 Tf (values are skipped) Tj T* 0 Tw .633318 Tw (though\): the ) Tj /F3 10 Tf (.outlist ) Tj /F1 10 Tf (grows as the task runs and more values are yielded. Accessing the ) Tj /F3 10 Tf (.outlist) Tj T* 0 Tw 1.051654 Tw /F1 10 Tf (is nonblocking and can be done freely. Finally there is a ) Tj /F3 10 Tf (.result ) Tj /F1 10 Tf (property which waits for the task to) Tj T* 0 Tw .830574 Tw (finish and returns the last yielded value or raises an exception. The code below provides an example of) Tj T* 0 Tw (how you could implement a GUI over the importer example:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 96.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 60 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 38 Tm /F3 10 Tf 12 TL (from __future__ import with_statement) Tj T* (from Tkinter import *) Tj T* (from importer3 import FakeImporter) Tj T* T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (38) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R389': class PDFStream -389 0 obj -% page stream -<< /Length 3716 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 439.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 324 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 302 Tm /F3 10 Tf 12 TL (def taskwidget\(root, task, tick=500\):) Tj T* ( "A Label widget showing the output of a task every 500 ms") Tj T* ( sv = StringVar\(root\)) Tj T* ( lb = Label\(root, textvariable=sv\)) Tj T* ( def show_outlist\(\):) Tj T* ( try:) Tj T* ( out = task.outlist[-1]) Tj T* ( except IndexError: # no output yet) Tj T* ( out = '') Tj T* ( sv.set\('%s %s' % \(task, out\)\)) Tj T* ( root.after\(tick, show_outlist\)) Tj T* ( root.after\(0, show_outlist\)) Tj T* ( return lb) Tj T* T* (def monitor\(tasks\):) Tj T* ( root = Tk\(\)) Tj T* ( for task in tasks:) Tj T* ( task.run\(\)) Tj T* ( taskwidget\(root, task\).pack\(\)) Tj T* ( root.mainloop\(\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac) Tj T* ( with plac.Interpreter\(plac.call\(FakeImporter\)\) as i:) Tj T* ( tasks = [i.submit\('import_file f1'\), i.submit\('import_file f2'\)]) Tj T* ( monitor\(tasks\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 409.8236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Monitor support) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 295.8236 cm -q -BT 1 0 0 1 0 98 Tm .493555 Tw 12 TL /F1 10 Tf 0 0 0 rg (Starting from release 0.8 ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (provides builtin support for monitoring the output of concurrent commands,) Tj T* 0 Tw 1.277318 Tw (at least for platforms where multiprocessing is fully supported. You can define your own monitor class,) Tj T* 0 Tw .839979 Tw (simply by inheriting from ) Tj /F3 10 Tf (plac.Monitor ) Tj /F1 10 Tf (and by overriding the methods ) Tj /F3 10 Tf (add_listener\(self, no\)) Tj /F1 10 Tf (,) Tj T* 0 Tw 2.953953 Tw /F3 10 Tf (del_listener\(self, taskno\)) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (notify_listener\(self, taskno, msg\)) Tj /F1 10 Tf (, ) Tj /F3 10 Tf (schedule\(self,) Tj T* 0 Tw .367674 Tw (seconds, func, arg\) ) Tj /F1 10 Tf (and ) Tj /F3 10 Tf (run\(self\)) Tj /F1 10 Tf (. Then, you can a monitor object to any ) Tj /F3 10 Tf (plac.Interpreter) Tj T* 0 Tw 1.149983 Tw /F1 10 Tf (object by simply calling the ) Tj /F3 10 Tf (add_monitor ) Tj /F1 10 Tf (method. For convenience, ) Tj /F3 10 Tf (plac ) Tj /F1 10 Tf (comes with a very simple) Tj T* 0 Tw .82683 Tw /F3 10 Tf (TkMonitor ) Tj /F1 10 Tf (based on Tkinter \(I chose Tkinter because it is easy to use and it is in the standard library,) Tj T* 0 Tw .08332 Tw (but you can use any GUI\): you can just look at how the ) Tj /F3 10 Tf (TkMonitor ) Tj /F1 10 Tf (is implemented in ) Tj /F3 10 Tf (plac_tk.py ) Tj /F1 10 Tf (and) Tj T* 0 Tw (adapt it. Here is an example of usage of the ) Tj /F3 10 Tf (TkMonitor) Tj /F1 10 Tf (:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 118.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 168 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 146 Tm /F3 10 Tf 12 TL (from __future__ import with_statement) Tj T* (import plac) Tj T* T* (class Hello\(object\):) Tj T* ( mpcommands = ['hello']) Tj T* ( def hello\(self\):) Tj T* ( yield 'hello') Tj T* T* (if __name__ == '__main__':) Tj T* ( i = plac.Interpreter\(Hello\(\)\)) Tj T* ( i.add_monitor\(plac.TkMonitor\('tkmon'\)\)) Tj T* ( with i:) Tj T* ( i.interact\(\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (39) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R390': class PDFStream -390 0 obj -% page stream -<< /Length 4914 >> -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 26 Tm 1.657633 Tw 12 TL /F1 10 Tf 0 0 0 rg (Try to give the ) Tj /F3 10 Tf (hello ) Tj /F1 10 Tf (command from the interactive interpreter: each time a new text widget will be) Tj T* 0 Tw 1.321235 Tw (added displaying the output of the command. Notice that if ) Tj /F3 10 Tf (Tkinter ) Tj /F1 10 Tf (is not installed correctly on your) Tj T* 0 Tw (system the ) Tj /F3 10 Tf (TkMonitor ) Tj /F1 10 Tf (class will not be available.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 699.0236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Parallel computing with plac) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 585.0236 cm -q -BT 1 0 0 1 0 98 Tm 1.174751 Tw 12 TL /F1 10 Tf 0 0 .501961 rg (plac ) Tj 0 0 0 rg (is certainly not intended as a tool for parallel computing, but still you can use it to launch a set of) Tj T* 0 Tw .497984 Tw (commands and to collect the results, similarly to the MapReduce pattern popularized by Google. In order) Tj T* 0 Tw .669431 Tw (to give an example, I will consider the "Hello World" of parallel computing, i.e. the computation of pi with) Tj T* 0 Tw .537633 Tw (independent processes. There is a huge number of algorithms to compute pi; here I will describe a trivial) Tj T* 0 Tw .101567 Tw (one chosen for simplicity, not per efficienty. The trick is to consider the first quadrant of a circle with radius) Tj T* 0 Tw .602488 Tw (1 and to extract a number of points ) Tj /F3 10 Tf (\(x, y\) ) Tj /F1 10 Tf (with ) Tj /F3 10 Tf (x ) Tj /F1 10 Tf (and ) Tj /F3 10 Tf (y ) Tj /F1 10 Tf (random variables in the interval ) Tj /F3 10 Tf ([0,1]) Tj /F1 10 Tf (. The) Tj T* 0 Tw .928876 Tw (probability of extracting a number inside the quadrant \(i.e. with ) Tj /F3 10 Tf (x^2 + y^2 < 1) Tj /F1 10 Tf (\) is proportional to the) Tj T* 0 Tw .433145 Tw (area of the quadrant \(i.e. ) Tj /F3 10 Tf (pi/4) Tj /F1 10 Tf (\). The value of ) Tj /F3 10 Tf (pi ) Tj /F1 10 Tf (therefore can be extracted by multiplying by 4 the ratio) Tj T* 0 Tw (between the number of points in the quadrant versus the total number of points ) Tj /F3 10 Tf (N) Tj /F1 10 Tf (, for ) Tj /F3 10 Tf (N ) Tj /F1 10 Tf (large:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 479.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 96 re B* -Q -q -BT 1 0 0 1 0 74 Tm 12 TL /F3 10 Tf 0 0 0 rg (def calc_pi\(N\):) Tj T* ( inside = 0) Tj T* ( for j in xrange\(N\):) Tj T* ( x, y = random\(\), random\(\)) Tj T* ( if x*x + y*y ) Tj (<) Tj ( 1:) Tj T* ( inside += 1) Tj T* ( return \(4.0 * inside\) / N) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 411.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 50 Tm /F1 10 Tf 12 TL .046654 Tw (The algorithm is trivially parallelizable: if you have n CPUs, you can compute pi n times with N/n iterations,) Tj T* 0 Tw 1.122488 Tw (sum the results and divide the total by n. I have a Macbook with two cores, therefore I would expect a) Tj T* 0 Tw 2.347984 Tw (speedup factor of 2 with respect to a sequential computation. Moreover, I would expect a threaded) Tj T* 0 Tw 2.827984 Tw (computation to be even slower than a sequential computation, due to the GIL and the scheduling) Tj T* 0 Tw (overhead.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 381.8236 cm -q -BT 1 0 0 1 0 14 Tm .313984 Tw 12 TL /F1 10 Tf 0 0 0 rg (Here is a script implementing the algorithm and working in three different modes \(parallel mode, threaded) Tj T* 0 Tw (mode and sequential mode\) depending on a ) Tj /F3 10 Tf (mode ) Tj /F1 10 Tf (option:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 96.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 276 re B* -Q -q -BT 1 0 0 1 0 254 Tm 12 TL /F3 10 Tf 0 0 0 rg (from __future__ import with_statement) Tj T* (from random import random) Tj T* (import multiprocessing) Tj T* (import plac) Tj T* T* (class PiCalculator\(object\):) Tj T* ( """Compute pi in parallel with threads or processes""") Tj T* ( ) Tj T* ( @plac.annotations\() Tj T* ( npoints=\('number of integration points', 'positional', None, int\),) Tj T* ( mode=\('sequential|parallel|threaded', 'option', 'm', str, 'SPT'\)\)) Tj T* ( def __init__\(self, npoints, mode='S'\):) Tj T* ( self.npoints = npoints) Tj T* ( if mode == 'P':) Tj T* ( self.mpcommands = ['calc_pi']) Tj T* ( elif mode == 'T':) Tj T* ( self.thcommands = ['calc_pi']) Tj T* ( elif mode == 'S':) Tj T* ( self.commands = ['calc_pi']) Tj T* ( self.n_cpu = multiprocessing.cpu_count\(\)) Tj T* ( ) Tj T* ( def submit_tasks\(self\):) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (40) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R391': class PDFStream -391 0 obj -% page stream -<< /Length 3511 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 259.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 504 re B* -Q -q -BT 1 0 0 1 0 482 Tm 12 TL /F3 10 Tf 0 0 0 rg ( self.i = plac.Interpreter\(self\).__enter__\(\) ) Tj T* ( return [self.i.submit\('calc_pi %d' % \(self.npoints / self.n_cpu\)\)) Tj T* ( for _ in range\(self.n_cpu\)]) Tj T* T* ( def close\(self\):) Tj T* ( self.i.close\(\)) Tj T* T* ( @plac.annotations\() Tj T* ( npoints=\('npoints', 'positional', None, int\)\)) Tj T* ( def calc_pi\(self, npoints\):) Tj T* ( counts = 0) Tj T* ( for j in xrange\(npoints\):) Tj T* ( n, r = divmod\(j, 1000000\)) Tj T* ( if r == 0:) Tj T* ( yield '%dM iterations' % n) Tj T* ( x, y = random\(\), random\(\)) Tj T* ( if x*x + y*y ) Tj (<) Tj ( 1:) Tj T* ( counts += 1) Tj T* ( yield \(4.0 * counts\)/npoints) Tj T* T* ( def run\(self\):) Tj T* ( tasks = self.i.tasks\(\)) Tj T* ( for t in tasks:) Tj T* ( t.run\(\)) Tj T* ( try:) Tj T* ( total = 0) Tj T* ( for task in tasks:) Tj T* ( total += task.result) Tj T* ( except: # the task was killed) Tj T* ( print tasks) Tj T* ( return) Tj T* ( return total / self.n_cpu) Tj T* T* (if __name__ == '__main__':) Tj T* ( pc = plac.call\(PiCalculator\)) Tj T* ( pc.submit_tasks\(\)) Tj T* ( try:) Tj T* ( import time; t0 = time.time\(\)) Tj T* ( print '%f in %f seconds ' % \(pc.run\(\), time.time\(\) - t0\)) Tj T* ( finally:) Tj T* ( pc.close\(\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 179.8236 cm -q -BT 1 0 0 1 0 62 Tm .381797 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice the ) Tj /F3 10 Tf (submit_tasks ) Tj /F1 10 Tf (method, which instantiates and initializes a ) Tj /F3 10 Tf (plac.Interpreter ) Tj /F1 10 Tf (object and) Tj T* 0 Tw 3.38152 Tw (submits a number of commands corresponding to the number of available CPUs. The ) Tj /F3 10 Tf (calc_pi) Tj T* 0 Tw 3.796651 Tw /F1 10 Tf (command yield a log message every million of interactions, just to monitor the progress of the) Tj T* 0 Tw 1.751318 Tw (computation. The ) Tj /F3 10 Tf (run ) Tj /F1 10 Tf (method starts all the submitted commands in parallel and sums the results. It) Tj T* 0 Tw 1.17104 Tw (returns the average value of ) Tj /F3 10 Tf (pi ) Tj /F1 10 Tf (after the slowest CPU has finished its job \(if the CPUs are equal and) Tj T* 0 Tw (equally busy they should finish more or less at the same time\).) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 161.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here are the results on my old Macbook with Ubuntu 10.04 and Python 2.6, for 10 million of iterations:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 92.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 60 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 38 Tm /F3 10 Tf 12 TL ($ python picalculator.py -mP 10000000 # two processes) Tj T* (3.141904 in 5.744545 seconds) Tj T* ($ python picalculator.py -mT 10000000 # two threads) Tj T* (3.141272 in 13.875645 seconds) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (41) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R392': class PDFStream -392 0 obj -% page stream -<< /Length 5158 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 727.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 36 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F3 10 Tf 12 TL ($ python picalculator.py -mS 10000000 # sequential) Tj T* (3.141586 in 11.353841 seconds) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 695.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL 1.711751 Tw (As you see using processes one gets a 2x speedup indeed, where the threaded mode is some 20%) Tj T* 0 Tw (slower than the sequential mode.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 629.8236 cm -q -BT 1 0 0 1 0 50 Tm .167765 Tw 12 TL /F1 10 Tf 0 0 0 rg (Since the pattern submit a bunch of tasks, starts them and collect the results is so common, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (provides) Tj T* 0 Tw 2.487251 Tw (an utility function ) Tj /F3 10 Tf (runp\(genseq, mode='p', monitors=\(\), start=True\) ) Tj /F1 10 Tf (to start a bunch a) Tj T* 0 Tw .598876 Tw (generators and return a list of task objects. By default ) Tj /F3 10 Tf (runp ) Tj /F1 10 Tf (use processes, but you can use threads by) Tj T* 0 Tw .225542 Tw (passing ) Tj /F3 10 Tf (mode='t') Tj /F1 10 Tf (. If you do not wont to start the tasks, you can say so \() Tj /F3 10 Tf (start=False) Tj /F1 10 Tf (\). With ) Tj /F3 10 Tf (runp ) Tj /F1 10 Tf (the) Tj T* 0 Tw (parallel pi calculation becomes a one-liner:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 597.2159 cm -q -q -.976496 0 0 .976496 0 0 cm -q -1 0 0 1 6.6 6.758862 cm -q -.662745 .662745 .662745 RG -.5 w -.960784 .960784 .862745 rg -n -6 -6 480 24 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F3 10 Tf 12 TL (sum\(task.result for task in plac.runp\(calc_pi\(N\) for i in range\(ncpus\)\)\)/ncpus) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 565.2159 cm -q -BT 1 0 0 1 0 14 Tm 1.05186 Tw 12 TL /F1 10 Tf 0 0 0 rg (The file ) Tj /F3 10 Tf (test_runp ) Tj /F1 10 Tf (in the ) Tj /F3 10 Tf (doc ) Tj /F1 10 Tf (directory of the plac distribution shows another couple of examples of) Tj T* 0 Tw (usage, including how to show the results of the running computation on a ) Tj /F3 10 Tf (TkMonitor) Tj /F1 10 Tf (.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 535.2159 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (The plac server) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 421.2159 cm -q -BT 1 0 0 1 0 98 Tm 1.258443 Tw 12 TL /F1 10 Tf 0 0 0 rg (A command-line oriented interface can be easily converted into a socket-based interface. Starting from) Tj T* 0 Tw .930574 Tw (release 0.7 plac features a builtin server which is able to accept commands from multiple clients and to) Tj T* 0 Tw .994692 Tw (execute them. The server works by instantiating a separate interpreter for each client, so that if a client) Tj T* 0 Tw 1.08784 Tw (interpreter dies for any reason the other interpreters keep working. To avoid external dependencies the) Tj T* 0 Tw .872209 Tw (server is based on the ) Tj /F3 10 Tf (asynchat ) Tj /F1 10 Tf (module in the standard library, but it would not be difficult to replace) Tj T* 0 Tw 2.386412 Tw (the server with a different one \(for instance, a Twisted server\). Since ) Tj /F3 10 Tf (asynchat) Tj /F1 10 Tf (-based servers are) Tj T* 0 Tw .755984 Tw (asynchronous, any blocking command in the interpreter should be run in a separated process or thread.) Tj T* 0 Tw 1.157633 Tw (The default port for the ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (server is 2199, and the command to signal end-of-connection is EOF. For) Tj T* 0 Tw (instance, here is how you could manage remote import on a database \(say a SQLite db\):) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 292.0159 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 98 Tm /F3 10 Tf 12 TL (import plac) Tj T* (from importer2 import FakeImporter) Tj T* T* (def main\(port=2199\):) Tj T* ( main = FakeImporter\('dsn'\)) Tj T* ( plac.Interpreter\(main\).start_server\(port\)) Tj T* ( ) Tj T* (if __name__ == '__main__':) Tj T* ( plac.call\(main\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 272.0159 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (You can connect to the server with ) Tj /F3 10 Tf (telnet ) Tj /F1 10 Tf (on port 2199, as follows:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 94.81593 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 168 re B* -Q -q -BT 1 0 0 1 0 146 Tm 12 TL /F3 10 Tf 0 0 0 rg ($ telnet localhost 2199) Tj T* (Trying ::1...) Tj T* (Trying 127.0.0.1...) Tj T* (Connected to localhost.) Tj T* (Escape character is '^]'.) Tj T* (i) Tj (>) Tj ( import_file f1) Tj T* (i) Tj (>) Tj ( .list) Tj T* (<) Tj (ThreadedTask 1 [import_file f1] RUNNING) Tj (>) Tj T* (i) Tj (>) Tj ( .out) Tj T* (Imported 100 lines) Tj T* (Imported 200 lines) Tj T* (i) Tj (>) Tj ( EOF) Tj T* (Connection closed by foreign host.) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (42) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R393': class PDFStream -393 0 obj -% page stream -<< /Length 6758 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 747.0236 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Summary) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 705.0236 cm -q -BT 1 0 0 1 0 26 Tm 2.203318 Tw 12 TL /F1 10 Tf 0 0 0 rg (Once ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (claimed to be the easiest command-line arguments parser in the world. Having read this) Tj T* 0 Tw .673322 Tw (document you may think that it is not so easy after all. But it is a false impression. Actually the rules are) Tj T* 0 Tw (quite simple:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 699.0236 cm -Q -q -1 0 0 1 62.69291 699.0236 cm -Q -q -1 0 0 1 62.69291 687.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 -3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (1.) Tj T* -5.66 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (if you want to implement a command-line script, use ) Tj /F3 10 Tf (plac.call) Tj /F1 10 Tf (;) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 681.0236 cm -Q -q -1 0 0 1 62.69291 633.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 33 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (2.) Tj T* -5.66 0 Td ET -Q -Q -q -1 0 0 1 23 33 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (if you want to implement a command interpreter, use ) Tj /F3 10 Tf (plac.Interpreter) Tj /F1 10 Tf (:) Tj T* ET -Q -Q -q -1 0 0 1 23 27 cm -Q -q -1 0 0 1 23 27 cm -Q -q -1 0 0 1 23 15 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 -3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (for an interactive interpreter, call the ) Tj /F3 10 Tf (.interact ) Tj /F1 10 Tf (method;) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 23 9 cm -Q -q -1 0 0 1 23 -3 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 -3 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 10.5 0 Td (\177) Tj T* -10.5 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (for an batch interpreter, call the ) Tj /F3 10 Tf (.execute ) Tj /F1 10 Tf (method;) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 23 -3 cm -Q -q -Q -Q -q -1 0 0 1 62.69291 627.0236 cm -Q -q -1 0 0 1 62.69291 603.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 9 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (3.) Tj T* -5.66 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 14 Tm 5.126647 Tw 12 TL /F1 10 Tf 0 0 0 rg (for testing call the ) Tj /F3 10 Tf (Interpreter.check ) Tj /F1 10 Tf (method in the appropriate context or use the) Tj T* 0 Tw /F3 10 Tf (Interpreter.doctest ) Tj /F1 10 Tf (feature;) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 597.0236 cm -Q -q -1 0 0 1 62.69291 573.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 9 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (4.) Tj T* -5.66 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 14 Tm 1.356457 Tw 12 TL /F1 10 Tf 0 0 0 rg (if you need to go at a lower level, you may need to call the ) Tj /F3 10 Tf (Interpreter.send ) Tj /F1 10 Tf (method which) Tj T* 0 Tw (returns a \(finished\) ) Tj /F3 10 Tf (Task ) Tj /F1 10 Tf (object.) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 567.0236 cm -Q -q -1 0 0 1 62.69291 543.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 9 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (5.) Tj T* -5.66 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 14 Tm 1.469269 Tw 12 TL /F1 10 Tf 0 0 0 rg (long running command can be executed in the background as threads or processes: just declare) Tj T* 0 Tw (them in the lists ) Tj /F3 10 Tf (thcommands ) Tj /F1 10 Tf (and ) Tj /F3 10 Tf (mpcommands ) Tj /F1 10 Tf (respectively.) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 537.0236 cm -Q -q -1 0 0 1 62.69291 513.0236 cm -0 0 0 rg -BT /F1 10 Tf 12 TL ET -q -1 0 0 1 6 9 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 5.66 0 Td (6.) Tj T* -5.66 0 Td ET -Q -Q -q -1 0 0 1 23 -3 cm -q -BT 1 0 0 1 0 14 Tm 2.171647 Tw 12 TL /F1 10 Tf 0 0 0 rg (the ) Tj /F3 10 Tf (.start_server ) Tj /F1 10 Tf (method starts an asynchronous server on the given port number \(default) Tj T* 0 Tw (2199\)) Tj T* ET -Q -Q -q -Q -Q -q -1 0 0 1 62.69291 513.0236 cm -Q -q -1 0 0 1 62.69291 495.0236 cm -q -BT 1 0 0 1 0 2 Tm 12 TL /F1 10 Tf 0 0 0 rg (Moreover, remember that ) Tj /F3 10 Tf (plac_runner.py ) Tj /F1 10 Tf (is your friend.) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 466.6772 cm -n 0 14.17323 m 469.8898 14.17323 l S -Q -q -1 0 0 1 62.69291 436.6772 cm -q -BT 1 0 0 1 0 3 Tm 18 TL /F2 15 Tf 0 0 0 rg (Appendix: custom annotation objects) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 406.6772 cm -q -BT 1 0 0 1 0 14 Tm .578651 Tw 12 TL /F1 10 Tf 0 0 0 rg (Internally ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (uses an ) Tj /F3 10 Tf (Annotation ) Tj /F1 10 Tf (class to convert the tuples 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 -q -1 0 0 1 62.69291 376.6772 cm -q -0 0 0 rg -BT 1 0 0 1 0 14 Tm /F1 10 Tf 12 TL .083735 Tw (Advanced users can implement their own annotation objects. For instance, here is an example of how you) Tj T* 0 Tw (could implement annotations for positional arguments:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 247.4772 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 98 Tm /F3 10 Tf 12 TL (# annotations.py) Tj T* (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 -1 0 0 1 62.69291 227.4772 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 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 98.27717 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 98 Tm /F3 10 Tf 12 TL (# example11.py) Tj T* (import plac) Tj T* (from annotations import Positional) Tj T* T* (@plac.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* ET -Q -Q -Q -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (43) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R394': class PDFStream -394 0 obj -% page stream -<< /Length 1709 >> -stream -1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET -q -1 0 0 1 62.69291 703.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 60 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 38 Tm /F3 10 Tf 12 TL ( print\(i, n, rest\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( import plac; plac.call\(main\)) Tj T* ET -Q -Q -Q -Q -Q -q -1 0 0 1 62.69291 683.8236 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (Here is the usage message you get:) Tj T* ET -Q -Q -q -1 0 0 1 62.69291 554.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 120 re B* -Q -q -0 0 0 rg -BT 1 0 0 1 0 98 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 510.6236 cm -q -BT 1 0 0 1 0 26 Tm .713516 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can go on and define ) Tj /F3 10 Tf (Option ) Tj /F1 10 Tf (and ) Tj /F3 10 Tf (Flag ) Tj /F1 10 Tf (classes, if you like. Using custom annotation objects you) Tj T* 0 Tw .17528 Tw (could do advanced things like extracting the annotations from a configuration file or from a database, but I) Tj T* 0 Tw (expect such use cases to be quite rare: the default mechanism should work pretty well for most users.) Tj T* ET -Q -Q -q -1 0 0 1 56.69291 56.69291 cm -q -0 0 0 rg -BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL 235.3849 0 Td (44) Tj T* -235.3849 0 Td ET -Q -Q - -endstream -endobj -% 'R395': class PDFPageLabels -395 0 obj -% Document Root -<< /Nums [ 0 - 396 0 R - 1 - 397 0 R - 2 - 398 0 R - 3 - 399 0 R - 4 - 400 0 R - 5 - 401 0 R - 6 - 402 0 R - 7 - 403 0 R - 8 - 404 0 R - 9 - 405 0 R - 10 - 406 0 R - 11 - 407 0 R - 12 - 408 0 R - 13 - 409 0 R - 14 - 410 0 R - 15 - 411 0 R - 16 - 412 0 R - 17 - 413 0 R - 18 - 414 0 R - 19 - 415 0 R - 20 - 416 0 R - 21 - 417 0 R - 22 - 418 0 R - 23 - 419 0 R - 24 - 420 0 R - 25 - 421 0 R - 26 - 422 0 R - 27 - 423 0 R - 28 - 424 0 R - 29 - 425 0 R - 30 - 426 0 R - 31 - 427 0 R - 32 - 428 0 R - 33 - 429 0 R - 34 - 430 0 R - 35 - 431 0 R - 36 - 432 0 R - 37 - 433 0 R - 38 - 434 0 R - 39 - 435 0 R - 40 - 436 0 R - 41 - 437 0 R - 42 - 438 0 R - 43 - 439 0 R ] >> -endobj -% 'R396': class PDFPageLabel -396 0 obj -% None -<< /S /D - /St 1 >> -endobj -% 'R397': class PDFPageLabel -397 0 obj -% None -<< /S /D - /St 2 >> -endobj -% 'R398': class PDFPageLabel -398 0 obj -% None -<< /S /D - /St 3 >> -endobj -% 'R399': class PDFPageLabel -399 0 obj -% None -<< /S /D - /St 4 >> -endobj -% 'R400': class PDFPageLabel -400 0 obj -% None -<< /S /D - /St 5 >> -endobj -% 'R401': class PDFPageLabel -401 0 obj -% None -<< /S /D - /St 6 >> -endobj -% 'R402': class PDFPageLabel -402 0 obj -% None -<< /S /D - /St 7 >> -endobj -% 'R403': class PDFPageLabel -403 0 obj -% None -<< /S /D - /St 8 >> -endobj -% 'R404': class PDFPageLabel -404 0 obj -% None -<< /S /D - /St 9 >> -endobj -% 'R405': class PDFPageLabel -405 0 obj -% None -<< /S /D - /St 10 >> -endobj -% 'R406': class PDFPageLabel -406 0 obj -% None -<< /S /D - /St 11 >> -endobj -% 'R407': class PDFPageLabel -407 0 obj -% None -<< /S /D - /St 12 >> -endobj -% 'R408': class PDFPageLabel -408 0 obj -% None -<< /S /D - /St 13 >> -endobj -% 'R409': class PDFPageLabel -409 0 obj -% None -<< /S /D - /St 14 >> -endobj -% 'R410': class PDFPageLabel -410 0 obj -% None -<< /S /D - /St 15 >> -endobj -% 'R411': class PDFPageLabel -411 0 obj -% None -<< /S /D - /St 16 >> -endobj -% 'R412': class PDFPageLabel -412 0 obj -% None -<< /S /D - /St 17 >> -endobj -% 'R413': class PDFPageLabel -413 0 obj -% None -<< /S /D - /St 18 >> -endobj -% 'R414': class PDFPageLabel -414 0 obj -% None -<< /S /D - /St 19 >> -endobj -% 'R415': class PDFPageLabel -415 0 obj -% None -<< /S /D - /St 20 >> -endobj -% 'R416': class PDFPageLabel -416 0 obj -% None -<< /S /D - /St 21 >> -endobj -% 'R417': class PDFPageLabel -417 0 obj -% None -<< /S /D - /St 22 >> -endobj -% 'R418': class PDFPageLabel -418 0 obj -% None -<< /S /D - /St 23 >> -endobj -% 'R419': class PDFPageLabel -419 0 obj -% None -<< /S /D - /St 24 >> -endobj -% 'R420': class PDFPageLabel -420 0 obj -% None -<< /S /D - /St 25 >> -endobj -% 'R421': class PDFPageLabel -421 0 obj -% None -<< /S /D - /St 26 >> -endobj -% 'R422': class PDFPageLabel -422 0 obj -% None -<< /S /D - /St 27 >> -endobj -% 'R423': class PDFPageLabel -423 0 obj -% None -<< /S /D - /St 28 >> -endobj -% 'R424': class PDFPageLabel -424 0 obj -% None -<< /S /D - /St 29 >> -endobj -% 'R425': class PDFPageLabel -425 0 obj -% None -<< /S /D - /St 30 >> -endobj -% 'R426': class PDFPageLabel -426 0 obj -% None -<< /S /D - /St 31 >> -endobj -% 'R427': class PDFPageLabel -427 0 obj -% None -<< /S /D - /St 32 >> -endobj -% 'R428': class PDFPageLabel -428 0 obj -% None -<< /S /D - /St 33 >> -endobj -% 'R429': class PDFPageLabel -429 0 obj -% None -<< /S /D - /St 34 >> -endobj -% 'R430': class PDFPageLabel -430 0 obj -% None -<< /S /D - /St 35 >> -endobj -% 'R431': class PDFPageLabel -431 0 obj -% None -<< /S /D - /St 36 >> -endobj -% 'R432': class PDFPageLabel -432 0 obj -% None -<< /S /D - /St 37 >> -endobj -% 'R433': class PDFPageLabel -433 0 obj -% None -<< /S /D - /St 38 >> -endobj -% 'R434': class PDFPageLabel -434 0 obj -% None -<< /S /D - /St 39 >> -endobj -% 'R435': class PDFPageLabel -435 0 obj -% None -<< /S /D - /St 40 >> -endobj -% 'R436': class PDFPageLabel -436 0 obj -% None -<< /S /D - /St 41 >> -endobj -% 'R437': class PDFPageLabel -437 0 obj -% None -<< /S /D - /St 42 >> -endobj -% 'R438': class PDFPageLabel -438 0 obj -% None -<< /S /D - /St 43 >> -endobj -% 'R439': class PDFPageLabel -439 0 obj -% None -<< /S /D - /St 44 >> -endobj -xref -0 440 -0000000000 65535 f -0000000113 00000 n -0000000246 00000 n -0000000411 00000 n -0000000598 00000 n -0000000850 00000 n -0000001100 00000 n -0000001349 00000 n -0000001508 00000 n -0000001840 00000 n -0000002080 00000 n -0000002321 00000 n -0000002563 00000 n -0000002805 00000 n -0000003047 00000 n -0000003290 00000 n -0000003534 00000 n -0000003778 00000 n -0000004022 00000 n -0000004266 00000 n -0000004510 00000 n -0000004754 00000 n -0000004998 00000 n -0000005242 00000 n -0000005486 00000 n -0000005730 00000 n -0000005974 00000 n -0000006218 00000 n -0000006462 00000 n -0000006706 00000 n -0000006950 00000 n -0000007194 00000 n -0000007438 00000 n -0000007682 00000 n -0000007926 00000 n -0000008170 00000 n -0000008414 00000 n -0000008658 00000 n -0000008902 00000 n -0000009146 00000 n -0000009390 00000 n -0000009634 00000 n -0000009878 00000 n -0000010122 00000 n -0000010366 00000 n -0000010610 00000 n -0000010854 00000 n -0000011098 00000 n -0000011342 00000 n -0000011586 00000 n -0000011830 00000 n -0000012074 00000 n -0000012318 00000 n -0000012562 00000 n -0000012806 00000 n -0000013050 00000 n -0000013294 00000 n -0000013538 00000 n -0000013782 00000 n -0000014026 00000 n -0000014270 00000 n -0000014514 00000 n -0000014758 00000 n -0000015002 00000 n -0000015246 00000 n -0000015490 00000 n -0000015734 00000 n -0000015978 00000 n -0000016222 00000 n -0000016466 00000 n -0000016710 00000 n -0000016954 00000 n -0000017198 00000 n -0000017442 00000 n -0000017686 00000 n -0000017930 00000 n -0000018174 00000 n -0000018418 00000 n -0000018662 00000 n -0000018906 00000 n -0000019150 00000 n -0000019378 00000 n -0000020334 00000 n -0000020596 00000 n -0000020859 00000 n -0000021109 00000 n -0000021358 00000 n -0000021624 00000 n -0000021876 00000 n -0000022126 00000 n -0000022378 00000 n -0000022628 00000 n -0000022880 00000 n -0000023130 00000 n -0000023382 00000 n -0000023633 00000 n -0000023872 00000 n -0000024052 00000 n -0000024487 00000 n -0000024748 00000 n -0000025012 00000 n -0000025262 00000 n -0000025515 00000 n -0000025768 00000 n -0000026020 00000 n -0000026271 00000 n -0000026524 00000 n -0000026762 00000 n -0000027160 00000 n -0000027411 00000 n -0000027665 00000 n -0000027919 00000 n -0000028157 00000 n -0000028508 00000 n -0000028762 00000 n -0000029016 00000 n -0000029290 00000 n -0000029631 00000 n -0000029885 00000 n -0000030175 00000 n -0000030465 00000 n -0000030719 00000 n -0000030957 00000 n -0000031302 00000 n -0000031601 00000 n -0000031855 00000 n -0000032093 00000 n -0000032424 00000 n -0000032678 00000 n -0000032932 00000 n -0000033184 00000 n -0000033436 00000 n -0000033688 00000 n -0000033942 00000 n -0000034179 00000 n -0000034545 00000 n -0000034844 00000 n -0000035098 00000 n -0000035346 00000 n -0000035610 00000 n -0000035951 00000 n -0000036205 00000 n -0000036443 00000 n -0000036774 00000 n -0000037013 00000 n -0000037334 00000 n -0000037586 00000 n -0000037825 00000 n -0000038156 00000 n -0000038409 00000 n -0000038661 00000 n -0000038913 00000 n -0000039165 00000 n -0000039419 00000 n -0000039671 00000 n -0000039923 00000 n -0000040177 00000 n -0000040431 00000 n -0000040683 00000 n -0000040922 00000 n -0000041343 00000 n -0000041595 00000 n -0000041848 00000 n -0000042102 00000 n -0000042385 00000 n -0000042639 00000 n -0000042893 00000 n -0000043175 00000 n -0000043458 00000 n -0000043712 00000 n -0000043964 00000 n -0000044282 00000 n -0000044536 00000 n -0000044825 00000 n -0000045062 00000 n -0000045513 00000 n -0000045765 00000 n -0000046019 00000 n -0000046273 00000 n -0000046527 00000 n -0000046786 00000 n -0000047043 00000 n -0000047304 00000 n -0000047556 00000 n -0000047810 00000 n -0000048068 00000 n -0000048320 00000 n -0000048578 00000 n -0000048832 00000 n -0000049085 00000 n -0000049346 00000 n -0000049600 00000 n -0000049854 00000 n -0000050108 00000 n -0000050367 00000 n -0000050621 00000 n -0000050873 00000 n -0000051126 00000 n -0000051378 00000 n -0000051631 00000 n -0000051885 00000 n -0000052137 00000 n -0000052389 00000 n -0000052642 00000 n -0000052896 00000 n -0000053150 00000 n -0000053404 00000 n -0000053658 00000 n -0000053941 00000 n -0000054194 00000 n -0000054484 00000 n -0000054738 00000 n -0000055049 00000 n -0000055301 00000 n -0000055538 00000 n -0000056239 00000 n -0000056528 00000 n -0000056780 00000 n -0000057032 00000 n -0000057290 00000 n -0000057544 00000 n -0000057798 00000 n -0000058052 00000 n -0000058306 00000 n -0000058560 00000 n -0000058814 00000 n -0000059059 00000 n -0000059313 00000 n -0000059567 00000 n -0000059821 00000 n -0000060075 00000 n -0000060325 00000 n -0000060781 00000 n -0000061080 00000 n -0000061319 00000 n -0000061640 00000 n -0000061894 00000 n -0000062133 00000 n -0000062464 00000 n -0000062718 00000 n -0000062980 00000 n -0000063234 00000 n -0000063497 00000 n -0000063750 00000 n -0000064013 00000 n -0000064267 00000 n -0000064521 00000 n -0000064759 00000 n -0000065160 00000 n -0000065413 00000 n -0000065678 00000 n -0000065932 00000 n -0000066186 00000 n -0000066425 00000 n -0000066786 00000 n -0000067025 00000 n -0000067346 00000 n -0000067600 00000 n -0000067861 00000 n -0000068097 00000 n -0000068423 00000 n -0000068722 00000 n -0000068961 00000 n -0000069282 00000 n -0000069540 00000 n -0000069794 00000 n -0000070062 00000 n -0000070316 00000 n -0000070570 00000 n -0000070831 00000 n -0000071077 00000 n -0000071458 00000 n -0000071710 00000 n -0000071963 00000 n -0000072215 00000 n -0000072469 00000 n -0000072723 00000 n -0000072977 00000 n -0000073231 00000 n -0000073471 00000 n -0000073862 00000 n -0000074099 00000 n -0000074420 00000 n -0000074659 00000 n -0000074965 00000 n -0000075264 00000 n -0000075502 00000 n -0000075823 00000 n -0000076077 00000 n -0000076350 00000 n -0000076589 00000 n -0000076930 00000 n -0000077184 00000 n -0000077423 00000 n -0000077739 00000 n -0000078038 00000 n -0000078292 00000 n -0000078565 00000 n -0000078804 00000 n -0000079145 00000 n -0000079384 00000 n -0000079705 00000 n -0000079944 00000 n -0000080250 00000 n -0000080549 00000 n -0000080803 00000 n -0000081042 00000 n -0000081373 00000 n -0000081627 00000 n -0000081866 00000 n -0000082182 00000 n -0000082467 00000 n -0000082631 00000 n -0000082881 00000 n -0000083010 00000 n -0000083264 00000 n -0000083461 00000 n -0000083675 00000 n -0000083889 00000 n -0000084115 00000 n -0000084317 00000 n -0000084526 00000 n -0000084723 00000 n -0000084926 00000 n -0000085127 00000 n -0000085345 00000 n -0000085546 00000 n -0000085760 00000 n -0000085955 00000 n -0000086153 00000 n -0000086389 00000 n -0000086569 00000 n -0000086793 00000 n -0000087003 00000 n -0000087202 00000 n -0000087404 00000 n -0000087612 00000 n -0000087817 00000 n -0000088017 00000 n -0000088216 00000 n -0000088426 00000 n -0000088639 00000 n -0000088845 00000 n -0000089047 00000 n -0000089270 00000 n -0000089497 00000 n -0000089697 00000 n -0000089910 00000 n -0000090110 00000 n -0000090302 00000 n -0000090487 00000 n -0000091025 00000 n -0000094059 00000 n -0000103789 00000 n -0000109846 00000 n -0000114110 00000 n -0000118422 00000 n -0000122396 00000 n -0000127676 00000 n -0000131835 00000 n -0000136678 00000 n -0000143033 00000 n -0000146830 00000 n -0000151468 00000 n -0000155384 00000 n -0000159558 00000 n -0000165727 00000 n -0000171766 00000 n -0000179481 00000 n -0000188078 00000 n -0000194864 00000 n -0000198677 00000 n -0000203639 00000 n -0000209507 00000 n -0000215979 00000 n -0000220983 00000 n -0000224818 00000 n -0000229308 00000 n -0000234261 00000 n -0000239022 00000 n -0000243642 00000 n -0000249719 00000 n -0000254639 00000 n -0000258668 00000 n -0000262019 00000 n -0000267869 00000 n -0000272448 00000 n -0000277885 00000 n -0000282183 00000 n -0000288270 00000 n -0000292087 00000 n -0000297102 00000 n -0000300714 00000 n -0000305973 00000 n -0000312832 00000 n -0000314646 00000 n -0000315378 00000 n -0000315457 00000 n -0000315536 00000 n -0000315615 00000 n -0000315694 00000 n -0000315773 00000 n -0000315852 00000 n -0000315931 00000 n -0000316010 00000 n -0000316089 00000 n -0000316169 00000 n -0000316249 00000 n -0000316329 00000 n -0000316409 00000 n -0000316489 00000 n -0000316569 00000 n -0000316649 00000 n -0000316729 00000 n -0000316809 00000 n -0000316889 00000 n -0000316969 00000 n -0000317049 00000 n -0000317129 00000 n -0000317209 00000 n -0000317289 00000 n -0000317369 00000 n -0000317449 00000 n -0000317529 00000 n -0000317609 00000 n -0000317689 00000 n -0000317769 00000 n -0000317849 00000 n -0000317929 00000 n -0000318009 00000 n -0000318089 00000 n -0000318169 00000 n -0000318249 00000 n -0000318329 00000 n -0000318409 00000 n -0000318489 00000 n -0000318569 00000 n -0000318649 00000 n -0000318729 00000 n -0000318809 00000 n -trailer -<< /ID - % ReportLab generated PDF document -- digest (http://www.reportlab.com) - [(@\241\272\314$\341\035g\323\330\247\312\366\021\262\304) (@\241\272\314$\341\035g\323\330\247\312\366\021\262\304)] - - /Info 312 0 R - /Root 311 0 R - /Size 440 >> -startxref -318858 -%%EOF diff --git a/plac/doc/plac.txt b/plac/doc/plac.txt deleted file mode 100644 index e7083f8..0000000 --- a/plac/doc/plac.txt +++ /dev/null @@ -1,2 +0,0 @@ -.. include:: plac_core.txt -.. include:: plac_adv.txt diff --git a/plac/doc/plac_adv.txt b/plac/doc/plac_adv.txt deleted file mode 100644 index 936f4af..0000000 --- a/plac/doc/plac_adv.txt +++ /dev/null @@ -1,1266 +0,0 @@ -Advanced usages of plac -========================================================= - -Introduction ------------------------------------------------------ - -One of the design goals of plac_ is to make it dead easy to write a -scriptable and testable interface for an application. You can use -plac_ whenever you have an API with strings in input and strings in -output, and that includes a *huge* domain of applications. - -A string-oriented interface is a scriptable interface by -construction. That means that you can define a command language for -your application and that it is possible to write scripts which are -interpretable by plac_ and can be run as batch scripts. - -Actually, at the most general level, you can see plac_ as a generic tool to -write domain specific languages (DSL). With plac_ you -can test your application interactively as well as with batch -scripts, and even with the analogous of Python doctests for your -defined language. - -You can easily replace the ``cmd`` module of the standard library and -you could easily write an application like twill_ with plac_. Or you -could use it to script your building procedure. plac_ also supports -parallel execution of multiple commands and can be used as -task manager and monitor. It is also quite easy to build a GUI -or a Web application on top of plac_. When speaking of things -you can do with plac_, your imagination is the only limit! - -From scripts to interactive applications ------------------------------------------------------------- - -Command-line scripts have many advantages, but they are no substitute -for interactive applications. -In particular, if you have a script with a large startup time which must be run -multiple times, it is best to turn it into an interactive application, -so that the startup is performed only once. ``plac`` provides an -``Interpreter`` class just for this purpose. - -The ``Interpreter`` class wraps the main function of a script and -provides an ``.interact`` method to start an interactive interpreter -reading commands from the console. - -For instance, you can define an interactive interpreter on top of the -``ishelve`` script introduced in the `basic documentation`_ as -follows: - -.. include:: shelve_interpreter.py - :literal: - -A trick has been used here: the ishelve command-line interface has been -hidden inside an external interface. They are distinct: for instance -the external interface recognizes the ``-h/--help`` flag whereas the -internal interface only recognizes the ``.help`` command:: - - $ python shelve_interpreter.py -h - -.. include:: shelve_interpreter.help - :literal: - -Thanks to this ingenuous trick, the script can be run both interactively -and non-interactively:: - - $ python shelve_interpreter.py .clear # non-interactive use - cleared the shelve - -Here is an usage session:: - - $ python shelve_interpreter.py -i # interactive use - A simple interface to a shelve. Use .help to see the available commands. - i> .help - Commands: .help, .showall, .clear, .delete - ... - ... - i> a=1 - setting a=1 - i> a - 1 - i> b=2 - setting b=2 - i> a b - 1 - 2 - i> .del a - deleted a - i> a - a: not found - i> .show - b=2 - i> [CTRL-D] - -The ``.interact`` method -reads commands from the console and send them to the -underlying interpreter, until the user send a CTRL-D -command (CTRL-Z in Windows). There is a default -argument ``prompt='i> '`` which -can be used to change the prompt. The text displayed at the beginning -of the interactive session is the docstring of the main function. -``plac`` also understands command abbreviations: in this example -``del`` is an abbreviation for ``delete``. In case of ambiguous -abbreviations plac_ raises a ``NameError``. - -Finally I must notice that the ``plac.Interpreter`` is available only if you -are using a recent version of Python (>= 2.5), because it is a context -manager object which uses extended generators internally. - -Testing a plac application ------------------------------------------------------------ - -You can conveniently test your application in interactive mode. -However manual testing is a poor substitute for automatic testing. - -In principle, one could write automatic tests for the -``ishelve`` application by using ``plac.call`` directly: - -.. include:: test_ishelve.py - :literal: - -However, using ``plac.call`` is not especially nice. The big -issue is that ``plac.call`` responds to invalid input by printing an -error message on stderr and by raising a ``SystemExit``: this is -certainly not a nice thing to do in a test. - -As a consequence of this behavior it is impossible to test for invalid -commands, unless you wrap the ``SystemExit`` exception by -hand each time (a possibly you do something with the error message in -stderr too). Luckily, ``plac`` offers a better testing support through -the ``check`` method of ``Interpreter`` objects: - -.. include:: test_ishelve_more.py - :literal: - -The method ``.check(given_input, expected_output)`` works on strings -and raises an ``AssertionError`` if the output produced by the -interpreter is different from the expected output for the given input. -Notice that ``AssertionError`` is catched by tools like ``py.test`` and -``nosetests`` and actually ``plac`` tests are intended to be run with -such tools. - -Interpreters offer a minor syntactic advantage with respect to calling -``plac.call`` directly, but they offer a *major* semantic advantage when things -go wrong (read exceptions): an ``Interpreter`` object internally invokes -something like ``plac.call``, but it wraps all exceptions, so that ``i.check`` -is guaranteed not to raise any exception except ``AssertionError``. - -Even the ``SystemExit`` exception is captured and you can write your test as - - ``i.check('-cler', 'SystemExit: unrecognized arguments: -cler')`` - -without risk of exiting from the Python interpreter. - -There is a second advantage of interpreters: if the main function contains some -initialization code and finalization code -(``__enter__`` and ``__exit__`` functions) they will be run only -once at the beginning and at the end of the interpreter loop. -``plac.call`` instead ignores the initialization/finalization code. - -Plac easy tests ---------------------------------------------------------- - -Writing your tests in terms of ``Interpreter.check`` is certainly an -improvement over writing them in terms of ``plac.call``, but they -are still too low-level for my taste. The ``Interpreter`` class provides -support for doctest-style tests, a.k.a. *plac easy tests*. - -By using plac easy tests you can cut and paste your interactive session and -turn it into a runnable automatics test. -Consider for instance the following file ``ishelve.placet`` (the ``.placet`` -extension is a mnemonic for plac easy tests): - -.. include:: ishelve.placet - :literal: - -Notice the precence of the shebang line containing the name of the -plac_ tool to test (a plac_ tool is just a Python module with a -function called ``main``). The shebang is ignored by the interpreter -(it looks like a comment to it) but it is there so that external -tools (say a test runner) can infer the plac interpreter -to use to test the file. - -You can test ``ishelve.placet`` file by calling the -``.doctest`` method of the interpreter, as in this example:: - - $ python -c"import plac, ishelve - plac.Interpreter(ishelve.main).doctest(open('ishelve.placet'), verbose=True)" - -Internally ``Interpreter.doctests`` invokes something like ``Interpreter.check`` -multiple times inside the same context and compare the output with the -expected output: if even a check fails, the whole test fail. - -You should realize tha the easy tests supported by ``plac`` are *not* -unittests: they are functional tests. They model then user interaction and the -order of the operations generally matters. The single subtests in a -``.placet`` file are not independent and it makes sense to exit -immediately at the first failure. - -The support for doctests in plac_ comes nearly for free, thanks to the -shlex_ module in the standard library, which is able to parse simple -languages as the ones you can implement with plac_. In particular, -thanks to shlex_, plac_ is able to recognize comments (the default -comment character is ``#``), escape sequences and more. Look at the -shlex_ documentation if you need to customize how the language is -interpreted. For more flexibility, it is even possible to pass to the -interpreter a custom split function with signature ``split(line, -commentchar)``. - -In addition, I have implemented from scratch some support for line number -recognition, so that if a test fail you get the line number of the -failing command. This is especially useful if your tests are -stored in external files, even if plac easy tests does not need to be in -a file: you can just pass to the ``.doctest`` method a list of -strings corresponding to the lines of the file. - -At the present plac_ does not use any code from the doctest -module, but the situation may change in the future (it would be nice -if plac_ could reuse doctests directives like ELLIPSIS). - -It is straighforward to integrate your ``.placet`` tests with standard -testing tools. For instance, you can integrate your doctests with ``nose`` -or ``py.test`` as follow:: - - import os, shlex, plac - - def test_doct(): - """ - Find all the doctests in the current directory and run them with the - corresponding plac interpreter (the shebang rules!) - """ - placets = [f for f in os.listdir('.') if f.endswith('.placet')] - for placet in placets: - lines = list(open(placet)) - assert lines[0].startswith('#!'), 'Missing or incorrect shebang line!' - firstline = lines[0][2:] # strip the shebang - main = plac.import_main(*shlex.split(firstline)) - yield plac.Interpreter(main).doctest, lines[1:] - -Here you should notice that usage of ``plac.import_main``, an utility -which is able to import the main function of the script specified in -the shebang line. You can use both the full path name of the -tool, or a relative path name. In this case the runner look at the -environment variable ``PLACPATH`` and it searches -the plac tool in the directories specified there (``PLACPATH`` is just -a string containing directory names separated by colons). If the variable -``PLACPATH`` is not defined, it just looks in the current directory. -If the plac tool is not found, an ``ImportError`` is raised. - -Plac batch scripts --------------------------------------------------- - -It is pretty easy to realize that an interactive interpreter can -also be used to run batch scripts: instead of reading the commands from -the console, it is enough to read the commands from a file. -plac_ interpreters provide an ``.execute`` method to perform just that. - -There is just a subtle point to notice: whereas in an interactive loop -one wants to manage all exceptions, a batch script should not in the -background in case of unexpected errors. The implementation of -``Interpreter.execute`` makes sure that any error raised by -``plac.call`` internally is re-raised. In other words, plac_ -interpreters *wrap the errors, but does not eat them*: the errors are -always accessible and can be re-raised on demand. - -The exception is the case of invalid commands, which are skipped. -Consider for instance the following batch file, which contains a -mispelled command (``.dl`` instead of ``.del``): - -.. include:: ishelve.plac - :literal: - -If you execute the batch file, the interpreter will print a ``.dl: not found`` -at the ``.dl`` line and will continue:: - - $ python -c "import plac, ishelve - plac.Interpreter(ishelve.main).execute(open('ishelve.plac'), verbose=True)" - i> .clear - cleared the shelve - i> a=1 b=2 - setting a=1 - setting b=2 - i> .show - b=2 - a=1 - i> .del a - deleted a - i> .dl b - 2 - .dl: not found - i> .show - b=2 - -The ``verbose`` flag is there to show the lines which are being interpreted -(prefixed by ``i>``). This is done on purpose, so that you can cut and paste -the output of the batch script and turn it into a ``.placet`` test -(cool, isn't it?). - -Implementing subcommands ----------------------------------------- - -When I discussed the ``ishelve`` implementation in the `basic -documentation`_, I said that it looked like a poor man implementation -of an object system as a chain of elifs; I also said that plac_ was -able to do much better than that. Here I will substantiate my claim. - -plac_ is actually able to infer a set of subparsers from a -generic container of commands. This is useful if you want to -implement *subcommands* (a familiar example of a command-line -application featuring subcommands is subversion). - -Technically a container of commands is any object with a ``.commands`` attribute -listing a set of functions or methods which are valid commands. A command -container may have initialization/finalization hooks (``__enter__/__exit__``) -and dispatch hooks (``__missing__``, invoked for invalid command names). -Moreover, only when using command containers plac_ is able to provide -automatic autocompletion of commands. - -The shelve interface can be rewritten in an object-oriented way as follows: - -.. include:: ishelve2.py - :literal: - -``plac.Interpreter`` objects wrap context manager objects -consistently. In other words, if you wrap an object with -``__enter__`` and ``__exit__`` methods, they are invoked in the right -order (``__enter__`` before the interpreter loop starts and -``__exit__`` after the interpreter loop ends, both in the regular and -in the exceptional case). In our example, the methods ``__enter__`` -and ``__exit__`` make sure the the shelve is opened and closed -correctly even in the case of exceptions. Notice that I have not -implemented any error checking in the ``show`` and ``delete`` methods -on purpose, to verify that plac_ works correctly in the presence of -exceptions. - -When working with command containers, plac_ automatically adds two -special commands to the set of provided commands: ``help`` -and ``.last_tb``. The ``help`` command is the easier to understand: -when invoked without arguments it displays the list of available commands -with the same formatting of the cmd_ module; when invoked with the name of -a command it displays the usage message for that command. -The ``.last_tb`` command is useful when debugging: in case of errors, -it allows you to display the traceback of the last executed command. - -Here is a session of usage on an Unix-like operating system:: - - $ python ishelve2.py - A minimal interface over a shelve object. - Operating on /home/micheles/conf.shelve. - Use help to see the available commands. - i> help - - special commands - ================ - last_tb - - custom commands - =============== - delete set show showall - - i> delete - deleting everything - i> set a pippo - setting a=pippo - i> set b lippo - setting b=lippo - i> showall - b = lippo - a = pippo - i> show a b - a = pippo - b = lippo - i> del a - deleting a - i> showall - b = lippo - i> delete a - deleting a - KeyError: 'a' - i> .last_tb - File "/usr/local/lib/python2.6/dist-packages/plac-0.6.0-py2.6.egg/plac_ext.py", line 190, in _wrap - for value in genobj: - File "./ishelve2.py", line 37, in delete - del self.sh[name] # no error checking - File "/usr/lib/python2.6/shelve.py", line 136, in __delitem__ - del self.dict[key] - i> - -Notice that in interactive mode the traceback is hidden, unless -you pass the ``verbose`` flag to the ``Interpreter.interact`` method. - -CHANGED IN VERSION 0.9: if you have an old version of plac_ the -``help`` command must be prefixed with a dot, i.e. you must write -``.help``. The old behavior was more consistent in my opinion, since -it made it clear that the ``help`` command was special and threated -differently from the regular commands. However many users complained against -the dot, so I changed it to make them happy. Starting from release 0.9 -the ``help`` command is just an alias for ``--help``, in the -sense that there is a preprocessor step replacing ``help`` with ``--help`` -in the argument list. -Notice that if you implement a custom ``help`` command in the commander class -the preprocessor will be automatically disabled: passing ``help`` -will call the custom help command, just as you would expect. - -plac.Interpreter.call --------------------------------------------- - -At the core of ``plac`` there is the ``call`` function which invokes -a callable with the list of arguments passed at the command-line -(``sys.argv[1:]``). Thanks to ``plac.call`` you can launch your module -by simply adding the lines:: - - if __name__ == '__main__': - plac.call(main) - -Everything works fine if ``main`` is a simple callable performing some -action; however, in many cases, one has a ``main`` "function" which -is a actually a factory returning a command container object. For -instance, in my second shelve example the main function is the class -``ShelveInterface``, and the two lines needed to run the module are -a bit ugly:: - - if __name__ == '__main__': - plac.Interpreter(plac.call(ShelveInterface)).interact() - -Moreover, now the program runs, but only in interactive mode, i.e. -it is not possible to run it as a script. Instead, it would be nice -to be able to specify the command to execute on the command-line -and have the interpreter start, execute the command and finish -properly (I mean by calling ``__enter__`` and ``__exit__``) -without needing user input. Then the script could be called from -a batch shell script working in the background. -In order to provide such functionality ``plac.Interpreter`` provides -a classmethod named ``.call`` which takes the factory, instantiates -it with the arguments read from the command line, wraps the resulting -container object as an interpreter and runs it with the rest arguments -found in the command line. Here is the code to turn the ``ShelveInterface`` -into a script - -.. include:: ishelve3.py - :literal: - -and here are a few examples of usage:: - - $ python ishelve3.py -h - usage: ishelve3.py [-h] [-i] [-configfile CONFIGFILE] [args [args ...]] - - positional arguments: - args - - optional arguments: - -h, --help show this help message and exit - -i, --interact start interactive interpreter - -configfile CONFIGFILE - path name of the shelve - - $ python ishelve3.py set a 1 - setting a=1 - $ python ishelve3.py show a - a = 1 - -If you pass the ``-i`` flag in the command line, then the -script will enter in interactive mode and ask the user -for the commands to execute:: - - $ python ishelve3.py -i - A minimal interface over a shelve object. - Operating on /home/micheles/conf.shelve. - Use help to see the available commands. - - i> - -In a sense, I have closed the circle: at the beginning of this -document I discussed how to turn a script into an interactive -application (the ``shelve_interpreter.py`` example), whereas here I -have show how to turn an interactive application into a script. - -The complete signature of ``plac.Interpreter.call`` is the following:: - - call(factory, arglist=sys.argv[1:], - commentchar='#', split=shlex.split, - stdin=sys.stdin, prompt='i> ', verbose=False) - -The factory must have a fixed number of positional arguments (no -default arguments, no varargs, no kwargs), otherwise a ``TypeError`` -is raised: the reason is that we want to be able to distinguish the -command-line arguments needed to instantiate the factory from the rest -arguments that must be sent to the corresponding interpreter object. -It is also possible to specify a list of arguments different from -``sys.argv[1:]`` (useful in tests), the character to be recognized as -a comment, the splitting function, the input source and the prompt to -use while in interactive mode, and a verbose flag. - -Readline support ---------------------------------------- - -Starting from release 0.6 plac_ offers full readline support. That -means that if your Python was compiled with readline support you get -autocompletion and persistent command history for free. By default -all commands are autocomplete in a case sensitive way. If you want to -add new words to the autocompletion set, or you want to change the -location of the ``.history`` file, or to change the case sensitivity, -the way to go is to pass a ``plac.ReadlineInput`` object to the -interpreter. Here is an example, assuming you want to build a -database interface understanding SQL commands: - -.. include:: sql_interface.py - :literal: - -Here is an example of usage:: - - $ python sql_interface.py - sql> SELECT a.* FROM TABLE1 AS a INNER JOIN TABLE2 AS b ON a.id = b.id - ... - -You can check that entering just ``sel`` and pressing TAB the readline library -completes the ``SELECT`` keyword for you and makes it upper case; idem for -``FROM``, ``INNER``, ``JOIN`` and even for the names of the tables. An -obvious improvement is to read the names of the tables by introspecting -the database: actually you can even read the names of the views and of -the columns, and have full autocompletion. All the entered commands -and recorded and saved in the file ``~/.sql_interface.history`` when -exiting from the command-line interface. - -If the readline library is not available, my suggestion is to use the -rlwrap_ tool which provides similar features, at least on Unix-like -platforms. plac_ should also work fine on Windows with the pyreadline_ -library (I do not use Windows, so this part is very little tested: I -tried it only once and it worked, but your mileage may vary). -For people worried about licenses, I will notice that plac_ uses the -readline library only if available, it does not include it and it does -not rely on it in any fundamental way, so that the plac_ licence does -not need to be the GPL (actually it is a BSD -do-whatever-you-want-with-it licence). - -The interactive mode of ``plac`` can be used as a replacement of the -cmd_ module in the standard library. It is actually better than cmd_: -for instance, the ``help`` command is more powerful, since it -provides information about the arguments accepted by the given command:: - - i> help set - usage: set name value - - set name value - - positional arguments: - name - value - - i> help delete - usage: delete [name] - - delete given parameter (or everything) - - positional arguments: - name [None] - - i> help show - usage: show [names [names ...]] - - show given parameters - - positional arguments: - names - -As you can imagine, the help message is provided by the underlying argparse_ -subparser (there is a subparser for each command). plac_ commands accept -options, flags, varargs, keyword arguments, arguments with defaults, -arguments with a fixed number of choices, type conversion and all the -features provided of argparse_ which should be reimplemented from scratch -using plac_. - -Moreover at the moment ``plac`` also understands command abbreviations. -However, this feature may disappear in -future releases. It was meaningful in the past, when plac_ did not support -readline. - -Notice that if an abbreviation is ambiguous, plac_ warns you:: - - i> sh - NameError: Ambiguous command 'sh': matching ['showall', 'show'] - -The plac runner --------------------------------------------------------- - -The distribution of plac_ includes a runner script named ``plac_runner.py``, -which will be installed in a suitable directory in your system by distutils_ -(say in ``\usr\local\bin\plac_runner.py`` in a Unix-like operative system). -The runner provides many facilities to run ``.plac`` scripts and -``.placet`` files, as well as Python modules containg a ``main`` -object, which can be a function, a command container object or -even a command container class. - -For instance, suppose you want to execute a script containing commands -defined in the ``ishelve2`` module like the following one: - -.. include:: ishelve2.plac - :literal: - -The first line of the ``.plac`` script contains the name of the -python module containing the plac interpreter and the arguments -which must be passed to its main function in order to be able -to instantiate an interpreter object. In this case I appended -``:ShelveInterface`` to the name of the module to specify the -object that must be imported: if not specified, by default the -object named 'main' is imported. -The other lines contains commands. -You can run the script as follows:: - - $ plac_runner.py --batch ishelve2.plac - setting a=1 - deleting a - Traceback (most recent call last): - ... - _bsddb.DBNotFoundError: (-30988, 'DB_NOTFOUND: No matching key/data pair found') - -The last command intentionally contained an error, to show that the -plac runner does not eat the traceback. - -The runner can also be used to run Python modules in interactive -mode and non-interactive mode. If you put this alias in your bashrc - - ``alias plac="plac_runner.py"`` - -(or you define a suitable ``plac.bat`` script in Windows) you can -run the ``ishelve2.py`` script in interactive mode as -follows:: - - $ plac -i ishelve2.py:ShelveInterface - A minimal interface over a shelve object. - Operating on /home/micheles/conf.shelve. - .help to see the available commands. - - i> del - deleting everything - i> set a 1 - setting a=1 - i> set b 2 - setting b=2 - i> show b - b = 2 - -Now you can cut and paste the interactive session an turns into into -a ``.placet`` file like the following: - -.. include:: ishelve2.placet - :literal: - -Notice that the first line specifies a test database -``~/test.shelve``, to avoid clobbering your default shelve. If you -mispell the arguments in the first line plac will give you an -argparse_ error message (just try). - -You can run placets following the shebang convention directly with -the plac runner:: - - $ plac --test ishelve2.placet - run 1 plac test(s) - -If you want to see the output of the tests, pass the ``-v/--verbose`` flag. -Notice that he runner ignore the extension, so you can actually use any -extension your like, but *it relies on the first line of the file to invoke -the corresponding plac tool with the given arguments*. - -The plac runner does not provide any test discovery facility, -but you can use standard Unix tools to help. For instance, you can -run all the ``.placet`` files into a directory and its subdirectories -as follows:: - - $ find . -name \*.placet | xargs plac_runner.py -t - -The plac runner expects the main function of your script to -return a plac tool, i.e. a function or an object with a ``.commands`` -attribute. It this is not the case the runner gracefully exits. - -It also works in non-interactive mode, if you call it as - - ``$ plac module.py args ...`` - -Here is an example:: - - $ plac ishelve.py a=1 - setting a=1 - $ plac ishelve.py .show - a=1 - -Notice that in non-interactive mode the runner just invokes ``plac.call`` -on the ``main`` object of the Python module. - -A non class-based example --------------------------------------------------------- - -plac_ does not force you to use classes to define command containers. -Even a simple function can be a valid command container, it is -enough to add to it a ``.commands`` attribute and possibly -``__enter__`` and/or ``__exit__`` attributes. - -In particular, a Python module is a perfect container of commands. As an -example, consider the following module implementing a fake Version -Control System: - -.. include:: vcs.py - :literal: - -Notice that I have defined both an ``__exit__`` hook and a ``__missing__`` -hook, invoked for non-existing commands. -The real trick here is the line ``main = __import__(__name__)``, which -define ``main`` to be an alias for the current module. - -The ``vcs`` module does not contain an ``if __name__ == '__main__'`` -block, but you can still run it through the plac runner -(try ``plac vcs.py -h``): - -.. include:: vcs.help - :literal: - -You can get help for the subcommands by postponing ``-h`` after the -name of the command:: - - $ plac vcs.py status -h - usage: vcs.py status [-h] [-q] - - A fake status command - - optional arguments: - -h, --help show this help message and exit - -q, --quiet summary information - -Notice how the docstring of the command is automatically shown in -usage message, as well as the documentation for the sub flag ``-q``. - -Here is an example of a non-interactive session:: - - $ plac vcs.py check url - checkout - url - $ plac vcs.py st -q - status - True - $ plac vcs.py co - commit - None - -and here is an interactive session:: - - $ plac -i vcs.py - usage: plac_runner.py vcs.py [-h] {status,commit,checkout} ... - i> check url - checkout - url - i> st -q - status - True - i> co - commit - None - i> sto - Command 'sto' does not exist - i> [CTRL-D] - ok - -Notice the invocation of the ``__missing__`` hook for non-existing commands. -Notice also that the ``__exit__`` hook gets called only in interactive -mode. - -If the commands are completely independent, a module is a good fit for -a method container. In other situations, it is best to use a custom -class. - -Writing your own plac runner ----------------------------------------------------------- - -The runner included in the plac_ distribution is intentionally kept -small (around 50 lines of code) so that you can study it and write -your own runner if want to. If you need to go to such level -of detail, you should know that the most important method of -the ``Interpreter`` class is the ``.send`` method, which takes -strings in input and returns a four-tuple with attributes -``.str``, ``.etype``, ``.exc`` and ``.tb``: - -- ``.str`` is the output of the command, if successful (a string); -- ``.etype`` is the class of the exception, if the command fail; -- ``.exc`` is the exception instance; -- ``.tb`` is the traceback. - -Moreover the ``__str__`` representation of the output object is redefined -to return the output string if the command was successful or the error -message if the command failed (actually it returns the error message -preceded by the name of the exception class). - -For instance, if you send a mispelled option to -the interpreter a ``SystemExit`` will be trapped: - ->>> import plac ->>> from ishelve import ishelve ->>> with plac.Interpreter(ishelve) as i: -... print(i.send('.cler')) -... -SystemExit: unrecognized arguments: .cler - -It is important to invoke the ``.send`` method inside the context manager, -otherwise you will get a ``RuntimeError``. - -For instance, suppose you want to implement a graphical runner for a -plac-based interpreter with two text widgets: one to enter the commands -and one to display the results. Suppose you want to display the errors -with tracebacks in red. You will need to code something like that -(pseudocode follows):: - - input_widget = WidgetReadingInput() - output_widget = WidgetDisplayingOutput() - - def send(interpreter, line): - out = interpreter.send(line) - if out.tb: # there was an error - output_widget.display(out.tb, color='red') - else: - output_widget.display(out.str) - - main = plac.import_main(tool_path) # get the main object - - with plac.Interpreter(main) as i: - def callback(event): - if event.user_pressed_ENTER(): - send(i, input_widget.last_line) - input_widget.addcallback(callback) - gui_mainloop.start() - -You can adapt the pseudocode to your GUI toolkit of choice and you can -also change the file associations in such a way that clicking on a -plac tool file the graphical user interface starts. - -An example of GUI program built on top of plac_ is given later on, in the -paragraph *Managing the output of concurrent commands* (using Tkinter -for simplicity and portability). - -There is a final *caveat*: since the plac interpreter loop is -implemented via extended generators, plac interpreters are single threaded: you -will get an error if you ``.send`` commands from separated threads. -You can circumvent the problem by using a queue. If EXIT is a sentinel -value to signal exiting from the interpreter look, you can write code -like this:: - - with interpreter: - for input_value in iter(input_queue.get, EXIT): - output_queue.put(interpreter.send(input_value)) - -The same trick also work for processes; you could run the interpreter -loop in a separate process and send commands to it via the Queue -class provided by the multiprocessing_ module. - -Long running commands ---------------------------------------- - -As we saw, by default a plac_ interpreter blocks until -the command terminates. This is an issue, in the sense that it makes -the interactive experience quite painful for long running commands. An -example is better than a thousand words, so consider the following -fake importer: - -.. include:: importer1.py - :literal: - -If you run the ``import_file`` command, you will have to wait for 200 seconds -before entering a new command:: - - $ python importer1.py dsn -i - A fake importer with an import_file command - i> import_file file1 - ... - Imported 100 lines - Imported 200 lines - Imported 300 lines - ... - Imported 10000 lines - closing the file - -Being unable to enter any other command is quite annoying: in such situation one -would like to run the long running commands in the background, to keep -the interface responsive. plac_ provides two ways to reach this goal: threads -and processes. - -Threaded commands ------------------------------------------ - -The most familiar way to execute a task in the background (even if not -necessarily the best way) is to run it into a separated thread. In our -example it is sufficient to replace the line - - ``commands = ['import_file']`` - -with - - ``thcommands = ['import_file']`` - -to tell to the plac_ interpreter that the command ``import_file`` should be -run into a separated thread. Here is an example session:: - - i> import_file file1 - - -The import task started in a separated thread. You can see the -progress of the task by using the special command ``.output``:: - - i> .output 1 - - Imported 100 lines - Imported 200 lines - -If you look after a while, you will get more lines of output:: - - i> .output 1 - - Imported 100 lines - Imported 200 lines - Imported 300 lines - Imported 400 lines - -If you look after a time long enough, the task will be finished:: - - i> .output 1 - - -It is possible to store the output of a task into a file, to be read -later (this is useful for tasks with a large output):: - - i> .output 1 /tmp/out.txt - saved output of 1 into /tmp/out.txt - -You can even skip the number argument: then ``.output`` will the return -the output of the last launched command (the special commands like .output -do not count). - -You can launch many tasks one after the other:: - - i> import_file file2 - - i> import_file file3 - - -The ``.list`` command displays all the running tasks:: - - i> .list - - - -It is even possible to kill a task:: - - i> .kill 5 - - # wait a bit ... - closing the file - i> .output 5 - - -You should notice that since at the Python level it is impossible to kill -a thread, the ``.kill`` commands works by setting the status of the task to -``TOBEKILLED``. Internally the generator corresponding to the command -is executed in the thread and the status is checked at each iteration: -when the status become ``TOBEKILLED`` a ``GeneratorExit`` exception is -raised and the thread terminates (softly, so that the ``finally`` clause -is honored). In our example the generator is yielding -back control once every 100 iterations, i.e. every two seconds (not much). -In order to get a responsive interface it is a good idea to yield more -often, for instance every 10 iterations (i.e. 5 times per second), -as in the following code: - -.. include:: importer2.py - :literal: - -Running commands as external processes ------------------------------------------ - -Threads are not loved much in the Python world and actually most people -prefer to use processes instead. For this reason plac_ provides the -option to execute long running commands as external processes. Unfortunately -the current implementation only works in Unix-like operating systems -(including Mac OS X) because it relies on fork via the multiprocessing_ -module. - -In our example, to enable the feature it is sufficient to replace the line - - ``thcommands = ['import_file']`` - -with - - ``mpcommands = ['import_file']``. - -The user experience is exactly the same as with threads and you will not see any -difference at the user interface level:: - - i> import_file file3 - - i> .kill 1 - - closing the file - i> .o 1 - - Imported 100 lines - Imported 200 lines - i> - -Still, using processes is quite different than using threads: in -particular, when using processes you can only yield pickleable values -and you cannot re-raise an exception first raised in a different -process, because traceback objects are not pickleable. Moreover, -you cannot rely on automatic sharing of your objects. - -On the plus side, when using processes you do not need to worry about -killing a command: they are killed immediately using a SIGTERM signal, -and there is not a ``TOBEKILLED`` mechanism. Moreover, the killing is -guaranteed to be soft: internally a command receiving a SIGTERM raises -a ``TerminatedProcess`` exception which is trapped in the generator -loop, so that the command is closed properly. - -Using processes allows to take full advantage of multicore machines -and it is safer than using threads, so it is the recommended approach -unless you are working on Windows. - -Managing the output of concurrent commands ---------------------------------------------- - -plac_ acts as a command-line task launcher and can be used as the base -to build a GUI-based task launcher and task monitor. To this aim the -interpreter class provides a ``.submit`` method which returns a task -object and a ``.tasks`` method returning the list of all the tasks -submitted to the interpreter. The ``submit`` method does not start the task -and thus it is nonblocking. -Each task has an ``.outlist`` attribute which is a list -storing the value yielded by the generator underlying the task (the -``None`` values are skipped though): the ``.outlist`` grows as the -task runs and more values are yielded. Accessing the ``.outlist`` is -nonblocking and can be done freely. -Finally there is a ``.result`` -property which waits for the task to finish and returns the last yielded -value or raises an exception. The code below provides an example of -how you could implement a GUI over the importer example: - -.. include:: importer_ui.py - :literal: - -Monitor support --------------------------------- - -Starting from release 0.8 plac_ provides builtin support for monitoring the -output of concurrent commands, at least for platforms where multiprocessing -is fully supported. You can define your own monitor -class, simply by inheriting from ``plac.Monitor`` and by -overriding the methods ``add_listener(self, no)``, -``del_listener(self, taskno)``, ``notify_listener(self, taskno, msg)``, -``schedule(self, seconds, func, arg)`` and ``run(self)``. -Then, you can a monitor object to any ``plac.Interpreter`` object -by simply calling the ``add_monitor`` method. -For convenience, -``plac`` comes with a very simple ``TkMonitor`` based on Tkinter -(I chose Tkinter because it is easy to use and it is -in the standard library, but you can use any GUI): you can just -look at how the ``TkMonitor`` is implemented in ``plac_tk.py`` -and adapt it. Here is an example of usage of the ``TkMonitor``: - -.. include:: tkmon.py - :literal: - -Try to give the ``hello`` command from the interactive interpreter: -each time a new text widget will be added displaying the output -of the command. Notice that if ``Tkinter`` is not installed correctly -on your system the ``TkMonitor`` class will not be available. - -Parallel computing with plac ---------------------------------------------- - -plac_ is certainly not intended as a tool for parallel computing, but -still you can use it to launch a set of commands and to collect the -results, similarly to the MapReduce pattern popularized by -Google. In order to give an example, I will consider the "Hello -World" of parallel computing, i.e. the computation of pi with -independent processes. There is a huge number of algorithms to -compute pi; here I will describe a trivial one chosen for simplicity, -not per efficienty. The trick is to consider the first quadrant of a -circle with radius 1 and to extract a number of points ``(x, y)`` with -``x`` and ``y`` random variables in the interval ``[0,1]``. The -probability of extracting a number inside the quadrant (i.e. with -``x^2 + y^2 < 1``) is proportional to the area of the quadrant -(i.e. ``pi/4``). The value of ``pi`` therefore can be extracted by -multiplying by 4 the ratio between the number of points in the -quadrant versus the total number of points ``N``, for ``N`` large:: - - def calc_pi(N): - inside = 0 - for j in xrange(N): - x, y = random(), random() - if x*x + y*y < 1: - inside += 1 - return (4.0 * inside) / N - -The algorithm is trivially parallelizable: if you have n CPUs, you can -compute pi n times with N/n iterations, sum the results and divide the total -by n. I have a Macbook with two cores, therefore I would expect a speedup -factor of 2 with respect to a sequential computation. Moreover, I would -expect a threaded computation to be even slower than a sequential -computation, due to the GIL and the scheduling overhead. - -Here is a script implementing the algorithm and working in three different -modes (parallel mode, threaded mode and sequential mode) depending on a -``mode`` option: - -.. include:: picalculator.py - :literal: - -Notice the ``submit_tasks`` method, which instantiates and initializes a -``plac.Interpreter`` object and submits a number of commands corresponding -to the number of available CPUs. The ``calc_pi`` command yield a log -message every million of interactions, just to monitor the progress of -the computation. The ``run`` method starts all the submitted commands -in parallel and sums the results. It returns the average value of ``pi`` -after the slowest CPU has finished its job (if the CPUs are equal and -equally busy they should finish more or less at the same time). - -Here are the results on my old Macbook with Ubuntu 10.04 and Python 2.6, -for 10 million of iterations:: - - $ python picalculator.py -mP 10000000 # two processes - 3.141904 in 5.744545 seconds - $ python picalculator.py -mT 10000000 # two threads - 3.141272 in 13.875645 seconds - $ python picalculator.py -mS 10000000 # sequential - 3.141586 in 11.353841 seconds - -As you see using processes one gets a 2x speedup indeed, where the threaded -mode is some 20% slower than the sequential mode. - -Since the pattern submit a bunch of tasks, starts them and collect the -results is so common, plac_ provides an utility function -``runp(genseq, mode='p', monitors=(), start=True)`` to start -a bunch a generators and return a list of task objects. By default -``runp`` use processes, but you can use threads by passing ``mode='t'``. -If you do not wont to start the tasks, you can say so (``start=False``). -With ``runp`` the parallel pi calculation becomes a one-liner:: - - sum(task.result for task in plac.runp(calc_pi(N) for i in range(ncpus)))/ncpus - -The file ``test_runp`` in the ``doc`` directory of the plac distribution -shows another couple of examples of usage, including how to show the -results of the running computation on a ``TkMonitor``. - -The plac server -------------------------------------------------------- - -A command-line oriented interface can be easily converted into a -socket-based interface. Starting from release 0.7 plac features -a builtin server which is able to accept commands from multiple -clients and to execute them. The server works by instantiating -a separate interpreter for each client, so that if a client interpreter -dies for any reason the other interpreters keep working. -To avoid external dependencies the server is based on the ``asynchat`` -module in the standard library, but it would not be difficult to -replace the server with a different one (for instance, a Twisted server). -Since ``asynchat``-based servers are asynchronous, any blocking command -in the interpreter should be run in a separated process or thread. -The default port for the plac_ server is 2199, and the command to -signal end-of-connection is EOF. -For instance, here is how you could manage remote import on a database -(say a SQLite db): - -.. include:: server_ex.py - :literal: - -You can connect to the server with ``telnet`` on port 2199, as follows:: - - $ telnet localhost 2199 - Trying ::1... - Trying 127.0.0.1... - Connected to localhost. - Escape character is '^]'. - i> import_file f1 - i> .list - - i> .out - Imported 100 lines - Imported 200 lines - i> EOF - Connection closed by foreign host. - -Summary -------------------------------------------------------- - -Once plac_ claimed to be the easiest command-line arguments parser -in the world. Having read this document you may think that it is not -so easy after all. But it is a false impression. Actually the -rules are quite simple: - -1. - if you want to implement a command-line script, use ``plac.call``; - -2. - if you want to implement a command interpreter, use ``plac.Interpreter``: - - - for an interactive interpreter, call the ``.interact`` method; - - for an batch interpreter, call the ``.execute`` method; - -3. for testing call the ``Interpreter.check`` method in the appropriate context - or use the ``Interpreter.doctest`` feature; - -4. if you need to go at a lower level, you may need to call the - ``Interpreter.send`` method which returns a (finished) ``Task`` object. - -5. long running command can be executed in the background as threads or - processes: just declare them in the lists ``thcommands`` and ``mpcommands`` - respectively. - -6. the ``.start_server`` method starts an asynchronous server on the - given port number (default 2199) - -Moreover, remember that ``plac_runner.py`` is your friend. - ------- - -Appendix: custom annotation objects --------------------------------------------------------- - -Internally plac_ 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 you get: - -.. include:: example11.help - :literal: - -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/ -.. _plac: http://pypi.python.org/pypi/plac -.. _scaling down: http://www.welton.it/articles/scalable_systems -.. _ArgumentParser: http://argparse.googlecode.com/svn/tags/r11/doc/ArgumentParser.html -.. _argparse.FileType: http://argparse.googlecode.com/svn/tags/r11/doc/other-utilities.html?highlight=filetype#FileType -.. _Clap: http://pypi.python.org/pypi/Clap/0.7 -.. _OptionParser: http://docs.python.org/library/optparse.html?highlight=optionparser#optparse.OptionParser -.. _SQLAlchemy: http://www.sqlalchemy.org/ -.. _SqlSoup: http://www.sqlalchemy.org/docs/reference/ext/sqlsoup.html -.. _advanced usage document: in-writing -.. _twill: http://twill.idyll.org/ -.. _basic documentation: http://plac.googlecode.com/hg/doc/plac.html -.. _shlex: http://docs.python.org/library/shlex.html -.. _multiprocessing: http://docs.python.org/library/multiprocessing.html -.. _distutils: http://docs.python.org/distutils/ -.. _cmd: http://docs.python.org/library/cmd.html -.. _rlwrap: http://freshmeat.net/projects/rlwrap/ -.. _pyreadline: http://ipython.scipy.org/moin/PyReadline/Intro diff --git a/plac/doc/plac_core.txt b/plac/doc/plac_core.txt deleted file mode 100644 index c4ffc9d..0000000 --- a/plac/doc/plac_core.txt +++ /dev/null @@ -1,793 +0,0 @@ -Plac: Parsing the Command Line the Easy Way -===================================================================== - -:Author: Michele Simionato -:E-mail: michele.simionato@gmail.com -:Date: June 2011 -:Download page: http://pypi.python.org/pypi/plac -:Project page: http://plac.googlecode.com/hg/doc/plac.html -:Requires: Python 2.3+ -:Installation: ``easy_install -U plac`` -:License: BSD license - -.. contents:: - -The importance of scaling down ------------------------------------------------- - -There is no want of command line arguments parsers in the Python -world. The standard library alone contains three different modules: -getopt_ (from the stone age), -optparse_ (from Python 2.3) and argparse_ (from Python 2.7). All of -them are quite powerful and especially argparse_ is an industrial -strength solution; unfortunately, all of them feature a non-zero learning -curve and a certain verbosity. They do not scale down well, at -least in my opinion. - -It should not be necessary to stress the importance `scaling down`_; -nevertheless, a lot of people are obsessed with features and concerned with -the possibility of scaling up, forgetting the equally important -issue of scaling down. This is an old meme in -the computing world: programs should address the common cases simply and -simple things should be kept simple, while at the same keeping -difficult things possible. plac_ adhere as much as possible to this -philosophy and it is designed to handle well the simple cases, while -retaining the ability to handle complex cases by relying on the -underlying power of argparse_. - -Technically plac_ is just a simple wrapper over argparse_ which hides -most of its complexity by using a declarative interface: the argument -parser is inferred rather than written down by imperatively. Still, plac_ is -surprisingly scalable upwards, even without using the underlying -argparse_. I have been using Python for 8 years and in my experience -it is extremely unlikely that you will ever need to go beyond the -features provided by the declarative interface of plac_: they should -be more than enough for 99.9% of the use cases. - -plac_ is targetting especially unsophisticated users, -programmers, sys-admins, scientists and in general people writing -throw-away scripts for themselves, choosing the command line -interface because it is the quick and simple. Such users are not -interested in features, they are interested in a small learning curve: -they just want to be able to write a simple command line tool from a -simple specification, not to build a command-line parser by -hand. Unfortunately, the modules in the standard library forces them -to go the hard way. They are designed to implement power user tools -and they have a non-trivial learning curve. On the contrary, plac_ -is designed to be simple to use and extremely concise, as the examples -below will show. - -Scripts with required arguments ---------------------------------------------- - -Let me start with the simplest possible thing: a script that takes a -single argument and does something to it. It cannot get simpler -than that, unless you consider a script without command-line -arguments, where there is nothing to parse. Still, it is a use -case *extremely common*: I need to write scripts like that nearly -every day, I wrote hundreds of them in the last few years and I have -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 exist. Actually I think -the language should recognize the main function and pass to it the -command-line arguments automatically; unfortunaly this is unlikely to -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 does not help for this -incredibly common use case. 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, it just feels too complex to instantiate a class and to -define a parser by hand for such a trivial task. - -The plac_ 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 -plac_ module all you need to write is - -.. include:: example3.py - :literal: - -The plac_ module provides for free (actually the work is done by the -underlying argparse_ module) a nice usage message:: - - $ python example3.py -h - -.. include:: example3.help - :literal: - -Moreover plac_ manages the case of missing arguments and of too many arguments. -This is only the tip of the iceberg: plac_ is able to do much more than that. - -Scripts with default arguments --------------------------------------------------- - -The need to have suitable defaults for command-line scripts is quite -common. For instance I have encountered this use case at work hundreds -of times: - -.. include:: example4.py - :literal: - -Here I want to perform a query on a database table, by extracting the -most recent data: it makes sense for ``today`` to be a default argument. -If there is a most used table (in this example a table called ``'product'``) -it also makes sense to make it a default argument. Performing the parsing -of the command-line arguments by hand takes 8 ugly lines of boilerplate -(using argparse_ would require about the same number of lines). -With plac_ the entire ``__main__`` block reduces to the usual two lines:: - - if __name__ == '__main__': - import plac; plac.call(main) - -In other words, six lines of boilerplate have been removed, and we get -the usage message for free: - -.. include:: example5.help - :literal: - -Notice that by default plac_ prints the string representation -of the default values (with square brackets) in the usage message. -plac_ 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:: example7.py - :literal: - -Here is the usage message: - -.. include:: example7.help - :literal: - -The examples here should have made clear that *plac is able to figure out -the command-line arguments parser to use from the signature of the main -function*. This is the whole idea behind plac_: if the intent is clear, -let's the machine take care of the details. - -plac_ is inspired to an old Python Cookbook recipe of mine (optionparse_), in -the sense that it delivers the programmer from the burden of writing -the parser, but is less of a hack: instead of extracting the parser -from the docstring of the module, it extracts it from the signature of -the ``main`` function. - -The idea comes from the `function annotations` concept, a new -feature of Python 3. An example is worth a thousand words, so here -it is: - -.. include:: example7_.py - :literal: - -Here the arguments of the ``main`` function have been annotated with -strings which are intented to be used in the help message: - -.. include:: example7_.help - :literal: - -plac_ is able to recognize much more complex annotations, as -I will show in the next paragraphs. - - -Scripts with options (and smart options) ------------------------------------------ - -It is surprising how few command-line scripts with options I have -written over the years (probably less than a hundred), compared to the -number of scripts with positional arguments I wrote (certainly more -than a thousand of them). Still, this use case cannot be neglected. -The standard library modules (all of them) are quite verbose when it -comes to specifying the options and frankly I have never used them -directly. Instead, I have always relied on the -optionparse_ recipe, which provides a convenient wrapper over -optionparse_. Alternatively, in the simplest cases, I have just -performed the parsing by hand. In plac_ the parser is inferred by the -function annotations. Here is an example: - -.. include:: example8.py - :literal: - -Here the argument ``command`` has been annotated with the tuple -``("SQL query", 'option', 'c')``: the first string is the help string -which will appear in the usage message, the second string tells plac_ -that ``command`` is an option and the third string that there is also -a short form of the option ``-c``, the long form being ``--command``. -The usage message is the following: - -.. include:: example8.help - :literal: - -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 - -The third argument in the function annotation can be omitted: in such -case it will be assumed to be ``None``. The consequence is that -the usual dichotomy between long and short options (GNU-style options) -disappears: we get *smart options*, which have the single character prefix -of short options and behave like both long and short options, since -they can be abbreviated. Here is an example featuring smart options: - -.. include:: example6.py - :literal: - -.. include:: example6.help - :literal: - -The following are all valid invocations ot the script:: - - $ python3 example6.py -c "select" dsn - executing 'select' on dsn - $ python3 example6.py -com "select" dsn - executing 'select' on dsn - $ python3 example6.py -command="select" dsn - executing 'select' on dsn - -Notice that the form ``-command=SQL`` is recognized only for the full -option, not for its abbreviations:: - - $ python3 example6.py -com="select" dsn - usage: example6.py [-h] [-command COMMAND] dsn - example6.py: error: unrecognized arguments: -com=select - -If the option is not passed, the variable ``command`` -will get the value ``None``. However, it is possible to specify a non-trivial -default. Here is an example: - -.. include:: example8_.py - :literal: - -Notice that the default value appears in the help message: - -.. include:: example8_.help - :literal: - -When you run the script and you do not pass the ``-command`` option, the -default query will be executed:: - - $ python3 example8_.py dsn - executing 'select * from table' on dsn - -Scripts with flags --------------------- - -plac_ is able to recognize 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: - -.. include:: example9.py - :literal: - -.. include:: example9.help - :literal: - -:: - - $ python3 example9.py -v dsn - connecting to dsn - -Notice that it is an error trying to specify a default for flags: the -default value for a flag is always ``False``. If you feel the need to -implement non-boolean flags, you should use an option with two -choices, as explained in the "more features" section. - -For consistency with the way the usage message is printed, I suggest -you to follow the Flag-Option-Required-Default (FORD) convention: in -the ``main`` function write first the flag arguments, then the option -arguments, then the required arguments and finally the default -arguments. This is just a convention and you are not forced to use it, -except for the default arguments (including the varargs) which must -stay at the end as it is required by the Python syntax. - -I also suggests to specify a one-character abbreviation for flags: in -this way you can use the GNU-style composition of flags (i.e. ``-zxvf`` -is an abbreviation of ``-z -x -v -f``). I usually do not provide -the one-character abbreviation for options, since it does not make sense -to compose them. - -plac for Python 2.X users --------------------------------------------------- - -I do not use Python 3. At work we are just starting to think about -migrating to Python 2.6. It will take years before we -think to migrate to Python 3. I am pretty much sure most Pythonistas -are in the same situation. Therefore plac_ provides a way to work -with function annotations even in Python 2.X (including Python 2.3). -There is no magic involved; you just need to add the annotations -by hand. For instance the annotated function declaration - -:: - - def main(dsn: "Database dsn", *scripts: "SQL scripts"): - ... - -is equivalent to the following code:: - - def main(dsn, *scripts): - ... - main.__annotations__ = dict( - dsn="Database dsn", - scripts="SQL scripts") - -One should be careful to match the keys of the annotation 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 -``plac.annotations`` decorator that performs the check for you:: - - @plac.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 ``plac.annotations`` decorator. Notice however -that the core features of plac_ run even on Python 2.3. - -More features --------------------------------------------------- - -One of the goals of plac_ is to have a learning curve of *minutes* for -its core features, compared to the learning curve of *hours* of -argparse_. In order to reach this goal, I have *not* sacrificed all -the features of argparse_. Actually a lot of argparse_ power persists -in plac_. Until now, I have only showed simple annotations, but in -general an annotation is a 6-tuple of the form - - ``(help, kind, abbrev, type, choices, metavar)`` - -where ``help`` is the help message, ``kind`` is a string in the set { -``"flag"``, ``"option"``, ``"positional"``}, ``abbrev`` is a -one-character string or ``None``, ``type`` is a callable taking a -string in input, -``choices`` is a discrete sequence of values and ``metavar`` is a string. - -``type`` is used to automagically convert the command line arguments -from the string type to any Python type; by default there is no -conversion and ``type=None``. - -``choices`` is used to restrict the number of the valid -options; by default there is no restriction i.e. ``choices=None``. - -``metavar`` has two meanings. For a positional argument it is used to -change the argument name in the usage message (and only there). By -default the metavar is ``None`` and the name in the usage message is -the same as the argument name. For an option -the ``metavar`` is used differently in the usage message, which has -now the form ``[--option-name METAVAR]``. If the ``metavar`` is ``None``, -then it is equal to the uppercased name of the argument, unless the -argument has a default and in such a case is equal to the stringified -form of the default. - -Here is an example showing many of the features (copied from the -argparse_ documentation): - -.. include:: example10.py - :literal: - -Here is the usage: - -.. include:: example10.help - :literal: - -Notice that the docstring of the ``main`` function has been automatically added -to the usage message. 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') - -``plac.call`` can also be used in doctests like this: - ->>> import plac, example10 ->>> plac.call(example10.main, ['add', '1', '2']) -3.0 - -``plac.call`` works for generators too: - ->>> def main(n): -... for i in range(int(n)): -... yield i ->>> plac.call(main, ['3']) -[0, 1, 2] - -Internally ``plac.call`` tries to convert the output of the main function -into a list, if possible. If the output is not iterable or it is a -string, it is left unchanged, but if it is iterable it is converted. -In particular, generator objects are exhausted by ``plac.call``. - -This behavior avoids mistakes like forgetting of applying -``list(result)`` to the result of ``plac.call``; moreover it makes -errors visible early, and avoids mistakes in code like the following:: - - try: - result = plac.call(main, args) - except: - # do something - -Without the "listify" functionality, a main function returning a -generator object would not raise any exception until the generator -is iterated over. - -If you are a fan of lazyness, you can still have it by setting the ``eager`` -flag to ``False``, as in the following example:: - - for line in plac.call(main, args, eager=False): - print(line) - -If ``main`` returns a generator object this example will print each -line as soon as available, whereas the default behaviour is to print -all the lines together and the end of the computation. - -A realistic example ---------------------------------------- - -Here is a more realistic script using most of the features of plac_ to -run SQL queries on a database by relying on SQLAlchemy_. Notice the usage -of the ``type`` feature to automagically convert a SQLAlchemy connection -string into a SqlSoup_ object: - -.. include:: dbcli.py - :literal: - -You can see the *yield-is-print* pattern here: instead of using -``print`` in the main function, I use ``yield``, and I perform the -print in the ``__main__`` block. The advantage of the pattern is that -tests invoking ``plac.call`` and checking the result become trivial: -had I performed the printing in the main function, the test would have -involved an ugly hack like redirecting ``sys.stdout`` to a -``StringIO`` object. - -Here is the usage message: - -.. include:: dbcli.help - :literal: - -You can check for yourself that the script works. - -Keyword arguments ---------------------------------------- - -Starting from release 0.4, plac_ supports keyword arguments. In -practice that means that if your main function has keyword arguments, -plac_ treats specially arguments of the form ``"name=value"`` in the -command line. Here is an example: - -.. include:: example12.py - :literal: - -Here is the generated usage message: - -.. include:: example12.help - :literal: - -Here is how you call the script:: - - $ python example12.py -o X a1 a2 name=value - opt=X - args=('a1', 'a2') - kw={'name': 'value'} - -When using keyword arguments, one must be careful to use names which -are not alreay taken; for instance in this examples the name ``opt`` -is taken:: - - $ python example12.py 1 2 kw1=1 kw2=2 opt=0 - usage: example12.py [-h] [-o OPT] [args [args ...]] [kw [kw ...]] - example12.py: error: colliding keyword arguments: opt - -The names taken are the names of the flags, of the options, and of the -positional arguments, excepted varargs and keywords. This limitation -is a consequence of the way the argument names are managed in function calls -by the Python language. - -Final example: a shelve interface ----------------------------------------------------------- - -Here is a less trivial example for the keyword arguments feature. -The use case is the following: suppose we have stored the -configuration parameters of a given application into a Python shelve -and we need a command-line tool to edit the shelve. -A possible implementation using plac_ could be the following: - -.. include:: ishelve.py - :literal: - -A few notes are in order: - -1. I have disabled the ordinary help provided by argparse_ and I have - implemented a custom help command. -2. I have changed the prefix character used to recognize the options - to a dot. -3. Keyword arguments recognition (in the ``**setters``) is used to make it - possible to store a value in the shelve with the syntax - ``param_name=param_value``. -4. ``*params`` are used to retrieve parameters from the shelve and some - error checking is performed in the case of missing parameters -5. A command to clear the shelve is implemented as a flag (``.clear``). -6. A command to delete a given parameter is implemented as an option - (``.delete``). -7. There is an option with default (``.filename=conf.shelve``) to store - the filename of the shelve. -8. All things considered, the code looks like a poor man object oriented - interface implemented with a chain of elifs instead of methods. Of course, - plac_ can do better than that, but let me start from a low-level approach - first. - -If you run ``ishelve.py`` without arguments you get the following -message:: - - $ python ishelve.py - no arguments passed, use .help to see the available commands - -If you run ``ishelve.py`` with the option ``.h`` (or any abbreviation -of ``.help``) you get:: - - $ python ishelve.py .h - Commands: .help, .showall, .clear, .delete - ... - ... - -You can check by hand that the tool work:: - - $ python ishelve.py .clear # start from an empty shelve - cleared the shelve - $ python ishelve.py a=1 b=2 - setting a=1 - setting b=2 - $ python ishelve.py .showall - b=2 - a=1 - $ python ishelve.py .del b # abbreviation for .delete - deleted b - $ python ishelve.py a - 1 - $ python ishelve.py b - b: not found - $ python ishelve.py .cler # mispelled command - usage: ishelve.py [.help] [.showall] [.clear] [.delete DELETE] - [.filename /home/micheles/conf.shelve] - [params [params ...]] [setters [setters ...]] - ishelve.py: error: unrecognized arguments: .cler - -plac vs argparse ---------------------------------------------- - -plac_ is opinionated and by design it does not try to make available -all of the features of argparse_ in an easy way. In particular you -should be aware of the following limitations/differences (the -following assumes knowledge of argparse_): - -- plac does not support the destination concept: the destination - coincides with the name of the argument, always. This restriction - has some drawbacks. For instance, suppose you want to define a long - option called ``--yield``. In this case the destination would be ``yield``, - which is a Python keyword, and since you cannot introduce an - argument with that name in a function definition, it is impossible - to implement it. Your choices are to change the name of the long - option, or to use argparse_ with a suitable destination. - -- plac_ does not support "required options". As the argparse_ - documentation puts it: *Required options are generally considered bad - form - normal users expect options to be optional. You should avoid - the use of required options whenever possible.* Notice that since - argparse_ supports them, plac_ can manage them too, but not directly. - -- plac_ supports only regular boolean flags. argparse_ has the ability to - define generalized two-value flags with values different from ``True`` - and ``False``. An earlier version of plac_ had this feature too, but - since you can use options with two choices instead, and in any case - the conversion from ``{True, False}`` to any couple of values - can be trivially implemented with a ternary operator - (``value1 if flag else value2``), I have removed it (KISS rules!). - -- plac_ does not support ``nargs`` options directly (it uses them internally, - though, to implement flag recognition). The reason it that all the use - cases of interest to me are covered by plac_ and did not feel the need - to increase the learning curve by adding direct support for ``nargs``. - -- plac_ does support subparsers, but you must read the `advanced usage - document`_ to see how it works. - -- plac_ does not support actions directly. This also - looks like a feature too advanced for the goals of plac_. Notice however - that the ability to define your own annotation objects (again, see - the `advanced usage document`_) may mitigate the need for custom actions. - -plac_ can leverage directly on many argparse_ features. - -For instance, you can make invisible an argument in the usage message -simply by using ``'==SUPPRESS=='`` as help string (or -``argparse.SUPPRESS``). Similarly, you can use argparse.FileType_ -directly. - -It is also possible to pass options to the underlying -``argparse.ArgumentParser`` object (currently it accepts the default -arguments ``description``, ``epilog``, ``prog``, ``usage``, -``add_help``, ``argument_default``, ``parents``, ``prefix_chars``, -``fromfile_prefix_chars``, ``conflict_handler``, ``formatter_class``). -It is enough to set such attributes on the ``main`` function. For -instance - -:: - - def main(...): - pass - - main.add_help = False - -disables the recognition of the help flag ``-h, --help``. This -mechanism does not look particularly elegant, but it works well -enough. I assume that the typical user of plac_ will be happy with -the defaults and would not want to change them; still it is possible -if she wants to. - -For instance, by setting the ``description`` attribute, it is possible -to add a comment to the usage message (by default the docstring of the -``main`` function is used as description). - -It is also possible to change the option prefix; for -instance if your script must run under Windows and you want to use "/" -as option prefix you can add the line:: - - main.prefix_chars='/-' - -The first prefix char (``/``) is used -as the default for the recognition of options and flags; -the second prefix char (``-``) is kept to keep the ``-h/--help`` option -working: however you can disable it and reimplement it, if you like, -as seen in the ``ishelve`` example. - -It is possible to access directly the underlying ArgumentParser_ object, by -invoking the ``plac.parser_from`` utility function: - ->>> import plac ->>> def main(arg): -... pass -... ->>> print(plac.parser_from(main)) #doctest: +ELLIPSIS -ArgumentParser(prog=...) - -Internally ``plac.call`` uses ``plac.parser_from`` and adds the parser -to the main function as an attribute. When ``plac.call(func)`` is -invoked multiple time, the parser is re-used and not rebuilt from scratch again. - -I use ``plac.parser_from`` in the unit tests of the module, but regular -users should not need to use it, unless they want to access *all* -of the features of argparse_ directly without calling the main function. - -Interested readers should read the documentation of argparse_ to -understand the meaning of the other options. If there is a set of -options that you use very often, you may consider writing a decorator -adding such options to the ``main`` function for you. For simplicity, -plac_ does not perform any magic except the addition of the ``.p`` -attribute. - -plac vs the rest of the world ------------------------------------------- - -Originally plac_ boasted about being "the easiest command-line -arguments parser in the world". Since then, people started pointing -out to me various projects which are based on the same idea -(extracting the parser from the main function signature) and are -arguably even easier than plac_: - -- opterator_ by Dusty Phillips -- CLIArgs_ by Pavel Panchekha -- commandline_ by David Laban - -Luckily for me none of such projects had the idea of using -function annotations and argparse_; as a consequence, they are -no match for the capabilities of plac_. - -Of course, there are tons of other libraries to parse the command -line. For instance Clap_ by Matthew Frazier which appeared on PyPI -just the day before plac_; Clap_ is fine but it is certainly not -easier than plac_. - -plac_ can also be used as a replacement of the cmd_ module in the standard -library and as such it shares many features with the module cmd2_ by -Catherine Devlin. However, this is completely coincidental, since I became -aware of the cmd2_ module only after writing plac_. - -Command-line argument parsers keep coming out; between the newcomers I -will notice `marrow.script`_ by Alice Bevan-McGregor, which is quite -similar to plac_ in spirit, but does not rely on argparse_ at all. -Argh_ by Andrey Mikhaylenko is also worth mentioning: it is also based -on argparse_, it came after plac_ and I must give credit to the author -for the choice of the name, much funnier than plac! - -The future -------------------------------- - -Currently the core of plac_ is around 200 lines of code, not counting blanks, -comments and docstrings. I do not plan to extend the core much in the -future. The idea is to keep the module short: it is and it should -remain a little wrapper over argparse_. Actually I have thought about -contributing the core back to argparse_ if plac_ becomes successfull -and gains a reasonable number of users. For the moment it should be -considered in alpha status. - -Notice that even if plac_ has been designed to be simple to use for -simple stuff, its power should not be underestimated; it is actually a -quite advanced tool with a domain of applicability which far exceeds -the realm of command-line arguments parsers. - -Version 0.5 of plac_ doubled the code base and the documentation: it is -based on the idea of using plac_ to implement command-line interpreters, -i.e. something akin to the ``cmd`` module in the standard library, only better. -The new features of plac_ are described in the `advanced usage document`_ . -They are implemented in a separated module (``plac_ext.py``), since -they require Python 2.5 to work, whereas ``plac_core.py`` only requires -Python 2.3. - -Trivia: the story behind the name ------------------------------------------ - -The plac_ project started very humbly: I just wanted to make -easy_installable my old optionparse_ recipe, and to publish it on PyPI. -The original name of plac_ was optionparser and the idea behind it was -to build an OptionParser_ object from the docstring of the module. -However, before doing that, I decided to check out the argparse_ module, -since I knew it was going into Python 2.7 and Python 2.7 was coming out. -Soon enough I realized two things: - -1. the single greatest idea of argparse_ was unifying the positional arguments - and the options in a single namespace object; -2. parsing the docstring was so old-fashioned, considering the existence - of functions annotations in Python 3. - -Putting together these two observations with the original idea of inferring the -parser I decided to build an ArgumentParser_ object from function -annotations. The ``optionparser`` name was ruled out, since I was -now using argparse_; a name like ``argparse_plus`` was also ruled out, -since the typical usage was completely different from the argparse_ usage. - -I made a research on PyPI and the name *clap* (Command Line Arguments Parser) -was not taken, so I renamed everything to clap. After two days -a Clap_ module appeared on PyPI ! - -Having little imagination, I decided to rename everything again to plac, -an anagram of clap: since it is a non-existing English name, I hope nobody -will steal it from me! - -That concludes the section about the basic usage of plac_. You are now ready to -read about the advanced usage. - -.. _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/ -.. _plac: http://pypi.python.org/pypi/plac -.. _scaling down: http://www.welton.it/articles/scalable_systems -.. _ArgumentParser: http://argparse.googlecode.com/svn/tags/r11/doc/ArgumentParser.html -.. _argparse.FileType: http://argparse.googlecode.com/svn/tags/r11/doc/other-utilities.html?highlight=filetype#FileType -.. _Clap: http://pypi.python.org/pypi/Clap/0.7 -.. _OptionParser: http://docs.python.org/library/optparse.html?highlight=optionparser#optparse.OptionParser -.. _SQLAlchemy: http://www.sqlalchemy.org/ -.. _SqlSoup: http://www.sqlalchemy.org/docs/reference/ext/sqlsoup.html -.. _CLIArgs: http://pypi.python.org/pypi/CLIArgs -.. _opterator: http://pypi.python.org/pypi/opterator -.. _advanced usage document: in-writing -.. _cmd2: http://packages.python.org/cmd2/ -.. _cmd: http://docs.python.org/library/cmd.html -.. _marrow.script: https://github.com/pulp/marrow.script -.. _commandline: http://pypi.python.org/pypi/commandline -.. _argh: http://packages.python.org/argh diff --git a/plac/doc/read_stdin.py b/plac/doc/read_stdin.py deleted file mode 100644 index 584644f..0000000 --- a/plac/doc/read_stdin.py +++ /dev/null @@ -1,13 +0,0 @@ -""" -You can run this script as -$ python read_stdin.py < ishelve.bat -""" -from __future__ import with_statement -import sys -from ishelve import ishelve -import plac - -if __name__ == '__main__': - with plac.Interpreter(ishelve) as i: - for line in sys.stdin: - print(i.send(line)) diff --git a/plac/doc/server_ex.py b/plac/doc/server_ex.py deleted file mode 100644 index e3d76ec..0000000 --- a/plac/doc/server_ex.py +++ /dev/null @@ -1,9 +0,0 @@ -import plac -from importer2 import FakeImporter - -def main(port=2199): - main = FakeImporter('dsn') - plac.Interpreter(main).start_server(port) - -if __name__ == '__main__': - plac.call(main) diff --git a/plac/doc/shelve_interpreter.help b/plac/doc/shelve_interpreter.help deleted file mode 100644 index f666445..0000000 --- a/plac/doc/shelve_interpreter.help +++ /dev/null @@ -1,13 +0,0 @@ -usage: shelve_interpreter.py [-h] [-interactive] - [subcommands [subcommands ...]] - - This script works both interactively and non-interactively. - Use .help to see the internal commands. - - -positional arguments: - subcommands the commands of the underlying ishelve interpreter - -optional arguments: - -h, --help show this help message and exit - -interactive start interactive interface diff --git a/plac/doc/shelve_interpreter.py b/plac/doc/shelve_interpreter.py deleted file mode 100644 index b7fb688..0000000 --- a/plac/doc/shelve_interpreter.py +++ /dev/null @@ -1,19 +0,0 @@ -# shelve_interpreter.py -import plac, ishelve - -@plac.annotations( - interactive=('start interactive interface', 'flag'), - subcommands='the commands of the underlying ishelve interpreter') -def main(interactive, *subcommands): - """ - This script works both interactively and non-interactively. - Use .help to see the internal commands. - """ - if interactive: - plac.Interpreter(ishelve.main).interact() - else: - for out in plac.call(ishelve.main, subcommands): - print(out) - -if __name__ == '__main__': - plac.call(main) diff --git a/plac/doc/sql_interface.py b/plac/doc/sql_interface.py deleted file mode 100644 index 67c6233..0000000 --- a/plac/doc/sql_interface.py +++ /dev/null @@ -1,29 +0,0 @@ -import os, plac -from sqlalchemy.ext.sqlsoup import SqlSoup - -SQLKEYWORDS = set(['help', 'select', 'from', - 'inner', 'join', 'outer', 'left', 'right'] - ) # and many others -DBTABLES = set(['table1', 'table2']) # you can read them from the db schema - -COMPLETIONS = SQLKEYWORDS | DBTABLES - -class SqlInterface(object): - commands = ['SELECT'] - def __init__(self, dsn): - self.soup = SqlSoup(dsn) - def SELECT(self, argstring): - sql = 'SELECT ' + argstring - for row in self.soup.bind.execute(sql): - yield str(row) # the formatting can be much improved - -rl_input = plac.ReadlineInput( - COMPLETIONS, histfile=os.path.expanduser('~/.sql_interface.history'), - case_sensitive=False) - -def split_on_first_space(line, commentchar): - return line.strip().split(' ', 1) # ignoring comments - -if __name__ == '__main__': - plac.Interpreter.call(SqlInterface, split=split_on_first_space, - stdin=rl_input, prompt='sql> ') diff --git a/plac/doc/test_ishelve.py b/plac/doc/test_ishelve.py deleted file mode 100644 index a34809d..0000000 --- a/plac/doc/test_ishelve.py +++ /dev/null @@ -1,12 +0,0 @@ -# test_ishelve.py -import plac, ishelve - -def test(): - assert plac.call(ishelve.main, ['.clear']) == ['cleared the shelve'] - assert plac.call(ishelve.main, ['a=1']) == ['setting a=1'] - assert plac.call(ishelve.main, ['a']) == ['1'] - assert plac.call(ishelve.main, ['.delete=a']) == ['deleted a'] - assert plac.call(ishelve.main, ['a']) == ['a: not found'] - -if __name__ == '__main__': - test() diff --git a/plac/doc/test_ishelve_more.py b/plac/doc/test_ishelve_more.py deleted file mode 100644 index 234ce0a..0000000 --- a/plac/doc/test_ishelve_more.py +++ /dev/null @@ -1,11 +0,0 @@ -# test_ishelve_more.py -from __future__ import with_statement -import plac, ishelve - -def test(): - with plac.Interpreter(ishelve.main) as i: - i.check('.clear', 'cleared the shelve') - i.check('a=1', 'setting a=1') - i.check('a', '1') - i.check('.delete=a', 'deleted a') - i.check('a', 'a: not found') diff --git a/plac/doc/test_pi.py b/plac/doc/test_pi.py deleted file mode 100644 index 9f1fbd0..0000000 --- a/plac/doc/test_pi.py +++ /dev/null @@ -1,11 +0,0 @@ -import time -from picalculator import PiCalculator - -def test(): - pc = PiCalculator(10, 'T') - tasks = pc.submit_tasks() - for task in tasks: - task.run() - print(sum(task.result for task in tasks)/pc.n_cpu) - pc.close() - diff --git a/plac/doc/test_plac.py b/plac/doc/test_plac.py deleted file mode 100644 index 414e2a7..0000000 --- a/plac/doc/test_plac.py +++ /dev/null @@ -1,231 +0,0 @@ -""" -The tests are runnable with nose, with py.test, or even as standalone script -""" - -import os, sys, datetime, doctest, subprocess -import plac, plac_core - -sys_argv0 = sys.argv[0] -os.chdir(os.path.dirname(__file__) or '.') # work in the current directory - -######################## helpers ####################### - -def fix_today(text): - return text.replace('YYYY-MM-DD', str(datetime.date.today())) - -def expect(errclass, func, *args, **kw): - try: - func(*args, **kw) - except errclass: - pass - else: - raise RuntimeError('%s expected, got none!', errclass.__name__) - -def parser_from(f, **kw): - f.__annotations__ = kw - return plac.parser_from(f) - -def check_help(name): - sys.argv[0] = name + '.py' # avoid issue with nosetests - plac_core._parser_registry.clear() # makes different imports independent - try: - try: - main = plac.import_main(name + '.py') - except SyntaxError: - if sys.version < '3': # expected for Python 2.X - return - else: # not expected for Python 3.X - raise - p = plac.parser_from(main) - expected = fix_today(open(name + '.help').read()).strip() - got = p.format_help().strip() - assert got == expected, got - finally: - sys.argv[0] = sys_argv0 - -####################### tests ############################ - -def test_expected_help(): - for fname in os.listdir('.'): - if fname.endswith('.help'): - name = fname[:-5] - if name not in ('vcs', 'ishelve'): - yield check_help, fname[:-5] - -p1 = parser_from(lambda delete, *args: None, - delete=('delete a file', 'option')) - -def test_p1(): - arg = p1.parse_args(['-d', 'foo', 'arg1', 'arg2']) - assert arg.delete == 'foo' - assert arg.args == ['arg1', 'arg2'] - - arg = p1.parse_args([]) - assert arg.delete is None, arg.delete - assert arg.args == [], arg.args - -p2 = parser_from(lambda arg1, delete, *args: None, - delete=('delete a file', 'option', 'd')) - -def test_p2(): - 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 is None, arg.delete - assert arg.args == [], arg.args - assert arg, arg - - expect(SystemExit, p2.parse_args, []) - -p3 = parser_from(lambda arg1, delete: None, - delete=('delete a file', 'option', 'd')) - -def test_p3(): - arg = p3.parse_args(['arg1']) - assert arg.delete is None, arg.delete - assert arg.arg1 == 'arg1', arg.args - - expect(SystemExit, p3.parse_args, ['arg1', 'arg2']) - expect(SystemExit, p3.parse_args, []) - -p4 = parser_from(lambda delete, delete_all, color="black": None, - delete=('delete a file', 'option', 'd'), - delete_all=('delete all files', 'flag', 'a'), - color=('color', 'option', 'c')) - -def test_p4(): - arg = p4.parse_args(['-a']) - assert arg.delete_all is True, arg.delete_all - - arg = p4.parse_args([]) - - arg = p4.parse_args(['--color=black']) - assert arg.color == 'black' - - arg = p4.parse_args(['--color=red']) - assert arg.color == 'red' - -def test_flag_with_default(): - expect(TypeError, parser_from, lambda yes_or_no='no': None, - yes_or_no=('A yes/no flag', 'flag', 'f')) - -def assert_usage(parser, expected): - usage = parser.format_usage() - assert usage == expected, usage - -def test_metavar_no_defaults(): - sys.argv[0] = 'test_plac.py' - - # positional - p = parser_from(lambda x: None, - x=('first argument', 'positional', None, str, [], 'METAVAR')) - assert_usage(p, 'usage: test_plac.py [-h] METAVAR\n') - - # option - p = parser_from(lambda x: None, - x=('first argument', 'option', None, str, [], 'METAVAR')) - assert_usage(p, 'usage: test_plac.py [-h] [-x METAVAR]\n') - sys.argv[0] = sys_argv0 - -def test_metavar_with_defaults(): - sys.argv[0] = 'test_plac.py' - - # positional - p = parser_from(lambda x='a': None, - x=('first argument', 'positional', None, str, [], 'METAVAR')) - assert_usage(p, 'usage: test_plac.py [-h] [METAVAR]\n') - - # option - p = parser_from(lambda x='a': None, - x=('first argument', 'option', None, str, [], 'METAVAR')) - assert_usage(p, 'usage: test_plac.py [-h] [-x METAVAR]\n') - - p = parser_from(lambda x='a': None, - x=('first argument', 'option', None, str, [])) - assert_usage(p, 'usage: test_plac.py [-h] [-x a]\n') - - sys.argv[0] = sys_argv0 - -def test_kwargs(): - def main(opt, arg1, *args, **kw): - print(opt, arg1) - return args, kw - main.__annotations__ = dict(opt=('Option', 'option')) - argskw = plac.call(main, ['arg1', 'arg2', 'a=1', 'b=2']) - assert argskw == [('arg2',), {'a': '1', 'b': '2'}], argskw - - argskw = plac.call(main, ['arg1', 'arg2', 'a=1', '-o', '2']) - assert argskw == [('arg2',), {'a': '1'}], argskw - - expect(SystemExit, plac.call, main, ['arg1', 'arg2', 'a=1', 'opt=2']) - -class Cmds(object): - add_help = False - commands = 'help', 'commit' - def help(self, name): - return 'help', name - def commit(self): - return 'commit' - -cmds = Cmds() - -def test_cmds(): - assert 'commit' == plac.call(cmds, ['commit']) - assert ['help', 'foo'] == plac.call(cmds, ['help', 'foo']) - expect(SystemExit, plac.call, cmds, []) - -def test_cmd_abbrevs(): - assert 'commit' == plac.call(cmds, ['comm']) - assert ['help', 'foo'] == plac.call(cmds, ['h', 'foo']) - expect(SystemExit, plac.call, cmds, ['foo']) - -def test_sub_help(): - c = Cmds() - c.add_help = True - expect(SystemExit, plac.call, c, ['commit', '-h']) - -def test_yield(): - def main(): - for i in (1, 2, 3): - yield i - assert plac.call(main, []) == [1, 2, 3] - -def test_doctest(): - failure, tot= doctest.testfile('plac.txt', module_relative=False) - assert not failure, failure - -failing_scripts = set(['ishelve2.plac']) - -def check_script(args): - if failing_scripts.intersection(args): - assert subprocess.call(args) > 0, ( # expected failure - 'Unexpected success for %s' % ' '.join(args)) - else: - assert subprocess.call(args) == 0 , 'Failed %s' % ' '.join(args) - -def test_batch(): - for batch in os.listdir('.'): - if batch.endswith('.plac'): - yield check_script, ['plac_runner.py', '-b', batch] - -def test_placet(): - for placet in os.listdir('.'): - if placet.endswith('.placet'): - yield check_script, ['plac_runner.py', '-t', placet] - -if __name__ == '__main__': - n = 0 - for name, test in sorted(globals().items()): - if name.startswith('test_'): - print('Running ' + name) - maybegen = test() - if hasattr(maybegen, '__iter__'): - for func, arg in maybegen: - func(arg) - n += 1 - else: - n +=1 - print('Executed %d tests OK' % n) diff --git a/plac/doc/test_runp.py b/plac/doc/test_runp.py deleted file mode 100644 index 4cdd494..0000000 --- a/plac/doc/test_runp.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -This test should work on Linux if you have Tkinter installed. -""" - -from __future__ import with_statement -import plac, plac_tk, time - -def gen(n): - for i in range(n): - yield str(i) - time.sleep(.1) - -def test(): - tasks = plac.runp([gen(3), gen(5), gen(10)]) - for t in tasks: - t.result - t.man.stop() - -def test_tkmonitor(): - mon = plac_tk.TkMonitor('tkmon') - tasks = plac.runp([gen(3), gen(5), gen(10)], monitors=[mon]) - for t in tasks: - t.result - t.man.stop() diff --git a/plac/doc/test_server.py b/plac/doc/test_server.py deleted file mode 100644 index adcc395..0000000 --- a/plac/doc/test_server.py +++ /dev/null @@ -1,38 +0,0 @@ -import multiprocessing, subprocess, random, time -import plac -from ishelve2 import ShelveInterface - -i = plac.Interpreter(ShelveInterface(configfile=None)) - -COMMANDS = ['''\ -help -set a 1 -''', -'''\ -set b 1 -wrong command -showall -'''] - -def telnet(commands, port): - po = subprocess.Popen(['telnet', 'localhost', str(port)], - stdin=subprocess.PIPE) - try: - for cmd in commands.splitlines(): - po.stdin.write(cmd + '\n') - time.sleep(.1) # wait a bit for the server to answer - finally: - po.stdin.close() - -def test(): - port = random.choice(range(2000, 20000)) - server = multiprocessing.Process(target=i.start_server, args=(port,)) - server.start() - clients = [] - for cmds in COMMANDS: - cl = multiprocessing.Process(target=telnet, args=(cmds, port)) - clients.append(cl) - cl.start() - for cl in clients: - cl.join() - server.terminate() diff --git a/plac/doc/vcs.help b/plac/doc/vcs.help deleted file mode 100644 index 51bd34a..0000000 --- a/plac/doc/vcs.help +++ /dev/null @@ -1,12 +0,0 @@ -usage: plac_runner.py vcs.py [-h] {status,commit,checkout} ... - -A Fake Version Control System - -optional arguments: - -h, --help show this help message and exit - -subcommands: - {status,commit,checkout} - checkout A fake checkout command - commit A fake commit command - status A fake status command diff --git a/plac/doc/vcs.py b/plac/doc/vcs.py deleted file mode 100644 index c423fb0..0000000 --- a/plac/doc/vcs.py +++ /dev/null @@ -1,30 +0,0 @@ -"A Fake Version Control System" - -import plac - -commands = 'checkout', 'commit', 'status' - -@plac.annotations(url='url of the source code') -def checkout(url): - "A fake checkout command" - return ('checkout ', url) - -@plac.annotations(message=('commit message', 'option')) -def commit(message): - "A fake commit command" - return ('commit ', message) - -@plac.annotations(quiet=('summary information', 'flag', 'q')) -def status(quiet): - "A fake status command" - return ('status ', quiet) - -def __missing__(name): - return 'Command %r does not exist' % name - -def __exit__(etype, exc, tb): - "Will be called automatically at the end of the call/cmdloop" - if etype in (None, GeneratorExit): # success - print('ok') - -main = __import__(__name__) # the module imports itself! diff --git a/plac/plac.py b/plac/plac.py deleted file mode 100644 index 4776afc..0000000 --- a/plac/plac.py +++ /dev/null @@ -1,40 +0,0 @@ -########################## LICENCE ############################### -## -## Copyright (c) 2010-2011, Michele Simionato -## All rights reserved. -## -## Redistributions of source code must retain the above copyright -## notice, this list of conditions and the following disclaimer. -## Redistributions in bytecode form must reproduce the above copyright -## notice, this list of conditions and the following disclaimer in -## the documentation and/or other materials provided with the -## distribution. - -## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -## HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -## BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -## OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -## TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -## USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -## DAMAGE. - -""" -See doc/plac.pdf, doc/plac_adv.pdf for the documentation. -""" - -__version__ = '0.9.0' - -from plac_core import * - -if sys.version >= '2.5': - from plac_ext import (Interpreter, import_main, ReadlineInput, - stdout, runp, Monitor, default_help) - try: - from plac_tk import TkMonitor - except ImportError: - pass diff --git a/plac/plac_core.py b/plac/plac_core.py deleted file mode 100644 index 7b0ad7f..0000000 --- a/plac/plac_core.py +++ /dev/null @@ -1,312 +0,0 @@ -# this module should be kept Python 2.3 compatible -import re, sys, inspect, argparse -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.varkw, self.defaults = \ - inspect.getargspec(f) - self.annotations = getattr(f, '__annotations__', {}) -try: - set -except NameError: # Python 2.3 - from sets import Set as set -from gettext import gettext as _ - - -def getargspec(callableobj): - """Given a callable return an object with attributes .args, .varargs, - .varkw, .defaults. It tries to do the "right thing" with functions, - methods, classes and generic callables.""" - if inspect.isfunction(callableobj): - argspec = getfullargspec(callableobj) - elif inspect.ismethod(callableobj): - argspec = getfullargspec(callableobj) - del argspec.args[0] # remove first argument - elif inspect.isclass(callableobj): - if callableobj.__init__ is object.__init__: # to avoid an error - argspec = getfullargspec(lambda self: None) - else: - argspec = getfullargspec(callableobj.__init__) - del argspec.args[0] # remove first argument - elif hasattr(callableobj, '__call__'): - argspec = getfullargspec(callableobj.__call__) - del argspec.args[0] # remove first argument - else: - raise TypeError(_('Could not determine the signature of ') + - str(callableobj)) - return argspec - -def annotations(**ann): - """ - Returns a decorator annotating a function with the given annotations. - This is a trick to support function annotations in Python 2.X. - """ - def annotate(f): - fas = getfullargspec(f) - args = fas.args - if fas.varargs: - args.append(fas.varargs) - if fas.varkw: - args.append(fas.varkw) - 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): - """ - An object is an annotation object if it has the attributes - help, kind, abbrev, type, choices, metavar. - """ - 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=None, kind="positional", abbrev=None, type=None, - choices=None, metavar=None): - assert kind in ('positional', 'option', 'flag'), kind - if kind == "positional": - assert abbrev is None, abbrev - self.help = help - self.kind = kind - self.abbrev = abbrev - self.type = type - self.choices = choices - self.metavar = metavar - - 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, basestring): - return cls(*obj) - return cls(obj) - from_ = classmethod(from_) - -NONE = object() # sentinel use to signal the absence of a default - -PARSER_CFG = getfullargspec(argparse.ArgumentParser.__init__).args[1:] -# the default arguments accepted by an ArgumentParser object - -def pconf(obj): - "Extracts the configuration of the underlying ArgumentParser from obj" - cfg = dict(description=obj.__doc__, - formatter_class=argparse.RawDescriptionHelpFormatter) - for name in dir(obj): - if name in PARSER_CFG: # argument of ArgumentParser - cfg[name] = getattr(obj, name) - return cfg - -_parser_registry = {} - -def parser_from(obj, **confparams): - """ - obj can be a callable or an object with a .commands attribute. - Returns an ArgumentParser. - """ - try: # the underlying parser has been generated already - return _parser_registry[obj] - except KeyError: # generate a new parser - pass - conf = pconf(obj).copy() - conf.update(confparams) - _parser_registry[obj] = parser = ArgumentParser(**conf) - parser.obj = obj - parser.case_sensitive = confparams.get( - 'case_sensitive', getattr(obj, 'case_sensitive', True)) - if hasattr(obj, 'commands') and obj.commands and not inspect.isclass(obj): - # a command container instance - parser.addsubcommands(obj.commands, obj, 'subcommands') - else: - parser.populate_from(obj) - return parser - -def _extract_kwargs(args): - "Returns two lists: regular args and name=value args" - arglist = [] - kwargs = {} - for arg in args: - match = re.match(r'([a-zA-Z_]\w*)=', arg) - if match: - name = match.group(1) - kwargs[name] = arg[len(name)+1:] - else: - arglist.append(arg) - return arglist, kwargs - -def _match_cmd(abbrev, commands, case_sensitive=True): - "Extract the command name from an abbreviation or raise a NameError" - if not case_sensitive: - abbrev = abbrev.upper(); commands = [c.upper() for c in commands] - perfect_matches = [name for name in commands if name == abbrev] - if len(perfect_matches) == 1: - return perfect_matches[0] - matches = [name for name in commands if name.startswith(abbrev)] - n = len(matches) - if n == 1: - return matches[0] - elif n > 1: - raise NameError( - _('Ambiguous command %r: matching %s' % (abbrev, matches))) - -class ArgumentParser(argparse.ArgumentParser): - """ - An ArgumentParser with .func and .argspec attributes, and possibly - .commands and .subparsers. - """ - case_sensitive = True - - def alias(self, arg): - "Can be overridden to preprocess command-line arguments" - return arg - - def consume(self, args): - """Call the underlying function with the args. Works also for - command containers, by dispatching to the right subparser.""" - arglist = map(self.alias, args) - cmd = None - if hasattr(self, 'subparsers'): - subp, cmd = self._extract_subparser_cmd(arglist) - if subp is None and cmd is not None: - return cmd, self.missing(cmd) - elif subp is not None: # use the subparser - self = subp - if hasattr(self, 'argspec') and self.argspec.varkw: - arglist, kwargs = _extract_kwargs(arglist) # modify arglist! - else: - kwargs = {} - if hasattr(self, 'argspec') and self.argspec.varargs: - # ignore unrecognized arguments - ns, extraopts = self.parse_known_args(arglist) - else: - ns, extraopts = self.parse_args(arglist), [] # may raise an exit - args = [getattr(ns, a) for a in self.argspec.args] - varargs = getattr(ns, self.argspec.varargs or '', []) - collision = set(self.argspec.args) & set(kwargs) - if collision: - self.error( - _('colliding keyword arguments: %s') % ' '.join(collision)) - return cmd, self.func(*(args + varargs + extraopts), **kwargs) - - def _extract_subparser_cmd(self, arglist): - "Extract the right subparser from the first recognized argument" - optprefix = self.prefix_chars[0] - name_parser_map = self.subparsers._name_parser_map - for i, arg in enumerate(arglist): - if not arg.startswith(optprefix): - cmd = _match_cmd(arg, name_parser_map, self.case_sensitive) - del arglist[i] - return name_parser_map.get(cmd), cmd or arg - return None, None - - def addsubcommands(self, commands, obj, title=None, cmdprefix=''): - "Extract a list of subcommands from obj and add them to the parser" - if hasattr(obj, cmdprefix) and obj.cmdprefix in self.prefix_chars: - raise ValueError(_('The prefix %r is already taken!' % cmdprefix)) - if not hasattr(self, 'subparsers'): - self.subparsers = self.add_subparsers(title=title) - elif title: - self.add_argument_group(title=title) # populate ._action_groups - prefixlen = len(getattr(obj, 'cmdprefix', '')) - add_help = getattr(obj, 'add_help', True) - for cmd in commands: - func = getattr(obj, cmd[prefixlen:]) # strip the prefix - self.subparsers.add_parser( - cmd, add_help=add_help, help=func.__doc__, **pconf(func) - ).populate_from(func) - - def _set_func_argspec(self, obj): - """Extracts the signature from a callable object and adds an .argspec - attribute to the parser. Also adds a .func reference to the object.""" - self.func = obj - self.argspec = getargspec(obj) - _parser_registry[obj] = self - - def populate_from(self, func): - """ - Extract the arguments from the attributes of the passed function - and return a populated ArgumentParser instance. - """ - self._set_func_argspec(func) - f = self.argspec - defaults = f.defaults or () - n_args = len(f.args) - n_defaults = len(defaults) - alldefaults = (NONE,) * (n_args - n_defaults) + defaults - prefix = self.prefix = getattr(func, 'prefix_chars', '-')[0] - for name, default in zip(f.args, alldefaults): - ann = f.annotations.get(name, ()) - a = Annotation.from_(ann) - metavar = a.metavar - if default is NONE: - dflt = None - else: - dflt = default - if a.help is None: - a.help = '[%s]' % dflt - if a.kind in ('option', 'flag'): - if a.abbrev: - shortlong = (prefix + a.abbrev, prefix*2 + name) - else: - shortlong = (prefix + name,) - elif default is NONE: # required argument - self.add_argument(name, help=a.help, type=a.type, - choices=a.choices, metavar=metavar) - else: # default argument - self.add_argument( - name, nargs='?', help=a.help, default=dflt, - type=a.type, choices=a.choices, metavar=metavar) - if a.kind == 'option': - if default is not NONE: - metavar = metavar or str(default) - self.add_argument( - help=a.help, default=dflt, type=a.type, - choices=a.choices, metavar=metavar, *shortlong) - elif a.kind == 'flag': - if default is not NONE and default is not False: - raise TypeError(_('Flag %r wants default False, got %r') % - (name, default)) - self.add_argument(action='store_true', help=a.help, *shortlong) - if f.varargs: - a = Annotation.from_(f.annotations.get(f.varargs, ())) - self.add_argument(f.varargs, nargs='*', help=a.help, default=[], - type=a.type, metavar=a.metavar) - if f.varkw: - a = Annotation.from_(f.annotations.get(f.varkw, ())) - self.add_argument(f.varkw, nargs='*', help=a.help, default={}, - type=a.type, metavar=a.metavar) - - def missing(self, name): - miss = getattr(self.obj, '__missing__', lambda name: - self.error('No command %r' % name)) - return miss(name) - - def print_actions(self): - "Useful for debugging" - print(self) - for a in self._actions: - print(a) - -def iterable(obj): - "Any object with an __iter__ method which is not a string" - return hasattr(obj, '__iter__') and not isinstance(obj, basestring) - -def call(obj, arglist=sys.argv[1:], eager=True): - """ - If obj is a function or a bound method, parse the given arglist - by using the parser inferred from the annotations of obj - and call obj with the parsed arguments. - If obj is an object with attribute .commands, dispatch to the - associated subparser. - """ - cmd, result = parser_from(obj).consume(arglist) - if iterable(result) and eager: # listify the result - return list(result) - return result diff --git a/plac/plac_ext.py b/plac/plac_ext.py deleted file mode 100644 index 35106ae..0000000 --- a/plac/plac_ext.py +++ /dev/null @@ -1,1063 +0,0 @@ -# this module requires Python 2.5+ -from __future__ import with_statement -from contextlib import contextmanager -from operator import attrgetter -from gettext import gettext as _ -import imp, inspect, os, sys, cmd, shlex, subprocess -import itertools, traceback, multiprocessing, signal, threading -import plac_core - -############################# generic utils ################################ - -@contextmanager -def stdout(fileobj): - "usage: with stdout(file('out.txt', 'a')): do_something()" - orig_stdout = sys.stdout - sys.stdout = fileobj - try: - yield - finally: - sys.stdout = orig_stdout - -def write(x): - "Write str(x) on stdout and flush, no newline added" - sys.stdout.write(str(x)) - sys.stdout.flush() - -def gen_val(value): - "Return a generator object with a single element" - yield value - -def gen_exc(etype, exc, tb): - "Return a generator object raising an exception" - raise etype, exc, tb - yield - -def less(text): - "Send a text to less via a pipe" - # -c clear the screen before starting less - po = subprocess.Popen(['less', '-c'], stdin=subprocess.PIPE) - try: - po.stdin.write(text) - except IOError: - pass - po.stdin.close() - po.wait() - -use_less = (sys.platform != 'win32') # unices - -class TerminatedProcess(Exception): - pass - -def terminatedProcess(signum, frame): - raise TerminatedProcess - -########################### readline support ############################# - -def read_line(stdin, prompt=''): - "Read a line from stdin, using readline when possible" - if isinstance(stdin, ReadlineInput): - return stdin.readline(prompt) - else: - write(prompt) - return stdin.readline() - -def read_long_line(stdin, terminator): - """ - Read multiple lines from stdin until the terminator character is found, then - yield a single space-separated long line. - """ - while True: - lines = [] - while True: - line = stdin.readline() # ends with \n - if not line: # EOF - return - line = line.strip() - if not line: - continue - elif line[-1] == terminator: - lines.append(line[:-1]) - break - else: - lines.append(line) - yield ' '.join(lines) - -class ReadlineInput(object): - """ - An iterable with a .readline method reading from stdin. - """ - def __init__(self, completions, case_sensitive=True, histfile=None): - self.completions = completions - self.case_sensitive = case_sensitive - self.histfile = histfile - if not case_sensitive: - self.completions = map(str.upper, completions) - import readline - self.rl = readline - readline.parse_and_bind("tab: complete") - readline.set_completer(self.complete) - - def __enter__(self): - self.old_completer = self.rl.get_completer() - try: - if self.histfile: - self.rl.read_history_file(self.histfile) - except IOError: # the first time - pass - return self - - def __exit__(self, etype, exc, tb): - self.rl.set_completer(self.old_completer) - if self.histfile: - self.rl.write_history_file(self.histfile) - - def complete(self, kw, state): - # state is 0, 1, 2, ... and increases by hitting TAB - if not self.case_sensitive: - kw = kw.upper() - try: - return [k for k in self.completions if k.startswith(kw)][state] - except IndexError: # no completions - return # exit - - def readline(self, prompt=''): - try: - return raw_input(prompt) + '\n' - except EOFError: - return '' - - def __iter__(self): - return iter(self.readline, '') - -################### help functionality in plac interpreters ################### - -class HelpSummary(object): - "Build the help summary consistently with the cmd module" - - @classmethod - def add(cls, obj, specialcommands): - p = plac_core.parser_from(obj) - c = cmd.Cmd(stdout=cls()) - c.stdout.write('\n') - c.print_topics('special commands', - sorted(specialcommands), 15, 80) - c.print_topics('custom commands', - sorted(obj.syncommands), 15, 80) - c.print_topics('commands run in external processes', - sorted(obj.mpcommands), 15, 80) - c.print_topics('threaded commands', - sorted(obj.thcommands), 15, 80) - p.helpsummary = unicode(c.stdout) - - def __init__(self): - self._ls = [] - - def write(self, s): - self._ls.append(s) - - def __str__(self): - return ''.join(self._ls) - -def format_help(self): - "Attached to plac_core.ArgumentParser for plac interpreters" - try: - return self.helpsummary - except AttributeError: - return super(plac_core.ArgumentParser, self).format_help() -plac_core.ArgumentParser.format_help = format_help - -def default_help(obj, cmd=None): - "An utility for implementing the help functionality in plac interpreters" - parser = plac_core.parser_from(obj) - if cmd is None: - yield parser.format_help() - subp = parser.subparsers._name_parser_map.get(cmd) - if subp is None: - yield _('Unknown command %s' % cmd) - else: - yield subp.format_help() - -########################### import management ################################ - -try: - PLACDIRS = os.environ.get('PLACPATH', '.').split(':') -except: - raise ValueError(_('Ill-formed PLACPATH: got %PLACPATHs') % os.environ) - -def partial_call(factory, arglist): - "Call a container factory with the arglist and return a plac object" - a = plac_core.parser_from(factory).argspec - if a.defaults or a.varargs or a.varkw: - raise TypeError('Interpreter.call must be invoked on ' - 'factories with required arguments only') - required_args = ', '.join(a.args) - if required_args: - required_args += ',' # trailing comma - code = '''def makeobj(interact, %s *args): - obj = factory(%s) - obj._interact_ = interact - obj._args_ = args - return obj\n'''% (required_args, required_args) - dic = dict(factory=factory) - exec code in dic - makeobj = dic['makeobj'] - makeobj.add_help = False - if inspect.isclass(factory): - makeobj.__annotations__ = getattr( - factory.__init__, '__annotations__', {}) - else: - makeobj.__annotations__ = getattr( - factory, '__annotations__', {}) - makeobj.__annotations__['interact'] = ( - 'start interactive interpreter', 'flag', 'i') - return plac_core.call(makeobj, arglist) - -def import_main(path, *args, **pconf): - """ - An utility to import the main function of a plac tool. It also - works with command container factories. - """ - if ':' in path: # importing a factory - path, factory_name = path.split(':') - else: # importing the main function - factory_name = None - if not os.path.isabs(path): # relative path, look at PLACDIRS - for placdir in PLACDIRS: - fullpath = os.path.join(placdir, path) - if os.path.exists(fullpath): - break - else: # no break - raise ImportError(_('Cannot find %s' % path)) - else: - fullpath = path - name, ext = os.path.splitext(os.path.basename(fullpath)) - module = imp.load_module(name, open(fullpath), fullpath, (ext, 'U', 1)) - if factory_name: - tool = partial_call(getattr(module, factory_name), args) - else: - tool = module.main - # set the parser configuration - plac_core.parser_from(tool, **pconf) - return tool - -############################## Task classes ############################## - -# base class not instantiated directly -class BaseTask(object): - """ - A task is a wrapper over a generator object with signature - Task(no, arglist, genobj), attributes - .no - .arglist - .outlist - .str - .etype - .exc - .tb - .status - and methods .run and .kill. - """ - STATES = ('SUBMITTED', 'RUNNING', 'TOBEKILLED', 'KILLED', 'FINISHED', - 'ABORTED') - - def __init__(self, no, arglist, genobj): - self.no = no - self.arglist = arglist - self._genobj = self._wrap(genobj) - self.str, self.etype, self.exc, self.tb = '', None, None, None - self.status = 'SUBMITTED' - self.outlist = [] - - def notify(self, msg): - "Notifies the underlying monitor. To be implemented" - - def _wrap(self, genobj, stringify_tb=False): - """ - Wrap the genobj into a generator managing the exceptions, - populating the .outlist, setting the .status and yielding None. - stringify_tb must be True if the traceback must be sent to a process. - """ - self.status = 'RUNNING' - try: - for value in genobj: - if self.status == 'TOBEKILLED': # exit from the loop - raise GeneratorExit - if value is not None: # add output - self.outlist.append(value) - self.notify(unicode(value)) - yield - except (GeneratorExit, TerminatedProcess, KeyboardInterrupt): - # soft termination - self.status = 'KILLED' - except: # unexpected exception - self.etype, self.exc, tb = sys.exc_info() - self.tb = ''.join(traceback.format_tb(tb)) if stringify_tb else tb - self.status = 'ABORTED' - else: # regular exit - self.status = 'FINISHED' - try: - self.str = '\n'.join(map(unicode, self.outlist)) - except IndexError: - self.str = 'no result' - - def run(self): - "Run the inner generator" - for none in self._genobj: - pass - - def kill(self): - "Set a TOBEKILLED status" - self.status = 'TOBEKILLED' - - def wait(self): - "Wait for the task to finish: to be overridden" - - @property - def traceback(self): - "Return the traceback as a (possibly empty) string" - if self.tb is None: - return '' - elif isinstance(self.tb, basestring): - return self.tb - else: - return ''.join(traceback.format_tb(self.tb)) - - @property - def result(self): - self.wait() - if self.exc: - raise self.etype, self.exc, self.tb or None - if not self.outlist: - return None - return self.outlist[-1] - - def __repr__(self): - "String representation containing class name, number, arglist, status" - return '<%s %d [%s] %s>' % ( - self.__class__.__name__, self.no, - ' '.join(self.arglist), self.status) - -nulltask = BaseTask(0, [], ('skip' for dummy in (1,))) - -########################## synchronous tasks ############################### - -class SynTask(BaseTask): - """ - Synchronous task running in the interpreter loop and displaying its - output as soon as available. - """ - def __str__(self): - "Return the output string or the error message" - if self.etype: # there was an error - return '%s: %s' % (self.etype.__name__, self.exc) - else: - return '\n'.join(map(unicode, self.outlist)) - -class ThreadedTask(BaseTask): - """ - A task running in a separated thread. - """ - def __init__(self, no, arglist, genobj): - BaseTask.__init__(self, no, arglist, genobj) - self.thread = threading.Thread(target=super(ThreadedTask, self).run) - - def run(self): - "Run the task into a thread" - self.thread.start() - - def wait(self): - "Block until the thread ends" - self.thread.join() - -######################### multiprocessing tasks ########################## - -def sharedattr(name, on_error): - "Return a property to be attached to an MPTask" - def get(self): - try: - return getattr(self.ns, name) - except: # the process was killed or died hard - return on_error - def set(self, value): - try: - setattr(self.ns, name, value) - except: # the process was killed or died hard - pass - return property(get, set) - -class MPTask(BaseTask): - """ - A task running as an external process. The current implementation - only works on Unix-like systems, where multiprocessing use forks. - """ - str = sharedattr('str', '') - etype = sharedattr('etype', None) - exc = sharedattr('exc', None) - tb = sharedattr('tb', None) - status = sharedattr('status', 'ABORTED') - - @property - def outlist(self): - try: - return self._outlist - except: # the process died hard - return [] - - def notify(self, msg): - self.man.send('notify_listener %d %r' % (self.no, msg)) - - def __init__(self, no, arglist, genobj, manager): - """ - The monitor has a .send method and a .man multiprocessing.Manager - """ - self.no = no - self.arglist = arglist - self._genobj = self._wrap(genobj, stringify_tb=True) - self.man = manager - self._outlist = manager.mp.list() - self.ns = manager.mp.Namespace() - self.status = 'SUBMITTED' - self.etype, self.exc, self.tb = None, None, None - self.str = repr(self) - self.proc = multiprocessing.Process(target=super(MPTask, self).run) - - def run(self): - "Run the task into an external process" - self.proc.start() - - def wait(self): - "Block until the external process ends or is killed" - self.proc.join() - - def kill(self): - """Kill the process with a SIGTERM inducing a TerminatedProcess - exception in the children""" - self.proc.terminate() - -######################### Task Manager ####################### - -class TaskManager(object): - """ - Store the given commands into a task registry. Provides methods to - manage the submitted tasks. - """ - cmdprefix = '.' - specialcommands = set(['.last_tb']) - - def __init__(self, obj): - self.obj = obj - self.registry = {} # {taskno : task} - if obj.mpcommands or obj.thcommands: - self.specialcommands.update(['.kill', '.list', '.output']) - self.parser = plac_core.parser_from(obj) - HelpSummary.add(obj, self.specialcommands) - self.man = Manager() if obj.mpcommands else None - signal.signal(signal.SIGTERM, terminatedProcess) - - def close(self): - "Kill all the running tasks" - for task in self.registry.itervalues(): - try: - if task.status == 'RUNNING': - task.kill() - task.wait() - except: # task killed, nothing to wait - pass - if self.man: - self.man.stop() - - def _get_latest(self, taskno=-1, status=None): - "Get the latest submitted task from the registry" - assert taskno < 0, 'You must pass a negative number' - if status: - tasks = [t for t in self.registry.itervalues() - if t.status == status] - else: - tasks = [t for t in self.registry.itervalues()] - tasks.sort(key=attrgetter('no')) - if len(tasks) >= abs(taskno): - return tasks[taskno] - - ########################### special commands ######################### - - @plac_core.annotations( - taskno=('task to kill', 'positional', None, int)) - def kill(self, taskno=-1): - 'kill the given task (-1 to kill the latest running task)' - if taskno < 0: - task = self._get_latest(taskno, status='RUNNING') - if task is None: - yield 'Nothing to kill' - return - elif not taskno in self.registry: - yield 'Unknown task %d' % taskno - return - else: - task = self.registry[taskno] - if task.status in ('ABORTED', 'KILLED', 'FINISHED'): - yield 'Already finished %s' % task - return - task.kill() - yield task - - @plac_core.annotations( - status=('', 'positional', None, str, BaseTask.STATES)) - def list(self, status='RUNNING'): - 'list tasks with a given status' - for task in self.registry.values(): - if task.status == status: - yield task - - @plac_core.annotations( - taskno=('task number', 'positional', None, int)) - def output(self, taskno=-1, fname=None): - 'show the output of a given task (and optionally save it to a file)' - if taskno < 0: - task = self._get_latest(taskno) - if task is None: - yield 'Nothing to show' - return - elif taskno not in self.registry: - yield 'Unknown task %d' % taskno - return - else: - task = self.registry[taskno] - outstr = '\n'.join(map(unicode, task.outlist)) - if fname: - open(fname, 'w').write(outstr) - yield 'saved output of %d into %s' % (taskno, fname); return - yield task - if len(task.outlist) > 20 and use_less: - less(outstr) - else: - yield outstr - - @plac_core.annotations( - taskno=('task number', 'positional', None, int)) - def last_tb(self, taskno=-1): - "show the traceback of a given task, if any" - task = self._get_latest(taskno) - if task: - yield task.traceback - else: - yield 'Nothing to show' - -########################### SyncProcess ############################## - -class Process(subprocess.Popen): - "Start the interpreter specified by the params in a subprocess" - - def __init__(self, params): - signal.signal(signal.SIGPIPE, signal.SIG_DFL) - # to avoid broken pipe messages - code = '''import plac, sys -sys.argv[0] = '<%s>' -plac.Interpreter(plac.import_main(*%s)).interact(prompt='i>\\n') -''' % (params[0], params) - subprocess.Popen.__init__( - self, [sys.executable, '-u', '-c', code], - stdin=subprocess.PIPE, stdout=subprocess.PIPE) - self.man = multiprocessing.Manager() - - def close(self): - "Close stdin and stdout" - self.stdin.close() - self.stdout.close() - self.man.shutdown() - - def recv(self): # char-by-char cannot work - "Return the output of the subprocess, line-by-line until the prompt" - lines = [] - while True: - lines.append(self.stdout.readline()) - if lines[-1] == 'i>\n': - out = ''.join(lines) - return out[:-1] + ' ' # remove last newline - - def send(self, line): - """Send a line (adding a newline) to the underlying subprocess - and wait for the answer""" - self.stdin.write(line + os.linesep) - return self.recv() - -class Monitor(object): - """ - Base monitor class with methods add_listener/del_listener/notify_listener - and start/stop/schedule/slave. - """ - commands = 'add_listener', 'del_listener', 'notify_listener' - def __init__(self, name): - self.name = name - def add_listener(self, taskno): - pass - def del_listener(self, taskno): - pass - def notify_listener(self, taskno, msg): - pass - def start(self): - pass - def stop(self): - pass - def schedule(self, seconds, display, arg): - pass - -import Queue - -class SlaveProcess(object): - """ - Spawn a slave process reading from an input queue and displaying - on a monitor object. Methods are start/send/stop. - """ - def __init__(self, mon): - self.mon= mon - self.queue = multiprocessing.Queue() - self.proc = multiprocessing.Process(None, self._run) - - def start(self): - self.proc.start() - - def send(self, line): - self.queue.put(line) - - def stop(self): - self.queue.close() - self.proc.terminate() - - def _sendline(self, i): - "Send a line to the underlying monitor" - try: - line = self.queue.get_nowait() - except Queue.Empty: - pass - else: - i.send(line) - self.mon.schedule(.1, self._sendline, i) - - def _run(self): - with Interpreter(self.mon) as i: - # .schedule() must be invoked inside the with block - self.mon.schedule(.1, self._sendline, i) - self.mon.run() - -class StartStopObject(object): - started = False - def start(self): pass - def stop(self): pass - -class Manager(StartStopObject): - """ - The plac Manager contains a multiprocessing.Manager and a set - of slave monitor processes to which we can send commands. There - is a manager for each interpreter with mpcommands. - """ - def add(self, monitor): - 'Add or replace a monitor in the registry' - slave = SlaveProcess(monitor) - name = slave.name = monitor.name - self.registry[name] = slave - - def delete(self, name): - 'Remove a named monitor from the registry' - del self.registry[name] - - def __init__(self): - self.registry = {} - self.started = False - self.mp = None - - # can be called more than once - def start(self): - if self.mp is None: - self.mp = multiprocessing.Manager() - for slave in self.registry.itervalues(): - slave.start() - self.started = True - - def stop(self): - for slave in self.registry.itervalues(): - slave.stop() - if self.mp: - self.mp.shutdown() - self.mp = None - self.started = False - - def send(self, line): - for slave in self.registry.itervalues(): - slave.send(line) - -########################## plac server ############################## - -import asyncore, asynchat, socket - -class _AsynHandler(asynchat.async_chat): - "asynchat handler starting a new interpreter loop for each connection" - - terminator = '\r\n' # the standard one for telnet - prompt = 'i> ' - - def __init__(self, socket, interpreter): - asynchat.async_chat.__init__(self, socket) - self.set_terminator(self.terminator) - self.i = interpreter - self.i.__enter__() - self.data = [] - self.write(self.prompt) - - def write(self, data, *args): - "Push a string back to the client" - if args: - data %= args - if data.endswith('\n') and not data.endswith(self.terminator): - data = data[:-1] + self.terminator # fix newlines - self.push(data) - - def collect_incoming_data(self, data): - "Collect one character at the time" - self.data.append(data) - - def found_terminator(self): - "Put in the queue the line received from the client" - line = ''.join(self.data) - self.log('Received line %r from %s' % (line, self.addr)) - if line == 'EOF': - self.i.__exit__(None, None, None) - self.handle_close() - else: - task = self.i.submit(line) - task.run() # synchronous or not - if task.etype: # manage exception - error = '%s: %s\nReceived: %s' % ( - task.etype.__name__, task.exc, ' '.join(task.arglist)) - self.log_info(task.traceback + error) # on the server - self.write(error + self.terminator) # back to the client - else: # no exception - self.write(task.str + self.terminator) - self.data = [] - self.write(self.prompt) - -class _AsynServer(asyncore.dispatcher): - "asyncore-based server spawning AsynHandlers" - - def __init__(self, interpreter, newhandler, port, listen=5): - self.interpreter = interpreter - self.newhandler = newhandler - self.port = port - asyncore.dispatcher.__init__(self) - self.create_socket(socket.AF_INET, socket.SOCK_STREAM) - self.bind(('', port)) - self.listen(listen) - - def handle_accept(self): - clientsock, clientaddr = self.accept() - self.log('Connected from %s' % str(clientaddr)) - i = self.interpreter.__class__(self.interpreter.obj) # new interpreter - self.newhandler(clientsock, i) # spawn a new handler - -########################### the Interpreter ############################# - -class Interpreter(object): - """ - A context manager with a .send method and a few utility methods: - execute, test and doctest. - """ - def __init__(self, obj, commentchar='#', split=shlex.split): - self.obj = obj - try: - self.name = obj.__module__ - except AttributeError: - self.name = 'plac' - self.commentchar = commentchar - self.split = split - self._set_commands(obj) - self.tm = TaskManager(obj) - self.man = self.tm.man - self.parser = plac_core.parser_from(obj, prog='') - if self.commands: - self.commands.update(self.tm.specialcommands) - self.parser.addsubcommands( - self.tm.specialcommands, self.tm, title='special commands') - if obj.mpcommands: - self.parser.addsubcommands( - obj.mpcommands, obj, title='commands run in external processes') - if obj.thcommands: - self.parser.addsubcommands( - obj.thcommands, obj, title='threaded commands') - self.parser.error = lambda msg: sys.exit(msg) # patch the parser - self._interpreter = None - - def _set_commands(self, obj): - "Make sure obj has the right command attributes as Python sets" - for attrname in ('commands', 'syncommands', 'mpcommands', 'thcommands'): - try: - sequence = getattr(obj, attrname) - except AttributeError: - sequence = [] - if not isinstance(sequence, set): - sequence = set(sequence) - setattr(obj, attrname, sequence) - obj.syncommands.update(obj.commands) - self.commands = obj.commands - self.commands.update(obj.syncommands) - self.commands.update(obj.mpcommands) - self.commands.update(obj.thcommands) - - def __enter__(self): - "Start the inner interpreter loop" - self._interpreter = self._make_interpreter() - self._interpreter.send(None) - return self - - def __exit__(self, exctype, exc, tb): - "Close the inner interpreter and the task manager" - self.close(exctype, exc, tb) - - def submit(self, line): - "Send a line to the underlying interpreter and return a task object" - if self._interpreter is None: - raise RuntimeError(_('%r not initialized: probably you forgot to ' - 'use the with statement') % self) - if isinstance(line, basestring): - arglist = self.split(line, self.commentchar) - else: # expects a list of strings - arglist = line - if not arglist: - return nulltask - m = self.tm.man # manager - if m and not m.started: - m.start() - task = self._interpreter.send(arglist) # nonblocking - if not plac_core._match_cmd(arglist[0], self.tm.specialcommands): - self.tm.registry[task.no] = task - if m: - m.send('add_listener %d' % task.no) - return task - - def send(self, line): - "Send a line to the underlying interpreter and return the finished task" - task = self.submit(line) - BaseTask.run(task) # blocking - return task - - def tasks(self): - "The full lists of the submitted tasks" - return self.tm.registry.values() - - def close(self, exctype=None, exc=None, tb=None): - "Can be called to close the interpreter prematurely" - self.tm.close() - if exctype is not None: - self._interpreter.throw(exctype, exc, tb) - else: - self._interpreter.close() - - def _make_interpreter(self): - "The interpreter main loop, from lists of arguments to task objects" - enter = getattr(self.obj, '__enter__', lambda : None) - exit = getattr(self.obj, '__exit__', lambda et, ex, tb: None) - enter() - task = None - try: - for no in itertools.count(1): - arglist = yield task - try: - cmd, result = self.parser.consume(arglist) - except SystemExit: # for invalid commands - task = SynTask(no, arglist, iter([])) - continue - except: # anything else - task = SynTask(no, arglist, gen_exc(*sys.exc_info())) - continue - if not plac_core.iterable(result): # atomic result - task = SynTask(no, arglist, gen_val(result)) - elif cmd in self.obj.mpcommands: - task = MPTask(no, arglist, result, self.tm.man) - elif cmd in self.obj.thcommands: - task = ThreadedTask(no, arglist, result) - else: # blocking task - task = SynTask(no, arglist, result) - except GeneratorExit: # regular exit - exit(None, None, None) - except: # exceptional exit - exit(*sys.exc_info()) - raise - - def check(self, given_input, expected_output): - "Make sure you get the expected_output from the given_input" - output = self.send(given_input).str # blocking - ok = (output == expected_output) - if not ok: - # the message here is not internationalized on purpose - msg = 'input: %s\noutput: %s\nexpected: %s' % ( - given_input, output, expected_output) - raise AssertionError(msg) - - def _parse_doctest(self, lineiter): - "Returns the lines of input, the lines of output, and the line number" - lines = [line.strip() for line in lineiter] - inputs = [] - positions = [] - for i, line in enumerate(lines): - if line.startswith('i> '): - inputs.append(line[3:]) - positions.append(i) - positions.append(len(lines) + 1) # last position - outputs = [] - for i, start in enumerate(positions[:-1]): - end = positions[i + 1] - outputs.append('\n'.join(lines[start+1:end])) - return zip(inputs, outputs, positions) - - def doctest(self, lineiter, verbose=False): - """ - Parse a text containing doctests in a context and tests of all them. - Raise an error even if a single doctest if broken. Use this for - sequential tests which are logically grouped. - """ - with self: - for input, output, no in self._parse_doctest(lineiter): - if verbose: - write('i> %s\n' % input) - write('-> %s\n' % output) - task = self.send(input) # blocking - if not str(task) == output: - msg = 'line %d: input: %s\noutput: %s\nexpected: %s\n' % ( - no + 1, input, task, output) - write(msg) - if task.exc: - raise task.etype, task.exc, task.tb - - def execute(self, lineiter, verbose=False): - "Execute a lineiter of commands in a context and print the output" - with self: - for line in lineiter: - if verbose: - write('i> ' + line) - task = self.send(line) # finished task - if task.etype: # there was an error - raise task.etype, task.exc, task.tb - write('%s\n' % task.str) - - def multiline(self, stdin=sys.stdin, terminator=';', verbose=False): - "The multiline mode is especially suited for usage with emacs" - with self: - for line in read_long_line(stdin, terminator): - task = self.submit(line) - task.run() - write('%s\n' % task.str) - if verbose and task.traceback: - write(task.traceback) - - def interact(self, stdin=sys.stdin, prompt='i> ', verbose=False): - "Starts an interactive command loop reading commands from the consolle" - try: - import readline - readline_present = True - except ImportError: - readline_present = False - if stdin is sys.stdin and readline_present: # use readline - histfile = os.path.expanduser('~/.%s.history' % self.name) - completions = list(self.commands) + ['help'] - self.stdin = ReadlineInput(completions, histfile=histfile) - else: - self.stdin = stdin - self.prompt = prompt - self.verbose = verbose - intro = self.obj.__doc__ or '' - write(intro + '\n') - with self: - if self.stdin is sys.stdin: # do not close stdin automatically - self._manage_input() - else: - with self.stdin: # close stdin automatically - self._manage_input() - - def _manage_input(self): - "Convert input lines into task which are then executed" - for line in iter(lambda : read_line(self.stdin, self.prompt), ''): - line = line.strip() - if not line: - continue - task = self.submit(line) - task.run() # synchronous or not - write(str(task) + '\n') - if self.verbose and task.etype: - write(task.traceback) - - def start_server(self, port=2199, **kw): - """Starts an asyncore server reading commands for clients and opening - a new interpreter for each connection.""" - _AsynServer(self, _AsynHandler, port) # register the server - try: - asyncore.loop(**kw) - except (KeyboardInterrupt, TerminatedProcess): - pass - finally: - asyncore.close_all() - - def add_monitor(self, mon): - self.man.add(mon) - - def del_monitor(self, name): - self.man.delete(name) - - @classmethod - def call(cls, factory, arglist=sys.argv[1:], - commentchar='#', split=shlex.split, - stdin=sys.stdin, prompt='i> ', verbose=False): - """ - Call a container factory with the arglist and instantiate an - interpreter object. If there are remaining arguments, send them to the - interpreter, else start an interactive session. - """ - obj = partial_call(factory, arglist) - if not hasattr(obj, 'help'): - # help is recognized as an alias for --help - aliases = dict(help='--help') - plac_core.parser_from(obj).alias = lambda a: aliases.get(a, a) - i = cls(obj, commentchar, split) - if i.obj._args_: - with i: - task = i.send(i.obj._args_) # synchronous - if task.exc: - raise task.etype, task.exc, task.tb - out = str(task) - if out: - print(out) - elif i.obj._interact_: - i.interact(stdin, prompt, verbose) - else: - i.parser.print_usage() - -#################################### runp ##################################### - -class _TaskLauncher(object): - "Helper for runp" - - def __init__(self, genseq, mode): - if mode == 'p': - self.mpcommands = ['rungen'] - else: - self.thcommands = ['rungen'] - self.genlist = list(genseq) - - def rungen(self, i): - for out in self.genlist[int(i) - 1]: - yield out - -def runp(genseq, mode='p', monitors=(), start=True): - """Run a sequence of generators in parallel. Mode can be 'p' (use processes) - or 't' (use threads). Return a list of running task objects. If start is - False, the tasks are only submitted and not automatically started. - """ - assert mode in 'pt', mode - launcher = _TaskLauncher(genseq, mode) - inter = Interpreter(launcher).__enter__() - for mon in monitors: # must be added before submit - inter.add_monitor(mon) - for i in range(len(launcher.genlist)): - inter.submit('rungen %d' % (i + 1)) - if start: - for task in inter.tasks(): - task.run() - return inter.tasks() diff --git a/plac/plac_runner.py b/plac/plac_runner.py deleted file mode 100644 index 2fdfc6d..0000000 --- a/plac/plac_runner.py +++ /dev/null @@ -1,65 +0,0 @@ -#!python -from __future__ import with_statement -import os, sys, shlex -import plac - -def run(fnames, cmd, verbose): - "Run batch scripts and tests" - for fname in fnames: - with open(fname) as f: - lines = list(f) - if not lines[0].startswith('#!'): - sys.exit('Missing or incorrect shebang line!') - firstline = lines[0][2:] # strip the shebang - init_args = shlex.split(firstline) - tool = plac.import_main(*init_args) - command = getattr(plac.Interpreter(tool), cmd) # doctest or execute - if verbose: - sys.stdout.write('Running %s with %s' % (fname, firstline)) - command(lines[1:], verbose=verbose) - -@plac.annotations( - verbose=('verbose mode', 'flag', 'v'), - interactive=('run plac tool in interactive mode', 'flag', 'i'), - multiline=('run plac tool in multiline mode', 'flag', 'm'), - serve=('run plac server', 'option', 's', int), - batch=('run plac batch files', 'flag', 'b'), - test=('run plac test files', 'flag', 't'), - fname='script to run (.py or .plac or .placet)', - extra='additional arguments', - ) -def main(verbose, interactive, multiline, serve, batch, test, fname=None, - *extra): - "Runner for plac tools, plac batch files and plac tests" - baseparser = plac.parser_from(main) - if fname is None: - baseparser.print_help() - elif sys.argv[1] == fname: # script mode - plactool = plac.import_main( - fname, prog=os.path.basename(sys.argv[0]) + ' ' + fname) - out = plac.call(plactool, sys.argv[2:], eager=False) - if plac.iterable(out): - for output in out: - print(output) - else: - print(out) - elif interactive or multiline or serve: - plactool = plac.import_main(fname, *extra, **{'prog': ''}) - i = plac.Interpreter(plactool) - if interactive: - i.interact(verbose=verbose) - elif multiline: - i.multiline(verbose=verbose) - elif serve: - i.start_server(serve) - elif batch: - run((fname,) + extra, 'execute', verbose) - elif test: - run((fname,) + extra, 'doctest', verbose) - print('run %s plac test(s)' % (len(extra) + 1)) - else: - baseparser.print_usage() -main.add_help = False - -if __name__ == '__main__': - plac.call(main) diff --git a/plac/setup.py b/plac/setup.py deleted file mode 100644 index 006786a..0000000 --- a/plac/setup.py +++ /dev/null @@ -1,50 +0,0 @@ -try: - from setuptools import setup -except ImportError: - from distutils.core import setup -import os.path - -def require(*modules): - """Check if the given modules are already available; if not add them to - the dependency list.""" - deplist = [] - for module in modules: - try: - __import__(module) - except ImportError: - deplist.append(module) - return deplist - -def getversion(fname): - "Get the __version__ without importing plac" - for line in open(fname): - if line.startswith('__version__'): - return eval(line[13:]) - -if __name__ == '__main__': - setup(name='plac', - version=getversion( - os.path.join(os.path.dirname(__file__), 'plac.py')), - description=('The smartest command line arguments parser ' - 'in the world'), - long_description=open('README.txt').read(), - author='Michele Simionato', - author_email='michele.simionato@gmail.com', - url='http://pypi.python.org/pypi/plac', - license="BSD License", - py_modules = ['plac_core', 'plac_ext', 'plac_tk', 'plac'], - scripts = ['plac_runner.py'], - install_requires=require('argparse', 'multiprocessing'), - use_2to3=True, - keywords="command line arguments parser", - platforms=["All"], - classifiers=['Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Topic :: Software Development :: Libraries', - 'Topic :: Utilities'], - zip_safe=False) -- cgit v1.2.1