diff --git a/test/test_readme.py b/test/test_readme.py index 6e86c04..54ccee0 100644 --- a/test/test_readme.py +++ b/test/test_readme.py @@ -59,7 +59,7 @@ def get_config(has_tree=False): def test_readme_help(): - output = """usage: prog.py [-h] [-v] {str,list,int,none} + output = """usage: prog.py [-h] [-v] [-nv] {str,list,int,none} positional arguments: {str,list,int,none} choice the sub argument @@ -67,6 +67,7 @@ positional arguments: optional arguments: -h, --help show this help message and exit -v, --verbosity increase output verbosity + -nv, --no-verbosity """ parser = TiramisuCmdlineParser(get_config(), 'prog.py') f = StringIO() @@ -76,7 +77,7 @@ optional arguments: def test_readme_help_tree(): - output = """usage: prog.py [-h] [-v] {str,list,int,none} + output = """usage: prog.py [-h] [-v] [-nv] {str,list,int,none} optional arguments: -h, --help show this help message and exit @@ -84,6 +85,7 @@ optional arguments: root: {str,list,int,none} choice the sub argument -v, --root.verbosity increase output verbosity + -nv, --root.no-verbosity """ parser = TiramisuCmdlineParser(get_config(True), 'prog.py') f = StringIO() @@ -93,7 +95,7 @@ root: def test_readme_help_tree_flatten(): - output = """usage: prog.py [-h] [-v] {str,list,int,none} + output = """usage: prog.py [-h] [-v] [-nv] {str,list,int,none} optional arguments: -h, --help show this help message and exit @@ -101,6 +103,7 @@ optional arguments: root: {str,list,int,none} choice the sub argument -v, --verbosity increase output verbosity + -nv, --no-verbosity """ parser = TiramisuCmdlineParser(get_config(True), 'prog.py', fullpath=False) f = StringIO() @@ -110,12 +113,13 @@ root: def test_readme_help_modif_positional(): - output = """usage: prog.py str [-h] [-v] --str STR + output = """usage: prog.py str [-h] [-v] [-nv] --str STR optional arguments: - -h, --help show this help message and exit - -v, --verbosity increase output verbosity - --str STR string option + -h, --help show this help message and exit + -v, --verbosity increase output verbosity + -nv, --no-verbosity + --str STR string option """ parser = TiramisuCmdlineParser(get_config(), 'prog.py') f = StringIO() @@ -130,11 +134,12 @@ optional arguments: def test_readme_help_modif(): - output = """usage: prog.py str --str toto [-h] [-v] + output = """usage: prog.py str --str toto [-h] [-v] [-nv] optional arguments: - -h, --help show this help message and exit - -v, --verbosity increase output verbosity + -h, --help show this help message and exit + -v, --verbosity increase output verbosity + -nv, --no-verbosity """ parser = TiramisuCmdlineParser(get_config(), 'prog.py') f = StringIO() @@ -167,8 +172,27 @@ optional arguments: assert f.getvalue() == output +def test_readme_help_modif_short_no(): + output = """usage: prog.py str --verbosity [-h] --str STR + +optional arguments: + -h, --help show this help message and exit + --str STR string option +""" + parser = TiramisuCmdlineParser(get_config(), 'prog.py') + f = StringIO() + with redirect_stdout(f): + try: + parser.parse_args(['str', '-nv', '--help']) + except SystemExit as err: + assert str(err) == "0" + else: + raise Exception('must raises') + assert f.getvalue() == output + + def test_readme_positional_mandatory(): - output = """usage: prog.py [-h] [-v] {str,list,int,none} + output = """usage: prog.py [-h] [-v] [-nv] {str,list,int,none} prog.py: error: the following arguments are required: cmd """ parser = TiramisuCmdlineParser(get_config(), 'prog.py') @@ -184,7 +208,7 @@ prog.py: error: the following arguments are required: cmd def test_readme_positional_mandatory_tree(): - output = """usage: prog.py [-h] [-v] {str,list,int,none} + output = """usage: prog.py [-h] [-v] [-nv] {str,list,int,none} prog.py: error: the following arguments are required: root.cmd """ parser = TiramisuCmdlineParser(get_config(True), 'prog.py') @@ -200,7 +224,7 @@ prog.py: error: the following arguments are required: root.cmd def test_readme_positional_mandatory_tree_flatten(): - output = """usage: prog.py [-h] [-v] {str,list,int,none} + output = """usage: prog.py [-h] [-v] [-nv] {str,list,int,none} prog.py: error: the following arguments are required: cmd """ parser = TiramisuCmdlineParser(get_config(True), 'prog.py', fullpath=False) @@ -216,7 +240,7 @@ prog.py: error: the following arguments are required: cmd def test_readme_mandatory(): - output = """usage: prog.py str [-h] [-v] --str STR + output = """usage: prog.py str [-h] [-v] [-nv] --str STR prog.py: error: the following arguments are required: --str """ parser = TiramisuCmdlineParser(get_config(), 'prog.py') @@ -232,7 +256,7 @@ prog.py: error: the following arguments are required: --str def test_readme_mandatory_tree(): - output = """usage: prog.py str [-h] [-v] --root.str STR + output = """usage: prog.py str [-h] [-v] [-nv] --root.str STR prog.py: error: the following arguments are required: --root.str """ parser = TiramisuCmdlineParser(get_config(True), 'prog.py') @@ -248,7 +272,7 @@ prog.py: error: the following arguments are required: --root.str def test_readme_mandatory_tree_flatten(): - output = """usage: prog.py str [-h] [-v] --str STR + output = """usage: prog.py str [-h] [-v] [-nv] --str STR prog.py: error: the following arguments are required: --str """ parser = TiramisuCmdlineParser(get_config(True), 'prog.py', fullpath=False) @@ -264,7 +288,7 @@ prog.py: error: the following arguments are required: --str def test_readme_cross(): - output = """usage: prog.py none [-h] [-v] + output = """usage: prog.py none [-h] [-v] [-nv] prog.py: error: unrecognized arguments: --int """ parser = TiramisuCmdlineParser(get_config(), 'prog.py') @@ -280,7 +304,7 @@ prog.py: error: unrecognized arguments: --int def test_readme_cross_tree(): - output = """usage: prog.py none [-h] [-v] + output = """usage: prog.py none [-h] [-v] [-nv] prog.py: error: unrecognized arguments: --int """ parser = TiramisuCmdlineParser(get_config(True), 'prog.py') @@ -296,7 +320,7 @@ prog.py: error: unrecognized arguments: --int def test_readme_cross_tree_flatten(): - output = """usage: prog.py none [-h] [-v] + output = """usage: prog.py none [-h] [-v] [-nv] prog.py: error: unrecognized arguments: --int """ parser = TiramisuCmdlineParser(get_config(True), 'prog.py', fullpath=False) @@ -388,6 +412,17 @@ def test_readme_int_verbosity_short(): assert config.value.dict() == output +def test_readme_int_verbosity_short_no(): + output = {'cmd': 'int', + 'int': 3, + 'verbosity': False, + 'v': False} + config = get_config() + parser = TiramisuCmdlineParser(config, 'prog.py') + parser.parse_args(['int', '--int', '3', '-nv']) + assert config.value.dict() == output + + def test_readme_int_verbosity_short_tree(): output = {'root.cmd': 'int', 'root.int': 3, diff --git a/tiramisu_cmdline_parser/tiramisu_cmdline_parser.py b/tiramisu_cmdline_parser/tiramisu_cmdline_parser.py index 95c0f0b..6a5beac 100644 --- a/tiramisu_cmdline_parser/tiramisu_cmdline_parser.py +++ b/tiramisu_cmdline_parser/tiramisu_cmdline_parser.py @@ -12,10 +12,11 @@ # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . - - from typing import Union, List, Optional from argparse import ArgumentParser, Namespace, SUPPRESS, _HelpAction, HelpFormatter +from copy import copy + + try: from tiramisu import Config from tiramisu.error import PropertiesOptionError @@ -146,8 +147,19 @@ class TiramisuCmdlineParser(ArgumentParser): def add_subparsers(self, *args, **kwargs): raise NotImplementedError('do not use add_subparsers') - def _gen_argument(self, name, properties): - if len(name) == 1 and 'longargument' not in properties: + def _gen_argument(self, name, longargument, no_prefix=False): + shortarg = len(name) == 1 and not longargument + if no_prefix: + if shortarg: + prefix = 'n' + else: + prefix = 'no-' + if '.' in name: + sname = name.rsplit('.', 1) + name = sname[0] + '.' + prefix + sname[1] + else: + name = prefix + name + if shortarg: return self.prefix_chars + name return self.prefix_chars * 2 + name @@ -182,67 +194,75 @@ class TiramisuCmdlineParser(ArgumentParser): if option.issymlinkoption(): symlink_name = option.name(follow_symlink=True) if symlink_name in actions: - actions[symlink_name][0].insert(0, self._gen_argument(option.name(), properties)) + actions[symlink_name][0][0].insert(0, self._gen_argument(option.name(), 'longargument' in properties)) + if len(actions[symlink_name]) == 2: + actions[symlink_name][1][0].insert(0, self._gen_argument(option.name(), False, True)) continue - if _forhelp and not obj.owner.isdefault() and obj.value.get(): + if _forhelp and not obj.owner.isdefault() and obj.value.get() is not None: if 'positional' not in properties: - self.prog += ' {}'.format(self._gen_argument(name, properties)) + self.prog += ' {}'.format(self._gen_argument(name, 'longargument' in properties)) if option.type() != 'boolean': self.prog += ' {}'.format(obj.value.get()) else: if 'positional' in properties: + if option.type() == 'boolean': + raise ValueError('boolean option must not be positional') if not 'mandatory' in properties: raise ValueError('"positional" argument must be "mandatory" too') + args = [option.path()] if _forhelp: - args = [option.path()] kwargs['default'] = obj.value.default() else: - args = [option.path()] kwargs['default'] = obj.value.get() kwargs['nargs'] = '?' else: kwargs['dest'] = option.path() kwargs['default'] = SUPPRESS - args = [self._gen_argument(name, properties)] if _forhelp and 'mandatory' in properties: kwargs['required'] = True - - if option.type() == 'boolean': - if 'mandatory' in properties: - raise ValueError('"mandatory" property is not allowed for BoolOption') - #if not isinstance(option.default(), bool): - # raise ValueError('default value is mandatory for BoolOption') - if obj.value.get() is False: - action = 'store_true' - else: - action = 'store_false' - kwargs['action'] = action - else: - if _forhelp: - value = obj.value.default() - else: - value = obj.value.get() - if value not in [None, []]: - #kwargs['default'] = kwargs['const'] = option.default() - #kwargs['action'] = 'store_const' - kwargs['nargs'] = '?' - if option.ismulti(): - if _forhelp and 'mandatory' in properties: - kwargs['nargs'] = '+' + if option.type() == 'boolean': + if obj.value.get() is False: + action = 'store_true' + no_action = 'store_false' else: - kwargs['nargs'] = '*' - if option.type() == 'string': - pass - elif option.type() == 'integer': - kwargs['type'] = int - elif option.type() == 'choice': - kwargs['choices'] = obj.value.list() + action = 'store_false' + no_action = 'store_true' + kwargs['action'] = action + args = [self._gen_argument(name, 'longargument' in properties)] + # + nkwargs = copy(kwargs) + nkwargs['action'] = no_action + del nkwargs['help'] + nargs = [self._gen_argument(name, 'longargument' in properties, True)] + actions[option.name()] = [(args, kwargs), (nargs, nkwargs)] + continue + args = [self._gen_argument(name, 'longargument' in properties)] + if _forhelp: + value = obj.value.default() + else: + value = obj.value.get() + if value not in [None, []]: + #kwargs['default'] = kwargs['const'] = option.default() + #kwargs['action'] = 'store_const' + kwargs['nargs'] = '?' + if option.ismulti(): + if _forhelp and 'mandatory' in properties: + kwargs['nargs'] = '+' else: - pass - #raise NotImplementedError('not supported yet') - actions[option.name()] = (args, kwargs) - for args, kwargs in actions.values(): - group.add_argument(*args, **kwargs) + kwargs['nargs'] = '*' + if option.type() == 'string': + pass + elif option.type() == 'integer': + kwargs['type'] = int + elif option.type() == 'choice': + kwargs['choices'] = obj.value.list() + else: + pass + #raise NotImplementedError('not supported yet') + actions[option.name()] = [(args, kwargs)] + for values in actions.values(): + for args, kwargs in values: + group.add_argument(*args, **kwargs) def parse_args(self, *args, @@ -272,7 +292,7 @@ class TiramisuCmdlineParser(ArgumentParser): name = key else: name = key.rsplit('.', 1)[1] - args = self._gen_argument(name, self.config.option(key).property.get()) + args = self._gen_argument(name, 'longargument' in self.config.option(key).property.get()) else: args = key if not self.fullpath and '.' in args: