diff --git a/test/test_leadership.py b/test/test_leadership.py index 3c76c56..25d535d 100644 --- a/test/test_leadership.py +++ b/test/test_leadership.py @@ -290,17 +290,18 @@ def test_leadership_modif_mandatory(json): 'leader.follower_choice': [None], 'leader.follower_integer': [None], 'leader.follower_submulti': [['255.255.255.128']]} - output2 = """usage: prog.py --leader.leader ['192.168.1.1'] [-h] - [--leader.pop-leader INDEX] - [--leader.follower INDEX [FOLLOWER]] - --leader.follower_submulti - INDEX [FOLLOWER_SUBMULTI ...] - [--leader.follower_integer INDEX [FOLLOWER_INTEGER]] - [--leader.follower_boolean INDEX] - [--leader.no-follower_boolean INDEX] - [--leader.follower_choice INDEX [{opt1,opt2}]] - --leader.follower_mandatory - INDEX FOLLOWER_MANDATORY + output2 = """usage: prog.py --leader.leader "192.168.1.1" [-h] + [--leader.leader [LEADER [LEADER ...]]] + [--leader.pop-leader INDEX] + [--leader.follower INDEX [FOLLOWER]] + --leader.follower_submulti INDEX + [FOLLOWER_SUBMULTI ...] + [--leader.follower_integer INDEX [FOLLOWER_INTEGER]] + [--leader.follower_boolean INDEX] + [--leader.no-follower_boolean INDEX] + [--leader.follower_choice INDEX [{opt1,opt2}]] + --leader.follower_mandatory INDEX + FOLLOWER_MANDATORY prog.py: error: the following arguments are required: --leader.follower_submulti""" config = get_config(json, with_mandatory=True) diff --git a/test/test_readme.py b/test/test_readme.py index 74ee790..e3bb1e4 100644 --- a/test/test_readme.py +++ b/test/test_readme.py @@ -131,7 +131,10 @@ root: def test_readme_help_modif_positional(json): - output = """usage: prog.py str [-h] [-v] [-nv] --str STR + output = """usage: prog.py "str" [-h] [-v] [-nv] --str STR {str,list,int,none} + +positional arguments: + {str,list,int,none} choice the sub argument optional arguments: -h, --help show this help message and exit @@ -152,12 +155,17 @@ optional arguments: def test_readme_help_modif(json): - output = """usage: prog.py str --str toto [-h] [-v] [-nv] + output = """usage: prog.py "str" --str "toto" [-h] [-v] [-nv] --str STR + {str,list,int,none} + +positional arguments: + {str,list,int,none} choice the sub argument optional arguments: -h, --help show this help message and exit -v, --verbosity increase output verbosity -nv, --no-verbosity + --str STR string option """ parser = TiramisuCmdlineParser(get_config(json), 'prog.py') f = StringIO() @@ -172,11 +180,16 @@ optional arguments: def test_readme_help_modif_short1(json): - output = """usage: prog.py str --verbosity [-h] --str STR + output = """usage: prog.py "str" -v [-h] [-v] [-nv] --str STR {str,list,int,none} + +positional arguments: + {str,list,int,none} choice the sub argument optional arguments: - -h, --help show this help message and exit - --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(json), 'prog.py') f = StringIO() @@ -191,11 +204,16 @@ optional arguments: def test_readme_help_modif_short_no(json): - output = """usage: prog.py str --verbosity [-h] --str STR + output = """usage: prog.py "str" -v [-h] [-v] [-nv] --str STR {str,list,int,none} + +positional arguments: + {str,list,int,none} choice the sub argument optional arguments: - -h, --help show this help message and exit - --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(json), 'prog.py') f = StringIO() @@ -258,7 +276,7 @@ prog.py: error: the following arguments are required: cmd def test_readme_mandatory(json): - output = """usage: prog.py str [-h] [-v] [-nv] --str STR + output = """usage: prog.py "str" [-h] [-v] [-nv] --str STR {str,list,int,none} prog.py: error: the following arguments are required: --str """ parser = TiramisuCmdlineParser(get_config(json), 'prog.py') @@ -274,7 +292,7 @@ prog.py: error: the following arguments are required: --str def test_readme_mandatory_tree(json): - output = """usage: prog.py str [-h] [-v] [-nv] --root.str STR + output = """usage: prog.py "str" [-h] [-v] [-nv] --root.str STR {str,list,int,none} prog.py: error: the following arguments are required: --root.str """ parser = TiramisuCmdlineParser(get_config(json, True), 'prog.py') @@ -290,7 +308,7 @@ prog.py: error: the following arguments are required: --root.str def test_readme_mandatory_tree_flatten(json): - output = """usage: prog.py str [-h] [-v] [-nv] --str STR + output = """usage: prog.py "str" [-h] [-v] [-nv] --str STR {str,list,int,none} prog.py: error: the following arguments are required: --str """ parser = TiramisuCmdlineParser(get_config(json, True), 'prog.py', fullpath=False) @@ -306,7 +324,7 @@ prog.py: error: the following arguments are required: --str def test_readme_cross(json): - output = """usage: prog.py none [-h] [-v] [-nv] + output = """usage: prog.py "none" [-h] [-v] [-nv] {str,list,int,none} prog.py: error: unrecognized arguments: --int """ parser = TiramisuCmdlineParser(get_config(json), 'prog.py') @@ -322,7 +340,7 @@ prog.py: error: unrecognized arguments: --int def test_readme_cross_tree(json): - output = """usage: prog.py none [-h] [-v] [-nv] + output = """usage: prog.py "none" [-h] [-v] [-nv] {str,list,int,none} prog.py: error: unrecognized arguments: --int """ parser = TiramisuCmdlineParser(get_config(json, True), 'prog.py') @@ -338,7 +356,7 @@ prog.py: error: unrecognized arguments: --int def test_readme_cross_tree_flatten(json): - output = """usage: prog.py none [-h] [-v] [-nv] + output = """usage: prog.py "none" [-h] [-v] [-nv] {str,list,int,none} prog.py: error: unrecognized arguments: --int """ parser = TiramisuCmdlineParser(get_config(json, True), 'prog.py', fullpath=False) diff --git a/tiramisu_cmdline_parser/api.py b/tiramisu_cmdline_parser/api.py index 2832986..e292a8b 100644 --- a/tiramisu_cmdline_parser/api.py +++ b/tiramisu_cmdline_parser/api.py @@ -19,13 +19,14 @@ from gettext import gettext as _ try: from tiramisu import Config - from tiramisu.error import PropertiesOptionError, RequirementError + from tiramisu.error import PropertiesOptionError, RequirementError, LeadershipError except ModuleNotFoundError: Config = None - from tiramisu_json_api.error import PropertiesOptionError + from tiramisu_api.error import PropertiesOptionError RequirementError = PropertiesOptionError + LeadershipError = ValueError try: - from tiramisu_json_api import Config as ConfigJson + from tiramisu__api import Config as ConfigJson if Config is None: Config = ConfigJson except ModuleNotFoundError: @@ -284,7 +285,7 @@ class TiramisuCmdlineParser(ArgumentParser): def _parse_known_args(self, args=None, namespace=None): try: namespace_, args_ = super()._parse_known_args(args, namespace) - except ValueError as err: + except (ValueError, LeadershipError) as err: self.error(err) if args != args_ and args_ and args_[0].startswith(self.prefix_chars): # option that was disabled are no more disable @@ -297,7 +298,7 @@ class TiramisuCmdlineParser(ArgumentParser): formatter_class=self.formatter_class, epilog=self.epilog, fullpath=self.fullpath) - namespace_, args_ = new_parser._parse_known_args(args_, namespace) + namespace_, args_ = new_parser._parse_known_args(args_, new_parser.namespace) else: if self._registries['action']['help'].needs: # display help only when all variables assignemnt are done @@ -327,7 +328,11 @@ class TiramisuCmdlineParser(ArgumentParser): is_short_name = self._is_short_name(name, 'longargument' in properties) self.prog += ' {}'.format(self._gen_argument(name, is_short_name)) if type != 'boolean': - self.prog += f' {value}' + if isinstance(value, list): + for val in value: + self.prog += f' "{val}"' + else: + self.prog += f' "{value}"' def _config_list(self, config: Config, @@ -372,6 +377,7 @@ class TiramisuCmdlineParser(ArgumentParser): group = super() actions = {} leadership_len = None + options_is_not_default = {} for obj, force_no, force_del in self._config_list(config, prefix, _forhelp, group, level): option = obj.option name = option.name() @@ -379,6 +385,8 @@ class TiramisuCmdlineParser(ArgumentParser): raise ValueError(_('name cannot startswith "{}"').format(self.prefix_chars)) if option.issymlinkoption(): symlink_name = option.name(follow_symlink=True) + if symlink_name in options_is_not_default: + options_is_not_default[symlink_name]['name'] = name if symlink_name in actions: for action in actions[symlink_name]: action.add_argument(option) @@ -401,90 +409,90 @@ class TiramisuCmdlineParser(ArgumentParser): else: properties = obj.property.get() kwargs = _BuildKwargs(name, option, self, properties, force_no, force_del) - if not option.isfollower() and _forhelp and not obj.owner.isdefault() and value is not None: - if not force_no: - self._option_is_not_default(properties, - option.type(), - name, - value) - 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') - if _forhelp: - kwargs['default'] = obj.value.default() - else: - kwargs['default'] = value - kwargs['nargs'] = '?' + if not option.isfollower() and _forhelp and not obj.owner.isdefault() and value is not None and not force_no: + options_is_not_default[option.name()] = {'properties': properties, + 'type': option.type(), + 'name': name, + 'value': value} + 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') + if _forhelp: + kwargs['default'] = obj.value.default() else: - kwargs['default'] = SUPPRESS - if _forhelp and 'mandatory' in properties: - kwargs['required'] = True - if not force_del and option.type() == 'boolean': - if not option.isfollower(): - if 'storefalse' in properties: - if force_no: - action = 'store_true' - else: - action = 'store_false' - elif force_no: - action = 'store_false' - else: + kwargs['default'] = value + kwargs['nargs'] = '?' + else: + kwargs['default'] = SUPPRESS + if _forhelp and 'mandatory' in properties: + kwargs['required'] = True + if not force_del and option.type() == 'boolean': + if not option.isfollower(): + if 'storefalse' in properties: + if force_no: action = 'store_true' - kwargs['action'] = action - else: - kwargs['metavar'] = 'INDEX' - if option.type() != 'boolean' or force_del: - if not force_del: - if _forhelp: - value = obj.value.default() - if value not in [None, []]: - #kwargs['default'] = kwargs['const'] = option.default() - #kwargs['action'] = 'store_const' - kwargs['nargs'] = '?' - - if not option.isfollower() and option.ismulti(): - if _forhelp and 'mandatory' in properties: - kwargs['nargs'] = '+' else: - kwargs['nargs'] = '*' - if option.isfollower() and not option.type() == 'boolean': - metavar = option.name().upper() - if option.issubmulti(): + action = 'store_false' + elif force_no: + action = 'store_false' + else: + action = 'store_true' + kwargs['action'] = action + else: + kwargs['metavar'] = 'INDEX' + if option.type() != 'boolean' or force_del: + if not force_del: + if _forhelp: + value = obj.value.default() + if value not in [None, []]: + #kwargs['default'] = kwargs['const'] = option.default() + #kwargs['action'] = 'store_const' + kwargs['nargs'] = '?' + + if not option.isfollower() and option.ismulti(): + if _forhelp and 'mandatory' in properties: kwargs['nargs'] = '+' else: - kwargs['nargs'] = 2 - if _forhelp and 'mandatory' not in properties: - metavar = f'[{metavar}]' - if option.type() == 'choice': - choice_list = obj.value.list() - if choice_list[0] == '': - del choice_list[0] - choices = '{{{}}}'.format(','.join(choice_list)) - if 'mandatory' not in properties: - choices = f'[{choices}]' - kwargs['metavar'] = ('INDEX', choices) - else: - kwargs['metavar'] = ('INDEX', metavar) - if force_del: - kwargs['metavar'] = 'INDEX' - kwargs['type'] = int - elif option.type() == 'string': - pass - elif option.type() == 'integer' or option.type() == 'boolean': - # when boolean we are here only if follower - kwargs['type'] = int - if _forhelp and option.type() == 'boolean': - kwargs['metavar'] = 'INDEX' - kwargs['nargs'] = 1 - elif option.type() == 'choice' and not option.isfollower(): - kwargs['choices'] = obj.value.list() + kwargs['nargs'] = '*' + if option.isfollower() and not option.type() == 'boolean': + metavar = option.name().upper() + if option.issubmulti(): + kwargs['nargs'] = '+' else: - pass - #raise NotImplementedError('not supported yet') - actions.setdefault(option.name(), []).append(kwargs) + kwargs['nargs'] = 2 + if _forhelp and 'mandatory' not in properties: + metavar = f'[{metavar}]' + if option.type() == 'choice': + choice_list = obj.value.list() + if choice_list[0] == '': + del choice_list[0] + choices = '{{{}}}'.format(','.join(choice_list)) + if 'mandatory' not in properties: + choices = f'[{choices}]' + kwargs['metavar'] = ('INDEX', choices) + else: + kwargs['metavar'] = ('INDEX', metavar) + if force_del: + kwargs['metavar'] = 'INDEX' + kwargs['type'] = int + elif option.type() == 'string': + pass + elif option.type() == 'integer' or option.type() == 'boolean': + # when boolean we are here only if follower + kwargs['type'] = int + if _forhelp and option.type() == 'boolean': + kwargs['metavar'] = 'INDEX' + kwargs['nargs'] = 1 + elif option.type() == 'choice' and not option.isfollower(): + kwargs['choices'] = obj.value.list() + else: + pass + #raise NotImplementedError('not supported yet') + actions.setdefault(option.name(), []).append(kwargs) + for option_is_not_default in options_is_not_default.values(): + self._option_is_not_default(**option_is_not_default) for values in actions.values(): for value in values: args, kwargs = value.get()