summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnthon van der Neut <anthon@mnt.org>2014-10-15 22:27:57 +0200
committerAnthon van der Neut <anthon@mnt.org>2014-10-15 22:27:57 +0200
commit59dfe13f5b31a4160fb0365dfab0c74d674b1e3b (patch)
tree01698e6033dd1d806ed3e238dfb2d2ecf1acf8a5
parent937fd83a0cf7fc443843a09ac05759c6a3ec6d1c (diff)
downloadruamel.std.argparse-59dfe13f5b31a4160fb0365dfab0c74d674b1e3b.tar.gz
added documentation and example programs
-rw-r--r--Makefile8
-rw-r--r--README.rst304
-rw-r--r--__init__.py2
-rw-r--r--_action/checksinglestore.py6
-rw-r--r--example/aliases.py13
-rw-r--r--example/checksingleaction.py9
-rw-r--r--example/countaction.py11
-rw-r--r--example/smartformatter.py28
-rw-r--r--example/splitaction.py11
-rw-r--r--example/testcmd.py44
-rw-r--r--test/test_argparse.py2
11 files changed, 430 insertions, 8 deletions
diff --git a/Makefile b/Makefile
index a44c161..81e8fb1 100644
--- a/Makefile
+++ b/Makefile
@@ -10,8 +10,8 @@ clean:
rm -rf build .tox $(PKGNAME).egg-info/ README.pdf
find . -name "*.pyc" -exec rm {} +
-BBASE:=ssh://hg@bitbucket.org/ruamel
-BB:=$(BBASE)/std.$$(/usr/bin/basename $$PWD)
+updatereadme:
+ updatereadme
-bitbucket:
- ssh ruamel@localhost "cd $$PWD; hg push $(BB)"
+layout: pdf
+ cp README.pdf /data0/tmp/pdf
diff --git a/README.rst b/README.rst
index 393ee03..ee25009 100644
--- a/README.rst
+++ b/README.rst
@@ -1,2 +1,306 @@
argparse extensions
===================
+
+This package provides extensions to argparse on two levels:
+
+- basic argparse extensions: default subparser, subparser aliases in
+ 2.X
+- additional actions that can be specified for add_argument
+- smart formatter that allows combination of defaults help formatting
+ **and** raw desciptions
+- wrapper for argparse using decorators
+
+Extensions to basic argparse
+----------------------------
+
+Insert the following to be able to specify `aliases
+<https://docs.python.org/3/library/argparse.html#sub-commands>`_ in
+subparser definitions in 2.6 and 2.7::
+
+ from __future__ import print_function
+
+ import sys
+ from ruamel.std.argparse import ArgumentParser, SubParsersAction
+
+ parser = ArgumentParser()
+ if sys.version_info < (3,): # add aliases support
+ parser.register('action', 'parsers', SubParsersAction)
+ subparsers = parser.add_subparsers()
+ checkout = subparsers.add_parser('checkout', aliases=['co'])
+ checkout.add_argument('foo')
+ args = parser.parse_args(['co', 'bar'])
+ print(args)
+
+.. example code aliases.py
+
+Resulting in::
+
+ Namespace(foo='bar')
+
+
+.. example output aliases.py
+
+Additional actions
+------------------
+
+CountAction
++++++++++++
+
+Count up and down::
+
+ from __future__ import print_function
+
+ from ruamel.std.argparse import CountAction
+ import argparse
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--verbose', '-v', action=CountAction, const=1, nargs=0)
+ parser.add_argument('--quiet', '-q', action=CountAction, dest='verbose',
+ const=-1, nargs=0)
+
+ print(parser.parse_args("--verbose -v -q".split()))
+
+.. example code countaction.py
+
+results in::
+
+ Namespace(verbose=1)
+
+
+.. example output countaction.py
+
+
+SplitAppend
++++++++++++
+
+Append after splitting on "``,``". Running::
+
+ from __future__ import print_function
+
+ from ruamel.std.argparse import SplitAppendAction
+ import argparse
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-d', action=SplitAppendAction)
+
+ print(parser.parse_args("-d ab -d cd -d kl -d mn".split()))
+ print(parser.parse_args("-d ab,cd,kl,mn".split()))
+ print(parser.parse_args("-d ab,cd -d kl,mn".split()))
+
+.. example code splitaction.py
+
+results in::
+
+ Namespace(d=['ab', 'cd', 'kl', 'mn'])
+ Namespace(d=['ab', 'cd', 'kl', 'mn'])
+ Namespace(d=['ab', 'cd', 'kl', 'mn'])
+
+
+.. example output splitaction.py
+
+CheckSingleStoreAction
+++++++++++++++++++++++
+
+Complain if the same option is called multiple times::
+
+ from __future__ import print_function
+
+ from ruamel.std.argparse import CheckSingleStoreAction
+ import argparse
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--check', '-c', action=CheckSingleStoreAction, const=1, nargs=0)
+
+ print(parser.parse_args("--check -c".split()))
+
+.. example code checksingleaction.py
+
+results in::
+
+ WARNING: previous optional argument "-c []" overwritten by "-c []"
+ Namespace(check=[])
+
+
+.. example output checksingleaction.py
+
+Smart formatting
+----------------
+
+You can only specify one formatter in standard argparse, so you cannot
+both have pre-formatted description. using
+RawDescriptionHelpFormatter,as well as default arguments with
+ArgumentDefaultsHelpFormatter.
+
+The ``SmartFormatter`` is a subclass of ``argparse.HelpFormatter`` and
+has the normal formatter as default. Help text can be marked at the
+beginning for variations in formatting:
+
+- ``R|..`` format raw, i.e. don't wrap and fill out, observer newline
+- ``*|..`` format a password help, never echo password defaults
+- ``D|..`` add defaults to **all** entries (that is why having ``*|``
+ is important)
+
+The version string is formatted using _split_lines and preserves any
+line breaks in the version string.
+
+::
+
+ from __future__ import print_function
+
+ from ruamel.std.argparse import SmartFormatter
+ import argparse
+
+ def exit(self, *args, **kw):
+ pass
+
+ argparse.ArgumentParser.exit = exit
+
+ # the 'D|....' in the second pass triggers generating defaults for all entries,
+ # while being smart about which one already have a %(default)s
+
+ for index, log_s in enumerate(['log to file', 'D|log to file']):
+ parser = argparse.ArgumentParser(formatter_class=SmartFormatter)
+
+ parser.add_argument('--log', default='abc.log', help=log_s)
+ parser.add_argument('--username',
+ help='username to login with (default: %(default)s)')
+ parser.add_argument('--password', help='*|password to use for login')
+ parser.add_argument('--recursive', '-r', action='store_true',
+ help="R|recurse into subdirectories \nto find files")
+ parser.set_defaults(username='anthon', password="test123")
+
+ if index > 0:
+ print('--------------------------------------\n')
+ parser.parse_args(["--help"])
+
+
+.. example code smartformatter.py
+
+results in::
+
+ usage: smartformatter.py [-h] [--log LOG] [--username USERNAME]
+ [--password PASSWORD] [--recursive]
+
+ optional arguments:
+ -h, --help show this help message and exit
+ --log LOG log to file
+ --username USERNAME username to login with (default: anthon)
+ --password PASSWORD password to use for login
+ --recursive, -r recurse into subdirectories
+ to find files
+ --------------------------------------
+
+ usage: smartformatter.py [-h] [--log LOG] [--username USERNAME]
+ [--password PASSWORD] [--recursive]
+
+ optional arguments:
+ -h, --help show this help message and exit
+ --log LOG log to file (default: abc.log)
+ --username USERNAME username to login with (default: anthon)
+ --password PASSWORD password to use for login (default: *******)
+ --recursive, -r recurse into subdirectories
+ to find files (default: False)
+
+
+.. example output smartformatter.py
+
+
+Wrapping argparse
+-----------------
+
+When using argparse with subparser, each of which have their own
+function ( using ``.set_defaults(func=function``) that can be called,
+there is a lot of repetitive code.
+
+An alternative is provided by the ``ProgramBase`` class that should be
+subclassed and the ``sub_parser``, ``option`` and ``version``
+decorators that can be applied to methods of that subclass.
+
+A typical use case is::
+
+ from __future__ import print_function
+
+ import sys
+ import os
+
+ from ruamel.std.argparse import ProgramBase, option, sub_parser, version, \
+ SmartFormatter
+
+ class TestCmd(ProgramBase):
+ def __init__(self):
+ super(TestCmd, self).__init__(
+ formatter_class=SmartFormatter
+ )
+
+ # you can put these on __init__, but subclassing TestCmd
+ # will cause that to break
+ @option('--quiet', '-q', help='suppress verbosity', action='store_true',
+ global_option=True)
+ @version('version: 1.2.3')
+ def _pb_init(self):
+ # special name for which attribs are included in help
+ pass
+
+ def run(self):
+ if self._args.func:
+ return self._args.func()
+
+ def parse_args(self, *args):
+ self._parse_args(*args)
+
+ @sub_parser(help='specific help for readit')
+ @option('--name', default='abc')
+ def readit(self):
+ print('calling readit')
+
+ @sub_parser('writeit', help='help for writeit')
+ @option('--target')
+ def other_name(self):
+ print('calling writeit')
+
+
+ n = TestCmd()
+ n.parse_args(['--help'])
+ n.run()
+
+.. example code testcmd.py
+
+and output::
+
+ usage: testcmd.py [-h] [--quiet] [--version] {readit,writeit} ...
+
+ positional arguments:
+ {readit,writeit}
+ readit specific help for readit
+ writeit help for writeit
+
+ optional arguments:
+ -h, --help show this help message and exit
+ --quiet, -q suppress verbosity
+ --version show program's version number and exit
+
+
+.. example output testcmd.py
+
+
+
+The method name is by default the name of the sub_parser. This can be
+overriden by providing a non-keyword argument to ``sub_parser``. The
+keyword arguments are passed to the ``add_parser`` method.
+
+The ``option`` functions as ``add_argument``. If ``option`` is put on
+a method that is not a sub_parser, such an option will be a global
+option. These have to be specified before any sub_parser argument when
+invoking the script. Often it is handy to specify such an option with
+an ``global_option=True`` keyword argument. This makes sure that
+option is added to all the sub_parsers as well. This allows you to
+invoke both ``prog --quiet writeit`` and ``prog writeit --quiet``).
+You can assing these options to ``__init__``, but when sub classing
+``TestCmd`` this will lead to problems. It is therefore better to pu
+them on the special handled method ``_pb_init`` if subclassing might
+happen.
+
+Care should be taken that all attributes on ``TestCmd`` are accessed
+during scanning for sub parsers. In particular any ``@property``
+decorated method will be accessed.
+
diff --git a/__init__.py b/__init__.py
index 4d551d3..da35031 100644
--- a/__init__.py
+++ b/__init__.py
@@ -32,7 +32,7 @@ def _convert_version(tup):
# <
-version_info = (0, 2, "alpha", 2)
+version_info = (0, 3, 1)
__version__ = _convert_version(version_info)
del _convert_version
diff --git a/_action/checksinglestore.py b/_action/checksinglestore.py
index 27d5cc8..11fd80c 100644
--- a/_action/checksinglestore.py
+++ b/_action/checksinglestore.py
@@ -11,6 +11,8 @@ class CheckSingleStoreAction(argparse.Action):
if getattr(namespace, self.dest, None) is not None:
print(
'WARNING: previous optional argument "' + option_string + " " +
- getattr(namespace, self.dest) + '" overwritten by "' +
- option_string + " " + values + '"')
+ str(getattr(namespace, self.dest)) + '" overwritten by "' +
+ str(option_string) +
+ " " + str(values) +
+ '"')
setattr(namespace, self.dest, values)
diff --git a/example/aliases.py b/example/aliases.py
new file mode 100644
index 0000000..f2d237e
--- /dev/null
+++ b/example/aliases.py
@@ -0,0 +1,13 @@
+from __future__ import print_function
+
+import sys
+from ruamel.std.argparse import ArgumentParser, SubParsersAction
+
+parser = ArgumentParser()
+if sys.version_info < (3,): # add aliases support
+ parser.register('action', 'parsers', SubParsersAction)
+subparsers = parser.add_subparsers()
+checkout = subparsers.add_parser('checkout', aliases=['co'])
+checkout.add_argument('foo')
+args = parser.parse_args(['co', 'bar'])
+print(args)
diff --git a/example/checksingleaction.py b/example/checksingleaction.py
new file mode 100644
index 0000000..cad05ea
--- /dev/null
+++ b/example/checksingleaction.py
@@ -0,0 +1,9 @@
+from __future__ import print_function
+
+from ruamel.std.argparse import CheckSingleStoreAction
+import argparse
+
+parser = argparse.ArgumentParser()
+parser.add_argument('--check', '-c', action=CheckSingleStoreAction, const=1, nargs=0)
+
+print(parser.parse_args("--check -c".split())) \ No newline at end of file
diff --git a/example/countaction.py b/example/countaction.py
new file mode 100644
index 0000000..b9ee104
--- /dev/null
+++ b/example/countaction.py
@@ -0,0 +1,11 @@
+from __future__ import print_function
+
+from ruamel.std.argparse import CountAction
+import argparse
+
+parser = argparse.ArgumentParser()
+parser.add_argument('--verbose', '-v', action=CountAction, const=1, nargs=0)
+parser.add_argument('--quiet', '-q', action=CountAction, dest='verbose',
+ const=-1, nargs=0)
+
+print(parser.parse_args("--verbose -v -q".split()))
diff --git a/example/smartformatter.py b/example/smartformatter.py
new file mode 100644
index 0000000..cb19f09
--- /dev/null
+++ b/example/smartformatter.py
@@ -0,0 +1,28 @@
+from __future__ import print_function
+
+from ruamel.std.argparse import SmartFormatter
+import argparse
+
+def exit(self, *args, **kw):
+ pass
+
+argparse.ArgumentParser.exit = exit
+
+# the 'D|....' in the second pass triggers generating defaults for all entries,
+# while being smart about which one already have a %(default)s
+
+for index, log_s in enumerate(['log to file', 'D|log to file']):
+ parser = argparse.ArgumentParser(formatter_class=SmartFormatter)
+
+ parser.add_argument('--log', default='abc.log', help=log_s)
+ parser.add_argument('--username',
+ help='username to login with (default: %(default)s)')
+ parser.add_argument('--password', help='*|password to use for login')
+ parser.add_argument('--recursive', '-r', action='store_true',
+ help="R|recurse into subdirectories \nto find files")
+ parser.set_defaults(username='anthon', password="test123")
+
+ if index > 0:
+ print('--------------------------------------\n')
+ parser.parse_args(["--help"])
+
diff --git a/example/splitaction.py b/example/splitaction.py
new file mode 100644
index 0000000..11198bd
--- /dev/null
+++ b/example/splitaction.py
@@ -0,0 +1,11 @@
+from __future__ import print_function
+
+from ruamel.std.argparse import SplitAppendAction
+import argparse
+
+parser = argparse.ArgumentParser()
+parser.add_argument('-d', action=SplitAppendAction)
+
+print(parser.parse_args("-d ab -d cd -d kl -d mn".split()))
+print(parser.parse_args("-d ab,cd,kl,mn".split()))
+print(parser.parse_args("-d ab,cd -d kl,mn".split()))
diff --git a/example/testcmd.py b/example/testcmd.py
new file mode 100644
index 0000000..a319306
--- /dev/null
+++ b/example/testcmd.py
@@ -0,0 +1,44 @@
+from __future__ import print_function
+
+import sys
+import os
+
+from ruamel.std.argparse import ProgramBase, option, sub_parser, version, \
+ SmartFormatter
+
+class TestCmd(ProgramBase):
+ def __init__(self):
+ super(TestCmd, self).__init__(
+ formatter_class=SmartFormatter
+ )
+
+ # you can put these on __init__, but subclassing TestCmd
+ # will cause that to break
+ @option('--quiet', '-q', help='suppress verbosity', action='store_true',
+ global_option=True)
+ @version('version: 1.2.3')
+ def _pb_init(self):
+ # special name for which attribs are included in help
+ pass
+
+ def run(self):
+ if self._args.func:
+ return self._args.func()
+
+ def parse_args(self, *args):
+ self._parse_args(*args)
+
+ @sub_parser(help='specific help for readit')
+ @option('--name', default='abc')
+ def readit(self):
+ print('calling readit')
+
+ @sub_parser('writeit', help='help for writeit')
+ @option('--target')
+ def other_name(self):
+ print('calling writeit')
+
+
+n = TestCmd()
+n.parse_args(['--help'])
+n.run()
diff --git a/test/test_argparse.py b/test/test_argparse.py
index 926b685..d50b46b 100644
--- a/test/test_argparse.py
+++ b/test/test_argparse.py
@@ -1,3 +1,4 @@
+# coding: utf-8
import pytest
@@ -7,7 +8,6 @@ from textwrap import dedent
def exit(self=None, status=None, message=None):
pass
-# default for tox stub is to Fail
def test_argparse(capsys):
desc = dedent("""\
Please do not mess up this text!