mirror of
https://github.com/kennethreitz-archive/plac.git
synced 2026-06-05 07:36:12 +00:00
Plac mirror update to Plac 0.7.4.
This commit is contained in:
+31
@@ -0,0 +1,31 @@
|
||||
HISTORY
|
||||
----------
|
||||
|
||||
0.7.4 Fixed the plac_runner switches -i and -s; fixed a bug with multiline output
|
||||
and issue with nosetest (2010-09-04)
|
||||
0.7.3 Put the documentation in a single document; added runp (2010-08-31)
|
||||
0.7.2 Interpreter.call does not start an interpreter automagically anymore;
|
||||
better documented and added tests for the metavar concept (2010-08-31)
|
||||
0.7.1 A few bug fixes (2010-08-11)
|
||||
0.7.0 Improved and documented the support for parallel programming;
|
||||
added an asynchronous server; added plac.Interpreter.call (2010-08-07)
|
||||
0.6.1 Fixed the history file location; added the ability to pass a split
|
||||
function; added two forgotten files; added a reference to cmd2 by
|
||||
Catherine Devlin (2010-07-12)
|
||||
0.6.0 Improved the interactive experience with full readline support and
|
||||
custom help. Added support for long running command, via threads and
|
||||
processes (2010-07-11)
|
||||
0.5.0 Gigantic release. Introduced smart options, added an Interpreter class
|
||||
and the command container concept. Made the split plac/plac_core/plac_ext
|
||||
and added a plac runner, able to run scripts, batch files and doctests.
|
||||
Removed the default formatter class (2010-06-20)
|
||||
0.4.3 Fixed the installation procedure to automatically download argparse
|
||||
if needed (2010-06-11)
|
||||
0.4.2 Added missing .help files, made the tests generative and added a
|
||||
note about Clap in the documentation (2010-06-04)
|
||||
0.4.1 Changed the default formatter class and fixed a bug in the
|
||||
display of the default arguments. Added more stringent tests. (2010-06-03)
|
||||
0.4.0 abbrev is now optional. Added a note about CLIArgs and opterate.
|
||||
Added keyword arguments recognition. ``plac.call`` now returns the
|
||||
the output of the main function. (2010-06-03)
|
||||
0.3.0 Initial version. (2010-06-02)
|
||||
@@ -0,0 +1 @@
|
||||
include *.txt doc/*.py doc/*.help doc/*.txt doc/*.html doc/*.pdf
|
||||
@@ -0,0 +1,77 @@
|
||||
Metadata-Version: 1.0
|
||||
Name: plac
|
||||
Version: 0.7.4
|
||||
Summary: The smartest command line arguments parser in the world
|
||||
Home-page: http://pypi.python.org/pypi/plac
|
||||
Author: Michele Simionato
|
||||
Author-email: michele.simionato@gmail.com
|
||||
License: BSD License
|
||||
Description: Installation
|
||||
-------------
|
||||
|
||||
If you are lazy, just perform
|
||||
|
||||
::
|
||||
|
||||
$ easy_install -U plac
|
||||
|
||||
which will install the module on your system (and possibly argparse
|
||||
too, if it is not already installed). Notice that Python 3 requires
|
||||
the easy_install version of the distribute_ project.
|
||||
|
||||
If you prefer to install the full distribution from source, including
|
||||
the documentation, download the tarball_, unpack it and run
|
||||
|
||||
::
|
||||
|
||||
$ python setup.py install
|
||||
|
||||
in the main directory, possibly as superuser.
|
||||
|
||||
.. _tarball: http://pypi.python.org/pypi/plac
|
||||
.. _distribute: http://packages.python.org/distribute/
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Run
|
||||
|
||||
::
|
||||
|
||||
$ python doc/test_plac.py
|
||||
|
||||
or
|
||||
|
||||
::
|
||||
|
||||
$ nosetests doc
|
||||
|
||||
or
|
||||
|
||||
::
|
||||
|
||||
$ py.test doc
|
||||
|
||||
Some tests will fail if sqlalchemy is not installed.
|
||||
Run an ``easy_install -U sqlalchemy`` or just ignore them.
|
||||
|
||||
Documentation
|
||||
--------------
|
||||
|
||||
The source code and the documentation are hosted on Google code.
|
||||
Here is the full documentation in HTML and PDF form:
|
||||
|
||||
http://micheles.googlecode.com/hg/plac/doc/plac.html
|
||||
|
||||
http://micheles.googlecode.com/hg/plac/doc/plac.pdf
|
||||
|
||||
Keywords: command line arguments parser
|
||||
Platform: All
|
||||
Classifier: Development Status :: 3 - Alpha
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Natural Language :: English
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Topic :: Software Development :: Libraries
|
||||
Classifier: Topic :: Utilities
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
Installation
|
||||
-------------
|
||||
|
||||
If you are lazy, just perform
|
||||
|
||||
::
|
||||
|
||||
$ easy_install -U plac
|
||||
|
||||
which will install the module on your system (and possibly argparse
|
||||
too, if it is not already installed). Notice that Python 3 requires
|
||||
the easy_install version of the distribute_ project.
|
||||
|
||||
If you prefer to install the full distribution from source, including
|
||||
the documentation, download the tarball_, unpack it and run
|
||||
|
||||
::
|
||||
|
||||
$ python setup.py install
|
||||
|
||||
in the main directory, possibly as superuser.
|
||||
|
||||
.. _tarball: http://pypi.python.org/pypi/plac
|
||||
.. _distribute: http://packages.python.org/distribute/
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Run
|
||||
|
||||
::
|
||||
|
||||
$ python doc/test_plac.py
|
||||
|
||||
or
|
||||
|
||||
::
|
||||
|
||||
$ nosetests doc
|
||||
|
||||
or
|
||||
|
||||
::
|
||||
|
||||
$ py.test doc
|
||||
|
||||
Some tests will fail if sqlalchemy is not installed.
|
||||
Run an ``easy_install -U sqlalchemy`` or just ignore them.
|
||||
|
||||
Documentation
|
||||
--------------
|
||||
|
||||
The source code and the documentation are hosted on Google code.
|
||||
Here is the full documentation in HTML and PDF form:
|
||||
|
||||
http://micheles.googlecode.com/hg/plac/doc/plac.html
|
||||
|
||||
http://micheles.googlecode.com/hg/plac/doc/plac.pdf
|
||||
@@ -0,0 +1,9 @@
|
||||
# annotations.py
|
||||
class Positional(object):
|
||||
def __init__(self, help='', type=None, choices=None, metavar=None):
|
||||
self.help = help
|
||||
self.kind = 'positional'
|
||||
self.abbrev = None
|
||||
self.type = type
|
||||
self.choices = choices
|
||||
self.metavar = metavar
|
||||
@@ -0,0 +1,6 @@
|
||||
# cmd_ext.py
|
||||
from plac_ext import cmd_interface
|
||||
import ishelve2
|
||||
|
||||
if __name__ == '__main__':
|
||||
cmd_interface(ishelve2.main()).cmdloop()
|
||||
@@ -0,0 +1,13 @@
|
||||
usage: dbcli.py [-h] [-H] [-c SQL] [-d |] db [scripts [scripts ...]]
|
||||
|
||||
A script to run queries and SQL scripts on a database
|
||||
|
||||
positional arguments:
|
||||
db Connection string
|
||||
scripts SQL scripts
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-H, --header Header
|
||||
-c SQL, --sqlcmd SQL SQL command
|
||||
-d |, --delimiter | Column separator
|
||||
@@ -0,0 +1,29 @@
|
||||
# dbcli.py
|
||||
import plac
|
||||
from sqlalchemy.ext.sqlsoup import SqlSoup
|
||||
|
||||
@plac.annotations(
|
||||
db=("Connection string", 'positional', None, SqlSoup),
|
||||
header=("Header", 'flag', 'H'),
|
||||
sqlcmd=("SQL command", 'option', 'c', str, None, "SQL"),
|
||||
delimiter=("Column separator", 'option', 'd'),
|
||||
scripts="SQL scripts",
|
||||
)
|
||||
def main(db, header, sqlcmd, delimiter="|", *scripts):
|
||||
"A script to run queries and SQL scripts on a database"
|
||||
yield 'Working on %s' % db.bind.url
|
||||
|
||||
if sqlcmd:
|
||||
result = db.bind.execute(sqlcmd)
|
||||
if header: # print the header
|
||||
yield delimiter.join(result.keys())
|
||||
for row in result: # print the rows
|
||||
yield delimiter.join(map(str, row))
|
||||
|
||||
for script in scripts:
|
||||
db.bind.execute(file(script).read())
|
||||
yield 'executed %s' % script
|
||||
|
||||
if __name__ == '__main__':
|
||||
for output in plac.call(main):
|
||||
print(output)
|
||||
@@ -0,0 +1,47 @@
|
||||
|
||||
..
|
||||
|
||||
Multiline support and Emacs integration
|
||||
|
||||
plac_ is optimized for the simplest use case and by default it provide
|
||||
support for simple command-line languages where a command take
|
||||
a single line. This is the simplest case: it is easy to keep
|
||||
track of the line number and to print it in the error message, if
|
||||
there is some error in a plac_ script. Starting from release 0.7
|
||||
plac_ is beginning to support multiline input: it is now possible
|
||||
to define command-line languages with commands spanning multiple
|
||||
lines. The topical use case is the implementation of a tool
|
||||
to interact with a relational database: the tool must be able to send
|
||||
complex SQL queries spanning multiple lines to the backend.
|
||||
To support multiline input the ``Interpreter`` class provides
|
||||
a method ``multiline(stdin=sys.stdin, terminator=';', verbose=False)``
|
||||
which reads input from ``stdin`` until the terminator character
|
||||
(by default a semicolon) is reached.
|
||||
|
||||
Since the Python readline module does not expose the
|
||||
multiline functionality of the underlying C library (which is there),
|
||||
plac_ multiline mode does not have readline functionality. This is
|
||||
not a big deal really, because if you are writing multiple line
|
||||
commands you don't really want to type them at the command-line. It is
|
||||
much better to use a real editor to type them, and to call plac_ from
|
||||
the editor. Since I use Emacs I will give the recipe to integrate
|
||||
Emacs with plac_: something equivalent can be done for vi and for
|
||||
other editors/IDEs.
|
||||
|
||||
The multiline mode can be enabled by invoking the plac_ runner with
|
||||
the ``-m`` option. Since the multiline mode is intended for use with
|
||||
Emacs in inferior mode, it does not print any prompt. Here is an example
|
||||
of usage::
|
||||
|
||||
$ plac -m ishelve2.py
|
||||
set a 1;
|
||||
setting a=1
|
||||
show a;
|
||||
a = 1
|
||||
|
||||
To integrate plac_ with Emacs, enters the following lines in your
|
||||
.emacs:
|
||||
|
||||
.. include:: plac.el
|
||||
:literal:
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import os, plac, ishelve2
|
||||
|
||||
try:
|
||||
fifo = os.mkfifo('/tmp/x')
|
||||
except OSError: # fifo exists
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
i = plac.Interpreter(ishelve2.main())
|
||||
i.execute(iter(open('/tmp/x').readline, ''), verbose=True)
|
||||
@@ -0,0 +1,6 @@
|
||||
def main(arg: "required argument"):
|
||||
"do something with arg"
|
||||
print('Got %s' % arg)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import plac; plac.call(main) # passes sys.argv[1:] to main
|
||||
@@ -0,0 +1,15 @@
|
||||
# example1.py
|
||||
def main(dsn):
|
||||
"Do something with the database"
|
||||
print(dsn)
|
||||
# ...
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
n = len(sys.argv[1:])
|
||||
if n == 0:
|
||||
sys.exit('usage: python %s dsn' % sys.argv[0])
|
||||
elif n == 1:
|
||||
main(sys.argv[1])
|
||||
else:
|
||||
sys.exit('Unrecognized arguments: %s' % ' '.join(sys.argv[2:]))
|
||||
@@ -0,0 +1,10 @@
|
||||
usage: example10.py [-h] {add,mul} [n [n ...]]
|
||||
|
||||
A script to add and multiply numbers
|
||||
|
||||
positional arguments:
|
||||
{add,mul} The name of an operator
|
||||
n A number
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
@@ -0,0 +1,20 @@
|
||||
# example10.py
|
||||
import plac
|
||||
|
||||
@plac.annotations(
|
||||
operator=("The name of an operator", 'positional', None, str, ['add', 'mul']),
|
||||
numbers=("A number", 'positional', None, float, None, "n"))
|
||||
def main(operator, *numbers):
|
||||
"A script to add and multiply numbers"
|
||||
if operator == 'mul':
|
||||
op = float.__mul__
|
||||
result = 1.0
|
||||
else: # operator == 'add'
|
||||
op = float.__add__
|
||||
result = 0.0
|
||||
for n in numbers:
|
||||
result = op(result, n)
|
||||
return result
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(plac.call(main))
|
||||
@@ -0,0 +1,9 @@
|
||||
usage: example11.py [-h] i n [rest [rest ...]]
|
||||
|
||||
positional arguments:
|
||||
i This is an int
|
||||
n This is a float
|
||||
rest Other arguments
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
@@ -0,0 +1,13 @@
|
||||
# example11.py
|
||||
import plac
|
||||
from annotations import Positional
|
||||
|
||||
@plac.annotations(
|
||||
i=Positional("This is an int", int),
|
||||
n=Positional("This is a float", float),
|
||||
rest=Positional("Other arguments"))
|
||||
def main(i, n, *rest):
|
||||
print(i, n, rest)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import plac; plac.call(main)
|
||||
@@ -0,0 +1,9 @@
|
||||
usage: example12.py [-h] [-opt OPT] [args [args ...]] [kw [kw ...]]
|
||||
|
||||
positional arguments:
|
||||
args default arguments
|
||||
kw keyword arguments
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-opt OPT some option
|
||||
@@ -0,0 +1,18 @@
|
||||
# example12.py
|
||||
import plac
|
||||
|
||||
@plac.annotations(
|
||||
opt=('some option', 'option'),
|
||||
args='default arguments',
|
||||
kw='keyword arguments')
|
||||
def main(opt, *args, **kw):
|
||||
if opt:
|
||||
yield 'opt=%s' % opt
|
||||
if args:
|
||||
yield 'args=%s' % str(args)
|
||||
if kw:
|
||||
yield 'kw=%s' % kw
|
||||
|
||||
if __name__ == '__main__':
|
||||
for output in plac.call(main):
|
||||
print(output)
|
||||
@@ -0,0 +1,9 @@
|
||||
usage: example13.py [-h] {status,commit,checkout,help} ...
|
||||
|
||||
A Fake Version Control System
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
|
||||
subcommands:
|
||||
{status,commit,checkout,help}
|
||||
@@ -0,0 +1,27 @@
|
||||
import plac
|
||||
|
||||
class FVCS(object):
|
||||
"A Fake Version Control System"
|
||||
commands = 'checkout', 'commit', 'status', 'help'
|
||||
|
||||
@plac.annotations(
|
||||
name=('a recognized command', 'positional', None, str, commands))
|
||||
def help(self, name):
|
||||
print(plac.parser_from(self).help_cmd(name))
|
||||
|
||||
@plac.annotations(
|
||||
url=('url of the source code', 'positional'))
|
||||
def checkout(self, url):
|
||||
print('checkout', url)
|
||||
|
||||
def commit(self):
|
||||
print('commit')
|
||||
|
||||
@plac.annotations(quiet=('summary information', 'flag'))
|
||||
def status(self, quiet):
|
||||
print('status', quiet)
|
||||
|
||||
main = FVCS()
|
||||
|
||||
if __name__ == '__main__':
|
||||
plac.call(main)
|
||||
@@ -0,0 +1,12 @@
|
||||
# example2.py
|
||||
def main(dsn):
|
||||
"Do something on the database"
|
||||
print(dsn)
|
||||
# ...
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument('dsn')
|
||||
arg = p.parse_args()
|
||||
main(arg.dsn)
|
||||
@@ -0,0 +1,9 @@
|
||||
usage: example3.py [-h] dsn
|
||||
|
||||
Do something with the database
|
||||
|
||||
positional arguments:
|
||||
dsn
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
@@ -0,0 +1,8 @@
|
||||
# example3.py
|
||||
def main(dsn):
|
||||
"Do something with the database"
|
||||
print(dsn)
|
||||
# ...
|
||||
|
||||
if __name__ == '__main__':
|
||||
import plac; plac.call(main)
|
||||
@@ -0,0 +1,15 @@
|
||||
# example4.py
|
||||
from datetime import datetime
|
||||
|
||||
def main(dsn, table='product', today=datetime.today()):
|
||||
"Do something on the database"
|
||||
print(dsn, table, today)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
args = sys.argv[1:]
|
||||
if not args:
|
||||
sys.exit('usage: python %s dsn' % sys.argv[0])
|
||||
elif len(args) > 2:
|
||||
sys.exit('Unrecognized arguments: %s' % ' '.join(argv[2:]))
|
||||
main(*args)
|
||||
@@ -0,0 +1,11 @@
|
||||
usage: example5.py [-h] dsn [table] [today]
|
||||
|
||||
Do something on the database
|
||||
|
||||
positional arguments:
|
||||
dsn
|
||||
table
|
||||
today
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
@@ -0,0 +1,9 @@
|
||||
# example5.py
|
||||
from datetime import datetime
|
||||
|
||||
def main(dsn, table='product', today=datetime.today()):
|
||||
"Do something on the database"
|
||||
print(dsn, table, today)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import plac; plac.call(main)
|
||||
@@ -0,0 +1,8 @@
|
||||
usage: example6.py [-h] [-command COMMAND] dsn
|
||||
|
||||
positional arguments:
|
||||
dsn
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-command COMMAND SQL query
|
||||
@@ -0,0 +1,6 @@
|
||||
# example6.py
|
||||
def main(dsn, command: ("SQL query", 'option')):
|
||||
print('executing %r on %s' % (command, dsn))
|
||||
|
||||
if __name__ == '__main__':
|
||||
import plac; plac.call(main)
|
||||
@@ -0,0 +1,10 @@
|
||||
usage: example7.py [-h] dsn [scripts [scripts ...]]
|
||||
|
||||
Run the given scripts on the database
|
||||
|
||||
positional arguments:
|
||||
dsn
|
||||
scripts
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
@@ -0,0 +1,11 @@
|
||||
# example7.py
|
||||
from datetime import datetime
|
||||
|
||||
def main(dsn, *scripts):
|
||||
"Run the given scripts on the database"
|
||||
for script in scripts:
|
||||
print('executing %s' % script)
|
||||
# ...
|
||||
|
||||
if __name__ == '__main__':
|
||||
import plac; plac.call(main)
|
||||
@@ -0,0 +1,10 @@
|
||||
usage: example7_.py [-h] dsn [scripts [scripts ...]]
|
||||
|
||||
Run the given scripts on the database
|
||||
|
||||
positional arguments:
|
||||
dsn Database dsn
|
||||
scripts SQL scripts
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
@@ -0,0 +1,11 @@
|
||||
# example7_.py
|
||||
from datetime import datetime
|
||||
|
||||
def main(dsn: "Database dsn", *scripts: "SQL scripts"):
|
||||
"Run the given scripts on the database"
|
||||
for script in scripts:
|
||||
print('executing %s' % script)
|
||||
# ...
|
||||
|
||||
if __name__ == '__main__':
|
||||
import plac; plac.call(main)
|
||||
@@ -0,0 +1,9 @@
|
||||
usage: example8.py [-h] [-c COMMAND] dsn
|
||||
|
||||
positional arguments:
|
||||
dsn
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-c COMMAND, --command COMMAND
|
||||
SQL query
|
||||
@@ -0,0 +1,8 @@
|
||||
# example8.py
|
||||
def main(command: ("SQL query", 'option', 'c'), dsn):
|
||||
if command:
|
||||
print('executing %s on %s' % (command, dsn))
|
||||
# ...
|
||||
|
||||
if __name__ == '__main__':
|
||||
import plac; plac.call(main)
|
||||
@@ -0,0 +1,9 @@
|
||||
usage: example8_.py [-h] [-command select * from table] dsn
|
||||
|
||||
positional arguments:
|
||||
dsn
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-command select * from table
|
||||
SQL query
|
||||
@@ -0,0 +1,6 @@
|
||||
# example8_.py
|
||||
def main(dsn, command: ("SQL query", 'option')='select * from table'):
|
||||
print('executing %r on %s' % (command, dsn))
|
||||
|
||||
if __name__ == '__main__':
|
||||
import plac; plac.call(main)
|
||||
@@ -0,0 +1,8 @@
|
||||
usage: example9.py [-h] [-v] dsn
|
||||
|
||||
positional arguments:
|
||||
dsn connection string
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-v, --verbose prints more info
|
||||
@@ -0,0 +1,9 @@
|
||||
# example9.py
|
||||
|
||||
def main(verbose: ('prints more info', 'flag', 'v'), dsn: 'connection string'):
|
||||
if verbose:
|
||||
print('connecting to %s' % dsn)
|
||||
# ...
|
||||
|
||||
if __name__ == '__main__':
|
||||
import plac; plac.call(main)
|
||||
@@ -0,0 +1,5 @@
|
||||
import plac
|
||||
from ishelve import ishelve
|
||||
|
||||
if __name__ == '__main__':
|
||||
plac.Interpreter(ishelve).execute(file('ishelve2.bat'))
|
||||
@@ -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.call(FakeImporter)
|
||||
@@ -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.call(FakeImporter)
|
||||
@@ -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.call(FakeImporter)
|
||||
@@ -0,0 +1,18 @@
|
||||
import time
|
||||
import plac
|
||||
|
||||
class Importer(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"
|
||||
for n in range(10000):
|
||||
time.sleep(.01)
|
||||
if n % 100 == 99:
|
||||
yield 'Imported %d lines' % (n+1)
|
||||
yield
|
||||
|
||||
if __name__ == '__main__':
|
||||
plac.Interpreter.plac.call(Importer)
|
||||
@@ -0,0 +1,29 @@
|
||||
from Tkinter import *
|
||||
from importer3 import FakeImporter
|
||||
|
||||
def taskwidget(root, task, tick=500):
|
||||
"A Label widget showing the output of a task every 500 ms"
|
||||
sv = StringVar(root)
|
||||
lb = Label(root, textvariable=sv)
|
||||
def show_outlist():
|
||||
try:
|
||||
out = task.outlist[-1]
|
||||
except IndexError: # no output yet
|
||||
out = ''
|
||||
sv.set('%s %s' % (task, out))
|
||||
root.after(tick, show_outlist)
|
||||
root.after(0, show_outlist)
|
||||
return lb
|
||||
|
||||
def monitor(tasks):
|
||||
root = Tk()
|
||||
for task in tasks:
|
||||
task.run()
|
||||
taskwidget(root, task).pack()
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
import plac
|
||||
with plac.Interpreter(plac.call(FakeImporter)) as i:
|
||||
tasks = [i.submit('import_file f1'), i.submit('import_file f2')]
|
||||
monitor(tasks)
|
||||
@@ -0,0 +1,18 @@
|
||||
usage: ishelve.py [.help] [.showall] [.clear] [.delete DELETE]
|
||||
[.filename /home/micheles/conf.shelve]
|
||||
[params [params ...]] [setters [setters ...]]
|
||||
|
||||
Simple interface to a shelve
|
||||
|
||||
positional arguments:
|
||||
params names of the parameters in the shelve
|
||||
setters setters param=value
|
||||
|
||||
optional arguments:
|
||||
.help show help
|
||||
.showall show all parameters in the shelve
|
||||
.clear clear the shelve
|
||||
.delete DELETE delete an element
|
||||
.filename /home/micheles/conf.shelve
|
||||
filename of the shelve
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# ishelve.py
|
||||
import os, shelve, plac
|
||||
|
||||
DEFAULT_SHELVE = os.path.expanduser('~/conf.shelve')
|
||||
|
||||
@plac.annotations(
|
||||
help=('show help', 'flag'),
|
||||
showall=('show all parameters in the shelve', 'flag'),
|
||||
clear=('clear the shelve', 'flag'),
|
||||
delete=('delete an element', 'option'),
|
||||
filename=('filename of the shelve', 'option'),
|
||||
params='names of the parameters in the shelve',
|
||||
setters='setters param=value')
|
||||
def main(help, showall, clear, delete, filename=DEFAULT_SHELVE,
|
||||
*params, **setters):
|
||||
"A simple interface to a shelve. Use .help to see the available commands."
|
||||
sh = shelve.open(filename)
|
||||
try:
|
||||
if not any([help, showall, clear, delete, params, setters]):
|
||||
yield 'no arguments passed, use .help to see the available commands'
|
||||
elif help: # custom help
|
||||
yield 'Commands: .help, .showall, .clear, .delete'
|
||||
yield '<param> ...'
|
||||
yield '<param=value> ...'
|
||||
elif showall:
|
||||
for param, name in sh.items():
|
||||
yield '%s=%s' % (param, name)
|
||||
elif clear:
|
||||
sh.clear()
|
||||
yield 'cleared the shelve'
|
||||
elif delete:
|
||||
try:
|
||||
del sh[delete]
|
||||
except KeyError:
|
||||
yield '%s: not found' % delete
|
||||
else:
|
||||
yield 'deleted %s' % delete
|
||||
for param in params:
|
||||
try:
|
||||
yield sh[param]
|
||||
except KeyError:
|
||||
yield '%s: not found' % param
|
||||
for param, value in setters.items():
|
||||
sh[param] = value
|
||||
yield 'setting %s=%s' % (param, value)
|
||||
finally:
|
||||
sh.close()
|
||||
|
||||
main.add_help = False # there is a custom help, remove the default one
|
||||
main.prefix_chars = '.' # use dot-prefixed commands
|
||||
|
||||
if __name__ == '__main__':
|
||||
for output in plac.call(main):
|
||||
print(output)
|
||||
@@ -0,0 +1,8 @@
|
||||
usage: ishelve2.py [-h] [-configfile CONFIGFILE]
|
||||
|
||||
A minimal interface over a shelve object.
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-configfile CONFIGFILE
|
||||
path name of the shelve
|
||||
@@ -0,0 +1,43 @@
|
||||
# ishelve2.py
|
||||
import shelve, os, sys, plac
|
||||
|
||||
class ShelveInterface(object):
|
||||
"A minimal interface over a shelve object."
|
||||
commands = 'set', 'show', 'showall', 'delete'
|
||||
@plac.annotations(
|
||||
configfile=('path name of the shelve', 'option'))
|
||||
def __init__(self, configfile):
|
||||
self.configfile = configfile or '~/conf.shelve'
|
||||
self.fname = os.path.expanduser(self.configfile)
|
||||
self.__doc__ += '\nOperating on %s.\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)
|
||||
self.sh[name] = value
|
||||
def show(self, *names):
|
||||
"show given parameters"
|
||||
for name in names:
|
||||
yield '%s = %s' % (name, self.sh[name]) # no error checking
|
||||
def showall(self):
|
||||
"show all parameters"
|
||||
for name in self.sh:
|
||||
yield '%s = %s' % (name, self.sh[name])
|
||||
def delete(self, name=None):
|
||||
"delete given parameter (or everything)"
|
||||
if name is None:
|
||||
yield 'deleting everything'
|
||||
self.sh.clear()
|
||||
else:
|
||||
yield 'deleting %s' % name
|
||||
del self.sh[name] # no error checking
|
||||
|
||||
main = ShelveInterface # useful for the tests
|
||||
|
||||
if __name__ == '__main__':
|
||||
plac.Interpreter.call(ShelveInterface)
|
||||
@@ -0,0 +1,5 @@
|
||||
# ishelve3.py
|
||||
from ishelve2 import ShelveInterface as main
|
||||
|
||||
if __name__ == '__main__':
|
||||
import plac; plac.Interpreter.call(main)
|
||||
@@ -0,0 +1,10 @@
|
||||
import sys
|
||||
from plac_ext import Process
|
||||
|
||||
if __name__ == '__main__':
|
||||
proc = Process(['ishelve2.py', '-c', '~/conf.shelve'])
|
||||
while True:
|
||||
multiline = ' '.join(iter(sys.stdin.readline, 'END\n'))
|
||||
if not multiline:
|
||||
break
|
||||
print proc.send(multiline)
|
||||
@@ -0,0 +1,32 @@
|
||||
from Tkinter import *
|
||||
from picalculator import PiCalculator
|
||||
|
||||
def taskwidget(root, task, tick=500):
|
||||
"A Label widget showing the output of a task every 500 ms"
|
||||
sv = StringVar(root)
|
||||
lb = Label(root, textvariable=sv)
|
||||
def show_outlist():
|
||||
try:
|
||||
out = task.outlist[-1]
|
||||
except IndexError: # no output yet
|
||||
out = ''
|
||||
sv.set('%s %s' % (task, out))
|
||||
root.after(tick, show_outlist)
|
||||
root.after(0, show_outlist)
|
||||
return lb
|
||||
|
||||
def monitor(calculator):
|
||||
tasks = calculator.submit_tasks()
|
||||
root = Tk()
|
||||
try:
|
||||
for task in tasks:
|
||||
task.run()
|
||||
taskwidget(root, task).pack()
|
||||
root.mainloop()
|
||||
except KeyboardInterrupt:
|
||||
root.quit()
|
||||
finally:
|
||||
calculator.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
import plac; monitor(plac.call(PiCalculator))
|
||||
@@ -0,0 +1,36 @@
|
||||
import time
|
||||
from Tkinter import *
|
||||
from threading import Thread
|
||||
from picalculator import PiCalculator
|
||||
|
||||
def taskwidget(root, task, tick=500):
|
||||
"A Label widget showing the output of a task every 500 ms"
|
||||
sv = StringVar(root)
|
||||
lb = Label(root, textvariable=sv)
|
||||
def show_outlist():
|
||||
try:
|
||||
out = task.outlist[-1]
|
||||
except IndexError: # no output yet
|
||||
out = ''
|
||||
sv.set('%s %s' % (task, out))
|
||||
root.after(tick, show_outlist)
|
||||
root.after(0, show_outlist)
|
||||
return lb
|
||||
|
||||
def monitor(calculator):
|
||||
calculator.submit_tasks()
|
||||
th = Thread(target=calculator.run)
|
||||
th.start()
|
||||
root = Tk()
|
||||
try:
|
||||
for task in calculator.i.tasks():
|
||||
taskwidget(root, task).pack()
|
||||
root.mainloop()
|
||||
except KeyboardInterrupt:
|
||||
root.quit()
|
||||
finally:
|
||||
calculator.close()
|
||||
th.join()
|
||||
|
||||
if __name__ == '__main__':
|
||||
import plac; monitor(plac.call(PiCalculator))
|
||||
@@ -0,0 +1,63 @@
|
||||
from __future__ import with_statement
|
||||
from random import random
|
||||
import multiprocessing
|
||||
import plac
|
||||
|
||||
class PiCalculator(object):
|
||||
"""Compute pi in parallel with threads or processes"""
|
||||
|
||||
@plac.annotations(
|
||||
npoints=('number of integration points', 'positional', None, int),
|
||||
mode=('sequential|parallel|threaded', 'option', 'm', str, 'SPT'))
|
||||
def __init__(self, npoints, mode='S'):
|
||||
self.npoints = npoints
|
||||
if mode == 'P':
|
||||
self.mpcommands = ['calc_pi']
|
||||
elif mode == 'T':
|
||||
self.thcommands = ['calc_pi']
|
||||
elif mode == 'S':
|
||||
self.commands = ['calc_pi']
|
||||
self.n_cpu = multiprocessing.cpu_count()
|
||||
|
||||
def submit_tasks(self):
|
||||
self.i = plac.Interpreter(self).__enter__()
|
||||
return [self.i.submit('calc_pi %d' % (self.npoints / self.n_cpu))
|
||||
for _ in range(self.n_cpu)]
|
||||
|
||||
def close(self):
|
||||
self.i.close()
|
||||
|
||||
@plac.annotations(
|
||||
npoints=('npoints', 'positional', None, int))
|
||||
def calc_pi(self, npoints):
|
||||
counts = 0
|
||||
for j in xrange(npoints):
|
||||
n, r = divmod(j, 1000000)
|
||||
if r == 0:
|
||||
yield '%dM iterations' % n
|
||||
x, y = random(), random()
|
||||
if x*x + y*y < 1:
|
||||
counts += 1
|
||||
yield (4.0 * counts)/npoints
|
||||
|
||||
def run(self):
|
||||
tasks = self.i.tasks()
|
||||
for t in tasks:
|
||||
t.run()
|
||||
try:
|
||||
total = 0
|
||||
for task in tasks:
|
||||
total += task.result
|
||||
except: # the task was killed
|
||||
print tasks
|
||||
return
|
||||
return total / self.n_cpu
|
||||
|
||||
if __name__ == '__main__':
|
||||
pc = plac.call(PiCalculator)
|
||||
pc.submit_tasks()
|
||||
try:
|
||||
import time; t0 = time.time()
|
||||
print '%f in %f seconds ' % (pc.run(), time.time() - t0)
|
||||
finally:
|
||||
pc.close()
|
||||
+2967
File diff suppressed because it is too large
Load Diff
+12911
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
.. include:: plac_core.txt
|
||||
.. include:: plac_adv.txt
|
||||
+1221
File diff suppressed because it is too large
Load Diff
+1354
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,782 @@
|
||||
Plac: Parsing the Command Line the Easy Way
|
||||
=====================================================================
|
||||
|
||||
:Author: Michele Simionato
|
||||
:E-mail: michele.simionato@gmail.com
|
||||
:Date: August 2010
|
||||
:Download page: http://pypi.python.org/pypi/plac
|
||||
:Project page: http://micheles.googlecode.com/hg/plac/doc/plac.html
|
||||
:Requires: Python 2.3+
|
||||
:Installation: ``easy_install -U plac``
|
||||
:License: BSD license
|
||||
|
||||
.. contents::
|
||||
|
||||
The importance of scaling down
|
||||
------------------------------------------------
|
||||
|
||||
There is no want of command line arguments parsers in the Python
|
||||
world. The standard library alone contains three different modules:
|
||||
getopt_ (from the stone age),
|
||||
optparse_ (from Python 2.3) and argparse_ (from Python 2.7). All of
|
||||
them are quite powerful and especially argparse_ is an industrial
|
||||
strength solution; unfortunately, all of them feature a non-zero learning
|
||||
curve and a certain verbosity. They do not scale down well, at
|
||||
least in my opinion.
|
||||
|
||||
It should not be necessary to stress the importance `scaling down`_;
|
||||
nevertheless, a lot of people are obsessed with features and concerned with
|
||||
the possibility of scaling up, forgetting the equally important
|
||||
issue of scaling down. This is an old meme in
|
||||
the computing world: programs should address the common cases simply and
|
||||
simple things should be kept simple, while at the same keeping
|
||||
difficult things possible. plac_ adhere as much as possible to this
|
||||
philosophy and it is designed to handle well the simple cases, while
|
||||
retaining the ability to handle complex cases by relying on the
|
||||
underlying power of argparse_.
|
||||
|
||||
Technically plac_ is just a simple wrapper over argparse_ which hides
|
||||
most of its complexity by using a declarative interface: the argument
|
||||
parser is inferred rather than written down by imperatively. Still, plac_ is
|
||||
surprisingly scalable upwards, even without using the underlying
|
||||
argparse_. I have been using Python for 8 years and in my experience
|
||||
it is extremely unlikely that you will ever need to go beyond the
|
||||
features provided by the declarative interface of plac_: they should
|
||||
be more than enough for 99.9% of the use cases.
|
||||
|
||||
plac_ is targetting especially unsophisticated users,
|
||||
programmers, sys-admins, scientists and in general people writing
|
||||
throw-away scripts for themselves, choosing the command line
|
||||
interface because it is the quick and simple. Such users are not
|
||||
interested in features, they are interested in a small learning curve:
|
||||
they just want to be able to write a simple command line tool from a
|
||||
simple specification, not to build a command-line parser by
|
||||
hand. Unfortunately, the modules in the standard library forces them
|
||||
to go the hard way. They are designed to implement power user tools
|
||||
and they have a non-trivial learning curve. On the contrary, plac_
|
||||
is designed to be simple to use and extremely concise, as the examples
|
||||
below will show.
|
||||
|
||||
Scripts with required arguments
|
||||
---------------------------------------------
|
||||
|
||||
Let me start with the simplest possible thing: a script that takes a
|
||||
single argument and does something to it. It cannot get simpler
|
||||
than that, unless you consider a script without command-line
|
||||
arguments, where there is nothing to parse. Still, it is a use
|
||||
case *extremely common*: I need to write scripts like that nearly
|
||||
every day, I wrote hundreds of them in the last few years and I have
|
||||
never been happy. Here is a typical example of code I have been
|
||||
writing by hand for years:
|
||||
|
||||
.. include:: example1.py
|
||||
:literal:
|
||||
|
||||
As you see the whole ``if __name__ == '__main__'`` block (nine lines)
|
||||
is essentially boilerplate that should not exist. Actually I think
|
||||
the language should recognize the main function and pass to it the
|
||||
command-line arguments automatically; unfortunaly this is unlikely to
|
||||
happen. I have been writing boilerplate like this in hundreds of
|
||||
scripts for years, and every time I *hate* it. The purpose of using a
|
||||
scripting language is convenience and trivial things should be
|
||||
trivial. Unfortunately the standard library does not help for this
|
||||
incredibly common use case. Using getopt_ and optparse_ does not help,
|
||||
since they are intended to manage options and not positional
|
||||
arguments; the argparse_ module helps a bit and it is able to reduce
|
||||
the boilerplate from nine lines to six lines:
|
||||
|
||||
.. include:: example2.py
|
||||
:literal:
|
||||
|
||||
However saving three lines does not justify introducing the external
|
||||
dependency: most people will not switch to Python 2.7, which at the time of
|
||||
this writing is just about to be released, for many years.
|
||||
Moreover, it just feels too complex to instantiate a class and to
|
||||
define a parser by hand for such a trivial task.
|
||||
|
||||
The plac_ module is designed to manage well such use cases, and it is able
|
||||
to reduce the original nine lines of boiler plate to two lines. With the
|
||||
plac_ module all you need to write is
|
||||
|
||||
.. include:: example3.py
|
||||
:literal:
|
||||
|
||||
The plac_ module provides for free (actually the work is done by the
|
||||
underlying argparse_ module) a nice usage message::
|
||||
|
||||
$ python example3.py -h
|
||||
|
||||
.. include:: example3.help
|
||||
:literal:
|
||||
|
||||
Moreover plac_ manages the case of missing arguments and of too many arguments.
|
||||
This is only the tip of the iceberg: plac_ is able to do much more than that.
|
||||
|
||||
Scripts with default arguments
|
||||
--------------------------------------------------
|
||||
|
||||
The need to have suitable defaults for command-line scripts is quite
|
||||
common. For instance I have encountered this use case at work hundreds
|
||||
of times:
|
||||
|
||||
.. include:: example4.py
|
||||
:literal:
|
||||
|
||||
Here I want to perform a query on a database table, by extracting the
|
||||
most recent data: it makes sense for ``today`` to be a default argument.
|
||||
If there is a most used table (in this example a table called ``'product'``)
|
||||
it also makes sense to make it a default argument. Performing the parsing
|
||||
of the command-line arguments by hand takes 8 ugly lines of boilerplate
|
||||
(using argparse_ would require about the same number of lines).
|
||||
With plac_ the entire ``__main__`` block reduces to the usual two lines::
|
||||
|
||||
if __name__ == '__main__':
|
||||
import plac; plac.call(main)
|
||||
|
||||
In other words, six lines of boilerplate have been removed, and we get
|
||||
the usage message for free:
|
||||
|
||||
.. include:: example5.help
|
||||
:literal:
|
||||
|
||||
plac_ manages transparently even the case when you want to pass a
|
||||
variable number of arguments. Here is an example, a script running
|
||||
on a database a series of SQL scripts:
|
||||
|
||||
.. include:: example7.py
|
||||
:literal:
|
||||
|
||||
Here is the usage message:
|
||||
|
||||
.. include:: example7.help
|
||||
:literal:
|
||||
|
||||
The examples here should have made clear that *plac is able to figure out
|
||||
the command-line arguments parser to use from the signature of the main
|
||||
function*. This is the whole idea behind plac_: if the intent is clear,
|
||||
let's the machine take care of the details.
|
||||
|
||||
plac_ is inspired to an old Python Cookbook recipe (optionparse_), in
|
||||
the sense that it delivers the programmer from the burden of writing
|
||||
the parser, but is less of a hack: instead of extracting the parser
|
||||
from the docstring of the module, it extracts it from the signature of
|
||||
the ``main`` function.
|
||||
|
||||
The idea comes from the `function annotations` concept, a new
|
||||
feature of Python 3. An example is worth a thousand words, so here
|
||||
it is:
|
||||
|
||||
.. include:: example7_.py
|
||||
:literal:
|
||||
|
||||
Here the arguments of the ``main`` function have been annotated with
|
||||
strings which are intented to be used in the help message:
|
||||
|
||||
.. include:: example7_.help
|
||||
:literal:
|
||||
|
||||
plac_ is able to recognize much more complex annotations, as
|
||||
I will show in the next paragraphs.
|
||||
|
||||
|
||||
Scripts with options (and smart options)
|
||||
-----------------------------------------
|
||||
|
||||
It is surprising how few command-line scripts with options I have
|
||||
written over the years (probably less than a hundred), compared to the
|
||||
number of scripts with positional arguments I wrote (certainly more
|
||||
than a thousand of them). Still, this use case cannot be neglected.
|
||||
The standard library modules (all of them) are quite verbose when it
|
||||
comes to specifying the options and frankly I have never used them
|
||||
directly. Instead, I have always relied on the
|
||||
optionparse_ recipe, which provides a convenient wrapper over
|
||||
optionparse_. Alternatively, in the simplest cases, I have just
|
||||
performed the parsing by hand. In plac_ the parser is inferred by the
|
||||
function annotations. Here is an example:
|
||||
|
||||
.. include:: example8.py
|
||||
:literal:
|
||||
|
||||
Here the argument ``command`` has been annotated with the tuple
|
||||
``("SQL query", 'option', 'c')``: the first string is the help string
|
||||
which will appear in the usage message, the second string tells plac_
|
||||
that ``command`` is an option and the third string that there is also
|
||||
a short form of the option ``-c``, the long form being ``--command``.
|
||||
The usage message is the following:
|
||||
|
||||
.. include:: example8.help
|
||||
:literal:
|
||||
|
||||
Here are two examples of usage::
|
||||
|
||||
$ python3 example8.py -c"select * from table" dsn
|
||||
executing select * from table on dsn
|
||||
|
||||
$ python3 example8.py --command="select * from table" dsn
|
||||
executing select * from table on dsn
|
||||
|
||||
The third argument in the function annotation can be omitted: in such
|
||||
case it will be assumed to be ``None``. The consequence is that
|
||||
the usual dichotomy between long and short options (GNU-style options)
|
||||
disappears: we get *smart options*, which have the single character prefix
|
||||
of short options and behave like both long and short options, since
|
||||
they can be abbreviated. Here is an example featuring smart options:
|
||||
|
||||
.. include:: example6.py
|
||||
:literal:
|
||||
|
||||
.. include:: example6.help
|
||||
:literal:
|
||||
|
||||
The following are all valid invocations ot the script::
|
||||
|
||||
$ python3 example6.py -c "select" dsn
|
||||
executing 'select' on dsn
|
||||
$ python3 example6.py -com "select" dsn
|
||||
executing 'select' on dsn
|
||||
$ python3 example6.py -command="select" dsn
|
||||
executing 'select' on dsn
|
||||
|
||||
Notice that the form ``-command=SQL`` is recognized only for the full
|
||||
option, not for its abbreviations::
|
||||
|
||||
$ python3 example6.py -com="select" dsn
|
||||
usage: example6.py [-h] [-command COMMAND] dsn
|
||||
example6.py: error: unrecognized arguments: -com=select
|
||||
|
||||
If the option is not passed, the variable ``command``
|
||||
will get the value ``None``. However, it is possible to specify a non-trivial
|
||||
default. Here is an example:
|
||||
|
||||
.. include:: example8_.py
|
||||
:literal:
|
||||
|
||||
Notice that the default value appears in the help message:
|
||||
|
||||
.. include:: example8_.help
|
||||
:literal:
|
||||
|
||||
When you run the script and you do not pass the ``-command`` option, the
|
||||
default query will be executed::
|
||||
|
||||
$ python3 example8_.py dsn
|
||||
executing 'select * from table' on dsn
|
||||
|
||||
Scripts with flags
|
||||
--------------------
|
||||
|
||||
plac_ is able to recognize flags, i.e. boolean options which are
|
||||
``True`` if they are passed to the command line and ``False``
|
||||
if they are absent. Here is an example:
|
||||
|
||||
.. include:: example9.py
|
||||
:literal:
|
||||
|
||||
.. include:: example9.help
|
||||
:literal:
|
||||
|
||||
::
|
||||
|
||||
$ python3 example9.py -v dsn
|
||||
connecting to dsn
|
||||
|
||||
Notice that it is an error trying to specify a default for flags: the
|
||||
default value for a flag is always ``False``. If you feel the need to
|
||||
implement non-boolean flags, you should use an option with two
|
||||
choices, as explained in the "more features" section.
|
||||
|
||||
For consistency with the way the usage message is printed, I suggest
|
||||
you to follow the Flag-Option-Required-Default (FORD) convention: in
|
||||
the ``main`` function write first the flag arguments, then the option
|
||||
arguments, then the required arguments and finally the default
|
||||
arguments. This is just a convention and you are not forced to use it,
|
||||
except for the default arguments (including the varargs) which must
|
||||
stay at the end as it is required by the Python syntax.
|
||||
|
||||
I also suggests to specify a one-character abbreviation for flags: in
|
||||
this way you can use the GNU-style composition of flags (i.e. ``-zxvf``
|
||||
is an abbreviation of ``-z -x -v -f``). I usually do not provide
|
||||
the one-character abbreviation for options, since it does not make sense
|
||||
to compose them.
|
||||
|
||||
plac for Python 2.X users
|
||||
--------------------------------------------------
|
||||
|
||||
I do not use Python 3. At work we are just starting to think about
|
||||
migrating to Python 2.6. It will take years before we
|
||||
think to migrate to Python 3. I am pretty much sure most Pythonistas
|
||||
are in the same situation. Therefore plac_ provides a way to work
|
||||
with function annotations even in Python 2.X (including Python 2.3).
|
||||
There is no magic involved; you just need to add the annotations
|
||||
by hand. For instance the annotated function declaration
|
||||
|
||||
::
|
||||
|
||||
def main(dsn: "Database dsn", *scripts: "SQL scripts"):
|
||||
...
|
||||
|
||||
is equivalent to the following code::
|
||||
|
||||
def main(dsn, *scripts):
|
||||
...
|
||||
main.__annotations__ = dict(
|
||||
dsn="Database dsn",
|
||||
scripts="SQL scripts")
|
||||
|
||||
One should be careful to match the keys of the annotation dictionary
|
||||
with the names of the arguments in the annotated function; for lazy
|
||||
people with Python 2.4 available the simplest way is to use the
|
||||
``plac.annotations`` decorator that performs the check for you::
|
||||
|
||||
@plac.annotations(
|
||||
dsn="Database dsn",
|
||||
scripts="SQL scripts")
|
||||
def main(dsn, *scripts):
|
||||
...
|
||||
|
||||
In the rest of this article I will assume that you are using Python 2.X with
|
||||
X >= 4 and I will use the ``plac.annotations`` decorator. Notice however
|
||||
that the core features of plac_ run even on Python 2.3.
|
||||
|
||||
More features
|
||||
--------------------------------------------------
|
||||
|
||||
One of the goals of plac_ is to have a learning curve of *minutes* for
|
||||
its core features, compared to the learning curve of *hours* of
|
||||
argparse_. In order to reach this goal, I have *not* sacrificed all
|
||||
the features of argparse_. Actually a lot of argparse_ power persists
|
||||
in plac_. Until now, I have only showed simple annotations, but in
|
||||
general an annotation is a 6-tuple of the form
|
||||
|
||||
``(help, kind, abbrev, type, choices, metavar)``
|
||||
|
||||
where ``help`` is the help message, ``kind`` is a string in the set {
|
||||
``"flag"``, ``"option"``, ``"positional"``}, ``abbrev`` is a
|
||||
one-character string or ``None``, ``type`` is a callable taking a
|
||||
string in input,
|
||||
``choices`` is a discrete sequence of values and ``metavar`` is a string.
|
||||
|
||||
``type`` is used to automagically convert the command line arguments
|
||||
from the string type to any Python type; by default there is no
|
||||
conversion and ``type=None``.
|
||||
|
||||
``choices`` is used to restrict the number of the valid
|
||||
options; by default there is no restriction i.e. ``choices=None``.
|
||||
|
||||
``metavar`` has two meanings. For a positional argument it is used to
|
||||
change the argument name in the usage message (and only there). By
|
||||
default the metavar is ``None`` and the name in the usage message is
|
||||
the same as the argument name. For an option
|
||||
the ``metavar`` is used differently in the usage message, which has
|
||||
now the form ``[--option-name METAVAR]``. If the ``metavar`` is ``None``,
|
||||
then it is equal to the uppercased name of the argument, unless the
|
||||
argument has a default and in such a case is equal to the stringified
|
||||
form of the default.
|
||||
|
||||
Here is an example showing many of the features (copied from the
|
||||
argparse_ documentation):
|
||||
|
||||
.. include:: example10.py
|
||||
:literal:
|
||||
|
||||
Here is the usage:
|
||||
|
||||
.. include:: example10.help
|
||||
:literal:
|
||||
|
||||
Notice that the docstring of the ``main`` function has been automatically added
|
||||
to the usage message. Here are a couple of examples of use::
|
||||
|
||||
$ python example10.py add 1 2 3 4
|
||||
10.0
|
||||
$ python example10.py mul 1 2 3 4
|
||||
24.0
|
||||
$ python example10.py ad 1 2 3 4 # a mispelling error
|
||||
usage: example10.py [-h] {add,mul} [n [n ...]]
|
||||
example10.py: error: argument operator: invalid choice: 'ad' (choose from 'add', 'mul')
|
||||
|
||||
``plac.call`` can also be used in doctests like this:
|
||||
|
||||
>>> import plac, example10
|
||||
>>> plac.call(example10.main, ['add', '1', '2'])
|
||||
3.0
|
||||
|
||||
``plac.call`` works for generators too:
|
||||
|
||||
>>> def main(n):
|
||||
... for i in range(int(n)):
|
||||
... yield i
|
||||
>>> plac.call(main, ['3'])
|
||||
[0, 1, 2]
|
||||
|
||||
Internally ``plac.call`` tries to convert the output of the main function
|
||||
into a list, if possible. If the output is not iterable or it is a
|
||||
string, it is left unchanged, but if it is iterable it is converted.
|
||||
In particular, generator objects are exhausted by ``plac.call``.
|
||||
|
||||
This behavior avoids mistakes like forgetting of applying
|
||||
``list(result)`` to the result of ``plac.call``; moreover it makes
|
||||
errors visible early, and avoids mistakes in code like the following::
|
||||
|
||||
try:
|
||||
result = plac.call(main, args)
|
||||
except:
|
||||
# do something
|
||||
|
||||
Without the "listify" functionality, a main function returning a
|
||||
generator object would not raise any exception until the generator
|
||||
is iterated over.
|
||||
|
||||
If you are a fan of lazyness, you can still have it by setting the ``eager``
|
||||
flag to ``False``, as in the following example::
|
||||
|
||||
for line in plac.call(main, args, eager=False):
|
||||
print(line)
|
||||
|
||||
If ``main`` returns a generator object this example will print each
|
||||
line as soon as available, whereas the default behaviour is to print
|
||||
all the lines together and the end of the computation.
|
||||
|
||||
A realistic example
|
||||
---------------------------------------
|
||||
|
||||
Here is a more realistic script using most of the features of plac_ to
|
||||
run SQL queries on a database by relying on SQLAlchemy_. Notice the usage
|
||||
of the ``type`` feature to automagically convert a SQLAlchemy connection
|
||||
string into a SqlSoup_ object:
|
||||
|
||||
.. include:: dbcli.py
|
||||
:literal:
|
||||
|
||||
You can see the *yield-is-print* pattern here: instead of using
|
||||
``print`` in the main function, I use ``yield``, and I perform the
|
||||
print in the ``__main__`` block. The advantage of the pattern is that
|
||||
tests invoking ``plac.call`` and checking the result become trivial:
|
||||
had I performed the printing in the main function, the test would have
|
||||
involved an ugly hack like redirecting ``sys.stdout`` to a
|
||||
``StringIO`` object.
|
||||
|
||||
Here is the usage message:
|
||||
|
||||
.. include:: dbcli.help
|
||||
:literal:
|
||||
|
||||
You can check for yourself that the script works.
|
||||
|
||||
Keyword arguments
|
||||
---------------------------------------
|
||||
|
||||
Starting from release 0.4, plac_ supports keyword arguments. In
|
||||
practice that means that if your main function has keyword arguments,
|
||||
plac_ treats specially arguments of the form ``"name=value"`` in the
|
||||
command line. Here is an example:
|
||||
|
||||
.. include:: example12.py
|
||||
:literal:
|
||||
|
||||
Here is the generated usage message:
|
||||
|
||||
.. include:: example12.help
|
||||
:literal:
|
||||
|
||||
Here is how you call the script::
|
||||
|
||||
$ python example12.py -o X a1 a2 name=value
|
||||
opt=X
|
||||
args=('a1', 'a2')
|
||||
kw={'name': 'value'}
|
||||
|
||||
When using keyword arguments, one must be careful to use names which
|
||||
are not alreay taken; for instance in this examples the name ``opt``
|
||||
is taken::
|
||||
|
||||
$ python example12.py 1 2 kw1=1 kw2=2 opt=0
|
||||
usage: example12.py [-h] [-o OPT] [args [args ...]] [kw [kw ...]]
|
||||
example12.py: error: colliding keyword arguments: opt
|
||||
|
||||
The names taken are the names of the flags, of the options, and of the
|
||||
positional arguments, excepted varargs and keywords. This limitation
|
||||
is a consequence of the way the argument names are managed in function calls
|
||||
by the Python language.
|
||||
|
||||
Final example: a shelve interface
|
||||
----------------------------------------------------------
|
||||
|
||||
Here is a less trivial example for the keyword arguments feature.
|
||||
The use case is the following: suppose we have stored the
|
||||
configuration parameters of a given application into a Python shelve
|
||||
and we need a command-line tool to edit the shelve.
|
||||
A possible implementation using plac_ could be the following:
|
||||
|
||||
.. include:: ishelve.py
|
||||
:literal:
|
||||
|
||||
A few notes are in order:
|
||||
|
||||
1. I have disabled the ordinary help provided by argparse_ and I have
|
||||
implemented a custom help command.
|
||||
2. I have changed the prefix character used to recognize the options
|
||||
to a dot.
|
||||
3. Keyword arguments recognition (in the ``**setters``) is used to make it
|
||||
possible to store a value in the shelve with the syntax
|
||||
``param_name=param_value``.
|
||||
4. ``*params`` are used to retrieve parameters from the shelve and some
|
||||
error checking is performed in the case of missing parameters
|
||||
5. A command to clear the shelve is implemented as a flag (``.clear``).
|
||||
6. A command to delete a given parameter is implemented as an option
|
||||
(``.delete``).
|
||||
7. There is an option with default (``.filename=conf.shelve``) to store
|
||||
the filename of the shelve.
|
||||
8. All things considered, the code looks like a poor man object oriented
|
||||
interface implemented with a chain of elifs instead of methods. Of course,
|
||||
plac_ can do better than that, but let me start from a low-level approach
|
||||
first.
|
||||
|
||||
If you run ``ishelve.py`` without arguments you get the following
|
||||
message::
|
||||
|
||||
$ python ishelve.py
|
||||
no arguments passed, use .help to see the available commands
|
||||
|
||||
If you run ``ishelve.py`` with the option ``.h`` (or any abbreviation
|
||||
of ``.help``) you get::
|
||||
|
||||
$ python ishelve.py .h
|
||||
Commands: .help, .showall, .clear, .delete
|
||||
<param> ...
|
||||
<param=value> ...
|
||||
|
||||
You can check by hand that the tool work::
|
||||
|
||||
$ python ishelve.py .clear # start from an empty shelve
|
||||
cleared the shelve
|
||||
$ python ishelve.py a=1 b=2
|
||||
setting a=1
|
||||
setting b=2
|
||||
$ python ishelve.py .showall
|
||||
b=2
|
||||
a=1
|
||||
$ python ishelve.py .del b # abbreviation for .delete
|
||||
deleted b
|
||||
$ python ishelve.py a
|
||||
1
|
||||
$ python ishelve.py b
|
||||
b: not found
|
||||
$ python ishelve.py .cler # mispelled command
|
||||
usage: ishelve.py [.help] [.showall] [.clear] [.delete DELETE]
|
||||
[.filename /home/micheles/conf.shelve]
|
||||
[params [params ...]] [setters [setters ...]]
|
||||
ishelve.py: error: unrecognized arguments: .cler
|
||||
|
||||
plac vs argparse
|
||||
---------------------------------------------
|
||||
|
||||
plac_ is opinionated and by design it does not try to make available
|
||||
all of the features of argparse_ in an easy way. In particular you
|
||||
should be aware of the following limitations/differences (the
|
||||
following assumes knowledge of argparse_):
|
||||
|
||||
- plac does not support the destination concept: the destination
|
||||
coincides with the name of the argument, always. This restriction
|
||||
has some drawbacks. For instance, suppose you want to define a long
|
||||
option called ``--yield``. In this case the destination would be ``yield``,
|
||||
which is a Python keyword, and since you cannot introduce an
|
||||
argument with that name in a function definition, it is impossible
|
||||
to implement it. Your choices are to change the name of the long
|
||||
option, or to use argparse_ with a suitable destination.
|
||||
|
||||
- plac_ does not support "required options". As the argparse_
|
||||
documentation puts it: *Required options are generally considered bad
|
||||
form - normal users expect options to be optional. You should avoid
|
||||
the use of required options whenever possible.* Notice that since
|
||||
argparse_ supports them, plac_ can manage them too, but not directly.
|
||||
|
||||
- plac_ supports only regular boolean flags. argparse_ has the ability to
|
||||
define generalized two-value flags with values different from ``True``
|
||||
and ``False``. An earlier version of plac_ had this feature too, but
|
||||
since you can use options with two choices instead, and in any case
|
||||
the conversion from ``{True, False}`` to any couple of values
|
||||
can be trivially implemented with a ternary operator
|
||||
(``value1 if flag else value2``), I have removed it (KISS rules!).
|
||||
|
||||
- plac_ does not support ``nargs`` options directly (it uses them internally,
|
||||
though, to implement flag recognition). The reason it that all the use
|
||||
cases of interest to me are covered by plac_ and did not feel the need
|
||||
to increase the learning curve by adding direct support for ``nargs``.
|
||||
|
||||
- plac_ does support subparsers, but you must read the `advanced usage
|
||||
document`_ to see how it works.
|
||||
|
||||
- plac_ does not support actions directly. This also
|
||||
looks like a feature too advanced for the goals of plac_. Notice however
|
||||
that the ability to define your own annotation objects (again, see
|
||||
the `advanced usage document`_) may mitigate the need for custom actions.
|
||||
|
||||
plac_ can leverage directly on many argparse_ features.
|
||||
|
||||
For instance, you can make invisible an argument in the usage message
|
||||
simply by using ``'==SUPPRESS=='`` as help string (or
|
||||
``argparse.SUPPRESS``). Similarly, you can use argparse.FileType_
|
||||
directly.
|
||||
|
||||
It is also possible to pass options to the underlying
|
||||
``argparse.ArgumentParser`` object (currently it accepts the default
|
||||
arguments ``description``, ``epilog``, ``prog``, ``usage``,
|
||||
``add_help``, ``argument_default``, ``parents``, ``prefix_chars``,
|
||||
``fromfile_prefix_chars``, ``conflict_handler``, ``formatter_class``).
|
||||
It is enough to set such attributes on the ``main`` function. For
|
||||
instance
|
||||
|
||||
::
|
||||
|
||||
def main(...):
|
||||
pass
|
||||
|
||||
main.add_help = False
|
||||
|
||||
disables the recognition of the help flag ``-h, --help``. This
|
||||
mechanism does not look particularly elegant, but it works well
|
||||
enough. I assume that the typical user of plac_ will be happy with
|
||||
the defaults and would not want to change them; still it is possible
|
||||
if she wants to.
|
||||
|
||||
For instance, by setting the ``description`` attribute, it is possible
|
||||
to add a comment to the usage message (by default the docstring of the
|
||||
``main`` function is used as description).
|
||||
|
||||
It is also possible to change the option prefix; for
|
||||
instance if your script must run under Windows and you want to use "/"
|
||||
as option prefix you can add the line::
|
||||
|
||||
main.prefix_chars='/-'
|
||||
|
||||
The first prefix char (``/``) is used
|
||||
as the default for the recognition of options and flags;
|
||||
the second prefix char (``-``) is kept to keep the ``-h/--help`` option
|
||||
working: however you can disable it and reimplement it, if you like,
|
||||
as seen in the ``ishelve`` example.
|
||||
|
||||
It is possible to access directly the underlying ArgumentParser_ object, by
|
||||
invoking the ``plac.parser_from`` utility function:
|
||||
|
||||
>>> import plac
|
||||
>>> def main(arg):
|
||||
... pass
|
||||
...
|
||||
>>> print(plac.parser_from(main)) #doctest: +ELLIPSIS
|
||||
ArgumentParser(prog=...)
|
||||
|
||||
Internally ``plac.call`` uses ``plac.parser_from`` and adds the parser
|
||||
to the main function as an attribute. When ``plac.call(func)`` is
|
||||
invoked multiple time, the parser is re-used and not rebuilt from scratch again.
|
||||
|
||||
I use ``plac.parser_from`` in the unit tests of the module, but regular
|
||||
users should not need to use it, unless they want to access *all*
|
||||
of the features of argparse_ directly without calling the main function.
|
||||
|
||||
Interested readers should read the documentation of argparse_ to
|
||||
understand the meaning of the other options. If there is a set of
|
||||
options that you use very often, you may consider writing a decorator
|
||||
adding such options to the ``main`` function for you. For simplicity,
|
||||
plac_ does not perform any magic except the addition of the ``.p``
|
||||
attribute.
|
||||
|
||||
plac vs the rest of the world
|
||||
------------------------------------------
|
||||
|
||||
Originally plac_ boasted about being "the easiest command-line
|
||||
arguments parser in the world". Since then, people started pointing
|
||||
out to me various projects which are based on the same idea
|
||||
(extracting the parser from the main function signature) and are
|
||||
arguably even easier than plac_:
|
||||
|
||||
- opterator_ by Dusty Phillips
|
||||
- CLIArgs_ by Pavel Panchekha
|
||||
|
||||
Luckily for me none of such projects had the idea of using
|
||||
function annotations and argparse_; as a consequence, they are
|
||||
no match for the capabilities of plac_.
|
||||
|
||||
Of course, there are tons of other libraries to parse the command
|
||||
line. For instance Clap_ by Matthew Frazier which appeared on PyPI
|
||||
just the day before plac_; Clap_ is fine but it is certainly not
|
||||
easier than plac_.
|
||||
|
||||
plac_ can also be used as a replacement of the cmd_ module in the standard
|
||||
library and as such it shares many features with the module cmd2_ by
|
||||
Catherine Devlin. However, this is completely coincidental, since I became
|
||||
aware of the cmd2_ module only after writing plac_.
|
||||
|
||||
The future
|
||||
-------------------------------
|
||||
|
||||
Currently the core of plac_ is around 200 lines of code, not counting blanks,
|
||||
comments and docstrings. I do not plan to extend the core much in the
|
||||
future. The idea is to keep the module short: it is and it should
|
||||
remain a little wrapper over argparse_. Actually I have thought about
|
||||
contributing the core back to argparse_ if plac_ becomes successfull
|
||||
and gains a reasonable number of users. For the moment it should be
|
||||
considered in alpha status.
|
||||
|
||||
Notice that even if plac_ has been designed to be simple to use for
|
||||
simple stuff, its power should not be underestimated; it is actually a
|
||||
quite advanced tool with a domain of applicability which far exceeds
|
||||
the realm of command-line arguments parsers.
|
||||
|
||||
Version 0.5 of plac_ doubled the code base and the documentation: it is
|
||||
based on the idea of using plac_ to implement command-line interpreters,
|
||||
i.e. something akin to the ``cmd`` module in the standard library, only better.
|
||||
The new features of plac_ are described in the `advanced usage document`_ .
|
||||
They are implemented in a separated module (``plac_ext.py``), since
|
||||
they require Python 2.5 to work, whereas ``plac_core.py`` only requires
|
||||
Python 2.3.
|
||||
|
||||
Trivia: the story behind the name
|
||||
-----------------------------------------
|
||||
|
||||
The plac_ project started very humbly: I just wanted to make
|
||||
easy_installable my old optionparse_ recipe, and to publish it on PyPI.
|
||||
The original name of plac_ was optionparser and the idea behind it was
|
||||
to build an OptionParser_ object from the docstring of the module.
|
||||
However, before doing that, I decided to check out the argparse_ module,
|
||||
since I knew it was going into Python 2.7 and Python 2.7 was coming out.
|
||||
Soon enough I realized two things:
|
||||
|
||||
1. the single greatest idea of argparse_ was unifying the positional arguments
|
||||
and the options in a single namespace object;
|
||||
2. parsing the docstring was so old-fashioned, considering the existence
|
||||
of functions annotations in Python 3.
|
||||
|
||||
Putting together these two observations with the original idea of inferring the
|
||||
parser I decided to build an ArgumentParser_ object from function
|
||||
annotations. The ``optionparser`` name was ruled out, since I was
|
||||
now using argparse_; a name like ``argparse_plus`` was also ruled out,
|
||||
since the typical usage was completely different from the argparse_ usage.
|
||||
|
||||
I made a research on PyPI and the name *clap* (Command Line Arguments Parser)
|
||||
was not taken, so I renamed everything to clap. After two days
|
||||
a Clap_ module appeared on PyPI <expletives deleted>!
|
||||
|
||||
Having little imagination, I decided to rename everything again to plac,
|
||||
an anagram of clap: since it is a non-existing English name, I hope nobody
|
||||
will steal it from me!
|
||||
|
||||
That's all, I hope you will enjoy working with plac_!
|
||||
|
||||
.. _argparse: http://argparse.googlecode.com
|
||||
.. _optparse: http://docs.python.org/library/optparse.html
|
||||
.. _getopt: http://docs.python.org/library/getopt.html
|
||||
.. _optionparse: http://code.activestate.com/recipes/278844-parsing-the-command-line/
|
||||
.. _plac: http://pypi.python.org/pypi/plac
|
||||
.. _scaling down: http://www.welton.it/articles/scalable_systems
|
||||
.. _ArgumentParser: http://argparse.googlecode.com/svn/tags/r11/doc/ArgumentParser.html
|
||||
.. _argparse.FileType: http://argparse.googlecode.com/svn/tags/r11/doc/other-utilities.html?highlight=filetype#FileType
|
||||
.. _Clap: http://pypi.python.org/pypi/Clap/0.7
|
||||
.. _OptionParser: http://docs.python.org/library/optparse.html?highlight=optionparser#optparse.OptionParser
|
||||
.. _SQLAlchemy: http://www.sqlalchemy.org/
|
||||
.. _SqlSoup: http://www.sqlalchemy.org/docs/reference/ext/sqlsoup.html
|
||||
.. _CLIArgs: http://pypi.python.org/pypi/CLIArgs
|
||||
.. _opterator: http://pypi.python.org/pypi/opterator
|
||||
.. _advanced usage document: in-writing
|
||||
.. _cmd2: http://packages.python.org/cmd2/
|
||||
.. _cmd: http://docs.python.org/library/cmd.html
|
||||
@@ -0,0 +1,24 @@
|
||||
# literal translation from http://groups.google.com/group/comp.lang.python/msg/de7c188e705f8eb2?hl=en
|
||||
# parser = optparse.OptionParser("usage: %lines [options] arg1")
|
||||
# parser.add_option("-l", "--lines", dest="lines",
|
||||
# default=10, type="int",
|
||||
# help="number of lines")
|
||||
# parser.add_option("-t", "--topbottom", dest="topbottom",
|
||||
# default="T", type="str",
|
||||
# help="T(op) or B(ottom)")
|
||||
# (options, args) = parser.parse_args()
|
||||
# if len(args) != 1:
|
||||
# parser.error("incorrect number of arguments")
|
||||
# lines=options.lines
|
||||
# tb=options.topbottom
|
||||
|
||||
import plac
|
||||
|
||||
@plac.annotations(
|
||||
lines=('number of lines', 'option', 'l', int),
|
||||
topbottom=('T(op) or B(ottom)', 'option', 't', str, 'TB'))
|
||||
def main(arg, lines=10, topbottom='T'):
|
||||
print arg, lines, topbottom
|
||||
|
||||
if __name__ == '__main__':
|
||||
plac.call(main)
|
||||
@@ -0,0 +1,13 @@
|
||||
"""
|
||||
You can run this script as
|
||||
$ python read_stdin.py < ishelve.bat
|
||||
"""
|
||||
from __future__ import with_statement
|
||||
import sys
|
||||
from ishelve import ishelve
|
||||
import plac
|
||||
|
||||
if __name__ == '__main__':
|
||||
with plac.Interpreter(ishelve) as i:
|
||||
for line in sys.stdin:
|
||||
print(i.send(line))
|
||||
@@ -0,0 +1,9 @@
|
||||
import plac
|
||||
from importer2 import FakeImporter
|
||||
|
||||
def main(port=2199):
|
||||
main = FakeImporter('dsn')
|
||||
plac.Interpreter(main).start_server(port)
|
||||
|
||||
if __name__ == '__main__':
|
||||
plac.call(main)
|
||||
@@ -0,0 +1,13 @@
|
||||
usage: shelve_interpreter.py [-h] [-interactive]
|
||||
[subcommands [subcommands ...]]
|
||||
|
||||
This script works both interactively and non-interactively.
|
||||
Use .help to see the internal commands.
|
||||
|
||||
|
||||
positional arguments:
|
||||
subcommands the commands of the underlying ishelve interpreter
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-interactive start interactive interface
|
||||
@@ -0,0 +1,19 @@
|
||||
# shelve_interpreter.py
|
||||
import plac, ishelve
|
||||
|
||||
@plac.annotations(
|
||||
interactive=('start interactive interface', 'flag'),
|
||||
subcommands='the commands of the underlying ishelve interpreter')
|
||||
def main(interactive, *subcommands):
|
||||
"""
|
||||
This script works both interactively and non-interactively.
|
||||
Use .help to see the internal commands.
|
||||
"""
|
||||
if interactive:
|
||||
plac.Interpreter(ishelve.main).interact()
|
||||
else:
|
||||
for out in plac.call(ishelve.main, subcommands):
|
||||
print(out)
|
||||
|
||||
if __name__ == '__main__':
|
||||
plac.call(main)
|
||||
@@ -0,0 +1,28 @@
|
||||
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, argstring):
|
||||
sql = 'SELECT ' + argstring
|
||||
for row in self.soup.bind.execute(sql):
|
||||
yield str(row) # the formatting can be much improved
|
||||
|
||||
rl_input = plac.ReadlineInput(
|
||||
COMPLETIONS, histfile=os.path.expanduser('~/.sql_interface.history'),
|
||||
case_sensitive=False)
|
||||
|
||||
def split_on_first_space(line, commentchar):
|
||||
return line.strip().split(' ', 1) # ignoring comments
|
||||
|
||||
if __name__ == '__main__':
|
||||
plac.Interpreter.call(SqlInterface, split=split_on_first_space,
|
||||
stdin=rl_input, prompt='sql> ')
|
||||
@@ -0,0 +1,11 @@
|
||||
from __future__ import with_statement
|
||||
import plac, plac_ext
|
||||
|
||||
def main(*args):
|
||||
yield args
|
||||
|
||||
if __name__ == '__main__':
|
||||
proc = plac_ext.SyncProcess(['syncproc.py'])
|
||||
while True:
|
||||
inp = raw_input(proc.recv())
|
||||
proc.send(inp)
|
||||
@@ -0,0 +1,12 @@
|
||||
# test_ishelve.py
|
||||
import plac, ishelve
|
||||
|
||||
def test():
|
||||
assert plac.call(ishelve.main, ['.clear']) == ['cleared the shelve']
|
||||
assert plac.call(ishelve.main, ['a=1']) == ['setting a=1']
|
||||
assert plac.call(ishelve.main, ['a']) == ['1']
|
||||
assert plac.call(ishelve.main, ['.delete=a']) == ['deleted a']
|
||||
assert plac.call(ishelve.main, ['a']) == ['a: not found']
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
||||
@@ -0,0 +1,11 @@
|
||||
# test_ishelve_more.py
|
||||
from __future__ import with_statement
|
||||
import plac, ishelve
|
||||
|
||||
def test():
|
||||
with plac.Interpreter(ishelve.main) as i:
|
||||
i.check('.clear', 'cleared the shelve')
|
||||
i.check('a=1', 'setting a=1')
|
||||
i.check('a', '1')
|
||||
i.check('.delete=a', 'deleted a')
|
||||
i.check('a', 'a: not found')
|
||||
@@ -0,0 +1,223 @@
|
||||
"""
|
||||
The tests are runnable with nose, with py.test, or even as standalone script
|
||||
"""
|
||||
|
||||
import os, sys, doctest, subprocess
|
||||
import plac
|
||||
|
||||
sys_argv0 = sys.argv[0]
|
||||
os.chdir(os.path.dirname(__file__) or '.') # work in the current directory
|
||||
|
||||
######################## helpers #######################
|
||||
|
||||
def expect(errclass, func, *args, **kw):
|
||||
try:
|
||||
func(*args, **kw)
|
||||
except errclass:
|
||||
pass
|
||||
else:
|
||||
raise RuntimeError('%s expected, got none!', errclass.__name__)
|
||||
|
||||
def parser_from(f, **kw):
|
||||
f.__annotations__ = kw
|
||||
return plac.parser_from(f)
|
||||
|
||||
def check_help(name):
|
||||
sys.argv[0] = name + '.py' # avoid issue with nosetests
|
||||
plac.parser_registry.clear() # makes different imports independent
|
||||
try:
|
||||
try:
|
||||
main = plac.import_main(name + '.py')
|
||||
except SyntaxError:
|
||||
if sys.version < '3': # expected for Python 2.X
|
||||
return
|
||||
else: # not expected for Python 3.X
|
||||
raise
|
||||
p = plac.parser_from(main)
|
||||
expected = open(name + '.help').read().strip()
|
||||
got = p.format_help().strip()
|
||||
assert got == expected, got
|
||||
finally:
|
||||
sys.argv[0] = sys_argv0
|
||||
|
||||
####################### tests ############################
|
||||
|
||||
def test_expected_help():
|
||||
for fname in os.listdir('.'):
|
||||
if fname.endswith('.help'):
|
||||
name = fname[:-5]
|
||||
if name not in ('vcs', 'ishelve'):
|
||||
yield check_help, fname[:-5]
|
||||
|
||||
p1 = parser_from(lambda delete, *args: None,
|
||||
delete=('delete a file', 'option'))
|
||||
|
||||
def test_p1():
|
||||
arg = p1.parse_args(['-d', 'foo', 'arg1', 'arg2'])
|
||||
assert arg.delete == 'foo'
|
||||
assert arg.args == ['arg1', 'arg2']
|
||||
|
||||
arg = p1.parse_args([])
|
||||
assert arg.delete is None, arg.delete
|
||||
assert arg.args == [], arg.args
|
||||
|
||||
p2 = parser_from(lambda arg1, delete, *args: None,
|
||||
delete=('delete a file', 'option', 'd'))
|
||||
|
||||
def test_p2():
|
||||
arg = p2.parse_args(['-d', 'foo', 'arg1', 'arg2'])
|
||||
assert arg.delete == 'foo', arg.delete
|
||||
assert arg.arg1 == 'arg1', arg.arg1
|
||||
assert arg.args == ['arg2'], arg.args
|
||||
|
||||
arg = p2.parse_args(['arg1'])
|
||||
assert arg.delete is None, arg.delete
|
||||
assert arg.args == [], arg.args
|
||||
assert arg, arg
|
||||
|
||||
expect(SystemExit, p2.parse_args, [])
|
||||
|
||||
p3 = parser_from(lambda arg1, delete: None,
|
||||
delete=('delete a file', 'option', 'd'))
|
||||
|
||||
def test_p3():
|
||||
arg = p3.parse_args(['arg1'])
|
||||
assert arg.delete is None, arg.delete
|
||||
assert arg.arg1 == 'arg1', arg.args
|
||||
|
||||
expect(SystemExit, p3.parse_args, ['arg1', 'arg2'])
|
||||
expect(SystemExit, p3.parse_args, [])
|
||||
|
||||
p4 = parser_from(lambda delete, delete_all, color="black": None,
|
||||
delete=('delete a file', 'option', 'd'),
|
||||
delete_all=('delete all files', 'flag', 'a'),
|
||||
color=('color', 'option', 'c'))
|
||||
|
||||
def test_p4():
|
||||
arg = p4.parse_args(['-a'])
|
||||
assert arg.delete_all is True, arg.delete_all
|
||||
|
||||
arg = p4.parse_args([])
|
||||
|
||||
arg = p4.parse_args(['--color=black'])
|
||||
assert arg.color == 'black'
|
||||
|
||||
arg = p4.parse_args(['--color=red'])
|
||||
assert arg.color == 'red'
|
||||
|
||||
def test_flag_with_default():
|
||||
expect(TypeError, parser_from, lambda yes_or_no='no': None,
|
||||
yes_or_no=('A yes/no flag', 'flag', 'f'))
|
||||
|
||||
def assert_usage(parser, expected):
|
||||
usage = parser.format_usage()
|
||||
assert usage == expected, usage
|
||||
|
||||
def test_metavar_no_defaults():
|
||||
sys.argv[0] = 'test_plac.py'
|
||||
|
||||
# positional
|
||||
p = parser_from(lambda x: None,
|
||||
x=('first argument', 'positional', None, str, [], 'METAVAR'))
|
||||
assert_usage(p, 'usage: test_plac.py [-h] METAVAR\n')
|
||||
|
||||
# option
|
||||
p = parser_from(lambda x: None,
|
||||
x=('first argument', 'option', None, str, [], 'METAVAR'))
|
||||
assert_usage(p, 'usage: test_plac.py [-h] [-x METAVAR]\n')
|
||||
sys.argv[0] = sys_argv0
|
||||
|
||||
def test_metavar_with_defaults():
|
||||
sys.argv[0] = 'test_plac.py'
|
||||
|
||||
# positional
|
||||
p = parser_from(lambda x='a': None,
|
||||
x=('first argument', 'positional', None, str, [], 'METAVAR'))
|
||||
assert_usage(p, 'usage: test_plac.py [-h] [METAVAR]\n')
|
||||
|
||||
# option
|
||||
p = parser_from(lambda x='a': None,
|
||||
x=('first argument', 'option', None, str, [], 'METAVAR'))
|
||||
assert_usage(p, 'usage: test_plac.py [-h] [-x METAVAR]\n')
|
||||
|
||||
p = parser_from(lambda x='a': None,
|
||||
x=('first argument', 'option', None, str, []))
|
||||
assert_usage(p, 'usage: test_plac.py [-h] [-x a]\n')
|
||||
|
||||
sys.argv[0] = sys_argv0
|
||||
|
||||
def test_kwargs():
|
||||
def main(opt, arg1, *args, **kw):
|
||||
print(opt, arg1)
|
||||
return args, kw
|
||||
main.__annotations__ = dict(opt=('Option', 'option'))
|
||||
argskw = plac.call(main, ['arg1', 'arg2', 'a=1', 'b=2'])
|
||||
assert argskw == [('arg2',), {'a': '1', 'b': '2'}], argskw
|
||||
|
||||
argskw = plac.call(main, ['arg1', 'arg2', 'a=1', '-o', '2'])
|
||||
assert argskw == [('arg2',), {'a': '1'}], argskw
|
||||
|
||||
expect(SystemExit, plac.call, main, ['arg1', 'arg2', 'a=1', 'opt=2'])
|
||||
|
||||
class Cmds(object):
|
||||
add_help = False
|
||||
commands = 'help', 'commit'
|
||||
def help(self, name):
|
||||
return 'help', name
|
||||
def commit(self):
|
||||
return 'commit'
|
||||
|
||||
cmds = Cmds()
|
||||
|
||||
def test_cmds():
|
||||
assert 'commit' == plac.call(cmds, ['commit'])
|
||||
assert ['help', 'foo'] == plac.call(cmds, ['help', 'foo'])
|
||||
expect(SystemExit, plac.call, cmds, [])
|
||||
|
||||
def test_cmd_abbrevs():
|
||||
assert 'commit' == plac.call(cmds, ['comm'])
|
||||
assert ['help', 'foo'] == plac.call(cmds, ['h', 'foo'])
|
||||
expect(SystemExit, plac.call, cmds, ['foo'])
|
||||
|
||||
def test_yield():
|
||||
def main():
|
||||
for i in (1, 2, 3):
|
||||
yield i
|
||||
assert plac.call(main, []) == [1, 2, 3]
|
||||
|
||||
def test_doctest():
|
||||
failure, tot= doctest.testfile('plac.txt', module_relative=False)
|
||||
assert not failure, failure
|
||||
|
||||
failing_scripts = set(['ishelve2.plac'])
|
||||
|
||||
def check_script(args):
|
||||
if failing_scripts.intersection(args):
|
||||
assert subprocess.call(args) > 0, ( # expected failure
|
||||
'Unexpected success for %s' % ' '.join(args))
|
||||
else:
|
||||
assert subprocess.call(args) == 0 , 'Failed %s' % ' '.join(args)
|
||||
|
||||
def test_batch():
|
||||
for batch in os.listdir('.'):
|
||||
if batch.endswith('.plac'):
|
||||
yield check_script, ['plac_runner.py', '-b', batch]
|
||||
|
||||
def test_placet():
|
||||
for placet in os.listdir('.'):
|
||||
if placet.endswith('.placet'):
|
||||
yield check_script, ['plac_runner.py', '-t', placet]
|
||||
|
||||
if __name__ == '__main__':
|
||||
n = 0
|
||||
for name, test in sorted(globals().items()):
|
||||
if name.startswith('test_'):
|
||||
print('Running ' + name)
|
||||
maybegen = test()
|
||||
if hasattr(maybegen, '__iter__'):
|
||||
for func, arg in maybegen:
|
||||
func(arg)
|
||||
n += 1
|
||||
else:
|
||||
n +=1
|
||||
print('Executed %d tests OK' % n)
|
||||
@@ -0,0 +1,35 @@
|
||||
import multiprocessing, subprocess, time, random
|
||||
import plac
|
||||
from ishelve2 import ShelveInterface
|
||||
|
||||
i = plac.Interpreter(ShelveInterface(configfile=None))
|
||||
|
||||
COMMANDS = ['''\
|
||||
.help
|
||||
set a 1
|
||||
''',
|
||||
'''\
|
||||
set b 1
|
||||
wrong command
|
||||
showall
|
||||
''']
|
||||
|
||||
def client_send(commands, port):
|
||||
time.sleep(.5) # wait a bit for the server to start
|
||||
po = subprocess.Popen(['telnet', 'localhost', str(port)],
|
||||
stdin=subprocess.PIPE)
|
||||
for cmd in commands.splitlines():
|
||||
po.stdin.write(cmd + '\n')
|
||||
time.sleep(.1) # wait a bit for the server to answer
|
||||
|
||||
def test():
|
||||
port = random.choice(range(2000, 20000))
|
||||
clients = []
|
||||
for cmds in COMMANDS:
|
||||
cl = multiprocessing.Process(target=client_send, args=(cmds, port))
|
||||
clients.append(cl)
|
||||
cl.start()
|
||||
i.stop_server(wait=1)
|
||||
i.start_server(port, timeout=.1)
|
||||
for cl in clients:
|
||||
cl.join()
|
||||
@@ -0,0 +1,40 @@
|
||||
import threading, Queue, time, random, Tkinter
|
||||
import plac
|
||||
|
||||
queue = Queue.Queue()
|
||||
|
||||
def make_target(i):
|
||||
def thunk():
|
||||
time.sleep(random.random())
|
||||
queue.put(str(i))
|
||||
return thunk
|
||||
|
||||
def launch_threads(n):
|
||||
for i in range(n):
|
||||
th = threading.Thread(target=make_target(i))
|
||||
th.start()
|
||||
yield th
|
||||
|
||||
class ThreadContext(object):
|
||||
def __init__(self, func, *args, **kw):
|
||||
self.thread = threading.Thread(None, func, args=args, kwargs=kw)
|
||||
def __enter__(self):
|
||||
self.thread.start()
|
||||
return self
|
||||
def __exit__(self, etype, exc, tb):
|
||||
self.thread.join()
|
||||
|
||||
def interpret_queue(interpreter, queue):
|
||||
with interpreter:
|
||||
for value in iter(queue.get, 'exit'):
|
||||
print(interpreter.send(value))
|
||||
|
||||
if __name__ == '__main__':
|
||||
thlist = list(launch_threads(10))
|
||||
i = plac.Interpreter(lambda x: x)
|
||||
root = Tkinter.Tk()
|
||||
threading.Timer(3, lambda : [queue.put('exit'), root.quit()]).start()
|
||||
with ThreadContext(interpret_queue, i, queue):
|
||||
root.mainloop()
|
||||
for th in thlist:
|
||||
th.join()
|
||||
@@ -0,0 +1,10 @@
|
||||
usage: plac_runner.py vcs.py [-h] {status,commit,checkout} ...
|
||||
|
||||
A Fake Version Control System
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
|
||||
subcommands:
|
||||
{status,commit,checkout}
|
||||
-h to get additional help
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
"A Fake Version Control System"
|
||||
|
||||
import plac
|
||||
|
||||
commands = 'checkout', 'commit', 'status'
|
||||
|
||||
@plac.annotations(url='url of the source code')
|
||||
def checkout(url):
|
||||
"A fake checkout command"
|
||||
return ('checkout ', url)
|
||||
|
||||
@plac.annotations(message=('commit message', 'option'))
|
||||
def commit(message):
|
||||
"A fake commit command"
|
||||
return ('commit ', message)
|
||||
|
||||
@plac.annotations(quiet=('summary information', 'flag', 'q'))
|
||||
def status(quiet):
|
||||
"A fake status command"
|
||||
return ('status ', quiet)
|
||||
|
||||
def __missing__(name):
|
||||
return 'Command %r does not exist' % name
|
||||
|
||||
def __exit__(etype, exc, tb):
|
||||
"Will be called automatically at the end of the call/cmdloop"
|
||||
if etype in (None, GeneratorExit): # success
|
||||
print('ok')
|
||||
|
||||
main = __import__(__name__) # the module imports itself!
|
||||
@@ -0,0 +1,7 @@
|
||||
class Main(object):
|
||||
commands = ['do']
|
||||
def do(self, x):
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
import plac; plac.Interpreter.call(Main)
|
||||
@@ -0,0 +1,406 @@
|
||||
Traceback (most recent call last):
|
||||
File "importer3.py", line 20, in <module>
|
||||
plac.Interpreter(plac.call(FakeImporter)).interact()
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 775, in interact
|
||||
self._manage_input()
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 785, in _manage_input
|
||||
write(str(task) + '\n')
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
File "/usr/local/lib/python2.6/dist-packages/plac-0.7.0-py2.6.egg/plac_ext.py", line 266, in __str__
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
RuntimeError: maximum recursion depth exceeded while calling a Python object
|
||||
@@ -0,0 +1,35 @@
|
||||
########################## LICENCE ###############################
|
||||
##
|
||||
## Copyright (c) 2010, Michele Simionato
|
||||
## All rights reserved.
|
||||
##
|
||||
## Redistributions of source code must retain the above copyright
|
||||
## notice, this list of conditions and the following disclaimer.
|
||||
## Redistributions in bytecode form must reproduce the above copyright
|
||||
## notice, this list of conditions and the following disclaimer in
|
||||
## the documentation and/or other materials provided with the
|
||||
## distribution.
|
||||
|
||||
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
## HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
## BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
## OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
## TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
## USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
## DAMAGE.
|
||||
|
||||
"""
|
||||
See doc/plac.pdf, doc/plac_adv.pdf for the documentation.
|
||||
"""
|
||||
|
||||
__version__ = '0.7.4'
|
||||
|
||||
from plac_core import *
|
||||
|
||||
if sys.version >= '2.5':
|
||||
from plac_ext import Interpreter, import_main, ReadlineInput, stdout, runp
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
# this module should be kept Python 2.3 compatible
|
||||
import re, sys, inspect, argparse
|
||||
if sys.version >= '3':
|
||||
from inspect import getfullargspec
|
||||
else:
|
||||
class getfullargspec(object):
|
||||
"A quick and dirty replacement for getfullargspec for Python 2.X"
|
||||
def __init__(self, f):
|
||||
self.args, self.varargs, self.varkw, self.defaults = \
|
||||
inspect.getargspec(f)
|
||||
self.annotations = getattr(f, '__annotations__', {})
|
||||
try:
|
||||
set
|
||||
except NameError: # Python 2.3
|
||||
from sets import Set as set
|
||||
from gettext import gettext as _
|
||||
|
||||
def annotations(**ann):
|
||||
"""
|
||||
Returns a decorator annotating a function with the given annotations.
|
||||
This is a trick to support function annotations in Python 2.X.
|
||||
"""
|
||||
def annotate(f):
|
||||
fas = getfullargspec(f)
|
||||
args = fas.args
|
||||
if fas.varargs:
|
||||
args.append(fas.varargs)
|
||||
if fas.varkw:
|
||||
args.append(fas.varkw)
|
||||
for argname in ann:
|
||||
if argname not in args:
|
||||
raise NameError(
|
||||
_('Annotating non-existing argument: %s') % argname)
|
||||
f.__annotations__ = ann
|
||||
return f
|
||||
return annotate
|
||||
|
||||
def is_annotation(obj):
|
||||
"""
|
||||
An object is an annotation object if it has the attributes
|
||||
help, kind, abbrev, type, choices, metavar.
|
||||
"""
|
||||
return (hasattr(obj, 'help') and hasattr(obj, 'kind') and
|
||||
hasattr(obj, 'abbrev') and hasattr(obj, 'type')
|
||||
and hasattr(obj, 'choices') and hasattr(obj, 'metavar'))
|
||||
|
||||
class Annotation(object):
|
||||
def __init__(self, help="", kind="positional", abbrev=None, type=None,
|
||||
choices=None, metavar=None):
|
||||
assert kind in ('positional', 'option', 'flag'), kind
|
||||
if kind == "positional":
|
||||
assert abbrev is None, abbrev
|
||||
self.help = help
|
||||
self.kind = kind
|
||||
self.abbrev = abbrev
|
||||
self.type = type
|
||||
self.choices = choices
|
||||
self.metavar = metavar
|
||||
|
||||
def from_(cls, obj):
|
||||
"Helper to convert an object into an annotation, if needed"
|
||||
if is_annotation(obj):
|
||||
return obj # do nothing
|
||||
elif hasattr(obj, '__iter__') and not isinstance(obj, basestring):
|
||||
return cls(*obj)
|
||||
return cls(obj)
|
||||
from_ = classmethod(from_)
|
||||
|
||||
NONE = object() # sentinel use to signal the absence of a default
|
||||
|
||||
PARSER_CFG = getfullargspec(argparse.ArgumentParser.__init__).args[1:]
|
||||
# the default arguments accepted by an ArgumentParser object
|
||||
|
||||
def pconf(obj):
|
||||
"Extracts the configuration of the underlying ArgumentParser from obj"
|
||||
cfg = dict(description=obj.__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
for name in dir(obj):
|
||||
if name in PARSER_CFG: # argument of ArgumentParser
|
||||
cfg[name] = getattr(obj, name)
|
||||
return cfg
|
||||
|
||||
parser_registry = {}
|
||||
|
||||
def parser_from(obj, **confparams):
|
||||
"""
|
||||
obj can be a callable or an object with a .commands attribute.
|
||||
Returns an ArgumentParser.
|
||||
"""
|
||||
try: # the underlying parser has been generated already
|
||||
return parser_registry[obj]
|
||||
except KeyError: # generate a new parser
|
||||
pass
|
||||
conf = pconf(obj).copy()
|
||||
conf.update(confparams)
|
||||
parser_registry[obj] = parser = ArgumentParser(**conf)
|
||||
parser.obj = obj
|
||||
parser.case_sensitive = confparams.get(
|
||||
'case_sensitive', getattr(obj, 'case_sensitive', True))
|
||||
if hasattr(obj, 'commands') and obj.commands and not inspect.isclass(obj):
|
||||
# a command container instance
|
||||
parser.addsubcommands(obj.commands, obj, 'subcommands')
|
||||
else:
|
||||
parser.populate_from(obj)
|
||||
return parser
|
||||
|
||||
def _extract_kwargs(args):
|
||||
"Returns two lists: regular args and name=value args"
|
||||
arglist = []
|
||||
kwargs = {}
|
||||
for arg in args:
|
||||
match = re.match(r'([a-zA-Z_]\w*)=', arg)
|
||||
if match:
|
||||
name = match.group(1)
|
||||
kwargs[name] = arg[len(name)+1:]
|
||||
else:
|
||||
arglist.append(arg)
|
||||
return arglist, kwargs
|
||||
|
||||
def _match_cmd(abbrev, commands, case_sensitive=True):
|
||||
"Extract the command name from an abbreviation or raise a NameError"
|
||||
if not case_sensitive:
|
||||
abbrev = abbrev.upper(); commands = [c.upper() for c in commands]
|
||||
perfect_matches = [name for name in commands if name == abbrev]
|
||||
if len(perfect_matches) == 1:
|
||||
return perfect_matches[0]
|
||||
matches = [name for name in commands if name.startswith(abbrev)]
|
||||
n = len(matches)
|
||||
if n == 1:
|
||||
return matches[0]
|
||||
elif n > 1:
|
||||
raise NameError(
|
||||
_('Ambiguous command %r: matching %s' % (abbrev, matches)))
|
||||
|
||||
class ArgumentParser(argparse.ArgumentParser):
|
||||
"""
|
||||
An ArgumentParser with .func and .argspec attributes, and possibly
|
||||
.commands and .subparsers.
|
||||
"""
|
||||
case_sensitive = True
|
||||
|
||||
def consume(self, args):
|
||||
"""Call the underlying function with the args. Works also for
|
||||
command containers, by dispatching to the right subparser."""
|
||||
arglist = list(args)
|
||||
cmd = None
|
||||
if hasattr(self, 'subparsers'):
|
||||
subp, cmd = self._extract_subparser_cmd(arglist)
|
||||
if subp is None and cmd is not None:
|
||||
return cmd, self.missing(cmd)
|
||||
elif subp is not None: # use the subparser
|
||||
self = subp
|
||||
if hasattr(self, 'argspec') and self.argspec.varkw:
|
||||
arglist, kwargs = _extract_kwargs(arglist) # modify arglist!
|
||||
else:
|
||||
kwargs = {}
|
||||
if hasattr(self, 'argspec') and self.argspec.varargs:
|
||||
# ignore unrecognized arguments
|
||||
ns, extraopts = self.parse_known_args(arglist)
|
||||
else:
|
||||
ns, extraopts = self.parse_args(arglist), [] # may raise an exit
|
||||
args = [getattr(ns, a) for a in self.argspec.args]
|
||||
varargs = getattr(ns, self.argspec.varargs or '', [])
|
||||
collision = set(self.argspec.args) & set(kwargs)
|
||||
if collision:
|
||||
self.error(
|
||||
_('colliding keyword arguments: %s') % ' '.join(collision))
|
||||
return cmd, self.func(*(args + varargs + extraopts), **kwargs)
|
||||
|
||||
def _extract_subparser_cmd(self, arglist):
|
||||
"Extract the right subparser from the first recognized argument"
|
||||
optprefix = self.prefix_chars[0]
|
||||
name_parser_map = self.subparsers._name_parser_map
|
||||
for i, arg in enumerate(arglist):
|
||||
if not arg.startswith(optprefix):
|
||||
cmd = _match_cmd(arg, name_parser_map, self.case_sensitive)
|
||||
del arglist[i]
|
||||
return name_parser_map.get(cmd), cmd or arg
|
||||
return None, None
|
||||
|
||||
def addsubcommands(self, commands, obj, title=None, cmdprefix=''):
|
||||
"Extract a list of subcommands from obj and add them to the parser"
|
||||
if hasattr(obj, cmdprefix) and obj.cmdprefix in self.prefix_chars:
|
||||
raise ValueError(_('The prefix %r is already taken!' % cmdprefix))
|
||||
if not hasattr(self, 'subparsers'):
|
||||
self.subparsers = self.add_subparsers(title=title)
|
||||
elif title:
|
||||
self.add_argument_group(title=title) # populate ._action_groups
|
||||
prefixlen = len(getattr(obj, 'cmdprefix', ''))
|
||||
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."""
|
||||
if inspect.isfunction(obj):
|
||||
self.argspec = getfullargspec(obj)
|
||||
elif inspect.ismethod(obj):
|
||||
self.argspec = getfullargspec(obj)
|
||||
del self.argspec.args[0] # remove first argument
|
||||
elif inspect.isclass(obj):
|
||||
if obj.__init__ is object.__init__: # to avoid an error
|
||||
self.argspec = getfullargspec(lambda self: None)
|
||||
else:
|
||||
self.argspec = getfullargspec(obj.__init__)
|
||||
del self.argspec.args[0] # remove first argument
|
||||
elif hasattr(obj, '__call__'):
|
||||
self.argspec = getfullargspec(obj.__call__)
|
||||
del self.argspec.args[0] # remove first argument
|
||||
else:
|
||||
raise TypeError(_('Could not determine the signature of %r') % obj)
|
||||
self.func = obj
|
||||
parser_registry[obj] = self
|
||||
|
||||
def populate_from(self, func):
|
||||
"""
|
||||
Extract the arguments from the attributes of the passed function
|
||||
and return a populated ArgumentParser instance.
|
||||
"""
|
||||
self._set_func_argspec(func)
|
||||
f = self.argspec
|
||||
defaults = f.defaults or ()
|
||||
n_args = len(f.args)
|
||||
n_defaults = len(defaults)
|
||||
alldefaults = (NONE,) * (n_args - n_defaults) + defaults
|
||||
prefix = self.prefix = getattr(func, 'prefix_chars', '-')[0]
|
||||
for name, default in zip(f.args, alldefaults):
|
||||
ann = f.annotations.get(name, ())
|
||||
a = Annotation.from_(ann)
|
||||
metavar = a.metavar
|
||||
if default is NONE:
|
||||
dflt = None
|
||||
else:
|
||||
dflt = default
|
||||
if a.kind in ('option', 'flag'):
|
||||
if a.abbrev:
|
||||
shortlong = (prefix + a.abbrev, prefix*2 + name)
|
||||
else:
|
||||
shortlong = (prefix + name,)
|
||||
elif default is NONE: # required argument
|
||||
self.add_argument(name, help=a.help, type=a.type,
|
||||
choices=a.choices, metavar=metavar)
|
||||
else: # default argument
|
||||
self.add_argument(
|
||||
name, nargs='?', help=a.help, default=dflt,
|
||||
type=a.type, choices=a.choices, metavar=metavar)
|
||||
if a.kind == 'option':
|
||||
if default is not NONE:
|
||||
metavar = metavar or str(default)
|
||||
self.add_argument(
|
||||
help=a.help, default=dflt, type=a.type,
|
||||
choices=a.choices, metavar=metavar, *shortlong)
|
||||
elif a.kind == 'flag':
|
||||
if default is not NONE and default is not False:
|
||||
raise TypeError(_('Flag %r wants default False, got %r') %
|
||||
(name, default))
|
||||
self.add_argument(action='store_true', help=a.help, *shortlong)
|
||||
if f.varargs:
|
||||
a = Annotation.from_(f.annotations.get(f.varargs, ()))
|
||||
self.add_argument(f.varargs, nargs='*', help=a.help, default=[],
|
||||
type=a.type, metavar=a.metavar)
|
||||
if f.varkw:
|
||||
a = Annotation.from_(f.annotations.get(f.varkw, ()))
|
||||
self.add_argument(f.varkw, nargs='*', help=a.help, default={},
|
||||
type=a.type, metavar=a.metavar)
|
||||
|
||||
def missing(self, name):
|
||||
miss = getattr(self.obj, '__missing__', lambda name:
|
||||
self.error('No command %r' % name))
|
||||
return miss(name)
|
||||
|
||||
def 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:], eager=True):
|
||||
"""
|
||||
If obj is a function or a bound method, parse the given arglist
|
||||
by using the parser inferred from the annotations of obj
|
||||
and call obj with the parsed arguments.
|
||||
If obj is an object with attribute .commands, dispatch to the
|
||||
associated subparser.
|
||||
"""
|
||||
cmd, result = parser_from(obj).consume(arglist)
|
||||
if iterable(result) and eager: # listify the result
|
||||
return list(result)
|
||||
return result
|
||||
+892
@@ -0,0 +1,892 @@
|
||||
# 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, threading
|
||||
import plac_core
|
||||
try:
|
||||
import readline
|
||||
except ImportError:
|
||||
readline = False
|
||||
|
||||
############################# generic utils ################################
|
||||
|
||||
@contextmanager
|
||||
def stdout(fileobj):
|
||||
"usage: with stdout(file('out.txt', 'a')): do_something()"
|
||||
orig_stdout = sys.stdout
|
||||
sys.stdout = fileobj
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
sys.stdout = orig_stdout
|
||||
|
||||
def write(x):
|
||||
"Write str(x) on stdout and flush, no newline added"
|
||||
sys.stdout.write(str(x))
|
||||
sys.stdout.flush()
|
||||
|
||||
def gen_val(value):
|
||||
"Return a generator object with a single element"
|
||||
yield value
|
||||
|
||||
def gen_exc(etype, exc, tb):
|
||||
"Return a generator object raising an exception"
|
||||
raise etype, exc, tb
|
||||
yield
|
||||
|
||||
def less(text):
|
||||
"Send a text to less via a pipe"
|
||||
# -c clear the screen before starting less
|
||||
po = subprocess.Popen(['less', '-c'], stdin=subprocess.PIPE)
|
||||
try:
|
||||
po.stdin.write(text)
|
||||
except IOError:
|
||||
pass
|
||||
po.stdin.close()
|
||||
po.wait()
|
||||
|
||||
use_less = (sys.platform != 'win32') # unices
|
||||
|
||||
class TerminatedProcess(Exception):
|
||||
pass
|
||||
|
||||
def terminatedProcess(signum, frame):
|
||||
raise TerminatedProcess
|
||||
|
||||
########################### readline support #############################
|
||||
|
||||
def read_line(stdin, prompt=''):
|
||||
"Read a line from stdin, using readline when possible"
|
||||
if isinstance(stdin, ReadlineInput):
|
||||
return stdin.readline(prompt)
|
||||
else:
|
||||
write(prompt)
|
||||
return stdin.readline()
|
||||
|
||||
def read_long_line(stdin, terminator):
|
||||
"""
|
||||
Read multiple lines from stdin until the terminator character is found, then
|
||||
yield a single space-separated long line.
|
||||
"""
|
||||
while True:
|
||||
lines = []
|
||||
while True:
|
||||
line = stdin.readline() # ends with \n
|
||||
if not line: # EOF
|
||||
return
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
elif line[-1] == terminator:
|
||||
lines.append(line[:-1])
|
||||
break
|
||||
else:
|
||||
lines.append(line)
|
||||
yield ' '.join(lines)
|
||||
|
||||
class ReadlineInput(object):
|
||||
"""
|
||||
An iterable with a .readline method reading from stdin.
|
||||
"""
|
||||
def __init__(self, completions, case_sensitive=True, histfile=None):
|
||||
self.completions = completions
|
||||
self.case_sensitive = case_sensitive
|
||||
self.histfile = histfile
|
||||
if not case_sensitive:
|
||||
self.completions = map(str.upper, completions)
|
||||
readline.parse_and_bind("tab: complete")
|
||||
readline.set_completer(self.complete)
|
||||
|
||||
def __enter__(self):
|
||||
self.old_completer = readline.get_completer()
|
||||
try:
|
||||
if self.histfile:
|
||||
readline.read_history_file(self.histfile)
|
||||
except IOError: # the first time
|
||||
pass
|
||||
return self
|
||||
|
||||
def __exit__(self, etype, exc, tb):
|
||||
readline.set_completer(self.old_completer)
|
||||
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, prompt=''):
|
||||
try:
|
||||
return raw_input(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)
|
||||
|
||||
def partial_call(factory, arglist):
|
||||
"Call a container factory with the arglist and return a plac object"
|
||||
|
||||
a = plac_core.parser_from(factory).argspec
|
||||
if a.defaults or a.varargs or a.varkw:
|
||||
raise TypeError('Interpreter.call must be invoked on '
|
||||
'factories with required arguments only')
|
||||
required_args = ', '.join(a.args)
|
||||
if required_args:
|
||||
required_args += ',' # trailing comma
|
||||
code = '''def makeobj(interact, %s *args):
|
||||
obj = factory(%s)
|
||||
obj._interact_ = interact
|
||||
obj._args_ = args
|
||||
return obj\n'''% (required_args, required_args)
|
||||
dic = dict(factory=factory)
|
||||
exec code in dic
|
||||
makeobj = dic['makeobj']
|
||||
if inspect.isclass(factory):
|
||||
makeobj.__annotations__ = getattr(
|
||||
factory.__init__, '__annotations__', {})
|
||||
else:
|
||||
makeobj.__annotations__ = getattr(
|
||||
factory, '__annotations__', {})
|
||||
makeobj.__annotations__['interact'] = (
|
||||
'start interactive interpreter', 'flag', 'i')
|
||||
return plac_core.call(makeobj, arglist)
|
||||
|
||||
def import_main(path, *args, **pconf):
|
||||
"""
|
||||
An utility to import the main function of a plac tool. It also
|
||||
works with command container factories.
|
||||
"""
|
||||
if ':' in path: # importing a factory
|
||||
path, factory_name = path.split(':')
|
||||
else: # importing the main function
|
||||
factory_name = None
|
||||
if not os.path.isabs(path): # relative path, look at PLACDIRS
|
||||
for placdir in PLACDIRS:
|
||||
fullpath = os.path.join(placdir, path)
|
||||
if os.path.exists(fullpath):
|
||||
break
|
||||
else: # no break
|
||||
raise ImportError(_('Cannot find %s' % path))
|
||||
else:
|
||||
fullpath = path
|
||||
name, ext = os.path.splitext(os.path.basename(fullpath))
|
||||
module = imp.load_module(name, open(fullpath), fullpath, (ext, 'U', 1))
|
||||
if factory_name:
|
||||
tool = partial_call(getattr(module, factory_name), args)
|
||||
else:
|
||||
tool = module.main
|
||||
# set the parser configuration
|
||||
plac_core.parser_from(tool, **pconf)
|
||||
return tool
|
||||
|
||||
############################## Task classes ##############################
|
||||
|
||||
# base class not instantiated directly
|
||||
class BaseTask(object):
|
||||
"""
|
||||
A task is a wrapper over a generator object with signature
|
||||
Task(no, arglist, genobj), attributes
|
||||
.no
|
||||
.arglist
|
||||
.outlist
|
||||
.str
|
||||
.etype
|
||||
.exc
|
||||
.tb
|
||||
.status
|
||||
and methods .run and .kill.
|
||||
"""
|
||||
STATES = ('SUBMITTED', 'RUNNING', 'TOBEKILLED', 'KILLED', 'FINISHED',
|
||||
'ABORTED')
|
||||
|
||||
def __init__(self, no, arglist, genobj):
|
||||
self.no = no
|
||||
self.arglist = arglist
|
||||
self._genobj = self._wrap(genobj)
|
||||
self.str, self.etype, self.exc, self.tb = '', None, None, None
|
||||
self.status = 'SUBMITTED'
|
||||
self.outlist = []
|
||||
|
||||
def _wrap(self, genobj, stringify_tb=False):
|
||||
"""
|
||||
Wrap the genobj into a generator managing the exceptions,
|
||||
populating the .outlist, setting the .status and yielding None.
|
||||
stringify_tb must be True if the traceback must be sent to a process.
|
||||
"""
|
||||
self.status = 'RUNNING'
|
||||
try:
|
||||
for value in genobj:
|
||||
if self.status == 'TOBEKILLED': # exit from the loop
|
||||
raise GeneratorExit
|
||||
if value is not None: # add output
|
||||
self.outlist.append(value)
|
||||
yield
|
||||
except (GeneratorExit, TerminatedProcess, KeyboardInterrupt):
|
||||
# soft termination
|
||||
self.status = 'KILLED'
|
||||
except: # unexpected exception
|
||||
self.etype, self.exc, tb = sys.exc_info()
|
||||
self.tb = self.traceback if stringify_tb else tb
|
||||
self.status = 'ABORTED'
|
||||
else: # regular exit
|
||||
self.status = 'FINISHED'
|
||||
try:
|
||||
self.str = '\n'.join(map(str, self.outlist))
|
||||
except IndexError:
|
||||
self.str = 'no result'
|
||||
|
||||
def run(self):
|
||||
"Run the inner generator"
|
||||
for none in self._genobj:
|
||||
pass
|
||||
|
||||
def kill(self):
|
||||
"Set a TOBEKILLED status"
|
||||
self.status = 'TOBEKILLED'
|
||||
|
||||
def wait(self):
|
||||
"Wait for the task to finish: to be overridden"
|
||||
|
||||
@property
|
||||
def traceback(self):
|
||||
"Return the traceback as a (possibly empty) string"
|
||||
if self.tb is None:
|
||||
return ''
|
||||
elif isinstance(self.tb, basestring):
|
||||
return self.tb
|
||||
else:
|
||||
return ''.join(traceback.format_tb(self.tb))
|
||||
|
||||
@property
|
||||
def result(self):
|
||||
self.wait()
|
||||
if self.exc:
|
||||
raise self.etype, self.exc, self.tb or None
|
||||
if not self.outlist:
|
||||
return None
|
||||
return self.outlist[-1]
|
||||
|
||||
def __repr__(self):
|
||||
"String representation containing class name, number, arglist, status"
|
||||
return '<%s %d [%s] %s>' % (
|
||||
self.__class__.__name__, self.no,
|
||||
' '.join(self.arglist), self.status)
|
||||
|
||||
nulltask = BaseTask(0, [], ('skip' for dummy in (1,)))
|
||||
|
||||
########################## synchronous tasks ###############################
|
||||
|
||||
class SynTask(BaseTask):
|
||||
"""
|
||||
Synchronous task running in the interpreter loop and displaying its
|
||||
output as soon as available.
|
||||
"""
|
||||
def __str__(self):
|
||||
"Return the output string or the error message"
|
||||
if self.etype: # there was an error
|
||||
return '%s: %s' % (self.etype.__name__, self.exc)
|
||||
else:
|
||||
return '\n'.join(map(str, self.outlist))
|
||||
|
||||
class ThreadedTask(BaseTask):
|
||||
"""
|
||||
A task running in a separated thread.
|
||||
"""
|
||||
def __init__(self, no, arglist, genobj):
|
||||
BaseTask.__init__(self, no, arglist, genobj)
|
||||
self.thread = threading.Thread(target=super(ThreadedTask, self).run)
|
||||
|
||||
def run(self):
|
||||
"Run the task into a thread"
|
||||
self.thread.start()
|
||||
|
||||
def wait(self):
|
||||
"Block until the thread ends"
|
||||
self.thread.join()
|
||||
|
||||
######################### multiprocessing tasks ##########################
|
||||
|
||||
def sharedattr(name, on_error):
|
||||
"Return a property to be attached to an MPTask"
|
||||
def get(self):
|
||||
try:
|
||||
return getattr(self.ns, name)
|
||||
except: # the process was killed or died hard
|
||||
return on_error
|
||||
def set(self, value):
|
||||
try:
|
||||
setattr(self.ns, name, value)
|
||||
except: # the process was killed or died hard
|
||||
pass
|
||||
return property(get, set)
|
||||
|
||||
class MPTask(BaseTask):
|
||||
"""
|
||||
A task running as an external process. The current implementation
|
||||
only works on Unix-like systems, where multiprocessing use forks.
|
||||
"""
|
||||
str = sharedattr('str', '')
|
||||
etype = sharedattr('etype', None)
|
||||
exc = sharedattr('exc', None)
|
||||
tb = sharedattr('tb', None)
|
||||
status = sharedattr('status', 'ABORTED')
|
||||
|
||||
@property
|
||||
def outlist(self):
|
||||
try:
|
||||
return self._outlist
|
||||
except: # the process died hard
|
||||
return []
|
||||
|
||||
def __init__(self, no, arglist, genobj, mp_manager):
|
||||
self.no = no
|
||||
self.arglist = arglist
|
||||
self._genobj = self._wrap(genobj, stringify_tb=True)
|
||||
self.mp_manager = mp_manager
|
||||
self._outlist = self.mp_manager.list()
|
||||
self.ns = self.mp_manager.Namespace()
|
||||
self.status = 'SUBMITTED'
|
||||
self.etype, self.exc, self.tb = None, None, None
|
||||
self.str = repr(self)
|
||||
self.proc = multiprocessing.Process(target=super(MPTask, self).run)
|
||||
|
||||
def run(self):
|
||||
"Run the task into an external process"
|
||||
self.proc.start()
|
||||
|
||||
def wait(self):
|
||||
"Block until the external process ends or is killed"
|
||||
self.proc.join()
|
||||
|
||||
def kill(self):
|
||||
"""Kill the process with a SIGTERM inducing a TerminatedProcess
|
||||
exception in the children"""
|
||||
self.proc.terminate()
|
||||
|
||||
######################### Task Manager #######################
|
||||
|
||||
class 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):
|
||||
"""
|
||||
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.registry = {} # {taskno : task}
|
||||
if obj.mpcommands or obj.thcommands:
|
||||
self.specialcommands.update(['.kill', '.list', '.output'])
|
||||
self.helpsummary = HelpSummary.make(obj, self.specialcommands)
|
||||
self.mp_manager = multiprocessing.Manager() if obj.mpcommands else None
|
||||
signal.signal(signal.SIGTERM, terminatedProcess)
|
||||
|
||||
def close(self):
|
||||
"Kill all the running tasks"
|
||||
for task in self.registry.itervalues():
|
||||
try:
|
||||
if task.status == 'RUNNING':
|
||||
task.kill()
|
||||
task.wait()
|
||||
except: # task killed, nothing to wait
|
||||
pass
|
||||
if self.mp_manager:
|
||||
self.mp_manager.shutdown()
|
||||
|
||||
def _get_latest(self, taskno=-1, status=None):
|
||||
"Get the latest submitted task from the registry"
|
||||
assert taskno < 0, 'You must pass a negative number'
|
||||
if status:
|
||||
tasks = [t for t in self.registry.itervalues()
|
||||
if t.status == status]
|
||||
else:
|
||||
tasks = [t for t in self.registry.itervalues()]
|
||||
tasks.sort(key=attrgetter('no'))
|
||||
if len(tasks) >= abs(taskno):
|
||||
return tasks[taskno]
|
||||
|
||||
########################### special commands #########################
|
||||
|
||||
@plac_core.annotations(
|
||||
taskno=('task to kill', 'positional', None, int))
|
||||
def kill(self, taskno=-1):
|
||||
'kill the given task (-1 to kill the latest running task)'
|
||||
if taskno < 0:
|
||||
task = self._get_latest(taskno, status='RUNNING')
|
||||
if task is None:
|
||||
yield 'Nothing to kill'
|
||||
return
|
||||
elif not taskno in self.registry:
|
||||
yield 'Unknown task %d' % taskno
|
||||
return
|
||||
else:
|
||||
task = self.registry[taskno]
|
||||
if task.status in ('ABORTED', 'KILLED', 'FINISHED'):
|
||||
yield 'Already finished %s' % task
|
||||
return
|
||||
task.kill()
|
||||
yield task
|
||||
|
||||
@plac_core.annotations(
|
||||
status=('', 'positional', None, str, BaseTask.STATES))
|
||||
def list(self, status='RUNNING'):
|
||||
'list tasks with a given status'
|
||||
for task in self.registry.values():
|
||||
if task.status == status:
|
||||
yield task
|
||||
|
||||
@plac_core.annotations(
|
||||
taskno=('task number', 'positional', None, int))
|
||||
def output(self, taskno=-1):
|
||||
'show the output of a given task'
|
||||
if taskno < 0:
|
||||
task = self._get_latest(taskno)
|
||||
if task is None:
|
||||
yield 'Nothing to show'
|
||||
return
|
||||
elif taskno not in self.registry:
|
||||
yield 'Unknown task %d' % taskno
|
||||
return
|
||||
else:
|
||||
task = self.registry[taskno]
|
||||
outstr = '\n'.join(map(str, task.outlist))
|
||||
yield task
|
||||
if len(task.outlist) > 20 and use_less:
|
||||
less(outstr)
|
||||
else:
|
||||
yield outstr
|
||||
|
||||
@plac_core.annotations(
|
||||
taskno=('task number', 'positional', None, int))
|
||||
def last_tb(self, taskno=-1):
|
||||
"show the traceback of a given task, if any"
|
||||
task = self._get_latest(taskno)
|
||||
if task:
|
||||
yield task.traceback
|
||||
else:
|
||||
yield 'Nothing to show'
|
||||
|
||||
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)
|
||||
|
||||
########################### SyncProcess ##############################
|
||||
|
||||
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)).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()
|
||||
|
||||
def recv(self): # char-by-char cannot work
|
||||
"Return the output of the subprocess, line-by-line until the prompt"
|
||||
lines = []
|
||||
while True:
|
||||
lines.append(self.stdout.readline())
|
||||
if lines[-1] == 'i>\n':
|
||||
out = ''.join(lines)
|
||||
return out[:-1] + ' ' # remove last newline
|
||||
|
||||
def send(self, line):
|
||||
"""Send a line (adding a newline) to the underlying subprocess
|
||||
and wait for the answer"""
|
||||
self.stdin.write(line + os.linesep)
|
||||
return self.recv()
|
||||
|
||||
########################## plac server ##############################
|
||||
|
||||
import asyncore, asynchat, socket
|
||||
|
||||
class _AsynHandler(asynchat.async_chat):
|
||||
"asynchat handler starting a new interpreter loop for each connection"
|
||||
|
||||
terminator = '\r\n' # the standard one for telnet
|
||||
prompt = 'i> '
|
||||
|
||||
def __init__(self, socket, interpreter):
|
||||
asynchat.async_chat.__init__(self, socket)
|
||||
self.set_terminator(self.terminator)
|
||||
self.i = interpreter
|
||||
self.i.__enter__()
|
||||
self.data = []
|
||||
self.write(self.prompt)
|
||||
|
||||
def write(self, data, *args):
|
||||
"Push a string back to the client"
|
||||
if args:
|
||||
data %= args
|
||||
if data.endswith('\n') and not data.endswith(self.terminator):
|
||||
data = data[:-1] + self.terminator # fix newlines
|
||||
self.push(data)
|
||||
|
||||
def collect_incoming_data(self, data):
|
||||
"Collect one character at the time"
|
||||
self.data.append(data)
|
||||
|
||||
def found_terminator(self):
|
||||
"Put in the queue the line received from the client"
|
||||
line = ''.join(self.data)
|
||||
self.log('Received line %r from %s' % (line, self.addr))
|
||||
if line == 'EOF':
|
||||
self.i.__exit__()
|
||||
self.handle_close()
|
||||
else:
|
||||
task = self.i.submit(line)
|
||||
task.run() # synchronous or not
|
||||
if task.etype: # manage exception
|
||||
error = '%s: %s\nReceived: %s' % (
|
||||
task.etype.__name__, task.exc, ' '.join(task.arglist))
|
||||
self.log_info(task.traceback + error) # on the server
|
||||
self.write(error + self.terminator) # back to the client
|
||||
else: # no exception
|
||||
self.write(task.str + self.terminator)
|
||||
self.data = []
|
||||
self.write(self.prompt)
|
||||
|
||||
class _AsynServer(asyncore.dispatcher):
|
||||
"asyncore-based server spawning AsynHandlers"
|
||||
|
||||
def __init__(self, interpreter, newhandler, port, listen=5):
|
||||
self.interpreter = interpreter
|
||||
self.newhandler = newhandler
|
||||
self.port = port
|
||||
asyncore.dispatcher.__init__(self)
|
||||
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.bind(('', port))
|
||||
self.listen(listen)
|
||||
|
||||
def handle_accept(self):
|
||||
clientsock, clientaddr = self.accept()
|
||||
self.log('Connected from %s' % str(clientaddr))
|
||||
i = self.interpreter.__class__(self.interpreter.obj) # new interpreter
|
||||
self.newhandler(clientsock, i) # spawn a new handler
|
||||
|
||||
########################### the Interpreter #############################
|
||||
|
||||
class Interpreter(object):
|
||||
"""
|
||||
A context manager with a .send method and a few utility methods:
|
||||
execute, test and doctest.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def call(cls, factory, arglist=sys.argv[1:],
|
||||
commentchar='#', split=shlex.split,
|
||||
stdin=sys.stdin, prompt='i> ', verbose=False):
|
||||
"""
|
||||
Call a container factory with the arglist and instantiate an
|
||||
interpreter object. If there are remaining arguments, send them to the
|
||||
interpreter, else start an interactive session.
|
||||
"""
|
||||
obj = partial_call(factory, arglist)
|
||||
i = cls(obj, commentchar, split)
|
||||
if i.obj._args_:
|
||||
with i:
|
||||
task = i.send(i.obj._args_) # synchronous
|
||||
if task.exc:
|
||||
raise task.etype, task.exc, task.tb
|
||||
print(task)
|
||||
elif i.obj._interact_:
|
||||
i.interact(stdin, prompt, verbose)
|
||||
else:
|
||||
i.parser.print_usage()
|
||||
|
||||
def __init__(self, obj, commentchar='#', split=shlex.split):
|
||||
self.obj = obj
|
||||
try:
|
||||
self.name = obj.__module__
|
||||
except AttributeError:
|
||||
self.name = 'plac'
|
||||
self.commentchar = commentchar
|
||||
self.split = split
|
||||
self._set_commands(obj)
|
||||
self.tm = TaskManager(obj)
|
||||
self.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):
|
||||
"Start the inner interpreter loop"
|
||||
self._interpreter = self._make_interpreter()
|
||||
self._interpreter.send(None)
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc):
|
||||
"Close the inner interpreter and the task manager"
|
||||
self.close()
|
||||
|
||||
def submit(self, line):
|
||||
"Send a line to the underlying interpreter and return a task object"
|
||||
if self._interpreter is None:
|
||||
raise RuntimeError(_('%r not initialized: probably you forgot to '
|
||||
'use the with statement') % self)
|
||||
if isinstance(line, basestring):
|
||||
arglist = self.split(line, self.commentchar)
|
||||
else: # expects a list of strings
|
||||
arglist = line
|
||||
if not arglist:
|
||||
return nulltask
|
||||
task = self._interpreter.send(arglist) # nonblocking
|
||||
if not plac_core._match_cmd(arglist[0], self.tm.specialcommands):
|
||||
self.tm.registry[task.no] = task
|
||||
return task
|
||||
|
||||
def send(self, line):
|
||||
"Send a line to the underlying interpreter and return the finished task"
|
||||
task = self.submit(line)
|
||||
BaseTask.run(task) # blocking
|
||||
return task
|
||||
|
||||
def tasks(self):
|
||||
"The full lists of the submitted tasks"
|
||||
return self.tm.registry.values()
|
||||
|
||||
def close(self):
|
||||
"Can be called to close the interpreter prematurely"
|
||||
self.tm.close()
|
||||
self._interpreter.close()
|
||||
|
||||
def _make_interpreter(self):
|
||||
"The interpreter main loop, from lists of arguments to task objects"
|
||||
enter = getattr(self.obj, '__enter__', lambda : None)
|
||||
exit = getattr(self.obj, '__exit__', lambda et, ex, tb: None)
|
||||
enter()
|
||||
task = None
|
||||
try:
|
||||
for no in itertools.count(1):
|
||||
arglist = yield task
|
||||
try:
|
||||
cmd, result = self.parser.consume(arglist)
|
||||
except: # i.e. SystemExit for invalid command
|
||||
task = SynTask(no, arglist, gen_exc(*sys.exc_info()))
|
||||
continue
|
||||
if not plac_core.iterable(result): # atomic result
|
||||
task = SynTask(no, arglist, gen_val(result))
|
||||
elif cmd in self.obj.mpcommands:
|
||||
task = MPTask(no, arglist, result, self.tm.mp_manager)
|
||||
elif cmd in self.obj.thcommands:
|
||||
task = ThreadedTask(no, arglist, result)
|
||||
else: # blocking task
|
||||
task = SynTask(no, arglist, result)
|
||||
except GeneratorExit: # regular exit
|
||||
exit(None, None, None)
|
||||
except: # exceptional exit
|
||||
exit(*sys.exc_info())
|
||||
raise
|
||||
|
||||
def check(self, given_input, expected_output):
|
||||
"Make sure you get the expected_output from the given_input"
|
||||
output = self.send(given_input).str # blocking
|
||||
ok = (output == expected_output)
|
||||
if not ok:
|
||||
# the message here is not internationalized on purpose
|
||||
msg = 'input: %s\noutput: %s\nexpected: %s' % (
|
||||
given_input, output, expected_output)
|
||||
raise AssertionError(msg)
|
||||
|
||||
def _parse_doctest(self, lineiter):
|
||||
"Returns the lines of input, the lines of output, and the line number"
|
||||
lines = [line.strip() for line in lineiter]
|
||||
inputs = []
|
||||
positions = []
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith('i> '):
|
||||
inputs.append(line[3:])
|
||||
positions.append(i)
|
||||
positions.append(len(lines) + 1) # last position
|
||||
outputs = []
|
||||
for i, start in enumerate(positions[:-1]):
|
||||
end = positions[i + 1]
|
||||
outputs.append('\n'.join(lines[start+1:end]))
|
||||
return zip(inputs, outputs, positions)
|
||||
|
||||
def doctest(self, lineiter, verbose=False):
|
||||
"""
|
||||
Parse a text containing doctests in a context and tests of all them.
|
||||
Raise an error even if a single doctest if broken. Use this for
|
||||
sequential tests which are logically grouped.
|
||||
"""
|
||||
with self:
|
||||
for input, output, no in self._parse_doctest(lineiter):
|
||||
if verbose:
|
||||
write('i> %s\n' % input)
|
||||
write('-> %s\n' % output)
|
||||
task = self.send(input) # blocking
|
||||
if not str(task) == output:
|
||||
msg = 'line %d: input: %s\noutput: %s\nexpected: %s\n' % (
|
||||
no + 1, input, task, output)
|
||||
write(msg)
|
||||
raise task.etype, task.exc, task.tb
|
||||
|
||||
def execute(self, lineiter, verbose=False):
|
||||
"Execute a lineiter of commands in a context and print the output"
|
||||
with self:
|
||||
for line in lineiter:
|
||||
if verbose:
|
||||
write('i> ' + line)
|
||||
task = self.send(line) # finished task
|
||||
if task.etype: # there was an error
|
||||
raise task.etype, task.exc, task.tb
|
||||
write('%s\n' % task.str)
|
||||
|
||||
def multiline(self, stdin=sys.stdin, terminator=';', verbose=False):
|
||||
"The multiline mode is especially suited for usage with emacs"
|
||||
with self:
|
||||
for line in read_long_line(stdin, terminator):
|
||||
task = self.submit(line)
|
||||
task.run()
|
||||
write('%s\n' % task.str)
|
||||
if verbose and task.traceback:
|
||||
write(task.traceback)
|
||||
|
||||
def interact(self, stdin=sys.stdin, prompt='i> ', verbose=False):
|
||||
"Starts an interactive command loop reading commands from the consolle"
|
||||
if stdin is sys.stdin and readline: # use readline
|
||||
histfile = os.path.expanduser('~/.%s.history' % self.name)
|
||||
self.stdin = ReadlineInput(self.commands, histfile=histfile)
|
||||
else:
|
||||
self.stdin = stdin
|
||||
self.prompt = prompt
|
||||
self.verbose = verbose
|
||||
intro = self.obj.__doc__ or ''
|
||||
write(intro + '\n')
|
||||
with self:
|
||||
if self.stdin is sys.stdin: # do not close stdin automatically
|
||||
self._manage_input()
|
||||
else:
|
||||
with self.stdin: # close stdin automatically
|
||||
self._manage_input()
|
||||
|
||||
def _manage_input(self):
|
||||
"Convert input lines into task which are then executed"
|
||||
for line in iter(lambda : read_line(self.stdin, self.prompt), ''):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
task = self.submit(line)
|
||||
task.run() # synchronous or not
|
||||
write(str(task) + '\n')
|
||||
if self.verbose and task.etype:
|
||||
write(task.traceback)
|
||||
|
||||
def start_server(self, port=2199, **kw):
|
||||
"""Starts an asyncore server reading commands for clients and opening
|
||||
a new interpreter for each connection."""
|
||||
_AsynServer(self, _AsynHandler, port) # register the server
|
||||
try:
|
||||
asyncore.loop(**kw)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
asyncore.close_all()
|
||||
|
||||
def stop_server(self, wait=0.0):
|
||||
"Stops the asyncore server, possibly after a given number of seconds"
|
||||
threading.Timer(wait, asyncore.socket_map.clear).start()
|
||||
|
||||
#################################### runp #####################################
|
||||
|
||||
class _TaskLauncher(object):
|
||||
"Helper for runp"
|
||||
def __init__(self, genseq, mode):
|
||||
if mode == 'p':
|
||||
self.mpcommands = ['rungen']
|
||||
else:
|
||||
self.thcommands = ['rungen']
|
||||
self.genlist = list(genseq)
|
||||
def rungen(self, i):
|
||||
for out in self.genlist[int(i) - 1]:
|
||||
yield out
|
||||
|
||||
def runp(genseq, mode='p', start=True):
|
||||
"""Run a sequence of generators in parallel. Mode can be 'p' (use processes)
|
||||
or 't' (use threads). Return a list of running task objects. If start is
|
||||
False, the tasks are only submitted and not automatically started.
|
||||
"""
|
||||
assert mode in 'pt', mode
|
||||
launcher = _TaskLauncher(genseq, mode)
|
||||
inter = Interpreter(launcher).__enter__()
|
||||
for i in range(len(launcher.genlist)):
|
||||
inter.submit('rungen %d' % (i + 1))
|
||||
if start:
|
||||
for task in inter.tasks():
|
||||
task.run()
|
||||
return inter.tasks()
|
||||
@@ -0,0 +1,65 @@
|
||||
#!python
|
||||
from __future__ import with_statement
|
||||
import os, sys, shlex, inspect
|
||||
import plac
|
||||
|
||||
def run(fnames, cmd, verbose):
|
||||
"Run batch scripts and tests"
|
||||
for fname in fnames:
|
||||
with open(fname) as f:
|
||||
lines = list(f)
|
||||
if not lines[0].startswith('#!'):
|
||||
sys.exit('Missing or incorrect shebang line!')
|
||||
firstline = lines[0][2:] # strip the shebang
|
||||
init_args = shlex.split(firstline)
|
||||
tool = plac.import_main(*init_args)
|
||||
command = getattr(plac.Interpreter(tool), cmd) # doctest or execute
|
||||
if verbose:
|
||||
sys.stdout.write('Running %s with %s' % (fname, firstline))
|
||||
command(lines[1:], verbose=verbose)
|
||||
|
||||
@plac.annotations(
|
||||
verbose=('verbose mode', 'flag', 'v'),
|
||||
interactive=('run plac tool in interactive mode', 'flag', 'i'),
|
||||
multiline=('run plac tool in multiline mode', 'flag', 'm'),
|
||||
serve=('run plac server', 'option', 's', int),
|
||||
batch=('run plac batch files', 'flag', 'b'),
|
||||
test=('run plac test files', 'flag', 't'),
|
||||
fname='script to run (.py or .plac or .placet)',
|
||||
extra='additional arguments',
|
||||
)
|
||||
def main(verbose, interactive, multiline, serve, batch, test, fname=None,
|
||||
*extra):
|
||||
"Runner for plac tools, plac batch files and plac tests"
|
||||
baseparser = plac.parser_from(main)
|
||||
if fname is None:
|
||||
baseparser.print_help()
|
||||
elif sys.argv[1] == fname: # script mode
|
||||
plactool = plac.import_main(
|
||||
fname, prog=os.path.basename(sys.argv[0]) + ' ' + fname)
|
||||
out = plac.call(plactool, sys.argv[2:], eager=False)
|
||||
if plac.iterable(out):
|
||||
for output in out:
|
||||
print(output)
|
||||
else:
|
||||
print(out)
|
||||
elif interactive or multiline or serve:
|
||||
plactool = plac.import_main(fname, *extra, **{'prog': ''})
|
||||
i = plac.Interpreter(plactool)
|
||||
if interactive:
|
||||
i.interact(verbose=verbose)
|
||||
elif multiline:
|
||||
i.multiline(verbose=verbose)
|
||||
elif serve:
|
||||
i.start_server(serve)
|
||||
elif batch:
|
||||
run((fname,) + extra, 'execute', verbose)
|
||||
elif test:
|
||||
run((fname,) + extra, 'doctest', verbose)
|
||||
print('run %s plac test(s)' % (len(extra) + 1))
|
||||
else:
|
||||
baseparser.print_usage()
|
||||
main.add_help = False
|
||||
|
||||
if __name__ == '__main__':
|
||||
plac.call(main)
|
||||
@@ -0,0 +1,49 @@
|
||||
try:
|
||||
from setuptools import setup
|
||||
except ImportError:
|
||||
from distutils.core import setup
|
||||
import os.path
|
||||
|
||||
def require(*modules):
|
||||
"""Check if the given modules are already available; if not add them to
|
||||
the dependency list."""
|
||||
deplist = []
|
||||
for module in modules:
|
||||
try:
|
||||
__import__(module)
|
||||
except ImportError:
|
||||
deplist.append(module)
|
||||
return deplist
|
||||
|
||||
def getversion(fname):
|
||||
"Get the __version__ without importing plac"
|
||||
for line in open(fname):
|
||||
if line.startswith('__version__'):
|
||||
return eval(line[13:])
|
||||
|
||||
if __name__ == '__main__':
|
||||
setup(name='plac',
|
||||
version=getversion(
|
||||
os.path.join(os.path.dirname(__file__), 'plac.py')),
|
||||
description=('The smartest command line arguments parser '
|
||||
'in the world'),
|
||||
long_description=open('README.txt').read(),
|
||||
author='Michele Simionato',
|
||||
author_email='michele.simionato@gmail.com',
|
||||
url='http://pypi.python.org/pypi/plac',
|
||||
license="BSD License",
|
||||
py_modules = ['plac_core', 'plac_ext', 'plac'],
|
||||
scripts = ['plac_runner.py'],
|
||||
install_requires=require('argparse', 'multiprocessing'),
|
||||
use_2to3=True,
|
||||
keywords="command line arguments parser",
|
||||
platforms=["All"],
|
||||
classifiers=['Development Status :: 3 - Alpha',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Software Development :: Libraries',
|
||||
'Topic :: Utilities'],
|
||||
zip_safe=False)
|
||||
Reference in New Issue
Block a user