summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnthon van der Neut <anthon@mnt.org>2014-03-28 00:21:02 +0100
committerAnthon van der Neut <anthon@mnt.org>2014-03-28 00:21:02 +0100
commitb4a67a39ef0221ab4d0c7b9011b08fb05f173c45 (patch)
tree849a4b65f7110031789cc63332a37406ca628bd5
parent6198e7e496bc4c840f2a75a973b6a852a2484389 (diff)
downloadruamel.std.argparse-b4a67a39ef0221ab4d0c7b9011b08fb05f173c45.tar.gz
sub sub parsers added
-rw-r--r--__init__.py120
-rw-r--r--test/test_program.py136
2 files changed, 198 insertions, 58 deletions
diff --git a/__init__.py b/__init__.py
index b69c8ca..fe57271 100644
--- a/__init__.py
+++ b/__init__.py
@@ -230,26 +230,68 @@ def create_argument_parser1(self, *args, **keywords):
class ProgramBase(object):
+ """
+ ToDo:
+ - grouping
+ - mutual exclusion
+ """
def __init__(self, *args, **kw):
self._verbose = kw.pop('verbose', 0)
self._parser = argparse.ArgumentParser(*args, **kw)
cls = self
- self._subparsers = None
+ self._sub_parsers = None
+ methods_with_sub_parsers = [] # list to process, multilevel
for x in dir(cls):
if x.startswith('_'):
continue
method = getattr(self, x)
if hasattr(method, "_sub_parser"):
- if self._subparsers is None:
- self._subparsers = self._parser.add_subparsers(
- dest="subparser_level_0", help='sub-command help')
- # (name, aliases=aliases, help=help)
+ if self._sub_parsers is None:
+ # create the top level subparsers
+ self._sub_parsers = self._parser.add_subparsers(
+ dest="subparser_level_0", help=None)
+ methods_with_sub_parsers.append(method)
+ max_depth = 10
+ level = 0
+ all_methods_with_sub_parsers = methods_with_sub_parsers[:]
+ while methods_with_sub_parsers:
+ level += 1
+ if level > max_depth:
+ raise NotImplementedError
+ for method in methods_with_sub_parsers:
+ parent = method._sub_parser['kw'].get('_parent', None)
+ sub_parsers = self._sub_parsers
+ if parent is None:
+ method._sub_parser['level'] = 0
+ # parent sub parser
+ elif not 'level' in parent._sub_parser:
+ print 'skipping', parent.__name__, method.__name__
+ continue
+ else: # have a parent
+ # make sure _parent is no longer in kw
+ method._sub_parser['parent'] = \
+ method._sub_parser['kw'].pop('_parent')
+ level = parent._sub_parser['level'] + 1
+ method._sub_parser['level'] = level
+ print 'level', level
+ ssp = parent._sub_parser.get('sp')
+ if ssp is None:
+ pparser = parent._sub_parser['parser']
+ ssp = pparser.add_subparsers(
+ dest="subparser_level_{}".format(level),
+ )
+ parent._sub_parser['sp'] = ssp
+ sub_parsers = ssp
arg = method._sub_parser['args']
+ print 'arg', arg
if not arg or not isinstance(arg[0], basestring):
arg = list(arg)
- arg.insert(0, x)
- sp = self._subparsers.add_parser(*arg,
+ arg.insert(0, method.__name__)
+ print 'arg', arg
+ sp = sub_parsers.add_parser(*arg,
**method._sub_parser['kw'])
+ # add parser primarily for being able to add subparsers
+ method._sub_parser['parser'] = sp
sp.set_defaults(func=method)
# print x, method._sub_parser
@@ -274,17 +316,8 @@ class ProgramBase(object):
# print 'arg2', arg
sp.add_argument(*arg, **o['kw'])
# print ' opt:', x, method._options
- if False:
- if hasattr(method, "global_only_option"):
- arg = method.global_only_option['args']
- kw = method.global_only_option['kw']
- if not arg or not arg[0].startswith('--'):
- arg = list(arg)
- arg.insert(0, '--' + x)
- try:
- self._parser.add_argument(*arg, **kw)
- except TypeError:
- print('args, kw', arg, kw)
+ print 'removing', method.__name__
+ methods_with_sub_parsers.remove(method)
for x in dir(self):
if x.startswith('_') and not x == '__init__':
continue
@@ -299,8 +332,8 @@ class ProgramBase(object):
except TypeError:
print('args, kw', arg, kw)
if global_option:
- for name in self._subparsers._name_parser_map:
- sp = self._subparsers._name_parser_map[name]
+ for m in all_methods_with_sub_parsers:
+ sp = m._sub_parser['parser']
sp.add_argument(*arg, **kw)
def _parse_args(self, *args):
@@ -322,41 +355,42 @@ class ProgramBase(object):
class Decorator(object):
def __init__(self):
self.target = None
+ self._parent = None
def __call__(self, target):
self.target = target
+ a = args
+ k = kw.copy()
+ if self._parent:
+ a = self._parent[1]
+ k = self._parent[2].copy()
+ k['_parent'] = self._parent[0]
# move options to sub_parser
o = getattr(target, '_options', [])
if o:
del target._options
- target._sub_parser = {'args': args, 'kw': kw, 'options': o}
+ print '__call', a
+ target._sub_parser = {'args': a, 'kw': k, 'options': o}
+ # assign the name
+ target.sub_parser = self.sub_parser
return target
- def XXXoption(self, *a, **k):
+ def sub_parser(self, *a, **k):
""" after a method xyz is decorated as sub_parser, you can add
- options with:
- @xyz.option('--force')
+ a sub parser by decorating another method with:
+ @xyz.sub_parser(*arguments, **keywords)
def force(self):
pass
- if option argument is a short option ('-F'), add the long
- option based on the function name
- if no option argument, add function_name if 'nargs' in k
- else add long option based on function name
+ if arguments is not given the name will be the method name
"""
- # here check if a is option name else take from function
- def option_func(*args):
- """called with the original function that is decorated
- add function name to last options element
- """
- self.target.options[-1]['fun'] = args[0].__name__
- pass
-
- if not hasattr(self.target, "options"):
- self.target.options = []
- self.target.options.append({'args': a, 'kw': k})
- return option_func
+ decorator = Decorator()
+ print '>>>>', self.target.__name__, self
+ decorator._parent = (self.target, a, k)
+ return decorator
+
decorator = Decorator()
+ #dbg('----', kw.get('_parent'))
return decorator
@@ -364,4 +398,8 @@ def option(*args, **kw):
return ProgramBase.option(*args, **kw)
def sub_parser(*args, **kw):
- return ProgramBase.sub_parser(*args, **kw) \ No newline at end of file
+ return ProgramBase.sub_parser(*args, **kw)
+
+def version(version_string):
+ return ProgramBase.option(
+ '--version', action='version', version=version_string) \ No newline at end of file
diff --git a/test/test_program.py b/test/test_program.py
index 719e653..c29599e 100644
--- a/test/test_program.py
+++ b/test/test_program.py
@@ -1,11 +1,13 @@
import pytest
-from ruamel.std.argparse import ProgramBase, option, sub_parser
+from ruamel.std.argparse import ProgramBase, option, sub_parser, version
class Program(ProgramBase):
@option('--verbose', global_option=True, action='store_true')
@option('--quiet', action='store_true')
+ #@option('--version', action='version', version='version: 42')
+ @version('version: 42')
def __init__(self):
ProgramBase.__init__(self)
@@ -16,26 +18,126 @@ class Program(ProgramBase):
def hg(self):
pass
- #@hg.sub_parser()
- #def check(self):
- # pass
+ # have to define hg.sub_parser after creating sub_parser
+ @hg.sub_parser(help='check something')
+ @option('--extra')
+ def check(self):
+ pass
@sub_parser(help="call git")
def git(self):
pass
+ @git.sub_parser('abc')
+ @option('--extra')
+ def just_some_name(self):
+ pass
+
+ @git.sub_parser('hihi', help='helphelp')
+ def hki(self):
+ pass
-def test_program(capsys):
- print '------------- hallo'
- p = Program()
+ @hki.sub_parser('oops')
+ def oops(self):
+ pass
+
+
+ #@sub_parser('svn')
+ #def subversion(self):
+ # pass
+
+class ParseHelpOutput:
+ def __init__(self, capsys, error=False):
+ self._capsys = capsys
+ out, err = self._capsys.readouterr()
+ o = err if error else out
+ self(o)
+
+ def __call__(self, out):
+ print out
+ print '+++++'
+ self._chunks = {}
+ chunk = None
+ for line in out.splitlines():
+ lsplit = line.split()
+ chunk_name = None
+ if lsplit and lsplit[0][-1] == ':':
+ chunk_name = lsplit[0]
+ line = line.split(':', 1)[1]
+ if line and line[-1] == ':':
+ chunk_name = line
+ if chunk_name:
+ chunk_name = chunk_name[:-1]
+ chunk = self._chunks.setdefault(chunk_name, [])
+ if chunk is None or not line.strip():
+ continue
+ chunk.append(line)
+ print self._chunks
- with pytest.raises(SystemExit):
- p._parse_args('-h'.split())
- out, err = capsys.readouterr()
- print out
- print '++++++++++++++'
- with pytest.raises(SystemExit):
- p._parse_args('hg -h'.split())
- out, err = capsys.readouterr()
- print out
- assert False \ No newline at end of file
+ def start(self, chunk, s, strip=True):
+ for l in self._chunks[chunk]:
+ if l.lstrip().startswith(s):
+ return True
+ return False
+
+
+@pytest.fixture(scope='class')
+def program():
+ return Program()
+
+
+class TestProgram:
+ def test_help(self, capsys, program):
+ with pytest.raises(SystemExit):
+ program._parse_args('-h'.split())
+ pho = ParseHelpOutput(capsys)
+ assert pho.start('positional arguments', 'hg')
+ assert pho.start('optional arguments', '--verbose')
+
+ def test_help_sub_parser(self, capsys, program):
+ with pytest.raises(SystemExit):
+ program._parse_args('hg -h'.split())
+ pho = ParseHelpOutput(capsys)
+ assert pho.start('positional arguments', 'file-name')
+ assert pho.start('optional arguments', '--verbose')
+ assert not pho.start('optional arguments', '--extra')
+
+ def test_sub_sub_parser(self, capsys, program):
+ with pytest.raises(SystemExit):
+ program._parse_args('hg check -h'.split())
+ pho = ParseHelpOutput(capsys)
+ #assert not pho.start('positional arguments', 'file-name')
+ #assert not pho.start('positional arguments', 'hg')
+ assert pho.start('optional arguments', '--extra')
+ assert pho.start('optional arguments', '--verbose')
+
+ def test_git_help_sub_parser(self, capsys, program):
+ with pytest.raises(SystemExit):
+ program._parse_args('git -h'.split())
+ pho = ParseHelpOutput(capsys)
+ assert pho.start('optional arguments', '--verbose')
+ assert not pho.start('optional arguments', '--extra')
+
+ def test_git_sub_sub_parser(self, capsys, program):
+ with pytest.raises(SystemExit):
+ program._parse_args('git abc -h'.split())
+ pho = ParseHelpOutput(capsys)
+ assert pho.start('optional arguments', '--extra')
+ assert pho.start('optional arguments', '--verbose')
+
+ def test_git_sub_sub_sub_parser(self, capsys, program):
+ with pytest.raises(SystemExit):
+ program._parse_args('git hihi oops -h'.split())
+ pho = ParseHelpOutput(capsys)
+ assert pho.start('usage', 'py.test git hihi oops')
+ assert pho.start('optional arguments', '--verbose')
+
+ def test_version(self, capsys, program):
+ with pytest.raises(SystemExit):
+ program._parse_args('--version'.split())
+ pho = ParseHelpOutput(capsys, error=True)
+ assert pho.start('version', '42')
+
+if __name__ == '__main__':
+ p = Program()
+ p._parse_args() \ No newline at end of file