38 Commits

Author SHA1 Message Date
c9a911870d new tiramisu_cmdline_parser version 2020-04-08 16:58:39 +02:00
7f95b1bfb0 new tiramisu_cmdline_parser version 2020-04-08 16:48:58 +02:00
c13da02553 add float support 2020-03-20 22:19:10 +01:00
505af25995 support empty choice 2020-03-16 15:34:51 +01:00
1bcfa0618a unrestraint 2019-08-23 16:22:34 +02:00
c26da98525 python 3.5 2019-08-22 16:24:41 +02:00
ed54090710 python 3.5 support 2019-08-22 15:58:34 +02:00
46866f1e38 choice already manage by argparse 2019-08-03 10:33:45 +02:00
e6a9a37607 support integer choiceoption 2019-08-03 09:06:27 +02:00
74215a6f80 no- + fullname == false 2019-07-30 21:50:48 +02:00
57e85b49eb 0.2 2019-07-30 08:49:09 +02:00
504d5d71a4 problem with boolean option 2019-07-30 08:48:46 +02:00
e2c4e3381a add display_modified_value parameter 2019-07-30 06:58:16 +02:00
7fe8cf322d setup.py 2019-07-29 22:10:40 +02:00
79ddb8bc11 remove OD if name == description is now managed with internal argparser mechanism 2019-07-28 22:44:17 +02:00
9bc52bcbbb add all options even if already set by user 2019-07-28 09:15:31 +02:00
4e2c37822c add --pop-leader to remove a value in a leader option 2019-07-27 18:51:16 +02:00
7721c55990 do not use RawDescriptionHelpFormatter by default 2019-07-26 21:35:22 +02:00
85fb384ab0 path => compare 2019-07-26 16:39:49 +02:00
7917fcabe5 use rawformatter and add epilog 2019-07-26 16:39:01 +02:00
71608ef63f remove_empty_description_od with path 2019-07-26 16:02:16 +02:00
62c0812863 remove OD with no description in help if option remove_empty_description_od set to True 2019-07-26 15:40:54 +02:00
3d0ac1fb19 remove empty OD in help if option remove_empty_od set to True 2019-07-26 15:17:40 +02:00
e14b997a54 support suboptiondescription in TiramisuCmdLineParser 2019-07-26 10:56:31 +02:00
9db0942291 add tests 2019-05-09 20:12:23 +02:00
111d0717b1 leader/follower support 2019-04-17 19:16:43 +02:00
0f38946670 use gettext 2019-04-09 07:28:11 +02:00
7130a53e7d hangman now works 2019-04-09 07:18:05 +02:00
1fed1eef8e ImportError is deprecated -> ModuleNotFoundError 2019-04-04 15:43:05 +02:00
b67a1ec430 add test for longargument property 2019-04-03 07:55:19 +02:00
eeca074331 add storefalse properties for boolean option 2019-04-03 07:51:26 +02:00
266cef224e for bool, generate --no-xxxx option 2019-04-02 21:02:08 +02:00
075de80f73 help with modified argument 2019-04-02 19:01:17 +02:00
3ac9031411 help with modified positional argument 2019-04-02 08:49:36 +02:00
f90c02282a --root.v => -v, --int ROOT.INT => --int INT 2019-04-01 21:08:41 +02:00
708e623107 Merge branch 'master' into 'master'
Create module directory and add setup.py to project.

See merge request tiramisu/tiramisu-cmdline-parser!1
2019-03-25 15:07:09 +01:00
06340fd40d Create module directory and add setup.py to project. 2019-03-25 15:07:09 +01:00
3320d795cc updates test 2019-02-27 07:32:32 +01:00
48 changed files with 15226 additions and 647 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
from os import unlink
from os.path import isfile
from tiramisu import RegexpOption, OptionDescription, Config, IntOption, UnicodeOption, BoolOption, ParamOption, Params
from tiramisu.storage import storage_type
from tiramisu.storage.sqlite3.storage import SETTING
from tiramisu import RegexpOption, OptionDescription, Config, IntOption, UnicodeOption, BoolOption, ParamOption, Params, default_storage
from tiramisu_cmdline_parser import TiramisuCmdlineParser
@ -120,7 +118,7 @@ def main():
proposal = ProposalOption('guess{}'.format(idx),
'Guess {}'.format(idx),
requires=requires,
properties=('positional',))
properties=('positional', 'mandatory'))
#FIXME maximum recursion ...
#if proposals:
# proposal.impl_add_consistency('not_equal', proposals[0])
@ -147,15 +145,16 @@ def main():
#descr = OptionDescription('proposals',
# 'Suggesting letters',
# 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')
parser = TiramisuCmdlineParser(config)
config.property.read_write()
try:
parser.parse_args()
parser = TiramisuCmdlineParser(config)
parser.parse_args(valid_mandatory=False)
except ValueError:
# if no more suggestion
pass
config = parser.get_config()
filename = '{}/tiramisu.db'.format(SETTING.dir_database)
filename = '/tmp/tiramisu.db'
lost = False
for name in ['proposal_word', 'misses', 'proposals_left']:
option = config.option(name)

34
setup.py Normal file
View File

@ -0,0 +1,34 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from setuptools import setup, find_packages
import os
from tiramisu_cmdline_parser import __version__
PACKAGE_NAME = os.environ.get('PACKAGE_DST', 'tiramisu_cmdline_parser')
setup(
version=__version__,
author="Tiramisu's team",
author_email='gnunux@gnunux.info',
name=PACKAGE_NAME,
description="command-line parser using Tiramisu.",
url='https://framagit.org/tiramisu/tiramisu-cmdline-parser',
license='GNU Library or Lesser General Public License (LGPL)',
install_requires=["tiramisu_api>=0.1"],
classifiers=[
"Programming Language :: Python :: 3",
"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,210 +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'),
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}])
return Config(OptionDescription('root',
'root',
[choiceoption,
booloption,
short_booloption,
str_,
list_,
int_
]))
def test_readme_help():
output = """usage: prog.py [-h] [-v] --str STR --list LIST [LIST ...] --int INT
{str,list,int}
positional arguments:
{str,list,int} choice the sub argument
optional arguments:
-h, --help show this help message and exit
-v, --verbosity increase output verbosity
--str STR string option
--list LIST [LIST ...]
list string option
--int INT int option
"""
parser = TiramisuCmdlineParser('prog.py')
parser.add_arguments(get_config())
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 STR --list LIST [LIST ...] --int INT
{str,list,int}
prog.py: error: the following arguments are required: cmd
"""
parser = TiramisuCmdlineParser('prog.py')
parser.add_arguments(get_config())
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 --list LIST [LIST ...] --int INT
{str,list,int}
prog.py: error: the following arguments are required: --str
"""
parser = TiramisuCmdlineParser('prog.py')
parser.add_arguments(get_config())
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 STR --list LIST [LIST ...] --int INT
{str,list,int}
prog.py: error: unrecognized arguments: --int
"""
parser = TiramisuCmdlineParser('prog.py')
parser.add_arguments(get_config())
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['str', '--int', '3'])
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('prog.py')
parser.add_arguments(config)
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('prog.py')
parser.add_arguments(config)
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('prog.py')
parser.add_arguments(config)
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('prog.py')
parser.add_arguments(config)
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('prog.py')
parser.add_arguments(config)
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('prog.py')
parser.add_arguments(config)
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('prog.py')
parser.add_arguments(config)
parser.parse_args(['list', '--list', 'a'])
assert config.value.dict() == output

View File

@ -1,155 +0,0 @@
from tiramisu import StrOption, BoolOption, IntOption, ChoiceOption, OptionDescription, SymLinkOption
from py.test import raises, fixture
from io import StringIO
import sys
from os import listdir
from os.path import join, isdir
from contextlib import redirect_stdout, redirect_stderr
from argparse import ArgumentParser
#from pouet import TiramisuCmdlineParser
from tiramisu_cmdline_parser import TiramisuCmdlineParser
DATA_DIR = 'test/data/compare'
TEST_DIRS = []
for test in listdir(DATA_DIR):
test_file = join(DATA_DIR, test)
if isdir(test_file):
TEST_DIRS.append(test_file)
TEST_DIRS.sort()
# TEST_DIRS.remove('test/data/compare/10_positional_list')
# TEST_DIRS = ['test/data/compare/10_positional_list']
@fixture(scope="module", params=TEST_DIRS)
def test_list(request):
return request.param
def import_subfile_and_test(filename, parser, arg):
parser_dict = []
parser_system_err = []
f = StringIO()
with redirect_stderr(f):
exec(open(filename).read())
# print('arg', arg)
try:
parser_dict.append(parser.parse_args(arg).__dict__)
except SystemExit as err:
parser_system_err.append(str(err))
else:
parser_system_err.append(None)
parser_error = f.getvalue()
f = StringIO()
with redirect_stdout(f):
parser.print_help()
parser_help = f.getvalue()
return parser_dict, parser_system_err, parser_error, parser_help
def test_files(test_list):
args = [[],
# 10_positional
['bar'], ['foo', 'bar'],
# 10_positional_int
['4'],
# 20_bool
['--verbosity'], ['--verbosity', 'arg'],
# 20_string
['--foo'], ['--foo', '--bar'], ['--foo', 'a'],
['--foo', 'a', '--foo', 'b'],
# 20_int
['--int', '3'], ['--int', 'a'],
# 20 choice
['--door', 'a'], ['--door', '1'],
# 30_string_short
['-f', 'b'], ['--foo', 'c', '-f', 'b'],
# 40 multi_bool
['-v'], ['-v', '-s'], ['-vs'],
# 40_short_long
['-v', '--foo', '1'], ['-vf', '2'], ['-vf'], ['-vf', '-v'],
# 40_positional_optional
['bar', '--verbosity'], ['--verbosity', 'bar'],
]
for arg in args:
# FIXME unknown argument is check before mandatory
if test_list == 'test/data/compare/10_positional_list':
check = False
for subarg in arg:
if not subarg.startswith('-'):
check = True
break
if not check:
continue
tiramparser = TiramisuCmdlineParser('prog.py')
tiramparser_dict, tiramparser_system_err, tiramparser_error, tiramparser_help = import_subfile_and_test(test_list + '/tiramisu.py',
tiramparser, arg)
#
argparser = ArgumentParser('prog.py')
argparser_dict, argparser_system_err, argparser_error, argparser_help = import_subfile_and_test(test_list + '/argparse.py',
argparser, arg)
# print('===>', test_list, arg)
# print(tiramparser_dict)
# print(tiramparser_system_err)
# print(tiramparser_error)
# print(tiramparser_help)
# print('-----')
# print(argparser_dict)
# print(argparser_system_err)
# print(argparser_error)
# print(argparser_help)
assert tiramparser_dict == argparser_dict
assert tiramparser_error == argparser_error
assert tiramparser_help == argparser_help
assert tiramparser_system_err == argparser_system_err
#FIXME --verbose sans --quiet
#parser = argparse.ArgumentParser(description="calculate X to the power of Y")
#group = parser.add_mutually_exclusive_group()
#group.add_argument("-v", "--verbose", action="store_true")
#group.add_argument("-q", "--quiet", action="store_true")
#parser.add_argument("x", type=int, help="the base")
#parser.add_argument("y", type=int, help="the exponent")
#args = parser.parse_args()
#answer = args.x**args.y
#FIXME --sum ?
#parser = argparse.ArgumentParser(description='Process some integers.')
#parser.add_argument('integers', metavar='N', type=int, nargs='+',
# help='an integer for the accumulator')
#parser.add_argument('--sum', dest='accumulate', action='store_const',
# const=sum, default=max,
# help='sum the integers (default: find the max)')
#args = parser.parse_args()
#print(args.accumulate(args.integers))
# +++++++++++++++++++++++++++++ nargs
#FIXME longueur fixe
#>>> parser = argparse.ArgumentParser()
#>>> parser.add_argument('--foo', nargs=2)
#>>> parser.add_argument('bar', nargs=1)
#>>> parser.parse_args('c --foo a b'.split())
#Namespace(bar=['c'], foo=['a', 'b'])
#FIXME const
#>>> parser = argparse.ArgumentParser()
#>>> parser.add_argument('--foo', nargs='?', const='c', default='d')
#>>> parser.add_argument('bar', nargs='?', default='d')
#>>> parser.parse_args(['XX', '--foo', 'YY'])
#Namespace(bar='XX', foo='YY')
#>>> parser.parse_args(['XX', '--foo'])
#Namespace(bar='XX', foo='c')
#>>> parser.parse_args([])
#Namespace(bar='d', foo='d')
#FIXME ? | * | +
# * == list
# + == list + mandatory

207
tests/test_choice.py Normal file
View File

@ -0,0 +1,207 @@
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']
except:
params = ['tiramisu']
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.dict())
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} [{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} [{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')
parser.parse_args(['str', '1'])
assert config.value.dict() == {'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} [{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')
parser.parse_args(['str', '1', '--str', 'str1'])
assert config.value.dict() == {'positional': 'str',
'positional_int': 1,
'str': 'str1',
'int': None,
'int_multi': []}
parser.parse_args(['str', '1', '--str', 'str2'])
assert config.value.dict() == {'positional': 'str',
'positional_int': 1,
'str': 'str2',
'int': None,
'int_multi': []}
parser.parse_args(['str', '1', '--str', 'str3'])
assert config.value.dict() == {'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 config.value.dict() == {'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} [{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')
parser.parse_args(['str', '1', '--int', '1'])
assert config.value.dict() == {'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 config.value.dict() == {'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} [{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')
parser.parse_args(['str', '1', '--int_multi', '1', '2'])
assert config.value.dict() == {'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 config.value.dict() == {'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 config.value.dict() == {'positional': 'str',
'positional_int': 1,
'str': None,
'int': None,
'int_multi': [1, 2]}

99
tests/test_help.py Normal file
View File

@ -0,0 +1,99 @@
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, \
SymLinkOption, OptionDescription, Config
try:
from tiramisu_api import Config as JsonConfig
params = ['tiramisu', 'tiramisu-json']
except:
params = ['tiramisu']
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}
optional arguments:
-h, --help show this help message and exit
od:
od
{str,list,int,none} choice the sub argument
"""
parser = TiramisuCmdlineParser(get_config(json), 'prog.py')
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}
optional arguments:
-h, --help show this help message and exit
od:
od
{str,list,int,none} choice the sub argument
two line
"""
parser = TiramisuCmdlineParser(get_config(json), 'prog.py', epilog="\ntwo\nline")
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}
optional arguments:
-h, --help show this help message and exit
od:
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

424
tests/test_leadership.py Normal file
View File

@ -0,0 +1,424 @@
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, Leadership, Config, submulti
try:
from tiramisu_api import Config as JsonConfig
params = ['tiramisu', 'tiramisu-json']
except:
params = ['tiramisu']
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 ...]]]
[--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
optional arguments:
-h, --help show this help message and exit
leader:
leader
--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')
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 config.value.dict() == 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 config.value.dict() == output
def test_leadership_modif_follower_not_submulti(json):
output = """usage: prog.py [-h] [--leader.leader [LEADER [LEADER ...]]]
[--leader.pop-leader INDEX]
[--leader.follower INDEX [FOLLOWER]]
[--leader.follower_submulti INDEX [FOLLOWER_SUBMULTI ...]]
[--leader.follower_integer INDEX [FOLLOWER_INTEGER]]
[--leader.follower_boolean INDEX]
[--leader.no-follower_boolean INDEX]
[--leader.follower_choice INDEX [{opt1,opt2}]]
prog.py: error: unrecognized arguments: 255.255.255.0
"""
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == output
def test_leadership_modif_follower_choice_unknown(json):
output = """usage: prog.py [-h] [--leader.leader [LEADER [LEADER ...]]]
[--leader.pop-leader INDEX]
[--leader.follower INDEX [FOLLOWER]]
[--leader.follower_submulti INDEX [FOLLOWER_SUBMULTI ...]]
[--leader.follower_integer INDEX [FOLLOWER_INTEGER]]
[--leader.follower_boolean INDEX]
[--leader.no-follower_boolean INDEX]
[--leader.follower_choice INDEX [{opt1,opt2}]]
prog.py: error: argument --leader.follower_choice: invalid choice: 'opt_unknown' (choose from 'opt1', 'opt2')
"""
config = get_config(json)
parser = TiramisuCmdlineParser(config, 'prog.py')
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 ...]]]
[--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')
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 config.value.dict() == 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 config.value.dict() == 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 ...]]]
[--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')
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 config.value.dict() == 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)
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 config.value.dict() == 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 config.value.dict() == output

View File

@ -0,0 +1,193 @@
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']
except:
params = ['tiramisu']
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}
optional arguments:
-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')
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}
optional arguments:
-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)
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}
optional arguments:
-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')
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
optional arguments:
-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')
f = StringIO()
with redirect_stdout(f):
parser.print_help()
assert f.getvalue() == output

894
tests/test_readme.py Normal file
View File

@ -0,0 +1,894 @@
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']
except:
params = ['tiramisu']
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',),
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}])
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
optional arguments:
-h, --help show this help message and exit
-v, --verbosity increase output verbosity
-nv, --no-verbosity
"""
parser = TiramisuCmdlineParser(get_config(json), 'prog.py')
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}
optional arguments:
-h, --help show this help message and exit
root:
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')
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}
optional arguments:
-h, --help show this help message and exit
root:
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)
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
optional arguments:
-h, --help show this help message and exit
-v, --verbosity increase output verbosity
-nv, --no-verbosity
--str STR string option
"""
parser = TiramisuCmdlineParser(get_config(json), 'prog.py')
f = StringIO()
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
optional arguments:
-h, --help show this help message and exit
-v, --verbosity increase output verbosity
-nv, --no-verbosity
--str STR string option
"""
parser = TiramisuCmdlineParser(get_config(json), 'prog.py', display_modified_value=False)
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
optional arguments:
-h, --help show this help message and exit
-v, --verbosity increase output verbosity
-nv, --no-verbosity
--str STR string option
"""
parser = TiramisuCmdlineParser(get_config(json), 'prog.py')
f = StringIO()
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]
optional arguments:
-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)
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
optional arguments:
-h, --help show this help message and exit
-v, --verbosity increase output verbosity
-nv, --no-verbosity
--str STR string option
"""
parser = TiramisuCmdlineParser(get_config(json), 'prog.py')
f = StringIO()
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
optional arguments:
-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)
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
optional arguments:
-h, --help show this help message and exit
-v, --verbosity increase output verbosity
-nv, --no-verbosity
--str STR string option
"""
parser = TiramisuCmdlineParser(get_config(json), 'prog.py')
f = StringIO()
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
optional arguments:
-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)
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')
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')
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)
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')
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)
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')
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)
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)
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)
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')
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)
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')
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)
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)
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)
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)
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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == output
parser.parse_args(['int', '--int', '3', '-nv'])
output = {'cmd': 'int',
'int': 3,
'verbosity': True,
'v': True}
assert config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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 config.value.dict() == 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)
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

208
tests/test_shortarg.py Normal file
View File

@ -0,0 +1,208 @@
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', 'tiramisu-json']
except:
params = ['tiramisu']
@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 config.value.dict() == output
#
output = {'list': 'a', 'l': 'a'}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--list', 'a'])
assert config.value.dict() == output
#
output = {'list': 'a', 'l': 'a'}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['-l', 'a'])
assert config.value.dict() == 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 config.value.dict() == output
#
output = {'list': 'a', 'l': 'a'}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['-l', 'a'])
assert config.value.dict() == 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 config.value.dict() == output
#
output = {'list': ['a'], 'l': ['a']}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--list', 'a'])
assert config.value.dict() == output
#
output = {'list': ['a', 'b'], 'l': ['a', 'b']}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--list', 'a', 'b'])
assert config.value.dict() == output
#
output = {'list': ['a'], 'l': ['a']}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['-l', 'a'])
assert config.value.dict() == output
#
output = {'list': ['a', 'b'], 'l': ['a', 'b']}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['-l', 'a', 'b'])
assert config.value.dict() == 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 config.value.dict() == output
#
output = {'list': ['a', 'b'], 'l': ['a', 'b']}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['--list', 'a', 'b'])
assert config.value.dict() == output
#
output = {'list': ['a'], 'l': ['a']}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['-l', 'a'])
assert config.value.dict() == output
#
output = {'list': ['a', 'b'], 'l': ['a', 'b']}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['-l', 'a', 'b'])
assert config.value.dict() == output

View File

@ -1,273 +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))
properties = obj.property.get()
kwargs = {'help': option.doc().replace('%', '%%')}
if 'positional' in properties:
#if not 'mandatory' in properties:
# raise ValueError('"positional" argument must be "mandatory" too')
args = [option.path()]
#if option.requires():
kwargs['default'] = obj.value.get()
kwargs['nargs'] = '?'
else:
kwargs['dest'] = option.path()
kwargs['default'] = SUPPRESS
if self.fullpath and prefix:
name = prefix + '.' + name
args = [self._gen_argument(name, properties)]
if _forhelp and 'mandatory' in properties:
kwargs['required'] = True
if option.type() == 'bool':
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 obj.value.get() 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() == 'str':
pass
elif option.type() == 'int':
kwargs['type'] = int
elif option.issymlinkoption():
actions[option.name(follow_symlink=True)][0].insert(0, args[0])
continue
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

View File

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

View File

@ -0,0 +1,638 @@
# 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, RequirementError, LeadershipError
except ImportError:
Config = None
from tiramisu_api.error import PropertiesOptionError
RequirementError = 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(obj, properties, display):
def convert(choice):
if isinstance(choice, int):
return str(choice)
return choice
choices = [convert(choice) for choice in obj.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)
for tiramisu_key, tiramisu_value in config.value.dict(fullpath=True).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)
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.option.isfollower():
_setattr = self._setattr_follower
true_value = ','.join(value[1:])
else:
_setattr = self._setattr
true_value = value
if option.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.option.type() == 'choice':
values = option.value.list()
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.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:
if not value[0].isdecimal():
raise ValueError('index must be a number, not {}'.format(value[0]))
index = int(value[0])
if option.option.type() == 'boolean':
value = key not in self.list_force_no
elif option.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.doc()
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):
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.option
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.list(type='all'):
# do not display frozen option
if 'frozen' in obj.option.properties():
continue
if obj.option.isoptiondescription():
if _forhelp:
newgroup = self.add_argument_group(obj.option.path(), obj.option.description())
else:
newgroup = group
if prefix:
prefix_ = prefix + '.' + obj.option.name()
else:
prefix_ = obj.option.path()
self._config_to_argparser(_forhelp, obj, prefix_, newgroup, level + 1)
elif obj.option.type() == 'boolean' and not obj.option.issymlinkoption():
if not obj.option.isleader():
yield obj, False, None
yield obj, True, None
else:
yield obj, False, False
yield obj, False, True
yield obj, True, None
elif obj.option.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 obj, force_no, force_del in self._config_list(config, prefix, _forhelp, group, level):
option = obj.option
name = option.name()
if name.startswith(self.prefix_chars):
raise ValueError(_('name cannot startswith "{}"').format(self.prefix_chars))
if option.issymlinkoption():
symlink_name = option.name(follow_symlink=True)
if symlink_name in options_is_not_default:
options_is_not_default[symlink_name]['name'] = name
if symlink_name in actions:
for action in actions[symlink_name]:
action.add_argument(option)
continue
if force_del:
value = None
elif option.isleader():
value = obj.value.get()
leadership_len = len(value)
elif option.isfollower():
value = []
try:
for index in range(leadership_len):
value.append(self.config.option(obj.option.path(), index).value.get())
except:
value = None
else:
value = obj.value.get()
if self.fullpath and prefix:
name = prefix + '.' + name
if option.isfollower():
properties = obj.option.properties()
else:
properties = obj.property.get()
not_display = not option.isfollower() and not obj.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'] = obj.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 = obj.value.default()
if value not in [None, []]:
#kwargs['default'] = kwargs['const'] = option.default()
#kwargs['action'] = 'store_const'
kwargs['nargs'] = '?'
if not option.isfollower() and option.ismulti():
if _forhelp and 'mandatory' in properties:
kwargs['nargs'] = '+'
else:
kwargs['nargs'] = '*'
if option.isfollower() and not option.type() == 'boolean':
metavar = option.name().upper()
if option.issubmulti():
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(obj, 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(obj, 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 key in self.config.value.mandatory():
properties = self.config.option(key).option.properties()
if not self.config.option(key).option.isfollower():
if 'positional' not in properties:
if self.fullpath or '.' not in key:
name = key
else:
name = key.rsplit('.', 1)[1]
is_short_name = self._is_short_name(name, 'longargument' in self.config.option(key).property.get())
args = self._gen_argument(name, is_short_name)
else:
args = key
else:
if 'positional' not in properties:
args = self._gen_argument(key, False)
else:
args = key
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