From 226a509d5321f4c23c0535b54522883398983eb3 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Thu, 29 Nov 2018 22:10:08 +0100 Subject: [PATCH] first commit --- examples/Hangman/hangman.py | 179 ++++++++++++++++++ test/data/compare/10_positional/argparse.py | 1 + test/data/compare/10_positional/tiramisu.py | 1 + .../compare/10_positional_default/argparse.py | 1 + .../compare/10_positional_default/tiramisu.py | 1 + .../compare/10_positional_int/argparse.py | 2 + .../compare/10_positional_int/tiramisu.py | 1 + .../compare/10_positional_list/argparse.py | 1 + .../compare/10_positional_list/tiramisu.py | 1 + test/data/compare/20_bool/argparse.py | 1 + test/data/compare/20_bool/tiramisu.py | 1 + test/data/compare/20_bool_true/argparse.py | 1 + test/data/compare/20_bool_true/tiramisu.py | 1 + test/data/compare/20_choice/argparse.py | 2 + test/data/compare/20_choice/tiramisu.py | 1 + test/data/compare/20_int/argparse.py | 2 + test/data/compare/20_int/tiramisu.py | 1 + test/data/compare/20_string/argparse.py | 2 + test/data/compare/20_string/tiramisu.py | 2 + test/data/compare/20_string_list/argparse.py | 2 + test/data/compare/20_string_list/tiramisu.py | 2 + test/data/compare/30_choice_int/argparse.py | 2 + test/data/compare/30_choice_int/tiramisu.py | 1 + .../compare/30_string_default/argparse.py | 2 + .../compare/30_string_default/tiramisu.py | 2 + test/data/compare/30_string_short/argparse.py | 1 + test/data/compare/30_string_short/tiramisu.py | 3 + test/data/compare/40_multi_bool/argparse.py | 2 + test/data/compare/40_multi_bool/tiramisu.py | 1 + .../40_positional_optional/argparse.py | 3 + .../40_positional_optional/tiramisu.py | 3 + test/data/compare/40_short_long/argparse.py | 2 + test/data/compare/40_short_long/tiramisu.py | 4 + test/test_simple.py | 145 ++++++++++++++ tiramisu_parser.py | 149 +++++++++++++++ 35 files changed, 526 insertions(+) create mode 100644 examples/Hangman/hangman.py create mode 100644 test/data/compare/10_positional/argparse.py create mode 100644 test/data/compare/10_positional/tiramisu.py create mode 100644 test/data/compare/10_positional_default/argparse.py create mode 100644 test/data/compare/10_positional_default/tiramisu.py create mode 100644 test/data/compare/10_positional_int/argparse.py create mode 100644 test/data/compare/10_positional_int/tiramisu.py create mode 100644 test/data/compare/10_positional_list/argparse.py create mode 100644 test/data/compare/10_positional_list/tiramisu.py create mode 100644 test/data/compare/20_bool/argparse.py create mode 100644 test/data/compare/20_bool/tiramisu.py create mode 100644 test/data/compare/20_bool_true/argparse.py create mode 100644 test/data/compare/20_bool_true/tiramisu.py create mode 100644 test/data/compare/20_choice/argparse.py create mode 100644 test/data/compare/20_choice/tiramisu.py create mode 100644 test/data/compare/20_int/argparse.py create mode 100644 test/data/compare/20_int/tiramisu.py create mode 100644 test/data/compare/20_string/argparse.py create mode 100644 test/data/compare/20_string/tiramisu.py create mode 100644 test/data/compare/20_string_list/argparse.py create mode 100644 test/data/compare/20_string_list/tiramisu.py create mode 100644 test/data/compare/30_choice_int/argparse.py create mode 100644 test/data/compare/30_choice_int/tiramisu.py create mode 100644 test/data/compare/30_string_default/argparse.py create mode 100644 test/data/compare/30_string_default/tiramisu.py create mode 100644 test/data/compare/30_string_short/argparse.py create mode 100644 test/data/compare/30_string_short/tiramisu.py create mode 100644 test/data/compare/40_multi_bool/argparse.py create mode 100644 test/data/compare/40_multi_bool/tiramisu.py create mode 100644 test/data/compare/40_positional_optional/argparse.py create mode 100644 test/data/compare/40_positional_optional/tiramisu.py create mode 100644 test/data/compare/40_short_long/argparse.py create mode 100644 test/data/compare/40_short_long/tiramisu.py create mode 100644 test/test_simple.py create mode 100644 tiramisu_parser.py diff --git a/examples/Hangman/hangman.py b/examples/Hangman/hangman.py new file mode 100644 index 0000000..9536b49 --- /dev/null +++ b/examples/Hangman/hangman.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +"""Hangman example +""" + + +from random import choice +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_parser import TiramisuParser + + +LANG = 'fr_FR' +DICT_FILE = '/usr/share/myspell/{}.dic'.format(LANG) +WORD_REGEXP = re.compile(r'^[a-z]{7,12}$') +PROPOSALS_LEN = 27 +NB_PROPOSALS = 6 + + +def remove_accent(word): + """remove all accent""" + word = unicodedata.normalize('NFD', word) + return word.encode('ascii', 'ignore').decode() + + +def get_random_word(): + """get line randomly in myspell file + """ + with open(DICT_FILE, 'r') as file_content: + word = choice(file_content.readlines()).strip() + if word.endswith('/S.'): + word = word[:-3] + if word.endswith('/X.'): + word = word[:-3] + if word.endswith('/F.'): + word = word[:-3] + if word.endswith('/a0p+') or word.endswith('/d0p+') or word.endswith('/a3p+'): + word = word[:-5] + if not WORD_REGEXP.search(remove_accent(word)): + return get_random_word() + return word + + +def display_uncomplete_word(word, *proposals): + """display response with proposals + """ + if display_proposals_left(display_misses(word, *proposals)) == 0: + return word + display = ['-'] * len(word) + for idx, char in enumerate(remove_accent(word)): + if char in proposals: + display[idx] = word[idx] + return ''.join(display) + + +def display_misses(word, *proposals): + """display all proposals + """ + ret = list(set(proposals) - set(list(remove_accent(word)))) + if None in ret: + ret.remove(None) + ret.sort() + return ' '.join(ret) + + +def validate_misses(misses): + if display_proposals_left(misses) == 0: + raise ValueError('No more guest possible') + + +def display_proposals_left(misses): + if not misses: + return NB_PROPOSALS + return max(NB_PROPOSALS - len(misses.split(' ')), 0) + + +def display_proposal(word, *proposals): + if display_uncomplete_word(word, *proposals) == word: + return False + return display_proposals_left(display_misses(word, *proposals)) != 0 + + +class ProposalOption(RegexpOption): + __slots__ = tuple() + _regexp = re.compile(r'^[a-z]$') + _display_name = 'proposal' + + +def main(): + options = [] + proposal = None + word = UnicodeOption('word', + 'Word', + properties=('hidden', 'force_store_value'), + callback=get_random_word) + proposals = [ParamOption(word)] + for idx in range(PROPOSALS_LEN): + requires = [{'option': 'self', + 'expected': None, + 'action': 'hidden', + 'inverse': True}] + if proposal is not None: + display = BoolOption('display{}'.format(idx), + 'Display {}'.format(idx), + properties=('hidden',), + callback=display_proposal, + callback_params=Params(tuple(proposals))) + options.append(display) + requires.append({'option': proposal, + 'expected': None, + 'action': 'disabled'}) + requires.append({'option': display, + 'expected': False, + 'action': 'disabled'}) + + proposal = ProposalOption('guess{}'.format(idx), + 'Guess {}'.format(idx), + requires=requires, + properties=('positional',)) + #FIXME maximum recursion ... + #if proposals: + # proposal.impl_add_consistency('not_equal', proposals[0]) + + proposals.append(ParamOption(proposal, True)) + options.append(proposal) + # + proposal_word = UnicodeOption('proposal_word', + 'Word', + properties=('frozen',), + callback=display_uncomplete_word, + callback_params=Params(tuple(proposals))) + misses = UnicodeOption('misses', + 'Misses', + properties=('frozen',), + callback=display_misses, + callback_params=Params(tuple(proposals)), + validator=validate_misses) + proposals_left = IntOption('proposals_left', + 'Proposals left', + properties=('frozen',), + callback=display_proposals_left, + callback_params=Params(ParamOption(misses))) + #descr = OptionDescription('proposals', + # 'Suggesting letters', + # options) + storage_type.set('sqlite3') + config = Config(OptionDescription('root', 'root', [word, proposal_word, misses, proposals_left] + options), persistent=True, session_id='hangman') + parser = TiramisuParser() + parser.add_arguments(config) + try: + parser.parse_args() + except ValueError: + pass + config = parser.get_config() + filename = '{}/tiramisu.db'.format(SETTING.dir_database) + lost = False + for name in ['proposal_word', 'misses', 'proposals_left']: + option = config.option(name) + try: + value = option.value.get() + print('{}: {}'.format(option.option.doc(), value)) + except ValueError as err: + lost = True + err.prefix = '' + print(option.option.doc(), str(err)) + if isfile(filename): + unlink(filename) + if not lost and \ + config.option('proposal_word').value.get() == config.forcepermissive.option('word').value.get(): + print('You win') + if isfile(filename): + unlink(filename) + +if __name__ == "__main__": + main() diff --git a/test/data/compare/10_positional/argparse.py b/test/data/compare/10_positional/argparse.py new file mode 100644 index 0000000..27c3da2 --- /dev/null +++ b/test/data/compare/10_positional/argparse.py @@ -0,0 +1 @@ +parser.add_argument("echo", help="echo the string you use here") diff --git a/test/data/compare/10_positional/tiramisu.py b/test/data/compare/10_positional/tiramisu.py new file mode 100644 index 0000000..3a82982 --- /dev/null +++ b/test/data/compare/10_positional/tiramisu.py @@ -0,0 +1 @@ +parser.add_arguments(StrOption('echo', 'echo the string you use here', properties=('mandatory', 'positional'))) diff --git a/test/data/compare/10_positional_default/argparse.py b/test/data/compare/10_positional_default/argparse.py new file mode 100644 index 0000000..380ee0c --- /dev/null +++ b/test/data/compare/10_positional_default/argparse.py @@ -0,0 +1 @@ +parser.add_argument("echo", help="echo the string you use here", default='blah', nargs='?') diff --git a/test/data/compare/10_positional_default/tiramisu.py b/test/data/compare/10_positional_default/tiramisu.py new file mode 100644 index 0000000..e7fc833 --- /dev/null +++ b/test/data/compare/10_positional_default/tiramisu.py @@ -0,0 +1 @@ +parser.add_arguments(StrOption('echo', 'echo the string you use here', properties=('mandatory', 'positional'), default='blah')) diff --git a/test/data/compare/10_positional_int/argparse.py b/test/data/compare/10_positional_int/argparse.py new file mode 100644 index 0000000..b7775ec --- /dev/null +++ b/test/data/compare/10_positional_int/argparse.py @@ -0,0 +1,2 @@ +parser.add_argument("square", help="display a square of a given number", + type=int) diff --git a/test/data/compare/10_positional_int/tiramisu.py b/test/data/compare/10_positional_int/tiramisu.py new file mode 100644 index 0000000..284b48e --- /dev/null +++ b/test/data/compare/10_positional_int/tiramisu.py @@ -0,0 +1 @@ +parser.add_arguments(IntOption('square', 'display a square of a given number', properties=('mandatory', 'positional'))) diff --git a/test/data/compare/10_positional_list/argparse.py b/test/data/compare/10_positional_list/argparse.py new file mode 100644 index 0000000..3406abb --- /dev/null +++ b/test/data/compare/10_positional_list/argparse.py @@ -0,0 +1 @@ +parser.add_argument("echo", help="echo the string you use here", nargs='+') diff --git a/test/data/compare/10_positional_list/tiramisu.py b/test/data/compare/10_positional_list/tiramisu.py new file mode 100644 index 0000000..edc8363 --- /dev/null +++ b/test/data/compare/10_positional_list/tiramisu.py @@ -0,0 +1 @@ +parser.add_arguments(StrOption('echo', 'echo the string you use here', properties=('mandatory', 'positional'), multi=True)) diff --git a/test/data/compare/20_bool/argparse.py b/test/data/compare/20_bool/argparse.py new file mode 100644 index 0000000..2370218 --- /dev/null +++ b/test/data/compare/20_bool/argparse.py @@ -0,0 +1 @@ +parser.add_argument('--verbosity', help='increase output verbosity', action='store_true') diff --git a/test/data/compare/20_bool/tiramisu.py b/test/data/compare/20_bool/tiramisu.py new file mode 100644 index 0000000..127d7d1 --- /dev/null +++ b/test/data/compare/20_bool/tiramisu.py @@ -0,0 +1 @@ +parser.add_arguments(BoolOption('verbosity', 'increase output verbosity', default=False)) diff --git a/test/data/compare/20_bool_true/argparse.py b/test/data/compare/20_bool_true/argparse.py new file mode 100644 index 0000000..4963ce7 --- /dev/null +++ b/test/data/compare/20_bool_true/argparse.py @@ -0,0 +1 @@ +parser.add_argument('--verbosity', help='increase output verbosity', action='store_false') diff --git a/test/data/compare/20_bool_true/tiramisu.py b/test/data/compare/20_bool_true/tiramisu.py new file mode 100644 index 0000000..16fbdc5 --- /dev/null +++ b/test/data/compare/20_bool_true/tiramisu.py @@ -0,0 +1 @@ +parser.add_arguments(BoolOption('verbosity', 'increase output verbosity', default=True)) diff --git a/test/data/compare/20_choice/argparse.py b/test/data/compare/20_choice/argparse.py new file mode 100644 index 0000000..0833112 --- /dev/null +++ b/test/data/compare/20_choice/argparse.py @@ -0,0 +1,2 @@ +parser.add_argument('--door', help='Door numbers', choices=['1', '2', '3']) + diff --git a/test/data/compare/20_choice/tiramisu.py b/test/data/compare/20_choice/tiramisu.py new file mode 100644 index 0000000..c0f3690 --- /dev/null +++ b/test/data/compare/20_choice/tiramisu.py @@ -0,0 +1 @@ +parser.add_arguments(ChoiceOption('door', 'Door numbers', ('1', '2', '3'))) diff --git a/test/data/compare/20_int/argparse.py b/test/data/compare/20_int/argparse.py new file mode 100644 index 0000000..338ee40 --- /dev/null +++ b/test/data/compare/20_int/argparse.py @@ -0,0 +1,2 @@ +parser.add_argument('--int', help='integer', type=int) + diff --git a/test/data/compare/20_int/tiramisu.py b/test/data/compare/20_int/tiramisu.py new file mode 100644 index 0000000..fe2f791 --- /dev/null +++ b/test/data/compare/20_int/tiramisu.py @@ -0,0 +1 @@ +parser.add_arguments(IntOption('int', 'integer')) diff --git a/test/data/compare/20_string/argparse.py b/test/data/compare/20_string/argparse.py new file mode 100644 index 0000000..4d4768c --- /dev/null +++ b/test/data/compare/20_string/argparse.py @@ -0,0 +1,2 @@ +parser.add_argument('--foo', help='foo help') + diff --git a/test/data/compare/20_string/tiramisu.py b/test/data/compare/20_string/tiramisu.py new file mode 100644 index 0000000..bf6912f --- /dev/null +++ b/test/data/compare/20_string/tiramisu.py @@ -0,0 +1,2 @@ +parser.add_arguments(StrOption('foo', 'foo help')) + diff --git a/test/data/compare/20_string_list/argparse.py b/test/data/compare/20_string_list/argparse.py new file mode 100644 index 0000000..658bb29 --- /dev/null +++ b/test/data/compare/20_string_list/argparse.py @@ -0,0 +1,2 @@ +parser.add_argument('--foo', help='foo help', nargs='*') + diff --git a/test/data/compare/20_string_list/tiramisu.py b/test/data/compare/20_string_list/tiramisu.py new file mode 100644 index 0000000..5274597 --- /dev/null +++ b/test/data/compare/20_string_list/tiramisu.py @@ -0,0 +1,2 @@ +parser.add_arguments(StrOption('foo', 'foo help', multi=True)) + diff --git a/test/data/compare/30_choice_int/argparse.py b/test/data/compare/30_choice_int/argparse.py new file mode 100644 index 0000000..a58d7a0 --- /dev/null +++ b/test/data/compare/30_choice_int/argparse.py @@ -0,0 +1,2 @@ +parser.add_argument('--door', help='Door numbers', choices=[1, 2, 3]) + diff --git a/test/data/compare/30_choice_int/tiramisu.py b/test/data/compare/30_choice_int/tiramisu.py new file mode 100644 index 0000000..058d682 --- /dev/null +++ b/test/data/compare/30_choice_int/tiramisu.py @@ -0,0 +1 @@ +parser.add_arguments(ChoiceOption('door', 'Door numbers', (1, 2, 3))) diff --git a/test/data/compare/30_string_default/argparse.py b/test/data/compare/30_string_default/argparse.py new file mode 100644 index 0000000..90a8aa2 --- /dev/null +++ b/test/data/compare/30_string_default/argparse.py @@ -0,0 +1,2 @@ +parser.add_argument('--foo', help='foo help', default='default', nargs='?') + diff --git a/test/data/compare/30_string_default/tiramisu.py b/test/data/compare/30_string_default/tiramisu.py new file mode 100644 index 0000000..12fccd2 --- /dev/null +++ b/test/data/compare/30_string_default/tiramisu.py @@ -0,0 +1,2 @@ +parser.add_arguments(StrOption('foo', 'foo help', 'default')) + diff --git a/test/data/compare/30_string_short/argparse.py b/test/data/compare/30_string_short/argparse.py new file mode 100644 index 0000000..699b426 --- /dev/null +++ b/test/data/compare/30_string_short/argparse.py @@ -0,0 +1 @@ +parser.add_argument('-f', '--foo', help='foo help') diff --git a/test/data/compare/30_string_short/tiramisu.py b/test/data/compare/30_string_short/tiramisu.py new file mode 100644 index 0000000..36cbd35 --- /dev/null +++ b/test/data/compare/30_string_short/tiramisu.py @@ -0,0 +1,3 @@ +str_long = StrOption('foo', 'foo help') +str_short = SymLinkOption('f', str_long) +parser.add_arguments([str_long, str_short]) diff --git a/test/data/compare/40_multi_bool/argparse.py b/test/data/compare/40_multi_bool/argparse.py new file mode 100644 index 0000000..718cbc0 --- /dev/null +++ b/test/data/compare/40_multi_bool/argparse.py @@ -0,0 +1,2 @@ +parser.add_argument('-v', help='increase output verbosity', action='store_true') +parser.add_argument('-s', help='second argument', action='store_true') diff --git a/test/data/compare/40_multi_bool/tiramisu.py b/test/data/compare/40_multi_bool/tiramisu.py new file mode 100644 index 0000000..47d4921 --- /dev/null +++ b/test/data/compare/40_multi_bool/tiramisu.py @@ -0,0 +1 @@ +parser.add_arguments([BoolOption('v', 'increase output verbosity', default=False), BoolOption('s', 'second argument', default=False)]) diff --git a/test/data/compare/40_positional_optional/argparse.py b/test/data/compare/40_positional_optional/argparse.py new file mode 100644 index 0000000..abe38ec --- /dev/null +++ b/test/data/compare/40_positional_optional/argparse.py @@ -0,0 +1,3 @@ +parser.add_argument("echo", help="echo the string you use here") +parser.add_argument('--verbosity', help='increase output verbosity', action='store_true') + diff --git a/test/data/compare/40_positional_optional/tiramisu.py b/test/data/compare/40_positional_optional/tiramisu.py new file mode 100644 index 0000000..4713d6c --- /dev/null +++ b/test/data/compare/40_positional_optional/tiramisu.py @@ -0,0 +1,3 @@ +parser.add_arguments([StrOption('echo', 'echo the string you use here', properties=('mandatory', 'positional')), + BoolOption('verbosity', 'increase output verbosity', default=False)]) + diff --git a/test/data/compare/40_short_long/argparse.py b/test/data/compare/40_short_long/argparse.py new file mode 100644 index 0000000..16b6a46 --- /dev/null +++ b/test/data/compare/40_short_long/argparse.py @@ -0,0 +1,2 @@ +parser.add_argument('-v', help='increase output verbosity', action='store_true') +parser.add_argument('-i', '--int', help='integer', type=int) diff --git a/test/data/compare/40_short_long/tiramisu.py b/test/data/compare/40_short_long/tiramisu.py new file mode 100644 index 0000000..7dcb6aa --- /dev/null +++ b/test/data/compare/40_short_long/tiramisu.py @@ -0,0 +1,4 @@ +int_long = IntOption('int', 'integer') +parser.add_arguments([BoolOption('v', 'increase output verbosity', default=False), + int_long, + SymLinkOption('i', int_long)]) diff --git a/test/test_simple.py b/test/test_simple.py new file mode 100644 index 0000000..b4391aa --- /dev/null +++ b/test/test_simple.py @@ -0,0 +1,145 @@ +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 TiramisuParser +from tiramisu_parser import TiramisuParser + + +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/50_conditional_disable'] + + +@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: + tiramparser = TiramisuParser('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(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 diff --git a/tiramisu_parser.py b/tiramisu_parser.py new file mode 100644 index 0000000..6d96fee --- /dev/null +++ b/tiramisu_parser.py @@ -0,0 +1,149 @@ +from typing import Union, List +from argparse import ArgumentParser, Namespace, SUPPRESS +from tiramisu import Option, OptionDescription, Config, BoolOption, StrOption, IntOption, \ + ChoiceOption, SymLinkOption +from tiramisu.error import PropertiesOptionError + + +class TiramisuNamespace(Namespace): + def _populate(self): + for tiramisu_key, tiramisu_value in self._config.value.dict().items(): + option = self._config.option(tiramisu_key) + if not isinstance(option.option.get(), SymLinkOption): + if tiramisu_value == [] and option.option.ismulti() and option.owner.isdefault(): + tiramisu_value = None + super().__setattr__(tiramisu_key, tiramisu_value) + + 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._config.property.read_only() + self._populate() + self._config.property.read_write() + return super().__getattribute__(key) + + +class TiramisuParser(ArgumentParser): + def __init__(self, *args, **kwargs): + self.config = None + super().__init__(*args, **kwargs) + + 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.unrestraint.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 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, tiramisu: Union[Config, Option, List[Option], OptionDescription]) -> None: + if not isinstance(tiramisu, Config): + if not isinstance(tiramisu, OptionDescription): + if isinstance(tiramisu, Option): + tiramisu = [tiramisu] + tiramisu = OptionDescription('root', 'root', tiramisu) + tiramisu = Config(tiramisu) + self.config = tiramisu + actions = {} + for obj in tiramisu.unrestraint.option.list(): + if obj.option.properties(only_raises=True) or 'frozen' in obj.option.properties(): + continue + option = obj.option + tiramisu_option = option.get() + 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(), + 'default': SUPPRESS} + if 'positional' in properties: + #if not 'mandatory' in properties: + # raise ValueError('"positional" argument must be "mandatory" too') + args = [name] + if option.requires(): + kwargs['nargs'] = '?' + else: + if len(name) == 1 and 'longargument' not in properties: + args = [self.prefix_chars + name] + else: + args = [self.prefix_chars * 2 + name] + if 'mandatory' in properties: + kwargs['required'] = True + if isinstance(tiramisu_option, BoolOption): + 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 option.default() is False: + action = 'store_true' + else: + action = 'store_false' + kwargs['action'] = action + else: + if option.default() not in [None, []]: + #kwargs['default'] = kwargs['const'] = option.default() + #kwargs['action'] = 'store_const' + kwargs['nargs'] = '?' + if option.ismulti(): + if 'mandatory' in properties: + kwargs['nargs'] = '+' + else: + kwargs['nargs'] = '*' + if isinstance(tiramisu_option, StrOption): + pass + elif isinstance(tiramisu_option, IntOption): + kwargs['type'] = int + elif isinstance(tiramisu_option, SymLinkOption): + tiramisu_option = tiramisu_option.impl_getopt() + actions[tiramisu_option.impl_getname()][0].insert(0, args[0]) + continue + elif isinstance(tiramisu_option, ChoiceOption): + kwargs['choices'] = obj.value.list() + else: + pass + #raise NotImplementedError('not supported yet') + actions[option.name()] = (args, kwargs) + for args, kwargs in actions.values(): + super().add_argument(*args, **kwargs) + + def parse_args(self, *args, **kwargs): + kwargs['namespace'] = TiramisuNamespace(self.config) + try: + namespaces = super().parse_args(*args, **kwargs) + except PropertiesOptionError as err: + # import traceback + # traceback.print_exc() + if err.proptype == ('mandatory',): + self.error('the following arguments are required: {}'.format(err._option_bag.option.impl_getname())) + else: + self.error('unexpected error: {}'.format(err)) + del namespaces.__dict__['_config'] + return namespaces + + def get_config(self): + return self.config