diff options
author | Michele Simionato <michele.simionato@gmail.com> | 2010-06-01 13:22:00 +0200 |
---|---|---|
committer | Michele Simionato <michele.simionato@gmail.com> | 2010-06-01 13:22:00 +0200 |
commit | 7ee9559aa9313824ea327111b7287b716172c286 (patch) | |
tree | def050dfc4dfc55848be93dcd5725b50a4fb85b3 /plac/plac.py | |
parent | 97dcd8ebb064319da3060d9795d4943b88870cd3 (diff) | |
download | micheles-7ee9559aa9313824ea327111b7287b716172c286.tar.gz |
Removed the clap project remnants
Diffstat (limited to 'plac/plac.py')
-rw-r--r-- | plac/plac.py | 156 |
1 files changed, 156 insertions, 0 deletions
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)) |