Compare commits

...

37 commits

Author SHA1 Message Date
7090b6bcbc feat: portage to tiramisu 5.0 2024-07-30 11:41:47 +02:00
Emmanuel Garette
86fc311277 if no value set for a choose, display no value 2024-07-30 11:41:47 +02:00
Emmanuel Garette
79355f92a7 new tiramisu_cmdline_parser version 2024-07-30 11:41:47 +02:00
Emmanuel Garette
44997328ef new tiramisu_cmdline_parser version 2024-07-30 11:41:47 +02:00
Emmanuel Garette
b7bb7ebe6f add float support 2024-07-30 11:41:47 +02:00
Emmanuel Garette
c09b3c0844 support empty choice 2024-07-30 11:41:47 +02:00
Emmanuel Garette
06027aad73 unrestraint 2024-07-30 11:41:47 +02:00
Emmanuel Garette
f1db5c87f6 python 3.5 2024-07-30 11:41:47 +02:00
Emmanuel Garette
2184332e62 python 3.5 support 2024-07-30 11:41:47 +02:00
Emmanuel Garette
11b2bcc591 choice already manage by argparse 2024-07-30 11:41:47 +02:00
Emmanuel Garette
dc94b37896 support integer choiceoption 2024-07-30 11:41:47 +02:00
Emmanuel Garette
0583c19f75 no- + fullname == false 2024-07-30 11:41:47 +02:00
Emmanuel Garette
d161b4ced7 0.2 2024-07-30 11:41:47 +02:00
Emmanuel Garette
eab6059b09 problem with boolean option 2024-07-30 11:41:47 +02:00
Emmanuel Garette
6f47a069c5 add display_modified_value parameter 2024-07-30 11:41:47 +02:00
Emmanuel Garette
3d1a558389 setup.py 2024-07-30 11:41:47 +02:00
Emmanuel Garette
88949d8682 remove OD if name == description is now managed with internal argparser mechanism 2024-07-30 11:41:47 +02:00
Emmanuel Garette
528d16ec88 add all options even if already set by user 2024-07-30 11:41:47 +02:00
Emmanuel Garette
dcc7510676 add --pop-leader to remove a value in a leader option 2024-07-30 11:41:47 +02:00
Emmanuel Garette
452e1fb0e0 do not use RawDescriptionHelpFormatter by default 2024-07-30 11:41:47 +02:00
Emmanuel Garette
549151b3b8 path => compare 2024-07-30 11:41:47 +02:00
Emmanuel Garette
d8ee3486e9 use rawformatter and add epilog 2024-07-30 11:41:47 +02:00
Emmanuel Garette
b0849ab260 remove_empty_description_od with path 2024-07-30 11:41:47 +02:00
Emmanuel Garette
1ade6c883f remove OD with no description in help if option remove_empty_description_od set to True 2024-07-30 11:41:46 +02:00
Emmanuel Garette
067831bddd remove empty OD in help if option remove_empty_od set to True 2024-07-30 11:41:46 +02:00
Emmanuel Garette
5f97544463 support suboptiondescription in TiramisuCmdLineParser 2024-07-30 11:41:46 +02:00
Emmanuel Garette
490879a2a9 add tests 2024-07-30 11:41:46 +02:00
Emmanuel Garette
1cb8a2cc7b leader/follower support 2024-07-30 11:41:46 +02:00
Emmanuel Garette
dd14b0f752 use gettext 2024-07-30 11:41:46 +02:00
Emmanuel Garette
5725bdf3b7 hangman now works 2024-07-30 11:41:46 +02:00
gwen
d9380eb2cf ImportError is deprecated -> ModuleNotFoundError 2024-07-30 11:41:46 +02:00
Emmanuel Garette
cb41e731ea add test for longargument property 2024-07-30 11:41:46 +02:00
Emmanuel Garette
132d57a4c6 add storefalse properties for boolean option 2024-07-30 11:41:46 +02:00
Emmanuel Garette
f25df1f1f4 for bool, generate --no-xxxx option 2024-07-30 11:41:46 +02:00
Emmanuel Garette
b5c50071b5 help with modified argument 2024-07-30 11:41:46 +02:00
Emmanuel Garette
89ec6309b1 help with modified positional argument 2024-07-30 11:41:46 +02:00
Emmanuel Garette
138c12616f --root.v => -v, --int ROOT.INT => --int INT 2024-07-30 11:41:46 +02:00
49 changed files with 15197 additions and 504 deletions

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,303 @@
import re, random, ipaddress
def get_version(*args, **kwargs):
return u'2.6.2'
def is_instanciate(*args, **kwargs):
return 'oui'
def calc_val(valeur):
return valeur
def concat(*args, **kwargs):
""" concatène deux valeurs """
sortedkeys = list(kwargs.keys())
sortedkeys.sort()
sortedkwvalues = []
for key in sortedkeys:
if kwargs[key] == '' or kwargs[key] is None:
return None
sortedkwvalues.append(kwargs[key])
if None in args:
return None
return "".join(args)+''.join(sortedkwvalues)
def auto_dns(*args, **kwargs):
return []
def calc_multi_condition(param, match=None, mismatch=None, operator='AND',
default_match=None, default_mismatch=None,
eval_match='False', eval_mismatch='False',
**conditions):
if operator not in ('AND', 'OR'):
raise ValueError(_(u'Operator must be either "AND" or "OR"'))
if eval_match not in ('True', 'False'):
raise ValueError(_(u'eval_match must be either "True" or "False"'))
if eval_mismatch not in ('True', 'False'):
raise ValueError(_(u'eval_mismatch must be either "True" or "False"'))
if match is None:
if default_match is None:
match = u'oui'
else:
if default_match == 'None':
match = None
else:
match = default_match
if mismatch is None:
if default_mismatch is None:
mismatch = u'non'
else:
if default_mismatch == 'None':
mismatch = None
else:
mismatch = default_mismatch
conditions_keys = list(conditions.keys())
conditions_keys.sort()
for condition in conditions_keys:
if not condition.startswith('condition_'):
raise ValueError(_(u'Condition keys must start with "condition".'))
# si le paramètre est une liste...
if param.startswith('['):
param = eval(param)
# si il faut évaluer le résultat à retourner...
if eval_match == 'True':
match = eval(match)
if eval_mismatch == 'True':
mismatch = eval(mismatch)
if isinstance(param, list) and len(param) != len(conditions):
raise ValueError(_(u'Conditions and parameters counts do not match in calc_multi_condition.'))
for num in range(0, len(conditions)):
key = conditions_keys[num]
value = conditions[key]
if not isinstance(param, list):
if operator == 'AND' and value != param:
return mismatch
if operator == 'OR' and value == param:
return match
else:
if operator == 'AND' and value != param[num]:
return mismatch
if operator == 'OR' and value == param[num]:
return match
if operator == 'AND':
return match
else:
return mismatch
auto_copy_val = calc_val
def activate_master_only_web_app(*args, **kwargs):
return 'oui'
def calc_val_first_value(*args, **kwargs):
if args == tuple():
return None
return args[0]
def valid_entier(*args, **kwargs):
return True
def list_cdrom_devices(*args, **kwargs):
return 'cdrom'
def cdrom_minormajor(*args, **kwargs):
return 0
def calc_free_PE(*args, **kwargs):
return 0
def enable_lv_creation(*args, **kwargs):
return "oui"
def get_lv_names(*args, **kwargs):
return "lvm"
def calc_multi_val(*args, **kwargs):
res = []
for arg in args:
if arg is None:
if kwargs.get('allow_none', u'False') == u'True':
continue
else:
return []
# gestion des valeurs multi
if isinstance(arg, list):
for ev_arg in arg:
if ev_arg is not None and ev_arg not in res:
res.append(ev_arg)
else:
res.append(arg)
return res
def check_partitioning_auto_extend(*args, **kwargs):
return 'non'
def check_free_space(*args, **kwargs):
return 0
def is_fs_type(*args, **kwargs):
return "ext4"
def is_lv_name(*args, **kwargs):
return "/etc"
def get_net_device(*args, **kwargs):
return 'eth' + str(args[0])
def list_len_gt(param, max_len=None, match=None, mismatch=None,
default_match=None, default_mismatch=None,
eval_match='False', eval_mismatch='False',):
if match is None:
if default_match is None:
match = u'oui'
else:
if default_match == 'None':
match = None
else:
match = default_match
if mismatch is None:
if default_mismatch is None:
mismatch = u'non'
else:
if default_mismatch == 'None':
mismatch = None
else:
mismatch = default_mismatch
# si il faut évaluer le résultat à retourner...
if eval_match == 'True':
match = eval(match)
if eval_mismatch == 'True':
mismatch = eval(mismatch)
if isinstance(param, list):
if len(param) > int(max_len):
return match
else:
return mismatch
else:
return mismatch
def get_zone_name_bridge(*args, **kwargs):
return "non"
def get_zone_name(*args, **kwargs):
return 'eth0'
def auto_eth(*args, **kwargs):
if kwargs['parametre'] == kwargs['condition']:
return "192.168.1.1"
def auto_netmask(*args, **kwargs):
if kwargs['parametre'] == kwargs['condition']:
return "255.255.255.0"
def calc_or_auto_network(*args, **kwargs):
if kwargs['parametre'] == kwargs['condition']:
return "192.168.1.0"
return calc_network(kwargs['ip'], kwargs['netmask'])
def calc_or_auto_broadcast(*args, **kwargs):
if kwargs['parametre'] == kwargs['condition']:
return "192.168.1.255"
def auto_defaultgw_ip(*args, **kwargs):
if args[0] != 'statique':
return "192.168.1.254"
def calc_network(ip, netmask):
if None not in (ip, netmask):
try:
return str(ipaddress.ip_interface('{}/{}'.format(ip, netmask)).network.network_address)
except:
return None
def calc_broadcast(ip, netmask):
if None not in (ip, netmask):
return str(ipaddress.ip_interface('{}/{}'.format(ip, netmask)).network.broadcast_address)
def calc_ssl_country_name(*args, **kwargs):
return 'FR'
def valid_country(*args, **kwargs):
return
def valid_regexp(data, exp_reg, err_msg=u"Invalid syntax"):
if data == "":
return True
match = re.match(exp_reg, data)
if match is None:
raise ValueError(err_msg)
else:
return True
def gen_random(*args, **kwargs):
return '4444444444444444444'
def check_name_uniq(value, values, index):
values.pop(index)
if value in values:
raise ValueError('le nom {} est déjà attribué à une autre plage'.format(value))
def calc_classe(*args, **kwargs):
return "24"
def calc_webredirection(*args, **kwargs):
return "/toto"
def calc_container(mode_container, container_info, mode_zephir='non'):
return container_info
def calc_libelle_annuaire(*args, **kwargs):
return "LDAP label"
def valid_differ(data, value):
if data == value:
raise ValueError(u"Value must be different from {0}".format(value))
def device_type(*args, **kwargs):
return 'b'
def random_int(vmin, vmax, exclude=None):
values = list(range(int(vmin), int(vmax)+1))
if exclude != None and exclude in values:
values.remove(exclude)
return random.choice(values)

