From 91a00df9056a5f4462fe9ab88ff74d7d69c5ffaa Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Fri, 9 Aug 2024 11:18:16 +0200 Subject: [PATCH] add add_extra_options option --- tests/test_default.py | 107 +++++++++++++++++++++++++++++++++ tiramisu_cmdline_parser/api.py | 54 +++++++++++------ 2 files changed, 144 insertions(+), 17 deletions(-) create mode 100644 tests/test_default.py diff --git a/tests/test_default.py b/tests/test_default.py new file mode 100644 index 0000000..399ec31 --- /dev/null +++ b/tests/test_default.py @@ -0,0 +1,107 @@ +from io import StringIO +from contextlib import redirect_stdout, redirect_stderr +import pytest + + +from tiramisu_cmdline_parser import TiramisuCmdlineParser +from tiramisu import IntOption, StrOption, BoolOption, ChoiceOption, \ + OptionDescription, Config +try: + from tiramisu_api import Config as JsonConfig + #params = ['tiramisu', 'tiramisu-json'] + params = ['tiramisu'] +except: + params = ['tiramisu'] +from .utils import TestHelpFormatter, to_dict + + +def get_config(json, has_tree=False, default_verbosity=False, add_long=False, add_store_false=False): + choiceoption = ChoiceOption('cmd', + 'choice the sub argument', + ('str', 'list', 'int', 'none'), + properties=('mandatory',)) + booloption = BoolOption('verbosity', + 'increase output verbosity', + default=default_verbosity, + ) + str_ = StrOption('str', + 'string option', + default='default' + ) + list_ = StrOption('list', + 'list string option', + multi=True, + default=['default'], + ) + int_ = IntOption('int', + 'int option', + default=10, + ) + + root = OptionDescription('root', + 'root', + [choiceoption, + booloption, + str_, + list_, + int_ + ]) + if has_tree: + root = OptionDescription('root', + 'root', + [root]) + config = Config(root) + config.property.read_write() + if add_store_false: + config.option('verbosity').property.add('storefalse') + if add_long: + config.option('verbosity').property.add('longargument') + if json == 'tiramisu': + return config + jconfig = JsonConfig(config.option.dict()) + return jconfig + + +@pytest.fixture(params=params) +def json(request): + return request.param + + +def test_readme_help(json): + output = """usage: prog.py [-h] --cmd {str,list,int,none} [--verbosity] [--no-verbosity] [--str [STR]] [--list [LIST ...]] [--int [INT]] + +options: + -h, --help show this help message and exit + --cmd {str,list,int,none} + choice the sub argument + --verbosity increase output verbosity (default: False) + --no-verbosity + --str [STR] string option (default: default) + --list [LIST ...] list string option (default: default) + --int [INT] int option (default: 10) +""" + parser = TiramisuCmdlineParser(get_config(json), 'prog.py', formatter_class=TestHelpFormatter) + f = StringIO() + with redirect_stdout(f): + parser.print_help() + assert f.getvalue() == output + + +def test_readme_help2(json): + output = """usage: prog.py [-h] --cmd {str,list,int,none} [--verbosity] [--no-verbosity] [--str [STR]] [--list [LIST ...]] [--int [INT]] + +options: + -h, --help show this help message and exit + --cmd {str,list,int,none} + choice the sub argument + --verbosity increase output verbosity (default: True) + --no-verbosity + --str [STR] string option (default: default) + --list [LIST ...] list string option (default: default) + --int [INT] int option (default: 10) +""" + parser = TiramisuCmdlineParser(get_config(json, default_verbosity=True), 'prog.py', formatter_class=TestHelpFormatter) + f = StringIO() + with redirect_stdout(f): + parser.print_help() + assert f.getvalue() == output diff --git a/tiramisu_cmdline_parser/api.py b/tiramisu_cmdline_parser/api.py index 87e051a..e62ccc9 100644 --- a/tiramisu_cmdline_parser/api.py +++ b/tiramisu_cmdline_parser/api.py @@ -195,6 +195,7 @@ class _BuildKwargs: properties: List[str], force_no: bool, force_del: bool, + add_extra_options: bool, display_modified_value: bool, not_display: bool) -> None: self.kwargs = {} @@ -202,10 +203,13 @@ class _BuildKwargs: self.properties = properties self.force_no = force_no self.force_del = force_del - if (not self.force_no or (not_display and not display_modified_value)) and not self.force_del: - description = option.description() - if not description: - description = description.replace('%', '%%') + if ((not self.force_no or not add_extra_options) or (not_display and not display_modified_value)) and not self.force_del: + if self.force_no: + description = option.information.get('negative_description', None) + else: + description = None + if description is None: + description = option.description() self.kwargs['help'] = description if 'positional' not in self.properties: is_short_name = self.cmdlineparser._is_short_name(name, 'longargument' in self.properties) @@ -437,9 +441,13 @@ class TiramisuCmdlineParser(ArgumentParser): yield obj, None, False yield obj, None, True else: - if obj.type() == 'boolean' and obj.value.default() is True: - raise ValueError(_(f'the boolean "{obj.path()}" cannot have a default value to True with option add_extra_options')) - yield obj, None, None + if not obj.issymlinkoption() and obj.type() == 'boolean' and obj.value.get() is True: + negative_description = obj.information.get('negative_description', None) + if _forhelp and not negative_description: + raise ValueError(_(f'the boolean "{obj.path()}" cannot have a default value to "True" with option add_extra_options if there is no negative_description')) + yield obj, True, None + else: + yield obj, None, None if obj is not None and not obj.isoptiondescription() and obj.isleader(): # no follower found, search if there is a symlink for sobj in config.list(uncalculated=True): @@ -474,23 +482,28 @@ class TiramisuCmdlineParser(ArgumentParser): continue if force_del: value = None +# elif force_no: +# value = not option.value.get() elif option.isleader(): value = option.value.get() leadership_len = len(value) elif option.isfollower(): - value = [] - try: - for index in range(leadership_len): - value.append(self.config.option(option.path(), index).value.get()) - except: - value = None + if _forhelp: + value = option.value.defaultmulti() + else: + value = [] + try: + for index in range(leadership_len): + value.append(self.config.option(option.path(), index).value.get()) + except: + value = None else: value = option.value.get() if self.fullpath and prefix: name = prefix + '.' + name properties = option.property.get() not_display = not option.isfollower() and not option.owner.isdefault() and value is not None - kwargs = _BuildKwargs(name, option, self, properties, force_no, force_del, self.display_modified_value, not_display) + kwargs = _BuildKwargs(name, option, self, properties, force_no, force_del, self.add_extra_options, self.display_modified_value, not_display) if _forhelp and not_display and ((value is not False and not force_no) or (value is False and force_no)): options_is_not_default[option.name()] = {'properties': properties, 'type': option.type(), @@ -499,21 +512,28 @@ class TiramisuCmdlineParser(ArgumentParser): } if not self.display_modified_value: continue + if force_no: + default = False + else: + default = option.value.default() + if isinstance(default, list): + str_default_value = ','.join([str(v) for v in default]) + else: + str_default_value = default 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'] = option.value.default() + kwargs['default'] = str_default_value else: kwargs['default'] = value kwargs['nargs'] = '?' else: if _forhelp and not option.isleader(): - default = option.value.default() if default not in [None, []]: - kwargs['default'] = default + kwargs['default'] = str_default_value else: kwargs['default'] = SUPPRESS else: