Metadata-Version: 2.1
Name: argparse-deco
Version: 0.6.0
Summary: argparse-deco - Syntactic sugar for argparse
Home-page: https://github.com/zaehlwerk/argparse-deco
Author: Gregor Giesen
Author-email: giesen@zaehlwerk.net
License: GPLv3
Keywords: cli
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Provides-Extra: test
Requires-Dist: pytest ; extra == 'test'
Requires-Dist: pytest-cov ; extra == 'test'
Requires-Dist: pytest-flakes ; extra == 'test'
Requires-Dist: pytest-mock ; extra == 'test'
Requires-Dist: pytest-pep8 ; extra == 'test'
Requires-Dist: pytest-runner ; extra == 'test'

=============
argparse-deco
=============

argparse-deco is basically syntatic sugar for argparse using
decorators. Although it inherited some ideas and concepts from
Kevin L. Mitchell's **cli_tools**
(https://github.com/klmitch/cli_tools), it does not share its source
code.

Its main difference is the possibility to abuse Python's class
syntax to define complex CLI tools with nested subcommands and
the use of `inspect.signature` to autogenerate a command's arguments
from a function's signature.

Simple CLI
==========

The API suffices to use three imports.

>>> from argparse_deco import CLI, Arg, Flag

An an example for a simple CLI, one may use `CLI` as decorator for a
function in order to transform it:

>>> @CLI(prog="prog")
... def prog(
...         integers: Arg(metavar='N', nargs='+', type=int,
...                       help="an integerfor the accumulator"),
...         accumulate: Arg('--sum', action='store_const', const=sum,
...                         help="sum the integers (default: find the max)"
...                         )=max):
...     """Process some integers."""
...     print(accumulate(integers))

The decorator `CLI` transforms the function `prog` into an `Command`
instance. Effectively `prog` takes some command line argument like
`[ "1", "2", "4", "--sum" ]` as `cli_args` keyword, which is transformed
by the `argparse` module into arguments `integer` and `accumulate`
passed down to the original function `prog`:

>>> prog(["1", "2", "4", "--sum"])
7
>>> prog(["1", "2", "4"])
4

In order to obtain the `ArgumentParser` instance, `prog` has the class
method `setup_parser`:

>>> parser = prog.setup_parser()
>>> print(parser)
ArgumentParser(prog='prog', usage=None, description='Process some integers.', formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_help=True)
>>> parser.print_usage()
usage: prog [-h] [--sum] N [N ...]

>>> parser.print_help()
usage: prog [-h] [--sum] N [N ...]
<BLANKLINE>
Process some integers.
<BLANKLINE>
positional arguments:
  N           an integerfor the accumulator
<BLANKLINE>
optional arguments:
  -h, --help  show this help message and exit
  --sum       sum the integers (default: find the max)


Arguments
---------

In order for a function's arguments to be processed as
`ArgumentParser` argument, they have to annotated by `Arg`. Basically
`Arg` allows a the type as keyword, and arbitrary keyword arguments
which are passed almost unchanged to `ArgumentParser.add_argument`.


Parser
------

While the `ArgumentParser` instance's `description` is usually the
function's docstring, one may want to further customise it using the
`CLI.parser` decorator which accepts any argument `ArgumentParser`
would.


Groups
------

Arguments can be groupsed by using the `Group` instead of `Arg` in the
argument's annotation, which accepts a group name as first keyword and
the type as second one. The group can be customised (title,
description) using the `CLI.group` decorator:

>>> @CLI("prog")
... @CLI.group('foo', title="Foo", description="Foo group")
... def prog(
...         bar: Arg['foo'](help="Bar option"),
...         baz: Arg['foo'](help="Baz option")):
...     pass
>>> prog.setup_parser().print_help()
usage: prog [-h] bar baz
<BLANKLINE>
optional arguments:
  -h, --help  show this help message and exit
<BLANKLINE>
Foo:
  Foo group
<BLANKLINE>
  bar         Bar option
  baz         Baz option

Similarily using the `CLI.mutually_exclusive` decorator, arguments can
be turned into a mutually exclusive group.


Subcommands
===========

>>> @CLI("prog")
... @CLI.subparsers(help="sub-command help")
... class prog:
...     def __call__(foo: Flag('--foo', help="foo help")):
...         pass
...     def a(bar: Arg(type=int, help="bar help")):
...         """a help"""
...     def b(baz: Arg('--baz', choices='XYZ', help="baz help")):
...         """b help"""
>>> prog.parser.print_help()
usage: prog [-h] [--foo] {a,b} ...
<BLANKLINE>
positional arguments:
  {a,b}       sub-command help
    a         a help
    b         b help
<BLANKLINE>
optional arguments:
  -h, --help  show this help message and exit
  --foo       foo help

>>> prog.parser.parse_args(['a', '12'])
Namespace(_func=<function prog.a at 0x...>, _parser=..., bar=12, foo=False)
>>> prog.parser.parse_args(['--foo', 'b', '--baz', 'Z'])
Namespace(_func=<function prog.b at 0x...>, _parser=..., baz='Z', foo=True)

Deeper levels of subcommands can be generated using class definitions within:

>>> @CLI("prog")
... class prog:
...     class foo:
...         """foo subcommand"""
...         def bar():
...             """foo bar subsubcommand"""
...         def baz():
...             """foo baz subsubcommand"""
...     class oof:
...         def rab():
...             """oof rab subsubcommand"""
...         def zab():
...             """oof zab subsubcommand"""
>>> prog.parser.print_help()
usage: prog [-h] {foo,oof} ...
<BLANKLINE>
positional arguments:
  {foo,oof}
    foo       foo subcommand
<BLANKLINE>
optional arguments:
  -h, --help  show this help message and exit


