diff options
author | Michele Simionato <michele.simionato@gmail.com> | 2011-06-13 09:57:28 +0200 |
---|---|---|
committer | Michele Simionato <michele.simionato@gmail.com> | 2011-06-13 09:57:28 +0200 |
commit | 68e5691ddde6246cd22c7cc6e0b189f2b343e661 (patch) | |
tree | afd0a525154b6540d048e039acea9db6da870e9a | |
parent | 2c9a92feab665152a02a7482bff9d31bafa2996e (diff) | |
download | micheles-68e5691ddde6246cd22c7cc6e0b189f2b343e661.tar.gz |
Removed the plac project, which now has its own page
75 files changed, 1 insertions, 21544 deletions
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 '<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) 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 @@ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="generator" content="Docutils 0.6: http://docutils.sourceforge.net/" /> <title></title> -<style type="text/css"> - -.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 } - -</style> </head> <body> -<div class="document"> - - -<div class="section" id="plac-parsing-the-command-line-the-easy-way"> -<h1><a class="toc-backref" href="#id15">Plac: Parsing the Command Line the Easy Way</a></h1> -<table class="docutils field-list" frame="void" rules="none"> -<col class="field-name" /> -<col class="field-body" /> -<tbody valign="top"> -<tr class="field"><th class="field-name">Author:</th><td class="field-body">Michele Simionato</td> -</tr> -<tr class="field"><th class="field-name">E-mail:</th><td class="field-body"><a class="reference external" href="mailto:michele.simionato@gmail.com">michele.simionato@gmail.com</a></td> -</tr> -<tr class="field"><th class="field-name">Date:</th><td class="field-body">June 2011</td> -</tr> -<tr class="field"><th class="field-name">Download page:</th><td class="field-body"><a class="reference external" href="http://pypi.python.org/pypi/plac">http://pypi.python.org/pypi/plac</a></td> -</tr> -<tr class="field"><th class="field-name">Project page:</th><td class="field-body"><a class="reference external" href="http://plac.googlecode.com/hg/doc/plac.html">http://plac.googlecode.com/hg/doc/plac.html</a></td> -</tr> -<tr class="field"><th class="field-name">Requires:</th><td class="field-body">Python 2.3+</td> -</tr> -<tr class="field"><th class="field-name">Installation:</th><td class="field-body"><tt class="docutils literal">easy_install <span class="pre">-U</span> plac</tt></td> -</tr> -<tr class="field"><th class="field-name">License:</th><td class="field-body">BSD license</td> -</tr> -</tbody> -</table> -<div class="contents topic" id="contents"> -<p class="topic-title first">Contents</p> -<ul class="simple"> -<li><a class="reference internal" href="#plac-parsing-the-command-line-the-easy-way" id="id15">Plac: Parsing the Command Line the Easy Way</a><ul> -<li><a class="reference internal" href="#the-importance-of-scaling-down" id="id16">The importance of scaling down</a></li> -<li><a class="reference internal" href="#scripts-with-required-arguments" id="id17">Scripts with required arguments</a></li> -<li><a class="reference internal" href="#scripts-with-default-arguments" id="id18">Scripts with default arguments</a></li> -<li><a class="reference internal" href="#scripts-with-options-and-smart-options" id="id19">Scripts with options (and smart options)</a></li> -<li><a class="reference internal" href="#scripts-with-flags" id="id20">Scripts with flags</a></li> -<li><a class="reference internal" href="#plac-for-python-2-x-users" id="id21">plac for Python 2.X users</a></li> -<li><a class="reference internal" href="#more-features" id="id22">More features</a></li> -<li><a class="reference internal" href="#a-realistic-example" id="id23">A realistic example</a></li> -<li><a class="reference internal" href="#keyword-arguments" id="id24">Keyword arguments</a></li> -<li><a class="reference internal" href="#final-example-a-shelve-interface" id="id25">Final example: a shelve interface</a></li> -<li><a class="reference internal" href="#plac-vs-argparse" id="id26">plac vs argparse</a></li> -<li><a class="reference internal" href="#plac-vs-the-rest-of-the-world" id="id27">plac vs the rest of the world</a></li> -<li><a class="reference internal" href="#the-future" id="id28">The future</a></li> -<li><a class="reference internal" href="#trivia-the-story-behind-the-name" id="id29">Trivia: the story behind the name</a></li> -</ul> -</li> -<li><a class="reference internal" href="#advanced-usages-of-plac" id="id30">Advanced usages of plac</a><ul> -<li><a class="reference internal" href="#introduction" id="id31">Introduction</a></li> -<li><a class="reference internal" href="#from-scripts-to-interactive-applications" id="id32">From scripts to interactive applications</a></li> -<li><a class="reference internal" href="#testing-a-plac-application" id="id33">Testing a plac application</a></li> -<li><a class="reference internal" href="#plac-easy-tests" id="id34">Plac easy tests</a></li> -<li><a class="reference internal" href="#plac-batch-scripts" id="id35">Plac batch scripts</a></li> -<li><a class="reference internal" href="#implementing-subcommands" id="id36">Implementing subcommands</a></li> -<li><a class="reference internal" href="#plac-interpreter-call" id="id37">plac.Interpreter.call</a></li> -<li><a class="reference internal" href="#readline-support" id="id38">Readline support</a></li> -<li><a class="reference internal" href="#the-plac-runner" id="id39">The plac runner</a></li> -<li><a class="reference internal" href="#a-non-class-based-example" id="id40">A non class-based example</a></li> -<li><a class="reference internal" href="#writing-your-own-plac-runner" id="id41">Writing your own plac runner</a></li> -<li><a class="reference internal" href="#long-running-commands" id="id42">Long running commands</a></li> -<li><a class="reference internal" href="#threaded-commands" id="id43">Threaded commands</a></li> -<li><a class="reference internal" href="#running-commands-as-external-processes" id="id44">Running commands as external processes</a></li> -<li><a class="reference internal" href="#managing-the-output-of-concurrent-commands" id="id45">Managing the output of concurrent commands</a></li> -<li><a class="reference internal" href="#monitor-support" id="id46">Monitor support</a></li> -<li><a class="reference internal" href="#parallel-computing-with-plac" id="id47">Parallel computing with plac</a></li> -<li><a class="reference internal" href="#the-plac-server" id="id48">The plac server</a></li> -<li><a class="reference internal" href="#summary" id="id49">Summary</a></li> -<li><a class="reference internal" href="#appendix-custom-annotation-objects" id="id50">Appendix: custom annotation objects</a></li> -</ul> -</li> -</ul> -</div> -<div class="section" id="the-importance-of-scaling-down"> -<h2><a class="toc-backref" href="#id16">The importance of scaling down</a></h2> -<p>There is no want of command line arguments parsers in the Python -world. The standard library alone contains three different modules: -<a class="reference external" href="http://docs.python.org/library/getopt.html">getopt</a> (from the stone age), -<a class="reference external" href="http://docs.python.org/library/optparse.html">optparse</a> (from Python 2.3) and <a class="reference external" href="http://argparse.googlecode.com">argparse</a> (from Python 2.7). All of -them are quite powerful and especially <a class="reference external" href="http://argparse.googlecode.com">argparse</a> is an industrial -strength solution; 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.</p> -<p>It should not be necessary to stress the importance <a class="reference external" href="http://www.welton.it/articles/scalable_systems">scaling down</a>; -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. <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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 <a class="reference external" href="http://argparse.googlecode.com">argparse</a>.</p> -<p>Technically <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> is just a simple wrapper over <a class="reference external" href="http://argparse.googlecode.com">argparse</a> which hides -most of its complexity by using a declarative interface: the argument -parser is inferred rather than written down by imperatively. Still, <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> is -surprisingly scalable upwards, even without using the underlying -<a class="reference external" href="http://argparse.googlecode.com">argparse</a>. 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 <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a>: they should -be more than enough for 99.9% of the use cases.</p> -<p><a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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, <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> -is designed to be simple to use and extremely concise, as the examples -below will show.</p> -</div> -<div class="section" id="scripts-with-required-arguments"> -<h2><a class="toc-backref" href="#id17">Scripts with required arguments</a></h2> -<p>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 <em>extremely common</em>: I need to write scripts like that nearly -every day, I wrote hundreds of them in the last few years and I have -never been happy. Here is a typical example of code I have been -writing by hand for years:</p> -<pre class="literal-block"> -# 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:])) - -</pre> -<p>As you see the whole <tt class="docutils literal">if __name__ == '__main__'</tt> 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 <em>hate</em> 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 <a class="reference external" href="http://docs.python.org/library/getopt.html">getopt</a> and <a class="reference external" href="http://docs.python.org/library/optparse.html">optparse</a> does not help, -since they are intended to manage options and not positional -arguments; the <a class="reference external" href="http://argparse.googlecode.com">argparse</a> module helps a bit and it is able to reduce -the boilerplate from nine lines to six lines:</p> -<pre class="literal-block"> -# 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) - -</pre> -<p>However, it just feels too complex to instantiate a class and to -define a parser by hand for such a trivial task.</p> -<p>The <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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 -<a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> module all you need to write is</p> -<pre class="literal-block"> -# example3.py -def main(dsn): - "Do something with the database" - print(dsn) - # ... - -if __name__ == '__main__': - import plac; plac.call(main) - -</pre> -<p>The <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> module provides for free (actually the work is done by the -underlying <a class="reference external" href="http://argparse.googlecode.com">argparse</a> module) a nice usage message:</p> -<pre class="literal-block"> -$ python example3.py -h -</pre> -<pre class="literal-block"> -usage: example3.py [-h] dsn - -Do something with the database - -positional arguments: - dsn - -optional arguments: - -h, --help show this help message and exit - -</pre> -<p>Moreover <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> manages the case of missing arguments and of too many arguments. -This is only the tip of the iceberg: <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> is able to do much more than that.</p> -</div> -<div class="section" id="scripts-with-default-arguments"> -<h2><a class="toc-backref" href="#id18">Scripts with default arguments</a></h2> -<p>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:</p> -<pre class="literal-block"> -# 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) - -</pre> -<p>Here I want to perform a query on a database table, by extracting the -most recent data: it makes sense for <tt class="docutils literal">today</tt> to be a default argument. -If there is a most used table (in this example a table called <tt class="docutils literal">'product'</tt>) -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 <a class="reference external" href="http://argparse.googlecode.com">argparse</a> would require about the same number of lines). -With <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> the entire <tt class="docutils literal">__main__</tt> block reduces to the usual two lines:</p> -<pre class="literal-block"> -if __name__ == '__main__': - import plac; plac.call(main) -</pre> -<p>In other words, six lines of boilerplate have been removed, and we get -the usage message for free:</p> -<pre class="literal-block"> -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 - -</pre> -<p>Notice that by default <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> prints the string representation -of the default values (with square brackets) in the usage message. -<a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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:</p> -<pre class="literal-block"> -# 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) - -</pre> -<p>Here is the usage message:</p> -<pre class="literal-block"> -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 - -</pre> -<p>The examples here should have made clear that <em>plac is able to figure out -the command-line arguments parser to use from the signature of the main -function</em>. This is the whole idea behind <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a>: if the intent is clear, -let's the machine take care of the details.</p> -<p><a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> is inspired to an old Python Cookbook recipe of mine (<a class="reference external" href="http://code.activestate.com/recipes/278844-parsing-the-command-line/">optionparse</a>), 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 <tt class="docutils literal">main</tt> function.</p> -<p>The idea comes from the <cite>function annotations</cite> concept, a new -feature of Python 3. An example is worth a thousand words, so here -it is:</p> -<pre class="literal-block"> -# 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) - -</pre> -<p>Here the arguments of the <tt class="docutils literal">main</tt> function have been annotated with -strings which are intented to be used in the help message:</p> -<pre class="literal-block"> -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 - -</pre> -<p><a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> is able to recognize much more complex annotations, as -I will show in the next paragraphs.</p> -</div> -<div class="section" id="scripts-with-options-and-smart-options"> -<h2><a class="toc-backref" href="#id19">Scripts with options (and smart options)</a></h2> -<p>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 -<a class="reference external" href="http://code.activestate.com/recipes/278844-parsing-the-command-line/">optionparse</a> recipe, which provides a convenient wrapper over -<a class="reference external" href="http://code.activestate.com/recipes/278844-parsing-the-command-line/">optionparse</a>. Alternatively, in the simplest cases, I have just -performed the parsing by hand. In <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> the parser is inferred by the -function annotations. Here is an example:</p> -<pre class="literal-block"> -# 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) - -</pre> -<p>Here the argument <tt class="docutils literal">command</tt> has been annotated with the tuple -<tt class="docutils literal">("SQL query", 'option', 'c')</tt>: the first string is the help string -which will appear in the usage message, the second string tells <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> -that <tt class="docutils literal">command</tt> is an option and the third string that there is also -a short form of the option <tt class="docutils literal"><span class="pre">-c</span></tt>, the long form being <tt class="docutils literal"><span class="pre">--command</span></tt>. -The usage message is the following:</p> -<pre class="literal-block"> -usage: example8.py [-h] [-c COMMAND] dsn - -positional arguments: - dsn - -optional arguments: - -h, --help show this help message and exit - -c COMMAND, --command COMMAND - SQL query - -</pre> -<p>Here are two examples of usage:</p> -<pre class="literal-block"> -$ python3 example8.py -c"select * from table" dsn -executing select * from table on dsn - -$ python3 example8.py --command="select * from table" dsn -executing select * from table on dsn -</pre> -<p>The third argument in the function annotation can be omitted: in such -case it will be assumed to be <tt class="docutils literal">None</tt>. The consequence is that -the usual dichotomy between long and short options (GNU-style options) -disappears: we get <em>smart options</em>, 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:</p> -<pre class="literal-block"> -# example6.py -def main(dsn, command: ("SQL query", 'option')): - print('executing %r on %s' % (command, dsn)) - -if __name__ == '__main__': - import plac; plac.call(main) - -</pre> -<pre class="literal-block"> -usage: example6.py [-h] [-command COMMAND] dsn - -positional arguments: - dsn - -optional arguments: - -h, --help show this help message and exit - -command COMMAND SQL query - -</pre> -<p>The following are all valid invocations ot the script:</p> -<pre class="literal-block"> -$ 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 -</pre> -<p>Notice that the form <tt class="docutils literal"><span class="pre">-command=SQL</span></tt> is recognized only for the full -option, not for its abbreviations:</p> -<pre class="literal-block"> -$ python3 example6.py -com="select" dsn -usage: example6.py [-h] [-command COMMAND] dsn -example6.py: error: unrecognized arguments: -com=select -</pre> -<p>If the option is not passed, the variable <tt class="docutils literal">command</tt> -will get the value <tt class="docutils literal">None</tt>. However, it is possible to specify a non-trivial -default. Here is an example:</p> -<pre class="literal-block"> -# 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) - -</pre> -<p>Notice that the default value appears in the help message:</p> -<pre class="literal-block"> -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 - -</pre> -<p>When you run the script and you do not pass the <tt class="docutils literal"><span class="pre">-command</span></tt> option, the -default query will be executed:</p> -<pre class="literal-block"> -$ python3 example8_.py dsn -executing 'select * from table' on dsn -</pre> -</div> -<div class="section" id="scripts-with-flags"> -<h2><a class="toc-backref" href="#id20">Scripts with flags</a></h2> -<p><a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> is able to recognize flags, i.e. boolean options which are -<tt class="docutils literal">True</tt> if they are passed to the command line and <tt class="docutils literal">False</tt> -if they are absent. Here is an example:</p> -<pre class="literal-block"> -# 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) - -</pre> -<pre class="literal-block"> -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 - -</pre> -<pre class="literal-block"> -$ python3 example9.py -v dsn -connecting to dsn -</pre> -<p>Notice that it is an error trying to specify a default for flags: the -default value for a flag is always <tt class="docutils literal">False</tt>. 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.</p> -<p>For consistency with the way the usage message is printed, I suggest -you to follow the Flag-Option-Required-Default (FORD) convention: in -the <tt class="docutils literal">main</tt> 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.</p> -<p>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. <tt class="docutils literal"><span class="pre">-zxvf</span></tt> -is an abbreviation of <tt class="docutils literal"><span class="pre">-z</span> <span class="pre">-x</span> <span class="pre">-v</span> <span class="pre">-f</span></tt>). I usually do not provide -the one-character abbreviation for options, since it does not make sense -to compose them.</p> -</div> -<div class="section" id="plac-for-python-2-x-users"> -<h2><a class="toc-backref" href="#id21">plac for Python 2.X users</a></h2> -<p>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 <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> provides a way to work -with function annotations even in Python 2.X (including Python 2.3). -There is no magic involved; you just need to add the annotations -by hand. For instance the annotated function declaration</p> -<pre class="literal-block"> -def main(dsn: "Database dsn", *scripts: "SQL scripts"): - ... -</pre> -<p>is equivalent to the following code:</p> -<pre class="literal-block"> -def main(dsn, *scripts): - ... -main.__annotations__ = dict( - dsn="Database dsn", - scripts="SQL scripts") -</pre> -<p>One should be careful to 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 -<tt class="docutils literal">plac.annotations</tt> decorator that performs the check for you:</p> -<pre class="literal-block"> -@plac.annotations( - dsn="Database dsn", - scripts="SQL scripts") -def main(dsn, *scripts): - ... -</pre> -<p>In the rest of this article I will assume that you are using Python 2.X with -X >= 4 and I will use the <tt class="docutils literal">plac.annotations</tt> decorator. Notice however -that the core features of <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> run even on Python 2.3.</p> -</div> -<div class="section" id="more-features"> -<h2><a class="toc-backref" href="#id22">More features</a></h2> -<p>One of the goals of <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> is to have a learning curve of <em>minutes</em> for -its core features, compared to the learning curve of <em>hours</em> of -<a class="reference external" href="http://argparse.googlecode.com">argparse</a>. In order to reach this goal, I have <em>not</em> sacrificed all -the features of <a class="reference external" href="http://argparse.googlecode.com">argparse</a>. Actually a lot of <a class="reference external" href="http://argparse.googlecode.com">argparse</a> power persists -in <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a>. Until now, I have only showed simple annotations, but in -general an annotation is a 6-tuple of the form</p> -<blockquote> -<tt class="docutils literal">(help, kind, abbrev, type, choices, metavar)</tt></blockquote> -<p>where <tt class="docutils literal">help</tt> is the help message, <tt class="docutils literal">kind</tt> is a string in the set { -<tt class="docutils literal">"flag"</tt>, <tt class="docutils literal">"option"</tt>, <tt class="docutils literal">"positional"</tt>}, <tt class="docutils literal">abbrev</tt> is a -one-character string or <tt class="docutils literal">None</tt>, <tt class="docutils literal">type</tt> is a callable taking a -string in input, -<tt class="docutils literal">choices</tt> is a discrete sequence of values and <tt class="docutils literal">metavar</tt> is a string.</p> -<p><tt class="docutils literal">type</tt> is used to automagically convert the command line arguments -from the string type to any Python type; by default there is no -conversion and <tt class="docutils literal">type=None</tt>.</p> -<p><tt class="docutils literal">choices</tt> is used to restrict the number of the valid -options; by default there is no restriction i.e. <tt class="docutils literal">choices=None</tt>.</p> -<p><tt class="docutils literal">metavar</tt> 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 <tt class="docutils literal">None</tt> and the name in the usage message is -the same as the argument name. For an option -the <tt class="docutils literal">metavar</tt> is used differently in the usage message, which has -now the form <tt class="docutils literal"><span class="pre">[--option-name</span> METAVAR]</tt>. If the <tt class="docutils literal">metavar</tt> is <tt class="docutils literal">None</tt>, -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.</p> -<p>Here is an example showing many of the features (copied from the -<a class="reference external" href="http://argparse.googlecode.com">argparse</a> documentation):</p> -<pre class="literal-block"> -# 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)) - -</pre> -<p>Here is the usage:</p> -<pre class="literal-block"> -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 - -</pre> -<p>Notice that the docstring of the <tt class="docutils literal">main</tt> function has been automatically added -to the usage message. Here are a couple of examples of use:</p> -<pre class="literal-block"> -$ 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') -</pre> -<p><tt class="docutils literal">plac.call</tt> can also be used in doctests like this:</p> -<pre class="doctest-block"> ->>> import plac, example10 ->>> plac.call(example10.main, ['add', '1', '2']) -3.0 -</pre> -<p><tt class="docutils literal">plac.call</tt> works for generators too:</p> -<pre class="doctest-block"> ->>> def main(n): -... for i in range(int(n)): -... yield i ->>> plac.call(main, ['3']) -[0, 1, 2] -</pre> -<p>Internally <tt class="docutils literal">plac.call</tt> 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 <tt class="docutils literal">plac.call</tt>.</p> -<p>This behavior avoids mistakes like forgetting of applying -<tt class="docutils literal">list(result)</tt> to the result of <tt class="docutils literal">plac.call</tt>; moreover it makes -errors visible early, and avoids mistakes in code like the following:</p> -<pre class="literal-block"> -try: - result = plac.call(main, args) -except: - # do something -</pre> -<p>Without the "listify" functionality, a main function returning a -generator object would not raise any exception until the generator -is iterated over.</p> -<p>If you are a fan of lazyness, you can still have it by setting the <tt class="docutils literal">eager</tt> -flag to <tt class="docutils literal">False</tt>, as in the following example:</p> -<pre class="literal-block"> -for line in plac.call(main, args, eager=False): - print(line) -</pre> -<p>If <tt class="docutils literal">main</tt> 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.</p> -</div> -<div class="section" id="a-realistic-example"> -<h2><a class="toc-backref" href="#id23">A realistic example</a></h2> -<p>Here is a more realistic script using most of the features of <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> to -run SQL queries on a database by relying on <a class="reference external" href="http://www.sqlalchemy.org/">SQLAlchemy</a>. Notice the usage -of the <tt class="docutils literal">type</tt> feature to automagically convert a SQLAlchemy connection -string into a <a class="reference external" href="http://www.sqlalchemy.org/docs/reference/ext/sqlsoup.html">SqlSoup</a> object:</p> -<pre class="literal-block"> -# 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) - -</pre> -<p>You can see the <em>yield-is-print</em> pattern here: instead of using -<tt class="docutils literal">print</tt> in the main function, I use <tt class="docutils literal">yield</tt>, and I perform the -print in the <tt class="docutils literal">__main__</tt> block. The advantage of the pattern is that -tests invoking <tt class="docutils literal">plac.call</tt> 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 <tt class="docutils literal">sys.stdout</tt> to a -<tt class="docutils literal">StringIO</tt> object.</p> -<p>Here is the usage message:</p> -<pre class="literal-block"> -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 - -</pre> -<p>You can check for yourself that the script works.</p> -</div> -<div class="section" id="keyword-arguments"> -<h2><a class="toc-backref" href="#id24">Keyword arguments</a></h2> -<p>Starting from release 0.4, <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> supports keyword arguments. In -practice that means that if your main function has keyword arguments, -<a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> treats specially arguments of the form <tt class="docutils literal">"name=value"</tt> in the -command line. Here is an example:</p> -<pre class="literal-block"> -# 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) - -</pre> -<p>Here is the generated usage message:</p> -<pre class="literal-block"> -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 - -</pre> -<p>Here is how you call the script:</p> -<pre class="literal-block"> -$ python example12.py -o X a1 a2 name=value -opt=X -args=('a1', 'a2') -kw={'name': 'value'} -</pre> -<p>When using keyword arguments, one must be careful to use names which -are not alreay taken; for instance in this examples the name <tt class="docutils literal">opt</tt> -is taken:</p> -<pre class="literal-block"> -$ 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 -</pre> -<p>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.</p> -</div> -<div class="section" id="final-example-a-shelve-interface"> -<h2><a class="toc-backref" href="#id25">Final example: a shelve interface</a></h2> -<p>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 <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> could be the following:</p> -<pre class="literal-block"> -# 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) - -</pre> -<p>A few notes are in order:</p> -<ol class="arabic simple"> -<li>I have disabled the ordinary help provided by <a class="reference external" href="http://argparse.googlecode.com">argparse</a> and I have -implemented a custom help command.</li> -<li>I have changed the prefix character used to recognize the options -to a dot.</li> -<li>Keyword arguments recognition (in the <tt class="docutils literal">**setters</tt>) is used to make it -possible to store a value in the shelve with the syntax -<tt class="docutils literal">param_name=param_value</tt>.</li> -<li><tt class="docutils literal">*params</tt> are used to retrieve parameters from the shelve and some -error checking is performed in the case of missing parameters</li> -<li>A command to clear the shelve is implemented as a flag (<tt class="docutils literal">.clear</tt>).</li> -<li>A command to delete a given parameter is implemented as an option -(<tt class="docutils literal">.delete</tt>).</li> -<li>There is an option with default (<tt class="docutils literal">.filename=conf.shelve</tt>) to store -the filename of the shelve.</li> -<li>All things considered, the code looks like a poor man object oriented -interface implemented with a chain of elifs instead of methods. Of course, -<a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> can do better than that, but let me start from a low-level approach -first.</li> -</ol> -<p>If you run <tt class="docutils literal">ishelve.py</tt> without arguments you get the following -message:</p> -<pre class="literal-block"> -$ python ishelve.py -no arguments passed, use .help to see the available commands -</pre> -<p>If you run <tt class="docutils literal">ishelve.py</tt> with the option <tt class="docutils literal">.h</tt> (or any abbreviation -of <tt class="docutils literal">.help</tt>) you get:</p> -<pre class="literal-block"> -$ python ishelve.py .h -Commands: .help, .showall, .clear, .delete -<param> ... -<param=value> ... -</pre> -<p>You can check by hand that the tool work:</p> -<pre class="literal-block"> -$ 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 -</pre> -</div> -<div class="section" id="plac-vs-argparse"> -<h2><a class="toc-backref" href="#id26">plac vs argparse</a></h2> -<p><a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> is opinionated and by design it does not try to make available -all of the features of <a class="reference external" href="http://argparse.googlecode.com">argparse</a> in an easy way. In particular you -should be aware of the following limitations/differences (the -following assumes knowledge of <a class="reference external" href="http://argparse.googlecode.com">argparse</a>):</p> -<ul class="simple"> -<li>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 <tt class="docutils literal"><span class="pre">--yield</span></tt>. In this case the destination would be <tt class="docutils literal">yield</tt>, -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 <a class="reference external" href="http://argparse.googlecode.com">argparse</a> with a suitable destination.</li> -<li><a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> does not support "required options". As the <a class="reference external" href="http://argparse.googlecode.com">argparse</a> -documentation puts it: <em>Required options are generally considered bad -form - normal users expect options to be optional. You should avoid -the use of required options whenever possible.</em> Notice that since -<a class="reference external" href="http://argparse.googlecode.com">argparse</a> supports them, <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> can manage them too, but not directly.</li> -<li><a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> supports only regular boolean flags. <a class="reference external" href="http://argparse.googlecode.com">argparse</a> has the ability to -define generalized two-value flags with values different from <tt class="docutils literal">True</tt> -and <tt class="docutils literal">False</tt>. An earlier version of <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> had this feature too, but -since you can use options with two choices instead, and in any case -the conversion from <tt class="docutils literal">{True, False}</tt> to any couple of values -can be trivially implemented with a ternary operator -(<tt class="docutils literal">value1 if flag else value2</tt>), I have removed it (KISS rules!).</li> -<li><a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> does not support <tt class="docutils literal">nargs</tt> 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 <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> and did not feel the need -to increase the learning curve by adding direct support for <tt class="docutils literal">nargs</tt>.</li> -<li><a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> does support subparsers, but you must read the <a class="reference external" href="in-writing">advanced usage -document</a> to see how it works.</li> -<li><a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> does not support actions directly. This also -looks like a feature too advanced for the goals of <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a>. Notice however -that the ability to define your own annotation objects (again, see -the <a class="reference external" href="in-writing">advanced usage document</a>) may mitigate the need for custom actions.</li> -</ul> -<p><a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> can leverage directly on many <a class="reference external" href="http://argparse.googlecode.com">argparse</a> features.</p> -<p>For instance, you can make invisible an argument in the usage message -simply by using <tt class="docutils literal"><span class="pre">'==SUPPRESS=='</span></tt> as help string (or -<tt class="docutils literal">argparse.SUPPRESS</tt>). Similarly, you can use <a class="reference external" href="http://argparse.googlecode.com/svn/tags/r11/doc/other-utilities.html?highlight=filetype#FileType">argparse.FileType</a> -directly.</p> -<p>It is also possible to pass options to the underlying -<tt class="docutils literal">argparse.ArgumentParser</tt> object (currently it accepts the default -arguments <tt class="docutils literal">description</tt>, <tt class="docutils literal">epilog</tt>, <tt class="docutils literal">prog</tt>, <tt class="docutils literal">usage</tt>, -<tt class="docutils literal">add_help</tt>, <tt class="docutils literal">argument_default</tt>, <tt class="docutils literal">parents</tt>, <tt class="docutils literal">prefix_chars</tt>, -<tt class="docutils literal">fromfile_prefix_chars</tt>, <tt class="docutils literal">conflict_handler</tt>, <tt class="docutils literal">formatter_class</tt>). -It is enough to set such attributes on the <tt class="docutils literal">main</tt> function. For -instance</p> -<pre class="literal-block"> -def main(...): - pass - -main.add_help = False -</pre> -<p>disables the recognition of the help flag <tt class="docutils literal"><span class="pre">-h,</span> <span class="pre">--help</span></tt>. This -mechanism does not look particularly elegant, but it works well -enough. I assume that the typical user of <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> will be happy with -the defaults and would not want to change them; still it is possible -if she wants to.</p> -<p>For instance, by setting the <tt class="docutils literal">description</tt> attribute, it is possible -to add a comment to the usage message (by default the docstring of the -<tt class="docutils literal">main</tt> function is used as description).</p> -<p>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:</p> -<pre class="literal-block"> -main.prefix_chars='/-' -</pre> -<p>The first prefix char (<tt class="docutils literal">/</tt>) is used -as the default for the recognition of options and flags; -the second prefix char (<tt class="docutils literal">-</tt>) is kept to keep the <tt class="docutils literal"><span class="pre">-h/--help</span></tt> option -working: however you can disable it and reimplement it, if you like, -as seen in the <tt class="docutils literal">ishelve</tt> example.</p> -<p>It is possible to access directly the underlying <a class="reference external" href="http://argparse.googlecode.com/svn/tags/r11/doc/ArgumentParser.html">ArgumentParser</a> object, by -invoking the <tt class="docutils literal">plac.parser_from</tt> utility function:</p> -<pre class="doctest-block"> ->>> import plac ->>> def main(arg): -... pass -... ->>> print(plac.parser_from(main)) #doctest: +ELLIPSIS -ArgumentParser(prog=...) -</pre> -<p>Internally <tt class="docutils literal">plac.call</tt> uses <tt class="docutils literal">plac.parser_from</tt> and adds the parser -to the main function as an attribute. When <tt class="docutils literal">plac.call(func)</tt> is -invoked multiple time, the parser is re-used and not rebuilt from scratch again.</p> -<p>I use <tt class="docutils literal">plac.parser_from</tt> in the unit tests of the module, but regular -users should not need to use it, unless they want to access <em>all</em> -of the features of <a class="reference external" href="http://argparse.googlecode.com">argparse</a> directly without calling the main function.</p> -<p>Interested readers should read the documentation of <a class="reference external" href="http://argparse.googlecode.com">argparse</a> 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 <tt class="docutils literal">main</tt> function for you. For simplicity, -<a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> does not perform any magic except the addition of the <tt class="docutils literal">.p</tt> -attribute.</p> -</div> -<div class="section" id="plac-vs-the-rest-of-the-world"> -<h2><a class="toc-backref" href="#id27">plac vs the rest of the world</a></h2> -<p>Originally <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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 <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a>:</p> -<ul class="simple"> -<li><a class="reference external" href="http://pypi.python.org/pypi/opterator">opterator</a> by Dusty Phillips</li> -<li><a class="reference external" href="http://pypi.python.org/pypi/CLIArgs">CLIArgs</a> by Pavel Panchekha</li> -<li><a class="reference external" href="http://pypi.python.org/pypi/commandline">commandline</a> by David Laban</li> -</ul> -<p>Luckily for me none of such projects had the idea of using -function annotations and <a class="reference external" href="http://argparse.googlecode.com">argparse</a>; as a consequence, they are -no match for the capabilities of <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a>.</p> -<p>Of course, there are tons of other libraries to parse the command -line. For instance <a class="reference external" href="http://pypi.python.org/pypi/Clap/0.7">Clap</a> by Matthew Frazier which appeared on PyPI -just the day before <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a>; <a class="reference external" href="http://pypi.python.org/pypi/Clap/0.7">Clap</a> is fine but it is certainly not -easier than <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a>.</p> -<p><a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> can also be used as a replacement of the <a class="reference external" href="http://docs.python.org/library/cmd.html">cmd</a> module in the standard -library and as such it shares many features with the module <a class="reference external" href="http://packages.python.org/cmd2/">cmd2</a> by -Catherine Devlin. However, this is completely coincidental, since I became -aware of the <a class="reference external" href="http://packages.python.org/cmd2/">cmd2</a> module only after writing <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a>.</p> -<p>Command-line argument parsers keep coming out; between the newcomers I -will notice <a class="reference external" href="https://github.com/pulp/marrow.script">marrow.script</a> by Alice Bevan-McGregor, which is quite -similar to <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> in spirit, but does not rely on <a class="reference external" href="http://argparse.googlecode.com">argparse</a> at all. -<a class="reference external" href="http://packages.python.org/argh">Argh</a> by Andrey Mikhaylenko is also worth mentioning: it is also based -on <a class="reference external" href="http://argparse.googlecode.com">argparse</a>, it came after <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> and I must give credit to the author -for the choice of the name, much funnier than plac!</p> -</div> -<div class="section" id="the-future"> -<h2><a class="toc-backref" href="#id28">The future</a></h2> -<p>Currently the core of <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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 <a class="reference external" href="http://argparse.googlecode.com">argparse</a>. Actually I have thought about -contributing the core back to <a class="reference external" href="http://argparse.googlecode.com">argparse</a> if <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> becomes successfull -and gains a reasonable number of users. For the moment it should be -considered in alpha status.</p> -<p>Notice that even if <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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.</p> -<p>Version 0.5 of <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> doubled the code base and the documentation: it is -based on the idea of using <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> to implement command-line interpreters, -i.e. something akin to the <tt class="docutils literal">cmd</tt> module in the standard library, only better. -The new features of <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> are described in the <a class="reference external" href="in-writing">advanced usage document</a> . -They are implemented in a separated module (<tt class="docutils literal">plac_ext.py</tt>), since -they require Python 2.5 to work, whereas <tt class="docutils literal">plac_core.py</tt> only requires -Python 2.3.</p> -</div> -<div class="section" id="trivia-the-story-behind-the-name"> -<h2><a class="toc-backref" href="#id29">Trivia: the story behind the name</a></h2> -<p>The <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> project started very humbly: I just wanted to make -easy_installable my old <a class="reference external" href="http://code.activestate.com/recipes/278844-parsing-the-command-line/">optionparse</a> recipe, and to publish it on PyPI. -The original name of <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> was optionparser and the idea behind it was -to build an <a class="reference external" href="http://docs.python.org/library/optparse.html?highlight=optionparser#optparse.OptionParser">OptionParser</a> object from the docstring of the module. -However, before doing that, I decided to check out the <a class="reference external" href="http://argparse.googlecode.com">argparse</a> module, -since I knew it was going into Python 2.7 and Python 2.7 was coming out. -Soon enough I realized two things:</p> -<ol class="arabic simple"> -<li>the single greatest idea of <a class="reference external" href="http://argparse.googlecode.com">argparse</a> was unifying the positional arguments -and the options in a single namespace object;</li> -<li>parsing the docstring was so old-fashioned, considering the existence -of functions annotations in Python 3.</li> -</ol> -<p>Putting together these two observations with the original idea of inferring the -parser I decided to build an <a class="reference external" href="http://argparse.googlecode.com/svn/tags/r11/doc/ArgumentParser.html">ArgumentParser</a> object from function -annotations. The <tt class="docutils literal">optionparser</tt> name was ruled out, since I was -now using <a class="reference external" href="http://argparse.googlecode.com">argparse</a>; a name like <tt class="docutils literal">argparse_plus</tt> was also ruled out, -since the typical usage was completely different from the <a class="reference external" href="http://argparse.googlecode.com">argparse</a> usage.</p> -<p>I made a research on PyPI and the name <em>clap</em> (Command Line Arguments Parser) -was not taken, so I renamed everything to clap. After two days -a <a class="reference external" href="http://pypi.python.org/pypi/Clap/0.7">Clap</a> module appeared on PyPI <expletives deleted>!</p> -<p>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!</p> -<p>That concludes the section about the basic usage of <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a>. You are now ready to -read about the advanced usage.</p> -</div> -</div> -<div class="section" id="advanced-usages-of-plac"> -<h1><a class="toc-backref" href="#id30">Advanced usages of plac</a></h1> -<div class="section" id="introduction"> -<h2><a class="toc-backref" href="#id31">Introduction</a></h2> -<p>One of the design goals of <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> is to make it dead easy to write a -scriptable and testable interface for an application. You can use -<a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> whenever you have an API with strings in input and strings in -output, and that includes a <em>huge</em> domain of applications.</p> -<p>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 <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> and can be run as batch scripts.</p> -<p>Actually, at the most general level, you can see <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> as a generic tool to -write domain specific languages (DSL). With <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> you -can test your application interactively as well as with batch -scripts, and even with the analogous of Python doctests for your -defined language.</p> -<p>You can easily replace the <tt class="docutils literal">cmd</tt> module of the standard library and -you could easily write an application like <a class="reference external" href="http://twill.idyll.org/">twill</a> with <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a>. Or you -could use it to script your building procedure. <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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 <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a>. When speaking of things -you can do with <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a>, your imagination is the only limit!</p> -</div> -<div class="section" id="from-scripts-to-interactive-applications"> -<h2><a class="toc-backref" href="#id32">From scripts to interactive applications</a></h2> -<p>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. <tt class="docutils literal">plac</tt> provides an -<tt class="docutils literal">Interpreter</tt> class just for this purpose.</p> -<p>The <tt class="docutils literal">Interpreter</tt> class wraps the main function of a script and -provides an <tt class="docutils literal">.interact</tt> method to start an interactive interpreter -reading commands from the console.</p> -<p>For instance, you can define an interactive interpreter on top of the -<tt class="docutils literal">ishelve</tt> script introduced in the <a class="reference external" href="http://plac.googlecode.com/hg/doc/plac.html">basic documentation</a> as -follows:</p> -<pre class="literal-block"> -# 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) - -</pre> -<p>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 <tt class="docutils literal"><span class="pre">-h/--help</span></tt> flag whereas the -internal interface only recognizes the <tt class="docutils literal">.help</tt> command:</p> -<pre class="literal-block"> -$ python shelve_interpreter.py -h -</pre> -<pre class="literal-block"> -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 - -</pre> -<p>Thanks to this ingenuous trick, the script can be run both interactively -and non-interactively:</p> -<pre class="literal-block"> -$ python shelve_interpreter.py .clear # non-interactive use -cleared the shelve -</pre> -<p>Here is an usage session:</p> -<pre class="literal-block"> -$ 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] -</pre> -<p>The <tt class="docutils literal">.interact</tt> 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 <tt class="docutils literal"><span class="pre">prompt='i></span> '</tt> 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. -<tt class="docutils literal">plac</tt> also understands command abbreviations: in this example -<tt class="docutils literal">del</tt> is an abbreviation for <tt class="docutils literal">delete</tt>. In case of ambiguous -abbreviations <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> raises a <tt class="docutils literal">NameError</tt>.</p> -<p>Finally I must notice that the <tt class="docutils literal">plac.Interpreter</tt> 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.</p> -</div> -<div class="section" id="testing-a-plac-application"> -<h2><a class="toc-backref" href="#id33">Testing a plac application</a></h2> -<p>You can conveniently test your application in interactive mode. -However manual testing is a poor substitute for automatic testing.</p> -<p>In principle, one could write automatic tests for the -<tt class="docutils literal">ishelve</tt> application by using <tt class="docutils literal">plac.call</tt> directly:</p> -<pre class="literal-block"> -# 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() - -</pre> -<p>However, using <tt class="docutils literal">plac.call</tt> is not especially nice. The big -issue is that <tt class="docutils literal">plac.call</tt> responds to invalid input by printing an -error message on stderr and by raising a <tt class="docutils literal">SystemExit</tt>: this is -certainly not a nice thing to do in a test.</p> -<p>As a consequence of this behavior it is impossible to test for invalid -commands, unless you wrap the <tt class="docutils literal">SystemExit</tt> exception by -hand each time (a possibly you do something with the error message in -stderr too). Luckily, <tt class="docutils literal">plac</tt> offers a better testing support through -the <tt class="docutils literal">check</tt> method of <tt class="docutils literal">Interpreter</tt> objects:</p> -<pre class="literal-block"> -# 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') - -</pre> -<p>The method <tt class="docutils literal">.check(given_input, expected_output)</tt> works on strings -and raises an <tt class="docutils literal">AssertionError</tt> if the output produced by the -interpreter is different from the expected output for the given input. -Notice that <tt class="docutils literal">AssertionError</tt> is catched by tools like <tt class="docutils literal">py.test</tt> and -<tt class="docutils literal">nosetests</tt> and actually <tt class="docutils literal">plac</tt> tests are intended to be run with -such tools.</p> -<p>Interpreters offer a minor syntactic advantage with respect to calling -<tt class="docutils literal">plac.call</tt> directly, but they offer a <em>major</em> semantic advantage when things -go wrong (read exceptions): an <tt class="docutils literal">Interpreter</tt> object internally invokes -something like <tt class="docutils literal">plac.call</tt>, but it wraps all exceptions, so that <tt class="docutils literal">i.check</tt> -is guaranteed not to raise any exception except <tt class="docutils literal">AssertionError</tt>.</p> -<p>Even the <tt class="docutils literal">SystemExit</tt> exception is captured and you can write your test as</p> -<blockquote> -<tt class="docutils literal"><span class="pre">i.check('-cler',</span> 'SystemExit: unrecognized arguments: <span class="pre">-cler')</span></tt></blockquote> -<p>without risk of exiting from the Python interpreter.</p> -<p>There is a second advantage of interpreters: if the main function contains some -initialization code and finalization code -(<tt class="docutils literal">__enter__</tt> and <tt class="docutils literal">__exit__</tt> functions) they will be run only -once at the beginning and at the end of the interpreter loop. -<tt class="docutils literal">plac.call</tt> instead ignores the initialization/finalization code.</p> -</div> -<div class="section" id="plac-easy-tests"> -<h2><a class="toc-backref" href="#id34">Plac easy tests</a></h2> -<p>Writing your tests in terms of <tt class="docutils literal">Interpreter.check</tt> is certainly an -improvement over writing them in terms of <tt class="docutils literal">plac.call</tt>, but they -are still too low-level for my taste. The <tt class="docutils literal">Interpreter</tt> class provides -support for doctest-style tests, a.k.a. <em>plac easy tests</em>.</p> -<p>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 <tt class="docutils literal">ishelve.placet</tt> (the <tt class="docutils literal">.placet</tt> -extension is a mnemonic for plac easy tests):</p> -<pre class="literal-block"> -#!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 - -</pre> -<p>Notice the precence of the shebang line containing the name of the -<a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> tool to test (a <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> tool is just a Python module with a -function called <tt class="docutils literal">main</tt>). 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.</p> -<p>You can test <tt class="docutils literal">ishelve.placet</tt> file by calling the -<tt class="docutils literal">.doctest</tt> method of the interpreter, as in this example:</p> -<pre class="literal-block"> -$ python -c"import plac, ishelve -plac.Interpreter(ishelve.main).doctest(open('ishelve.placet'), verbose=True)" -</pre> -<p>Internally <tt class="docutils literal">Interpreter.doctests</tt> invokes something like <tt class="docutils literal">Interpreter.check</tt> -multiple times inside the same context and compare the output with the -expected output: if even a check fails, the whole test fail.</p> -<p>You should realize tha the easy tests supported by <tt class="docutils literal">plac</tt> are <em>not</em> -unittests: they are functional tests. They model then user interaction and the -order of the operations generally matters. The single subtests in a -<tt class="docutils literal">.placet</tt> file are not independent and it makes sense to exit -immediately at the first failure.</p> -<p>The support for doctests in <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> comes nearly for free, thanks to the -<a class="reference external" href="http://docs.python.org/library/shlex.html">shlex</a> module in the standard library, which is able to parse simple -languages as the ones you can implement with <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a>. In particular, -thanks to <a class="reference external" href="http://docs.python.org/library/shlex.html">shlex</a>, <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> is able to recognize comments (the default -comment character is <tt class="docutils literal">#</tt>), escape sequences and more. Look at the -<a class="reference external" href="http://docs.python.org/library/shlex.html">shlex</a> 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 <tt class="docutils literal">split(line, -commentchar)</tt>.</p> -<p>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 <tt class="docutils literal">.doctest</tt> method a list of -strings corresponding to the lines of the file.</p> -<p>At the present <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> does not use any code from the doctest -module, but the situation may change in the future (it would be nice -if <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> could reuse doctests directives like ELLIPSIS).</p> -<p>It is straighforward to integrate your <tt class="docutils literal">.placet</tt> tests with standard -testing tools. For instance, you can integrate your doctests with <tt class="docutils literal">nose</tt> -or <tt class="docutils literal">py.test</tt> as follow:</p> -<pre class="literal-block"> -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:] -</pre> -<p>Here you should notice that usage of <tt class="docutils literal">plac.import_main</tt>, 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 <tt class="docutils literal">PLACPATH</tt> and it searches -the plac tool in the directories specified there (<tt class="docutils literal">PLACPATH</tt> is just -a string containing directory names separated by colons). If the variable -<tt class="docutils literal">PLACPATH</tt> is not defined, it just looks in the current directory. -If the plac tool is not found, an <tt class="docutils literal">ImportError</tt> is raised.</p> -</div> -<div class="section" id="plac-batch-scripts"> -<h2><a class="toc-backref" href="#id35">Plac batch scripts</a></h2> -<p>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. -<a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> interpreters provide an <tt class="docutils literal">.execute</tt> method to perform just that.</p> -<p>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 -<tt class="docutils literal">Interpreter.execute</tt> makes sure that any error raised by -<tt class="docutils literal">plac.call</tt> internally is re-raised. In other words, <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> -interpreters <em>wrap the errors, but does not eat them</em>: the errors are -always accessible and can be re-raised on demand.</p> -<p>The exception is the case of invalid commands, which are skipped. -Consider for instance the following batch file, which contains a -mispelled command (<tt class="docutils literal">.dl</tt> instead of <tt class="docutils literal">.del</tt>):</p> -<pre class="literal-block"> -#!ishelve.py -.clear -a=1 b=2 -.show -.del a -.dl b -.show - -</pre> -<p>If you execute the batch file, the interpreter will print a <tt class="docutils literal">.dl: not found</tt> -at the <tt class="docutils literal">.dl</tt> line and will continue:</p> -<pre class="literal-block"> -$ 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 -</pre> -<p>The <tt class="docutils literal">verbose</tt> flag is there to show the lines which are being interpreted -(prefixed by <tt class="docutils literal">i></tt>). This is done on purpose, so that you can cut and paste -the output of the batch script and turn it into a <tt class="docutils literal">.placet</tt> test -(cool, isn't it?).</p> -</div> -<div class="section" id="implementing-subcommands"> -<h2><a class="toc-backref" href="#id36">Implementing subcommands</a></h2> -<p>When I discussed the <tt class="docutils literal">ishelve</tt> implementation in the <a class="reference external" href="http://plac.googlecode.com/hg/doc/plac.html">basic -documentation</a>, I said that it looked like a poor man implementation -of an object system as a chain of elifs; I also said that <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> was -able to do much better than that. Here I will substantiate my claim.</p> -<p><a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> is actually able to infer a set of subparsers from a -generic container of commands. This is useful if you want to -implement <em>subcommands</em> (a familiar example of a command-line -application featuring subcommands is subversion).</p> -<p>Technically a container of commands is any object with a <tt class="docutils literal">.commands</tt> attribute -listing a set of functions or methods which are valid commands. A command -container may have initialization/finalization hooks (<tt class="docutils literal">__enter__/__exit__</tt>) -and dispatch hooks (<tt class="docutils literal">__missing__</tt>, invoked for invalid command names). -Moreover, only when using command containers <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> is able to provide -automatic autocompletion of commands.</p> -<p>The shelve interface can be rewritten in an object-oriented way as follows:</p> -<pre class="literal-block"> -# 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) - -</pre> -<p><tt class="docutils literal">plac.Interpreter</tt> objects wrap context manager objects -consistently. In other words, if you wrap an object with -<tt class="docutils literal">__enter__</tt> and <tt class="docutils literal">__exit__</tt> methods, they are invoked in the right -order (<tt class="docutils literal">__enter__</tt> before the interpreter loop starts and -<tt class="docutils literal">__exit__</tt> after the interpreter loop ends, both in the regular and -in the exceptional case). In our example, the methods <tt class="docutils literal">__enter__</tt> -and <tt class="docutils literal">__exit__</tt> 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 <tt class="docutils literal">show</tt> and <tt class="docutils literal">delete</tt> methods -on purpose, to verify that <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> works correctly in the presence of -exceptions.</p> -<p>When working with command containers, <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> automatically adds two -special commands to the set of provided commands: <tt class="docutils literal">help</tt> -and <tt class="docutils literal">.last_tb</tt>. The <tt class="docutils literal">help</tt> command is the easier to understand: -when invoked without arguments it displays the list of available commands -with the same formatting of the <a class="reference external" href="http://docs.python.org/library/cmd.html">cmd</a> module; when invoked with the name of -a command it displays the usage message for that command. -The <tt class="docutils literal">.last_tb</tt> command is useful when debugging: in case of errors, -it allows you to display the traceback of the last executed command.</p> -<p>Here is a session of usage on an Unix-like operating system:</p> -<pre class="literal-block"> -$ 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> -</pre> -<p>Notice that in interactive mode the traceback is hidden, unless -you pass the <tt class="docutils literal">verbose</tt> flag to the <tt class="docutils literal">Interpreter.interact</tt> method.</p> -<p>CHANGED IN VERSION 0.9: if you have an old version of <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> the -<tt class="docutils literal">help</tt> command must be prefixed with a dot, i.e. you must write -<tt class="docutils literal">.help</tt>. The old behavior was more consistent in my opinion, since -it made it clear that the <tt class="docutils literal">help</tt> 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 <tt class="docutils literal">help</tt> command is just an alias for <tt class="docutils literal"><span class="pre">--help</span></tt>, in the -sense that there is a preprocessor step replacing <tt class="docutils literal">help</tt> with <tt class="docutils literal"><span class="pre">--help</span></tt> -in the argument list. -Notice that if you implement a custom <tt class="docutils literal">help</tt> command in the commander class -the preprocessor will be automatically disabled: passing <tt class="docutils literal">help</tt> -will call the custom help command, just as you would expect.</p> -</div> -<div class="section" id="plac-interpreter-call"> -<h2><a class="toc-backref" href="#id37">plac.Interpreter.call</a></h2> -<p>At the core of <tt class="docutils literal">plac</tt> there is the <tt class="docutils literal">call</tt> function which invokes -a callable with the list of arguments passed at the command-line -(<tt class="docutils literal">sys.argv[1:]</tt>). Thanks to <tt class="docutils literal">plac.call</tt> you can launch your module -by simply adding the lines:</p> -<pre class="literal-block"> -if __name__ == '__main__': - plac.call(main) -</pre> -<p>Everything works fine if <tt class="docutils literal">main</tt> is a simple callable performing some -action; however, in many cases, one has a <tt class="docutils literal">main</tt> "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 -<tt class="docutils literal">ShelveInterface</tt>, and the two lines needed to run the module are -a bit ugly:</p> -<pre class="literal-block"> -if __name__ == '__main__': - plac.Interpreter(plac.call(ShelveInterface)).interact() -</pre> -<p>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 <tt class="docutils literal">__enter__</tt> and <tt class="docutils literal">__exit__</tt>) -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 <tt class="docutils literal">plac.Interpreter</tt> provides -a classmethod named <tt class="docutils literal">.call</tt> 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 <tt class="docutils literal">ShelveInterface</tt> -into a script</p> -<pre class="literal-block"> -# ishelve3.py -from ishelve2 import ShelveInterface as main - -if __name__ == '__main__': - import plac; plac.Interpreter.call(main) - -</pre> -<p>and here are a few examples of usage:</p> -<pre class="literal-block"> -$ 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 -</pre> -<p>If you pass the <tt class="docutils literal"><span class="pre">-i</span></tt> flag in the command line, then the -script will enter in interactive mode and ask the user -for the commands to execute:</p> -<pre class="literal-block"> -$ 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> -</pre> -<p>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 <tt class="docutils literal">shelve_interpreter.py</tt> example), whereas here I -have show how to turn an interactive application into a script.</p> -<p>The complete signature of <tt class="docutils literal">plac.Interpreter.call</tt> is the following:</p> -<pre class="literal-block"> -call(factory, arglist=sys.argv[1:], - commentchar='#', split=shlex.split, - stdin=sys.stdin, prompt='i> ', verbose=False) -</pre> -<p>The factory must have a fixed number of positional arguments (no -default arguments, no varargs, no kwargs), otherwise a <tt class="docutils literal">TypeError</tt> -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 -<tt class="docutils literal">sys.argv[1:]</tt> (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.</p> -</div> -<div class="section" id="readline-support"> -<h2><a class="toc-backref" href="#id38">Readline support</a></h2> -<p>Starting from release 0.6 <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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 <tt class="docutils literal">.history</tt> file, or to change the case sensitivity, -the way to go is to pass a <tt class="docutils literal">plac.ReadlineInput</tt> object to the -interpreter. Here is an example, assuming you want to build a -database interface understanding SQL commands:</p> -<pre class="literal-block"> -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> ') - -</pre> -<p>Here is an example of usage:</p> -<pre class="literal-block"> -$ python sql_interface.py <some dsn> -sql> SELECT a.* FROM TABLE1 AS a INNER JOIN TABLE2 AS b ON a.id = b.id -... -</pre> -<p>You can check that entering just <tt class="docutils literal">sel</tt> and pressing TAB the readline library -completes the <tt class="docutils literal">SELECT</tt> keyword for you and makes it upper case; idem for -<tt class="docutils literal">FROM</tt>, <tt class="docutils literal">INNER</tt>, <tt class="docutils literal">JOIN</tt> 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 <tt class="docutils literal"><span class="pre">~/.sql_interface.history</span></tt> when -exiting from the command-line interface.</p> -<p>If the readline library is not available, my suggestion is to use the -<a class="reference external" href="http://freshmeat.net/projects/rlwrap/">rlwrap</a> tool which provides similar features, at least on Unix-like -platforms. <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> should also work fine on Windows with the <a class="reference external" href="http://ipython.scipy.org/moin/PyReadline/Intro">pyreadline</a> -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 <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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 <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> licence does -not need to be the GPL (actually it is a BSD -do-whatever-you-want-with-it licence).</p> -<p>The interactive mode of <tt class="docutils literal">plac</tt> can be used as a replacement of the -<a class="reference external" href="http://docs.python.org/library/cmd.html">cmd</a> module in the standard library. It is actually better than <a class="reference external" href="http://docs.python.org/library/cmd.html">cmd</a>: -for instance, the <tt class="docutils literal">help</tt> command is more powerful, since it -provides information about the arguments accepted by the given command:</p> -<pre class="literal-block"> -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 -</pre> -<p>As you can imagine, the help message is provided by the underlying <a class="reference external" href="http://argparse.googlecode.com">argparse</a> -subparser (there is a subparser for each command). <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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 <a class="reference external" href="http://argparse.googlecode.com">argparse</a> which should be reimplemented from scratch -using <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a>.</p> -<p>Moreover at the moment <tt class="docutils literal">plac</tt> also understands command abbreviations. -However, this feature may disappear in -future releases. It was meaningful in the past, when <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> did not support -readline.</p> -<p>Notice that if an abbreviation is ambiguous, <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> warns you:</p> -<pre class="literal-block"> -i> sh -NameError: Ambiguous command 'sh': matching ['showall', 'show'] -</pre> -</div> -<div class="section" id="the-plac-runner"> -<h2><a class="toc-backref" href="#id39">The plac runner</a></h2> -<p>The distribution of <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> includes a runner script named <tt class="docutils literal">plac_runner.py</tt>, -which will be installed in a suitable directory in your system by <a class="reference external" href="http://docs.python.org/distutils/">distutils</a> -(say in <tt class="docutils literal">\usr\local\bin\plac_runner.py</tt> in a Unix-like operative system). -The runner provides many facilities to run <tt class="docutils literal">.plac</tt> scripts and -<tt class="docutils literal">.placet</tt> files, as well as Python modules containg a <tt class="docutils literal">main</tt> -object, which can be a function, a command container object or -even a command container class.</p> -<p>For instance, suppose you want to execute a script containing commands -defined in the <tt class="docutils literal">ishelve2</tt> module like the following one:</p> -<pre class="literal-block"> -#!ishelve2.py:ShelveInterface -c ~/conf.shelve -set a 1 -del a -del a # intentional error - -</pre> -<p>The first line of the <tt class="docutils literal">.plac</tt> 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 -<tt class="docutils literal">:ShelveInterface</tt> 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:</p> -<pre class="literal-block"> -$ 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') -</pre> -<p>The last command intentionally contained an error, to show that the -plac runner does not eat the traceback.</p> -<p>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</p> -<blockquote> -<tt class="docutils literal">alias <span class="pre">plac="plac_runner.py"</span></tt></blockquote> -<p>(or you define a suitable <tt class="docutils literal">plac.bat</tt> script in Windows) you can -run the <tt class="docutils literal">ishelve2.py</tt> script in interactive mode as -follows:</p> -<pre class="literal-block"> -$ 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 -</pre> -<p>Now you can cut and paste the interactive session an turns into into -a <tt class="docutils literal">.placet</tt> file like the following:</p> -<pre class="literal-block"> -#!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 - -</pre> -<p>Notice that the first line specifies a test database -<tt class="docutils literal">~/test.shelve</tt>, to avoid clobbering your default shelve. If you -mispell the arguments in the first line plac will give you an -<a class="reference external" href="http://argparse.googlecode.com">argparse</a> error message (just try).</p> -<p>You can run placets following the shebang convention directly with -the plac runner:</p> -<pre class="literal-block"> -$ plac --test ishelve2.placet -run 1 plac test(s) -</pre> -<p>If you want to see the output of the tests, pass the <tt class="docutils literal"><span class="pre">-v/--verbose</span></tt> flag. -Notice that he runner ignore the extension, so you can actually use any -extension your like, but <em>it relies on the first line of the file to invoke -the corresponding plac tool with the given arguments</em>.</p> -<p>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 <tt class="docutils literal">.placet</tt> files into a directory and its subdirectories -as follows:</p> -<pre class="literal-block"> -$ find . -name \*.placet | xargs plac_runner.py -t -</pre> -<p>The plac runner expects the main function of your script to -return a plac tool, i.e. a function or an object with a <tt class="docutils literal">.commands</tt> -attribute. It this is not the case the runner gracefully exits.</p> -<p>It also works in non-interactive mode, if you call it as</p> -<blockquote> -<tt class="docutils literal">$ plac module.py args ...</tt></blockquote> -<p>Here is an example:</p> -<pre class="literal-block"> -$ plac ishelve.py a=1 -setting a=1 -$ plac ishelve.py .show -a=1 -</pre> -<p>Notice that in non-interactive mode the runner just invokes <tt class="docutils literal">plac.call</tt> -on the <tt class="docutils literal">main</tt> object of the Python module.</p> -</div> -<div class="section" id="a-non-class-based-example"> -<h2><a class="toc-backref" href="#id40">A non class-based example</a></h2> -<p><a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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 <tt class="docutils literal">.commands</tt> attribute and possibly -<tt class="docutils literal">__enter__</tt> and/or <tt class="docutils literal">__exit__</tt> attributes.</p> -<p>In particular, a Python module is a perfect container of commands. As an -example, consider the following module implementing a fake Version -Control System:</p> -<pre class="literal-block"> -"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! - -</pre> -<p>Notice that I have defined both an <tt class="docutils literal">__exit__</tt> hook and a <tt class="docutils literal">__missing__</tt> -hook, invoked for non-existing commands. -The real trick here is the line <tt class="docutils literal">main = __import__(__name__)</tt>, which -define <tt class="docutils literal">main</tt> to be an alias for the current module.</p> -<p>The <tt class="docutils literal">vcs</tt> module does not contain an <tt class="docutils literal">if __name__ == '__main__'</tt> -block, but you can still run it through the plac runner -(try <tt class="docutils literal">plac vcs.py <span class="pre">-h</span></tt>):</p> -<pre class="literal-block"> -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 - -</pre> -<p>You can get help for the subcommands by postponing <tt class="docutils literal"><span class="pre">-h</span></tt> after the -name of the command:</p> -<pre class="literal-block"> -$ 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 -</pre> -<p>Notice how the docstring of the command is automatically shown in -usage message, as well as the documentation for the sub flag <tt class="docutils literal"><span class="pre">-q</span></tt>.</p> -<p>Here is an example of a non-interactive session:</p> -<pre class="literal-block"> -$ plac vcs.py check url -checkout -url -$ plac vcs.py st -q -status -True -$ plac vcs.py co -commit -None -</pre> -<p>and here is an interactive session:</p> -<pre class="literal-block"> -$ 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 -</pre> -<p>Notice the invocation of the <tt class="docutils literal">__missing__</tt> hook for non-existing commands. -Notice also that the <tt class="docutils literal">__exit__</tt> hook gets called only in interactive -mode.</p> -<p>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.</p> -</div> -<div class="section" id="writing-your-own-plac-runner"> -<h2><a class="toc-backref" href="#id41">Writing your own plac runner</a></h2> -<p>The runner included in the <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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 <tt class="docutils literal">Interpreter</tt> class is the <tt class="docutils literal">.send</tt> method, which takes -strings in input and returns a four-tuple with attributes -<tt class="docutils literal">.str</tt>, <tt class="docutils literal">.etype</tt>, <tt class="docutils literal">.exc</tt> and <tt class="docutils literal">.tb</tt>:</p> -<ul class="simple"> -<li><tt class="docutils literal">.str</tt> is the output of the command, if successful (a string);</li> -<li><tt class="docutils literal">.etype</tt> is the class of the exception, if the command fail;</li> -<li><tt class="docutils literal">.exc</tt> is the exception instance;</li> -<li><tt class="docutils literal">.tb</tt> is the traceback.</li> -</ul> -<p>Moreover the <tt class="docutils literal">__str__</tt> 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).</p> -<p>For instance, if you send a mispelled option to -the interpreter a <tt class="docutils literal">SystemExit</tt> will be trapped:</p> -<pre class="doctest-block"> ->>> import plac ->>> from ishelve import ishelve ->>> with plac.Interpreter(ishelve) as i: -... print(i.send('.cler')) -... -SystemExit: unrecognized arguments: .cler -</pre> -<p>It is important to invoke the <tt class="docutils literal">.send</tt> method inside the context manager, -otherwise you will get a <tt class="docutils literal">RuntimeError</tt>.</p> -<p>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):</p> -<pre class="literal-block"> -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() -</pre> -<p>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.</p> -<p>An example of GUI program built on top of <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> is given later on, in the -paragraph <em>Managing the output of concurrent commands</em> (using Tkinter -for simplicity and portability).</p> -<p>There is a final <em>caveat</em>: since the plac interpreter loop is -implemented via extended generators, plac interpreters are single threaded: you -will get an error if you <tt class="docutils literal">.send</tt> 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:</p> -<pre class="literal-block"> -with interpreter: - for input_value in iter(input_queue.get, EXIT): - output_queue.put(interpreter.send(input_value)) -</pre> -<p>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 <a class="reference external" href="http://docs.python.org/library/multiprocessing.html">multiprocessing</a> module.</p> -</div> -<div class="section" id="long-running-commands"> -<h2><a class="toc-backref" href="#id42">Long running commands</a></h2> -<p>As we saw, by default a <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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:</p> -<pre class="literal-block"> -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) - -</pre> -<p>If you run the <tt class="docutils literal">import_file</tt> command, you will have to wait for 200 seconds -before entering a new command:</p> -<pre class="literal-block"> -$ 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 -</pre> -<p>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. <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> provides two ways to reach this goal: threads -and processes.</p> -</div> -<div class="section" id="threaded-commands"> -<h2><a class="toc-backref" href="#id43">Threaded commands</a></h2> -<p>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</p> -<blockquote> -<tt class="docutils literal">commands = ['import_file']</tt></blockquote> -<p>with</p> -<blockquote> -<tt class="docutils literal">thcommands = ['import_file']</tt></blockquote> -<p>to tell to the <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> interpreter that the command <tt class="docutils literal">import_file</tt> should be -run into a separated thread. Here is an example session:</p> -<pre class="literal-block"> -i> import_file file1 -<ThreadedTask 1 [import_file file1] RUNNING> -</pre> -<p>The import task started in a separated thread. You can see the -progress of the task by using the special command <tt class="docutils literal">.output</tt>:</p> -<pre class="literal-block"> -i> .output 1 -<ThreadedTask 1 [import_file file1] RUNNING> -Imported 100 lines -Imported 200 lines -</pre> -<p>If you look after a while, you will get more lines of output:</p> -<pre class="literal-block"> -i> .output 1 -<ThreadedTask 1 [import_file file1] RUNNING> -Imported 100 lines -Imported 200 lines -Imported 300 lines -Imported 400 lines -</pre> -<p>If you look after a time long enough, the task will be finished:</p> -<pre class="literal-block"> -i> .output 1 -<ThreadedTask 1 [import_file file1] FINISHED> -</pre> -<p>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):</p> -<pre class="literal-block"> -i> .output 1 /tmp/out.txt -saved output of 1 into /tmp/out.txt -</pre> -<p>You can even skip the number argument: then <tt class="docutils literal">.output</tt> will the return -the output of the last launched command (the special commands like .output -do not count).</p> -<p>You can launch many tasks one after the other:</p> -<pre class="literal-block"> -i> import_file file2 -<ThreadedTask 5 [import_file file2] RUNNING> -i> import_file file3 -<ThreadedTask 6 [import_file file3] RUNNING> -</pre> -<p>The <tt class="docutils literal">.list</tt> command displays all the running tasks:</p> -<pre class="literal-block"> -i> .list -<ThreadedTask 5 [import_file file2] RUNNING> -<ThreadedTask 6 [import_file file3] RUNNING> -</pre> -<p>It is even possible to kill a task:</p> -<pre class="literal-block"> -i> .kill 5 -<ThreadedTask 5 [import_file file2] TOBEKILLED> -# wait a bit ... -closing the file -i> .output 5 -<ThreadedTask 5 [import_file file2] KILLED> -</pre> -<p>You should notice that since at the Python level it is impossible to kill -a thread, the <tt class="docutils literal">.kill</tt> commands works by setting the status of the task to -<tt class="docutils literal">TOBEKILLED</tt>. Internally the generator corresponding to the command -is executed in the thread and the status is checked at each iteration: -when the status become <tt class="docutils literal">TOBEKILLED</tt> a <tt class="docutils literal">GeneratorExit</tt> exception is -raised and the thread terminates (softly, so that the <tt class="docutils literal">finally</tt> 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:</p> -<pre class="literal-block"> -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) - -</pre> -</div> -<div class="section" id="running-commands-as-external-processes"> -<h2><a class="toc-backref" href="#id44">Running commands as external processes</a></h2> -<p>Threads are not loved much in the Python world and actually most people -prefer to use processes instead. For this reason <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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 <a class="reference external" href="http://docs.python.org/library/multiprocessing.html">multiprocessing</a> -module.</p> -<p>In our example, to enable the feature it is sufficient to replace the line</p> -<blockquote> -<tt class="docutils literal">thcommands = ['import_file']</tt></blockquote> -<p>with</p> -<blockquote> -<tt class="docutils literal">mpcommands = ['import_file']</tt>.</blockquote> -<p>The user experience is exactly the same as with threads and you will not see any -difference at the user interface level:</p> -<pre class="literal-block"> -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> -</pre> -<p>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.</p> -<p>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 <tt class="docutils literal">TOBEKILLED</tt> mechanism. Moreover, the killing is -guaranteed to be soft: internally a command receiving a SIGTERM raises -a <tt class="docutils literal">TerminatedProcess</tt> exception which is trapped in the generator -loop, so that the command is closed properly.</p> -<p>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.</p> -</div> -<div class="section" id="managing-the-output-of-concurrent-commands"> -<h2><a class="toc-backref" href="#id45">Managing the output of concurrent commands</a></h2> -<p><a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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 <tt class="docutils literal">.submit</tt> method which returns a task -object and a <tt class="docutils literal">.tasks</tt> method returning the list of all the tasks -submitted to the interpreter. The <tt class="docutils literal">submit</tt> method does not start the task -and thus it is nonblocking. -Each task has an <tt class="docutils literal">.outlist</tt> attribute which is a list -storing the value yielded by the generator underlying the task (the -<tt class="docutils literal">None</tt> values are skipped though): the <tt class="docutils literal">.outlist</tt> grows as the -task runs and more values are yielded. Accessing the <tt class="docutils literal">.outlist</tt> is -nonblocking and can be done freely. -Finally there is a <tt class="docutils literal">.result</tt> -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:</p> -<pre class="literal-block"> -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) - -</pre> -</div> -<div class="section" id="monitor-support"> -<h2><a class="toc-backref" href="#id46">Monitor support</a></h2> -<p>Starting from release 0.8 <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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 <tt class="docutils literal">plac.Monitor</tt> and by -overriding the methods <tt class="docutils literal">add_listener(self, no)</tt>, -<tt class="docutils literal">del_listener(self, taskno)</tt>, <tt class="docutils literal">notify_listener(self, taskno, msg)</tt>, -<tt class="docutils literal">schedule(self, seconds, func, arg)</tt> and <tt class="docutils literal">run(self)</tt>. -Then, you can a monitor object to any <tt class="docutils literal">plac.Interpreter</tt> object -by simply calling the <tt class="docutils literal">add_monitor</tt> method. -For convenience, -<tt class="docutils literal">plac</tt> comes with a very simple <tt class="docutils literal">TkMonitor</tt> 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 <tt class="docutils literal">TkMonitor</tt> is implemented in <tt class="docutils literal">plac_tk.py</tt> -and adapt it. Here is an example of usage of the <tt class="docutils literal">TkMonitor</tt>:</p> -<pre class="literal-block"> -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() - - -</pre> -<p>Try to give the <tt class="docutils literal">hello</tt> command from the interactive interpreter: -each time a new text widget will be added displaying the output -of the command. Notice that if <tt class="docutils literal">Tkinter</tt> is not installed correctly -on your system the <tt class="docutils literal">TkMonitor</tt> class will not be available.</p> -</div> -<div class="section" id="parallel-computing-with-plac"> -<h2><a class="toc-backref" href="#id47">Parallel computing with plac</a></h2> -<p><a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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 <tt class="docutils literal">(x, y)</tt> with -<tt class="docutils literal">x</tt> and <tt class="docutils literal">y</tt> random variables in the interval <tt class="docutils literal">[0,1]</tt>. The -probability of extracting a number inside the quadrant (i.e. with -<tt class="docutils literal">x^2 + y^2 < 1</tt>) is proportional to the area of the quadrant -(i.e. <tt class="docutils literal">pi/4</tt>). The value of <tt class="docutils literal">pi</tt> therefore can be extracted by -multiplying by 4 the ratio between the number of points in the -quadrant versus the total number of points <tt class="docutils literal">N</tt>, for <tt class="docutils literal">N</tt> large:</p> -<pre class="literal-block"> -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 -</pre> -<p>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.</p> -<p>Here is a script implementing the algorithm and working in three different -modes (parallel mode, threaded mode and sequential mode) depending on a -<tt class="docutils literal">mode</tt> option:</p> -<pre class="literal-block"> -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() - -</pre> -<p>Notice the <tt class="docutils literal">submit_tasks</tt> method, which instantiates and initializes a -<tt class="docutils literal">plac.Interpreter</tt> object and submits a number of commands corresponding -to the number of available CPUs. The <tt class="docutils literal">calc_pi</tt> command yield a log -message every million of interactions, just to monitor the progress of -the computation. The <tt class="docutils literal">run</tt> method starts all the submitted commands -in parallel and sums the results. It returns the average value of <tt class="docutils literal">pi</tt> -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).</p> -<p>Here are the results on my old Macbook with Ubuntu 10.04 and Python 2.6, -for 10 million of iterations:</p> -<pre class="literal-block"> -$ 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 -</pre> -<p>As you see using processes one gets a 2x speedup indeed, where the threaded -mode is some 20% slower than the sequential mode.</p> -<p>Since the pattern submit a bunch of tasks, starts them and collect the -results is so common, <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> provides an utility function -<tt class="docutils literal">runp(genseq, <span class="pre">mode='p',</span> <span class="pre">monitors=(),</span> start=True)</tt> to start -a bunch a generators and return a list of task objects. By default -<tt class="docutils literal">runp</tt> use processes, but you can use threads by passing <tt class="docutils literal"><span class="pre">mode='t'</span></tt>. -If you do not wont to start the tasks, you can say so (<tt class="docutils literal">start=False</tt>). -With <tt class="docutils literal">runp</tt> the parallel pi calculation becomes a one-liner:</p> -<pre class="literal-block"> -sum(task.result for task in plac.runp(calc_pi(N) for i in range(ncpus)))/ncpus -</pre> -<p>The file <tt class="docutils literal">test_runp</tt> in the <tt class="docutils literal">doc</tt> directory of the plac distribution -shows another couple of examples of usage, including how to show the -results of the running computation on a <tt class="docutils literal">TkMonitor</tt>.</p> -</div> -<div class="section" id="the-plac-server"> -<h2><a class="toc-backref" href="#id48">The plac server</a></h2> -<p>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 <tt class="docutils literal">asynchat</tt> -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 <tt class="docutils literal">asynchat</tt>-based servers are asynchronous, any blocking command -in the interpreter should be run in a separated process or thread. -The default port for the <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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):</p> -<pre class="literal-block"> -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) - -</pre> -<p>You can connect to the server with <tt class="docutils literal">telnet</tt> on port 2199, as follows:</p> -<pre class="literal-block"> -$ 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. -</pre> -</div> -<div class="section" id="summary"> -<h2><a class="toc-backref" href="#id49">Summary</a></h2> -<p>Once <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> 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:</p> -<ol class="arabic simple"> -<li>if you want to implement a command-line script, use <tt class="docutils literal">plac.call</tt>;</li> -<li>if you want to implement a command interpreter, use <tt class="docutils literal">plac.Interpreter</tt>:<ul> -<li>for an interactive interpreter, call the <tt class="docutils literal">.interact</tt> method;</li> -<li>for an batch interpreter, call the <tt class="docutils literal">.execute</tt> method;</li> -</ul> -</li> -<li>for testing call the <tt class="docutils literal">Interpreter.check</tt> method in the appropriate context -or use the <tt class="docutils literal">Interpreter.doctest</tt> feature;</li> -<li>if you need to go at a lower level, you may need to call the -<tt class="docutils literal">Interpreter.send</tt> method which returns a (finished) <tt class="docutils literal">Task</tt> object.</li> -<li>long running command can be executed in the background as threads or -processes: just declare them in the lists <tt class="docutils literal">thcommands</tt> and <tt class="docutils literal">mpcommands</tt> -respectively.</li> -<li>the <tt class="docutils literal">.start_server</tt> method starts an asynchronous server on the -given port number (default 2199)</li> -</ol> -<p>Moreover, remember that <tt class="docutils literal">plac_runner.py</tt> is your friend.</p> -</div> -<hr class="docutils" /> -<div class="section" id="appendix-custom-annotation-objects"> -<h2><a class="toc-backref" href="#id50">Appendix: custom annotation objects</a></h2> -<p>Internally <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> uses an <tt class="docutils literal">Annotation</tt> class to convert the tuples -in the function signature into annotation objects, i.e. objects with -six attributes <tt class="docutils literal">help, kind, short, type, choices, metavar</tt>.</p> -<p>Advanced users can implement their own annotation objects. -For instance, here is an example of how you could implement annotations for -positional arguments:</p> -<pre class="literal-block"> -# 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 - -</pre> -<p>You can use such annotations objects as follows:</p> -<pre class="literal-block"> -# 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) - -</pre> -<p>Here is the usage message you get:</p> -<pre class="literal-block"> -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 <a href="http://plac.googlecode.com/hg/doc/plac.html">moved here</a>. -</pre> -<p>You can go on and define <tt class="docutils literal">Option</tt> and <tt class="docutils literal">Flag</tt> 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.</p> -</div> -</div> -</div> </body> </html> 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 - <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: - -.. 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 <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: - -.. 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 - ... <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: - -.. 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 - <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: - -.. 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 - <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. - 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 - <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_: - -- 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 <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. - -.. _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) |