summaryrefslogtreecommitdiff
path: root/plac
diff options
context:
space:
mode:
authorMichele Simionato <michele.simionato@gmail.com>2010-06-01 13:22:00 +0200
committerMichele Simionato <michele.simionato@gmail.com>2010-06-01 13:22:00 +0200
commit7ee9559aa9313824ea327111b7287b716172c286 (patch)
treedef050dfc4dfc55848be93dcd5725b50a4fb85b3 /plac
parent97dcd8ebb064319da3060d9795d4943b88870cd3 (diff)
downloadmicheles-7ee9559aa9313824ea327111b7287b716172c286.tar.gz
Removed the clap project remnants
Diffstat (limited to 'plac')
-rw-r--r--plac/README.txt52
-rw-r--r--plac/plac.py156
-rw-r--r--plac/setup.py29
-rw-r--r--plac/test_plac.py88
4 files changed, 325 insertions, 0 deletions
diff --git a/plac/README.txt b/plac/README.txt
new file mode 100644
index 0000000..50ac30c
--- /dev/null
+++ b/plac/README.txt
@@ -0,0 +1,52 @@
+plac, the easiest command line arguments parser in the world
+============================================================
+
+:Author: Michele Simionato
+:E-mail: michele.simionato@gmail.com
+:Requires: Python 2.3+
+:Download page: http://pypi.python.org/pypi/plac
+:Installation: ``easy_install plac``
+:License: BSD license
+
+Installation
+-------------
+
+If you are lazy, just perform
+
+$ easy_install plac
+
+which will install just the module on your system. Notice that
+Python 3 requires the easy_install version of the distribute_ project.
+
+If you prefer to install the full distribution from source, including
+the documentation, download the tarball_, unpack it and run
+
+$ python setup.py install
+
+in the main directory, possibly as superuser.
+
+.. _tarball: http://pypi.python.org/pypi/plac
+.. _distribute: http://packages.python.org/distribute/
+
+Testing
+--------
+
+Run
+
+$ python test_plac.py
+
+or
+
+$ nosetests test_plac
+
+or
+
+$ py.test test_plac
+
+Documentation
+--------------
+
+You can choose between the `HTML version`_ and the `PDF version`_ .
+
+.. _HTML version: http://micheles.googlecode.com/hg/plac/doc/plac.html
+.. _PDF version: http://micheles.googlecode.com/hg/plac/doc/plac.pdf
diff --git a/plac/plac.py b/plac/plac.py
new file mode 100644
index 0000000..fdde35f
--- /dev/null
+++ b/plac/plac.py
@@ -0,0 +1,156 @@
+########################## 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.
+
+"""
+plac, the easiest Command Line Arguments Parser in the world.
+See plac/doc.html for the documentation.
+"""
+# this module should be kept Python 2.3 compatible
+
+__version__ = '0.2.0'
+
+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.keywords, self.defaults = \
+ inspect.getargspec(f)
+ self.annotations = getattr(f, '__annotations__', {})
+
+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)
+ 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=str,
+ choices=None, metavar=None):
+ if kind == "positional":
+ assert abbrev is None, abbrev
+ else:
+ assert isinstance(abbrev, str) and len(abbrev) == 1, abbrev
+ self.help = help
+ self.kind = kind
+ self.abbrev = abbrev
+ self.type = type
+ self.choices = choices
+ self.metavar = metavar
+
+ def from_(cls, obj):
+ "Helper to convert an object into an annotation, if needed"
+ if is_annotation(obj):
+ return obj # do nothing
+ elif hasattr(obj, '__iter__') and not isinstance(obj, str):
+ return cls(*obj)
+ return cls(obj)
+ from_ = classmethod(from_)
+
+NONE = object() # sentinel use to signal the absence of a default
+
+valid_attrs = getfullargspec(argparse.ArgumentParser.__init__).args[1:]
+
+def parser_from(func):
+ """
+ Extract the arguments from the attributes of the passed function and
+ return an ArgumentParser instance.
+ """
+ short_prefix = getattr(func, 'short_prefix', '-')
+ long_prefix = getattr(func, 'long_prefix', '--')
+ attrs = {'description': func.__doc__}
+ for n, v in vars(func).items():
+ if n in valid_attrs:
+ attrs[n] = v
+ p = argparse.ArgumentParser(**attrs)
+ f = p.argspec = getfullargspec(func)
+ defaults = f.defaults or ()
+ n_args = len(f.args)
+ n_defaults = len(defaults)
+ alldefaults = (NONE,) * (n_args - n_defaults) + defaults
+ for name, default in zip(f.args, alldefaults):
+ a = Annotation.from_(f.annotations.get(name, ()))
+ if default is NONE:
+ dflt, metavar = None, a.metavar
+ else:
+ dflt, metavar = default, a.metavar or str(default)
+ if a.kind in ('option', 'flag'):
+ short = short_prefix + a.abbrev
+ long = long_prefix + name
+ elif default is NONE: # required argument
+ p.add_argument(name, help=a.help, type=a.type, choices=a.choices,
+ metavar=metavar)
+ else: # default argument
+ p.add_argument(name, nargs='?', help=a.help, default=dflt,
+ type=a.type, choices=a.choices, metavar=metavar)
+ if a.kind == 'option':
+ p.add_argument(short, long, help=a.help, default=dflt,
+ type=a.type, choices=a.choices, metavar=metavar)
+ elif a.kind == 'flag':
+ if default is not NONE:
+ raise TypeError('Flag %r does not want a default' % name)
+ p.add_argument(short, long, action='store_true', help=a.help)
+ if f.varargs:
+ a = Annotation.from_(f.annotations.get(f.varargs, ()))
+ p.add_argument(f.varargs, nargs='*', help=a.help, default=[],
+ type=a.type, metavar=a.metavar)
+ return p
+
+def call(func, arglist=sys.argv[1:]):
+ """
+ Parse the given arglist by using an argparser inferred from the
+ annotations of the given function (the main function of the script)
+ and call that function with the parsed arguments. The user can
+ provide a custom parse_annotation hook or replace the default one.
+ """
+ p = parser_from(func)
+ argdict = vars(p.parse_args(arglist))
+ args = [argdict[a] for a in p.argspec.args]
+ varargs = argdict.get(p.argspec.varargs, [])
+ func(*(args + varargs))
diff --git a/plac/setup.py b/plac/setup.py
new file mode 100644
index 0000000..ca34c5b
--- /dev/null
+++ b/plac/setup.py
@@ -0,0 +1,29 @@
+try:
+ from setuptools import setup
+except ImportError:
+ from distutils.core import setup
+import os.path
+import plac
+
+if __name__ == '__main__':
+ setup(name=plac.__name__,
+ version=plac.__version__,
+ description='The easiest 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'],
+ install_requires=['argparse>=1.1'],
+ 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)
diff --git a/plac/test_plac.py b/plac/test_plac.py
new file mode 100644
index 0000000..c961759
--- /dev/null
+++ b/plac/test_plac.py
@@ -0,0 +1,88 @@
+"""
+The tests are runnable with nose, with py.test, or even as standalone script
+"""
+
+import sys
+import plac
+
+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)
+
+p1 = parser_from(lambda delete, *args: None,
+ delete=('delete a file', 'option', 'd'))
+
+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'))
+
+if __name__ == '__main__':
+ n = 0
+ for name, test in sorted(globals().items()):
+ if name.startswith('test_'):
+ print('Running ' + name)
+ test()
+ n +=1
+ print('Executed %d tests OK' % n)