View file

@ -0,0 +1,32 @@
#!/usr/bin/env python3
"""AmonEcole example
"""
from examples.AmonEcole import amonecole
from tiramisu_cmdline_parser import TiramisuCmdlineParser
from tiramisu import default_storage
def display_name(option, dyn_name):
return "--" + option.impl_getpath()
def main():
"""AmonEcole
"""
default_storage.setting(engine='sqlite3', name='amonecole_cmdline_parser')
config = amonecole.get_config(display_name=display_name)
config.property.read_write()
config.property.pop('expert')
config.property.pop('normal')
config.property.add('expert')
config.property.add('normal')
config.permissive.add('expert')
config.permissive.add('normal')
parser = TiramisuCmdlineParser(config, root='creole')
#parser.parse_args(valid_mandatory=False)
parser.parse_args()
if __name__ == "__main__":
main()

View file

@ -8,9 +8,7 @@ import unicodedata
import re import re
from os import unlink from os import unlink
from os.path import isfile from os.path import isfile
from tiramisu import RegexpOption, OptionDescription, Config, IntOption, UnicodeOption, BoolOption, ParamOption, Params from tiramisu import RegexpOption, OptionDescription, Config, IntOption, UnicodeOption, BoolOption, ParamOption, Params, default_storage
from tiramisu.storage import storage_type
from tiramisu.storage.sqlite3.storage import SETTING
from tiramisu_cmdline_parser import TiramisuCmdlineParser from tiramisu_cmdline_parser import TiramisuCmdlineParser
@ -120,7 +118,7 @@ def main():
proposal = ProposalOption('guess{}'.format(idx), proposal = ProposalOption('guess{}'.format(idx),
'Guess {}'.format(idx), 'Guess {}'.format(idx),
requires=requires, requires=requires,
properties=('positional',)) properties=('positional', 'mandatory'))
#FIXME maximum recursion ... #FIXME maximum recursion ...
#if proposals: #if proposals:
# proposal.impl_add_consistency('not_equal', proposals[0]) # proposal.impl_add_consistency('not_equal', proposals[0])
@ -147,15 +145,16 @@ def main():
#descr = OptionDescription('proposals', #descr = OptionDescription('proposals',
# 'Suggesting letters', # 'Suggesting letters',
# options) # options)
storage_type.set('sqlite3') default_storage.setting(engine='sqlite3', name='hangman_cmdline_parser')
config = Config(OptionDescription('root', 'root', [word, proposal_word, misses, proposals_left] + options), persistent=True, session_id='hangman') config = Config(OptionDescription('root', 'root', [word, proposal_word, misses, proposals_left] + options), persistent=True, session_id='hangman')
parser = TiramisuCmdlineParser(config) config.property.read_write()
try: try:
parser.parse_args() parser = TiramisuCmdlineParser(config)
parser.parse_args(valid_mandatory=False)
except ValueError: except ValueError:
# if no more suggestion
pass pass
config = parser.get_config() filename = '/tmp/tiramisu.db'
filename = '{}/tiramisu.db'.format(SETTING.dir_database)
lost = False lost = False
for name in ['proposal_word', 'misses', 'proposals_left']: for name in ['proposal_word', 'misses', 'proposals_left']:
option = config.option(name) option = config.option(name)

View file

@ -1,29 +1,34 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from setuptools import setup, find_packages from setuptools import setup, find_packages
import os
from tiramisu_cmdline_parser import __version__
import tiramisu_cmdline_parser
PACKAGE_NAME = os.environ.get('PACKAGE_DST', 'tiramisu_cmdline_parser')
setup( setup(
name='tiramisu_cmdline_parser', version=__version__,
version=tiramisu_cmdline_parser.__version__, author="Tiramisu's team",
packages=find_packages(), author_email='gnunux@gnunux.info',
author="Emmanuel Garette & Tiramisu Team", name=PACKAGE_NAME,
description="command-line parser using Tiramisu.", description="command-line parser using Tiramisu.",
long_description="""
Python3 parser for command-line options and arguments using Tiramisu engine.
""",
include_package_data=True,
url='https://framagit.org/tiramisu/tiramisu-cmdline-parser', url='https://framagit.org/tiramisu/tiramisu-cmdline-parser',
license='GNU Library or Lesser General Public License (LGPL)',
install_requires=["tiramisu>=5.0"],
classifiers=[ classifiers=[
"Programming Language :: Python",
"Development Status :: 1 - Planning",
"License :: LGPLv3",
"Natural Language :: French",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Topic :: Communications", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
"Operating System :: OS Independent",
"Natural Language :: English",
"Natural Language :: French",
], ],
long_description="""\
tiramisu-cmdline-parser
---------------------------------
Python3 parser for command-line options and arguments using Tiramisu engine.
""",
include_package_data=True,
packages=find_packages(include=['tiramisu_cmdline_parser'])
) )

View file

@ -1,193 +0,0 @@
from io import StringIO
from contextlib import redirect_stdout, redirect_stderr
from tiramisu_cmdline_parser import TiramisuCmdlineParser
from tiramisu import IntOption, StrOption, BoolOption, ChoiceOption, \
SymLinkOption, OptionDescription, Config
def get_config():
choiceoption = ChoiceOption('cmd',
'choice the sub argument',
('str', 'list', 'int', 'none'),
properties=('mandatory',
'positional'))
booloption = BoolOption('verbosity',
'increase output verbosity',
default=False)
short_booloption = SymLinkOption('v', booloption)
str_ = StrOption('str',
'string option',
properties=('mandatory',),
requires=[{'option': choiceoption,
'expected': 'str',
'action': 'disabled',
'inverse': True}])
list_ = StrOption('list',
'list string option',
multi=True,
properties=('mandatory',),
requires=[{'option': choiceoption,
'expected': 'list',
'action': 'disabled',
'inverse': True}])
int_ = IntOption('int',
'int option',
properties=('mandatory',),
requires=[{'option': choiceoption,
'expected': 'int',
'action': 'disabled',
'inverse': True}])
config = Config(OptionDescription('root',
'root',
[choiceoption,
booloption,
short_booloption,
str_,
list_,
int_
]))
config.property.read_write()
return config
def test_readme_help():
output = """usage: prog.py [-h] [-v] {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
"""
parser = TiramisuCmdlineParser(get_config(), 'prog.py')
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
"""
parser = TiramisuCmdlineParser(get_config(), '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_mandatory():
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(), '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_cross():
output = """usage: prog.py [-h] [-v] {str,list,int,none}
prog.py: error: unrecognized arguments: --int
"""
parser = TiramisuCmdlineParser(get_config(), '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_int():
output = {'cmd': 'int',
'int': 3,
'verbosity': False,
'v': False}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['int', '--int', '3'])
# assert config.value.dict() == output
def test_readme_int_verbosity():
output = {'cmd': 'int',
'int': 3,
'verbosity': True,
'v': True}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['int', '--int', '3', '--verbosity'])
assert config.value.dict() == output
def test_readme_int_verbosity_short():
output = {'cmd': 'int',
'int': 3,
'verbosity': True,
'v': True}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['int', '--int', '3', '-v'])
assert config.value.dict() == output
def test_readme_str():
output = {'cmd': 'str',
'str': 'value',
'verbosity': False,
'v': False}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['str', '--str', 'value'])
assert config.value.dict() == output
def test_readme_str_int():
output = {'cmd': 'str',
'str': '3',
'verbosity': False,
'v': False}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['str', '--str', '3'])
assert config.value.dict() == output
def test_readme_list():
output = {'cmd': 'list',
'list': ['a', 'b', 'c'],
'verbosity': False,
'v': False}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['list', '--list', 'a', 'b', 'c'])
assert config.value.dict() == output
def test_readme_list_uniq():
output = {'cmd': 'list',
'list': ['a'],
'verbosity': False,
'v': False}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['list', '--list', 'a'])
assert config.value.dict() == output

0
tests/__init__.py Normal file
View file

196
tests/test_choice.py Normal file
View file

@ -0,0 +1,196 @@
from io import StringIO
from contextlib import redirect_stdout, redirect_stderr
import pytest
from tiramisu_cmdline_parser import TiramisuCmdlineParser
from tiramisu import 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):
positional = ChoiceOption('positional',
'choice the sub argument',
('str', 'list', 'int', 'none'),
properties=('positional', 'mandatory'))
positional_int = ChoiceOption('positional_int',
'choice the sub argument',
(1, 2, 3),
properties=('positional', 'mandatory'))
str_ = ChoiceOption('str',
'choice the sub argument',
('str1', 'str2', 'str3'))
int_ = ChoiceOption('int',
'choice the sub argument',
(1, 2, 3))
int_multi = ChoiceOption('int_multi',
'choice the sub argument',
(1, 2, 3),
multi=True)
od = OptionDescription('od',
'od',
[positional, positional_int, str_, int_, int_multi])
config = Config(od)
config.property.read_write()
if json == 'tiramisu':
return config
jconfig = JsonConfig(config.option.get())
return jconfig
@pytest.fixture(params=params)
def json(request):
return request.param
def test_choice_positional(json):
output1 = '''usage: prog.py "str" "1" [-h] [--str {str1,str2,str3}] [--int {1,2,3}] [--int_multi [{1,2,3} ...]] {str,list,int,none} {1,2,3}
prog.py: error: argument positional: invalid choice: 'error' (choose from 'str', 'list', 'int', 'none')
'''
output2 = '''usage: prog.py "str" "1" [-h] [--str {str1,str2,str3}] [--int {1,2,3}] [--int_multi [{1,2,3} ...]] {str,list,int,none} {1,2,3}
prog.py: error: argument positional_int: invalid choice: '4' (choose from '1', '2', '3')
'''
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py', formatter_class=TestHelpFormatter)
parser.parse_args(['str', '1'])
assert to_dict(config.value.get()) == {'positional': 'str',
'positional_int': 1,
'str': None,
'int': None,
'int_multi': []}
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['error', '1'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output1
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['str', '4'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output2
def test_choice_str(json):
output = """usage: prog.py "str" "1" --str "str3" [-h] [--str {str1,str2,str3}] [--int {1,2,3}] [--int_multi [{1,2,3} ...]] {str,list,int,none} {1,2,3}
prog.py: error: argument --str: invalid choice: 'error' (choose from 'str1', 'str2', 'str3')
"""
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py', formatter_class=TestHelpFormatter)
parser.parse_args(['str', '1', '--str', 'str1'])
assert to_dict(config.value.get()) == {'positional': 'str',
'positional_int': 1,
'str': 'str1',
'int': None,
'int_multi': []}
parser.parse_args(['str', '1', '--str', 'str2'])
assert to_dict(config.value.get()) == {'positional': 'str',
'positional_int': 1,
'str': 'str2',
'int': None,
'int_multi': []}
parser.parse_args(['str', '1', '--str', 'str3'])
assert to_dict(config.value.get()) == {'positional': 'str',
'positional_int': 1,
'str': 'str3',
'int': None,
'int_multi': []}
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['str', '1', '--str', 'error'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output
assert to_dict(config.value.get()) == {'positional': 'str',
'positional_int': 1,
'str': 'str3',
'int': None,
'int_multi': []}
def test_choice_int(json):
output = """usage: prog.py "str" "1" --int "1" [-h] [--str {str1,str2,str3}] [--int {1,2,3}] [--int_multi [{1,2,3} ...]] {str,list,int,none} {1,2,3}
prog.py: error: argument --int: invalid choice: '4' (choose from '1', '2', '3')
"""
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py', formatter_class=TestHelpFormatter)
parser.parse_args(['str', '1', '--int', '1'])
assert to_dict(config.value.get()) == {'positional': 'str',
'positional_int': 1,
'str': None,
'int': 1,
'int_multi': []}
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['str', '1', '--int', '4'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output
assert to_dict(config.value.get()) == {'positional': 'str',
'positional_int': 1,
'str': None,
'int': 1,
'int_multi': []}
def test_choice_int_multi(json):
output = """usage: prog.py "str" "1" --int_multi "1" "2" [-h] [--str {str1,str2,str3}] [--int {1,2,3}] [--int_multi [{1,2,3} ...]] {str,list,int,none} {1,2,3}
prog.py: error: argument --int_multi: invalid choice: '4' (choose from '1', '2', '3')
"""
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py', formatter_class=TestHelpFormatter)
parser.parse_args(['str', '1', '--int_multi', '1', '2'])
assert to_dict(config.value.get()) == {'positional': 'str',
'positional_int': 1,
'str': None,
'int': None,
'int_multi': [1, 2]}
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['str', '1', '--int_multi', '4'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output
assert to_dict(config.value.get()) == {'positional': 'str',
'positional_int': 1,
'str': None,
'int': None,
'int_multi': [1, 2]}
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['str', '1', '--int_multi', '1', '4'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output
assert to_dict(config.value.get()) == {'positional': 'str',
'positional_int': 1,
'str': None,
'int': None,
'int_multi': [1, 2]}

95
tests/test_help.py Normal file
View file

@ -0,0 +1,95 @@
from io import StringIO
from contextlib import redirect_stdout, redirect_stderr
import pytest
from argparse import RawDescriptionHelpFormatter
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
def get_config(json):
choiceoption = ChoiceOption('cmd',
'choice the sub argument',
('str', 'list', 'int', 'none'),
properties=('mandatory',
'positional'))
od = OptionDescription('od',
'od',
[choiceoption])
root = OptionDescription('root',
'root',
[od])
config = Config(root)
config.property.read_write()
if json == 'tiramisu':
return config
jconfig = JsonConfig(config.option.dict())
return jconfig
@pytest.fixture(params=params)
def json(request):
return request.param
def test_help(json):
output = """usage: prog.py [-h] {str,list,int,none}
options:
-h, --help show this help message and exit
od:
{str,list,int,none} choice the sub argument
"""
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_help_epilog(json):
output = """usage: prog.py [-h] {str,list,int,none}
options:
-h, --help show this help message and exit
od:
{str,list,int,none} choice the sub argument
two line
"""
parser = TiramisuCmdlineParser(get_config(json), 'prog.py', epilog="\ntwo\nline", formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stdout(f):
parser.print_help()
assert f.getvalue() == output
def test_help_epilog_raw(json):
output = """usage: prog.py [-h] {str,list,int,none}
options:
-h, --help show this help message and exit
od:
{str,list,int,none} choice the sub argument
two
line
"""
parser = TiramisuCmdlineParser(get_config(json), 'prog.py', epilog="\ntwo\nline", formatter_class=RawDescriptionHelpFormatter)
f = StringIO()
with redirect_stdout(f):
parser.print_help()
assert f.getvalue() == output

376
tests/test_leadership.py Normal file
View file

@ -0,0 +1,376 @@
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, Leadership, Config, submulti
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, with_mandatory=False):
leader = StrOption('leader', "Leader var", ['192.168.0.1'], multi=True)
follower = StrOption('follower', "Follower", multi=True)
if with_mandatory:
properties = ('mandatory',)
else:
properties = None
follower_submulti = StrOption('follower_submulti', "Follower submulti", multi=submulti, properties=properties)
follower_integer = IntOption('follower_integer', "Follower integer", multi=True)
follower_boolean = BoolOption('follower_boolean', "Follower boolean", multi=True)
follower_choice = ChoiceOption('follower_choice', "Follower choice", ('opt1', 'opt2'), multi=True)
opt_list = [leader, follower, follower_submulti, follower_integer, follower_boolean, follower_choice]
if with_mandatory:
opt_list.append(StrOption('follower_mandatory', "Follower mandatory", multi=True, properties=('mandatory',)))
leadership = Leadership('leader', 'leader', opt_list)
config = Config(OptionDescription('root', 'root', [leadership]))
if json == 'tiramisu':
return config
jconfig = JsonConfig(config.option.dict())
return jconfig
@pytest.fixture(params=params)
def json(request):
return request.param
def test_leadership_help(json):
output = """usage: prog.py [-h] [--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
options:
-h, --help show this help message and exit
leader:
--leader.leader [LEADER ...]
Leader var
--leader.pop-leader INDEX
--leader.follower INDEX [FOLLOWER]
Follower
--leader.follower_submulti INDEX [FOLLOWER_SUBMULTI ...]
Follower submulti
--leader.follower_integer INDEX [FOLLOWER_INTEGER]
Follower integer
--leader.follower_boolean INDEX
Follower boolean
--leader.no-follower_boolean INDEX
--leader.follower_choice INDEX [{opt1,opt2}]
Follower choice
--leader.follower_mandatory INDEX FOLLOWER_MANDATORY
Follower mandatory
"""
parser = TiramisuCmdlineParser(get_config(json, with_mandatory=True), 'prog.py', formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stdout(f):
parser.print_help()
assert f.getvalue() == output
def test_leadership_modif_leader(json):
output = {'leader.leader': ['192.168.1.1'],
'leader.follower': [None],
'leader.follower_boolean': [None],
'leader.follower_choice': [None],
'leader.follower_integer': [None],
'leader.follower_submulti': [[]]}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--leader.leader', '192.168.1.1'])
assert to_dict(config.value.get()) == output
def test_leadership_modif_follower(json):
output = {'leader.leader': ['192.168.0.1'],
'leader.follower': ['255.255.255.0'],
'leader.follower_boolean': [None],
'leader.follower_choice': [None],
'leader.follower_integer': [None],
'leader.follower_submulti': [[]]}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--leader.follower', '0', '255.255.255.0'])
assert to_dict(config.value.get()) == output
def test_leadership_modif_follower_not_submulti(json):
output = """usage: prog.py [-h] [--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}]]
prog.py: error: unrecognized arguments: 255.255.255.0
"""
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py', formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['--leader.follower', '0', '255.255.255.0', '255.255.255.0'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_leadership_modif_follower_submulti(json):
output = {'leader.leader': ['192.168.0.1'],
'leader.follower': [None],
'leader.follower_boolean': [None],
'leader.follower_choice': [None],
'leader.follower_integer': [None],
'leader.follower_submulti': [['255.255.255.0']]}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--leader.follower_submulti', '0', '255.255.255.0'])
assert to_dict(config.value.get()) == output
def test_leadership_modif_follower_submulti_multi(json):
output = {'leader.leader': ['192.168.0.1'],
'leader.follower': [None],
'leader.follower_boolean': [None],
'leader.follower_choice': [None],
'leader.follower_integer': [None],
'leader.follower_submulti': [['255.255.255.0', '255.255.255.128']]}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--leader.follower_submulti', '0', '255.255.255.0', '255.255.255.128'])
assert to_dict(config.value.get()) == output
def test_leadership_modif_follower_bool_true(json):
output = {'leader.leader': ['192.168.0.1'],
'leader.follower': [None],
'leader.follower_boolean': [True],
'leader.follower_choice': [None],
'leader.follower_integer': [None],
'leader.follower_submulti': [[]]}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--leader.follower_boolean', '0'])
assert to_dict(config.value.get()) == output
def test_leadership_modif_follower_bool_false(json):
output = {'leader.leader': ['192.168.0.1'],
'leader.follower': [None],
'leader.follower_boolean': [False],
'leader.follower_choice': [None],
'leader.follower_integer': [None],
'leader.follower_submulti': [[]]}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--leader.no-follower_boolean', '0'])
assert to_dict(config.value.get()) == output
def test_leadership_modif_follower_bool_true_fullname(json):
output = {'leader.leader': ['192.168.0.1'],
'leader.follower': [None],
'leader.follower_boolean': [True],
'leader.follower_choice': [None],
'leader.follower_integer': [None],
'leader.follower_submulti': [[]]}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py', fullpath=False)
parser.parse_args(['--follower_boolean', '0'])
assert to_dict(config.value.get()) == output
def test_leadership_modif_follower_bool_false_fullname(json):
output = {'leader.leader': ['192.168.0.1'],
'leader.follower': [None],
'leader.follower_boolean': [False],
'leader.follower_choice': [None],
'leader.follower_integer': [None],
'leader.follower_submulti': [[]]}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py', fullpath=False)
parser.parse_args(['--no-follower_boolean', '0'])
assert to_dict(config.value.get()) == output
def test_leadership_modif_follower_choice(json):
output = {'leader.leader': ['192.168.0.1'],
'leader.follower': [None],
'leader.follower_boolean': [None],
'leader.follower_choice': ['opt1'],
'leader.follower_integer': [None],
'leader.follower_submulti': [[]]}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--leader.follower_choice', '0', 'opt1'])
assert to_dict(config.value.get()) == output
def test_leadership_modif_follower_choice_unknown(json):
output = """usage: prog.py [-h] [--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}]]
prog.py: error: argument --leader.follower_choice: invalid choice: 'opt_unknown' (choose from 'opt1', 'opt2')
"""
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py', formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['--leader.follower_choice', '0', 'opt_unknown'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_leadership_modif_follower_not_number(json):
output = """usage: prog.py [-h] [--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}]]
prog.py: error: index must be a number, not a
"""
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py', formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['--leader.follower', 'a', '255.255.255.0'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_leadership_modif_multi(json):
output = {'leader.leader': ['192.168.1.1', '10.253.10.1', '192.168.253.1'],
'leader.follower': ['255.255.255.128', None, '255.255.255.0'],
'leader.follower_boolean': [None, None, None],
'leader.follower_choice': [None, None, None],
'leader.follower_integer': [None, None, None],
'leader.follower_submulti': [[], [], []]}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--leader.leader', '192.168.1.1', '10.253.10.1', '192.168.253.1',
'--leader.follower', '0', '255.255.255.128',
'--leader.follower', '2', '255.255.255.0'])
assert to_dict(config.value.get()) == output
def test_leadership_modif_multi_reduce(json):
output = {'leader.leader': ['192.168.1.1', '192.168.253.1'],
'leader.follower': ['255.255.255.128', '255.255.255.0'],
'leader.follower_boolean': [None, None],
'leader.follower_choice': [None, None],
'leader.follower_integer': [None, None],
'leader.follower_submulti': [[], []]}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--leader.leader', '192.168.1.1', '10.253.10.1', '192.168.253.1',
'--leader.follower', '0', '255.255.255.128',
'--leader.follower', '2', '255.255.255.0',
'--leader.pop-leader', '1'])
assert to_dict(config.value.get()) == output
def test_leadership_modif_mandatory(json):
output = {'leader.leader': ['192.168.1.1'],
'leader.follower': [None],
'leader.follower_mandatory': ['255.255.255.128'],
'leader.follower_boolean': [None],
'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.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)
parser = TiramisuCmdlineParser(config, 'prog.py', formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['--leader.leader', '192.168.1.1'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output2 + ', --leader.follower_mandatory\n'
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['--leader.leader', '192.168.1.1',
'--leader.follower_mandatory', '0', '255.255.255.128'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output2 + '\n'
parser.parse_args(['--leader.leader', '192.168.1.1',
'--leader.follower_submulti', '0', '255.255.255.128',
'--leader.follower_mandatory', '0', '255.255.255.128'])
assert to_dict(config.value.get()) == output
def test_leadership_modif_mandatory_remove(json):
output = {'leader.leader': ['192.168.1.1'],
'leader.follower': [None],
'leader.follower_mandatory': ['255.255.255.128'],
'leader.follower_boolean': [None],
'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
prog.py: error: the following arguments are required: --leader.follower_submulti"""
config = get_config(json, with_mandatory=True)
parser = TiramisuCmdlineParser(config, 'prog.py', display_modified_value=False, formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['--leader.leader', '192.168.1.1'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output2 + ', --leader.follower_mandatory\n'
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['--leader.leader', '192.168.1.1',
'--leader.follower_mandatory', '0', '255.255.255.128'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output2 + '\n'
parser.parse_args(['--leader.leader', '192.168.1.1',
'--leader.follower_submulti', '0', '255.255.255.128',
'--leader.follower_mandatory', '0', '255.255.255.128'])
assert to_dict(config.value.get()) == output
def test_leadership_modif_mandatory_unvalidate(json):
output = {'leader.leader': ['192.168.1.1'],
'leader.follower': [None],
'leader.follower_mandatory': [None],
'leader.follower_boolean': [None],
'leader.follower_choice': [None],
'leader.follower_integer': [None],
'leader.follower_submulti': [[]]}
config = get_config(json, with_mandatory=True)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--leader.leader', '192.168.1.1'], valid_mandatory=False)
assert to_dict(config.value.get()) == output

View file

@ -0,0 +1,188 @@
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, \
SymLinkOption, OptionDescription, Config
try:
from tiramisu_api import Config as JsonConfig
#params = ['tiramisu', 'tiramisu-json']
params = ['tiramisu']
except:
params = ['tiramisu']
from .utils import TestHelpFormatter
def get_config(json, has_tree=False, default_verbosity=False, add_long=False, add_store_false=False, empty_optiondescription=False):
choiceoption = ChoiceOption('cmd',
'choice the sub argument',
('str', 'list', 'int', 'none'),
properties=('mandatory',
'positional'))
booloption = BoolOption('verbosity',
'increase output verbosity',
default=default_verbosity)
short_booloption = SymLinkOption('v', booloption)
od0 = OptionDescription('od0',
'Sub-Tree 1',
[choiceoption,
booloption,
short_booloption,
])
if empty_optiondescription:
descr = None
else:
descr = 'First OptionDescription'
od1 = OptionDescription('od1',
descr,
[od0])
before = StrOption('before',
'Before',
properties=('mandatory',))
after = StrOption('after',
'After',
properties=('mandatory',))
str_ = StrOption('str',
'string option 2',
properties=('mandatory',))
subtree = OptionDescription('subtree',
'Sub-Tree 2',
[str_])
od2 = OptionDescription('od2',
None,
[before, subtree, after])
root = OptionDescription('root',
'root',
[od1, od2])
config = Config(root)
config.property.read_write()
if json == 'tiramisu':
return config
jconfig = JsonConfig(config.option.dict())
return jconfig
@pytest.fixture(params=params)
def json(request):
return request.param
def test_optiondescription_help(json):
output = """usage: prog.py [-h] [-v] [-nv] --od2.subtree.str STR --od2.before BEFORE --od2.after AFTER {str,list,int,none}
options:
-h, --help show this help message and exit
od1:
First OptionDescription
od1.od0:
Sub-Tree 1
{str,list,int,none} choice the sub argument
-v, --od1.od0.verbosity
increase output verbosity
-nv, --od1.od0.no-verbosity
od2:
--od2.before BEFORE Before
--od2.after AFTER After
od2.subtree:
Sub-Tree 2
--od2.subtree.str STR
string option 2
"""
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_optiondescription_help_remove_empty_od(json):
output = """usage: prog.py [-h] [-v] [-nv] --od2.subtree.str STR --od2.before BEFORE --od2.after AFTER {str,list,int,none}
options:
-h, --help show this help message and exit
od1.od0:
Sub-Tree 1
{str,list,int,none} choice the sub argument
-v, --od1.od0.verbosity
increase output verbosity
-nv, --od1.od0.no-verbosity
od2:
--od2.before BEFORE Before
--od2.after AFTER After
od2.subtree:
Sub-Tree 2
--od2.subtree.str STR
string option 2
"""
parser = TiramisuCmdlineParser(get_config(json), 'prog.py', remove_empty_od=True, formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stdout(f):
parser.print_help()
assert f.getvalue() == output
def test_optiondescription_help_remove_empty_description_od(json):
output = """usage: prog.py [-h] [-v] [-nv] --od2.subtree.str STR --od2.before BEFORE --od2.after AFTER {str,list,int,none}
options:
-h, --help show this help message and exit
od1.od0:
Sub-Tree 1
{str,list,int,none} choice the sub argument
-v, --od1.od0.verbosity
increase output verbosity
-nv, --od1.od0.no-verbosity
od2:
--od2.before BEFORE Before
--od2.after AFTER After
od2.subtree:
Sub-Tree 2
--od2.subtree.str STR
string option 2
"""
parser = TiramisuCmdlineParser(get_config(json, empty_optiondescription=True), 'prog.py', formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stdout(f):
parser.print_help()
assert f.getvalue() == output
def test_optiondescription_help_subtree(json):
output = """usage: prog.py [-h] --od2.subtree.str STR --od2.before BEFORE --od2.after AFTER
options:
-h, --help show this help message and exit
--od2.before BEFORE Before
--od2.after AFTER After
od2.subtree:
Sub-Tree 2
--od2.subtree.str STR
string option 2
"""
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py', root='od2', formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stdout(f):
parser.print_help()
assert f.getvalue() == output

901
tests/test_readme.py Normal file
View file

@ -0,0 +1,901 @@
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, \
SymLinkOption, OptionDescription, Config, calc_value, \
Calculation, ParamValue, ParamOption, Params
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',
'positional'))
booloption = BoolOption('verbosity',
'increase output verbosity',
default=default_verbosity)
short_booloption = SymLinkOption('v', booloption)
str_ = StrOption('str',
'string option',
properties=('mandatory',
Calculation(calc_value,
Params(ParamValue('disabled'),
kwargs={'condition': ParamOption(choiceoption),
'reverse_condition': ParamValue(True),
'expected': ParamValue('str')})),
),
)
list_ = StrOption('list',
'list string option',
multi=True,
properties=('mandatory',
Calculation(calc_value,
Params(ParamValue('disabled'),
kwargs={'condition': ParamOption(choiceoption),
'reverse_condition': ParamValue(True),
'expected': ParamValue('list')})),
),
)
int_ = IntOption('int',
'int option',
properties=('mandatory',
Calculation(calc_value,
Params(ParamValue('disabled'),
kwargs={'condition': ParamOption(choiceoption),
'reverse_condition': ParamValue(True),
'expected': ParamValue('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()
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] [-v] [-nv] {str,list,int,none}
positional arguments:
{str,list,int,none} choice the sub argument
options:
-h, --help show this help message and exit
-v, --verbosity increase output verbosity
-nv, --no-verbosity
"""
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_help_tree(json):
output = """usage: prog.py [-h] [-v] [-nv] {str,list,int,none}
options:
-h, --help show this help message and exit
root:
{str,list,int,none} choice the sub argument
-v, --root.verbosity increase output verbosity
-nv, --root.no-verbosity
"""
parser = TiramisuCmdlineParser(get_config(json, True), 'prog.py', formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stdout(f):
parser.print_help()
assert f.getvalue() == output
def test_readme_help_tree_flatten(json):
output = """usage: prog.py [-h] [-v] [-nv] {str,list,int,none}
options:
-h, --help show this help message and exit
root:
{str,list,int,none} choice the sub argument
-v, --verbosity increase output verbosity
-nv, --no-verbosity
"""
parser = TiramisuCmdlineParser(get_config(json, True), 'prog.py', fullpath=False, formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stdout(f):
parser.print_help()
assert f.getvalue() == output
def test_readme_help_modif_positional(json):
output = """usage: prog.py "str" [-h] [-v] [-nv] --str STR {str,list,int,none}
positional arguments:
{str,list,int,none} choice the sub argument
options:
-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', formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stdout(f):
try:
parser.parse_args(['str', '--help'])
except SystemExit as err:
assert str(err) == "0"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_readme_help_modif_positional_remove(json):
output = """usage: prog.py "str" [-h] [-v] [-nv] --str STR
options:
-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', display_modified_value=False, formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stdout(f):
try:
parser.parse_args(['str', '--help'])
except SystemExit as err:
assert str(err) == "0"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_readme_help_modif(json):
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
options:
-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', formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stdout(f):
try:
parser.parse_args(['str', '--str', 'toto', '--help'])
except SystemExit as err:
assert str(err) == "0"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_readme_help_modif_remove(json):
output = """usage: prog.py "str" --str "toto" [-h] [-v] [-nv]
options:
-h, --help show this help message and exit
-v, --verbosity increase output verbosity
-nv, --no-verbosity
"""
parser = TiramisuCmdlineParser(get_config(json), 'prog.py', display_modified_value=False, formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stdout(f):
try:
parser.parse_args(['str', '--str', 'toto', '--help'])
except SystemExit as err:
assert str(err) == "0"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_readme_help_modif_short(json):
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
options:
-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', formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stdout(f):
try:
parser.parse_args(['str', '-v', '--help'])
except SystemExit as err:
assert str(err) == "0"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_readme_help_modif_short_remove(json):
# FIXME -v -nv ?? pas de description
output = """usage: prog.py "str" -v [-h] [-nv] --str STR
options:
-h, --help show this help message and exit
-nv, --no-verbosity increase output verbosity
--str STR string option
"""
parser = TiramisuCmdlineParser(get_config(json), 'prog.py', display_modified_value=False, formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stdout(f):
try:
parser.parse_args(['str', '-v', '--help'])
except SystemExit as err:
assert str(err) == "0"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_readme_help_modif_short_no1(json):
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
options:
-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', formatter_class=TestHelpFormatter)
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_help_modif_short_no_remove(json):
output = """usage: prog.py "str" -v [-h] [-v] --str STR
options:
-h, --help show this help message and exit
-v, --verbosity increase output verbosity
--str STR string option
"""
parser = TiramisuCmdlineParser(get_config(json), 'prog.py', display_modified_value=False, formatter_class=TestHelpFormatter)
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(json):
output = """usage: prog.py [-h] [-v] [-nv] {str,list,int,none}
prog.py: error: the following arguments are required: cmd
"""
parser = TiramisuCmdlineParser(get_config(json), 'prog.py', formatter_class=TestHelpFormatter)
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(json):
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(json, True), 'prog.py', formatter_class=TestHelpFormatter)
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(json):
output = """usage: prog.py [-h] [-v] [-nv] {str,list,int,none}
prog.py: error: the following arguments are required: cmd
"""
parser = TiramisuCmdlineParser(get_config(json, True), 'prog.py', fullpath=False, formatter_class=TestHelpFormatter)
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(json):
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', formatter_class=TestHelpFormatter)
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_remove(json):
output = """usage: prog.py "str" [-h] [-v] [-nv] --str STR
prog.py: error: the following arguments are required: --str
"""
parser = TiramisuCmdlineParser(get_config(json), 'prog.py', display_modified_value=False, formatter_class=TestHelpFormatter)
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(json):
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', formatter_class=TestHelpFormatter)
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_remove(json):
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(json, True), 'prog.py', display_modified_value=False, formatter_class=TestHelpFormatter)
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(json):
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, formatter_class=TestHelpFormatter)
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_remove(json):
output = """usage: prog.py "str" [-h] [-v] [-nv] --str STR
prog.py: error: the following arguments are required: --str
"""
parser = TiramisuCmdlineParser(get_config(json, True), 'prog.py', fullpath=False, display_modified_value=False, formatter_class=TestHelpFormatter)
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(json):
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', formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['none', '--int', '1'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_readme_cross_remove(json):
output = """usage: prog.py "none" [-h] [-v] [-nv]
prog.py: error: unrecognized arguments: --int
"""
parser = TiramisuCmdlineParser(get_config(json), 'prog.py', display_modified_value=False, formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['none', '--int', '1'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_readme_cross_tree(json):
output = """usage: prog.py "none" [-h] [-v] [-nv] {str,list,int,none}
prog.py: error: unrecognized arguments: --root.int
"""
parser = TiramisuCmdlineParser(get_config(json, True), 'prog.py', formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['none', '--root.int', '1'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_readme_cross_tree_remove(json):
output = """usage: prog.py "none" [-h] [-v] [-nv]
prog.py: error: unrecognized arguments: --root.int
"""
parser = TiramisuCmdlineParser(get_config(json, True), 'prog.py', display_modified_value=False, formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['none', '--root.int', '1'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_readme_cross_tree_flatten(json):
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, formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['none', '--int', '1'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_readme_cross_tree_flatten_remove(json):
output = """usage: prog.py "none" [-h] [-v] [-nv]
prog.py: error: unrecognized arguments: --int
"""
parser = TiramisuCmdlineParser(get_config(json, True), 'prog.py', fullpath=False, display_modified_value=False, formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['none', '--int', '1'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_readme_unknown(json):
output = """usage: prog.py [-h] [-v] [-nv] {str,list,int,none}
prog.py: error: argument root.cmd: invalid choice: 'unknown' (choose from 'str', 'list', 'int', 'none')
"""
parser = TiramisuCmdlineParser(get_config(json, True), 'prog.py', fullpath=False, formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['unknown'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_readme_int(json):
output = {'cmd': 'int',
'int': 3,
'verbosity': False,
'v': False}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['int', '--int', '3'])
assert to_dict(config.value.get()) == output
def test_readme_int_tree(json):
output = {'root.cmd': 'int',
'root.int': 3,
'root.verbosity': False,
'root.v': False}
config = get_config(json, True)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['int', '--root.int', '3'])
assert to_dict(config.value.get()) == output
def test_readme_int_tree_flatten(json):
output = {'root.cmd': 'int',
'root.int': 3,
'root.verbosity': False,
'root.v': False}
config = get_config(json, True)
parser = TiramisuCmdlineParser(config, 'prog.py', fullpath=False)
parser.parse_args(['int', '--int', '3'])
assert to_dict(config.value.get()) == output
def test_readme_int_verbosity(json):
output = {'cmd': 'int',
'int': 3,
'verbosity': True,
'v': True}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['int', '--int', '3', '--verbosity'])
assert to_dict(config.value.get()) == output
def test_readme_int_verbosity_tree(json):
output = {'root.cmd': 'int',
'root.int': 3,
'root.verbosity': True,
'root.v': True}
config = get_config(json, True)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['int', '--root.int', '3', '--root.verbosity'])
assert to_dict(config.value.get()) == output
def test_readme_int_verbosity_tree_flatten(json):
output = {'root.cmd': 'int',
'root.int': 3,
'root.verbosity': True,
'root.v': True}
config = get_config(json, True)
parser = TiramisuCmdlineParser(config, 'prog.py', fullpath=False)
parser.parse_args(['int', '--int', '3', '--verbosity'])
assert to_dict(config.value.get()) == output
def test_readme_int_verbosity_short(json):
output = {'cmd': 'int',
'int': 3,
'verbosity': True,
'v': True}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['int', '--int', '3', '-v'])
assert to_dict(config.value.get()) == output
def test_readme_int_verbosity_short_store_false(json):
output = {'cmd': 'int',
'int': 3,
'verbosity': None,
'v': True}
config = get_config(json, default_verbosity=None, add_store_false=True)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['int', '--int', '3', '-v'])
output = {'cmd': 'int',
'int': 3,
'verbosity': False,
'v': False}
assert to_dict(config.value.get()) == output
parser.parse_args(['int', '--int', '3', '-nv'])
output = {'cmd': 'int',
'int': 3,
'verbosity': True,
'v': True}
assert to_dict(config.value.get()) == output
def test_readme_int_verbosity_short_no(json):
output = {'cmd': 'int',
'int': 3,
'verbosity': False,
'v': False}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['int', '--int', '3', '-nv'])
assert to_dict(config.value.get()) == output
def test_readme_int_verbosity_short_tree(json):
output = {'root.cmd': 'int',
'root.int': 3,
'root.verbosity': True,
'root.v': True}
config = get_config(json, True)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['int', '--root.int', '3', '-v'])
assert to_dict(config.value.get()) == output
def test_readme_int_verbosity_short_tree_flatten(json):
output = {'root.cmd': 'int',
'root.int': 3,
'root.verbosity': True,
'root.v': True}
config = get_config(json, True)
parser = TiramisuCmdlineParser(config, 'prog.py', fullpath=False)
parser.parse_args(['int', '--int', '3', '-v'])
assert to_dict(config.value.get()) == output
def test_readme_int_verbosity_short_and_not(json):
output = {'cmd': 'int',
'int': 3,
'verbosity': False,
'v': False}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['int', '--int', '3', '-v', '-nv'])
assert to_dict(config.value.get()) == output
def test_readme_str(json):
output = {'cmd': 'str',
'str': 'value',
'verbosity': False,
'v': False}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['str', '--str', 'value'])
assert to_dict(config.value.get()) == output
def test_readme_str_tree(json):
output = {'root.cmd': 'str',
'root.str': 'value',
'root.verbosity': False,
'root.v': False}
config = get_config(json, True)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['str', '--root.str', 'value'])
assert to_dict(config.value.get()) == output
def test_readme_str_tree_flatten(json):
output = {'root.cmd': 'str',
'root.str': 'value',
'root.verbosity': False,
'root.v': False}
config = get_config(json, True)
parser = TiramisuCmdlineParser(config, 'prog.py', fullpath=False)
parser.parse_args(['str', '--str', 'value'])
assert to_dict(config.value.get()) == output
def test_readme_str_int(json):
output = {'cmd': 'str',
'str': '3',
'verbosity': False,
'v': False}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['str', '--str', '3'])
assert to_dict(config.value.get()) == output
def test_readme_str_int_tree(json):
output = {'root.cmd': 'str',
'root.str': '3',
'root.verbosity': False,
'root.v': False}
config = get_config(json, True)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['str', '--root.str', '3'])
assert to_dict(config.value.get()) == output
def test_readme_str_int_tree_flatten(json):
output = {'root.cmd': 'str',
'root.str': '3',
'root.verbosity': False,
'root.v': False}
config = get_config(json, True)
parser = TiramisuCmdlineParser(config, 'prog.py', fullpath=False)
parser.parse_args(['str', '--str', '3'])
assert to_dict(config.value.get()) == output
def test_readme_list_single(json):
output = {'cmd': 'list',
'list': ['a'],
'verbosity': False,
'v': False}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['list', '--list', 'a'])
assert to_dict(config.value.get()) == output
def test_readme_list(json):
output = {'cmd': 'list',
'list': ['a', 'b', 'c'],
'verbosity': False,
'v': False}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['list', '--list', 'a', 'b', 'c'])
assert to_dict(config.value.get()) == output
def test_readme_list_tree(json):
output = {'root.cmd': 'list',
'root.list': ['a', 'b', 'c'],
'root.verbosity': False,
'root.v': False}
config = get_config(json, True)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['list', '--root.list', 'a', 'b', 'c'])
assert to_dict(config.value.get()) == output
def test_readme_list_tree_flatten(json):
output = {'root.cmd': 'list',
'root.list': ['a', 'b', 'c'],
'root.verbosity': False,
'root.v': False}
config = get_config(json, True)
parser = TiramisuCmdlineParser(config, 'prog.py', fullpath=False)
parser.parse_args(['list', '--list', 'a', 'b', 'c'])
assert to_dict(config.value.get()) == output
def test_readme_list_uniq(json):
output = {'cmd': 'list',
'list': ['a'],
'verbosity': False,
'v': False}
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['list', '--list', 'a'])
assert to_dict(config.value.get()) == output
def test_readme_list_uniq_tree(json):
output = {'root.cmd': 'list',
'root.list': ['a'],
'root.verbosity': False,
'root.v': False}
config = get_config(json, True)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['list', '--root.list', 'a'])
assert to_dict(config.value.get()) == output
def test_readme_list_uniq_tree_flatten(json):
output = {'root.cmd': 'list',
'root.list': ['a'],
'root.verbosity': False,
'root.v': False}
config = get_config(json, True)
parser = TiramisuCmdlineParser(config, 'prog.py', fullpath=False)
parser.parse_args(['list', '--list', 'a'])
assert to_dict(config.value.get()) == output
def test_readme_longargument(json):
output = {'cmd': 'list',
'list': ['a'],
'verbosity': True,
'v': True}
config = get_config(json, add_long=True)
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['list', '--list', 'a', '--v'])
assert to_dict(config.value.get()) == output
def test_readme_unknown_key(json):
output1 = """usage: prog.py [-h] [-v] [-nv] {str,list,int,none}
prog.py: error: unrecognized arguments: --unknown
"""
output2 = """usage: prog.py [-h] [-v] [-nv] {str,list,int,none}
prog.py: error: unrecognized arguments: --root.unknown
"""
parser = TiramisuCmdlineParser(get_config(json, True), 'prog.py', fullpath=False, formatter_class=TestHelpFormatter)
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['--unknown'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output1
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['--root.unknown'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output2

211
tests/test_shortarg.py Normal file
View file

@ -0,0 +1,211 @@
from io import StringIO
import pytest
from contextlib import redirect_stderr
from tiramisu_cmdline_parser import TiramisuCmdlineParser
from tiramisu import IntOption, StrOption, BoolOption, ChoiceOption, \
SymLinkOption, OptionDescription, Config
try:
from tiramisu_api import Config as JsonConfig
params = ['tiramisu']
#params = ['tiramisu', 'tiramisu-json']
except:
params = ['tiramisu']
from .utils import to_dict
@pytest.fixture(params=params)
def json(request):
return request.param
def test_short(json):
def get_config():
list_ = StrOption('list',
'list string option')
slist_ = SymLinkOption('l', list_)
root = OptionDescription('root',
'root',
[list_,
slist_,
])
config = Config(root)
config.property.read_write()
if json != 'tiramisu':
config = JsonConfig(config.option.dict())
return config
#
output = {'list': None, 'l': None}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args([])
assert to_dict(config.value.get()) == output
#
output = {'list': 'a', 'l': 'a'}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--list', 'a'])
assert to_dict(config.value.get()) == output
#
output = {'list': 'a', 'l': 'a'}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['-l', 'a'])
assert to_dict(config.value.get()) == output
#
assert config.option('list').value.get() == config.option('l').value.get()
assert config.option('list').owner.get() == config.option('l').owner.get()
assert config.option('list').owner.isdefault() == config.option('l').owner.isdefault()
def test_short_mandatory(json):
def get_config():
list_ = StrOption('list',
'list string option',
properties=('mandatory',))
slist_ = SymLinkOption('l', list_)
root = OptionDescription('root',
'root',
[list_,
slist_,
])
config = Config(root)
config.property.read_write()
if json != 'tiramisu':
config = JsonConfig(config.option.dict())
return config
#
output = """usage: prog.py [-h] -l LIST
prog.py: error: the following arguments are required: --list
"""
config = get_config()
parser = TiramisuCmdlineParser(config, '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
#
output = {'list': 'a', 'l': 'a'}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--list', 'a'])
assert to_dict(config.value.get()) == output
#
output = {'list': 'a', 'l': 'a'}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['-l', 'a'])
assert to_dict(config.value.get()) == output
def test_short_multi(json):
def get_config():
list_ = StrOption('list',
'list string option',
multi=True)
slist_ = SymLinkOption('l', list_)
root = OptionDescription('root',
'root',
[list_,
slist_,
])
config = Config(root)
config.property.read_write()
if json != 'tiramisu':
config = JsonConfig(config.option.dict())
return config
#
output = {'list': [], 'l': []}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args([])
assert to_dict(config.value.get()) == output
#
output = {'list': ['a'], 'l': ['a']}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--list', 'a'])
assert to_dict(config.value.get()) == output
#
output = {'list': ['a', 'b'], 'l': ['a', 'b']}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--list', 'a', 'b'])
assert to_dict(config.value.get()) == output
#
output = {'list': ['a'], 'l': ['a']}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['-l', 'a'])
assert to_dict(config.value.get()) == output
#
output = {'list': ['a', 'b'], 'l': ['a', 'b']}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['-l', 'a', 'b'])
assert to_dict(config.value.get()) == output
def test_short_multi_mandatory(json):
def get_config():
list_ = StrOption('list',
'list string option',
multi=True,
properties=('mandatory',))
slist_ = SymLinkOption('l', list_)
root = OptionDescription('root',
'root',
[list_,
slist_,
])
config = Config(root)
config.property.read_write()
if json != 'tiramisu':
config = JsonConfig(config.option.dict())
return config
#
output = """usage: prog.py [-h] -l LIST [LIST ...]
prog.py: error: the following arguments are required: --list
"""
config = get_config()
parser = TiramisuCmdlineParser(config, '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
#
output = {'list': ['a'], 'l': ['a']}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--list', 'a'])
assert to_dict(config.value.get()) == output
#
output = {'list': ['a', 'b'], 'l': ['a', 'b']}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--list', 'a', 'b'])
assert to_dict(config.value.get()) == output
#
output = {'list': ['a'], 'l': ['a']}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['-l', 'a'])
assert to_dict(config.value.get()) == output
#
output = {'list': ['a', 'b'], 'l': ['a', 'b']}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['-l', 'a', 'b'])
assert to_dict(config.value.get()) == output

35
tests/utils.py Normal file
View file

@ -0,0 +1,35 @@
from argparse import HelpFormatter
from tiramisu_cmdline_parser.api import TiramisuHelpFormatter
def _leadership_to_dict(dico, ret):
leader, *followers = list(dico)
ret[leader.path()] = dico[leader]
for follower in followers:
follower_path = follower.path()
ret.setdefault(follower_path, []).append(dico[follower])
def _to_dict(dico, ret):
for key, value in dico.items():
if key.isoptiondescription():
if key.isleadership():
_leadership_to_dict(value, ret)
else:
_to_dict(value, ret)
else:
ret[key.path()] = value
def to_dict(dico):
ret = {}
_to_dict(dico, ret)
return ret
class TestHelpFormatter(TiramisuHelpFormatter, HelpFormatter):
def __init__(self,
*args,
**kwargs,
):
return super().__init__(*args, **kwargs, width=5000)

View file

@ -1,4 +1,9 @@
from .tiramisu_cmdline_parser import TiramisuCmdlineParser try:
from .api import TiramisuCmdlineParser
except ImportError as err:
import warnings
warnings.warn("cannot not import TiramisuCmdlineParser {err}", ImportWarning)
TiramisuCmdlineParser = None
__version__ = "0.0.1" __version__ = "0.5"
__all__ = ('TiramisuCmdlineParser',) __all__ = ('TiramisuCmdlineParser',)

View file

@ -0,0 +1,644 @@
# Copyright (C) 2018-2019 Team tiramisu (see AUTHORS for all contributors)
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from typing import Union, List, Dict, Tuple, Optional, Any
from argparse import ArgumentParser, Namespace, SUPPRESS, _HelpAction, HelpFormatter
from copy import copy
from gettext import gettext as _
#try:
from tiramisu import Config
from tiramisu.error import PropertiesOptionError, LeadershipError
#except ImportError:
# Config = None
# from tiramisu_api.error import PropertiesOptionError
# LeadershipError = ValueError
try:
from tiramisu_api import Config as ConfigJson
if Config is None:
Config = ConfigJson
except ImportError:
ConfigJson = Config
def get_choice_list(config, properties, display):
def convert(choice):
if isinstance(choice, int):
return str(choice)
return choice
choices = [convert(choice) for choice in config.value.list()]
if choices and choices[0] == '':
del choices[0]
if display:
choices = '{{{}}}'.format(','.join(choices))
if 'mandatory' not in properties:
choices = '[{}]'.format(choices)
return choices
class TiramisuNamespace(Namespace):
def __init__(self,
config: Config,
root: Optional[str]) -> None:
super().__setattr__('_config', config)
super().__setattr__('_root', root)
super().__setattr__('list_force_no', {})
super().__setattr__('list_force_del', {})
super().__setattr__('arguments', {})
self._populate()
super().__init__()
def _populate(self) -> None:
if self._root is None:
config = self._config
else:
config = self._config.option(self._root)
self._parse(config.value.get())
def _parse(self, subconfig):
for option, value in subconfig.items():
if option.isoptiondescription():
self._parse(value)
elif not option.issymlinkoption():
if value == [] and option.ismulti():
value = None
super().__setattr__(option.path(), value)
def __setattr__(self,
key: str,
value: Any,
) -> None:
if key in self.list_force_no:
true_key = self.list_force_no[key]
elif key in self.list_force_del:
true_key = self.list_force_del[key]
else:
true_key = key
option = self._config.option(true_key)
if option.isfollower():
_setattr = self._setattr_follower
if not value[0].isdecimal():
raise ValueError('index must be a number, not {}'.format(value[0]))
index = int(value[0])
option = self._config.option(true_key, index)
true_value = ','.join(value[1:])
else:
_setattr = self._setattr
true_value = value
if option.type() == 'choice':
# HACK if integer in choice
values = option.value.list()
if isinstance(value, list):
int_value = []
for val in value:
if isinstance(val, str) and val.isdigit():
int_val = int(val)
if int_val in values:
val = int_val
int_value.append(val)
value = int_value
elif value not in values and isinstance(value, str) and value.isdigit():
int_value = int(value)
if int_value in values:
value = int_value
try:
if key in self.list_force_del:
option.value.pop(value)
else:
_setattr(option, true_key, key, value)
except ValueError as err:
if option.type() == 'choice':
values = option.value.list()
display_value = ''
if isinstance(true_value, list):
for val in value:
if val not in values:
display_value = val
break
else:
display_value = true_value
choices = get_choice_list(option, option.property.get(), False)
raise ValueError("argument {}: invalid choice: '{}' (choose from {})".format(self.arguments[key], display_value, ', '.join([f"'{val}'" for val in choices])))
else:
raise err
def _setattr(self,
option: 'Option',
true_key: str,
key: str,
value: Any) -> None:
if option.ismulti() and \
value is not None and \
not isinstance(value, list):
value = [value]
try:
option.value.set(value)
except PropertiesOptionError:
raise AttributeError('unrecognized arguments: {}'.format(self.arguments[key]))
def _setattr_follower(self,
option: 'Option',
true_key: str,
key: str,
value: Any) -> None:
index = int(value[0])
if option.type() == 'boolean':
value = key not in self.list_force_no
elif option.issubmulti():
value = value[1:]
else:
value = value[1]
self._config.option(true_key, index).value.set(value)
class TiramisuHelpFormatter:
def _get_default_metavar_for_optional(self,
action):
ret = super()._get_default_metavar_for_optional(action)
if '.' in ret:
ret = ret.rsplit('.', 1)[1]
return ret
class _Section(HelpFormatter._Section):
def format_help(self):
# Remove empty OD
if self.formatter.remove_empty_od and \
len(self.items) == 1 and \
self.items[0][0].__name__ == '_format_text':
return ''
return super().format_help()
class _TiramisuHelpAction(_HelpAction):
needs = False
def __call__(self, *args, **kwargs):
_TiramisuHelpAction.needs = True
def display(self, parser):
_HelpAction.__call__(self, parser, None, None)
class _BuildKwargs:
def __init__(self,
name: str,
option: 'Option',
cmdlineparser: 'TiramisuCmdlineParser',
properties: List[str],
force_no: bool,
force_del: bool,
display_modified_value: bool,
not_display: bool) -> None:
self.kwargs = {}
self.cmdlineparser = cmdlineparser
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('%', '%%')
self.kwargs['help'] = description
if 'positional' not in self.properties:
is_short_name = self.cmdlineparser._is_short_name(name, 'longargument' in self.properties)
if self.force_no:
ga_name = self.gen_argument_name(name, is_short_name)
ga_path = self.gen_argument_name(option.path(), is_short_name)
self.cmdlineparser.namespace.list_force_no[ga_path] = option.path()
elif self.force_del:
ga_name = self.gen_argument_name(name, is_short_name)
ga_path = self.gen_argument_name(option.path(), is_short_name)
self.cmdlineparser.namespace.list_force_del[ga_path] = option.path()
else:
ga_name = name
self.kwargs['dest'] = self.gen_argument_name(option.path(), False)
argument = self.cmdlineparser._gen_argument(ga_name, is_short_name)
self.cmdlineparser.namespace.arguments[option.path()] = argument
self.args = [argument]
else:
self.cmdlineparser.namespace.arguments[option.path()] = option.path()
self.args = [option.path()]
def __setitem__(self,
key: str,
value: Any) -> None:
self.kwargs[key] = value
def add_argument(self,
option: 'Option'):
is_short_name = self.cmdlineparser._is_short_name(option.name(), 'longargument' in self.properties)
if self.force_no:
name = self.gen_argument_name(option.name(), is_short_name)
elif self.force_del:
name = self.gen_argument_name(option.name(), is_short_name)
else:
name = option.name()
argument = self.cmdlineparser._gen_argument(name, is_short_name)
self.cmdlineparser.namespace.arguments[option.path()] = argument
self.args.insert(0, argument)
def gen_argument_name(self, name, is_short_name):
if self.force_no:
if is_short_name:
prefix = 'n'
else:
prefix = 'no-'
if '.' in name:
sname = name.rsplit('.', 1)
name = sname[0] + '.' + prefix + sname[1]
else:
name = prefix + name
if self.force_del:
if is_short_name:
prefix = 'p'
else:
prefix = 'pop-'
if '.' in name:
sname = name.rsplit('.', 1)
name = sname[0] + '.' + prefix + sname[1]
else:
name = prefix + name
return name
def get(self) -> Tuple[Dict]:
return self.args, self.kwargs
class TiramisuCmdlineParser(ArgumentParser):
def __init__(self,
config: Union[Config, ConfigJson],
*args,
root: str=None,
fullpath: bool=True,
remove_empty_od: bool=False,
display_modified_value: bool=True,
formatter_class=HelpFormatter,
unrestraint: bool=False,
_forhelp: bool=False,
**kwargs):
if not _forhelp:
unrestraint = True
self.fullpath = fullpath
self.config = config
self.root = root
self.remove_empty_od = remove_empty_od
self.unrestraint = unrestraint
self.display_modified_value = display_modified_value
if TiramisuHelpFormatter not in formatter_class.__mro__:
formatter_class = type('TiramisuHelpFormatter', (TiramisuHelpFormatter, formatter_class), {})
formatter_class.remove_empty_od = self.remove_empty_od
kwargs['formatter_class'] = formatter_class
if not _forhelp and self.unrestraint:
subconfig = self.config.unrestraint
else:
subconfig = self.config
if self.root is None:
subconfig = subconfig
else:
subconfig = subconfig.option(self.root)
self.namespace = TiramisuNamespace(self.config, self.root)
super().__init__(*args, **kwargs)
self.register('action', 'help', _TiramisuHelpAction)
self._config_to_argparser(_forhelp,
subconfig,
self.root)
def _pop_action_class(self, kwargs, default=None):
ret = super()._pop_action_class(kwargs, default)
if kwargs.get('action') != 'help' and kwargs.get('dest') != 'help':
return ret
return _TiramisuHelpAction
def _match_arguments_partial(self,
actions,
arg_string_pattern):
# used only when check first proposal for first value
# we have to remove all actions with propertieserror
# so only first settable option will be returned
actions_pop = []
for idx, action in enumerate(actions):
if self.config.option(action.dest).property.get(only_raises=True):
actions_pop.append(idx)
else:
break
for idx in actions_pop:
actions.pop(0)
return super()._match_arguments_partial(actions, arg_string_pattern)
def _is_short_name(self, name, longargument):
return len(name) == 1 and not longargument
def _gen_argument(self, name, is_short_name):
if is_short_name:
return self.prefix_chars + name
return self.prefix_chars * 2 + name
def _parse_known_args(self, args=None, namespace=None):
try:
namespace_, args_ = super()._parse_known_args(args, namespace)
except (ValueError, LeadershipError, AttributeError) 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
# so create a new parser
new_parser = TiramisuCmdlineParser(self.config,
self.prog,
root=self.root,
remove_empty_od=self.remove_empty_od,
display_modified_value=self.display_modified_value,
formatter_class=self.formatter_class,
epilog=self.epilog,
description=self.description,
unrestraint=self.unrestraint,
fullpath=self.fullpath)
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
self._registries['action']['help'].needs = False
helper = self._registries['action']['help'](None)
helper.display(self)
return namespace_, args_
def add_argument(self, *args, **kwargs):
if args == ('-h', '--help'):
super().add_argument(*args, **kwargs)
else:
raise NotImplementedError(_('do not use add_argument'))
def add_arguments(self, *args, **kwargs):
raise NotImplementedError(_('do not use add_argument'))
def add_subparsers(self, *args, **kwargs):
raise NotImplementedError(_('do not use add_subparsers'))
def _option_is_not_default(self,
properties,
type,
name,
value):
if 'positional' not in properties:
is_short_name = self._is_short_name(name, 'longargument' in properties)
self.prog += ' {}'.format(self._gen_argument(name, is_short_name))
if type != 'boolean':
if isinstance(value, list):
for val in value:
self.prog += ' "{}"'.format(val)
else:
self.prog += ' "{}"'.format(value)
def _config_list(self,
config: Config,
prefix: Optional[str],
_forhelp: bool,
group, level):
for obj in config:
# do not display frozen option
if 'frozen' in obj.property.get():
continue
if obj.isoptiondescription():
if _forhelp:
description = obj.description()
if description == obj.name():
description = None
newgroup = self.add_argument_group(obj.path(), description)
else:
newgroup = group
if prefix:
prefix_ = prefix + '.' + obj.name()
else:
prefix_ = obj.path()
self._config_to_argparser(_forhelp, obj, prefix_, newgroup, level + 1)
elif obj.type() == 'boolean' and not obj.issymlinkoption():
if not obj.isleader():
yield obj, False, None
yield obj, True, None
else:
yield obj, False, False
yield obj, False, True
yield obj, True, None
elif obj.isleader():
yield obj, None, False
yield obj, None, True
else:
yield obj, None, None
def _config_to_argparser(self,
_forhelp: bool,
config,
prefix: Optional[str],
group=None,
level=0) -> None:
if group is None:
group = super()
actions = {}
leadership_len = None
options_is_not_default = {}
for option, force_no, force_del in self._config_list(config, prefix, _forhelp, group, level):
name = option.name()
if name.startswith(self.prefix_chars):
raise ValueError(_('name cannot startswith "{}"').format(self.prefix_chars))
if option.issymlinkoption():
symlink_name = option.option().name()
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)
continue
if force_del:
value = None
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
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)
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(),
'name': name,
'value': value}
if not self.display_modified_value:
continue
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()
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'
else:
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 = option.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():
kwargs['nargs'] = '+'
else:
kwargs['nargs'] = 2
if _forhelp and 'mandatory' not in properties:
metavar = '[{}]'.format(metavar)
if option.type() == 'choice':
# do not manage choice with argparse there is problem with integer problem
kwargs['metavar'] = ('INDEX', get_choice_list(option, properties, True))
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():
# do not manage choice with argparse there is problem with integer problem
kwargs['choices'] = get_choice_list(option, properties, False)
elif option.type() == 'float':
kwargs['type'] = float
else:
pass
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()
group.add_argument(*args, **kwargs)
def _valid_mandatory(self):
pass
def parse_args(self,
*args,
valid_mandatory=True,
**kwargs):
kwargs['namespace'] = self.namespace
try:
namespaces = super().parse_args(*args, **kwargs)
except PropertiesOptionError as err:
name = err._option_bag.option.impl_getname()
properties = self.config.option(name).property.get()
if self.fullpath and 'positional' not in properties:
if len(name) == 1 and 'longargument' not in properties:
name = self.prefix_chars + name
else:
name = self.prefix_chars * 2 + name
if err.proptype == ['mandatory']:
self.error('the following arguments are required: {}'.format(name))
else:
self.error('unrecognized arguments: {}'.format(name))
if valid_mandatory:
errors = []
for option in self.config.value.mandatory():
properties = option.property.get()
if not option.isfollower():
if 'positional' not in properties:
if self.fullpath:
name = option.path()
else:
name = option.name()
is_short_name = self._is_short_name(name, 'longargument' in option.property.get())
args = self._gen_argument(name, is_short_name)
else:
args = option.path()
else:
if 'positional' not in properties:
args = self._gen_argument(option.path(), False)
else:
args = option.path()
if not self.fullpath and '.' in args:
args = args.rsplit('.', 1)[1]
if 'positional' not in properties:
args = self._gen_argument(args, False)
errors.append(args)
if errors:
self.error('the following arguments are required: {}'.format(', '.join(errors)))
return namespaces
def format_usage(self,
*args,
**kwargs):
help_formatter = TiramisuCmdlineParser(self.config,
self.prog,
root=self.root,
fullpath=self.fullpath,
remove_empty_od=self.remove_empty_od,
display_modified_value=self.display_modified_value,
formatter_class=self.formatter_class,
epilog=self.epilog,
description=self.description,
_forhelp=True)
return super(TiramisuCmdlineParser, help_formatter).format_usage(*args, **kwargs)
def format_help(self):
help_formatter = TiramisuCmdlineParser(self.config,
self.prog,
root=self.root,
fullpath=self.fullpath,
remove_empty_od=self.remove_empty_od,
display_modified_value=self.display_modified_value,
formatter_class=self.formatter_class,
epilog=self.epilog,
description=self.description,
_forhelp=True)
return super(TiramisuCmdlineParser, help_formatter).format_help()
def get_config(self):
return self.config

View file

@ -1,281 +0,0 @@
# Copyright (C) 2018 Team tiramisu (see AUTHORS for all contributors)
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from typing import Union, List, Optional
from argparse import ArgumentParser, Namespace, SUPPRESS, _HelpAction
try:
from tiramisu import Config
from tiramisu.error import PropertiesOptionError
except ImportError:
Config = None
from tiramisu_json_api.error import PropertiesOptionError
try:
from tiramisu_json_api import Config as ConfigJson
if Config is None:
Config = ConfigJson
except ImportError:
ConfigJson = Config
class TiramisuNamespace(Namespace):
def _populate(self):
#self._config.property.read_only()
for tiramisu_key, tiramisu_value in self._config.value.dict().items():
option = self._config.option(tiramisu_key)
if not option.option.issymlinkoption():
if tiramisu_value == [] and option.option.ismulti() and option.owner.isdefault():
tiramisu_value = None
super().__setattr__(tiramisu_key, tiramisu_value)
#self._config.property.read_write()
def __init__(self, config):
self._config = config
super().__init__()
def __setattr__(self, key, value):
if key == '_config':
super().__setattr__(key, value)
return
# self._config.property.read_write()
option = self._config.option(key)
if option.option.ismulti() and value is not None and not isinstance(value, list):
value = [value]
option.value.set(value)
def __getattribute__(self, key):
if key == '__dict__' and hasattr(self, '_config'):
self._populate()
return super().__getattribute__(key)
class _TiramisuHelpAction(_HelpAction):
needs = False
def __call__(self, *args, **kwargs):
_TiramisuHelpAction.needs = True
def display(self, parser):
_HelpAction.__call__(self, parser, None, None)
class TiramisuCmdlineParser(ArgumentParser):
def __init__(self,
config: Union[Config, ConfigJson],
*args,
fullpath: bool=True,
_forhelp: bool=False,
**kwargs):
self.fullpath = fullpath
self.config = config
super().__init__(*args, **kwargs)
self.register('action', 'help', _TiramisuHelpAction)
self._config_to_argparser(_forhelp,
self.config.option)
def _pop_action_class(self, kwargs, default=None):
ret = super()._pop_action_class(kwargs, default)
if kwargs.get('action') != 'help' and kwargs.get('dest') != 'help':
return ret
return _TiramisuHelpAction
def _match_arguments_partial(self,
actions,
arg_string_pattern):
# used only when check first proposal for first value
# we have to remove all actions with propertieserror
# so only first settable option will be returned
actions_pop = []
for idx, action in enumerate(actions):
if self.config.option(action.dest).property.get(only_raises=True):
actions_pop.append(idx)
else:
break
for idx in actions_pop:
actions.pop(0)
return super()._match_arguments_partial(actions, arg_string_pattern)
def _parse_known_args(self, args=None, namespace=None):
namespace_, args_ = super()._parse_known_args(args, namespace)
if args != args_ and args_ and args_[0].startswith(self.prefix_chars):
# option that was disabled are no more disable
# so create a new parser
new_parser = TiramisuCmdlineParser(self.config,
self.prog,
fullpath=self.fullpath)
namespace_, args_ = new_parser._parse_known_args(args_, namespace)
else:
if self._registries['action']['help'].needs:
# display help only when all variables assignemnt are done
self._registries['action']['help'].needs = False
helper = self._registries['action']['help'](None)
helper.display(self)
return namespace_, args_
def add_argument(self, *args, **kwargs):
if args == ('-h', '--help'):
super().add_argument(*args, **kwargs)
else:
raise NotImplementedError('do not use add_argument')
def add_arguments(self, *args, **kwargs):
raise NotImplementedError('do not use add_argument')
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:
return self.prefix_chars + name
return self.prefix_chars * 2 + name
def _config_to_argparser(self,
_forhelp: bool,
config,
prefix: Optional[str]=None,
group=None) -> None:
if group is None:
group = super()
actions = {}
for obj in config.list(type='all'):
option = obj.option
if option.isoptiondescription():
if _forhelp:
group = self.add_argument_group(option.doc())
if prefix:
prefix_ = prefix + '.' + option.name()
else:
prefix_ = option.path()
self._config_to_argparser(_forhelp, obj, prefix_, group)
continue
if 'frozen' in option.properties():
continue
name = option.name()
if name.startswith(self.prefix_chars):
raise ValueError('name cannot startswith "{}"'.format(self.prefix_chars))
if self.fullpath and prefix:
name = prefix + '.' + name
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))
continue
if 'positional' in properties:
if not 'mandatory' in properties:
raise ValueError('"positional" argument must be "mandatory" too')
args = [option.path()]
if _forhelp:
kwargs['default'] = obj.value.default()
else:
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'] = '+'
else:
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 args, kwargs in actions.values():
group.add_argument(*args, **kwargs)
def parse_args(self,
*args,
valid_mandatory=True,
**kwargs):
kwargs['namespace'] = TiramisuNamespace(self.config)
try:
namespaces = super().parse_args(*args, **kwargs)
del namespaces.__dict__['_config']
except PropertiesOptionError as err:
name = err._option_bag.option.impl_getname()
properties = self.config.option(name).property.get()
if 'positional' not in properties:
if len(name) == 1 and 'longargument' not in properties:
name = self.prefix_chars + name
else:
name = self.prefix_chars * 2 + name
if err.proptype == ['mandatory']:
self.error('the following arguments are required: {}'.format(name))
else:
self.error('unrecognized arguments: {}'.format(name))
if valid_mandatory:
for key in self.config.value.mandatory():
properties = self.config.option(key).property.get()
if 'positional' not in properties:
if self.fullpath or '.' not in key:
name = key
else:
name = key.rsplit('.', 1)[1]
args = self._gen_argument(name, self.config.option(key).property.get())
else:
args = key
self.error('the following arguments are required: {}'.format(args))
return namespaces
def format_usage(self,
*args,
**kwargs):
help_formatter = TiramisuCmdlineParser(self.config,
self.prog,
fullpath=self.fullpath,
_forhelp=True)
return super(TiramisuCmdlineParser, help_formatter).format_usage(*args, **kwargs)
def format_help(self,
*args,
**kwargs):
help_formatter = TiramisuCmdlineParser(self.config,
self.prog,
fullpath=self.fullpath,
_forhelp=True)
return super(TiramisuCmdlineParser, help_formatter).format_help(*args, **kwargs)
def get_config(self):
return self.config