diff options
author | Michele Simionato <michele.simionato@gmail.com> | 2010-07-10 08:33:55 +0200 |
---|---|---|
committer | Michele Simionato <michele.simionato@gmail.com> | 2010-07-10 08:33:55 +0200 |
commit | 5e4bdc93b22e41ba39e871d30f646a34ac7475e0 (patch) | |
tree | 82d34c1c18a07f803147c82c4424485e79896d40 | |
parent | ecf1d3f604d3b68e318924560b76b9dae7d41f16 (diff) | |
download | micheles-5e4bdc93b22e41ba39e871d30f646a34ac7475e0.tar.gz |
A lot of work for plac 0.6
-rw-r--r-- | plac/CHANGES.txt | 3 | ||||
-rw-r--r-- | plac/Makefile | 4 | ||||
-rw-r--r-- | plac/doc/example13.help | 1 | ||||
-rw-r--r-- | plac/doc/example13.py | 2 | ||||
-rw-r--r-- | plac/doc/example14.help | 8 | ||||
-rw-r--r-- | plac/doc/example14.py | 15 | ||||
-rw-r--r-- | plac/doc/importer1.py | 20 | ||||
-rw-r--r-- | plac/doc/importer2.py | 22 | ||||
-rw-r--r-- | plac/doc/importer3.py | 20 | ||||
-rw-r--r-- | plac/doc/ishelve.placet | 2 | ||||
-rw-r--r-- | plac/doc/ishelve.py | 2 | ||||
-rw-r--r-- | plac/doc/ishelve2.help | 8 | ||||
-rw-r--r-- | plac/doc/ishelve2.placet | 4 | ||||
-rw-r--r-- | plac/doc/plac.html | 14 | ||||
-rw-r--r-- | plac/doc/plac.pdf | 997 | ||||
-rw-r--r-- | plac/doc/plac.txt | 16 | ||||
-rw-r--r-- | plac/doc/plac_adv.html | 547 | ||||
-rw-r--r-- | plac/doc/plac_adv.pdf | 3606 | ||||
-rw-r--r-- | plac/doc/plac_adv.txt | 461 | ||||
-rw-r--r-- | plac/doc/shelve_interpreter.help | 5 | ||||
-rw-r--r-- | plac/doc/sql_interface.py | 25 | ||||
-rw-r--r-- | plac/doc/test_plac.py | 29 | ||||
-rw-r--r-- | plac/plac.py | 4 | ||||
-rw-r--r-- | plac/plac_core.py | 134 | ||||
-rw-r--r-- | plac/plac_ext.py | 674 |
25 files changed, 4185 insertions, 2438 deletions
diff --git a/plac/CHANGES.txt b/plac/CHANGES.txt index fc42bf1..dfb8c23 100644 --- a/plac/CHANGES.txt +++ b/plac/CHANGES.txt @@ -1,6 +1,9 @@ HISTORY ---------- +0.6.0 Improvement the interactive experience with full readline support and + custom help. Added support for long running command, via threads and + processes. 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. diff --git a/plac/Makefile b/plac/Makefile index 52bd70c..7e119b6 100644 --- a/plac/Makefile +++ b/plac/Makefile @@ -6,3 +6,7 @@ doc/plac_adv.pdf: doc/plac_adv.txt cd doc; rst2pdf --footer=###Page### plac_adv.txt; rst2html --stylesheet=$(HOME)/gcode/df.css plac_adv.txt plac_adv.html upload: python3 setup.py register sdist upload +2: + python setup.py build; sudo python setup.py install +3: + python3 setup.py build; sudo python3 setup.py install diff --git a/plac/doc/example13.help b/plac/doc/example13.help index 91216a3..94401ad 100644 --- a/plac/doc/example13.help +++ b/plac/doc/example13.help @@ -7,4 +7,3 @@ optional arguments: subcommands: {status,commit,checkout,help} - -h to get additional help diff --git a/plac/doc/example13.py b/plac/doc/example13.py index 3c91f07..715f2e1 100644 --- a/plac/doc/example13.py +++ b/plac/doc/example13.py @@ -7,7 +7,7 @@ class FVCS(object): @plac.annotations( name=('a recognized command', 'positional', None, str, commands)) def help(self, name): - self.p.subp[name].print_help() + print(plac.parser_from(self).help_cmd(name)) @plac.annotations( url=('url of the source code', 'positional')) diff --git a/plac/doc/example14.help b/plac/doc/example14.help deleted file mode 100644 index 50c600b..0000000 --- a/plac/doc/example14.help +++ /dev/null @@ -1,8 +0,0 @@ -usage: example14.py [-h] {status,commit,checkout,help} ... - -optional arguments: - -h, --help show this help message and exit - -subcommands: - {status,commit,checkout,help} - -h to get additional help diff --git a/plac/doc/example14.py b/plac/doc/example14.py deleted file mode 100644 index 18a2931..0000000 --- a/plac/doc/example14.py +++ /dev/null @@ -1,15 +0,0 @@ -import plac -from example13 import FVCS - -class VCS_with_help(FVCS): - commands = FVCS.commands + ('help',) - - @plac.annotations( - name=('a recognized command', 'positional', None, str, commands)) - def help(self, name): - self.p.subp[name].print_help() - -main = VCS_with_help() - -if __name__ == '__main__': - plac.call(main) diff --git a/plac/doc/importer1.py b/plac/doc/importer1.py new file mode 100644 index 0000000..43e6833 --- /dev/null +++ b/plac/doc/importer1.py @@ -0,0 +1,20 @@ +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(plac.call(FakeImporter)).interact() diff --git a/plac/doc/importer2.py b/plac/doc/importer2.py new file mode 100644 index 0000000..b448aa6 --- /dev/null +++ b/plac/doc/importer2.py @@ -0,0 +1,22 @@ +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(plac.call(FakeImporter)).interact() diff --git a/plac/doc/importer3.py b/plac/doc/importer3.py new file mode 100644 index 0000000..fb3aee5 --- /dev/null +++ b/plac/doc/importer3.py @@ -0,0 +1,20 @@ +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(plac.call(FakeImporter)).interact() diff --git a/plac/doc/ishelve.placet b/plac/doc/ishelve.placet index 87c1b32..2637223 100644 --- a/plac/doc/ishelve.placet +++ b/plac/doc/ishelve.placet @@ -10,4 +10,4 @@ deleted a i> a a: not found i> .cler # spelling error -SystemExit: unrecognized arguments: .cler +.cler: not found diff --git a/plac/doc/ishelve.py b/plac/doc/ishelve.py index 343b4ed..4f22ba2 100644 --- a/plac/doc/ishelve.py +++ b/plac/doc/ishelve.py @@ -13,7 +13,7 @@ DEFAULT_SHELVE = os.path.expanduser('~/conf.shelve') setters='setters param=value') def main(help, showall, clear, delete, filename=DEFAULT_SHELVE, *params, **setters): - "A simple interface to a shelve" + "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]): diff --git a/plac/doc/ishelve2.help b/plac/doc/ishelve2.help new file mode 100644 index 0000000..2ce64db --- /dev/null +++ b/plac/doc/ishelve2.help @@ -0,0 +1,8 @@ +usage: ishelve2.py [-h] [-configfile ~/conf.shelve] + +A minimal interface over a shelve object. + +optional arguments: + -h, --help show this help message and exit + -configfile ~/conf.shelve + path name of the shelve diff --git a/plac/doc/ishelve2.placet b/plac/doc/ishelve2.placet index a41fc8d..f954932 100644 --- a/plac/doc/ishelve2.placet +++ b/plac/doc/ishelve2.placet @@ -5,5 +5,5 @@ i> set a 1 setting a=1 i> set b 2 setting b=2 -i> show b -b = 2 +i> show a +a = 1 diff --git a/plac/doc/plac.html b/plac/doc/plac.html index da6cbad..c63e131 100644 --- a/plac/doc/plac.html +++ b/plac/doc/plac.html @@ -1027,12 +1027,18 @@ try: except: # do something </pre> -<p>Without the <tt class="docutils literal">listify</tt> functionality, a main function returning a +<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>Moreover you can rely on type checks like <tt class="docutils literal">isinstance(result, list)</tt> -for the output of <tt class="docutils literal">plac.call</tt>, to check if the output is an iterable -or not.</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"> <h1><a class="toc-backref" href="#id8">A realistic example</a></h1> diff --git a/plac/doc/plac.pdf b/plac/doc/plac.pdf index 5e15047..836a8d6 100644 --- a/plac/doc/plac.pdf +++ b/plac/doc/plac.pdf @@ -351,7 +351,7 @@ endobj /Dest [ 94 0 R
/XYZ
62.69291
- 765.0236
+ 683.8236
0 ]
/Rect [ 62.69291
422.5936
@@ -369,7 +369,7 @@ endobj /Dest [ 94 0 R
/XYZ
62.69291
- 765.0236
+ 683.8236
0 ]
/Rect [ 521.4627
422.5936
@@ -387,7 +387,7 @@ endobj /Dest [ 97 0 R
/XYZ
62.69291
- 707.8236
+ 623.8236
0 ]
/Rect [ 62.69291
404.5936
@@ -405,7 +405,7 @@ endobj /Dest [ 97 0 R
/XYZ
62.69291
- 707.8236
+ 623.8236
0 ]
/Rect [ 521.4627
404.5936
@@ -423,7 +423,7 @@ endobj /Dest [ 99 0 R
/XYZ
62.69291
- 683.8236
+ 594.6236
0 ]
/Rect [ 62.69291
386.5936
@@ -441,7 +441,7 @@ endobj /Dest [ 99 0 R
/XYZ
62.69291
- 683.8236
+ 594.6236
0 ]
/Rect [ 521.4627
386.5936
@@ -456,10 +456,10 @@ endobj 0
0 ]
/Contents ()
- /Dest [ 125 0 R
+ /Dest [ 124 0 R
/XYZ
62.69291
- 595.8236
+ 511.8236
0 ]
/Rect [ 62.69291
368.5936
@@ -474,10 +474,10 @@ endobj 0
0 ]
/Contents ()
- /Dest [ 125 0 R
+ /Dest [ 124 0 R
/XYZ
62.69291
- 595.8236
+ 511.8236
0 ]
/Rect [ 521.4627
368.5936
@@ -492,10 +492,10 @@ endobj 0
0 ]
/Contents ()
- /Dest [ 140 0 R
+ /Dest [ 133 0 R
/XYZ
62.69291
- 259.4236
+ 161.4236
0 ]
/Rect [ 62.69291
350.5936
@@ -510,10 +510,10 @@ endobj 0
0 ]
/Contents ()
- /Dest [ 140 0 R
+ /Dest [ 133 0 R
/XYZ
62.69291
- 259.4236
+ 161.4236
0 ]
/Rect [ 521.4627
350.5936
@@ -531,7 +531,7 @@ endobj /Dest [ 162 0 R
/XYZ
62.69291
- 741.0236
+ 645.0236
0 ]
/Rect [ 62.69291
332.5936
@@ -549,7 +549,7 @@ endobj /Dest [ 162 0 R
/XYZ
62.69291
- 741.0236
+ 645.0236
0 ]
/Rect [ 521.4627
332.5936
@@ -567,7 +567,7 @@ endobj /Dest [ 162 0 R
/XYZ
62.69291
- 534.0236
+ 438.0236
0 ]
/Rect [ 62.69291
314.5936
@@ -585,7 +585,7 @@ endobj /Dest [ 162 0 R
/XYZ
62.69291
- 534.0236
+ 438.0236
0 ]
/Rect [ 521.4627
314.5936
@@ -1521,9 +1521,9 @@ endobj 0
0 ]
/Rect [ 338.1568
- 729.5936
+ 648.3936
360.5113
- 741.5936 ]
+ 660.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1536,9 +1536,9 @@ endobj 0
0 ]
/Rect [ 110.6843
- 717.5936
+ 636.3936
169.0343
- 729.5936 ]
+ 648.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1551,9 +1551,9 @@ endobj 0
0 ]
/Rect [ 168.3029
- 705.5936
+ 624.3936
208.8829
- 717.5936 ]
+ 636.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1588,9 +1588,9 @@ endobj 0
0 ]
/Rect [ 185.0709
- 672.3936
+ 588.3936
208.0228
- 684.3936 ]
+ 600.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1603,9 +1603,9 @@ endobj 0
0 ]
/Rect [ 220.5998
- 660.3936
+ 576.3936
243.819
- 672.3936 ]
+ 588.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1639,9 +1639,9 @@ endobj 0
0 ]
/Rect [ 374.4929
- 624.3936
+ 535.1936
395.6129
- 636.3936 ]
+ 547.1936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1674,9 +1674,9 @@ endobj 0
0 ]
/Rect [ 304.0655
- 570.3936
+ 486.3936
348.3808
- 582.3936 ]
+ 498.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1689,9 +1689,9 @@ endobj 0
0 ]
/Rect [ 293.7749
- 396.3936
+ 312.3936
316.2402
- 408.3936 ]
+ 324.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1725,9 +1725,9 @@ endobj 0
0 ]
/Rect [ 62.69291
- 560.3936
+ 476.3936
84.8789
- 572.3936 ]
+ 488.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1740,9 +1740,9 @@ endobj 0
0 ]
/Rect [ 466.5307
- 560.3936
+ 476.3936
509.8367
- 572.3936 ]
+ 488.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1755,9 +1755,9 @@ endobj 0
0 ]
/Rect [ 124.3929
- 536.3936
+ 452.3936
163.8529
- 548.3936 ]
+ 464.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1770,9 +1770,9 @@ endobj 0
0 ]
/Rect [ 85.69291
- 455.3936
+ 371.3936
127.9329
- 467.3936 ]
+ 383.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1785,9 +1785,9 @@ endobj 0
0 ]
/Rect [ 85.69291
- 437.3936
+ 353.3936
107.9337
- 449.3936 ]
+ 365.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1800,9 +1800,9 @@ endobj 0
0 ]
/Rect [ 308.5389
- 437.3936
+ 353.3936
351.8997
- 449.3936 ]
+ 365.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1815,9 +1815,9 @@ endobj 0
0 ]
/Rect [ 380.6856
- 413.3936
+ 329.3936
423.7999
- 425.3936 ]
+ 341.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1830,9 +1830,9 @@ endobj 0
0 ]
/Rect [ 494.4684
- 413.3936
+ 329.3936
516.4627
- 425.3936 ]
+ 341.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1845,9 +1845,9 @@ endobj 0
0 ]
/Rect [ 85.69291
- 383.3936
+ 299.3936
108.3529
- 395.3936 ]
+ 311.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1860,9 +1860,9 @@ endobj 0
0 ]
/Rect [ 277.2428
- 383.3936
+ 299.3936
321.0228
- 395.3936 ]
+ 311.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1875,9 +1875,9 @@ endobj 0
0 ]
/Rect [ 404.5839
- 371.3936
+ 287.3936
426.0657
- 383.3936 ]
+ 299.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1890,9 +1890,9 @@ endobj 0
0 ]
/Rect [ 85.69291
- 317.3936
+ 233.3936
108.61
- 329.3936 ]
+ 245.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1905,9 +1905,9 @@ endobj 0
0 ]
/Rect [ 459.2622
- 305.3936
+ 221.3936
481.289
- 317.3936 ]
+ 233.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1920,9 +1920,9 @@ endobj 0
0 ]
/Rect [ 85.69291
- 275.3936
+ 191.3936
108.9242
- 287.3936 ]
+ 203.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1935,9 +1935,9 @@ endobj 0
0 ]
/Rect [ 340.9248
- 275.3936
+ 191.3936
470.1087
- 287.3936 ]
+ 203.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1950,9 +1950,9 @@ endobj 0
0 ]
/Rect [ 85.69291
- 245.3936
+ 161.3936
107.9247
- 257.3936 ]
+ 173.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1965,9 +1965,9 @@ endobj 0
0 ]
/Rect [ 85.69291
- 233.3936
+ 149.3936
104.0329
- 245.3936 ]
+ 161.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1980,9 +1980,9 @@ endobj 0
0 ]
/Rect [ 489.2227
- 233.3936
+ 149.3936
532.176
- 245.3936 ]
+ 161.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -1995,9 +1995,9 @@ endobj 0
0 ]
/Rect [ 85.69291
- 221.3936
+ 137.3936
159.6229
- 233.3936 ]
+ 149.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2010,9 +2010,9 @@ endobj 0
0 ]
/Rect [ 62.69291
- 200.3936
+ 116.3936
83.81291
- 212.3936 ]
+ 128.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2025,29 +2025,14 @@ endobj 0
0 ]
/Rect [ 219.4229
- 200.3936
+ 116.3936
261.6629
- 212.3936 ]
- /Subtype /Link
- /Type /Annot >>
-endobj
-% 'Annot.NUMBER104': class PDFDictionary
-124 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
- 170.3936
- 534.3667
- 182.3936 ]
+ 128.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Page15': class PDFPage
-125 0 obj
+124 0 obj
% Page dictionary
<< /Annots [ 103 0 R
104 0 R
@@ -2069,8 +2054,7 @@ endobj 120 0 R
121 0 R
122 0 R
- 123 0 R
- 124 0 R ]
+ 123 0 R ]
/Contents 195 0 R
/MediaBox [ 0
0
@@ -2087,6 +2071,21 @@ endobj /Trans << >>
/Type /Page >>
endobj
+% 'Annot.NUMBER104': class PDFDictionary
+125 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
+ 744.5936
+ 534.3667
+ 756.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
% 'Annot.NUMBER105': class PDFDictionary
126 0 obj
<< /A << /S /URI
@@ -2096,9 +2095,9 @@ endobj 0
0 ]
/Rect [ 325.7268
- 675.3936
+ 577.3936
347.4138
- 687.3936 ]
+ 589.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2111,9 +2110,9 @@ endobj 0
0 ]
/Rect [ 327.2261
- 508.1936
+ 410.1936
410.5152
- 520.1936 ]
+ 422.1936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2126,9 +2125,9 @@ endobj 0
0 ]
/Rect [ 275.5829
- 328.9936
+ 230.9936
317.8229
- 340.9936 ]
+ 242.9936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2141,9 +2140,9 @@ endobj 0
0 ]
/Rect [ 307.9178
- 310.9936
+ 212.9936
351.5999
- 322.9936 ]
+ 224.9936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2156,9 +2155,9 @@ endobj 0
0 ]
/Rect [ 329.8034
- 286.9936
+ 188.9936
352.1804
- 298.9936 ]
+ 200.9936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2171,9 +2170,9 @@ endobj 0
0 ]
/Rect [ 109.0098
- 223.9936
+ 125.9936
131.9967
- 235.9936 ]
+ 137.9936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2186,14 +2185,41 @@ endobj 0
0 ]
/Rect [ 397.2929
- 199.9936
+ 101.9936
415.6329
- 211.9936 ]
+ 113.9936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER112': class PDFDictionary
+% 'Page16': class PDFPage
133 0 obj
+% Page dictionary
+<< /Annots [ 125 0 R
+ 126 0 R
+ 127 0 R
+ 128 0 R
+ 129 0 R
+ 130 0 R
+ 131 0 R
+ 132 0 R ]
+ /Contents 196 0 R
+ /MediaBox [ 0
+ 0
+ 595.2756
+ 841.8898 ]
+ /Parent 180 0 R
+ /Resources << /Font 1 0 R
+ /ProcSet [ /PDF
+ /Text
+ /ImageB
+ /ImageC
+ /ImageI ] >>
+ /Rotate 0
+ /Trans << >>
+ /Type /Page >>
+endobj
+% 'Annot.NUMBER112': class PDFDictionary
+134 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/opterator) >>
@@ -2201,14 +2227,14 @@ endobj 0
0 ]
/Rect [ 85.69291
- 178.9936
+ 753.5936
128.4929
- 190.9936 ]
+ 765.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Annot.NUMBER113': class PDFDictionary
-134 0 obj
+135 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/CLIArgs) >>
@@ -2216,14 +2242,14 @@ endobj 0
0 ]
/Rect [ 85.69291
- 160.9936
+ 735.5936
124.5929
- 172.9936 ]
+ 747.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Annot.NUMBER114': class PDFDictionary
-135 0 obj
+136 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://argparse.googlecode.com) >>
@@ -2231,14 +2257,14 @@ endobj 0
0 ]
/Rect [ 464.3898
- 139.9936
+ 714.5936
503.8498
- 151.9936 ]
+ 726.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Annot.NUMBER115': class PDFDictionary
-136 0 obj
+137 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
@@ -2246,14 +2272,14 @@ endobj 0
0 ]
/Rect [ 305.0429
- 127.9936
+ 702.5936
323.3829
- 139.9936 ]
+ 714.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Annot.NUMBER116': class PDFDictionary
-137 0 obj
+138 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/Clap/0.7) >>
@@ -2261,14 +2287,14 @@ endobj 0
0 ]
/Rect [ 455.0104
- 109.9936
+ 684.5936
479.9015
- 121.9936 ]
+ 696.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Annot.NUMBER117': class PDFDictionary
-138 0 obj
+139 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
@@ -2276,14 +2302,14 @@ endobj 0
0 ]
/Rect [ 303.707
- 97.99362
+ 672.5936
322.047
- 109.9936 ]
+ 684.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Annot.NUMBER118': class PDFDictionary
-139 0 obj
+140 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/Clap/0.7) >>
@@ -2291,45 +2317,12 @@ endobj 0
0 ]
/Rect [ 328.8186
- 97.99362
+ 672.5936
353.3701
- 109.9936 ]
+ 684.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Page16': class PDFPage
-140 0 obj
-% Page dictionary
-<< /Annots [ 126 0 R
- 127 0 R
- 128 0 R
- 129 0 R
- 130 0 R
- 131 0 R
- 132 0 R
- 133 0 R
- 134 0 R
- 135 0 R
- 136 0 R
- 137 0 R
- 138 0 R
- 139 0 R ]
- /Contents 196 0 R
- /MediaBox [ 0
- 0
- 595.2756
- 841.8898 ]
- /Parent 180 0 R
- /Resources << /Font 1 0 R
- /ProcSet [ /PDF
- /Text
- /ImageB
- /ImageC
- /ImageI ] >>
- /Rotate 0
- /Trans << >>
- /Type /Page >>
-endobj
% 'Annot.NUMBER119': class PDFDictionary
141 0 obj
<< /A << /S /URI
@@ -2339,9 +2332,9 @@ endobj 0
0 ]
/Rect [ 62.69291
- 756.5936
+ 660.5936
81.03291
- 768.5936 ]
+ 672.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2354,9 +2347,9 @@ endobj 0
0 ]
/Rect [ 156.6051
- 705.5936
+ 609.5936
177.8606
- 717.5936 ]
+ 621.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2369,9 +2362,9 @@ endobj 0
0 ]
/Rect [ 186.6535
- 681.5936
+ 585.5936
226.1135
- 693.5936 ]
+ 597.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2384,9 +2377,9 @@ endobj 0
0 ]
/Rect [ 493.1227
- 681.5936
+ 585.5936
532.4646
- 693.5936 ]
+ 597.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2399,9 +2392,9 @@ endobj 0
0 ]
/Rect [ 72.7804
- 669.5936
+ 573.5936
96.20788
- 681.5936 ]
+ 585.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2414,9 +2407,9 @@ endobj 0
0 ]
/Rect [ 149.2229
- 639.5936
+ 543.5936
171.2704
- 651.5936 ]
+ 555.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2429,9 +2422,9 @@ endobj 0
0 ]
/Rect [ 128.0309
- 597.5936
+ 501.5936
149.4369
- 609.5936 ]
+ 513.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2444,9 +2437,9 @@ endobj 0
0 ]
/Rect [ 502.8367
- 597.5936
+ 501.5936
524.2427
- 609.5936 ]
+ 513.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2459,9 +2452,9 @@ endobj 0
0 ]
/Rect [ 187.4797
- 573.5936
+ 477.5936
209.0991
- 585.5936 ]
+ 489.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2474,9 +2467,9 @@ endobj 0
0 ]
/Rect [ 301.6965
- 573.5936
+ 477.5936
426.0446
- 585.5936 ]
+ 489.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2489,9 +2482,9 @@ endobj 0
0 ]
/Rect [ 83.64556
- 498.5936
+ 402.5936
105.7082
- 510.5936 ]
+ 414.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2504,9 +2497,9 @@ endobj 0
0 ]
/Rect [ 446.6
- 498.5936
+ 402.5936
502.5727
- 510.5936 ]
+ 414.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2519,9 +2512,9 @@ endobj 0
0 ]
/Rect [ 275.6828
- 486.5936
+ 390.5936
297.3688
- 498.5936 ]
+ 402.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2534,9 +2527,9 @@ endobj 0
0 ]
/Rect [ 77.19665
- 474.5936
+ 378.5936
139.4904
- 486.5936 ]
+ 390.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2549,9 +2542,9 @@ endobj 0
0 ]
/Rect [ 96.54131
- 462.5936
+ 366.5936
139.0255
- 474.5936 ]
+ 378.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2564,9 +2557,9 @@ endobj 0
0 ]
/Rect [ 203.5016
- 429.5936
+ 333.5936
245.8453
- 441.5936 ]
+ 345.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2579,9 +2572,9 @@ endobj 0
0 ]
/Rect [ 62.69291
- 354.5936
+ 258.5936
138.7898
- 366.5936 ]
+ 270.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2594,9 +2587,9 @@ endobj 0
0 ]
/Rect [ 114.6649
- 342.5936
+ 246.5936
154.1249
- 354.5936 ]
+ 258.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2609,9 +2602,9 @@ endobj 0
0 ]
/Rect [ 191.6329
- 330.5936
+ 234.5936
233.8729
- 342.5936 ]
+ 246.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2624,9 +2617,9 @@ endobj 0
0 ]
/Rect [ 263.3429
- 300.5936
+ 204.5936
286.6829
- 312.5936 ]
+ 216.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -2639,16 +2632,23 @@ endobj 0
0 ]
/Rect [ 258.5629
- 252.5936
+ 156.5936
276.9029
- 264.5936 ]
+ 168.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Page17': class PDFPage
162 0 obj
% Page dictionary
-<< /Annots [ 141 0 R
+<< /Annots [ 134 0 R
+ 135 0 R
+ 136 0 R
+ 137 0 R
+ 138 0 R
+ 139 0 R
+ 140 0 R
+ 141 0 R
142 0 R
143 0 R
144 0 R
@@ -2697,7 +2697,7 @@ endobj % 'R164': class PDFInfo
164 0 obj
<< /Author (Michele Simionato)
- /CreationDate (D:20100620102407-01'00')
+ /CreationDate (D:20100703163715-01'00')
/Keywords ()
/Producer (ReportLab http://www.reportlab.com)
/Subject (\(unspecified\))
@@ -2798,7 +2798,7 @@ endobj << /Dest [ 94 0 R
/XYZ
62.69291
- 765.0236
+ 683.8236
0 ]
/Next 174 0 R
/Parent 165 0 R
@@ -2810,7 +2810,7 @@ endobj << /Dest [ 97 0 R
/XYZ
62.69291
- 707.8236
+ 623.8236
0 ]
/Next 175 0 R
/Parent 165 0 R
@@ -2822,7 +2822,7 @@ endobj << /Dest [ 99 0 R
/XYZ
62.69291
- 683.8236
+ 594.6236
0 ]
/Next 176 0 R
/Parent 165 0 R
@@ -2831,10 +2831,10 @@ endobj endobj
% 'Outline.10': class OutlineEntryObject
176 0 obj
-<< /Dest [ 125 0 R
+<< /Dest [ 124 0 R
/XYZ
62.69291
- 595.8236
+ 511.8236
0 ]
/Next 177 0 R
/Parent 165 0 R
@@ -2843,10 +2843,10 @@ endobj endobj
% 'Outline.11': class OutlineEntryObject
177 0 obj
-<< /Dest [ 140 0 R
+<< /Dest [ 133 0 R
/XYZ
62.69291
- 259.4236
+ 161.4236
0 ]
/Next 178 0 R
/Parent 165 0 R
@@ -2858,7 +2858,7 @@ endobj << /Dest [ 162 0 R
/XYZ
62.69291
- 741.0236
+ 645.0236
0 ]
/Next 179 0 R
/Parent 165 0 R
@@ -2870,7 +2870,7 @@ endobj << /Dest [ 162 0 R
/XYZ
62.69291
- 534.0236
+ 438.0236
0 ]
/Parent 165 0 R
/Prev 178 0 R
@@ -2894,8 +2894,8 @@ endobj 97 0 R
99 0 R
102 0 R
- 125 0 R
- 140 0 R
+ 124 0 R
+ 133 0 R
162 0 R ]
/Type /Pages >>
endobj
@@ -4474,7 +4474,7 @@ endobj % 'R190': class PDFStream
190 0 obj
% page stream
-<< /Length 4567 >>
+<< /Length 4511 >>
stream
1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
q
@@ -4613,13 +4613,14 @@ Q q
1 0 0 1 62.69291 152.8849 cm
q
-BT 1 0 0 1 0 16.82 Tm 1.986412 Tw 12 TL /F1 10 Tf 0 0 0 rg (Without the ) Tj /F4 10 Tf (listify ) Tj /F1 10 Tf (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
+0 0 0 rg
+BT 1 0 0 1 0 16.82 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 122.8849 cm
q
-BT 1 0 0 1 0 16.82 Tm .094269 Tw 12 TL /F1 10 Tf 0 0 0 rg (Moreover you can rely on type checks like ) Tj /F4 10 Tf (isinstance\(result, list\) ) Tj /F1 10 Tf (for the output of ) Tj /F4 10 Tf (plac.call) Tj /F1 10 Tf (,) Tj T* 0 Tw (to check if the output is an iterable or not.) Tj T* ET
+BT 1 0 0 1 0 16.82 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 /F4 10 Tf (eager ) Tj /F1 10 Tf (flag to ) Tj /F4 10 Tf (False) Tj /F1 10 Tf (, as in the following) Tj T* 0 Tw (example:) Tj T* ET
Q
Q
q
@@ -4636,23 +4637,50 @@ endobj % 'R191': class PDFStream
191 0 obj
% page stream
-<< /Length 3839 >>
+<< /Length 4236 >>
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
+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 17.71 Tm /F4 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 695.8236 cm
+q
+BT 1 0 0 1 0 16.82 Tm 1.35528 Tw 12 TL /F1 10 Tf 0 0 0 rg (If ) Tj /F4 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 662.8236 cm
q
BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (A realistic example) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 702.0236 cm
+1 0 0 1 62.69291 620.8236 cm
q
BT 1 0 0 1 0 28.82 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 /F4 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 332.8236 cm
+1 0 0 1 62.69291 251.6236 cm
q
q
1 0 0 1 0 0 cm
@@ -4673,20 +4701,20 @@ Q Q
Q
q
-1 0 0 1 62.69291 276.8236 cm
+1 0 0 1 62.69291 195.6236 cm
q
BT 1 0 0 1 0 40.82 Tm .049987 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can see the ) Tj /F5 10 Tf (yield-is-print ) Tj /F1 10 Tf (pattern here: instead of using ) Tj /F4 10 Tf (print ) Tj /F1 10 Tf (in the main function, I use ) Tj /F4 10 Tf (yield) Tj /F1 10 Tf (, and) Tj T* 0 Tw 3.55061 Tw (I perform the print in the ) Tj /F4 10 Tf (__main__ ) Tj /F1 10 Tf (block. The advantage of the pattern is that tests invoking) Tj T* 0 Tw .52936 Tw /F4 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 /F4 10 Tf (sys.stdout ) Tj /F1 10 Tf (to a ) Tj /F4 10 Tf (StringIO ) Tj /F1 10 Tf (object.) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 258.8236 cm
+1 0 0 1 62.69291 177.6236 cm
q
0 0 0 rg
BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (Here is the usage message:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 93.62362 cm
+1 0 0 1 62.69291 96.42362 cm
q
q
1 0 0 1 0 0 cm
@@ -4696,11 +4724,11 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 156 re B*
+n -6 -6 468.6898 72 re B*
Q
q
0 0 0 rg
-BT 1 0 0 1 0 137.71 Tm /F4 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* ET
+BT 1 0 0 1 0 53.71 Tm /F4 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* ET
Q
Q
Q
@@ -4720,11 +4748,11 @@ endobj % 'R192': class PDFStream
192 0 obj
% page stream
-<< /Length 3848 >>
+<< /Length 3423 >>
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
+1 0 0 1 62.69291 655.8236 cm
q
q
1 0 0 1 0 0 cm
@@ -4734,37 +4762,37 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 24 re B*
+n -6 -6 468.6898 108 re B*
Q
q
0 0 0 rg
-BT 1 0 0 1 0 5.71 Tm /F4 10 Tf 12 TL ( -d |, --delimiter | Column separator) Tj T* ET
+BT 1 0 0 1 0 89.71 Tm /F4 10 Tf 12 TL ( 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 719.8236 cm
+1 0 0 1 62.69291 635.8236 cm
q
0 0 0 rg
BT 1 0 0 1 0 4.82 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 686.8236 cm
+1 0 0 1 62.69291 602.8236 cm
q
BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Keyword arguments) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 644.8236 cm
+1 0 0 1 62.69291 560.8236 cm
q
BT 1 0 0 1 0 28.82 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 /F4 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 407.6236 cm
+1 0 0 1 62.69291 323.6236 cm
q
q
1 0 0 1 0 0 cm
@@ -4785,14 +4813,14 @@ Q Q
Q
q
-1 0 0 1 62.69291 387.6236 cm
+1 0 0 1 62.69291 303.6236 cm
q
0 0 0 rg
BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (Here is the generated usage message:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 258.4236 cm
+1 0 0 1 62.69291 174.4236 cm
q
q
1 0 0 1 0 0 cm
@@ -4813,14 +4841,14 @@ Q Q
Q
q
-1 0 0 1 62.69291 238.4236 cm
+1 0 0 1 62.69291 154.4236 cm
q
0 0 0 rg
BT 1 0 0 1 0 4.82 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 169.2236 cm
+1 0 0 1 62.69291 97.22362 cm
q
q
1 0 0 1 0 0 cm
@@ -4830,24 +4858,35 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 60 re B*
+n -6 -6 468.6898 48 re B*
Q
q
0 0 0 rg
-BT 1 0 0 1 0 41.71 Tm /F4 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
+BT 1 0 0 1 0 29.71 Tm /F4 10 Tf 12 TL ($ python example12.py -o X a1 a2 name=value) Tj T* (opt=X) Tj T* (args=\('a1', 'a2'\)) Tj T* ET
Q
Q
Q
Q
Q
q
-1 0 0 1 62.69291 137.2236 cm
+1 0 0 1 56.69291 56.69291 cm
q
-BT 1 0 0 1 0 16.82 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 /F4 10 Tf (opt ) Tj /F1 10 Tf (is taken:) Tj T* ET
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (12) Tj T* -235.3849 0 Td ET
Q
Q
+
+endstream
+
+endobj
+% 'R193': class PDFStream
+193 0 obj
+% page stream
+<< /Length 4047 >>
+stream
+1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
q
-1 0 0 1 62.69291 92.02362 cm
+1 0 0 1 62.69291 739.8236 cm
q
q
1 0 0 1 0 0 cm
@@ -4857,35 +4896,24 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 36 re B*
+n -6 -6 468.6898 24 re B*
Q
q
0 0 0 rg
-BT 1 0 0 1 0 17.71 Tm /F4 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* ET
+BT 1 0 0 1 0 5.71 Tm /F4 10 Tf 12 TL (kw={'name': 'value'}) Tj T* ET
Q
Q
Q
Q
Q
q
-1 0 0 1 56.69291 56.69291 cm
+1 0 0 1 62.69291 707.8236 cm
q
-0 0 0 rg
-BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (12) Tj T* -235.3849 0 Td ET
+BT 1 0 0 1 0 16.82 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 /F4 10 Tf (opt ) Tj /F1 10 Tf (is taken:) Tj T* ET
Q
Q
-
-endstream
-
-endobj
-% 'R193': class PDFStream
-193 0 obj
-% page stream
-<< /Length 3629 >>
-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
+1 0 0 1 62.69291 650.6236 cm
q
q
1 0 0 1 0 0 cm
@@ -4895,37 +4923,37 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 24 re B*
+n -6 -6 468.6898 48 re B*
Q
q
0 0 0 rg
-BT 1 0 0 1 0 5.71 Tm /F4 10 Tf 12 TL (example12.py: error: colliding keyword arguments: opt) Tj T* ET
+BT 1 0 0 1 0 29.71 Tm /F4 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 695.8236 cm
+1 0 0 1 62.69291 606.6236 cm
q
0 0 0 rg
BT 1 0 0 1 0 28.82 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 662.8236 cm
+1 0 0 1 62.69291 573.6236 cm
q
BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Final example: a shelve interface) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 620.8236 cm
+1 0 0 1 62.69291 531.6236 cm
q
BT 1 0 0 1 0 28.82 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 120.0682 cm
+1 0 0 1 62.69291 110.8981 cm
q
q
.952737 0 0 .952737 0 0 cm
@@ -4935,10 +4963,10 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 492 516 re B*
+n -6 -6 492 432 re B*
Q
q
-BT 1 0 0 1 0 497.71 Tm 12 TL /F4 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") Tj T* ( sh = shelve.open\(filename\)) Tj T* ( try:) Tj T* ( if not any\([help, showall, clear, delete, params, setters]\):) Tj T* ( 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* ET
+BT 1 0 0 1 0 413.71 Tm 12 TL /F4 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") Tj T* ( sh = shelve.open\(filename\)) Tj T* ( try:) Tj T* ( if not any\([help, showall, clear, delete, params, setters]\):) Tj T* ( 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* ET
Q
Q
Q
@@ -4958,11 +4986,11 @@ endobj % 'R194': class PDFStream
194 0 obj
% page stream
-<< /Length 6972 >>
+<< /Length 6795 >>
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
+1 0 0 1 62.69291 523.8236 cm
q
q
1 0 0 1 0 0 cm
@@ -4972,30 +5000,30 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 156 re B*
+n -6 -6 468.6898 240 re B*
Q
q
-BT 1 0 0 1 0 137.71 Tm 12 TL /F4 10 Tf 0 0 0 rg ( 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
+BT 1 0 0 1 0 221.71 Tm 12 TL /F4 10 Tf 0 0 0 rg ( 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 587.8236 cm
+1 0 0 1 62.69291 503.8236 cm
q
0 0 0 rg
BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (A few notes are in order:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 581.8236 cm
+1 0 0 1 62.69291 497.8236 cm
Q
q
-1 0 0 1 62.69291 581.8236 cm
+1 0 0 1 62.69291 497.8236 cm
Q
q
-1 0 0 1 62.69291 551.8236 cm
+1 0 0 1 62.69291 467.8236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -5015,13 +5043,13 @@ q Q
Q
q
-1 0 0 1 62.69291 551.8236 cm
+1 0 0 1 62.69291 467.8236 cm
Q
q
-1 0 0 1 62.69291 551.8236 cm
+1 0 0 1 62.69291 467.8236 cm
Q
q
-1 0 0 1 62.69291 533.8236 cm
+1 0 0 1 62.69291 449.8236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -5042,13 +5070,13 @@ q Q
Q
q
-1 0 0 1 62.69291 533.8236 cm
+1 0 0 1 62.69291 449.8236 cm
Q
q
-1 0 0 1 62.69291 533.8236 cm
+1 0 0 1 62.69291 449.8236 cm
Q
q
-1 0 0 1 62.69291 503.8236 cm
+1 0 0 1 62.69291 419.8236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -5068,13 +5096,13 @@ q Q
Q
q
-1 0 0 1 62.69291 503.8236 cm
+1 0 0 1 62.69291 419.8236 cm
Q
q
-1 0 0 1 62.69291 503.8236 cm
+1 0 0 1 62.69291 419.8236 cm
Q
q
-1 0 0 1 62.69291 473.8236 cm
+1 0 0 1 62.69291 389.8236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -5094,13 +5122,13 @@ q Q
Q
q
-1 0 0 1 62.69291 473.8236 cm
+1 0 0 1 62.69291 389.8236 cm
Q
q
-1 0 0 1 62.69291 473.8236 cm
+1 0 0 1 62.69291 389.8236 cm
Q
q
-1 0 0 1 62.69291 455.8236 cm
+1 0 0 1 62.69291 371.8236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -5120,13 +5148,13 @@ q Q
Q
q
-1 0 0 1 62.69291 455.8236 cm
+1 0 0 1 62.69291 371.8236 cm
Q
q
-1 0 0 1 62.69291 455.8236 cm
+1 0 0 1 62.69291 371.8236 cm
Q
q
-1 0 0 1 62.69291 437.8236 cm
+1 0 0 1 62.69291 353.8236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -5146,13 +5174,13 @@ q Q
Q
q
-1 0 0 1 62.69291 437.8236 cm
+1 0 0 1 62.69291 353.8236 cm
Q
q
-1 0 0 1 62.69291 437.8236 cm
+1 0 0 1 62.69291 353.8236 cm
Q
q
-1 0 0 1 62.69291 419.8236 cm
+1 0 0 1 62.69291 335.8236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -5172,13 +5200,13 @@ q Q
Q
q
-1 0 0 1 62.69291 419.8236 cm
+1 0 0 1 62.69291 335.8236 cm
Q
q
-1 0 0 1 62.69291 419.8236 cm
+1 0 0 1 62.69291 335.8236 cm
Q
q
-1 0 0 1 62.69291 377.8236 cm
+1 0 0 1 62.69291 293.8236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -5198,19 +5226,19 @@ q Q
Q
q
-1 0 0 1 62.69291 377.8236 cm
+1 0 0 1 62.69291 293.8236 cm
Q
q
-1 0 0 1 62.69291 377.8236 cm
+1 0 0 1 62.69291 293.8236 cm
Q
q
-1 0 0 1 62.69291 359.8236 cm
+1 0 0 1 62.69291 275.8236 cm
q
BT 1 0 0 1 0 4.82 Tm 12 TL /F1 10 Tf 0 0 0 rg (If you run ) Tj /F4 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 62.69291 314.6236 cm
+1 0 0 1 62.69291 230.6236 cm
q
q
1 0 0 1 0 0 cm
@@ -5231,13 +5259,13 @@ Q Q
Q
q
-1 0 0 1 62.69291 294.6236 cm
+1 0 0 1 62.69291 210.6236 cm
q
BT 1 0 0 1 0 4.82 Tm 12 TL /F1 10 Tf 0 0 0 rg (If you run ) Tj /F4 10 Tf (ishelve.py ) Tj /F1 10 Tf (with the option ) Tj /F4 10 Tf (.h ) Tj /F1 10 Tf (\(or any abbreviation of ) Tj /F4 10 Tf (.help) Tj /F1 10 Tf (\) you get:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 225.4236 cm
+1 0 0 1 62.69291 141.4236 cm
q
q
1 0 0 1 0 0 cm
@@ -5257,34 +5285,13 @@ Q Q
Q
q
-1 0 0 1 62.69291 205.4236 cm
+1 0 0 1 62.69291 121.4236 cm
q
0 0 0 rg
BT 1 0 0 1 0 4.82 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 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 96 re B*
-Q
-q
-0 0 0 rg
-BT 1 0 0 1 0 77.71 Tm /F4 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* ET
-Q
-Q
-Q
-Q
-Q
-q
1 0 0 1 56.69291 56.69291 cm
q
0 0 0 rg
@@ -5298,11 +5305,11 @@ endobj % 'R195': class PDFStream
195 0 obj
% page stream
-<< /Length 8351 >>
+<< /Length 7204 >>
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
+1 0 0 1 62.69291 523.8236 cm
q
q
1 0 0 1 0 0 cm
@@ -5312,36 +5319,36 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 156 re B*
+n -6 -6 468.6898 240 re B*
Q
q
0 0 0 rg
-BT 1 0 0 1 0 137.71 Tm /F4 10 Tf 12 TL (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
+BT 1 0 0 1 0 221.71 Tm /F4 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 574.8236 cm
+1 0 0 1 62.69291 490.8236 cm
q
BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (plac vs argparse) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 532.8236 cm
+1 0 0 1 62.69291 448.8236 cm
q
BT 1 0 0 1 0 28.82 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 526.8236 cm
+1 0 0 1 62.69291 442.8236 cm
Q
q
-1 0 0 1 62.69291 526.8236 cm
+1 0 0 1 62.69291 442.8236 cm
Q
q
-1 0 0 1 62.69291 448.8236 cm
+1 0 0 1 62.69291 364.8236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -5361,13 +5368,13 @@ q Q
Q
q
-1 0 0 1 62.69291 448.8236 cm
+1 0 0 1 62.69291 364.8236 cm
Q
q
-1 0 0 1 62.69291 448.8236 cm
+1 0 0 1 62.69291 364.8236 cm
Q
q
-1 0 0 1 62.69291 394.8236 cm
+1 0 0 1 62.69291 310.8236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -5387,13 +5394,13 @@ q Q
Q
q
-1 0 0 1 62.69291 394.8236 cm
+1 0 0 1 62.69291 310.8236 cm
Q
q
-1 0 0 1 62.69291 394.8236 cm
+1 0 0 1 62.69291 310.8236 cm
Q
q
-1 0 0 1 62.69291 328.8236 cm
+1 0 0 1 62.69291 244.8236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -5413,13 +5420,13 @@ q Q
Q
q
-1 0 0 1 62.69291 328.8236 cm
+1 0 0 1 62.69291 244.8236 cm
Q
q
-1 0 0 1 62.69291 328.8236 cm
+1 0 0 1 62.69291 244.8236 cm
Q
q
-1 0 0 1 62.69291 286.8236 cm
+1 0 0 1 62.69291 202.8236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -5439,13 +5446,13 @@ q Q
Q
q
-1 0 0 1 62.69291 286.8236 cm
+1 0 0 1 62.69291 202.8236 cm
Q
q
-1 0 0 1 62.69291 286.8236 cm
+1 0 0 1 62.69291 202.8236 cm
Q
q
-1 0 0 1 62.69291 256.8236 cm
+1 0 0 1 62.69291 172.8236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -5465,13 +5472,13 @@ q Q
Q
q
-1 0 0 1 62.69291 256.8236 cm
+1 0 0 1 62.69291 172.8236 cm
Q
q
-1 0 0 1 62.69291 256.8236 cm
+1 0 0 1 62.69291 172.8236 cm
Q
q
-1 0 0 1 62.69291 214.8236 cm
+1 0 0 1 62.69291 130.8236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -5491,30 +5498,18 @@ q Q
Q
q
-1 0 0 1 62.69291 214.8236 cm
+1 0 0 1 62.69291 130.8236 cm
Q
q
-1 0 0 1 62.69291 214.8236 cm
+1 0 0 1 62.69291 130.8236 cm
Q
q
-1 0 0 1 62.69291 196.8236 cm
+1 0 0 1 62.69291 112.8236 cm
q
BT 1 0 0 1 0 4.82 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 154.8236 cm
-q
-BT 1 0 0 1 0 28.82 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 /F4 10 Tf ('==SUPPRESS==' ) Tj /F1 10 Tf (as help string \(or ) Tj /F4 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 100.8236 cm
-q
-BT 1 0 0 1 0 40.82 Tm 1.639213 Tw 12 TL /F1 10 Tf 0 0 0 rg (It is also possible to pass options to the underlying ) Tj /F4 10 Tf (argparse.ArgumentParser ) Tj /F1 10 Tf (object \(currently it) Tj T* 0 Tw .285529 Tw (accepts the default arguments ) Tj /F4 10 Tf (description) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (epilog) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (prog) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (usage) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (add_help) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (argument_default) Tj /F1 10 Tf (,) Tj T* 0 Tw 1.439953 Tw /F4 10 Tf (parents) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (prefix_chars) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (fromfile_prefix_chars) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (conflict_handler) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (formatter_class) Tj /F1 10 Tf (\). It) Tj T* 0 Tw (is enough to set such attributes on the ) Tj /F4 10 Tf (main ) Tj /F1 10 Tf (function. For instance) Tj T* ET
-Q
-Q
-q
1 0 0 1 56.69291 56.69291 cm
q
0 0 0 rg
@@ -5528,11 +5523,23 @@ endobj % 'R196': class PDFStream
196 0 obj
% page stream
-<< /Length 7105 >>
+<< /Length 6860 >>
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
+1 0 0 1 62.69291 729.0236 cm
+q
+BT 1 0 0 1 0 28.82 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 /F4 10 Tf ('==SUPPRESS==' ) Tj /F1 10 Tf (as help string \(or ) Tj /F4 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 675.0236 cm
+q
+BT 1 0 0 1 0 40.82 Tm 1.639213 Tw 12 TL /F1 10 Tf 0 0 0 rg (It is also possible to pass options to the underlying ) Tj /F4 10 Tf (argparse.ArgumentParser ) Tj /F1 10 Tf (object \(currently it) Tj T* 0 Tw .285529 Tw (accepts the default arguments ) Tj /F4 10 Tf (description) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (epilog) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (prog) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (usage) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (add_help) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (argument_default) Tj /F1 10 Tf (,) Tj T* 0 Tw 1.439953 Tw /F4 10 Tf (parents) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (prefix_chars) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (fromfile_prefix_chars) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (conflict_handler) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (formatter_class) Tj /F1 10 Tf (\). It) Tj T* 0 Tw (is enough to set such attributes on the ) Tj /F4 10 Tf (main ) Tj /F1 10 Tf (function. For instance) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 605.8236 cm
q
q
1 0 0 1 0 0 cm
@@ -5553,26 +5560,26 @@ Q Q
Q
q
-1 0 0 1 62.69291 659.8236 cm
+1 0 0 1 62.69291 561.8236 cm
q
BT 1 0 0 1 0 28.82 Tm .239318 Tw 12 TL /F1 10 Tf 0 0 0 rg (disables the recognition of the help flag ) Tj /F4 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 629.8236 cm
+1 0 0 1 62.69291 531.8236 cm
q
BT 1 0 0 1 0 16.82 Tm 2.391235 Tw 12 TL /F1 10 Tf 0 0 0 rg (For instance, by setting the ) Tj /F4 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 /F4 10 Tf (main ) Tj /F1 10 Tf (function is used as description\).) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 599.8236 cm
+1 0 0 1 62.69291 501.8236 cm
q
0 0 0 rg
BT 1 0 0 1 0 16.82 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 566.6236 cm
+1 0 0 1 62.69291 468.6236 cm
q
q
1 0 0 1 0 0 cm
@@ -5593,19 +5600,19 @@ Q Q
Q
q
-1 0 0 1 62.69291 522.6236 cm
+1 0 0 1 62.69291 424.6236 cm
q
BT 1 0 0 1 0 28.82 Tm .924198 Tw 12 TL /F1 10 Tf 0 0 0 rg (The first prefix char \() Tj /F4 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 /F4 10 Tf (-) Tj /F1 10 Tf (\) is kept to keep the ) Tj /F4 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 /F4 10 Tf (ishelve ) Tj /F1 10 Tf (example.) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 492.6236 cm
+1 0 0 1 62.69291 394.6236 cm
q
BT 1 0 0 1 0 16.82 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 /F4 10 Tf (plac.parser_from ) Tj /F1 10 Tf (utility function:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 399.4236 cm
+1 0 0 1 62.69291 301.4236 cm
q
q
1 0 0 1 0 0 cm
@@ -5625,43 +5632,60 @@ Q Q
Q
q
-1 0 0 1 62.69291 355.4236 cm
+1 0 0 1 62.69291 257.4236 cm
q
BT 1 0 0 1 0 28.82 Tm 2.646905 Tw 12 TL /F1 10 Tf 0 0 0 rg (Internally ) Tj /F4 10 Tf (plac.call ) Tj /F1 10 Tf (uses ) Tj /F4 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 /F4 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 325.4236 cm
+1 0 0 1 62.69291 227.4236 cm
q
BT 1 0 0 1 0 16.82 Tm .982765 Tw 12 TL /F1 10 Tf 0 0 0 rg (I use ) Tj /F4 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 /F5 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 62.69291 271.4236 cm
+1 0 0 1 62.69291 173.4236 cm
q
BT 1 0 0 1 0 40.82 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 /F4 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 /F4 10 Tf (.p ) Tj /F1 10 Tf (attribute.) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 238.4236 cm
+1 0 0 1 62.69291 140.4236 cm
q
BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (plac vs the rest of the world) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 196.4236 cm
+1 0 0 1 62.69291 98.42362 cm
q
BT 1 0 0 1 0 28.82 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 190.4236 cm
+1 0 0 1 62.69291 92.42362 cm
Q
q
-1 0 0 1 62.69291 190.4236 cm
+1 0 0 1 62.69291 92.42362 cm
+Q
+q
+1 0 0 1 56.69291 56.69291 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (16) Tj T* -235.3849 0 Td ET
+Q
Q
+
+endstream
+
+endobj
+% 'R197': class PDFStream
+197 0 obj
+% page stream
+<< /Length 7645 >>
+stream
+1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
q
-1 0 0 1 62.69291 172.4236 cm
+1 0 0 1 62.69291 747.0236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -5681,13 +5705,13 @@ q Q
Q
q
-1 0 0 1 62.69291 172.4236 cm
+1 0 0 1 62.69291 747.0236 cm
Q
q
-1 0 0 1 62.69291 172.4236 cm
+1 0 0 1 62.69291 747.0236 cm
Q
q
-1 0 0 1 62.69291 154.4236 cm
+1 0 0 1 62.69291 729.0236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -5707,90 +5731,67 @@ q Q
Q
q
-1 0 0 1 62.69291 154.4236 cm
+1 0 0 1 62.69291 729.0236 cm
Q
q
-1 0 0 1 62.69291 154.4236 cm
+1 0 0 1 62.69291 729.0236 cm
Q
q
-1 0 0 1 62.69291 124.4236 cm
+1 0 0 1 62.69291 699.0236 cm
q
BT 1 0 0 1 0 16.82 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 94.42362 cm
-q
-BT 1 0 0 1 0 16.82 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 ET
-Q
-Q
-q
-1 0 0 1 56.69291 56.69291 cm
-q
-0 0 0 rg
-BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (16) Tj T* -235.3849 0 Td ET
-Q
-Q
-
-endstream
-
-endobj
-% 'R197': class PDFStream
-197 0 obj
-% page stream
-<< /Length 6121 >>
-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
+1 0 0 1 62.69291 657.0236 cm
q
-BT 1 0 0 1 0 4.82 Tm 12 TL /F1 10 Tf 0 0 .501961 rg (plac) Tj 0 0 0 rg (.) Tj T* ET
+BT 1 0 0 1 0 28.82 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 720.0236 cm
+1 0 0 1 62.69291 624.0236 cm
q
BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (The future) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 654.0236 cm
+1 0 0 1 62.69291 558.0236 cm
q
BT 1 0 0 1 0 52.82 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 612.0236 cm
+1 0 0 1 62.69291 516.0236 cm
q
BT 1 0 0 1 0 28.82 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 546.0236 cm
+1 0 0 1 62.69291 450.0236 cm
q
BT 1 0 0 1 0 52.82 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 /F4 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 /F4 10 Tf (plac_ext.py) Tj /F1 10 Tf (\), since they require Python 2.5 to work, whereas ) Tj /F4 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 513.0236 cm
+1 0 0 1 62.69291 417.0236 cm
q
BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Trivia: the story behind the name) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 447.0236 cm
+1 0 0 1 62.69291 351.0236 cm
q
BT 1 0 0 1 0 52.82 Tm .942651 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 humble: 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 441.0236 cm
+1 0 0 1 62.69291 345.0236 cm
Q
q
-1 0 0 1 62.69291 441.0236 cm
+1 0 0 1 62.69291 345.0236 cm
Q
q
-1 0 0 1 62.69291 411.0236 cm
+1 0 0 1 62.69291 315.0236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -5810,13 +5811,13 @@ q Q
Q
q
-1 0 0 1 62.69291 411.0236 cm
+1 0 0 1 62.69291 315.0236 cm
Q
q
-1 0 0 1 62.69291 411.0236 cm
+1 0 0 1 62.69291 315.0236 cm
Q
q
-1 0 0 1 62.69291 381.0236 cm
+1 0 0 1 62.69291 285.0236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -5837,32 +5838,32 @@ q Q
Q
q
-1 0 0 1 62.69291 381.0236 cm
+1 0 0 1 62.69291 285.0236 cm
Q
q
-1 0 0 1 62.69291 381.0236 cm
+1 0 0 1 62.69291 285.0236 cm
Q
q
-1 0 0 1 62.69291 327.0236 cm
+1 0 0 1 62.69291 231.0236 cm
q
BT 1 0 0 1 0 40.82 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 /F4 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 /F4 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 297.0236 cm
+1 0 0 1 62.69291 201.0236 cm
q
BT 1 0 0 1 0 16.82 Tm 1.093876 Tw 12 TL /F1 10 Tf 0 0 0 rg (I made a research on PyPI and the name ) Tj /F5 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 267.0236 cm
+1 0 0 1 62.69291 171.0236 cm
q
0 0 0 rg
BT 1 0 0 1 0 16.82 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 249.0236 cm
+1 0 0 1 62.69291 153.0236 cm
q
BT 1 0 0 1 0 4.82 Tm 12 TL /F1 10 Tf 0 0 0 rg (That's all, I hope you will enjoy working with ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (!) Tj T* ET
Q
@@ -6144,45 +6145,45 @@ xref 0000031138 00000 n
0000031417 00000 n
0000031671 00000 n
-0000031923 00000 n
-0000032226 00000 n
-0000032757 00000 n
-0000033011 00000 n
-0000033300 00000 n
-0000033552 00000 n
-0000033804 00000 n
-0000034058 00000 n
-0000034312 00000 n
-0000034566 00000 n
-0000034825 00000 n
-0000035082 00000 n
-0000035334 00000 n
-0000035588 00000 n
-0000035846 00000 n
-0000036098 00000 n
-0000036341 00000 n
-0000036792 00000 n
-0000037046 00000 n
-0000037300 00000 n
-0000037552 00000 n
-0000037804 00000 n
-0000038057 00000 n
-0000038311 00000 n
-0000038565 00000 n
-0000038819 00000 n
-0000039073 00000 n
-0000039352 00000 n
-0000039606 00000 n
-0000039893 00000 n
-0000040147 00000 n
-0000040458 00000 n
-0000040710 00000 n
-0000040962 00000 n
-0000041251 00000 n
-0000041503 00000 n
-0000041755 00000 n
-0000042013 00000 n
-0000042252 00000 n
+0000031908 00000 n
+0000032429 00000 n
+0000032747 00000 n
+0000033001 00000 n
+0000033290 00000 n
+0000033542 00000 n
+0000033794 00000 n
+0000034048 00000 n
+0000034302 00000 n
+0000034541 00000 n
+0000034932 00000 n
+0000035191 00000 n
+0000035448 00000 n
+0000035700 00000 n
+0000035954 00000 n
+0000036212 00000 n
+0000036464 00000 n
+0000036722 00000 n
+0000036976 00000 n
+0000037230 00000 n
+0000037482 00000 n
+0000037734 00000 n
+0000037987 00000 n
+0000038241 00000 n
+0000038495 00000 n
+0000038749 00000 n
+0000039003 00000 n
+0000039282 00000 n
+0000039536 00000 n
+0000039823 00000 n
+0000040077 00000 n
+0000040388 00000 n
+0000040640 00000 n
+0000040892 00000 n
+0000041181 00000 n
+0000041433 00000 n
+0000041685 00000 n
+0000041943 00000 n
+0000042182 00000 n
0000042759 00000 n
0000042923 00000 n
0000043197 00000 n
@@ -6211,39 +6212,39 @@ xref 0000082887 00000 n
0000088055 00000 n
0000093492 00000 n
-0000098162 00000 n
-0000102104 00000 n
-0000106055 00000 n
-0000109787 00000 n
-0000116862 00000 n
-0000125316 00000 n
-0000132524 00000 n
-0000138752 00000 n
-0000139079 00000 n
-0000139158 00000 n
-0000139237 00000 n
-0000139316 00000 n
-0000139395 00000 n
-0000139474 00000 n
-0000139553 00000 n
-0000139632 00000 n
-0000139711 00000 n
-0000139790 00000 n
-0000139870 00000 n
-0000139950 00000 n
-0000140030 00000 n
-0000140110 00000 n
-0000140190 00000 n
-0000140270 00000 n
-0000140350 00000 n
+0000098106 00000 n
+0000102445 00000 n
+0000105971 00000 n
+0000110121 00000 n
+0000117019 00000 n
+0000124326 00000 n
+0000131289 00000 n
+0000139041 00000 n
+0000139368 00000 n
+0000139447 00000 n
+0000139526 00000 n
+0000139605 00000 n
+0000139684 00000 n
+0000139763 00000 n
+0000139842 00000 n
+0000139921 00000 n
+0000140000 00000 n
+0000140079 00000 n
+0000140159 00000 n
+0000140239 00000 n
+0000140319 00000 n
+0000140399 00000 n
+0000140479 00000 n
+0000140559 00000 n
+0000140639 00000 n
trailer
<< /ID
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
- [(\270G\023\(\355mFCp\200\3773\015M\207;) (\270G\023\(\355mFCp\200\3773\015M\207;)]
+ [(\035U\351\206J$\3719\335\010i\224\201=?\204) (\035U\351\206J$\3719\335\010i\224\201=?\204)]
/Info 164 0 R
/Root 163 0 R
/Size 216 >>
startxref
-140399
+140688
%%EOF
diff --git a/plac/doc/plac.txt b/plac/doc/plac.txt index ccbe8f7..f43d57c 100644 --- a/plac/doc/plac.txt +++ b/plac/doc/plac.txt @@ -419,13 +419,19 @@ errors visible early, and avoids mistakes in code like the following:: except: # do something -Without the ``listify`` functionality, a main function returning a +Without the "listify" functionality, a main function returning a generator object would not raise any exception until the generator -is iterated over. +is iterated over. -Moreover you can rely on type checks like ``isinstance(result, list)`` -for the output of ``plac.call``, to check if the output is an iterable -or not. +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 --------------------------------------- diff --git a/plac/doc/plac_adv.html b/plac/doc/plac_adv.html index ecbaf2f..5629997 100644 --- a/plac/doc/plac_adv.html +++ b/plac/doc/plac_adv.html @@ -4,9 +4,9 @@ <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="generator" content="Docutils 0.6: http://docutils.sourceforge.net/" /> -<title>Testing and scripting your applications with plac</title> +<title>Advanced usages of plac</title> <meta name="author" content="Michele Simionato" /> -<meta name="date" content="June 2010" /> +<meta name="date" content="July 2010" /> <style type="text/css"> .first { @@ -413,8 +413,8 @@ h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { </style> </head> <body> -<div class="document" id="testing-and-scripting-your-applications-with-plac"> -<h1 class="title">Testing and scripting your applications with plac</h1> +<div class="document" id="advanced-usages-of-plac"> +<h1 class="title">Advanced usages of plac</h1> <table class="docinfo" frame="void" rules="none"> <col class="docinfo-name" /> <col class="docinfo-content" /> @@ -424,7 +424,7 @@ h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { <tr class="field"><th class="docinfo-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><th class="docinfo-name">Date:</th> -<td>June 2010</td></tr> +<td>July 2010</td></tr> <tr class="field"><th class="docinfo-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="docinfo-name">Project page:</th><td class="field-body"><a class="reference external" href="http://micheles.googlecode.com/hg/plac/doc/plac.html">http://micheles.googlecode.com/hg/plac/doc/plac.html</a></td> @@ -438,7 +438,9 @@ h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { </tbody> </table> <p><em>The present document discusses a few of the advanced use -cases for plac. It assumes you have already read an understood the +cases for plac. It shows how to write interactive and non-interactive +interpreters with plac, and how to use plac for testing and scripting a generic +application. It assumes you have already read an understood the basic documentation.</em></p> <div class="contents topic" id="contents"> <p class="topic-title first">Contents</p> @@ -448,13 +450,16 @@ basic documentation.</em></p> <li><a class="reference internal" href="#testing-a-plac-application" id="id3">Testing a plac application</a></li> <li><a class="reference internal" href="#plac-easy-tests" id="id4">Plac easy tests</a></li> <li><a class="reference internal" href="#plac-batch-scripts" id="id5">Plac batch scripts</a></li> -<li><a class="reference internal" href="#containers-of-commands" id="id6">Containers of commands</a></li> -<li><a class="reference internal" href="#for-cmd-lovers" id="id7">For <tt class="docutils literal">cmd</tt> lovers</a></li> +<li><a class="reference internal" href="#implementing-subcommands" id="id6">Implementing subcommands</a></li> +<li><a class="reference internal" href="#readline-support" id="id7">Readline support</a></li> <li><a class="reference internal" href="#the-plac-runner" id="id8">The plac runner</a></li> <li><a class="reference internal" href="#a-non-class-based-example" id="id9">A non class-based example</a></li> <li><a class="reference internal" href="#writing-your-own-plac-runner" id="id10">Writing your own plac runner</a></li> -<li><a class="reference internal" href="#summary" id="id11">Summary</a></li> -<li><a class="reference internal" href="#appendix-custom-annotation-objects" id="id12">Appendix: custom annotation objects</a></li> +<li><a class="reference internal" href="#long-running-commands" id="id11">Long running commands</a></li> +<li><a class="reference internal" href="#threaded-commands" id="id12">Threaded commands</a></li> +<li><a class="reference internal" href="#running-commands-as-external-processes" id="id13">Running commands as external processes</a></li> +<li><a class="reference internal" href="#summary" id="id14">Summary</a></li> +<li><a class="reference internal" href="#appendix-custom-annotation-objects" id="id15">Appendix: custom annotation objects</a></li> </ul> </div> <div class="section" id="introduction"> @@ -524,8 +529,9 @@ $ python shelve_interpreter.py -h usage: shelve_interpreter.py [-h] [-interactive] [subcommands [subcommands ...]] -This script works both interactively and non-interactively. Use .help to see -the internal commands. + 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 @@ -541,13 +547,14 @@ and non-interactively:</p> $ python shelve_interpreter.py .clear # non-interactive use cleared the shelve </pre> -<p>Here is an usage session, using <a class="reference external" href="http://freshmeat.net/projects/rlwrap/">rlwrap</a> to enable readline features -(<a class="reference external" href="http://freshmeat.net/projects/rlwrap/">rlwrap</a> is available in Unix-like systems):</p> +<p>Here is an usage session:</p> <pre class="literal-block"> -$ rlwrap python shelve_interpreter.py -i # interactive use -usage: shelve_interpreter.py [.help] [.showall] [.clear] [.delete DELETE] - [.filename /home/micheles/conf.shelve] - [params [params ...]] [setters [setters ...]] +$ 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 @@ -570,17 +577,19 @@ 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 message displayed -by default is the argparse-provided usage message, but can be -customized by setting an <tt class="docutils literal">.intro</tt> attribute on the main function.</p> -<p>Notice that <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> -<p>You can conveniently test your application in interactive mode. -However manual testing is a poor substitute for automatic testing.</p> +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"> <h1><a class="toc-backref" href="#id3">Testing a plac application</a></h1> +<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"> @@ -623,8 +632,8 @@ def test(): </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.</p> -<p><tt class="docutils literal">AssertionError</tt> is catched by tools like <tt class="docutils literal">py.test</tt> and +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 @@ -649,7 +658,7 @@ improvement over writing them in terms of <tt class="docutils literal">plac.call 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! +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"> @@ -665,7 +674,7 @@ deleted a i> a a: not found i> .cler # spelling error -SystemExit: unrecognized arguments: .cler +.cler: not found </pre> <p>Notice the precence of the shebang line containing the name of the @@ -682,25 +691,26 @@ plac.Interpreter(ishelve.main).doctest(open('ishelve.placet'), verbose=True)&quo </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. The -easy tests supported by <tt class="docutils literal">plac</tt> are <em>not</em> unittests: they should be -used to model user interaction when the order of the operations -matters. Since the single subtests in a <tt class="docutils literal">.placet</tt> file are not -independent, 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 +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>), continuation lines, 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.</p> +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.</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 (plac easy tests does not need to be in +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 plac easy tests do not use any code from the doctest +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 @@ -724,7 +734,7 @@ def test_doct(): </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 shabng line. You can use both the full path name of the +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 @@ -745,8 +755,9 @@ background in case of unexpected errors. The implementation of <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>In particular consider the following batch file, which contains a syntax -error (<tt class="docutils literal">.dl</tt> instead of <tt class="docutils literal">.del</tt>):</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 @@ -757,9 +768,8 @@ a=1 b=2 .show </pre> -<p>If you execute the batch file, the interpreter will raise a <tt class="docutils literal">SystemExit</tt> -with an appropriated error message at the <tt class="docutils literal">.dl</tt> line and the last command -will <em>not</em> be executed:</p> +<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)" @@ -774,19 +784,22 @@ a=1 i> .del a deleted a i> .dl b -unrecognized arguments: .dl +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="containers-of-commands"> -<h1><a class="toc-backref" href="#id6">Containers of commands</a></h1> +<div class="section" id="implementing-subcommands"> +<h1><a class="toc-backref" href="#id6">Implementing subcommands</a></h1> <p>When I discussed the <tt class="docutils literal">ishelve</tt> implementation in the <a class="reference external" href="http://micheles.googlecode.com/hg/plac/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 better. Here I will substantiate my claim.</p> +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 @@ -794,25 +807,28 @@ 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).</p> -<p>Using this feature the shelve interface can be rewritten in a more -object-oriented way as follows:</p> +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>Rhe 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" + "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='~/conf.shelve'): self.fname = os.path.expanduser(configfile) - self.intro = 'Operating on %s. Available commands:\n%s' % ( - self.fname, '\n'.join(c for c in self.commands)) + self.__doc__ += '\nOperating on %s.\n.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) @@ -833,14 +849,12 @@ class ShelveInterface(object): else: yield 'deleting %s' % name del self.sh[name] # no error checking - def __exit__(self, etype, exc, tb): - self.sh.close() main = ShelveInterface # the main 'function' can also be a class! if __name__ == '__main__': - i = plac.Interpreter(main()) - i.interact() + shelve_interface = plac.call(main) + plac.Interpreter(shelve_interface).interact() </pre> <p><tt class="docutils literal">plac.Interpreter</tt> objects wrap context manager objects @@ -854,14 +868,30 @@ 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"> -$ rlwrap python ishelve2.py -Operating on /home/micheles/conf.shelve. Available commands: -set -show -showall -delete +$ python ishelve2.py +A minimal interface over a shelve object. +Operating on /home/micheles/conf.shelve. +.help to see the available commands. +i> .help + +special commands +================ +.help .last_tb + +custom commands +=============== +delete set show showall + i> delete deleting everything i> set a pippo @@ -879,77 +909,128 @@ deleting a i> showall b = lippo i> delete a -DBNotFoundError: (-30988, 'DB_NOTFOUND: No matching key/data pair found') +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>The interactive mode of <tt class="docutils literal">plac</tt> can be used as a replacement of the -<tt class="docutils literal">cmd</tt> module in the standard library. There are a few differences, -however. For instance you miss tab completion, even if use <a class="reference external" href="http://freshmeat.net/projects/rlwrap/">rlwrap</a> -(you get persistent command history for free, however). This is not -a big issue, since <tt class="docutils literal">plac</tt> understands command abbreviations.</p> -<p>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"> -$ rlwrap python ishelve2.py -usage: plac_runner.py ishelve2.py [-h] {delete,set,showall,show} ... -i> sh -NameError: Ambiguous command 'sh': matching ['showall', 'show'] -</pre> </div> -<div class="section" id="for-cmd-lovers"> -<h1><a class="toc-backref" href="#id7">For <tt class="docutils literal">cmd</tt> lovers</a></h1> -<p>I have been using the <a class="reference external" href="http://docs.python.org/library/cmd.html">cmd</a> module of the standard library for years. -I have also written a much enhanced <tt class="docutils literal">cmd2</tt> module which we are using -internally at work and from which I have taken some ideas used in <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a>. -In many ways <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> makes the <a class="reference external" href="http://docs.python.org/library/cmd.html">cmd</a> module obsolete, -but I realize why many nostalgic souls would still use <a class="reference external" href="http://docs.python.org/library/cmd.html">cmd</a>, especially -until <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> does not grow real auto-completion features, instead of -relying on <a class="reference external" href="http://freshmeat.net/projects/rlwrap/">rlwrap</a>. But there must not be competition between <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> -and <a class="reference external" href="http://docs.python.org/library/cmd.html">cmd</a>: actually the two can happily work togethere. For this -reason I have put in the <tt class="docutils literal">plac_ext</tt> module a few lines of code -for gluing together <a class="reference external" href="http://docs.python.org/library/cmd.html">cmd</a> and <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a>, the <tt class="docutils literal">cmd_interface</tt>. -Using the <tt class="docutils literal">cmd_interface</tt> is quite trivial: give to it a plac -command container and you will get in exchange a <tt class="docutils literal">cmd.Cmd</tt> object:</p> +<div class="section" id="readline-support"> +<h1><a class="toc-backref" href="#id7">Readline support</a></h1> +<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 sensitivirt, or you want to change the prompt, the way to do +it 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"> -# cmd_ext.py -from plac_ext import cmd_interface -import ishelve2 +import os, plac +from sqlalchemy.ext.sqlsoup import SqlSoup + +SQLKEYWORDS = set(['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, *args): + sql = 'SELECT ' + ' '.join(args) + for row in self.soup.bind.execute(sql): + yield str(row) # the formatting can be much improved + +rl_input = plac.ReadlineInput( + COMPLETIONS, prompt='sql> ', + histfile=os.path.expanduser('~/.sql_interface.history'), + case_sensitive=False) if __name__ == '__main__': - cmd_interface(ishelve2.main()).cmdloop() + plac.Interpreter(plac.call(SqlInterface)).interact(rl_input) </pre> -<p>Here is an example of interactive session:</p> +<p>Here is an example of usage:</p> <pre class="literal-block"> -$ python cmd_ex.py -(Cmd) help +$ 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 pyreadline +library (I do not use Windows, so this part is very little tested). +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 -Documented commands (type help <topic>): -======================================== -delete set show showall +set name value -Undocumented commands: -====================== -EOF help +positional arguments: + name + value -(Cmd) set a 1 -setting a=1 -(Cmd) show a -a = 1 -(Cmd) showall -a = 1 -(Cmd) delete b -KeyError: 'b' -(Cmd) EOF [or CTRL-D] +i> .help delete +usage: delete [name] + +delete given parameter (or everything) + +positional arguments: + name + +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 should be considered deprecated and 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> -<p>Internally the <tt class="docutils literal">cmd_interface</tt> builds a <tt class="docutils literal">cmd.Cmd</tt> class and adds -to it the <tt class="docutils literal">do_</tt> methods corresponding to the commands in the container, -then it returns a <tt class="docutils literal">cmd.Cmd</tt> instance.</p> -<p>The <tt class="docutils literal">cmd_interface</tt> is just a proof of concept: it is there so that you -can study the source code and see an example of integration of <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a> -with a different framework. It may change and even go away in future -releases of <a class="reference external" href="http://pypi.python.org/pypi/plac">plac</a>.</p> </div> <div class="section" id="the-plac-runner"> <h1><a class="toc-backref" href="#id8">The plac runner</a></h1> @@ -987,17 +1068,16 @@ 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="rlwrap</span> plac_runner.py"</tt></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 -Operating on /home/micheles/conf.shelve. Available commands: -set -show -showall -delete +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 @@ -1017,8 +1097,8 @@ i> set a 1 setting a=1 i> set b 2 setting b=2 -i> show b -b = 2 +i> show a +a = 1 </pre> <p>Notice that the first line specifies a test database @@ -1245,8 +1325,196 @@ with 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"> +<h1><a class="toc-backref" href="#id11">Long running commands</a></h1> +<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(plac.call(FakeImporter)).interact() + +</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 +A fake importer with an import_file command +i> import_file file1 +Imported 100 lines +Imported 200 lines +Imported 300 lines +... <wait 3+ minutes> +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"> +<h1><a class="toc-backref" href="#id12">Threaded commands</a></h1> +<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>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(plac.call(FakeImporter)).interact() + +</pre> +</div> +<div class="section" id="running-commands-as-external-processes"> +<h1><a class="toc-backref" href="#id13">Running commands as external processes</a></h1> +<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="summary"> -<h1><a class="toc-backref" href="#id11">Summary</a></h1> +<h1><a class="toc-backref" href="#id14">Summary</a></h1> <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 @@ -1261,12 +1529,15 @@ rules are quite simple:</p> <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.</li> +<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> </ol> <p>Moreover, remember that <tt class="docutils literal">plac_runner.py</tt> is your friend.</p> </div> <div class="section" id="appendix-custom-annotation-objects"> -<h1><a class="toc-backref" href="#id12">Appendix: custom annotation objects</a></h1> +<h1><a class="toc-backref" href="#id15">Appendix: custom annotation objects</a></h1> <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> @@ -1320,10 +1591,6 @@ 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> -<!-- If the script is invoked with no arguments, the default help is -invoked: this is done by invoking the argparse_ low-level method -``.format_help``. In other words, the recognition of the couple -``-h/- -help`` is disabled, but the original help feature is still there. --> </div> </div> </body> diff --git a/plac/doc/plac_adv.pdf b/plac/doc/plac_adv.pdf index 0061f67..7abe4f7 100644 --- a/plac/doc/plac_adv.pdf +++ b/plac/doc/plac_adv.pdf @@ -7,8 +7,7 @@ /F2 3 0 R
/F3 4 0 R
/F4 8 0 R
- /F5 9 0 R
- /F6 22 0 R >>
+ /F5 9 0 R >>
endobj
% 'F1': class PDFType1Font
2 0 obj
@@ -106,15 +105,15 @@ endobj 0
0 ]
/Contents ()
- /Dest [ 42 0 R
+ /Dest [ 45 0 R
/XYZ
62.69291
- 302.0236
+ 236.0236
0 ]
/Rect [ 62.69291
- 518.5936
+ 506.5936
121.0229
- 530.5936 ]
+ 518.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -124,15 +123,15 @@ endobj 0
0 ]
/Contents ()
- /Dest [ 42 0 R
+ /Dest [ 45 0 R
/XYZ
62.69291
- 302.0236
+ 236.0236
0 ]
/Rect [ 527.0227
- 518.5936
+ 506.5936
532.5827
- 530.5936 ]
+ 518.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -142,15 +141,15 @@ endobj 0
0 ]
/Contents ()
- /Dest [ 44 0 R
+ /Dest [ 49 0 R
/XYZ
62.69291
- 765.0236
+ 717.0236
0 ]
/Rect [ 62.69291
- 500.5936
+ 488.5936
249.4129
- 512.5936 ]
+ 500.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -160,15 +159,15 @@ endobj 0
0 ]
/Contents ()
- /Dest [ 44 0 R
+ /Dest [ 49 0 R
/XYZ
62.69291
- 765.0236
+ 717.0236
0 ]
/Rect [ 527.0227
- 500.5936
+ 488.5936
532.5827
- 512.5936 ]
+ 500.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -178,15 +177,15 @@ endobj 0
0 ]
/Contents ()
- /Dest [ 47 0 R
+ /Dest [ 51 0 R
/XYZ
62.69291
- 306.6236
+ 259.4236
0 ]
/Rect [ 62.69291
- 482.5936
+ 470.5936
184.9529
- 494.5936 ]
+ 482.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -196,15 +195,15 @@ endobj 0
0 ]
/Contents ()
- /Dest [ 47 0 R
+ /Dest [ 51 0 R
/XYZ
62.69291
- 306.6236
+ 259.4236
0 ]
/Rect [ 527.0227
- 482.5936
+ 470.5936
532.5827
- 494.5936 ]
+ 482.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -214,15 +213,15 @@ endobj 0
0 ]
/Contents ()
- /Dest [ 48 0 R
+ /Dest [ 52 0 R
/XYZ
62.69291
- 297.8236
+ 222.6236
0 ]
/Rect [ 62.69291
- 464.5936
+ 452.5936
134.4029
- 476.5936 ]
+ 464.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -232,15 +231,15 @@ endobj 0
0 ]
/Contents ()
- /Dest [ 48 0 R
+ /Dest [ 52 0 R
/XYZ
62.69291
- 297.8236
+ 222.6236
0 ]
/Rect [ 527.0227
- 464.5936
+ 452.5936
532.5827
- 476.5936 ]
+ 464.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -250,15 +249,15 @@ endobj 0
0 ]
/Contents ()
- /Dest [ 61 0 R
+ /Dest [ 66 0 R
/XYZ
62.69291
- 681.0236
+ 588.7757
0 ]
/Rect [ 62.69291
- 446.5936
+ 434.5936
148.2829
- 458.5936 ]
+ 446.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -268,15 +267,15 @@ endobj 0
0 ]
/Contents ()
- /Dest [ 61 0 R
+ /Dest [ 66 0 R
/XYZ
62.69291
- 681.0236
+ 588.7757
0 ]
/Rect [ 527.0227
- 446.5936
+ 434.5936
532.5827
- 458.5936 ]
+ 446.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -286,15 +285,15 @@ endobj 0
0 ]
/Contents ()
- /Dest [ 65 0 R
+ /Dest [ 71 0 R
/XYZ
62.69291
- 765.0236
+ 671.8236
0 ]
/Rect [ 62.69291
- 428.5936
- 182.7129
- 440.5936 ]
+ 416.5936
+ 201.0529
+ 428.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
@@ -304,350 +303,419 @@ endobj 0
0 ]
/Contents ()
- /Dest [ 65 0 R
+ /Dest [ 71 0 R
/XYZ
62.69291
- 765.0236
+ 671.8236
0 ]
/Rect [ 527.0227
- 428.5936
+ 416.5936
532.5827
- 440.5936 ]
+ 428.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'F6': class PDFType1Font
-22 0 obj
-% Font Courier-Bold
-<< /BaseFont /Courier-Bold
- /Encoding /WinAnsiEncoding
- /Name /F6
- /Subtype /Type1
- /Type /Font >>
-endobj
% 'Annot.NUMBER16': class LinkAnnotation
-23 0 obj
+22 0 obj
<< /Border [ 0
0
0 ]
/Contents ()
- /Dest [ 85 0 R
+ /Dest [ 77 0 R
/XYZ
62.69291
- 765.0236
+ 562.2269
0 ]
/Rect [ 62.69291
- 410.5936
- 135.0429
- 422.5936 ]
+ 398.5936
+ 144.3729
+ 410.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Annot.NUMBER17': class LinkAnnotation
-24 0 obj
+23 0 obj
<< /Border [ 0
0
0 ]
/Contents ()
- /Dest [ 85 0 R
+ /Dest [ 77 0 R
/XYZ
62.69291
- 765.0236
+ 562.2269
0 ]
/Rect [ 527.0227
- 410.5936
+ 398.5936
532.5827
- 422.5936 ]
+ 410.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Annot.NUMBER18': class LinkAnnotation
-25 0 obj
+24 0 obj
<< /Border [ 0
0
0 ]
/Contents ()
- /Dest [ 85 0 R
+ /Dest [ 93 0 R
/XYZ
62.69291
- 181.6236
+ 715.8236
0 ]
/Rect [ 62.69291
- 392.5936
+ 380.5936
137.7129
- 404.5936 ]
+ 392.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Annot.NUMBER19': class LinkAnnotation
-26 0 obj
+25 0 obj
<< /Border [ 0
0
0 ]
/Contents ()
- /Dest [ 85 0 R
+ /Dest [ 93 0 R
/XYZ
62.69291
- 181.6236
+ 715.8236
0 ]
- /Rect [ 527.0227
- 392.5936
+ /Rect [ 521.4627
+ 380.5936
532.5827
- 404.5936 ]
+ 392.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Annot.NUMBER20': class LinkAnnotation
-27 0 obj
+26 0 obj
<< /Border [ 0
0
0 ]
/Contents ()
- /Dest [ 89 0 R
+ /Dest [ 96 0 R
/XYZ
62.69291
- 371.4236
+ 242.2236
0 ]
/Rect [ 62.69291
- 374.5936
+ 362.5936
193.8529
- 386.5936 ]
+ 374.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Annot.NUMBER21': class LinkAnnotation
-28 0 obj
+27 0 obj
<< /Border [ 0
0
0 ]
/Contents ()
- /Dest [ 89 0 R
+ /Dest [ 96 0 R
/XYZ
62.69291
- 371.4236
+ 242.2236
0 ]
/Rect [ 521.4627
- 374.5936
+ 362.5936
532.5827
- 386.5936 ]
+ 374.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Annot.NUMBER22': class LinkAnnotation
-29 0 obj
+28 0 obj
<< /Border [ 0
0
0 ]
/Contents ()
- /Dest [ 92 0 R
+ /Dest [ 99 0 R
/XYZ
62.69291
- 384.6236
+ 253.4236
0 ]
/Rect [ 62.69291
- 356.5936
+ 344.5936
201.6029
- 368.5936 ]
+ 356.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Annot.NUMBER23': class LinkAnnotation
-30 0 obj
+29 0 obj
<< /Border [ 0
0
0 ]
/Contents ()
- /Dest [ 92 0 R
+ /Dest [ 99 0 R
/XYZ
62.69291
- 384.6236
+ 253.4236
0 ]
/Rect [ 521.4627
- 356.5936
+ 344.5936
532.5827
- 368.5936 ]
+ 356.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Annot.NUMBER24': class LinkAnnotation
-31 0 obj
+30 0 obj
<< /Border [ 0
0
0 ]
/Contents ()
- /Dest [ 95 0 R
+ /Dest [ 104 0 R
/XYZ
62.69291
- 241.4236
+ 765.0236
0 ]
/Rect [ 62.69291
- 338.5936
- 108.2629
- 350.5936 ]
+ 326.5936
+ 182.7029
+ 338.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Annot.NUMBER25': class LinkAnnotation
-32 0 obj
+31 0 obj
<< /Border [ 0
0
0 ]
/Contents ()
- /Dest [ 95 0 R
+ /Dest [ 104 0 R
/XYZ
62.69291
- 241.4236
+ 765.0236
0 ]
/Rect [ 521.4627
- 338.5936
+ 326.5936
532.5827
- 350.5936 ]
+ 338.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Annot.NUMBER26': class LinkAnnotation
-33 0 obj
+32 0 obj
<< /Border [ 0
0
0 ]
/Contents ()
- /Dest [ 97 0 R
+ /Dest [ 104 0 R
/XYZ
62.69291
- 687.0236
+ 223.6236
0 ]
/Rect [ 62.69291
- 320.5936
- 241.6029
- 332.5936 ]
+ 308.5936
+ 163.2729
+ 320.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Annot.NUMBER27': class LinkAnnotation
-34 0 obj
+33 0 obj
<< /Border [ 0
0
0 ]
/Contents ()
- /Dest [ 97 0 R
+ /Dest [ 104 0 R
/XYZ
62.69291
- 687.0236
+ 223.6236
0 ]
/Rect [ 521.4627
- 320.5936
+ 308.5936
532.5827
- 332.5936 ]
+ 320.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER28': class PDFDictionary
+% 'Annot.NUMBER28': class LinkAnnotation
+34 0 obj
+<< /Border [ 0
+ 0
+ 0 ]
+ /Contents ()
+ /Dest [ 109 0 R
+ /XYZ
+ 62.69291
+ 419.8236
+ 0 ]
+ /Rect [ 62.69291
+ 290.5936
+ 266.0929
+ 302.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER29': class LinkAnnotation
35 0 obj
-<< /A << /S /URI
- /Type /Action
- /URI (http://pypi.python.org/pypi/plac) >>
- /Border [ 0
+<< /Border [ 0
0
0 ]
- /Rect [ 185.4471
- 266.5936
- 207.1062
- 278.5936 ]
+ /Contents ()
+ /Dest [ 109 0 R
+ /XYZ
+ 62.69291
+ 419.8236
+ 0 ]
+ /Rect [ 521.4627
+ 290.5936
+ 532.5827
+ 302.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER29': class PDFDictionary
+% 'Annot.NUMBER30': class LinkAnnotation
36 0 obj
-<< /A << /S /URI
- /Type /Action
- /URI (http://pypi.python.org/pypi/plac) >>
- /Border [ 0
+<< /Border [ 0
0
0 ]
- /Rect [ 177.6784
+ /Contents ()
+ /Dest [ 112 0 R
+ /XYZ
+ 62.69291
+ 621.0236
+ 0 ]
+ /Rect [ 62.69291
+ 272.5936
+ 108.2629
+ 284.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER31': class LinkAnnotation
+37 0 obj
+<< /Border [ 0
+ 0
+ 0 ]
+ /Contents ()
+ /Dest [ 112 0 R
+ /XYZ
+ 62.69291
+ 621.0236
+ 0 ]
+ /Rect [ 521.4627
+ 272.5936
+ 532.5827
+ 284.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER32': class LinkAnnotation
+38 0 obj
+<< /Border [ 0
+ 0
+ 0 ]
+ /Contents ()
+ /Dest [ 112 0 R
+ /XYZ
+ 62.69291
+ 354.0236
+ 0 ]
+ /Rect [ 62.69291
254.5936
- 199.6123
+ 241.6029
266.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER30': class PDFDictionary
-37 0 obj
+% 'Annot.NUMBER33': class LinkAnnotation
+39 0 obj
+<< /Border [ 0
+ 0
+ 0 ]
+ /Contents ()
+ /Dest [ 112 0 R
+ /XYZ
+ 62.69291
+ 354.0236
+ 0 ]
+ /Rect [ 521.4627
+ 254.5936
+ 532.5827
+ 266.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER34': class PDFDictionary
+40 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 62.69291
+ /Rect [ 185.4471
200.5936
- 83.81291
+ 207.1062
212.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER31': class PDFDictionary
-38 0 obj
+% 'Annot.NUMBER35': class PDFDictionary
+41 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 278.4678
- 182.5936
- 300.0328
- 194.5936 ]
+ /Rect [ 177.6784
+ 188.5936
+ 199.6123
+ 200.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER32': class PDFDictionary
-39 0 obj
+% 'Annot.NUMBER36': class PDFDictionary
+42 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 117.3573
- 170.5936
- 138.5845
- 182.5936 ]
+ /Rect [ 62.69291
+ 134.5936
+ 83.81291
+ 146.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER33': class PDFDictionary
-40 0 obj
+% 'Annot.NUMBER37': class PDFDictionary
+43 0 obj
<< /A << /S /URI
/Type /Action
- /URI (http://twill.idyll.org/) >>
+ /URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 83.20457
- 128.5936
- 105.3762
- 140.5936 ]
+ /Rect [ 278.4678
+ 116.5936
+ 300.0328
+ 128.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER34': class PDFDictionary
-41 0 obj
+% 'Annot.NUMBER38': class PDFDictionary
+44 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 128.6679
- 128.5936
- 147.0079
- 140.5936 ]
+ /Rect [ 117.3573
+ 104.5936
+ 138.5845
+ 116.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Page1': class PDFPage
-42 0 obj
+45 0 obj
% Page dictionary
<< /Annots [ 5 0 R
6 0 R
@@ -664,6 +732,7 @@ endobj 19 0 R
20 0 R
21 0 R
+ 22 0 R
23 0 R
24 0 R
25 0 R
@@ -682,13 +751,16 @@ endobj 38 0 R
39 0 R
40 0 R
- 41 0 R ]
- /Contents 115 0 R
+ 41 0 R
+ 42 0 R
+ 43 0 R
+ 44 0 R ]
+ /Contents 133 0 R
/MediaBox [ 0
0
595.2756
841.8898 ]
- /Parent 114 0 R
+ /Parent 132 0 R
/Resources << /Font 1 0 R
/ProcSet [ /PDF
/Text
@@ -699,8 +771,38 @@ endobj /Trans << >>
/Type /Page >>
endobj
-% 'Annot.NUMBER35': class PDFDictionary
-43 0 obj
+% 'Annot.NUMBER39': class PDFDictionary
+46 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://twill.idyll.org/) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 83.20457
+ 744.5936
+ 105.3762
+ 756.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER40': class PDFDictionary
+47 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://pypi.python.org/pypi/plac) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 128.6679
+ 744.5936
+ 147.0079
+ 756.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER41': class PDFDictionary
+48 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://micheles.googlecode.com/hg/plac/doc/plac.html) >>
@@ -708,22 +810,24 @@ endobj 0
0 ]
/Rect [ 62.69291
- 633.5936
+ 585.5936
157.1829
- 645.5936 ]
+ 597.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Page2': class PDFPage
-44 0 obj
+49 0 obj
% Page dictionary
-<< /Annots [ 43 0 R ]
- /Contents 116 0 R
+<< /Annots [ 46 0 R
+ 47 0 R
+ 48 0 R ]
+ /Contents 134 0 R
/MediaBox [ 0
0
595.2756
841.8898 ]
- /Parent 114 0 R
+ /Parent 132 0 R
/Resources << /Font 1 0 R
/ProcSet [ /PDF
/Text
@@ -734,47 +838,31 @@ endobj /Trans << >>
/Type /Page >>
endobj
-% 'Annot.NUMBER36': class PDFDictionary
-45 0 obj
-<< /A << /S /URI
- /Type /Action
- /URI (http://freshmeat.net/projects/rlwrap/) >>
- /Border [ 0
- 0
- 0 ]
- /Rect [ 223.0988
- 711.3936
- 255.7481
- 723.3936 ]
- /Subtype /Link
- /Type /Annot >>
-endobj
-% 'Annot.NUMBER37': class PDFDictionary
-46 0 obj
+% 'Annot.NUMBER42': class PDFDictionary
+50 0 obj
<< /A << /S /URI
/Type /Action
- /URI (http://freshmeat.net/projects/rlwrap/) >>
+ /URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 390.8554
- 711.3936
- 423.5047
- 723.3936 ]
+ /Rect [ 383.9329
+ 304.9936
+ 405.0529
+ 316.9936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Page3': class PDFPage
-47 0 obj
+51 0 obj
% Page dictionary
-<< /Annots [ 45 0 R
- 46 0 R ]
- /Contents 117 0 R
+<< /Annots [ 50 0 R ]
+ /Contents 135 0 R
/MediaBox [ 0
0
595.2756
841.8898 ]
- /Parent 114 0 R
+ /Parent 132 0 R
/Resources << /Font 1 0 R
/ProcSet [ /PDF
/Text
@@ -786,14 +874,14 @@ endobj /Type /Page >>
endobj
% 'Page4': class PDFPage
-48 0 obj
+52 0 obj
% Page dictionary
-<< /Contents 118 0 R
+<< /Contents 136 0 R
/MediaBox [ 0
0
595.2756
841.8898 ]
- /Parent 114 0 R
+ /Parent 132 0 R
/Resources << /Font 1 0 R
/ProcSet [ /PDF
/Text
@@ -804,8 +892,8 @@ endobj /Trans << >>
/Type /Page >>
endobj
-% 'Annot.NUMBER38': class PDFDictionary
-49 0 obj
+% 'Annot.NUMBER43': class PDFDictionary
+53 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
@@ -813,14 +901,14 @@ endobj 0
0 ]
/Rect [ 370.6785
- 651.3936
+ 579.3936
392.4956
- 663.3936 ]
+ 591.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER39': class PDFDictionary
-50 0 obj
+% 'Annot.NUMBER44': class PDFDictionary
+54 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
@@ -828,14 +916,14 @@ endobj 0
0 ]
/Rect [ 455.8742
- 651.3936
+ 579.3936
477.6913
- 663.3936 ]
+ 591.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER40': class PDFDictionary
-51 0 obj
+% 'Annot.NUMBER45': class PDFDictionary
+55 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
@@ -843,14 +931,14 @@ endobj 0
0 ]
/Rect [ 185.9351
- 454.6093
+ 364.6093
207.4695
- 466.6093 ]
+ 376.6093 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER41': class PDFDictionary
-52 0 obj
+% 'Annot.NUMBER46': class PDFDictionary
+56 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://docs.python.org/library/shlex.html) >>
@@ -858,14 +946,14 @@ endobj 0
0 ]
/Rect [ 369.8905
- 454.6093
+ 364.6093
396.425
- 466.6093 ]
+ 376.6093 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER42': class PDFDictionary
-53 0 obj
+% 'Annot.NUMBER47': class PDFDictionary
+57 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
@@ -873,14 +961,14 @@ endobj 0
0 ]
/Rect [ 408.8916
- 442.6093
+ 352.6093
427.2316
- 454.6093 ]
+ 364.6093 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER43': class PDFDictionary
-54 0 obj
+% 'Annot.NUMBER48': class PDFDictionary
+58 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://docs.python.org/library/shlex.html) >>
@@ -888,75 +976,91 @@ endobj 0
0 ]
/Rect [ 62.69291
- 430.6093
+ 340.6093
86.03291
- 442.6093 ]
+ 352.6093 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER44': class PDFDictionary
-55 0 obj
+% 'Annot.NUMBER49': class PDFDictionary
+59 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 91.65423
- 430.6093
- 112.8355
- 442.6093 ]
+ /Rect [ 92.4689
+ 340.6093
+ 114.4649
+ 352.6093 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER45': class PDFDictionary
-56 0 obj
+% 'Annot.NUMBER50': class PDFDictionary
+60 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://docs.python.org/library/shlex.html) >>
/Border [ 0
0
0 ]
- /Rect [ 223.1366
- 418.6093
- 250.7972
- 430.6093 ]
+ /Rect [ 143.2929
+ 328.6093
+ 169.4129
+ 340.6093 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER46': class PDFDictionary
-57 0 obj
+% 'Annot.NUMBER51': class PDFDictionary
+61 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 201.0929
- 322.6093
- 222.2129
- 334.6093 ]
+ /Rect [ 129.6923
+ 256.6093
+ 151.4655
+ 268.6093 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER52': class PDFDictionary
+62 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://pypi.python.org/pypi/plac) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 173.8529
+ 244.6093
+ 194.9729
+ 256.6093 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Page5': class PDFPage
-58 0 obj
+63 0 obj
% Page dictionary
-<< /Annots [ 49 0 R
- 50 0 R
- 51 0 R
- 52 0 R
- 53 0 R
+<< /Annots [ 53 0 R
54 0 R
55 0 R
56 0 R
- 57 0 R ]
- /Contents 119 0 R
+ 57 0 R
+ 58 0 R
+ 59 0 R
+ 60 0 R
+ 61 0 R
+ 62 0 R ]
+ /Contents 137 0 R
/MediaBox [ 0
0
595.2756
841.8898 ]
- /Parent 114 0 R
+ /Parent 132 0 R
/Resources << /Font 1 0 R
/ProcSet [ /PDF
/Text
@@ -967,8 +1071,8 @@ endobj /Trans << >>
/Type /Page >>
endobj
-% 'Annot.NUMBER47': class PDFDictionary
-59 0 obj
+% 'Annot.NUMBER53': class PDFDictionary
+64 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
@@ -976,14 +1080,14 @@ endobj 0
0 ]
/Rect [ 460.388
- 633.5936
+ 541.3457
482.0127
- 645.5936 ]
+ 553.3457 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER48': class PDFDictionary
-60 0 obj
+% 'Annot.NUMBER54': class PDFDictionary
+65 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
@@ -991,23 +1095,23 @@ endobj 0
0 ]
/Rect [ 95.32996
- 567.5936
+ 475.3457
116.857
- 579.5936 ]
+ 487.3457 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Page6': class PDFPage
-61 0 obj
+66 0 obj
% Page dictionary
-<< /Annots [ 59 0 R
- 60 0 R ]
- /Contents 120 0 R
+<< /Annots [ 64 0 R
+ 65 0 R ]
+ /Contents 138 0 R
/MediaBox [ 0
0
595.2756
841.8898 ]
- /Parent 114 0 R
+ /Parent 132 0 R
/Resources << /Font 1 0 R
/ProcSet [ /PDF
/Text
@@ -1018,8 +1122,8 @@ endobj /Trans << >>
/Type /Page >>
endobj
-% 'Annot.NUMBER49': class PDFDictionary
-62 0 obj
+% 'Annot.NUMBER55': class PDFDictionary
+67 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://micheles.googlecode.com/hg/plac/doc/plac.html) >>
@@ -1027,29 +1131,29 @@ endobj 0
0 ]
/Rect [ 316.3528
- 729.5936
+ 636.3936
409.2453
- 741.5936 ]
+ 648.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER50': class PDFDictionary
-63 0 obj
+% 'Annot.NUMBER56': class PDFDictionary
+68 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 441.0467
- 717.5936
- 463.6507
- 729.5936 ]
+ /Rect [ 419.1694
+ 624.3936
+ 440.4061
+ 636.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER51': class PDFDictionary
-64 0 obj
+% 'Annot.NUMBER57': class PDFDictionary
+69 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
@@ -1057,24 +1161,40 @@ endobj 0
0 ]
/Rect [ 62.69291
- 687.5936
+ 594.3936
84.70395
- 699.5936 ]
+ 606.3936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER58': class PDFDictionary
+70 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://pypi.python.org/pypi/plac) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 293.2359
+ 516.3936
+ 316.4697
+ 528.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Page7': class PDFPage
-65 0 obj
+71 0 obj
% Page dictionary
-<< /Annots [ 62 0 R
- 63 0 R
- 64 0 R ]
- /Contents 121 0 R
+<< /Annots [ 67 0 R
+ 68 0 R
+ 69 0 R
+ 70 0 R ]
+ /Contents 139 0 R
/MediaBox [ 0
0
595.2756
841.8898 ]
- /Parent 114 0 R
+ /Parent 132 0 R
/Resources << /Font 1 0 R
/ProcSet [ /PDF
/Text
@@ -1085,8 +1205,8 @@ endobj /Trans << >>
/Type /Page >>
endobj
-% 'Annot.NUMBER52': class PDFDictionary
-66 0 obj
+% 'Annot.NUMBER59': class PDFDictionary
+72 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
@@ -1094,54 +1214,54 @@ endobj 0
0 ]
/Rect [ 431.7904
- 639.3936
+ 531.3936
453.7245
- 651.3936 ]
+ 543.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER53': class PDFDictionary
-67 0 obj
+% 'Annot.NUMBER60': class PDFDictionary
+73 0 obj
<< /A << /S /URI
/Type /Action
- /URI (http://freshmeat.net/projects/rlwrap/) >>
+ /URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 465.1151
- 226.1936
- 495.7839
- 238.1936 ]
+ /Rect [ 255.5885
+ 501.3936
+ 278.2757
+ 513.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER54': class PDFDictionary
-68 0 obj
+% 'Annot.NUMBER61': class PDFDictionary
+74 0 obj
<< /A << /S /URI
/Type /Action
- /URI (http://pypi.python.org/pypi/plac) >>
+ /URI (http://docs.python.org/library/cmd.html) >>
/Border [ 0
0
0 ]
- /Rect [ 206.6529
- 184.1936
- 227.7729
- 196.1936 ]
+ /Rect [ 513.6927
+ 477.3936
+ 532.1846
+ 489.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
% 'Page8': class PDFPage
-69 0 obj
+75 0 obj
% Page dictionary
-<< /Annots [ 66 0 R
- 67 0 R
- 68 0 R ]
- /Contents 122 0 R
+<< /Annots [ 72 0 R
+ 73 0 R
+ 74 0 R ]
+ /Contents 140 0 R
/MediaBox [ 0
0
595.2756
841.8898 ]
- /Parent 114 0 R
+ /Parent 132 0 R
/Resources << /Font 1 0 R
/ProcSet [ /PDF
/Text
@@ -1152,203 +1272,254 @@ endobj /Trans << >>
/Type /Page >>
endobj
-% 'Annot.NUMBER55': class PDFDictionary
-70 0 obj
+% 'Annot.NUMBER62': class PDFDictionary
+76 0 obj
<< /A << /S /URI
/Type /Action
- /URI (http://docs.python.org/library/cmd.html) >>
+ /URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 163.0507
- 729.5936
- 185.1123
- 741.5936 ]
+ /Rect [ 179.0529
+ 526.7969
+ 201.1953
+ 538.7969 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER56': class PDFDictionary
-71 0 obj
+% 'Page9': class PDFPage
+77 0 obj
+% Page dictionary
+<< /Annots [ 76 0 R ]
+ /Contents 141 0 R
+ /MediaBox [ 0
+ 0
+ 595.2756
+ 841.8898 ]
+ /Parent 132 0 R
+ /Resources << /Font 1 0 R
+ /ProcSet [ /PDF
+ /Text
+ /ImageB
+ /ImageC
+ /ImageI ] >>
+ /Rotate 0
+ /Trans << >>
+ /Type /Page >>
+endobj
+% 'Annot.NUMBER63': class PDFDictionary
+78 0 obj
<< /A << /S /URI
/Type /Action
- /URI (http://pypi.python.org/pypi/plac) >>
+ /URI (http://freshmeat.net/projects/rlwrap/) >>
/Border [ 0
0
0 ]
- /Rect [ 514.6827
- 717.5936
- 533.0227
- 729.5936 ]
+ /Rect [ 377.8504
+ 621.3936
+ 409.861
+ 633.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER57': class PDFDictionary
-72 0 obj
+% 'Annot.NUMBER64': class PDFDictionary
+79 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 127.0179
- 705.5936
- 148.2762
- 717.5936 ]
+ /Rect [ 242.4466
+ 609.3936
+ 263.7922
+ 621.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER58': class PDFDictionary
-73 0 obj
+% 'Annot.NUMBER65': class PDFDictionary
+80 0 obj
<< /A << /S /URI
/Type /Action
- /URI (http://docs.python.org/library/cmd.html) >>
+ /URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 197.4628
- 705.5936
- 219.2712
- 717.5936 ]
+ /Rect [ 62.69291
+ 585.3936
+ 85.67624
+ 597.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER59': class PDFDictionary
-74 0 obj
+% 'Annot.NUMBER66': class PDFDictionary
+81 0 obj
<< /A << /S /URI
/Type /Action
- /URI (http://docs.python.org/library/cmd.html) >>
+ /URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 62.69291
- 693.5936
- 81.58291
- 705.5936 ]
+ /Rect [ 211.9062
+ 573.3936
+ 236.7428
+ 585.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER60': class PDFDictionary
-75 0 obj
+% 'Annot.NUMBER67': class PDFDictionary
+82 0 obj
<< /A << /S /URI
/Type /Action
- /URI (http://pypi.python.org/pypi/plac) >>
+ /URI (http://docs.python.org/library/cmd.html) >>
/Border [ 0
0
0 ]
- /Rect [ 158.1049
- 693.5936
- 180.2789
- 705.5936 ]
+ /Rect [ 366.9454
+ 543.3936
+ 388.8033
+ 555.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER61': class PDFDictionary
-76 0 obj
+% 'Annot.NUMBER68': class PDFDictionary
+83 0 obj
<< /A << /S /URI
/Type /Action
- /URI (http://freshmeat.net/projects/rlwrap/) >>
+ /URI (http://docs.python.org/library/cmd.html) >>
/Border [ 0
0
0 ]
- /Rect [ 483.7387
- 693.5936
- 510.9587
- 705.5936 ]
+ /Rect [ 170.8855
+ 531.3936
+ 189.7755
+ 543.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER62': class PDFDictionary
-77 0 obj
+% 'Annot.NUMBER69': class PDFDictionary
+84 0 obj
<< /A << /S /URI
/Type /Action
- /URI (http://pypi.python.org/pypi/plac) >>
+ /URI (http://argparse.googlecode.com) >>
/Border [ 0
0
0 ]
- /Rect [ 244.5366
- 681.5936
- 266.7822
- 693.5936 ]
+ /Rect [ 390.8027
+ 190.1936
+ 435.0027
+ 202.1936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER63': class PDFDictionary
-78 0 obj
+% 'Annot.NUMBER70': class PDFDictionary
+85 0 obj
<< /A << /S /URI
/Type /Action
- /URI (http://docs.python.org/library/cmd.html) >>
+ /URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 287.3678
- 681.5936
- 306.2578
- 693.5936 ]
+ /Rect [ 213.451
+ 178.1936
+ 237.5255
+ 190.1936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER64': class PDFDictionary
-79 0 obj
+% 'Annot.NUMBER71': class PDFDictionary
+86 0 obj
<< /A << /S /URI
/Type /Action
- /URI (http://docs.python.org/library/cmd.html) >>
+ /URI (http://argparse.googlecode.com) >>
/Border [ 0
0
0 ]
- /Rect [ 452.6357
- 669.5936
- 474.6447
- 681.5936 ]
+ /Rect [ 114.9429
+ 154.1936
+ 157.1829
+ 166.1936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER65': class PDFDictionary
-80 0 obj
+% 'Annot.NUMBER72': class PDFDictionary
+87 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 494.4437
- 669.5936
- 512.7837
- 681.5936 ]
+ /Rect [ 385.0429
+ 154.1936
+ 403.3829
+ 166.1936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER66': class PDFDictionary
-81 0 obj
+% 'Annot.NUMBER73': class PDFDictionary
+88 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 193.5645
- 209.1936
- 215.6228
- 221.1936 ]
+ /Rect [ 514.2427
+ 124.1936
+ 532.3264
+ 136.1936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER67': class PDFDictionary
-82 0 obj
+% 'Annot.NUMBER74': class PDFDictionary
+89 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 114.3829
- 197.1936
- 132.7229
- 209.1936 ]
+ /Rect [ 256.6729
+ 94.19362
+ 277.7929
+ 106.1936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER68': class PDFDictionary
-83 0 obj
+% 'Page10': class PDFPage
+90 0 obj
+% Page dictionary
+<< /Annots [ 78 0 R
+ 79 0 R
+ 80 0 R
+ 81 0 R
+ 82 0 R
+ 83 0 R
+ 84 0 R
+ 85 0 R
+ 86 0 R
+ 87 0 R
+ 88 0 R
+ 89 0 R ]
+ /Contents 142 0 R
+ /MediaBox [ 0
+ 0
+ 595.2756
+ 841.8898 ]
+ /Parent 132 0 R
+ /Resources << /Font 1 0 R
+ /ProcSet [ /PDF
+ /Text
+ /ImageB
+ /ImageC
+ /ImageI ] >>
+ /Rotate 0
+ /Trans << >>
+ /Type /Page >>
+endobj
+% 'Annot.NUMBER75': class PDFDictionary
+91 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
@@ -1356,14 +1527,14 @@ endobj 0
0 ]
/Rect [ 149.5469
- 146.1936
+ 680.3936
172.1982
- 158.1936 ]
+ 692.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER69': class PDFDictionary
-84 0 obj
+% 'Annot.NUMBER76': class PDFDictionary
+92 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://docs.python.org/distutils/) >>
@@ -1371,36 +1542,23 @@ endobj 0
0 ]
/Rect [ 224.3178
- 134.1936
+ 668.3936
260.8853
- 146.1936 ]
+ 680.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Page9': class PDFPage
-85 0 obj
+% 'Page11': class PDFPage
+93 0 obj
% Page dictionary
-<< /Annots [ 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
- 81 0 R
- 82 0 R
- 83 0 R
- 84 0 R ]
- /Contents 123 0 R
+<< /Annots [ 91 0 R
+ 92 0 R ]
+ /Contents 143 0 R
/MediaBox [ 0
0
595.2756
841.8898 ]
- /Parent 114 0 R
+ /Parent 132 0 R
/Resources << /Font 1 0 R
/ProcSet [ /PDF
/Text
@@ -1411,15 +1569,47 @@ endobj /Trans << >>
/Type /Page >>
endobj
-% 'Page10': class PDFPage
-86 0 obj
+% 'Annot.NUMBER77': class PDFDictionary
+94 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://argparse.googlecode.com) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 381.1529
+ 615.3936
+ 423.3929
+ 627.3936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER78': class PDFDictionary
+95 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://pypi.python.org/pypi/plac) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 62.69291
+ 206.7936
+ 84.72012
+ 218.7936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Page12': class PDFPage
+96 0 obj
% Page dictionary
-<< /Contents 124 0 R
+<< /Annots [ 94 0 R
+ 95 0 R ]
+ /Contents 144 0 R
/MediaBox [ 0
0
595.2756
841.8898 ]
- /Parent 114 0 R
+ /Parent 132 0 R
/Resources << /Font 1 0 R
/ProcSet [ /PDF
/Text
@@ -1430,47 +1620,85 @@ endobj /Trans << >>
/Type /Page >>
endobj
-% 'Annot.NUMBER70': class PDFDictionary
-87 0 obj
+% 'Page13': class PDFPage
+97 0 obj
+% Page dictionary
+<< /Contents 145 0 R
+ /MediaBox [ 0
+ 0
+ 595.2756
+ 841.8898 ]
+ /Parent 132 0 R
+ /Resources << /Font 1 0 R
+ /ProcSet [ /PDF
+ /Text
+ /ImageB
+ /ImageC
+ /ImageI ] >>
+ /Rotate 0
+ /Trans << >>
+ /Type /Page >>
+endobj
+% 'Annot.NUMBER79': class PDFDictionary
+98 0 obj
<< /A << /S /URI
/Type /Action
- /URI (http://argparse.googlecode.com) >>
+ /URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 381.1529
- 744.5936
- 423.3929
- 756.5936 ]
+ /Rect [ 182.479
+ 217.9936
+ 203.7662
+ 229.9936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER71': class PDFDictionary
-88 0 obj
+% 'Page14': class PDFPage
+99 0 obj
+% Page dictionary
+<< /Annots [ 98 0 R ]
+ /Contents 146 0 R
+ /MediaBox [ 0
+ 0
+ 595.2756
+ 841.8898 ]
+ /Parent 132 0 R
+ /Resources << /Font 1 0 R
+ /ProcSet [ /PDF
+ /Text
+ /ImageB
+ /ImageC
+ /ImageI ] >>
+ /Rotate 0
+ /Trans << >>
+ /Type /Page >>
+endobj
+% 'Annot.NUMBER80': class PDFDictionary
+100 0 obj
<< /A << /S /URI
/Type /Action
- /URI (http://pypi.python.org/pypi/plac) >>
+ /URI (http://docs.python.org/library/multiprocessing.html) >>
/Border [ 0
0
0 ]
- /Rect [ 62.69291
- 335.9936
- 84.72012
- 347.9936 ]
+ /Rect [ 295.0229
+ 134.9936
+ 367.2629
+ 146.9936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Page11': class PDFPage
-89 0 obj
+% 'Page15': class PDFPage
+101 0 obj
% Page dictionary
-<< /Annots [ 87 0 R
- 88 0 R ]
- /Contents 125 0 R
+<< /Annots [ 100 0 R ]
+ /Contents 147 0 R
/MediaBox [ 0
0
595.2756
841.8898 ]
- /Parent 114 0 R
+ /Parent 132 0 R
/Resources << /Font 1 0 R
/ProcSet [ /PDF
/Text
@@ -1481,15 +1709,47 @@ endobj /Trans << >>
/Type /Page >>
endobj
-% 'Page12': class PDFPage
-90 0 obj
+% 'Annot.NUMBER81': class PDFDictionary
+102 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://pypi.python.org/pypi/plac) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 179.1295
+ 729.5936
+ 201.6839
+ 741.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER82': class PDFDictionary
+103 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://pypi.python.org/pypi/plac) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 414.8874
+ 251.1936
+ 436.9487
+ 263.1936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Page16': class PDFPage
+104 0 obj
% Page dictionary
-<< /Contents 126 0 R
+<< /Annots [ 102 0 R
+ 103 0 R ]
+ /Contents 148 0 R
/MediaBox [ 0
0
595.2756
841.8898 ]
- /Parent 114 0 R
+ /Parent 132 0 R
/Resources << /Font 1 0 R
/ProcSet [ /PDF
/Text
@@ -1500,31 +1760,31 @@ endobj /Trans << >>
/Type /Page >>
endobj
-% 'Annot.NUMBER72': class PDFDictionary
-91 0 obj
+% 'Annot.NUMBER83': class PDFDictionary
+105 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 182.479
- 349.1936
- 203.7662
- 361.1936 ]
+ /Rect [ 122.7054
+ 756.5936
+ 145.2085
+ 768.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Page13': class PDFPage
-92 0 obj
+% 'Page17': class PDFPage
+106 0 obj
% Page dictionary
-<< /Annots [ 91 0 R ]
- /Contents 127 0 R
+<< /Annots [ 105 0 R ]
+ /Contents 149 0 R
/MediaBox [ 0
0
595.2756
841.8898 ]
- /Parent 114 0 R
+ /Parent 132 0 R
/Resources << /Font 1 0 R
/ProcSet [ /PDF
/Text
@@ -1535,47 +1795,47 @@ endobj /Trans << >>
/Type /Page >>
endobj
-% 'Annot.NUMBER73': class PDFDictionary
-93 0 obj
+% 'Annot.NUMBER84': class PDFDictionary
+107 0 obj
<< /A << /S /URI
/Type /Action
- /URI (http://docs.python.org/library/multiprocessing.html) >>
+ /URI (http://pypi.python.org/pypi/plac) >>
/Border [ 0
0
0 ]
- /Rect [ 295.0229
- 256.9936
- 367.2629
- 268.9936 ]
+ /Rect [ 183.3657
+ 372.3936
+ 207.8364
+ 384.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Annot.NUMBER74': class PDFDictionary
-94 0 obj
+% 'Annot.NUMBER85': class PDFDictionary
+108 0 obj
<< /A << /S /URI
/Type /Action
- /URI (http://pypi.python.org/pypi/plac) >>
+ /URI (http://docs.python.org/library/multiprocessing.html) >>
/Border [ 0
0
0 ]
- /Rect [ 91.57623
- 205.9936
- 114.8995
- 217.9936 ]
+ /Rect [ 254.9929
+ 348.3936
+ 327.2329
+ 360.3936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Page14': class PDFPage
-95 0 obj
+% 'Page18': class PDFPage
+109 0 obj
% Page dictionary
-<< /Annots [ 93 0 R
- 94 0 R ]
- /Contents 128 0 R
+<< /Annots [ 107 0 R
+ 108 0 R ]
+ /Contents 150 0 R
/MediaBox [ 0
0
595.2756
841.8898 ]
- /Parent 114 0 R
+ /Parent 132 0 R
/Resources << /Font 1 0 R
/ProcSet [ /PDF
/Text
@@ -1586,8 +1846,23 @@ endobj /Trans << >>
/Type /Page >>
endobj
-% 'Annot.NUMBER75': class PDFDictionary
-96 0 obj
+% 'Annot.NUMBER86': class PDFDictionary
+110 0 obj
+<< /A << /S /URI
+ /Type /Action
+ /URI (http://pypi.python.org/pypi/plac) >>
+ /Border [ 0
+ 0
+ 0 ]
+ /Rect [ 91.57623
+ 585.5936
+ 114.8995
+ 597.5936 ]
+ /Subtype /Link
+ /Type /Annot >>
+endobj
+% 'Annot.NUMBER87': class PDFDictionary
+111 0 obj
<< /A << /S /URI
/Type /Action
/URI (http://pypi.python.org/pypi/plac) >>
@@ -1595,22 +1870,23 @@ endobj 0
0 ]
/Rect [ 106.6216
- 651.5936
+ 318.5936
128.3202
- 663.5936 ]
+ 330.5936 ]
/Subtype /Link
/Type /Annot >>
endobj
-% 'Page15': class PDFPage
-97 0 obj
+% 'Page19': class PDFPage
+112 0 obj
% Page dictionary
-<< /Annots [ 96 0 R ]
- /Contents 129 0 R
+<< /Annots [ 110 0 R
+ 111 0 R ]
+ /Contents 151 0 R
/MediaBox [ 0
0
595.2756
841.8898 ]
- /Parent 114 0 R
+ /Parent 132 0 R
/Resources << /Font 1 0 R
/ProcSet [ /PDF
/Text
@@ -1621,15 +1897,15 @@ endobj /Trans << >>
/Type /Page >>
endobj
-% 'Page16': class PDFPage
-98 0 obj
+% 'Page20': class PDFPage
+113 0 obj
% Page dictionary
-<< /Contents 130 0 R
+<< /Contents 152 0 R
/MediaBox [ 0
0
595.2756
841.8898 ]
- /Parent 114 0 R
+ /Parent 132 0 R
/Resources << /Font 1 0 R
/ProcSet [ /PDF
/Text
@@ -1640,205 +1916,245 @@ endobj /Trans << >>
/Type /Page >>
endobj
-% 'R99': class PDFCatalog
-99 0 obj
+% 'R114': class PDFCatalog
+114 0 obj
% Document Root
-<< /Outlines 101 0 R
- /PageLabels 131 0 R
+<< /Outlines 116 0 R
+ /PageLabels 153 0 R
/PageMode /UseNone
- /Pages 114 0 R
+ /Pages 132 0 R
/Type /Catalog >>
endobj
-% 'R100': class PDFInfo
-100 0 obj
+% 'R115': class PDFInfo
+115 0 obj
<< /Author (Michele Simionato)
- /CreationDate (D:20100621062523-01'00')
+ /CreationDate (D:20100710083035-01'00')
/Keywords ()
/Producer (ReportLab http://www.reportlab.com)
/Subject (\(unspecified\))
- /Title (Testing and scripting your applications with plac) >>
+ /Title (Advanced usages of plac) >>
endobj
-% 'R101': class PDFOutlines
-101 0 obj
-<< /Count 12
- /First 102 0 R
- /Last 113 0 R
+% 'R116': class PDFOutlines
+116 0 obj
+<< /Count 15
+ /First 117 0 R
+ /Last 131 0 R
/Type /Outlines >>
endobj
% 'Outline.0': class OutlineEntryObject
-102 0 obj
-<< /Dest [ 42 0 R
+117 0 obj
+<< /Dest [ 45 0 R
/XYZ
62.69291
- 302.0236
+ 236.0236
0 ]
- /Next 103 0 R
- /Parent 101 0 R
+ /Next 118 0 R
+ /Parent 116 0 R
/Title (Introduction) >>
endobj
% 'Outline.1': class OutlineEntryObject
-103 0 obj
-<< /Dest [ 44 0 R
+118 0 obj
+<< /Dest [ 49 0 R
/XYZ
62.69291
- 765.0236
+ 717.0236
0 ]
- /Next 104 0 R
- /Parent 101 0 R
- /Prev 102 0 R
+ /Next 119 0 R
+ /Parent 116 0 R
+ /Prev 117 0 R
/Title (From scripts to interactive applications) >>
endobj
% 'Outline.2': class OutlineEntryObject
-104 0 obj
-<< /Dest [ 47 0 R
+119 0 obj
+<< /Dest [ 51 0 R
/XYZ
62.69291
- 306.6236
+ 259.4236
0 ]
- /Next 105 0 R
- /Parent 101 0 R
- /Prev 103 0 R
+ /Next 120 0 R
+ /Parent 116 0 R
+ /Prev 118 0 R
/Title (Testing a plac application) >>
endobj
% 'Outline.3': class OutlineEntryObject
-105 0 obj
-<< /Dest [ 48 0 R
+120 0 obj
+<< /Dest [ 52 0 R
/XYZ
62.69291
- 297.8236
+ 222.6236
0 ]
- /Next 106 0 R
- /Parent 101 0 R
- /Prev 104 0 R
+ /Next 121 0 R
+ /Parent 116 0 R
+ /Prev 119 0 R
/Title (Plac easy tests) >>
endobj
% 'Outline.4': class OutlineEntryObject
-106 0 obj
-<< /Dest [ 61 0 R
+121 0 obj
+<< /Dest [ 66 0 R
/XYZ
62.69291
- 681.0236
+ 588.7757
0 ]
- /Next 107 0 R
- /Parent 101 0 R
- /Prev 105 0 R
+ /Next 122 0 R
+ /Parent 116 0 R
+ /Prev 120 0 R
/Title (Plac batch scripts) >>
endobj
% 'Outline.5': class OutlineEntryObject
-107 0 obj
-<< /Dest [ 65 0 R
+122 0 obj
+<< /Dest [ 71 0 R
/XYZ
62.69291
- 765.0236
+ 671.8236
0 ]
- /Next 108 0 R
- /Parent 101 0 R
- /Prev 106 0 R
- /Title (Containers of commands) >>
+ /Next 123 0 R
+ /Parent 116 0 R
+ /Prev 121 0 R
+ /Title (Implementing subcommands) >>
endobj
% 'Outline.6': class OutlineEntryObject
-108 0 obj
-<< /Dest [ 85 0 R
+123 0 obj
+<< /Dest [ 77 0 R
/XYZ
62.69291
- 765.0236
+ 562.2269
0 ]
- /Next 109 0 R
- /Parent 101 0 R
- /Prev 107 0 R
- /Title (For cmd lovers) >>
+ /Next 124 0 R
+ /Parent 116 0 R
+ /Prev 122 0 R
+ /Title (Readline support) >>
endobj
% 'Outline.7': class OutlineEntryObject
-109 0 obj
-<< /Dest [ 85 0 R
+124 0 obj
+<< /Dest [ 93 0 R
/XYZ
62.69291
- 181.6236
+ 715.8236
0 ]
- /Next 110 0 R
- /Parent 101 0 R
- /Prev 108 0 R
+ /Next 125 0 R
+ /Parent 116 0 R
+ /Prev 123 0 R
/Title (The plac runner) >>
endobj
% 'Outline.8': class OutlineEntryObject
-110 0 obj
-<< /Dest [ 89 0 R
+125 0 obj
+<< /Dest [ 96 0 R
/XYZ
62.69291
- 371.4236
+ 242.2236
0 ]
- /Next 111 0 R
- /Parent 101 0 R
- /Prev 109 0 R
+ /Next 126 0 R
+ /Parent 116 0 R
+ /Prev 124 0 R
/Title (A non class-based example) >>
endobj
% 'Outline.9': class OutlineEntryObject
-111 0 obj
-<< /Dest [ 92 0 R
+126 0 obj
+<< /Dest [ 99 0 R
/XYZ
62.69291
- 384.6236
+ 253.4236
0 ]
- /Next 112 0 R
- /Parent 101 0 R
- /Prev 110 0 R
+ /Next 127 0 R
+ /Parent 116 0 R
+ /Prev 125 0 R
/Title (Writing your own plac runner) >>
endobj
% 'Outline.10': class OutlineEntryObject
-112 0 obj
-<< /Dest [ 95 0 R
+127 0 obj
+<< /Dest [ 104 0 R
/XYZ
62.69291
- 241.4236
+ 765.0236
0 ]
- /Next 113 0 R
- /Parent 101 0 R
- /Prev 111 0 R
- /Title (Summary) >>
+ /Next 128 0 R
+ /Parent 116 0 R
+ /Prev 126 0 R
+ /Title (Long running commands) >>
endobj
% 'Outline.11': class OutlineEntryObject
-113 0 obj
-<< /Dest [ 97 0 R
+128 0 obj
+<< /Dest [ 104 0 R
/XYZ
62.69291
- 687.0236
+ 223.6236
0 ]
- /Parent 101 0 R
- /Prev 112 0 R
+ /Next 129 0 R
+ /Parent 116 0 R
+ /Prev 127 0 R
+ /Title (Threaded commands) >>
+endobj
+% 'Outline.12': class OutlineEntryObject
+129 0 obj
+<< /Dest [ 109 0 R
+ /XYZ
+ 62.69291
+ 419.8236
+ 0 ]
+ /Next 130 0 R
+ /Parent 116 0 R
+ /Prev 128 0 R
+ /Title (Running commands as external processes) >>
+endobj
+% 'Outline.13': class OutlineEntryObject
+130 0 obj
+<< /Dest [ 112 0 R
+ /XYZ
+ 62.69291
+ 621.0236
+ 0 ]
+ /Next 131 0 R
+ /Parent 116 0 R
+ /Prev 129 0 R
+ /Title (Summary) >>
+endobj
+% 'Outline.14': class OutlineEntryObject
+131 0 obj
+<< /Dest [ 112 0 R
+ /XYZ
+ 62.69291
+ 354.0236
+ 0 ]
+ /Parent 116 0 R
+ /Prev 130 0 R
/Title (Appendix: custom annotation objects) >>
endobj
-% 'R114': class PDFPages
-114 0 obj
+% 'R132': class PDFPages
+132 0 obj
% page tree
-<< /Count 16
- /Kids [ 42 0 R
- 44 0 R
- 47 0 R
- 48 0 R
- 58 0 R
- 61 0 R
- 65 0 R
- 69 0 R
- 85 0 R
- 86 0 R
- 89 0 R
+<< /Count 20
+ /Kids [ 45 0 R
+ 49 0 R
+ 51 0 R
+ 52 0 R
+ 63 0 R
+ 66 0 R
+ 71 0 R
+ 75 0 R
+ 77 0 R
90 0 R
- 92 0 R
- 95 0 R
+ 93 0 R
+ 96 0 R
97 0 R
- 98 0 R ]
+ 99 0 R
+ 101 0 R
+ 104 0 R
+ 106 0 R
+ 109 0 R
+ 112 0 R
+ 113 0 R ]
/Type /Pages >>
endobj
-% 'R115': class PDFStream
-115 0 obj
+% 'R133': class PDFStream
+133 0 obj
% page stream
-<< /Length 8624 >>
+<< /Length 9046 >>
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 9.64 Tm 2.664882 0 Td 24 TL /F2 20 Tf 0 0 0 rg (Testing and scripting your applications with plac) Tj T* -2.664882 0 Td ET
+BT 1 0 0 1 0 9.64 Tm 114.9049 0 Td 24 TL /F2 20 Tf 0 0 0 rg (Advanced usages of plac) Tj T* -114.9049 0 Td ET
Q
Q
q
@@ -1899,7 +2215,7 @@ q 1 0 0 1 91.03937 3 cm
q
0 0 0 rg
-BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (June 2010) Tj T* ET
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (July 2010) Tj T* ET
Q
Q
q
@@ -2013,30 +2329,30 @@ q Q
Q
q
-1 0 0 1 62.69291 569.0236 cm
+1 0 0 1 62.69291 557.0236 cm
q
0 0 0 rg
-BT 1 0 0 1 0 16.82 Tm /F5 10 Tf 12 TL .372339 Tw (The present document discusses a few of the advanced use cases for plac. It assumes you have already) Tj T* 0 Tw (read an understood the basic documentation.) Tj T* ET
+BT 1 0 0 1 0 28.82 Tm /F5 10 Tf 12 TL 2.399986 Tw (The present document discusses a few of the advanced use cases for plac. It shows how to write) Tj T* 0 Tw 2.164651 Tw (interactive and non-interactive interpreters with plac, and how to use plac for testing and scripting a) Tj T* 0 Tw (generic application. It assumes you have already read an understood the basic documentation.) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 536.0236 cm
+1 0 0 1 62.69291 524.0236 cm
q
BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Contents) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 314.0236 cm
+1 0 0 1 62.69291 248.0236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
-1 0 0 1 0 201 cm
+1 0 0 1 0 255 cm
q
BT 1 0 0 1 0 4.82 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (Introduction) Tj T* ET
Q
Q
q
-1 0 0 1 397.8898 201 cm
+1 0 0 1 397.8898 255 cm
q
0 0 .501961 rg
0 0 .501961 RG
@@ -2044,13 +2360,13 @@ BT 1 0 0 1 0 4.82 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 183 cm
+1 0 0 1 0 237 cm
q
BT 1 0 0 1 0 4.82 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (From scripts to interactive applications) Tj T* ET
Q
Q
q
-1 0 0 1 397.8898 183 cm
+1 0 0 1 397.8898 237 cm
q
0 0 .501961 rg
0 0 .501961 RG
@@ -2058,13 +2374,13 @@ BT 1 0 0 1 0 4.82 Tm /F2 10 Tf 12 TL 66.44 0 Td (2) Tj T* -66.44 0 Td ET Q
Q
q
-1 0 0 1 0 165 cm
+1 0 0 1 0 219 cm
q
BT 1 0 0 1 0 4.82 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (Testing a plac application) Tj T* ET
Q
Q
q
-1 0 0 1 397.8898 165 cm
+1 0 0 1 397.8898 219 cm
q
0 0 .501961 rg
0 0 .501961 RG
@@ -2072,13 +2388,13 @@ BT 1 0 0 1 0 4.82 Tm /F2 10 Tf 12 TL 66.44 0 Td (3) Tj T* -66.44 0 Td ET Q
Q
q
-1 0 0 1 0 147 cm
+1 0 0 1 0 201 cm
q
BT 1 0 0 1 0 4.82 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (Plac easy tests) Tj T* ET
Q
Q
q
-1 0 0 1 397.8898 147 cm
+1 0 0 1 397.8898 201 cm
q
0 0 .501961 rg
0 0 .501961 RG
@@ -2086,13 +2402,13 @@ BT 1 0 0 1 0 4.82 Tm /F2 10 Tf 12 TL 66.44 0 Td (4) Tj T* -66.44 0 Td ET Q
Q
q
-1 0 0 1 0 129 cm
+1 0 0 1 0 183 cm
q
BT 1 0 0 1 0 4.82 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (Plac batch scripts) Tj T* ET
Q
Q
q
-1 0 0 1 397.8898 129 cm
+1 0 0 1 397.8898 183 cm
q
0 0 .501961 rg
0 0 .501961 RG
@@ -2100,9 +2416,51 @@ BT 1 0 0 1 0 4.82 Tm /F2 10 Tf 12 TL 66.44 0 Td (6) Tj T* -66.44 0 Td ET Q
Q
q
+1 0 0 1 0 165 cm
+q
+BT 1 0 0 1 0 4.82 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (Implementing subcommands) 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 4.82 Tm /F2 10 Tf 12 TL 66.44 0 Td (7) Tj T* -66.44 0 Td ET
+Q
+Q
+q
+1 0 0 1 0 147 cm
+q
+BT 1 0 0 1 0 4.82 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (Readline support) 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 4.82 Tm /F2 10 Tf 12 TL 66.44 0 Td (9) Tj T* -66.44 0 Td ET
+Q
+Q
+q
+1 0 0 1 0 129 cm
+q
+BT 1 0 0 1 0 4.82 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (The plac runner) 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 4.82 Tm /F2 10 Tf 12 TL 60.88 0 Td (11) Tj T* -60.88 0 Td ET
+Q
+Q
+q
1 0 0 1 0 111 cm
q
-BT 1 0 0 1 0 4.82 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (Containers of commands) Tj T* ET
+BT 1 0 0 1 0 4.82 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (A non class-based example) Tj T* ET
Q
Q
q
@@ -2110,13 +2468,13 @@ q q
0 0 .501961 rg
0 0 .501961 RG
-BT 1 0 0 1 0 4.82 Tm /F2 10 Tf 12 TL 66.44 0 Td (7) Tj T* -66.44 0 Td ET
+BT 1 0 0 1 0 4.82 Tm /F2 10 Tf 12 TL 60.88 0 Td (12) Tj T* -60.88 0 Td ET
Q
Q
q
1 0 0 1 0 93 cm
q
-BT 1 0 0 1 0 4.82 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (For ) Tj /F6 10 Tf (cmd ) Tj /F2 10 Tf (lovers) Tj T* ET
+BT 1 0 0 1 0 4.82 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (Writing your own plac runner) Tj T* ET
Q
Q
q
@@ -2124,13 +2482,13 @@ q q
0 0 .501961 rg
0 0 .501961 RG
-BT 1 0 0 1 0 4.82 Tm /F2 10 Tf 12 TL 66.44 0 Td (9) Tj T* -66.44 0 Td ET
+BT 1 0 0 1 0 4.82 Tm /F2 10 Tf 12 TL 60.88 0 Td (14) Tj T* -60.88 0 Td ET
Q
Q
q
1 0 0 1 0 75 cm
q
-BT 1 0 0 1 0 4.82 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (The plac runner) Tj T* ET
+BT 1 0 0 1 0 4.82 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (Long running commands) Tj T* ET
Q
Q
q
@@ -2138,13 +2496,13 @@ q q
0 0 .501961 rg
0 0 .501961 RG
-BT 1 0 0 1 0 4.82 Tm /F2 10 Tf 12 TL 66.44 0 Td (9) Tj T* -66.44 0 Td ET
+BT 1 0 0 1 0 4.82 Tm /F2 10 Tf 12 TL 60.88 0 Td (16) Tj T* -60.88 0 Td ET
Q
Q
q
1 0 0 1 0 57 cm
q
-BT 1 0 0 1 0 4.82 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (A non class-based example) Tj T* ET
+BT 1 0 0 1 0 4.82 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (Threaded commands) Tj T* ET
Q
Q
q
@@ -2152,13 +2510,13 @@ q q
0 0 .501961 rg
0 0 .501961 RG
-BT 1 0 0 1 0 4.82 Tm /F2 10 Tf 12 TL 60.88 0 Td (11) Tj T* -60.88 0 Td ET
+BT 1 0 0 1 0 4.82 Tm /F2 10 Tf 12 TL 60.88 0 Td (16) Tj T* -60.88 0 Td ET
Q
Q
q
1 0 0 1 0 39 cm
q
-BT 1 0 0 1 0 4.82 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (Writing your own plac runner) Tj T* ET
+BT 1 0 0 1 0 4.82 Tm 12 TL /F2 10 Tf 0 0 .501961 rg (Running commands as external processes) Tj T* ET
Q
Q
q
@@ -2166,7 +2524,7 @@ q q
0 0 .501961 rg
0 0 .501961 RG
-BT 1 0 0 1 0 4.82 Tm /F2 10 Tf 12 TL 60.88 0 Td (13) Tj T* -60.88 0 Td ET
+BT 1 0 0 1 0 4.82 Tm /F2 10 Tf 12 TL 60.88 0 Td (18) Tj T* -60.88 0 Td ET
Q
Q
q
@@ -2180,7 +2538,7 @@ q q
0 0 .501961 rg
0 0 .501961 RG
-BT 1 0 0 1 0 4.82 Tm /F2 10 Tf 12 TL 60.88 0 Td (14) Tj T* -60.88 0 Td ET
+BT 1 0 0 1 0 4.82 Tm /F2 10 Tf 12 TL 60.88 0 Td (19) Tj T* -60.88 0 Td ET
Q
Q
q
@@ -2194,43 +2552,37 @@ q q
0 0 .501961 rg
0 0 .501961 RG
-BT 1 0 0 1 0 4.82 Tm /F2 10 Tf 12 TL 60.88 0 Td (15) Tj T* -60.88 0 Td ET
+BT 1 0 0 1 0 4.82 Tm /F2 10 Tf 12 TL 60.88 0 Td (19) Tj T* -60.88 0 Td ET
Q
Q
q
Q
Q
q
-1 0 0 1 62.69291 281.0236 cm
+1 0 0 1 62.69291 215.0236 cm
q
BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Introduction) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 239.0236 cm
+1 0 0 1 62.69291 173.0236 cm
q
BT 1 0 0 1 0 28.82 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 /F5 10 Tf (huge ) Tj /F1 10 Tf (domain of applications.) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 197.0236 cm
+1 0 0 1 62.69291 131.0236 cm
q
BT 1 0 0 1 0 28.82 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 155.0236 cm
+1 0 0 1 62.69291 89.02362 cm
q
BT 1 0 0 1 0 28.82 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 113.0236 cm
-q
-BT 1 0 0 1 0 28.82 Tm .694104 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can easily replace the ) Tj /F4 10 Tf (cmd ) Tj /F1 10 Tf (module of the standard library and you could easily write an application) Tj T* 0 Tw 2.731654 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. Or any other thing, your) Tj T* 0 Tw (imagination is the only limit!) Tj T* ET
-Q
-Q
-q
1 0 0 1 56.69291 56.69291 cm
q
0 0 0 rg
@@ -2241,38 +2593,44 @@ Q endstream
endobj
-% 'R116': class PDFStream
-116 0 obj
+% 'R134': class PDFStream
+134 0 obj
% page stream
-<< /Length 4233 >>
+<< /Length 4496 >>
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
+1 0 0 1 62.69291 729.0236 cm
+q
+BT 1 0 0 1 0 28.82 Tm .694104 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can easily replace the ) Tj /F4 10 Tf (cmd ) Tj /F1 10 Tf (module of the standard library and you could easily write an application) Tj T* 0 Tw 2.731654 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. Or any other thing, your) Tj T* 0 Tw (imagination is the only limit!) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 696.0236 cm
q
BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (From scripts to interactive applications) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 690.0236 cm
+1 0 0 1 62.69291 642.0236 cm
q
BT 1 0 0 1 0 40.82 Tm 3.215814 Tw 12 TL /F1 10 Tf 0 0 0 rg (Command-line scripts have many advantages, but 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 /F4 10 Tf (plac ) Tj /F1 10 Tf (provides an) Tj T* 0 Tw /F4 10 Tf (Interpreter ) Tj /F1 10 Tf (class just for this purpose.) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 660.0236 cm
+1 0 0 1 62.69291 612.0236 cm
q
BT 1 0 0 1 0 16.82 Tm 1.293984 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F4 10 Tf (Interpreter ) Tj /F1 10 Tf (class wraps the main function of a script and provides an ) Tj /F4 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 630.0236 cm
+1 0 0 1 62.69291 582.0236 cm
q
BT 1 0 0 1 0 16.82 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 /F4 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 380.8236 cm
+1 0 0 1 62.69291 332.8236 cm
q
q
1 0 0 1 0 0 cm
@@ -2293,13 +2651,13 @@ Q Q
Q
q
-1 0 0 1 62.69291 336.8236 cm
+1 0 0 1 62.69291 288.8236 cm
q
BT 1 0 0 1 0 28.82 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 /F4 10 Tf (-h/--help ) Tj /F1 10 Tf (flag whereas) Tj T* 0 Tw (the internal interface only recognizes the ) Tj /F4 10 Tf (.help ) Tj /F1 10 Tf (command:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 303.6236 cm
+1 0 0 1 62.69291 255.6236 cm
q
q
1 0 0 1 0 0 cm
@@ -2320,7 +2678,7 @@ Q Q
Q
q
-1 0 0 1 62.69291 138.4236 cm
+1 0 0 1 62.69291 90.42362 cm
q
q
1 0 0 1 0 0 cm
@@ -2334,20 +2692,13 @@ n -6 -6 468.6898 156 re B* Q
q
0 0 0 rg
-BT 1 0 0 1 0 137.71 Tm /F4 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. Use .help to see) Tj T* (the internal commands.) 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
+BT 1 0 0 1 0 137.71 Tm /F4 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* ET
Q
Q
Q
Q
Q
q
-1 0 0 1 62.69291 118.4236 cm
-q
-0 0 0 rg
-BT 1 0 0 1 0 4.82 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 56.69291 56.69291 cm
q
0 0 0 rg
@@ -2358,14 +2709,42 @@ Q endstream
endobj
-% 'R117': class PDFStream
-117 0 obj
+% 'R135': class PDFStream
+135 0 obj
% page stream
-<< /Length 4141 >>
+<< /Length 4451 >>
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
+1 0 0 1 62.69291 739.8236 cm
+q
+q
+1 0 0 1 0 0 cm
+q
+1 0 0 1 6.6 6.6 cm
+q
+.662745 .662745 .662745 RG
+.5 w
+.960784 .960784 .862745 rg
+n -6 -6 468.6898 24 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 5.71 Tm /F4 10 Tf 12 TL ( -interactive start interactive interface) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 719.8236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 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 674.6236 cm
q
q
1 0 0 1 0 0 cm
@@ -2386,13 +2765,14 @@ Q Q
Q
q
-1 0 0 1 62.69291 695.8236 cm
+1 0 0 1 62.69291 654.6236 cm
q
-BT 1 0 0 1 0 16.82 Tm 2.649318 Tw 12 TL /F1 10 Tf 0 0 0 rg (Here is an usage session, using ) Tj 0 0 .501961 rg (rlwrap ) Tj 0 0 0 rg (to enable readline features \() Tj 0 0 .501961 rg (rlwrap ) Tj 0 0 0 rg (is available in Unix-like) Tj T* 0 Tw (systems\):) Tj T* ET
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (Here is an usage session:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 434.6236 cm
+1 0 0 1 62.69291 369.4236 cm
q
q
1 0 0 1 0 0 cm
@@ -2402,48 +2782,48 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 252 re B*
+n -6 -6 468.6898 276 re B*
Q
q
-BT 1 0 0 1 0 233.71 Tm 12 TL /F4 10 Tf 0 0 0 rg ($ rlwrap python shelve_interpreter.py -i # interactive use) Tj T* (usage: shelve_interpreter.py [.help] [.showall] [.clear] [.delete DELETE]) Tj T* ( [.filename /home/micheles/conf.shelve]) Tj T* ( [params [params ...]] [setters [setters ...]]) 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* (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
+BT 1 0 0 1 0 257.71 Tm 12 TL /F4 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* (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 378.6236 cm
+1 0 0 1 62.69291 301.4236 cm
q
-BT 1 0 0 1 0 40.82 Tm .256412 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F4 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 /F4 10 Tf (prompt='i) Tj (>) Tj T* 0 Tw .81936 Tw (' ) Tj /F1 10 Tf (which can be used to change the prompt. The message displayed by default is the argparse-provided) Tj T* 0 Tw (usage message, but can be customized by setting an ) Tj /F4 10 Tf (.intro ) Tj /F1 10 Tf (attribute on the main function.) Tj T* ET
+BT 1 0 0 1 0 52.82 Tm .256412 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F4 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 /F4 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 /F4 10 Tf (plac ) Tj /F1 10 Tf (also understands command abbreviations: in this example) Tj T* 0 Tw /F4 10 Tf (del ) Tj /F1 10 Tf (is an abbreviation for ) Tj /F4 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 /F4 10 Tf (NameError) Tj /F1 10 Tf (.) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 348.6236 cm
+1 0 0 1 62.69291 271.4236 cm
q
-BT 1 0 0 1 0 16.82 Tm 1.03811 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that ) Tj /F4 10 Tf (plac.Interpreter ) Tj /F1 10 Tf (is available only if you are using a recent version of Python \() Tj (>) Tj (= 2.5\),) Tj T* 0 Tw (because it is a context manager object which uses extended generators internally.) Tj T* ET
+BT 1 0 0 1 0 16.82 Tm .847045 Tw 12 TL /F1 10 Tf 0 0 0 rg (Finally I must notice that the ) Tj /F4 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 318.6236 cm
+1 0 0 1 62.69291 238.4236 cm
q
-0 0 0 rg
-BT 1 0 0 1 0 16.82 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
+BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Testing a plac application) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 285.6236 cm
+1 0 0 1 62.69291 208.4236 cm
q
-BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Testing a plac application) Tj T* ET
+0 0 0 rg
+BT 1 0 0 1 0 16.82 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 267.6236 cm
+1 0 0 1 62.69291 190.4236 cm
q
BT 1 0 0 1 0 4.82 Tm 12 TL /F1 10 Tf 0 0 0 rg (In principle, one could write automatic tests for the ) Tj /F4 10 Tf (ishelve ) Tj /F1 10 Tf (application by using ) Tj /F4 10 Tf (plac.call ) Tj /F1 10 Tf (directly:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 102.4236 cm
+1 0 0 1 62.69291 97.22362 cm
q
q
1 0 0 1 0 0 cm
@@ -2453,11 +2833,11 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 156 re B*
+n -6 -6 468.6898 84 re B*
Q
q
0 0 0 rg
-BT 1 0 0 1 0 137.71 Tm /F4 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
+BT 1 0 0 1 0 65.71 Tm /F4 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* ET
Q
Q
Q
@@ -2474,26 +2854,47 @@ Q endstream
endobj
-% 'R118': class PDFStream
-118 0 obj
+% 'R136': class PDFStream
+136 0 obj
% page stream
-<< /Length 6129 >>
+<< /Length 6154 >>
stream
1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
q
-1 0 0 1 62.69291 729.0236 cm
+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 65.71 Tm /F4 10 Tf 12 TL ( 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 635.8236 cm
q
BT 1 0 0 1 0 28.82 Tm .390651 Tw 12 TL /F1 10 Tf 0 0 0 rg (However, using ) Tj /F4 10 Tf (plac.call ) Tj /F1 10 Tf (is not especially nice. The big issue is that ) Tj /F4 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 /F4 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 687.0236 cm
+1 0 0 1 62.69291 593.8236 cm
q
BT 1 0 0 1 0 28.82 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 /F4 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 /F4 10 Tf (plac ) Tj /F1 10 Tf (offers a better testing support through the ) Tj /F4 10 Tf (check ) Tj /F1 10 Tf (method of ) Tj /F4 10 Tf (Interpreter ) Tj /F1 10 Tf (objects:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 533.8236 cm
+1 0 0 1 62.69291 440.6236 cm
q
q
1 0 0 1 0 0 cm
@@ -2514,34 +2915,28 @@ Q Q
Q
q
-1 0 0 1 62.69291 489.8236 cm
+1 0 0 1 62.69291 384.6236 cm
q
-BT 1 0 0 1 0 28.82 Tm 6.299974 Tw 12 TL /F1 10 Tf 0 0 0 rg (The method ) Tj /F4 10 Tf (.check\(given_input, expected_output\) ) Tj /F1 10 Tf (works on strings and raises an) Tj T* 0 Tw .971318 Tw /F4 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 (given input.) Tj T* ET
+BT 1 0 0 1 0 40.82 Tm 6.299974 Tw 12 TL /F1 10 Tf 0 0 0 rg (The method ) Tj /F4 10 Tf (.check\(given_input, expected_output\) ) Tj /F1 10 Tf (works on strings and raises an) Tj T* 0 Tw .971318 Tw /F4 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 /F4 10 Tf (AssertionError ) Tj /F1 10 Tf (is catched by tools like ) Tj /F4 10 Tf (py.test ) Tj /F1 10 Tf (and ) Tj /F4 10 Tf (nosetests ) Tj /F1 10 Tf (and) Tj T* 0 Tw (actually ) Tj /F4 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 459.8236 cm
-q
-BT 1 0 0 1 0 16.82 Tm 2.179982 Tw 12 TL /F4 10 Tf 0 0 0 rg (AssertionError ) Tj /F1 10 Tf (is catched by tools like ) Tj /F4 10 Tf (py.test ) Tj /F1 10 Tf (and ) Tj /F4 10 Tf (nosetests ) Tj /F1 10 Tf (and actually ) Tj /F4 10 Tf (plac ) Tj /F1 10 Tf (tests are) Tj T* 0 Tw (intended to be run with such tools.) Tj T* ET
-Q
-Q
-q
-1 0 0 1 62.69291 405.8236 cm
+1 0 0 1 62.69291 330.6236 cm
q
BT 1 0 0 1 0 40.82 Tm .239984 Tw 12 TL /F1 10 Tf 0 0 0 rg (Interpreters offer a minor syntactic advantage with respect to calling ) Tj /F4 10 Tf (plac.call ) Tj /F1 10 Tf (directly, but they offer a) Tj T* 0 Tw .96748 Tw /F5 10 Tf (major ) Tj /F1 10 Tf (semantic advantage when things go wrong \(read exceptions\): an ) Tj /F4 10 Tf (Interpreter ) Tj /F1 10 Tf (object internally) Tj T* 0 Tw 1.181318 Tw (invokes something like ) Tj /F4 10 Tf (plac.call) Tj /F1 10 Tf (, but it wraps all exceptions, so that ) Tj /F4 10 Tf (i.check ) Tj /F1 10 Tf (is guaranteed not to) Tj T* 0 Tw (raise any exception except ) Tj /F4 10 Tf (AssertionError) Tj /F1 10 Tf (.) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 387.8236 cm
+1 0 0 1 62.69291 312.6236 cm
q
BT 1 0 0 1 0 4.82 Tm 12 TL /F1 10 Tf 0 0 0 rg (Even the ) Tj /F4 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 381.8236 cm
+1 0 0 1 62.69291 306.6236 cm
Q
q
-1 0 0 1 62.69291 369.8236 cm
+1 0 0 1 62.69291 294.6236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
BT 1 0 0 1 0 2 Tm T* ET
@@ -2556,57 +2951,37 @@ q Q
Q
q
-1 0 0 1 62.69291 369.8236 cm
+1 0 0 1 62.69291 294.6236 cm
Q
q
-1 0 0 1 62.69291 351.8236 cm
+1 0 0 1 62.69291 276.6236 cm
q
0 0 0 rg
BT 1 0 0 1 0 4.82 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 309.8236 cm
+1 0 0 1 62.69291 234.6236 cm
q
BT 1 0 0 1 0 28.82 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 /F4 10 Tf (__enter__ ) Tj /F1 10 Tf (and ) Tj /F4 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 /F4 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 276.8236 cm
+1 0 0 1 62.69291 201.6236 cm
q
BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Plac easy tests) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 234.8236 cm
+1 0 0 1 62.69291 159.6236 cm
q
BT 1 0 0 1 0 28.82 Tm 1.517126 Tw 12 TL /F1 10 Tf 0 0 0 rg (Writing your tests in terms of ) Tj /F4 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 /F4 10 Tf (plac.call) Tj /F1 10 Tf (, but they are still too low-level for my taste. The ) Tj /F4 10 Tf (Interpreter ) Tj /F1 10 Tf (class provides) Tj T* 0 Tw (support for doctest-style tests, a.k.a. ) Tj /F5 10 Tf (plac easy tests) Tj /F1 10 Tf (.) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 192.8236 cm
-q
-BT 1 0 0 1 0 28.82 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 /F4 10 Tf (ishelve.placet ) Tj /F1 10 Tf (\(the ) Tj /F4 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 99.62362 cm
-q
+1 0 0 1 62.69291 117.6236 cm
q
-1 0 0 1 0 0 cm
-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 65.71 Tm 12 TL /F4 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* ET
-Q
-Q
-Q
+BT 1 0 0 1 0 28.82 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 /F4 10 Tf (ishelve.placet ) Tj /F1 10 Tf (\(the ) Tj /F4 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
@@ -2620,14 +2995,14 @@ Q endstream
endobj
-% 'R119': class PDFStream
-119 0 obj
+% 'R137': class PDFStream
+137 0 obj
% page stream
-<< /Length 5874 >>
+<< /Length 5781 >>
stream
1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
q
-1 0 0 1 62.69291 667.8236 cm
+1 0 0 1 62.69291 595.8236 cm
q
q
1 0 0 1 0 0 cm
@@ -2637,29 +3012,29 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 96 re B*
+n -6 -6 468.6898 168 re B*
Q
q
-BT 1 0 0 1 0 77.71 Tm 12 TL /F4 10 Tf 0 0 0 rg (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* (SystemExit: unrecognized arguments: .cler) Tj T* ET
+BT 1 0 0 1 0 149.71 Tm 12 TL /F4 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 611.8236 cm
+1 0 0 1 62.69291 539.8236 cm
q
BT 1 0 0 1 0 40.82 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 /F4 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 581.8236 cm
+1 0 0 1 62.69291 509.8236 cm
q
BT 1 0 0 1 0 16.82 Tm 2.419984 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can test ) Tj /F4 10 Tf (ishelve.placet ) Tj /F1 10 Tf (file by calling the ) Tj /F4 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 62.69291 537.0393 cm
+1 0 0 1 62.69291 465.0393 cm
q
q
.988825 0 0 .988825 0 0 cm
@@ -2680,51 +3055,57 @@ Q Q
Q
q
-1 0 0 1 62.69291 469.0393 cm
+1 0 0 1 62.69291 421.0393 cm
q
-BT 1 0 0 1 0 52.82 Tm 4.007109 Tw 12 TL /F1 10 Tf 0 0 0 rg (Internally ) Tj /F4 10 Tf (Interpreter.doctests ) Tj /F1 10 Tf (invokes something like ) Tj /F4 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 2.628221 Tw (test fail. The easy tests supported by ) Tj /F4 10 Tf (plac ) Tj /F1 10 Tf (are ) Tj /F5 10 Tf (not ) Tj /F1 10 Tf (unittests: they should be used to model user) Tj T* 0 Tw .491163 Tw (interaction when the order of the operations matters. Since the single subtests in a ) Tj /F4 10 Tf (.placet ) Tj /F1 10 Tf (file are not) Tj T* 0 Tw (independent, it makes sense to exit immediately at the first failure.) Tj T* ET
+BT 1 0 0 1 0 28.82 Tm 4.007109 Tw 12 TL /F1 10 Tf 0 0 0 rg (Internally ) Tj /F4 10 Tf (Interpreter.doctests ) Tj /F1 10 Tf (invokes something like ) Tj /F4 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 403.0393 cm
+1 0 0 1 62.69291 379.0393 cm
q
-BT 1 0 0 1 0 52.82 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 .061318 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 /F4 10 Tf (#) Tj /F1 10 Tf (\), continuation lines, escape) Tj T* 0 Tw 1.54061 Tw (sequences and 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) Tj T* 0 Tw (interpreted.) Tj T* ET
+BT 1 0 0 1 0 28.82 Tm .175868 Tw 12 TL /F1 10 Tf 0 0 0 rg (You should realize tha the easy tests supported by ) Tj /F4 10 Tf (plac ) Tj /F1 10 Tf (are ) Tj /F5 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 /F4 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 349.0393 cm
+1 0 0 1 62.69291 325.0393 cm
q
-BT 1 0 0 1 0 40.82 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 .86408 Tw (files \(plac easy tests does not need to be in a file: you can just pass to the ) Tj /F4 10 Tf (.doctest ) Tj /F1 10 Tf (method a list of) Tj T* 0 Tw (strings corresponding to the lines of the file\).) Tj T* ET
+BT 1 0 0 1 0 40.82 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 /F4 10 Tf (#) Tj /F1 10 Tf (\), escape sequences and) Tj T* 0 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.) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 319.0393 cm
+1 0 0 1 62.69291 271.0393 cm
q
-BT 1 0 0 1 0 16.82 Tm .12683 Tw 12 TL /F1 10 Tf 0 0 0 rg (At the present plac easy tests do not use any code from the doctest module, but the situation may change) Tj T* 0 Tw (in the 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
+BT 1 0 0 1 0 40.82 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 /F4 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 289.0393 cm
+1 0 0 1 62.69291 241.0393 cm
+q
+BT 1 0 0 1 0 16.82 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 211.0393 cm
q
BT 1 0 0 1 0 16.82 Tm 1.447318 Tw 12 TL /F1 10 Tf 0 0 0 rg (It is straighforward to integrate your ) Tj /F4 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 /F4 10 Tf (nose ) Tj /F1 10 Tf (or ) Tj /F4 10 Tf (py.test ) Tj /F1 10 Tf (as follow:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 101.8642 cm
+1 0 0 1 62.69291 93.83932 cm
q
q
-.988825 0 0 .988825 0 0 cm
+1 0 0 1 0 0 cm
q
-1 0 0 1 6.6 6.674587 cm
+1 0 0 1 6.6 6.6 cm
q
.662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 474 180 re B*
+n -6 -6 468.6898 108 re B*
Q
q
0 0 0 rg
-BT 1 0 0 1 0 161.71 Tm /F4 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
+BT 1 0 0 1 0 89.71 Tm /F4 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* ET
Q
Q
Q
@@ -2741,44 +3122,65 @@ Q endstream
endobj
-% 'R120': class PDFStream
-120 0 obj
+% 'R138': class PDFStream
+138 0 obj
% page stream
-<< /Length 4648 >>
+<< /Length 4789 >>
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
+1 0 0 1 62.69291 680.7757 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 84 re B*
+Q
q
-BT 1 0 0 1 0 64.82 Tm 1.44811 Tw 12 TL /F1 10 Tf 0 0 0 rg (Here you should notice that usage of ) Tj /F4 10 Tf (plac.import_main) Tj /F1 10 Tf (, an utility which is able to import the main) Tj T* 0 Tw 1.040465 Tw (function of the script specified in the shabng 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 /F4 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 /F4 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 /F4 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 /F4 10 Tf (ImportError ) Tj /F1 10 Tf (is raised.) Tj T* ET
+0 0 0 rg
+BT 1 0 0 1 0 65.71 Tm /F4 10 Tf 12 TL ( 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 660.0236 cm
+1 0 0 1 62.69291 600.7757 cm
+q
+BT 1 0 0 1 0 64.82 Tm 1.44811 Tw 12 TL /F1 10 Tf 0 0 0 rg (Here you should notice that usage of ) Tj /F4 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 /F4 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 /F4 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 /F4 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 /F4 10 Tf (ImportError ) Tj /F1 10 Tf (is raised.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 567.7757 cm
q
BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Plac batch scripts) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 618.0236 cm
+1 0 0 1 62.69291 525.7757 cm
q
BT 1 0 0 1 0 28.82 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 /F4 10 Tf (.execute ) Tj /F1 10 Tf (method to perform just that.) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 552.0236 cm
+1 0 0 1 62.69291 459.7757 cm
q
BT 1 0 0 1 0 52.82 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 /F4 10 Tf (Interpreter.execute ) Tj /F1 10 Tf (makes sure that any error raised by ) Tj /F4 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 /F5 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 534.0236 cm
+1 0 0 1 62.69291 429.7757 cm
q
-BT 1 0 0 1 0 4.82 Tm 12 TL /F1 10 Tf 0 0 0 rg (In particular consider the following batch file, which contains a syntax error \() Tj /F4 10 Tf (.dl ) Tj /F1 10 Tf (instead of ) Tj /F4 10 Tf (.del) Tj /F1 10 Tf (\):) Tj T* ET
+BT 1 0 0 1 0 16.82 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 /F4 10 Tf (.dl ) Tj /F1 10 Tf (instead of ) Tj /F4 10 Tf (.del) Tj /F1 10 Tf (\):) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 428.8236 cm
+1 0 0 1 62.69291 324.5757 cm
q
q
1 0 0 1 0 0 cm
@@ -2799,13 +3201,13 @@ Q Q
Q
q
-1 0 0 1 62.69291 396.8236 cm
+1 0 0 1 62.69291 292.5757 cm
q
-BT 1 0 0 1 0 16.82 Tm .26686 Tw 12 TL /F1 10 Tf 0 0 0 rg (If you execute the batch file, the interpreter will raise a ) Tj /F4 10 Tf (SystemExit ) Tj /F1 10 Tf (with an appropriated error message) Tj T* 0 Tw (at the ) Tj /F4 10 Tf (.dl ) Tj /F1 10 Tf (line and the last command will ) Tj /F5 10 Tf (not ) Tj /F1 10 Tf (be executed:) Tj T* ET
+BT 1 0 0 1 0 16.82 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 /F4 10 Tf (.dl: not found ) Tj /F1 10 Tf (at the ) Tj /F4 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 207.6236 cm
+1 0 0 1 62.69291 91.37571 cm
q
q
1 0 0 1 0 0 cm
@@ -2815,22 +3217,16 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 180 re B*
+n -6 -6 468.6898 192 re B*
Q
q
-BT 1 0 0 1 0 161.71 Tm 12 TL /F4 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* (unrecognized arguments: .dl) Tj T* ET
+BT 1 0 0 1 0 173.71 Tm 12 TL /F4 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* ET
Q
Q
Q
Q
Q
q
-1 0 0 1 62.69291 163.6236 cm
-q
-BT 1 0 0 1 0 28.82 Tm .159988 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F4 10 Tf (verbose ) Tj /F1 10 Tf (flag is there to show the lines which are being interpreted \(prefixed by ) Tj /F4 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 /F4 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 56.69291 56.69291 cm
q
0 0 0 rg
@@ -2841,45 +3237,71 @@ Q endstream
endobj
-% 'R121': class PDFStream
-121 0 obj
+% 'R139': class PDFStream
+139 0 obj
% page stream
-<< /Length 3980 >>
+<< /Length 4588 >>
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
+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
-BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Containers of commands) Tj T* ET
+.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 17.71 Tm 12 TL /F4 10 Tf 0 0 0 rg (i) Tj (>) Tj ( .show) Tj T* (b=2) Tj T* ET
+Q
+Q
+Q
Q
Q
q
-1 0 0 1 62.69291 702.0236 cm
+1 0 0 1 62.69291 683.8236 cm
q
-BT 1 0 0 1 0 28.82 Tm 1.182485 Tw 12 TL /F1 10 Tf 0 0 0 rg (When I discussed the ) Tj /F4 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 1.483988 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) Tj T* 0 Tw (better. Here I will substantiate my claim.) Tj T* ET
+BT 1 0 0 1 0 28.82 Tm .159988 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F4 10 Tf (verbose ) Tj /F1 10 Tf (flag is there to show the lines which are being interpreted \(prefixed by ) Tj /F4 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 /F4 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 660.0236 cm
+1 0 0 1 62.69291 650.8236 cm
+q
+BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Implementing subcommands) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 608.8236 cm
+q
+BT 1 0 0 1 0 28.82 Tm 1.182485 Tw 12 TL /F1 10 Tf 0 0 0 rg (When I discussed the ) Tj /F4 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 566.8236 cm
q
BT 1 0 0 1 0 28.82 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 /F5 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 618.0236 cm
+1 0 0 1 62.69291 500.8236 cm
q
-BT 1 0 0 1 0 28.82 Tm .015868 Tw 12 TL /F1 10 Tf 0 0 0 rg (Technically a container of commands is any object with a ) Tj /F4 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 (\() Tj /F4 10 Tf (__enter__/__exit__) Tj /F1 10 Tf (\) and dispatch hooks \() Tj /F4 10 Tf (__missing__) Tj /F1 10 Tf (, invoked for invalid command names\).) Tj T* ET
+BT 1 0 0 1 0 52.82 Tm .015868 Tw 12 TL /F1 10 Tf 0 0 0 rg (Technically a container of commands is any object with a ) Tj /F4 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 /F4 10 Tf (__enter__/__exit__) Tj /F1 10 Tf (\) and dispatch hooks \() Tj /F4 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 (commands.) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 600.0236 cm
+1 0 0 1 62.69291 482.8236 cm
q
0 0 0 rg
-BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (Using this feature the shelve interface can be rewritten in a more object-oriented way as follows:) Tj T* ET
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (Rhe shelve interface can be rewritten in an object-oriented way as follows:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 98.82362 cm
+1 0 0 1 62.69291 89.62362 cm
q
q
1 0 0 1 0 0 cm
@@ -2889,11 +3311,11 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 492 re B*
+n -6 -6 468.6898 384 re B*
Q
q
0 0 0 rg
-BT 1 0 0 1 0 473.71 Tm /F4 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='~/conf.shelve'\):) Tj T* ( self.fname = os.path.expanduser\(configfile\)) Tj T* ( self.intro = 'Operating on %s. Available commands:\\n%s' % \() Tj T* ( self.fname, '\\n'.join\(c for c in self.commands\)\)) Tj T* ( def __enter__\(self\):) Tj T* ( self.sh = shelve.open\(self.fname\)) Tj T* ( return self) 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* ( def __exit__\(self, etype, exc, tb\):) Tj T* ( self.sh.close\(\)) Tj T* T* (main = ShelveInterface # the main 'function' can also be a class!) Tj T* T* ET
+BT 1 0 0 1 0 365.71 Tm /F4 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='~/conf.shelve'\):) Tj T* ( self.fname = os.path.expanduser\(configfile\)) Tj T* ( self.__doc__ += '\\nOperating on %s.\\n.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* ET
Q
Q
Q
@@ -2910,14 +3332,14 @@ Q endstream
endobj
-% 'R122': class PDFStream
-122 0 obj
+% 'R140': class PDFStream
+140 0 obj
% page stream
-<< /Length 4384 >>
+<< /Length 4077 >>
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
+1 0 0 1 62.69291 607.8236 cm
q
q
1 0 0 1 0 0 cm
@@ -2927,31 +3349,37 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 48 re B*
+n -6 -6 468.6898 156 re B*
Q
q
0 0 0 rg
-BT 1 0 0 1 0 29.71 Tm /F4 10 Tf 12 TL (if __name__ == '__main__':) Tj T* ( i = plac.Interpreter\(main\(\)\)) Tj T* ( i.interact\(\)) Tj T* ET
+BT 1 0 0 1 0 137.71 Tm /F4 10 Tf 12 TL ( 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 # the main 'function' can also be a class!) Tj T* T* (if __name__ == '__main__':) Tj T* ( shelve_interface = plac.call\(main\)) Tj T* ( plac.Interpreter\(shelve_interface\).interact\(\)) Tj T* ET
Q
Q
Q
Q
Q
q
-1 0 0 1 62.69291 623.8236 cm
+1 0 0 1 62.69291 515.8236 cm
q
BT 1 0 0 1 0 76.82 Tm .885366 Tw 12 TL /F4 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 /F4 10 Tf (__enter__ ) Tj /F1 10 Tf (and ) Tj /F4 10 Tf (__exit__ ) Tj /F1 10 Tf (methods, they are invoked in the right order \() Tj /F4 10 Tf (__enter__) Tj T* 0 Tw .23528 Tw /F1 10 Tf (before the interpreter loop starts and ) Tj /F4 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 /F4 10 Tf (__enter__ ) Tj /F1 10 Tf (and ) Tj /F4 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 /F4 10 Tf (show ) Tj /F1 10 Tf (and ) Tj /F4 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 62.69291 605.8236 cm
+1 0 0 1 62.69291 437.8236 cm
+q
+BT 1 0 0 1 0 64.82 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 1.176136 Tw (provided commands: ) Tj /F4 10 Tf (.help ) Tj /F1 10 Tf (and ) Tj /F4 10 Tf (.last_tb) Tj /F1 10 Tf (. The ) Tj /F4 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 /F4 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 419.8236 cm
q
0 0 0 rg
BT 1 0 0 1 0 4.82 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 284.6236 cm
+1 0 0 1 62.69291 98.62362 cm
q
q
1 0 0 1 0 0 cm
@@ -2964,81 +3392,113 @@ q n -6 -6 468.6898 312 re B*
Q
q
-BT 1 0 0 1 0 293.71 Tm 12 TL /F4 10 Tf 0 0 0 rg ($ rlwrap python ishelve2.py) Tj T* (Operating on /home/micheles/conf.shelve. Available commands:) Tj T* (set) Tj T* (show) Tj T* (showall) Tj T* (delete) Tj 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* (DBNotFoundError: \(-30988, 'DB_NOTFOUND: No matching key/data pair found'\)) Tj T* (i) Tj (>) Tj T* ET
+BT 1 0 0 1 0 293.71 Tm 12 TL /F4 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* (.help to see the available commands.) Tj T* (i) Tj (>) Tj ( .help) Tj T* T* (special commands) Tj T* (================) Tj T* (.help .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* ET
Q
Q
Q
Q
Q
q
-1 0 0 1 62.69291 252.6236 cm
+1 0 0 1 56.69291 56.69291 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 238.1649 0 Td (8) Tj T* -238.1649 0 Td ET
+Q
+Q
+
+endstream
+
+endobj
+% 'R141': class PDFStream
+141 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 606.2269 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 204 re B*
+Q
+q
+BT 1 0 0 1 0 185.71 Tm 12 TL /F4 10 Tf 0 0 0 rg (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 574.2269 cm
q
BT 1 0 0 1 0 16.82 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 /F4 10 Tf (verbose ) Tj /F1 10 Tf (flag to the) Tj T* 0 Tw /F4 10 Tf (Interpreter.interact ) Tj /F1 10 Tf (method.) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 198.6236 cm
+1 0 0 1 62.69291 541.2269 cm
q
-BT 1 0 0 1 0 40.82 Tm .532209 Tw 12 TL /F1 10 Tf 0 0 0 rg (The interactive mode of ) Tj /F4 10 Tf (plac ) Tj /F1 10 Tf (can be used as a replacement of the ) Tj /F4 10 Tf (cmd ) Tj /F1 10 Tf (module in the standard library.) Tj T* 0 Tw .66881 Tw (There are a few differences, however. For instance you miss tab completion, even if use ) Tj 0 0 .501961 rg (rlwrap ) Tj 0 0 0 rg (\(you get) Tj T* 0 Tw .092651 Tw (persistent command history for free, however\). This is not a big issue, since ) Tj /F4 10 Tf (plac ) Tj /F1 10 Tf (understands command) Tj T* 0 Tw (abbreviations.) Tj T* ET
+BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Readline support) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 180.6236 cm
+1 0 0 1 62.69291 463.2269 cm
q
-BT 1 0 0 1 0 4.82 Tm 12 TL /F1 10 Tf 0 0 0 rg (If an abbreviation is ambiguous, ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (warns you:) Tj T* ET
+BT 1 0 0 1 0 64.82 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 1.14683 Tw (set, or you want to change the location of the ) Tj /F4 10 Tf (.history ) Tj /F1 10 Tf (file, or to change the case sensitivirt, or you) Tj T* 0 Tw .162209 Tw (want to change the prompt, the way to do it is to pass a ) Tj /F4 10 Tf (plac.ReadlineInput ) Tj /F1 10 Tf (object to the interpreter.) Tj T* 0 Tw (Here is an example, assuming you want to build a database interface understanding SQL commands:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 111.4236 cm
+1 0 0 1 62.69291 153.1549 cm
q
q
-1 0 0 1 0 0 cm
+.96447 0 0 .96447 0 0 cm
q
-1 0 0 1 6.6 6.6 cm
+1 0 0 1 6.6 6.843137 cm
q
.662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 60 re B*
+n -6 -6 486 312 re B*
Q
q
-BT 1 0 0 1 0 41.71 Tm 12 TL /F4 10 Tf 0 0 0 rg ($ rlwrap python ishelve2.py) Tj T* (usage: plac_runner.py ishelve2.py [-h] {delete,set,showall,show} ...) Tj T* (i) Tj (>) Tj ( sh) Tj T* (NameError: Ambiguous command 'sh': matching ['showall', 'show']) Tj T* ET
+BT 1 0 0 1 0 293.71 Tm 12 TL /F4 10 Tf 0 0 0 rg (import os, plac) Tj T* (from sqlalchemy.ext.sqlsoup import SqlSoup) Tj T* T* (SQLKEYWORDS = set\(['select', 'from', '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, *args\):) Tj T* ( sql = 'SELECT ' + ' '.join\(args\)) 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, prompt='sql) Tj (>) Tj ( ', ) Tj T* ( histfile=os.path.expanduser\('~/.sql_interface.history'\), ) Tj T* ( case_sensitive=False\)) Tj T* T* (if __name__ == '__main__':) Tj T* ( plac.Interpreter\(plac.call\(SqlInterface\)\).interact\(rl_input\)) Tj T* ET
Q
Q
Q
Q
Q
q
+1 0 0 1 62.69291 133.1549 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (Here is an example of usage:) 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 4.82 Tm /F1 10 Tf 12 TL 238.1649 0 Td (8) Tj T* -238.1649 0 Td ET
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 238.1649 0 Td (9) Tj T* -238.1649 0 Td ET
Q
Q
endstream
endobj
-% 'R123': class PDFStream
-123 0 obj
+% 'R142': class PDFStream
+142 0 obj
% page stream
-<< /Length 5097 >>
+<< /Length 5061 >>
stream
1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
q
-1 0 0 1 62.69291 744.0236 cm
-q
-BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (For ) Tj /F6 17.5 Tf (cmd ) Tj /F2 17.5 Tf (lovers) Tj T* ET
-Q
-Q
-q
-1 0 0 1 62.69291 642.0236 cm
-q
-BT 1 0 0 1 0 88.82 Tm .391567 Tw 12 TL /F1 10 Tf 0 0 0 rg (I have been using the ) Tj 0 0 .501961 rg (cmd ) Tj 0 0 0 rg (module of the standard library for years. I have also written a much enhanced) Tj T* 0 Tw .212619 Tw /F4 10 Tf (cmd2 ) Tj /F1 10 Tf (module which we are using internally at work and from which I have taken some ideas used in ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (.) Tj T* 0 Tw .13832 Tw (In many ways ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (makes the ) Tj 0 0 .501961 rg (cmd ) Tj 0 0 0 rg (module obsolete, but I realize why many nostalgic souls would still use) Tj T* 0 Tw 1.053984 Tw 0 0 .501961 rg (cmd) Tj 0 0 0 rg (, especially until ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (does not grow real auto-completion features, instead of relying on ) Tj 0 0 .501961 rg (rlwrap) Tj 0 0 0 rg (. But) Tj T* 0 Tw 1.12561 Tw (there must not be competition between ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (and ) Tj 0 0 .501961 rg (cmd) Tj 0 0 0 rg (: actually the two can happily work togethere. For) Tj T* 0 Tw .338988 Tw (this reason I have put in the ) Tj /F4 10 Tf (plac_ext ) Tj /F1 10 Tf (module a few lines of code for gluing together ) Tj 0 0 .501961 rg (cmd ) Tj 0 0 0 rg (and ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (, the) Tj T* 0 Tw 1.229269 Tw /F4 10 Tf (cmd_interface) Tj /F1 10 Tf (. Using the ) Tj /F4 10 Tf (cmd_interface ) Tj /F1 10 Tf (is quite trivial: give to it a plac command container and) Tj T* 0 Tw (you will get in exchange a ) Tj /F4 10 Tf (cmd.Cmd ) Tj /F1 10 Tf (object:) Tj T* ET
-Q
-Q
-q
-1 0 0 1 62.69291 548.8236 cm
+1 0 0 1 62.69291 715.8236 cm
q
q
1 0 0 1 0 0 cm
@@ -3048,25 +3508,35 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 84 re B*
+n -6 -6 468.6898 48 re B*
Q
q
-0 0 0 rg
-BT 1 0 0 1 0 65.71 Tm /F4 10 Tf 12 TL (# cmd_ext.py) Tj T* (from plac_ext import cmd_interface) Tj T* (import ishelve2) Tj T* T* (if __name__ == '__main__':) Tj T* ( cmd_interface\(ishelve2.main\(\)\).cmdloop\(\)) Tj T* ET
+BT 1 0 0 1 0 29.71 Tm 12 TL /F4 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 528.8236 cm
+1 0 0 1 62.69291 635.8236 cm
q
-0 0 0 rg
-BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (Here is an example of interactive session:) Tj T* ET
+BT 1 0 0 1 0 64.82 Tm 1.951318 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can check that entering just ) Tj /F4 10 Tf (sel ) Tj /F1 10 Tf (and pressing TAB the readline library completes the ) Tj /F4 10 Tf (SELECT) Tj T* 0 Tw .797356 Tw /F1 10 Tf (keyword for you and makes it upper case; idem for ) Tj /F4 10 Tf (FROM) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (INNER) Tj /F1 10 Tf (, ) Tj /F4 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 /F4 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 557.8236 cm
+q
+BT 1 0 0 1 0 64.82 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 pyreadline library) Tj T* 0 Tw .068488 Tw (\(I do not use Windows, so this part is very little tested\). For people worried about licenses, I will notice that) Tj T* 0 Tw 1.863322 Tw 0 0 .501961 rg (plac ) Tj 0 0 0 rg (uses the readline library only if available, it does not include it and it does not rely on it in any) Tj T* 0 Tw 3.716654 Tw (fundamental way, so that the ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (licence does not need to be the GPL \(actually it is a BSD) Tj T* 0 Tw (do-whatever-you-want-with-it licence\).) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 515.8236 cm
+q
+BT 1 0 0 1 0 28.82 Tm .187882 Tw 12 TL /F1 10 Tf 0 0 0 rg (The interactive mode of ) Tj /F4 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 2.730651 Tw (is actually better than ) Tj 0 0 .501961 rg (cmd) Tj 0 0 0 rg (: for instance, the ) Tj /F4 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 267.6236 cm
+1 0 0 1 62.69291 206.6236 cm
q
q
1 0 0 1 0 0 cm
@@ -3076,64 +3546,90 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 252 re B*
+n -6 -6 468.6898 300 re B*
Q
q
-BT 1 0 0 1 0 233.71 Tm 12 TL /F4 10 Tf 0 0 0 rg ($ python cmd_ex.py) Tj T* (\(Cmd\) help) Tj T* T* (Documented commands \(type help ) Tj (<) Tj (topic) Tj (>) Tj (\):) Tj T* (========================================) Tj T* (delete set show showall) Tj T* T* (Undocumented commands:) Tj T* (======================) Tj T* (EOF help) Tj T* T* (\(Cmd\) set a 1) Tj T* (setting a=1) Tj T* (\(Cmd\) show a) Tj T* (a = 1) Tj T* (\(Cmd\) showall) Tj T* (a = 1) Tj T* (\(Cmd\) delete b) Tj T* (KeyError: 'b') Tj T* (\(Cmd\) EOF [or CTRL-D]) Tj T* ET
+BT 1 0 0 1 0 281.71 Tm 12 TL /F4 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) Tj T* T* (i) Tj (>) Tj ( .help show) Tj T* (usage: show [names [names ...]]) Tj T* T* (show given parameters) Tj T* T* (positional arguments:) Tj T* ( names) Tj T* ET
Q
Q
Q
Q
Q
q
-1 0 0 1 62.69291 235.6236 cm
+1 0 0 1 62.69291 150.6236 cm
q
-BT 1 0 0 1 0 16.82 Tm .504983 Tw 12 TL /F1 10 Tf 0 0 0 rg (Internally the ) Tj /F4 10 Tf (cmd_interface ) Tj /F1 10 Tf (builds a ) Tj /F4 10 Tf (cmd.Cmd ) Tj /F1 10 Tf (class and adds to it the ) Tj /F4 10 Tf (do_ ) Tj /F1 10 Tf (methods corresponding) Tj T* 0 Tw (to the commands in the container, then it returns a ) Tj /F4 10 Tf (cmd.Cmd ) Tj /F1 10 Tf (instance.) Tj T* ET
+BT 1 0 0 1 0 40.82 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 193.6236 cm
+1 0 0 1 62.69291 108.6236 cm
q
-BT 1 0 0 1 0 28.82 Tm .089488 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F4 10 Tf (cmd_interface ) Tj /F1 10 Tf (is just a proof of concept: it is there so that you can study the source code and see) Tj T* 0 Tw .93832 Tw (an example of integration of ) Tj 0 0 .501961 rg (plac ) Tj 0 0 0 rg (with a different framework. It may change and even go away in future) Tj T* 0 Tw (releases of ) Tj 0 0 .501961 rg (plac) Tj 0 0 0 rg (.) Tj T* ET
-Q
-Q
-q
-1 0 0 1 62.69291 160.6236 cm
-q
-BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (The plac runner) Tj T* ET
+BT 1 0 0 1 0 28.82 Tm .901647 Tw 12 TL /F1 10 Tf 0 0 0 rg (Moreover at the moment ) Tj /F4 10 Tf (plac ) Tj /F1 10 Tf (also understands command abbreviations. However, this feature should) Tj T* 0 Tw .256235 Tw (be considered deprecated and may disappear in future releases. It was meaningful in the past, when ) Tj 0 0 .501961 rg (plac) Tj T* 0 Tw 0 0 0 rg (did not support readline.) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 94.62362 cm
+1 0 0 1 62.69291 90.62362 cm
q
-BT 1 0 0 1 0 52.82 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 /F4 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 /F4 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 /F4 10 Tf (.plac ) Tj /F1 10 Tf (scripts and ) Tj /F4 10 Tf (.placet ) Tj /F1 10 Tf (files, as well) Tj T* 0 Tw 1.47311 Tw (as Python modules containg a ) Tj /F4 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
+BT 1 0 0 1 0 4.82 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 56.69291 56.69291 cm
q
0 0 0 rg
-BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 238.1649 0 Td (9) Tj T* -238.1649 0 Td ET
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (10) Tj T* -235.3849 0 Td ET
Q
Q
endstream
endobj
-% 'R124': class PDFStream
-124 0 obj
+% 'R143': class PDFStream
+143 0 obj
% page stream
-<< /Length 4203 >>
+<< /Length 5053 >>
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
+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 17.71 Tm 12 TL /F4 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 694.8236 cm
+q
+BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (The plac runner) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 628.8236 cm
+q
+BT 1 0 0 1 0 52.82 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 /F4 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 /F4 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 /F4 10 Tf (.plac ) Tj /F1 10 Tf (scripts and ) Tj /F4 10 Tf (.placet ) Tj /F1 10 Tf (files, as well) Tj T* 0 Tw 1.47311 Tw (as Python modules containg a ) Tj /F4 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 598.8236 cm
q
BT 1 0 0 1 0 16.82 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 /F4 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 671.8236 cm
+1 0 0 1 62.69291 529.6236 cm
q
q
1 0 0 1 0 0 cm
@@ -3154,13 +3650,13 @@ Q Q
Q
q
-1 0 0 1 62.69291 627.8236 cm
+1 0 0 1 62.69291 485.6236 cm
q
BT 1 0 0 1 0 28.82 Tm .575868 Tw 12 TL /F1 10 Tf 0 0 0 rg (The first line of the ) Tj /F4 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 (interpreter object. The other lines contains commands. Then you can run the script as follows:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 538.6505 cm
+1 0 0 1 62.69291 396.4505 cm
q
q
.952737 0 0 .952737 0 0 cm
@@ -3181,24 +3677,24 @@ Q Q
Q
q
-1 0 0 1 62.69291 506.6505 cm
+1 0 0 1 62.69291 364.4505 cm
q
0 0 0 rg
BT 1 0 0 1 0 16.82 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 476.6505 cm
+1 0 0 1 62.69291 334.4505 cm
q
0 0 0 rg
BT 1 0 0 1 0 16.82 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 470.6505 cm
+1 0 0 1 62.69291 328.4505 cm
Q
q
-1 0 0 1 62.69291 458.6505 cm
+1 0 0 1 62.69291 316.4505 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
BT 1 0 0 1 0 2 Tm T* ET
@@ -3206,23 +3702,23 @@ q 1 0 0 1 20 0 cm
q
0 0 0 rg
-BT 1 0 0 1 0 5.71 Tm /F4 10 Tf 12 TL (alias plac="rlwrap plac_runner.py") Tj T* ET
+BT 1 0 0 1 0 5.71 Tm /F4 10 Tf 12 TL (alias plac="plac_runner.py") Tj T* ET
Q
Q
q
Q
Q
q
-1 0 0 1 62.69291 458.6505 cm
+1 0 0 1 62.69291 316.4505 cm
Q
q
-1 0 0 1 62.69291 428.6505 cm
+1 0 0 1 62.69291 286.4505 cm
q
BT 1 0 0 1 0 16.82 Tm 2.955318 Tw 12 TL /F1 10 Tf 0 0 0 rg (\(or you define a suitable ) Tj /F4 10 Tf (plac.bat ) Tj /F1 10 Tf (script in Windows\) you can run the ) Tj /F4 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 62.69291 239.4505 cm
+1 0 0 1 62.69291 109.2505 cm
q
q
1 0 0 1 0 0 cm
@@ -3232,23 +3728,40 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 180 re B*
+n -6 -6 468.6898 168 re B*
Q
q
-BT 1 0 0 1 0 161.71 Tm 12 TL /F4 10 Tf 0 0 0 rg ($ plac -i ishelve2.py) Tj T* (Operating on /home/micheles/conf.shelve. Available commands:) Tj T* (set) Tj T* (show) Tj T* (showall) Tj T* (delete) 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 b) Tj T* (b = 2) Tj T* ET
+BT 1 0 0 1 0 149.71 Tm 12 TL /F4 10 Tf 0 0 0 rg ($ plac -i ishelve2.py) 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 219.4505 cm
+1 0 0 1 62.69291 89.25045 cm
q
BT 1 0 0 1 0 4.82 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 /F4 10 Tf (.placet ) Tj /F1 10 Tf (file like the following:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 90.25045 cm
+1 0 0 1 56.69291 56.69291 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (11) Tj T* -235.3849 0 Td ET
+Q
+Q
+
+endstream
+
+endobj
+% 'R144': class PDFStream
+144 0 obj
+% page stream
+<< /Length 5476 >>
+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
@@ -3261,44 +3774,27 @@ q n -6 -6 468.6898 120 re B*
Q
q
-BT 1 0 0 1 0 101.71 Tm 12 TL /F4 10 Tf 0 0 0 rg (#!ishelve2.py -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 b) Tj T* (b = 2) Tj T* ET
+BT 1 0 0 1 0 101.71 Tm 12 TL /F4 10 Tf 0 0 0 rg (#!ishelve2.py -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 56.69291 56.69291 cm
-q
-0 0 0 rg
-BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (10) Tj T* -235.3849 0 Td ET
-Q
-Q
-
-endstream
-
-endobj
-% 'R125': class PDFStream
-125 0 obj
-% page stream
-<< /Length 5347 >>
-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
+1 0 0 1 62.69291 611.8236 cm
q
BT 1 0 0 1 0 16.82 Tm 2.145697 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that the first line specifies a test database ) Tj /F4 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 723.0236 cm
+1 0 0 1 62.69291 593.8236 cm
q
0 0 0 rg
BT 1 0 0 1 0 4.82 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 677.8236 cm
+1 0 0 1 62.69291 548.6236 cm
q
q
1 0 0 1 0 0 cm
@@ -3319,19 +3815,19 @@ Q Q
Q
q
-1 0 0 1 62.69291 633.8236 cm
+1 0 0 1 62.69291 504.6236 cm
q
BT 1 0 0 1 0 28.82 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 /F4 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 /F5 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 603.8236 cm
+1 0 0 1 62.69291 474.6236 cm
q
BT 1 0 0 1 0 16.82 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 /F4 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 570.6236 cm
+1 0 0 1 62.69291 441.4236 cm
q
q
1 0 0 1 0 0 cm
@@ -3352,23 +3848,23 @@ Q Q
Q
q
-1 0 0 1 62.69291 538.6236 cm
+1 0 0 1 62.69291 409.4236 cm
q
BT 1 0 0 1 0 16.82 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 /F4 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 520.6236 cm
+1 0 0 1 62.69291 391.4236 cm
q
0 0 0 rg
BT 1 0 0 1 0 4.82 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 514.6236 cm
+1 0 0 1 62.69291 385.4236 cm
Q
q
-1 0 0 1 62.69291 502.6236 cm
+1 0 0 1 62.69291 373.4236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
BT 1 0 0 1 0 2 Tm T* ET
@@ -3383,17 +3879,17 @@ q Q
Q
q
-1 0 0 1 62.69291 502.6236 cm
+1 0 0 1 62.69291 373.4236 cm
Q
q
-1 0 0 1 62.69291 484.6236 cm
+1 0 0 1 62.69291 355.4236 cm
q
0 0 0 rg
BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (Here is an example:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 415.4236 cm
+1 0 0 1 62.69291 286.2236 cm
q
q
1 0 0 1 0 0 cm
@@ -3414,32 +3910,32 @@ Q Q
Q
q
-1 0 0 1 62.69291 383.4236 cm
+1 0 0 1 62.69291 254.2236 cm
q
BT 1 0 0 1 0 16.82 Tm .18936 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that it non-interactive mode the runner just invokes ) Tj /F4 10 Tf (plac.call ) Tj /F1 10 Tf (on the ) Tj /F4 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 350.4236 cm
+1 0 0 1 62.69291 221.2236 cm
q
BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (A non class-based example) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 308.4236 cm
+1 0 0 1 62.69291 179.2236 cm
q
BT 1 0 0 1 0 28.82 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 /F4 10 Tf (.commands ) Tj /F1 10 Tf (attribute and possibly ) Tj /F4 10 Tf (__enter__) Tj T* 0 Tw /F1 10 Tf (and/or ) Tj /F4 10 Tf (__exit__ ) Tj /F1 10 Tf (attributes.) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 278.4236 cm
+1 0 0 1 62.69291 149.2236 cm
q
0 0 0 rg
BT 1 0 0 1 0 16.82 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 89.22362 cm
+1 0 0 1 62.69291 92.02362 cm
q
q
1 0 0 1 0 0 cm
@@ -3449,11 +3945,11 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 180 re B*
+n -6 -6 468.6898 48 re B*
Q
q
0 0 0 rg
-BT 1 0 0 1 0 161.71 Tm /F4 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* ET
+BT 1 0 0 1 0 29.71 Tm /F4 10 Tf 12 TL ("A Fake Version Control System") Tj T* T* (import plac) Tj T* ET
Q
Q
Q
@@ -3463,21 +3959,21 @@ q 1 0 0 1 56.69291 56.69291 cm
q
0 0 0 rg
-BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (11) Tj T* -235.3849 0 Td ET
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (12) Tj T* -235.3849 0 Td ET
Q
Q
endstream
endobj
-% 'R126': class PDFStream
-126 0 obj
+% 'R145': class PDFStream
+145 0 obj
% page stream
-<< /Length 3894 >>
+<< /Length 3414 >>
stream
1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
q
-1 0 0 1 62.69291 559.8236 cm
+1 0 0 1 62.69291 427.8236 cm
q
q
1 0 0 1 0 0 cm
@@ -3487,30 +3983,30 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 204 re B*
+n -6 -6 468.6898 336 re B*
Q
q
0 0 0 rg
-BT 1 0 0 1 0 185.71 Tm /F4 10 Tf 12 TL ( 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
+BT 1 0 0 1 0 317.71 Tm /F4 10 Tf 12 TL 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 515.8236 cm
+1 0 0 1 62.69291 383.8236 cm
q
BT 1 0 0 1 0 28.82 Tm .431318 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice that I have defined both an ) Tj /F4 10 Tf (__exit__ ) Tj /F1 10 Tf (hook and a ) Tj /F4 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 /F4 10 Tf (main = __import__\(__name__\)) Tj /F1 10 Tf (, which define ) Tj /F4 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 485.8236 cm
+1 0 0 1 62.69291 353.8236 cm
q
BT 1 0 0 1 0 16.82 Tm 1.259986 Tw 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F4 10 Tf (vcs ) Tj /F1 10 Tf (module does not contain an ) Tj /F4 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 /F4 10 Tf (plac vcs.py -h) Tj /F1 10 Tf (\):) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 344.6236 cm
+1 0 0 1 62.69291 212.6236 cm
q
q
1 0 0 1 0 0 cm
@@ -3531,13 +4027,13 @@ Q Q
Q
q
-1 0 0 1 62.69291 324.6236 cm
+1 0 0 1 62.69291 192.6236 cm
q
BT 1 0 0 1 0 4.82 Tm 12 TL /F1 10 Tf 0 0 0 rg (You can get help for the subcommands by postponing ) Tj /F4 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 207.4236 cm
+1 0 0 1 62.69291 99.42362 cm
q
q
1 0 0 1 0 0 cm
@@ -3547,31 +4043,35 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 108 re B*
+n -6 -6 468.6898 84 re B*
Q
q
0 0 0 rg
-BT 1 0 0 1 0 89.71 Tm /F4 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
+BT 1 0 0 1 0 65.71 Tm /F4 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* ET
Q
Q
Q
Q
Q
q
-1 0 0 1 62.69291 175.4236 cm
-q
-BT 1 0 0 1 0 16.82 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 /F4 10 Tf (-q) Tj /F1 10 Tf (.) Tj T* ET
-Q
-Q
-q
-1 0 0 1 62.69291 157.4236 cm
+1 0 0 1 56.69291 56.69291 cm
q
0 0 0 rg
-BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (Here is an example of a non-interactive session:) Tj T* ET
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (13) Tj T* -235.3849 0 Td ET
Q
Q
+
+endstream
+
+endobj
+% 'R146': class PDFStream
+146 0 obj
+% page stream
+<< /Length 5361 >>
+stream
+1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
q
-1 0 0 1 62.69291 100.2236 cm
+1 0 0 1 62.69291 727.8236 cm
q
q
1 0 0 1 0 0 cm
@@ -3581,35 +4081,31 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 48 re B*
+n -6 -6 468.6898 36 re B*
Q
q
0 0 0 rg
-BT 1 0 0 1 0 29.71 Tm /F4 10 Tf 12 TL ($ plac vcs.py check url) Tj T* (checkout) Tj T* (url) Tj T* ET
+BT 1 0 0 1 0 17.71 Tm /F4 10 Tf 12 TL ( -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 56.69291 56.69291 cm
+1 0 0 1 62.69291 695.8236 cm
+q
+BT 1 0 0 1 0 16.82 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 /F4 10 Tf (-q) Tj /F1 10 Tf (.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 677.8236 cm
q
0 0 0 rg
-BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (12) Tj T* -235.3849 0 Td ET
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (Here is an example of a non-interactive session:) Tj T* ET
Q
Q
-
-endstream
-
-endobj
-% 'R127': class PDFStream
-127 0 obj
-% page stream
-<< /Length 5650 >>
-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
+1 0 0 1 62.69291 548.6236 cm
q
q
1 0 0 1 0 0 cm
@@ -3619,25 +4115,25 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 84 re B*
+n -6 -6 468.6898 120 re B*
Q
q
0 0 0 rg
-BT 1 0 0 1 0 65.71 Tm /F4 10 Tf 12 TL ($ 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
+BT 1 0 0 1 0 101.71 Tm /F4 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 659.8236 cm
+1 0 0 1 62.69291 528.6236 cm
q
0 0 0 rg
BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (and here is an interactive session:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 458.6236 cm
+1 0 0 1 62.69291 327.4236 cm
q
q
1 0 0 1 0 0 cm
@@ -3657,38 +4153,38 @@ Q Q
Q
q
-1 0 0 1 62.69291 426.6236 cm
+1 0 0 1 62.69291 295.4236 cm
q
BT 1 0 0 1 0 16.82 Tm 2.986905 Tw 12 TL /F1 10 Tf 0 0 0 rg (Notice the invocation of the ) Tj /F4 10 Tf (__missing__ ) Tj /F1 10 Tf (hook for non-existing commands. Notice also that the) Tj T* 0 Tw /F4 10 Tf (__exit__ ) Tj /F1 10 Tf (hook gets called only in interactive mode.) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 396.6236 cm
+1 0 0 1 62.69291 265.4236 cm
q
0 0 0 rg
BT 1 0 0 1 0 16.82 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 363.6236 cm
+1 0 0 1 62.69291 232.4236 cm
q
BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Writing your own plac runner) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 309.6236 cm
+1 0 0 1 62.69291 178.4236 cm
q
BT 1 0 0 1 0 40.82 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 /F4 10 Tf (Interpreter ) Tj /F1 10 Tf (class is the ) Tj /F4 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 /F4 10 Tf (.str) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (.etype) Tj /F1 10 Tf (, ) Tj /F4 10 Tf (.exc ) Tj /F1 10 Tf (and ) Tj /F4 10 Tf (.tb) Tj /F1 10 Tf (:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 303.6236 cm
+1 0 0 1 62.69291 172.4236 cm
Q
q
-1 0 0 1 62.69291 303.6236 cm
+1 0 0 1 62.69291 172.4236 cm
Q
q
-1 0 0 1 62.69291 285.6236 cm
+1 0 0 1 62.69291 154.4236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -3708,13 +4204,13 @@ q Q
Q
q
-1 0 0 1 62.69291 285.6236 cm
+1 0 0 1 62.69291 154.4236 cm
Q
q
-1 0 0 1 62.69291 285.6236 cm
+1 0 0 1 62.69291 154.4236 cm
Q
q
-1 0 0 1 62.69291 267.6236 cm
+1 0 0 1 62.69291 136.4236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -3734,13 +4230,13 @@ q Q
Q
q
-1 0 0 1 62.69291 267.6236 cm
+1 0 0 1 62.69291 136.4236 cm
Q
q
-1 0 0 1 62.69291 267.6236 cm
+1 0 0 1 62.69291 136.4236 cm
Q
q
-1 0 0 1 62.69291 249.6236 cm
+1 0 0 1 62.69291 118.4236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -3760,13 +4256,13 @@ q Q
Q
q
-1 0 0 1 62.69291 249.6236 cm
+1 0 0 1 62.69291 118.4236 cm
Q
q
-1 0 0 1 62.69291 249.6236 cm
+1 0 0 1 62.69291 118.4236 cm
Q
q
-1 0 0 1 62.69291 231.6236 cm
+1 0 0 1 62.69291 100.4236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -3786,25 +4282,75 @@ q Q
Q
q
-1 0 0 1 62.69291 231.6236 cm
+1 0 0 1 62.69291 100.4236 cm
Q
q
-1 0 0 1 62.69291 231.6236 cm
+1 0 0 1 62.69291 100.4236 cm
Q
q
-1 0 0 1 62.69291 189.6236 cm
+1 0 0 1 56.69291 56.69291 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (14) Tj T* -235.3849 0 Td ET
+Q
+Q
+
+endstream
+
+endobj
+% 'R147': class PDFStream
+147 0 obj
+% page stream
+<< /Length 4689 >>
+stream
+1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 729.0236 cm
q
BT 1 0 0 1 0 28.82 Tm .937485 Tw 12 TL /F1 10 Tf 0 0 0 rg (Moreover the ) Tj /F4 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 171.6236 cm
+1 0 0 1 62.69291 711.0236 cm
q
BT 1 0 0 1 0 4.82 Tm 12 TL /F1 10 Tf 0 0 0 rg (For instance, if you send a mispelled option to the interpreter a ) Tj /F4 10 Tf (SystemExit ) Tj /F1 10 Tf (will be trapped:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 90.42362 cm
+1 0 0 1 62.69291 617.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 65.71 Tm 12 TL /F4 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 585.8236 cm
+q
+BT 1 0 0 1 0 16.82 Tm 2.90561 Tw 12 TL /F1 10 Tf 0 0 0 rg (It is important to invoke the ) Tj /F4 10 Tf (.send ) Tj /F1 10 Tf (method inside the context manager, otherwise you will get a) Tj T* 0 Tw /F4 10 Tf (RuntimeError) Tj /F1 10 Tf (.) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 543.8236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 28.82 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 306.6236 cm
q
q
1 0 0 1 0 0 cm
@@ -3814,34 +4360,87 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 72 re B*
+n -6 -6 468.6898 228 re B*
Q
q
-BT 1 0 0 1 0 53.71 Tm 12 TL /F4 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* ET
+0 0 0 rg
+BT 1 0 0 1 0 209.71 Tm /F4 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 62.69291 274.6236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 16.82 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 220.6236 cm
+q
+BT 1 0 0 1 0 40.82 Tm 2.090651 Tw 12 TL /F1 10 Tf 0 0 0 rg (There is a final ) Tj /F5 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 /F4 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 163.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 29.71 Tm /F4 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 131.4236 cm
+q
+BT 1 0 0 1 0 16.82 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 56.69291 56.69291 cm
q
0 0 0 rg
-BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (13) Tj T* -235.3849 0 Td ET
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (15) Tj T* -235.3849 0 Td ET
Q
Q
endstream
endobj
-% 'R128': class PDFStream
-128 0 obj
+% 'R148': class PDFStream
+148 0 obj
% page stream
-<< /Length 6119 >>
+<< /Length 4115 >>
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
+1 0 0 1 62.69291 744.0236 cm
+q
+BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Long running commands) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 702.0236 cm
+q
+BT 1 0 0 1 0 28.82 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 440.8236 cm
q
q
1 0 0 1 0 0 cm
@@ -3851,30 +4450,135 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 24 re B*
+n -6 -6 468.6898 252 re B*
Q
q
-BT 1 0 0 1 0 5.71 Tm 12 TL /F4 10 Tf 0 0 0 rg (SystemExit: unrecognized arguments: .cler) Tj T* ET
+0 0 0 rg
+BT 1 0 0 1 0 233.71 Tm /F4 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\(plac.call\(FakeImporter\)\).interact\(\)) Tj T* ET
Q
Q
Q
Q
Q
q
-1 0 0 1 62.69291 707.8236 cm
+1 0 0 1 62.69291 408.8236 cm
q
-BT 1 0 0 1 0 16.82 Tm 2.90561 Tw 12 TL /F1 10 Tf 0 0 0 rg (It is important to invoke the ) Tj /F4 10 Tf (.send ) Tj /F1 10 Tf (method inside the context manager, otherwise you will get a) Tj T* 0 Tw /F4 10 Tf (RuntimeError) Tj /F1 10 Tf (.) Tj T* ET
+BT 1 0 0 1 0 16.82 Tm 1.466457 Tw 12 TL /F1 10 Tf 0 0 0 rg (If you run the ) Tj /F4 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 665.8236 cm
+1 0 0 1 62.69291 279.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 101.71 Tm 12 TL /F4 10 Tf 0 0 0 rg ($ python importer1.py dsn) Tj T* (A fake importer with an import_file command) Tj T* (i) Tj (>) Tj ( import_file file1) Tj T* (Imported 100 lines) Tj T* (Imported 200 lines) Tj T* (Imported 300 lines) Tj T* (... ) Tj (<) Tj (wait 3+ minutes) Tj (>) 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 235.6236 cm
+q
+BT 1 0 0 1 0 28.82 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 202.6236 cm
+q
+BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Threaded commands) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 172.6236 cm
q
0 0 0 rg
-BT 1 0 0 1 0 28.82 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
+BT 1 0 0 1 0 16.82 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 428.6236 cm
+1 0 0 1 62.69291 166.6236 cm
+Q
+q
+1 0 0 1 62.69291 154.6236 cm
+0 0 0 rg
+BT /F3 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm T* ET
+q
+1 0 0 1 20 0 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 5.71 Tm /F4 10 Tf 12 TL (commands = ['import_file']) Tj T* ET
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 154.6236 cm
+Q
+q
+1 0 0 1 62.69291 136.6236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (with) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 130.6236 cm
+Q
+q
+1 0 0 1 62.69291 118.6236 cm
+0 0 0 rg
+BT /F3 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm T* ET
+q
+1 0 0 1 20 0 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 5.71 Tm /F4 10 Tf 12 TL (thcommands = ['import_file']) Tj T* ET
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 118.6236 cm
+Q
+q
+1 0 0 1 56.69291 56.69291 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (16) Tj T* -235.3849 0 Td ET
+Q
+Q
+
+endstream
+
+endobj
+% 'R149': class PDFStream
+149 0 obj
+% page stream
+<< /Length 4990 >>
+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 16.82 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 /F4 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 695.8236 cm
q
q
1 0 0 1 0 0 cm
@@ -3884,31 +4588,130 @@ q .662745 .662745 .662745 RG
.5 w
.960784 .960784 .862745 rg
-n -6 -6 468.6898 228 re B*
+n -6 -6 468.6898 36 re B*
Q
q
+BT 1 0 0 1 0 17.71 Tm 12 TL /F4 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 663.8236 cm
+q
+BT 1 0 0 1 0 16.82 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 /F4 10 Tf (.output) Tj /F1 10 Tf (:) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 594.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 41.71 Tm 12 TL /F4 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 574.6236 cm
+q
0 0 0 rg
-BT 1 0 0 1 0 209.71 Tm /F4 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
+BT 1 0 0 1 0 4.82 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 481.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 65.71 Tm 12 TL /F4 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 396.6236 cm
+1 0 0 1 62.69291 461.4236 cm
q
0 0 0 rg
-BT 1 0 0 1 0 16.82 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
+BT 1 0 0 1 0 4.82 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 342.6236 cm
+1 0 0 1 62.69291 416.2236 cm
q
-BT 1 0 0 1 0 40.82 Tm 2.090651 Tw 12 TL /F1 10 Tf 0 0 0 rg (There is a final ) Tj /F5 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 /F4 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
+1 0 0 1 0 0 cm
+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 17.71 Tm 12 TL /F4 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 285.4236 cm
+1 0 0 1 62.69291 396.2236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 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 327.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 60 re B*
+Q
+q
+BT 1 0 0 1 0 41.71 Tm 12 TL /F4 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 307.0236 cm
+q
+BT 1 0 0 1 0 4.82 Tm 12 TL /F1 10 Tf 0 0 0 rg (The ) Tj /F4 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 249.8236 cm
q
q
1 0 0 1 0 0 cm
@@ -3921,39 +4724,240 @@ q n -6 -6 468.6898 48 re B*
Q
q
+BT 1 0 0 1 0 29.71 Tm 12 TL /F4 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 229.8236 cm
+q
0 0 0 rg
-BT 1 0 0 1 0 29.71 Tm /F4 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
+BT 1 0 0 1 0 4.82 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 136.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 84 re B*
+Q
+q
+BT 1 0 0 1 0 65.71 Tm 12 TL /F4 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 253.4236 cm
+1 0 0 1 62.69291 92.62362 cm
q
-BT 1 0 0 1 0 16.82 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
+BT 1 0 0 1 0 28.82 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 /F4 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 /F4 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 ET
+Q
+Q
+q
+1 0 0 1 56.69291 56.69291 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (17) Tj T* -235.3849 0 Td ET
+Q
+Q
+
+endstream
+
+endobj
+% 'R150': class PDFStream
+150 0 obj
+% page stream
+<< /Length 4477 >>
+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 40.82 Tm 2.578443 Tw 12 TL /F4 10 Tf 0 0 0 rg (TOBEKILLED ) Tj /F1 10 Tf (a ) Tj /F4 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 /F4 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 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 276 re B*
+Q
+q
+0 0 0 rg
+BT 1 0 0 1 0 257.71 Tm /F4 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\(plac.call\(FakeImporter\)\).interact\(\)) Tj T* ET
+Q
+Q
+Q
+Q
+Q
+q
+1 0 0 1 62.69291 398.8236 cm
+q
+BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Running commands as external processes) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 220.4236 cm
+1 0 0 1 62.69291 344.8236 cm
+q
+BT 1 0 0 1 0 40.82 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 326.8236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 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 320.8236 cm
+Q
+q
+1 0 0 1 62.69291 308.8236 cm
+0 0 0 rg
+BT /F3 10 Tf 12 TL ET
+BT 1 0 0 1 0 2 Tm T* ET
+q
+1 0 0 1 20 0 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 5.71 Tm /F4 10 Tf 12 TL (thcommands = ['import_file']) Tj T* ET
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 308.8236 cm
+Q
+q
+1 0 0 1 62.69291 290.8236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (with) Tj T* ET
+Q
+Q
+q
+1 0 0 1 62.69291 284.8236 cm
+Q
+q
+1 0 0 1 62.69291 272.8236 cm
+0 0 0 rg
+BT /F3 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 4.82 Tm 12 TL /F4 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 272.8236 cm
+Q
+q
+1 0 0 1 62.69291 242.8236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 16.82 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 101.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
+BT 1 0 0 1 0 113.71 Tm 12 TL /F4 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 56.69291 56.69291 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (18) Tj T* -235.3849 0 Td ET
+Q
+Q
+
+endstream
+
+endobj
+% 'R151': class PDFStream
+151 0 obj
+% page stream
+<< /Length 7386 >>
+stream
+1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
+q
+1 0 0 1 62.69291 717.0236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 40.82 Tm /F1 10 Tf 12 TL 1.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 663.0236 cm
+q
+BT 1 0 0 1 0 40.82 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 /F4 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 /F4 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 633.0236 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 16.82 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 600.0236 cm
q
BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Summary) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 178.4236 cm
+1 0 0 1 62.69291 558.0236 cm
q
BT 1 0 0 1 0 28.82 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 172.4236 cm
+1 0 0 1 62.69291 552.0236 cm
Q
q
-1 0 0 1 62.69291 172.4236 cm
+1 0 0 1 62.69291 552.0236 cm
Q
q
-1 0 0 1 62.69291 154.4236 cm
+1 0 0 1 62.69291 534.0236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -3973,13 +4977,13 @@ q Q
Q
q
-1 0 0 1 62.69291 154.4236 cm
+1 0 0 1 62.69291 534.0236 cm
Q
q
-1 0 0 1 62.69291 154.4236 cm
+1 0 0 1 62.69291 534.0236 cm
Q
q
-1 0 0 1 62.69291 94.42362 cm
+1 0 0 1 62.69291 474.0236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
@@ -4057,107 +5061,116 @@ q Q
Q
q
-1 0 0 1 62.69291 94.42362 cm
+1 0 0 1 62.69291 474.0236 cm
Q
q
-1 0 0 1 62.69291 94.42362 cm
+1 0 0 1 62.69291 474.0236 cm
Q
q
-1 0 0 1 56.69291 56.69291 cm
+1 0 0 1 62.69291 444.0236 cm
+0 0 0 rg
+BT /F3 10 Tf 12 TL ET
+q
+1 0 0 1 6 15 cm
q
0 0 0 rg
-BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (14) Tj T* -235.3849 0 Td ET
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 5.66 0 Td (3.) Tj T* -5.66 0 Td ET
Q
Q
-
-endstream
-
-endobj
-% 'R129': class PDFStream
-129 0 obj
-% page stream
-<< /Length 4712 >>
-stream
-1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
q
-1 0 0 1 62.69291 735.0236 cm
+1 0 0 1 23 3 cm
+q
+BT 1 0 0 1 0 16.82 Tm 5.126647 Tw 12 TL /F1 10 Tf 0 0 0 rg (for testing call the ) Tj /F4 10 Tf (Interpreter.check ) Tj /F1 10 Tf (method in the appropriate context or use the) Tj T* 0 Tw /F4 10 Tf (Interpreter.doctest ) Tj /F1 10 Tf (feature;) Tj T* ET
+Q
+Q
+q
+Q
+Q
+q
+1 0 0 1 62.69291 444.0236 cm
+Q
+q
+1 0 0 1 62.69291 444.0236 cm
+Q
+q
+1 0 0 1 62.69291 414.0236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
1 0 0 1 6 15 cm
q
0 0 0 rg
-BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 5.66 0 Td (3.) Tj T* -5.66 0 Td ET
+BT 1 0 0 1 0 4.82 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 16.82 Tm 5.126647 Tw 12 TL /F1 10 Tf 0 0 0 rg (for testing call the ) Tj /F4 10 Tf (Interpreter.check ) Tj /F1 10 Tf (method in the appropriate context or use the) Tj T* 0 Tw /F4 10 Tf (Interpreter.doctest ) Tj /F1 10 Tf (feature;) Tj T* ET
+BT 1 0 0 1 0 16.82 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 /F4 10 Tf (Interpreter.send ) Tj /F1 10 Tf (method which) Tj T* 0 Tw (returns a \(finished\) ) Tj /F4 10 Tf (Task ) Tj /F1 10 Tf (object.) Tj T* ET
Q
Q
q
Q
Q
q
-1 0 0 1 62.69291 735.0236 cm
+1 0 0 1 62.69291 414.0236 cm
Q
q
-1 0 0 1 62.69291 735.0236 cm
+1 0 0 1 62.69291 414.0236 cm
Q
q
-1 0 0 1 62.69291 717.0236 cm
+1 0 0 1 62.69291 384.0236 cm
0 0 0 rg
BT /F3 10 Tf 12 TL ET
q
-1 0 0 1 6 3 cm
+1 0 0 1 6 15 cm
q
0 0 0 rg
-BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 5.66 0 Td (4.) Tj T* -5.66 0 Td ET
+BT 1 0 0 1 0 4.82 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 4.82 Tm 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 /F4 10 Tf (Interpreter.send ) Tj /F1 10 Tf (method.) Tj T* ET
+BT 1 0 0 1 0 16.82 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 /F4 10 Tf (thcommands ) Tj /F1 10 Tf (and ) Tj /F4 10 Tf (mpcommands ) Tj /F1 10 Tf (respectively.) Tj T* ET
Q
Q
q
Q
Q
q
-1 0 0 1 62.69291 717.0236 cm
+1 0 0 1 62.69291 384.0236 cm
Q
q
-1 0 0 1 62.69291 717.0236 cm
+1 0 0 1 62.69291 384.0236 cm
Q
q
-1 0 0 1 62.69291 699.0236 cm
+1 0 0 1 62.69291 366.0236 cm
q
BT 1 0 0 1 0 4.82 Tm 12 TL /F1 10 Tf 0 0 0 rg (Moreover, remember that ) Tj /F4 10 Tf (plac_runner.py ) Tj /F1 10 Tf (is your friend.) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 666.0236 cm
+1 0 0 1 62.69291 333.0236 cm
q
BT 1 0 0 1 0 8.435 Tm 21 TL /F2 17.5 Tf 0 0 0 rg (Appendix: custom annotation objects) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 636.0236 cm
+1 0 0 1 62.69291 303.0236 cm
q
BT 1 0 0 1 0 16.82 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 /F4 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 /F4 10 Tf (help, kind, short, type, choices, metavar) Tj /F1 10 Tf (.) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 606.0236 cm
+1 0 0 1 62.69291 273.0236 cm
q
0 0 0 rg
BT 1 0 0 1 0 16.82 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 476.8236 cm
+1 0 0 1 62.69291 143.8236 cm
q
q
1 0 0 1 0 0 cm
@@ -4178,14 +5191,31 @@ Q Q
Q
q
-1 0 0 1 62.69291 456.8236 cm
+1 0 0 1 62.69291 123.8236 cm
q
0 0 0 rg
BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (You can use such annotations objects as follows:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 279.6236 cm
+1 0 0 1 56.69291 56.69291 cm
+q
+0 0 0 rg
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (19) Tj T* -235.3849 0 Td ET
+Q
+Q
+
+endstream
+
+endobj
+% 'R152': class PDFStream
+152 0 obj
+% page stream
+<< /Length 2033 >>
+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
@@ -4206,14 +5236,14 @@ Q Q
Q
q
-1 0 0 1 62.69291 259.6236 cm
+1 0 0 1 62.69291 575.8236 cm
q
0 0 0 rg
BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (Here is the usage message you get:) Tj T* ET
Q
Q
q
-1 0 0 1 62.69291 130.4236 cm
+1 0 0 1 62.69291 446.6236 cm
q
q
1 0 0 1 0 0 cm
@@ -4234,336 +5264,370 @@ Q Q
Q
q
-1 0 0 1 62.69291 98.42362 cm
+1 0 0 1 62.69291 402.6236 cm
q
-BT 1 0 0 1 0 16.82 Tm .713516 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can go on and define ) Tj /F4 10 Tf (Option ) Tj /F1 10 Tf (and ) Tj /F4 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 ET
+BT 1 0 0 1 0 28.82 Tm .713516 Tw 12 TL /F1 10 Tf 0 0 0 rg (You can go on and define ) Tj /F4 10 Tf (Option ) Tj /F1 10 Tf (and ) Tj /F4 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 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (15) Tj T* -235.3849 0 Td ET
+BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (20) Tj T* -235.3849 0 Td ET
Q
Q
endstream
endobj
-% 'R130': class PDFStream
-130 0 obj
-% page stream
-<< /Length 379 >>
-stream
-1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
-q
-1 0 0 1 62.69291 753.0236 cm
-q
-0 0 0 rg
-BT 1 0 0 1 0 4.82 Tm /F1 10 Tf 12 TL (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 4.82 Tm /F1 10 Tf 12 TL 235.3849 0 Td (16) Tj T* -235.3849 0 Td ET
-Q
-Q
-
-endstream
-
-endobj
-% 'R131': class PDFPageLabels
-131 0 obj
+% 'R153': class PDFPageLabels
+153 0 obj
% Document Root
<< /Nums [ 0
- 132 0 R
+ 154 0 R
1
- 133 0 R
+ 155 0 R
2
- 134 0 R
+ 156 0 R
3
- 135 0 R
+ 157 0 R
4
- 136 0 R
+ 158 0 R
5
- 137 0 R
+ 159 0 R
6
- 138 0 R
+ 160 0 R
7
- 139 0 R
+ 161 0 R
8
- 140 0 R
+ 162 0 R
9
- 141 0 R
+ 163 0 R
10
- 142 0 R
+ 164 0 R
11
- 143 0 R
+ 165 0 R
12
- 144 0 R
+ 166 0 R
13
- 145 0 R
+ 167 0 R
14
- 146 0 R
+ 168 0 R
15
- 147 0 R ] >>
-endobj
-% 'R132': class PDFPageLabel
-132 0 obj
+ 169 0 R
+ 16
+ 170 0 R
+ 17
+ 171 0 R
+ 18
+ 172 0 R
+ 19
+ 173 0 R ] >>
+endobj
+% 'R154': class PDFPageLabel
+154 0 obj
% None
<< /S /D
/St 1 >>
endobj
-% 'R133': class PDFPageLabel
-133 0 obj
+% 'R155': class PDFPageLabel
+155 0 obj
% None
<< /S /D
/St 2 >>
endobj
-% 'R134': class PDFPageLabel
-134 0 obj
+% 'R156': class PDFPageLabel
+156 0 obj
% None
<< /S /D
/St 3 >>
endobj
-% 'R135': class PDFPageLabel
-135 0 obj
+% 'R157': class PDFPageLabel
+157 0 obj
% None
<< /S /D
/St 4 >>
endobj
-% 'R136': class PDFPageLabel
-136 0 obj
+% 'R158': class PDFPageLabel
+158 0 obj
% None
<< /S /D
/St 5 >>
endobj
-% 'R137': class PDFPageLabel
-137 0 obj
+% 'R159': class PDFPageLabel
+159 0 obj
% None
<< /S /D
/St 6 >>
endobj
-% 'R138': class PDFPageLabel
-138 0 obj
+% 'R160': class PDFPageLabel
+160 0 obj
% None
<< /S /D
/St 7 >>
endobj
-% 'R139': class PDFPageLabel
-139 0 obj
+% 'R161': class PDFPageLabel
+161 0 obj
% None
<< /S /D
/St 8 >>
endobj
-% 'R140': class PDFPageLabel
-140 0 obj
+% 'R162': class PDFPageLabel
+162 0 obj
% None
<< /S /D
/St 9 >>
endobj
-% 'R141': class PDFPageLabel
-141 0 obj
+% 'R163': class PDFPageLabel
+163 0 obj
% None
<< /S /D
/St 10 >>
endobj
-% 'R142': class PDFPageLabel
-142 0 obj
+% 'R164': class PDFPageLabel
+164 0 obj
% None
<< /S /D
/St 11 >>
endobj
-% 'R143': class PDFPageLabel
-143 0 obj
+% 'R165': class PDFPageLabel
+165 0 obj
% None
<< /S /D
/St 12 >>
endobj
-% 'R144': class PDFPageLabel
-144 0 obj
+% 'R166': class PDFPageLabel
+166 0 obj
% None
<< /S /D
/St 13 >>
endobj
-% 'R145': class PDFPageLabel
-145 0 obj
+% 'R167': class PDFPageLabel
+167 0 obj
% None
<< /S /D
/St 14 >>
endobj
-% 'R146': class PDFPageLabel
-146 0 obj
+% 'R168': class PDFPageLabel
+168 0 obj
% None
<< /S /D
/St 15 >>
endobj
-% 'R147': class PDFPageLabel
-147 0 obj
+% 'R169': class PDFPageLabel
+169 0 obj
% None
<< /S /D
/St 16 >>
endobj
+% 'R170': class PDFPageLabel
+170 0 obj
+% None
+<< /S /D
+ /St 17 >>
+endobj
+% 'R171': class PDFPageLabel
+171 0 obj
+% None
+<< /S /D
+ /St 18 >>
+endobj
+% 'R172': class PDFPageLabel
+172 0 obj
+% None
+<< /S /D
+ /St 19 >>
+endobj
+% 'R173': class PDFPageLabel
+173 0 obj
+% None
+<< /S /D
+ /St 20 >>
+endobj
xref
-0 148
+0 174
0000000000 65535 f
0000000113 00000 n
-0000000270 00000 n
-0000000435 00000 n
-0000000610 00000 n
-0000000791 00000 n
-0000001043 00000 n
-0000001293 00000 n
-0000001551 00000 n
-0000001712 00000 n
-0000001906 00000 n
-0000002148 00000 n
-0000002390 00000 n
-0000002632 00000 n
-0000002874 00000 n
-0000003116 00000 n
-0000003359 00000 n
-0000003602 00000 n
-0000003845 00000 n
-0000004088 00000 n
-0000004331 00000 n
-0000004574 00000 n
-0000004803 00000 n
-0000004989 00000 n
-0000005232 00000 n
-0000005475 00000 n
-0000005718 00000 n
-0000005961 00000 n
-0000006204 00000 n
-0000006447 00000 n
-0000006690 00000 n
-0000006933 00000 n
-0000007176 00000 n
-0000007419 00000 n
-0000007662 00000 n
-0000007904 00000 n
-0000008156 00000 n
-0000008408 00000 n
-0000008660 00000 n
-0000008912 00000 n
-0000009164 00000 n
-0000009407 00000 n
-0000009644 00000 n
-0000010256 00000 n
-0000010513 00000 n
-0000010831 00000 n
-0000011088 00000 n
-0000011330 00000 n
-0000011642 00000 n
-0000011939 00000 n
-0000012191 00000 n
-0000012443 00000 n
-0000012695 00000 n
-0000012955 00000 n
-0000013207 00000 n
-0000013468 00000 n
-0000013720 00000 n
-0000013981 00000 n
-0000014218 00000 n
-0000014608 00000 n
-0000014859 00000 n
-0000015095 00000 n
-0000015422 00000 n
-0000015694 00000 n
-0000015946 00000 n
-0000016183 00000 n
-0000016519 00000 n
-0000016771 00000 n
-0000017028 00000 n
-0000017265 00000 n
-0000017601 00000 n
-0000017860 00000 n
-0000018112 00000 n
-0000018364 00000 n
-0000018623 00000 n
-0000018882 00000 n
-0000019134 00000 n
-0000019391 00000 n
-0000019643 00000 n
-0000019902 00000 n
-0000020161 00000 n
-0000020413 00000 n
-0000020665 00000 n
-0000020917 00000 n
-0000021169 00000 n
-0000021407 00000 n
-0000021837 00000 n
-0000022134 00000 n
-0000022384 00000 n
-0000022622 00000 n
-0000022935 00000 n
-0000023232 00000 n
-0000023469 00000 n
-0000023787 00000 n
-0000024058 00000 n
-0000024296 00000 n
-0000024623 00000 n
-0000024861 00000 n
-0000025165 00000 n
-0000025448 00000 n
-0000025611 00000 n
-0000025891 00000 n
-0000026020 00000 n
-0000026196 00000 n
-0000026416 00000 n
-0000026622 00000 n
-0000026817 00000 n
-0000027015 00000 n
-0000027217 00000 n
-0000027411 00000 n
-0000027606 00000 n
-0000027811 00000 n
-0000028020 00000 n
-0000028208 00000 n
-0000028392 00000 n
-0000028638 00000 n
-0000037365 00000 n
-0000041701 00000 n
-0000045945 00000 n
-0000052177 00000 n
-0000058154 00000 n
-0000062905 00000 n
-0000066988 00000 n
-0000071475 00000 n
-0000076675 00000 n
-0000080981 00000 n
-0000086431 00000 n
-0000090428 00000 n
-0000096181 00000 n
-0000102403 00000 n
-0000107218 00000 n
-0000107703 00000 n
-0000108015 00000 n
-0000108094 00000 n
-0000108173 00000 n
-0000108252 00000 n
-0000108331 00000 n
-0000108410 00000 n
-0000108489 00000 n
-0000108568 00000 n
-0000108647 00000 n
-0000108726 00000 n
-0000108806 00000 n
-0000108886 00000 n
-0000108966 00000 n
-0000109046 00000 n
-0000109126 00000 n
-0000109206 00000 n
+0000000257 00000 n
+0000000422 00000 n
+0000000597 00000 n
+0000000778 00000 n
+0000001030 00000 n
+0000001280 00000 n
+0000001538 00000 n
+0000001699 00000 n
+0000001893 00000 n
+0000002135 00000 n
+0000002377 00000 n
+0000002619 00000 n
+0000002861 00000 n
+0000003103 00000 n
+0000003346 00000 n
+0000003589 00000 n
+0000003832 00000 n
+0000004075 00000 n
+0000004318 00000 n
+0000004561 00000 n
+0000004804 00000 n
+0000005047 00000 n
+0000005290 00000 n
+0000005533 00000 n
+0000005776 00000 n
+0000006019 00000 n
+0000006262 00000 n
+0000006505 00000 n
+0000006748 00000 n
+0000006992 00000 n
+0000007236 00000 n
+0000007480 00000 n
+0000007724 00000 n
+0000007968 00000 n
+0000008212 00000 n
+0000008456 00000 n
+0000008700 00000 n
+0000008944 00000 n
+0000009187 00000 n
+0000009439 00000 n
+0000009691 00000 n
+0000009943 00000 n
+0000010195 00000 n
+0000010432 00000 n
+0000011080 00000 n
+0000011323 00000 n
+0000011575 00000 n
+0000011832 00000 n
+0000012168 00000 n
+0000012405 00000 n
+0000012708 00000 n
+0000013005 00000 n
+0000013257 00000 n
+0000013509 00000 n
+0000013761 00000 n
+0000014021 00000 n
+0000014273 00000 n
+0000014534 00000 n
+0000014785 00000 n
+0000015046 00000 n
+0000015298 00000 n
+0000015535 00000 n
+0000015934 00000 n
+0000016185 00000 n
+0000016421 00000 n
+0000016748 00000 n
+0000017020 00000 n
+0000017272 00000 n
+0000017524 00000 n
+0000017761 00000 n
+0000018106 00000 n
+0000018358 00000 n
+0000018610 00000 n
+0000018854 00000 n
+0000019190 00000 n
+0000019427 00000 n
+0000019745 00000 n
+0000020001 00000 n
+0000020253 00000 n
+0000020505 00000 n
+0000020757 00000 n
+0000021016 00000 n
+0000021275 00000 n
+0000021525 00000 n
+0000021776 00000 n
+0000022026 00000 n
+0000022278 00000 n
+0000022530 00000 n
+0000022768 00000 n
+0000023185 00000 n
+0000023437 00000 n
+0000023676 00000 n
+0000024003 00000 n
+0000024253 00000 n
+0000024491 00000 n
+0000024804 00000 n
+0000025101 00000 n
+0000025338 00000 n
+0000025656 00000 n
+0000025914 00000 n
+0000026234 00000 n
+0000026487 00000 n
+0000026726 00000 n
+0000027056 00000 n
+0000027295 00000 n
+0000027615 00000 n
+0000027868 00000 n
+0000028126 00000 n
+0000028456 00000 n
+0000028709 00000 n
+0000028948 00000 n
+0000029264 00000 n
+0000029549 00000 n
+0000029713 00000 n
+0000029967 00000 n
+0000030096 00000 n
+0000030272 00000 n
+0000030492 00000 n
+0000030698 00000 n
+0000030893 00000 n
+0000031091 00000 n
+0000031295 00000 n
+0000031491 00000 n
+0000031686 00000 n
+0000031891 00000 n
+0000032100 00000 n
+0000032303 00000 n
+0000032502 00000 n
+0000032722 00000 n
+0000032911 00000 n
+0000033096 00000 n
+0000033384 00000 n
+0000042533 00000 n
+0000047132 00000 n
+0000051686 00000 n
+0000057943 00000 n
+0000063827 00000 n
+0000068719 00000 n
+0000073410 00000 n
+0000077590 00000 n
+0000081508 00000 n
+0000086672 00000 n
+0000091828 00000 n
+0000097407 00000 n
+0000100924 00000 n
+0000106388 00000 n
+0000111180 00000 n
+0000115398 00000 n
+0000120491 00000 n
+0000125071 00000 n
+0000132560 00000 n
+0000134700 00000 n
+0000135072 00000 n
+0000135151 00000 n
+0000135230 00000 n
+0000135309 00000 n
+0000135388 00000 n
+0000135467 00000 n
+0000135546 00000 n
+0000135625 00000 n
+0000135704 00000 n
+0000135783 00000 n
+0000135863 00000 n
+0000135943 00000 n
+0000136023 00000 n
+0000136103 00000 n
+0000136183 00000 n
+0000136263 00000 n
+0000136343 00000 n
+0000136423 00000 n
+0000136503 00000 n
+0000136583 00000 n
trailer
<< /ID
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
- [(\275o9\206\006\260\252\254St*\243\362\376\031\013) (\275o9\206\006\260\252\254St*\243\362\376\031\013)]
+ [(\317\011\333\344\342}}2\233\022:\30301\344\211) (\317\011\333\344\342}}2\233\022:\30301\344\211)]
- /Info 100 0 R
- /Root 99 0 R
- /Size 148 >>
+ /Info 115 0 R
+ /Root 114 0 R
+ /Size 174 >>
startxref
-109255
+136632
%%EOF
diff --git a/plac/doc/plac_adv.txt b/plac/doc/plac_adv.txt index d8a421c..9ff1dab 100644 --- a/plac/doc/plac_adv.txt +++ b/plac/doc/plac_adv.txt @@ -1,9 +1,9 @@ -Testing and scripting your applications with plac +Advanced usages of plac ========================================================= :Author: Michele Simionato :E-mail: michele.simionato@gmail.com -:Date: June 2010 +:Date: July 2010 :Download page: http://pypi.python.org/pypi/plac :Project page: http://micheles.googlecode.com/hg/plac/doc/plac.html :Installation: ``easy_install -U plac`` @@ -11,7 +11,9 @@ Testing and scripting your applications with plac :Requires: Python 2.5+ *The present document discusses a few of the advanced use -cases for plac. It assumes you have already read an understood the +cases for plac. It shows how to write interactive and non-interactive +interpreters with plac, and how to use plac for testing and scripting a generic +application. It assumes you have already read an understood the basic documentation.* .. contents:: @@ -77,13 +79,14 @@ and non-interactively:: $ python shelve_interpreter.py .clear # non-interactive use cleared the shelve -Here is an usage session, using rlwrap_ to enable readline features -(rlwrap_ is available in Unix-like systems):: +Here is an usage session:: - $ rlwrap python shelve_interpreter.py -i # interactive use - usage: shelve_interpreter.py [.help] [.showall] [.clear] [.delete DELETE] - [.filename /home/micheles/conf.shelve] - [params [params ...]] [setters [setters ...]] + $ 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 @@ -106,20 +109,22 @@ 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 message displayed -by default is the argparse-provided usage message, but can be -customized by setting an ``.intro`` attribute on the main function. +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``. -Notice that ``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. - -You can conveniently test your application in interactive mode. -However manual testing is a poor substitute for automatic testing. +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: @@ -143,8 +148,7 @@ the ``check`` method of ``Interpreter`` objects: 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. - -``AssertionError`` is catched by tools like ``py.test`` and +Notice that ``AssertionError`` is catched by tools like ``py.test`` and ``nosetests`` and actually ``plac`` tests are intended to be run with such tools. @@ -175,7 +179,7 @@ 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! +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): @@ -197,28 +201,30 @@ You can test ``ishelve.placet`` file by calling the 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. The -easy tests supported by ``plac`` are *not* unittests: they should be -used to model user interaction when the order of the operations -matters. Since the single subtests in a ``.placet`` file are not -independent, 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 +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 ``#``), continuation lines, escape sequences and -more. Look at the shlex_ documentation if you need to customize how -the language is interpreted. +comment character is ``#``), escape sequences and more. Look at the +shlex_ documentation if you need to customize how the language is +interpreted. 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 (plac easy tests does not need to be in +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). +strings corresponding to the lines of the file. -At the present plac easy tests do not use any code from the doctest +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). @@ -243,7 +249,7 @@ or ``py.test`` as follow:: 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 shabng line. You can use both the full path name of the +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 @@ -267,15 +273,15 @@ background in case of unexpected errors. The implementation of interpreters *wrap the errors, but does not eat them*: the errors are always accessible and can be re-raised on demand. -In particular consider the following batch file, which contains a syntax -error (``.dl`` instead of ``.del``): +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 raise a ``SystemExit`` -with an appropriated error message at the ``.dl`` line and the last command -will *not* be executed:: +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)" @@ -290,20 +296,23 @@ will *not* be executed:: i> .del a deleted a i> .dl b - unrecognized arguments: .dl + 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?). -Containers of commands +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 better. Here I will substantiate my claim. +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 @@ -314,9 +323,10 @@ 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. -Using this feature the shelve interface can be rewritten in a more -object-oriented way as follows: +Rhe shelve interface can be rewritten in an object-oriented way as follows: .. include:: ishelve2.py :literal: @@ -333,14 +343,31 @@ 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:: - $ rlwrap python ishelve2.py - Operating on /home/micheles/conf.shelve. Available commands: - set - show - showall - delete + $ python ishelve2.py + A minimal interface over a shelve object. + Operating on /home/micheles/conf.shelve. + .help to see the available commands. + i> .help + + special commands + ================ + .help .last_tb + + custom commands + =============== + delete set show showall + i> delete deleting everything i> set a pippo @@ -358,75 +385,108 @@ Here is a session of usage on an Unix-like operating system:: i> showall b = lippo i> delete a - DBNotFoundError: (-30988, 'DB_NOTFOUND: No matching key/data pair found') + 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. -The interactive mode of ``plac`` can be used as a replacement of the -``cmd`` module in the standard library. There are a few differences, -however. For instance you miss tab completion, even if use rlwrap_ -(you get persistent command history for free, however). This is not -a big issue, since ``plac`` understands command abbreviations. - -If an abbreviation is ambiguous, plac_ warns you:: - - $ rlwrap python ishelve2.py - usage: plac_runner.py ishelve2.py [-h] {delete,set,showall,show} ... - i> sh - NameError: Ambiguous command 'sh': matching ['showall', 'show'] +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 sensitivirt, or you want to change the prompt, the way to do +it 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: -For ``cmd`` lovers --------------------------------------------------------- +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). +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 have been using the cmd_ module of the standard library for years. -I have also written a much enhanced ``cmd2`` module which we are using -internally at work and from which I have taken some ideas used in plac_. -In many ways plac_ makes the cmd_ module obsolete, -but I realize why many nostalgic souls would still use cmd_, especially -until plac_ does not grow real auto-completion features, instead of -relying on rlwrap_. But there must not be competition between plac_ -and cmd_: actually the two can happily work togethere. For this -reason I have put in the ``plac_ext`` module a few lines of code -for gluing together cmd_ and plac_, the ``cmd_interface``. -Using the ``cmd_interface`` is quite trivial: give to it a plac -command container and you will get in exchange a ``cmd.Cmd`` object: - -.. include:: cmd_ex.py - :literal: - -Here is an example of interactive session:: - - $ python cmd_ex.py - (Cmd) help + i> .help delete + usage: delete [name] - Documented commands (type help <topic>): - ======================================== - delete set show showall + delete given parameter (or everything) - Undocumented commands: - ====================== - EOF help + positional arguments: + name + + i> .help show + usage: show [names [names ...]] - (Cmd) set a 1 - setting a=1 - (Cmd) show a - a = 1 - (Cmd) showall - a = 1 - (Cmd) delete b - KeyError: 'b' - (Cmd) EOF [or CTRL-D] - -Internally the ``cmd_interface`` builds a ``cmd.Cmd`` class and adds -to it the ``do_`` methods corresponding to the commands in the container, -then it returns a ``cmd.Cmd`` instance. - -The ``cmd_interface`` is just a proof of concept: it is there so that you -can study the source code and see an example of integration of plac_ -with a different framework. It may change and even go away in future -releases of plac_. + 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 should be considered deprecated and 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 -------------------------------------------------------- @@ -464,18 +524,17 @@ 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="rlwrap plac_runner.py"`` + ``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 - Operating on /home/micheles/conf.shelve. Available commands: - set - show - showall - delete + 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 @@ -690,6 +749,163 @@ 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 + A fake importer with an import_file command + i> import_file file1 + Imported 100 lines + Imported 200 lines + Imported 300 lines + ... <wait 3+ minutes> + 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> + +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. + Summary ------------------------------------------------------- @@ -711,7 +927,11 @@ rules are quite simple: 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. + ``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. Moreover, remember that ``plac_runner.py`` is your friend. @@ -767,8 +987,3 @@ pretty well for most users. .. _distutils: http://docs.python.org/distutils/ .. _cmd: http://docs.python.org/library/cmd.html .. _rlwrap: http://freshmeat.net/projects/rlwrap/ - -.. If the script is invoked with no arguments, the default help is - invoked: this is done by invoking the argparse_ low-level method - ``.format_help``. In other words, the recognition of the couple - ``-h/--help`` is disabled, but the original help feature is still there. diff --git a/plac/doc/shelve_interpreter.help b/plac/doc/shelve_interpreter.help index e8c5320..f666445 100644 --- a/plac/doc/shelve_interpreter.help +++ b/plac/doc/shelve_interpreter.help @@ -1,8 +1,9 @@ usage: shelve_interpreter.py [-h] [-interactive] [subcommands [subcommands ...]] -This script works both interactively and non-interactively. Use .help to see -the internal commands. + 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 diff --git a/plac/doc/sql_interface.py b/plac/doc/sql_interface.py new file mode 100644 index 0000000..d045d03 --- /dev/null +++ b/plac/doc/sql_interface.py @@ -0,0 +1,25 @@ +import os, plac +from sqlalchemy.ext.sqlsoup import SqlSoup + +SQLKEYWORDS = set(['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, *args): + sql = 'SELECT ' + ' '.join(args) + for row in self.soup.bind.execute(sql): + yield str(row) # the formatting can be much improved + +rl_input = plac.ReadlineInput( + COMPLETIONS, prompt='sql> ', + histfile=os.path.expanduser('~/.sql_interface.history'), + case_sensitive=False) + +if __name__ == '__main__': + plac.Interpreter(plac.call(SqlInterface)).interact(rl_input) diff --git a/plac/doc/test_plac.py b/plac/doc/test_plac.py index 450f9ff..86c600b 100644 --- a/plac/doc/test_plac.py +++ b/plac/doc/test_plac.py @@ -41,6 +41,13 @@ def check_help(name): ####################### 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')) @@ -114,13 +121,6 @@ def test_kwargs(): expect(SystemExit, plac.call, main, ['arg1', 'arg2', 'a=1', 'opt=2']) -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] - class Cmds(object): add_help = False commands = 'help', 'commit' @@ -151,17 +151,24 @@ 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'): - args = [sys.executable, '../plac_runner.py', '-b', batch] - yield subprocess.call, args + yield check_script, ['plac_runner.py', '-b', batch] def test_placet(): for placet in os.listdir('.'): if placet.endswith('.placet'): - args = [sys.executable, '../plac_runner.py', '-t', placet] - yield subprocess.call, args + yield check_script, ['plac_runner.py', '-t', placet] if __name__ == '__main__': n = 0 diff --git a/plac/plac.py b/plac/plac.py index ce53890..4427ea2 100644 --- a/plac/plac.py +++ b/plac/plac.py @@ -24,7 +24,7 @@ ## DAMAGE. """ -See doc/plac.pdf for the documentation. +See doc/plac.pdf, doc/plac_adv.pdf for the documentation. """ __version__ = '0.6.0' @@ -32,4 +32,4 @@ __version__ = '0.6.0' from plac_core import * if sys.version >= '2.5': - from plac_ext import Interpreter, import_main + from plac_ext import Interpreter, import_main, ReadlineInput diff --git a/plac/plac_core.py b/plac/plac_core.py index 365732c..89cb9a7 100644 --- a/plac/plac_core.py +++ b/plac/plac_core.py @@ -13,6 +13,7 @@ try: set except NameError: # Python 2.3 from sets import Set as set +from gettext import gettext as _ def annotations(**ann): """ @@ -29,7 +30,7 @@ def annotations(**ann): for argname in ann: if argname not in args: raise NameError( - 'Annotating non-existing argument: %s' % argname) + _('Annotating non-existing argument: %s') % argname) f.__annotations__ = ann return f return annotate @@ -72,33 +73,36 @@ PARSER_CFG = getfullargspec(argparse.ArgumentParser.__init__).args[1:] def pconf(obj): "Extracts the configuration of the underlying ArgumentParser from obj" - cfg = dict(description=obj.__doc__) + 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 -def parser_from(obj): +parser_registry = {} + +def parser_from(obj, **confparams): """ obj can be a callable or an object with a .commands attribute. Returns an ArgumentParser. - """ - if hasattr(obj, 'func'): # added by the task manager - obj = obj.func - if hasattr(obj, 'p'): # the underlying parser has been generated already - return obj.p - parser = obj.p = ArgumentParser(**pconf(obj)) - if hasattr(obj, 'commands'): # a command container - for cmd in obj.commands: - parser.addsubparser(cmd, getattr(obj, cmd)) + """ + 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) + if hasattr(obj, 'commands') and obj.commands and not inspect.isclass(obj): + # a command container instance + parser.addsubcommands(obj.commands, obj, 'subcommands') parser.missing = getattr( obj, '__missing__', lambda name: parser.error('No command %r' % name)) - parser.func = lambda : None - parser.argspec = getfullargspec(parser.func) - return parser else: - return parser._populated(obj) + parser.populate_from(obj) + return parser def _extract_kwargs(args): "Returns two lists: regular args and name=value args" @@ -124,14 +128,14 @@ def _match_cmd(abbrev, commands): return matches[0] elif n > 1: raise NameError( - 'Ambiguous command %r: matching %s' % (abbrev, matches)) + _('Ambiguous command %r: matching %s' % (abbrev, matches))) class ArgumentParser(argparse.ArgumentParser): """ An ArgumentParser with .func and .argspec attributes, and possibly .commands and .subparsers. """ - def consume(self, args, ignore_extra=False): + def consume(self, args): """Call the underlying function with the args. Works also for command containers, by dispatching to the right subparser.""" arglist = list(args) @@ -139,70 +143,74 @@ class ArgumentParser(argparse.ArgumentParser): if hasattr(self, 'subparsers'): subp, cmd = self._extract_subparser_cmd(arglist) if subp is None and cmd is not None: - return self.missing(cmd) + return cmd, self.missing(cmd) elif subp is not None: # use the subparser self = subp - if self.argspec.varkw: - arglist, kwargs = _extract_kwargs(arglist) + if hasattr(self, 'argspec') and self.argspec.varkw: + arglist, kwargs = _extract_kwargs(arglist) # modify arglist! else: kwargs = {} - if ignore_extra: # ignore unrecognized arguments - ns, self.extra_args = self.parse_known_args(arglist) + if hasattr(self, 'argspec') and self.argspec.varargs: + # ignore unrecognized arguments + ns, extraopts = self.parse_known_args(arglist) else: - ns = self.parse_args(arglist) + ns, extraopts = self.parse_args(arglist), [] 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), **kwargs) + self.error( + _('colliding keyword arguments: %s') % ' '.join(collision)) + return cmd, self.func(*(args + varargs + extraopts), **kwargs) def _extract_subparser_cmd(self, arglist): - "Extract the subparser from the first recognized argument" - prefix = self.prefix_chars[0] + "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(prefix): + if not arg.startswith(optprefix): cmd = _match_cmd(arg, name_parser_map) del arglist[i] - return name_parser_map.get(cmd), cmd + return name_parser_map.get(cmd), cmd or arg return None, None - def addsubparser(self, cmd, func): - "Add a subparser for a command" + 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='subcommands', help='-h to get additional help') - subp = self.subparsers.add_parser(cmd, **pconf(func)) - return subp._populated(func) + self.subparsers = self.add_subparsers(title=title) + elif title: + self.add_argument_group(title=title) # populate ._action_groups + prefixlen = len(getattr(obj, 'cmdprefix', '')) + for cmd in commands: + func = getattr(obj, cmd[prefixlen:]) # strip the prefix + self.subparsers.add_parser( + cmd, add_help=False, **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 if inspect.isfunction(obj): self.argspec = getfullargspec(obj) - obj.p = self elif inspect.ismethod(obj): self.argspec = getfullargspec(obj) - obj.im_func.p = self del self.argspec.args[0] # remove first argument elif inspect.isclass(obj): self.argspec = getfullargspec(obj.__init__) - obj.__init__.im_func.p = self del self.argspec.args[0] # remove first argument elif hasattr(obj, '__call__'): self.argspec = getfullargspec(obj.__call__) - obj.__call__.im_func.p = self del self.argspec.args[0] # remove first argument else: - raise TypeError('Could not determine signature of %r' % obj) + raise TypeError(_('Could not determine the signature of %r') % obj) + self.func = obj + parser_registry[obj] = self - def _populated(self, func): + def populate_from(self, func): """ Extract the arguments from the attributes of the passed function - and return a populated ArgumentParser instance. As a side - effect, adds a .p attribute to func. + and return a populated ArgumentParser instance. """ self._set_func_argspec(func) f = self.argspec @@ -226,7 +234,7 @@ class ArgumentParser(argparse.ArgumentParser): shortlong = (prefix + name,) elif default is NONE: # required argument self.add_argument(name, help=a.help, type=a.type, - choices=a.choices, metavar=metavar) + choices=a.choices, metavar=metavar) else: # default argument self.add_argument( name, nargs='?', help=a.help, default=dflt, @@ -239,35 +247,45 @@ class ArgumentParser(argparse.ArgumentParser): 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' % + 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) + 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) - return self + type=a.type, metavar=a.metavar) + + def help_cmd(self, cmd): + "Return the help message for a subcommand" + p = self.subparsers._name_parser_map.get(cmd) + if p is None: + return _('Unknown command %s' % cmd) + else: + return p.format_help() + + 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:], ignore_extra=False): +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 argument parser inferred from the annotations of obj + 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. Returns a list or an atomic object. - If ignore_extra is True, unrecognized arguments are stored - in the attribute .extra_args of the associated parser for - later processing. + associated subparser. """ - cmd, result = parser_from(obj).consume(arglist, ignore_extra) - if iterable(result): + 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 index 37051fe..69d7772 100644 --- a/plac/plac_ext.py +++ b/plac/plac_ext.py @@ -1,12 +1,27 @@ # 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, time, select, multiprocessing, signal +import itertools, traceback, time, select, multiprocessing, signal, threading import plac_core +try: + import readline +except ImportError: + readline = None ############################# generic utils ################################ +@contextmanager +def stdout(fileobj): + 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)) @@ -34,12 +49,82 @@ def less(text): use_less = (sys.platform != 'win32') # unices +class TerminatedProcess(Exception): + pass + +def terminatedProcess(signum, frame): + raise TerminatedProcess + +def namedpipe(fname): + "Return a line iterator reading from a named pipe every twice per second" + try: + os.mkfifo(fname) + except OSError: # already there + pass + with open(fname) as fifo: + while True: + line = fifo.readline() + if line == 'EOF\n': + break + elif line: + yield line + time.sleep(.5) + os.remove(fname) + +########################### readline support ############################# + +class ReadlineInput(object): + """ + An iterable with a .readline method reading from stdin with readline + features enabled, if possible. Otherwise return sys.stdin itself. + """ + def __init__(self, completions, prompt='', case_sensitive=True, + histfile=None): + self.completions = completions + self.case_sensitive = case_sensitive + self.histfile = histfile + self.prompt = prompt + if not case_sensitive: + self.completions = map(str.upper, completions) + readline.parse_and_bind("tab: complete") + readline.set_completer(self.complete) + + def __enter__(self): + try: + if self.histfile: + readline.read_history_file(self.histfile) + except IOError: # the first time + pass + return self + + def __exit__(self, etype, exc, tb): + if self.histfile: + readline.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): + try: + return raw_input(self.prompt) + '\n' + except EOFError: + return '' + + def __iter__(self): + return iter(self.readline, '') + ########################### import management ################################ try: PLACDIRS = os.environ.get('PLACPATH', '.').split(':') except: - raise ValueError('Ill-formed PLACPATH: got %PLACPATHs' % os.environ) + raise ValueError(_('Ill-formed PLACPATH: got %PLACPATHs') % os.environ) def import_main(path, *args, **pconf): """ @@ -52,28 +137,23 @@ def import_main(path, *args, **pconf): if os.path.exists(fullpath): break else: # no break - raise ImportError('Cannot find %s', path) + raise ImportError(_('Cannot find %s'), path) else: fullpath = path name, ext = os.path.splitext(os.path.basename(fullpath)) - tool = imp.load_module(name, open(fullpath), fullpath, (ext, 'U', 1)).main + main = imp.load_module(name, open(fullpath), fullpath, (ext, 'U', 1)).main if args: - cmd, tool = plac_core.parser_from(tool).consume(args) - elif inspect.isclass(tool): - tool = tool() # instantiate it - vars(tool).update(pconf) - plac_core.parser_from(tool) # raise a TypeError if not + cmd, tool = plac_core.parser_from(main).consume(args) + else: + tool = main + # set the parser configuration and possibly raise a TypeError early + plac_core.parser_from(tool, **pconf) return tool -######################## Tasks management ########################## - -class TerminatedProcess(Exception): - pass - -def terminatedProcess(signum, frame): - raise TerminatedProcess +############################## Task classes ############################## -class Task(object): +# base class not instantiated directly +class BaseTask(object): """ A task is a wrapper over a generator object with signature Task(no, arglist, genobj), attributes @@ -85,16 +165,17 @@ class Task(object): .exc .tb .status + .synchronous and methods .run and .kill. """ - STATES = 'SUBMITTED', 'RUNNING', 'FINISHED', 'ABORTED', 'KILLED' - synchronous = True # may be overridden in subclasses + 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.str, self.etype, self.exc, self.tb = '', None, None, None self.status = 'SUBMITTED' self.outlist = [] @@ -102,19 +183,21 @@ class Task(object): """ 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 value is not None: + if self.status == 'TOBEKILLED': # exit from the loop + raise GeneratorExit + if value is not None: # add output self.outlist.append(value) yield except (GeneratorExit, TerminatedProcess): # soft termination self.status = 'KILLED' - except: # unexpect exception + except: # unexpected exception self.etype, self.exc, tb = sys.exc_info() self.tb = self.traceback if stringify_tb else tb - # needed when sending the traceback to a process self.status = 'ABORTED' else: # regular exit self.status = 'FINISHED' @@ -126,21 +209,15 @@ class Task(object): pass def kill(self): - "Kill softly the task by closing the inner generator" - self._genobj.close() + "Set a TOBEKILLED status" + self.status = 'TOBEKILLED' def wait(self): - "Wait for the task to finish: overridden in MPTask" - - def __str__(self): - "Returns the output string or the error message" - if self.etype: # there was an error - return '%s: %s' % (self.etype.__name__, self.exc) - else: - return self.str + "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): @@ -148,35 +225,158 @@ class Task(object): else: return ''.join(traceback.format_tb(self.tb)) + def __str__(self): + "String representation containing class name, number, arglist, status" + return '<%s %d [%s] %s>' % ( + self.__class__.__name__, self.no, + ' '.join(self.arglist), self.status) + +########################## synchronous tasks ############################### + +class Outlist(object): + "A list wrapper displaying each appended value on stdout" + def __init__(self): + self._ls = [] + def append(self, value): + self._ls.append(value) + print(value) + def __iter__(self): + return iter(self._ls) + def __len__(self): + return len(self._ls) + +class SynTask(BaseTask): + """ + Synchronous task running in the interpreter loop and displaying its + output as soon as available. + """ + synchronous = True + + def __init__(self, no, arglist, genobj): + BaseTask.__init__(self, no, arglist, genobj) + self.outlist = Outlist() + + 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 self.str + +class ThreadedTask(BaseTask): + """ + A task running in a separated thread. + """ + synchronous = False + + 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): + "Return a property to be attached to an object with a .ns attribute" + def get(self): + return getattr(self.ns, name) + def set(self, value): + setattr(self.ns, name, value) + 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. + """ + + synchronous = False + _mp_manager = None + + str = sharedattr('str') + etype = sharedattr('etype') + exc = sharedattr('exc') + tb = sharedattr('tb') + status = sharedattr('status') + + def __init__(self, no, arglist, genobj): + if self.__class__._mp_manager is None: # the first time + self.__class__._mp_manager = multiprocessing.Manager() + self.no = no + self.arglist = arglist + self.outlist = self._mp_manager.list() + self.ns = self._mp_manager.Namespace() + self.str, self.etype, self.exc, self.tb = '*', None, None, None + self.status = 'SUBMITTED' + self._genobj = self._wrap(genobj, stringify_tb=True) + 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" + 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 HelpSummary(object): + "Build the help summary consistently with the cmd module" + @classmethod + def make(cls, obj, specialcommands): + 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) + return c.stdout + def __init__(self): + self._ls = [] + def write(self, s): + self._ls.append(s) + def __str__(self): + return ''.join(self._ls) + class TaskManager(object): - specialcommands = set(['_help', '_kill', '_list', '_output', '_last_tb']) + """ + Store the given commands into a task registry. Provides methods to + manage the submitted tasks. + """ + cmdprefix = '.' + specialcommands = set(['.help', '.last_tb']) def __init__(self, obj): self.obj = obj - self._extract_commands_from(obj) self.registry = {} # {taskno : task} + if obj.mpcommands or obj.thcommands: + self.specialcommands.update(['.kill', '.list', '.output']) + self.helpsummary = HelpSummary.make(obj, self.specialcommands) signal.signal(signal.SIGTERM, terminatedProcess) - def _extract_commands_from(self, obj): - "Make sure self has the right command attributes" - for attrname in ('commands', 'asyncommands', 'mpcommands'): - try: - sequence = getattr(obj, attrname) - except AttributeError: - sequence = [] - if not isinstance(sequence, set): - sequence = set(sequence) - setattr(self, attrname, sequence) - self.commands.update(self.asyncommands, self.mpcommands) - for cmd in self.commands: - setattr(self, cmd, getattr(obj, cmd)) - if self.commands: - self.commands.update(self.specialcommands) - self.add_help = False - def run_task(self, task): "Run the task and update the registry" - if not task.synchronous: + if not task.arglist: + return + cmd = task.arglist[0] + if cmd not in self.specialcommands: self.registry[task.no] = task task.run() @@ -187,15 +387,14 @@ class TaskManager(object): task.kill() task.wait() - def _get_latest(self, taskno=-1, status=None, synchronous=False): + 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 and t.synchronous == synchronous] + if t.status == status] else: - tasks = [t for t in self.registry.itervalues() - if t.synchronous == synchronous] + tasks = [t for t in self.registry.itervalues()] tasks.sort(key=attrgetter('no')) if len(tasks) >= abs(taskno): return tasks[taskno] @@ -204,7 +403,7 @@ class TaskManager(object): @plac_core.annotations( taskno=('task to kill', 'positional', None, int)) - def _kill(self, taskno=-1): + 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') @@ -220,12 +419,11 @@ class TaskManager(object): yield 'Already finished %s' % task return task.kill() - yield 'Killed task %s' % task + yield task @plac_core.annotations( - status=('list of tasks with a given status', 'positional', - None, str, Task.STATES)) - def _list(self, status='RUNNING'): + 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: @@ -233,7 +431,7 @@ class TaskManager(object): @plac_core.annotations( taskno=('task number', 'positional', None, int)) - def _output(self, taskno=-1): + def output(self, taskno=-1): 'show the output of a given task' if taskno < 0: task = self._get_latest(taskno) @@ -252,81 +450,38 @@ class TaskManager(object): else: yield outstr - def _last_tb(self): - task = self._get_latest(synchronous=True) - yield task.traceback + '\n' - - def _help(self, cmd=None): - yield cmd_help(self.obj.asyncommands) - #yield self.p.format_help() - -######################## Process management ########################## - -def sharedattr(name): - "Return a property to be attached to an object with a .ns attribute" - def get(self): - return getattr(self.ns, name) - def set(self, value): - setattr(self.ns, name, value) - return property(get, set) - -class MPTask(Task): - """ - A task running as an external process. The current implementation - only works on Unix-like systems, where multiprocessing use forks. - """ - - synchronous = False - _mp_manager = None - - str = sharedattr('str') - etype = sharedattr('etype') - exc = sharedattr('exc') - tb = sharedattr('tb') - status = sharedattr('status') - - def __init__(self, no, arglist, genobj): - if self.__class__._mp_manager is None: # the first time - self.__class__._mp_manager = multiprocessing.Manager() - self.no = no - self.arglist = arglist - self.outlist = self._mp_manager.list() - self.ns = self._mp_manager.Namespace() - self.str, self.etype, self.exc, self.tb = None, None, None, None - self.status = 'SUBMITTED' - self._genobj = self._wrap(genobj, stringify_tb=True) - 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" - self.proc.join() + @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' - def kill(self): - """Kill the process with a SIGTERM inducing a TerminatedProcess - exception in the children""" - self.proc.terminate() + def help(self, cmd=None): + "show help about a given command" + if cmd is None: + yield str(self.helpsummary) + else: + yield plac_core.parser_from(self.obj).help_cmd(cmd) - def __str__(self): - return '<%s %d [%s] %s>' % ( - self.__class__.__name__, self.no, - ' '.join(self.arglist), self.status) +########################### SyncProcess ############################## -class SyncProcess(subprocess.Popen): +class Process(subprocess.Popen): "Start the interpreter specified by the params in a subprocess" def __init__(self, params): code = '''import plac -plac.Interpreter(plac.import_main(*%s), prompt='i>\\n').interact() +plac.Interpreter(plac.import_main(*%s)).interact(prompt='i>\\n') ''' % params subprocess.Popen.__init__( self, [sys.executable, '-u', '-c', code], stdin=subprocess.PIPE, stdout=subprocess.PIPE) def close(self): + "Close stdin and stdout" self.stdin.close() self.stdout.close() @@ -340,63 +495,11 @@ plac.Interpreter(plac.import_main(*%s), prompt='i>\\n').interact() return out[:-1] + ' ' # remove last newline def send(self, line): - "Send a line (adding a newline) to the subprocess" + """Send a line (adding a newline) to the underlying subprocess + and wait for the answer""" self.stdin.write(line + os.linesep) return self.recv() -############################# asynchronous utilities ######################### - -# eventloop inspired to monocle (http://github.com/saucelabs/monocle) -class EventLoop(object): - """ - A trivial event loop with a monocle-consistent interface, i.e. methods - queue_task, run and halt. - """ - def __init__(self): - self._running = True - self._queue = [] - - def queue_task(self, delay, callable, *args, **kw): - when = time.time() + delay - self._queue.append((when, callable, args, kw)) - self._queue.sort(reverse=True) # the last is the most recent - - def run(self): - while self._running: - if self._queue: # there is always the select in queue - when = self._queue[-1][0] - if when <= time.time(): - task = self._queue.pop() - task[1](*task[2], **task[3]) - time.sleep(0.05) - - def halt(self): - self._running = False - -class AsynTask(Task): - "Lightweight wrapper over a generator running into an event loop" - - synchronous = False - eventloop = EventLoop() - delay = 0 - - def run(self): - "Run the asyntask inside an eventloop" - eventloop = self.eventloop - delay = self.delay - def next_and_reschedule(): # unless stop iteration - try: - self._genobj.next() - except StopIteration: # error management inside _wrap - return - eventloop.queue_task(delay, next_and_reschedule) - eventloop.queue_task(delay, next_and_reschedule) - - def __str__(self): - return '<%s %d [%s] %s>' % ( - self.__class__.__name__, self.no, - ' '.join(self.arglist), self.status) - ########################### the Interpreter ############################# class Interpreter(object): @@ -404,42 +507,68 @@ class Interpreter(object): A context manager with a .send method and a few utility methods: execute, test and doctest. """ - counter = itertools.count(1) - - def __init__(self, obj, commentchar='#', prompt='i> ', - loop=AsynTask.eventloop): + def __init__(self, obj, commentchar='#'): self.obj = obj + try: + self.name = obj.__module__ + except AttributeError: + self.name = 'plac' self.commentchar = commentchar - self.prompt = prompt - self.eventloop = loop + self._set_commands(obj) self.tm = TaskManager(obj) - try: - self.p = plac_core.parser_from(obj) - except TypeError: # obj is not callable - self.p = plac_core.parser_from(self.tm) - self.p.error = lambda msg: sys.exit(msg) # patch the parser + self.parser = plac_core.parser_from(obj, prog='', add_help=False) + 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): self._interpreter = self._make_interpreter() self._interpreter.send(None) return self - def maketask(self, line): + def __exit__(self, *exc): + self.close() + + def make_task(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) + raise RuntimeError(_('%r not initialized: probably you forgot to ' + 'use the with statement') % self) if isinstance(line, basestring): arglist = shlex.split(line, self.commentchar) - else: + else: # expects a list of strings arglist = line return self._interpreter.send(arglist) def send(self, line): "Send a line to the underlying interpreter and return the result" - task = self.maketask(line) - Task.run(task) # blocking + task = self.make_task(line) + BaseTask.run(task) # blocking return task def close(self): @@ -447,9 +576,6 @@ class Interpreter(object): self.tm.close() self._interpreter.close() - def __exit__(self, *exc): - self.close() - def _make_interpreter(self): "The interpreter main loop, from lists of arguments to task objects" enter = getattr(self.obj, '__enter__', lambda : None) @@ -460,19 +586,18 @@ class Interpreter(object): for no in itertools.count(1): arglist = yield task try: - cmd, result = self.p.consume(arglist) + cmd, result = self.parser.consume(arglist) except: # i.e. SystemExit for invalid command - task = Task(no, arglist, gen_exc(*sys.exc_info())) + task = SynTask(no, arglist, gen_exc(*sys.exc_info())) continue - if not plac_core.iterable(result): - task = Task(no, arglist, gen_value(result)) - elif cmd in self.tm.asyncommands: - task = AsynTask(no, arglist, result) - task.eventloop = self.eventloop - elif cmd in self.tm.mpcommands: + 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) + elif cmd in self.obj.thcommands: + task = ThreadedTask(no, arglist, result) else: # blocking task - task = Task(no, arglist, result) + task = SynTask(no, arglist, result) except GeneratorExit: # regular exit exit(None, None, None) except: # exceptional exit @@ -484,6 +609,7 @@ class Interpreter(object): 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) @@ -505,7 +631,7 @@ class Interpreter(object): positions.append(len(lines) + 1) # last position return zip(inputs, self._getoutputs(lines, positions), positions) - def doctest(self, lineiter, put=write, verbose=False): + 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 @@ -514,103 +640,61 @@ class Interpreter(object): with self: for input, output, no in self._parse_doctest(lineiter): if verbose: - put('i> %s\n' % input) - put('-> %s\n' % output) - out = self.send(input) - if not out.str == output: - msg = 'line %d: input: %s\noutput: %s\nexpected: %s\n' % ( - no + 1, input, out, output) - put(msg) - raise out.etype, out.exc, out.tb - - def execute(self, lineiter, put=write, verbose=False): + 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) + 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: - put('i> ' + line) - output = self.send(line) - if output.etype: # there was an error - raise output.etype, output.exc, output.tb - put('%s\n' % output.str) - - def interact(self, stdin=sys.stdin, put=write, verbose=False): + write('i> ' + line) + task = self.send(line) # finished task + if task.etype: # there was an error + raise task.etype, task.exc, task.tb + if not task.synchronous: + write('%s\n' % task.str) + + def interact(self, stdin=sys.stdin, prompt='i> ', verbose=False): """ Starts an interactive command loop reading commands from the consolle. Using rlwrap is recommended. """ + if stdin is sys.stdin and readline: # use readline + # print '.%s.history' % self.name + stdin = ReadlineInput( + self.commands, prompt, histfile='.%s.history' % self.name, + case_sensitive=True) self.stdin = stdin - self.put = put - try: - put(self.obj.intro + '\n') - except AttributeError: # no intro - put(self.p.format_usage() + '\n') - put(self.prompt) + self.prompt = getattr(stdin, 'prompt', prompt) + self.verbose = verbose + intro = self.obj.__doc__ or '' + write(intro + '\n') with self: - if self.tm.asyncommands: - loop.queue_task(0, self._dispatch_async_input) - loop.run() + if self.stdin is sys.stdin: # do not close stdin automatically + self._manage_input() else: - while True: - line = stdin.readline() # including \n - if not line: - break - task = self.maketask(line) - self.tm.run_task(task) - if verbose and task.synchronous and task.etype: - put(task.traceback + '\n') - put(str(task) + '\n') - put(self.prompt) - - def _dispatch_async_input(self): - i, o, e = select.select([self.stdin], [], [], 0) - if i: - line = i[0].readline() # including \n - if not line: # stdin was closed - self.loop.halt() - return - task = self.maketask(line) + with self.stdin: + self._manage_input() + + def _manage_input(self): + while True: # using 'for' would not work well with unbuffered mode + if not isinstance(self.stdin, ReadlineInput): + write(self.prompt) # else the prompt is already there + line = self.stdin.readline() # including \n + if not line: + break + task = self.make_task(line) self.tm.run_task(task) - self.put('%s\n' % task) - self.put(self.prompt) - self.loop.queue_task(0, self._dispatch_async_input) # reschedule - -################################## others #################################### - -def cmd_interface(obj): - "Returns a cmd.Cmd wrapper over the command container" - i = Interpreter(obj) - def default(self, line): - print(i.send(line)) - dic = dict(preloop=lambda self: i.__enter__(), - postloop=lambda self: i.__exit__(), - do_EOF=lambda self, line: True, - default=default, - intro=getattr(i, 'intro', None)) - for command in obj.commands: - method = getattr(obj, command) - def do_func(self, line, command=command): - print(i.send(command + ' ' + line)) - do_func.__doc__ = method.__doc__ - do_func.__name__ = method.__name__ - dic['do_' + command] = do_func - clsname = '_%s_' % obj.__class__.__name__ - cls = type(clsname, (cmd.Cmd, object), dic) - return cls() - -class FakeOut(object): - def __init__(self): - self._s = '' - def write(self, s): - self._s += s - def __str__(self): - return self._s - -def cmd_help(cmds, displaywidth=80, cmd=cmd.Cmd(stdout=FakeOut())): - cmd.stdout.write("%s\n" % str(cmd.doc_leader)) - cmd.print_topics(cmd.doc_header, cmds, 15,80) - #cmd.print_topics(cmd.misc_header, helps,15,80) - #cmd.print_topics(cmd.undoc_header, cmds_undoc, 15,80) - return cmd.stdout + if self.verbose and task.synchronous and task.etype: + write(task.traceback) + if task.etype or not task.synchronous: + write(str(task) + '\n') |