diff options
author | Anthon van der Neut <anthon@mnt.org> | 2014-03-28 00:21:02 +0100 |
---|---|---|
committer | Anthon van der Neut <anthon@mnt.org> | 2014-03-28 00:21:02 +0100 |
commit | b4a67a39ef0221ab4d0c7b9011b08fb05f173c45 (patch) | |
tree | 849a4b65f7110031789cc63332a37406ca628bd5 | |
parent | 6198e7e496bc4c840f2a75a973b6a852a2484389 (diff) | |
download | ruamel.std.argparse-b4a67a39ef0221ab4d0c7b9011b08fb05f173c45.tar.gz |
sub sub parsers added
-rw-r--r-- | __init__.py | 120 | ||||
-rw-r--r-- | test/test_program.py | 136 |
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 |