diff --git a/test/test_readme.py b/test/test_readme.py index 52bb794..e026fcf 100644 --- a/test/test_readme.py +++ b/test/test_readme.py @@ -7,7 +7,7 @@ from tiramisu import IntOption, StrOption, BoolOption, ChoiceOption, \ SymLinkOption, OptionDescription, Config -def get_config(): +def get_config(has_tree=False): choiceoption = ChoiceOption('cmd', 'choice the sub argument', ('str', 'list', 'int', 'none'), @@ -39,18 +39,25 @@ def get_config(): 'expected': 'int', 'action': 'disabled', 'inverse': True}]) - config = Config(OptionDescription('root', - 'root', - [choiceoption, - booloption, - short_booloption, - str_, - list_, - int_ - ])) + + root = OptionDescription('root', + 'root', + [choiceoption, + booloption, + short_booloption, + str_, + list_, + int_ + ]) + if has_tree: + root = OptionDescription('root', + 'root', + [root]) + config = Config(root) config.property.read_write() return config + def test_readme_help(): output = """usage: prog.py [-h] [-v] {str,list,int,none} @@ -68,6 +75,40 @@ optional arguments: assert f.getvalue() == output +def test_readme_help_tree(): + output = """usage: prog.py [-h] [-v] {str,list,int,none} + +optional arguments: + -h, --help show this help message and exit + +root: + {str,list,int,none} choice the sub argument + -v, --root.verbosity increase output verbosity +""" + parser = TiramisuCmdlineParser(get_config(True), 'prog.py') + f = StringIO() + with redirect_stdout(f): + parser.print_help() + assert f.getvalue() == output + + +def test_readme_help_tree_flatten(): + output = """usage: prog.py [-h] [-v] {str,list,int,none} + +optional arguments: + -h, --help show this help message and exit + +root: + {str,list,int,none} choice the sub argument + -v, --verbosity increase output verbosity +""" + parser = TiramisuCmdlineParser(get_config(True), 'prog.py', fullpath=False) + f = StringIO() + with redirect_stdout(f): + parser.print_help() + assert f.getvalue() == output + + def test_readme_positional_mandatory(): output = """usage: prog.py [-h] [-v] {str,list,int,none} prog.py: error: the following arguments are required: cmd @@ -84,6 +125,38 @@ prog.py: error: the following arguments are required: cmd assert f.getvalue() == output +def test_readme_positional_mandatory_tree(): + output = """usage: prog.py [-h] [-v] {str,list,int,none} +prog.py: error: the following arguments are required: root.cmd +""" + parser = TiramisuCmdlineParser(get_config(True), 'prog.py') + f = StringIO() + with redirect_stderr(f): + try: + parser.parse_args([]) + except SystemExit as err: + assert str(err) == "2" + else: + raise Exception('must raises') + assert f.getvalue() == output + + +def test_readme_positional_mandatory_tree_flatten(): + output = """usage: prog.py [-h] [-v] {str,list,int,none} +prog.py: error: the following arguments are required: cmd +""" + parser = TiramisuCmdlineParser(get_config(True), 'prog.py', fullpath=False) + f = StringIO() + with redirect_stderr(f): + try: + parser.parse_args([]) + except SystemExit as err: + assert str(err) == "2" + else: + raise Exception('must raises') + assert f.getvalue() == output + + def test_readme_mandatory(): output = """usage: prog.py [-h] [-v] --str STR {str,list,int,none} prog.py: error: the following arguments are required: --str @@ -100,6 +173,38 @@ prog.py: error: the following arguments are required: --str assert f.getvalue() == output +def test_readme_mandatory_tree(): + output = """usage: prog.py [-h] [-v] --root.str STR {str,list,int,none} +prog.py: error: the following arguments are required: --root.str +""" + parser = TiramisuCmdlineParser(get_config(True), 'prog.py') + f = StringIO() + with redirect_stderr(f): + try: + parser.parse_args(['str']) + except SystemExit as err: + assert str(err) == "2" + else: + raise Exception('must raises') + assert f.getvalue() == output + + +def test_readme_mandatory_tree_flatten(): + output = """usage: prog.py [-h] [-v] --str STR {str,list,int,none} +prog.py: error: the following arguments are required: --str +""" + parser = TiramisuCmdlineParser(get_config(True), 'prog.py', fullpath=False) + f = StringIO() + with redirect_stderr(f): + try: + parser.parse_args(['str']) + except SystemExit as err: + assert str(err) == "2" + else: + raise Exception('must raises') + assert f.getvalue() == output + + def test_readme_cross(): output = """usage: prog.py [-h] [-v] {str,list,int,none} prog.py: error: unrecognized arguments: --int @@ -116,6 +221,38 @@ prog.py: error: unrecognized arguments: --int assert f.getvalue() == output +def test_readme_cross_tree(): + output = """usage: prog.py [-h] [-v] {str,list,int,none} +prog.py: error: unrecognized arguments: --int +""" + parser = TiramisuCmdlineParser(get_config(True), 'prog.py') + f = StringIO() + with redirect_stderr(f): + try: + parser.parse_args(['none', '--int']) + except SystemExit as err: + assert str(err) == "2" + else: + raise Exception('must raises') + assert f.getvalue() == output + + +def test_readme_cross_tree_flatten(): + output = """usage: prog.py [-h] [-v] {str,list,int,none} +prog.py: error: unrecognized arguments: --int +""" + parser = TiramisuCmdlineParser(get_config(True), 'prog.py', fullpath=False) + f = StringIO() + with redirect_stderr(f): + try: + parser.parse_args(['none', '--int']) + except SystemExit as err: + assert str(err) == "2" + else: + raise Exception('must raises') + assert f.getvalue() == output + + def test_readme_int(): output = {'cmd': 'int', 'int': 3, @@ -124,7 +261,29 @@ def test_readme_int(): config = get_config() parser = TiramisuCmdlineParser(config, 'prog.py') parser.parse_args(['int', '--int', '3']) - # assert config.value.dict() == output + assert config.value.dict() == output + + +def test_readme_int_tree(): + output = {'root.cmd': 'int', + 'root.int': 3, + 'root.verbosity': False, + 'root.v': False} + config = get_config(True) + parser = TiramisuCmdlineParser(config, 'prog.py') + parser.parse_args(['int', '--root.int', '3']) + assert config.value.dict() == output + + +def test_readme_int_tree_flatten(): + output = {'root.cmd': 'int', + 'root.int': 3, + 'root.verbosity': False, + 'root.v': False} + config = get_config(True) + parser = TiramisuCmdlineParser(config, 'prog.py', fullpath=False) + parser.parse_args(['int', '--int', '3']) + assert config.value.dict() == output def test_readme_int_verbosity(): @@ -138,6 +297,28 @@ def test_readme_int_verbosity(): assert config.value.dict() == output +def test_readme_int_verbosity_tree(): + output = {'root.cmd': 'int', + 'root.int': 3, + 'root.verbosity': True, + 'root.v': True} + config = get_config(True) + parser = TiramisuCmdlineParser(config, 'prog.py') + parser.parse_args(['int', '--root.int', '3', '--root.verbosity']) + assert config.value.dict() == output + + +def test_readme_int_verbosity_tree_flatten(): + output = {'root.cmd': 'int', + 'root.int': 3, + 'root.verbosity': True, + 'root.v': True} + config = get_config(True) + parser = TiramisuCmdlineParser(config, 'prog.py', fullpath=False) + parser.parse_args(['int', '--int', '3', '--verbosity']) + assert config.value.dict() == output + + def test_readme_int_verbosity_short(): output = {'cmd': 'int', 'int': 3, @@ -149,6 +330,28 @@ def test_readme_int_verbosity_short(): assert config.value.dict() == output +def test_readme_int_verbosity_short_tree(): + output = {'root.cmd': 'int', + 'root.int': 3, + 'root.verbosity': True, + 'root.v': True} + config = get_config(True) + parser = TiramisuCmdlineParser(config, 'prog.py') + parser.parse_args(['int', '--root.int', '3', '-v']) + assert config.value.dict() == output + + +def test_readme_int_verbosity_short_tree_flatten(): + output = {'root.cmd': 'int', + 'root.int': 3, + 'root.verbosity': True, + 'root.v': True} + config = get_config(True) + parser = TiramisuCmdlineParser(config, 'prog.py', fullpath=False) + parser.parse_args(['int', '--int', '3', '-v']) + assert config.value.dict() == output + + def test_readme_str(): output = {'cmd': 'str', 'str': 'value', @@ -160,6 +363,28 @@ def test_readme_str(): assert config.value.dict() == output +def test_readme_str_tree(): + output = {'root.cmd': 'str', + 'root.str': 'value', + 'root.verbosity': False, + 'root.v': False} + config = get_config(True) + parser = TiramisuCmdlineParser(config, 'prog.py') + parser.parse_args(['str', '--root.str', 'value']) + assert config.value.dict() == output + + +def test_readme_str_tree_flatten(): + output = {'root.cmd': 'str', + 'root.str': 'value', + 'root.verbosity': False, + 'root.v': False} + config = get_config(True) + parser = TiramisuCmdlineParser(config, 'prog.py', fullpath=False) + parser.parse_args(['str', '--str', 'value']) + assert config.value.dict() == output + + def test_readme_str_int(): output = {'cmd': 'str', 'str': '3', @@ -171,6 +396,28 @@ def test_readme_str_int(): assert config.value.dict() == output +def test_readme_str_int_tree(): + output = {'root.cmd': 'str', + 'root.str': '3', + 'root.verbosity': False, + 'root.v': False} + config = get_config(True) + parser = TiramisuCmdlineParser(config, 'prog.py') + parser.parse_args(['str', '--root.str', '3']) + assert config.value.dict() == output + + +def test_readme_str_int_tree_flatten(): + output = {'root.cmd': 'str', + 'root.str': '3', + 'root.verbosity': False, + 'root.v': False} + config = get_config(True) + parser = TiramisuCmdlineParser(config, 'prog.py', fullpath=False) + parser.parse_args(['str', '--str', '3']) + assert config.value.dict() == output + + def test_readme_list(): output = {'cmd': 'list', 'list': ['a', 'b', 'c'], @@ -182,6 +429,28 @@ def test_readme_list(): assert config.value.dict() == output +def test_readme_list_tree(): + output = {'root.cmd': 'list', + 'root.list': ['a', 'b', 'c'], + 'root.verbosity': False, + 'root.v': False} + config = get_config(True) + parser = TiramisuCmdlineParser(config, 'prog.py') + parser.parse_args(['list', '--root.list', 'a', 'b', 'c']) + assert config.value.dict() == output + + +def test_readme_list_tree_flatten(): + output = {'root.cmd': 'list', + 'root.list': ['a', 'b', 'c'], + 'root.verbosity': False, + 'root.v': False} + config = get_config(True) + parser = TiramisuCmdlineParser(config, 'prog.py', fullpath=False) + parser.parse_args(['list', '--list', 'a', 'b', 'c']) + assert config.value.dict() == output + + def test_readme_list_uniq(): output = {'cmd': 'list', 'list': ['a'], @@ -191,3 +460,25 @@ def test_readme_list_uniq(): parser = TiramisuCmdlineParser(config, 'prog.py') parser.parse_args(['list', '--list', 'a']) assert config.value.dict() == output + + +def test_readme_list_uniq_tree(): + output = {'root.cmd': 'list', + 'root.list': ['a'], + 'root.verbosity': False, + 'root.v': False} + config = get_config(True) + parser = TiramisuCmdlineParser(config, 'prog.py') + parser.parse_args(['list', '--root.list', 'a']) + assert config.value.dict() == output + + +def test_readme_list_uniq_tree_flatten(): + output = {'root.cmd': 'list', + 'root.list': ['a'], + 'root.verbosity': False, + 'root.v': False} + config = get_config(True) + parser = TiramisuCmdlineParser(config, 'prog.py', fullpath=False) + parser.parse_args(['list', '--list', 'a']) + assert config.value.dict() == output diff --git a/tiramisu_cmdline_parser/tiramisu_cmdline_parser.py b/tiramisu_cmdline_parser/tiramisu_cmdline_parser.py index b79e7c4..16bbe2d 100644 --- a/tiramisu_cmdline_parser/tiramisu_cmdline_parser.py +++ b/tiramisu_cmdline_parser/tiramisu_cmdline_parser.py @@ -15,7 +15,7 @@ from typing import Union, List, Optional -from argparse import ArgumentParser, Namespace, SUPPRESS, _HelpAction +from argparse import ArgumentParser, Namespace, SUPPRESS, _HelpAction, HelpFormatter try: from tiramisu import Config from tiramisu.error import PropertiesOptionError @@ -61,6 +61,15 @@ class TiramisuNamespace(Namespace): return super().__getattribute__(key) +class TiramisuHelpFormatter(HelpFormatter): + def _get_default_metavar_for_optional(self, + action): + ret = super()._get_default_metavar_for_optional(action) + if '.' in ret: + ret = ret.split('.', 1)[1] + return ret + + class _TiramisuHelpAction(_HelpAction): needs = False def __call__(self, *args, **kwargs): @@ -79,6 +88,7 @@ class TiramisuCmdlineParser(ArgumentParser): **kwargs): self.fullpath = fullpath self.config = config + kwargs['formatter_class'] = TiramisuHelpFormatter super().__init__(*args, **kwargs) self.register('action', 'help', _TiramisuHelpAction) self._config_to_argparser(_forhelp, @@ -170,7 +180,7 @@ class TiramisuCmdlineParser(ArgumentParser): properties = obj.property.get() kwargs = {'help': option.doc().replace('%', '%%')} if option.issymlinkoption(): - actions[option.name(follow_symlink=True)][0].insert(0, self._gen_argument(name, properties)) + actions[option.name(follow_symlink=True)][0].insert(0, self._gen_argument(option.name(), properties)) continue if 'positional' in properties: if not 'mandatory' in properties: @@ -224,7 +234,6 @@ class TiramisuCmdlineParser(ArgumentParser): for args, kwargs in actions.values(): group.add_argument(*args, **kwargs) - def parse_args(self, *args, valid_mandatory=True, @@ -236,7 +245,7 @@ class TiramisuCmdlineParser(ArgumentParser): except PropertiesOptionError as err: name = err._option_bag.option.impl_getname() properties = self.config.option(name).property.get() - if 'positional' not in properties: + if self.fullpath and 'positional' not in properties: if len(name) == 1 and 'longargument' not in properties: name = self.prefix_chars + name else: @@ -256,6 +265,8 @@ class TiramisuCmdlineParser(ArgumentParser): args = self._gen_argument(name, self.config.option(key).property.get()) else: args = key + if not self.fullpath and '.' in args: + args = args.rsplit('.', 1)[1] self.error('the following arguments are required: {}'.format(args)) return namespaces