Compare commits

...

20 commits

Author SHA1 Message Date
antislash21
6d01f9ffee Create module directory and add setup.py to project. 2024-07-30 11:31:18 +02:00
Emmanuel Garette
52d96c2c51 updates test 2024-07-30 11:31:18 +02:00
Emmanuel Garette
15eb20ae11 positional arguments works well 2024-07-30 11:31:18 +02:00
Emmanuel Garette
c6f7dc8206 update README examples 2024-07-30 11:31:18 +02:00
Emmanuel Garette
295d7f912e symlink support 2024-07-30 11:31:18 +02:00
Emmanuel Garette
e61e064c41 add valid_mandatory to parse_args 2024-07-30 11:31:18 +02:00
Emmanuel Garette
6de205b260 valid mandatory arguments 2024-07-30 11:31:18 +02:00
Emmanuel Garette
694d9df04b can use tiramisu-cmdline-parser without tiramisu 2024-07-30 11:31:18 +02:00
Emmanuel Garette
e2951f1a21 remove 'add_arguments' function 2024-07-30 11:31:18 +02:00
Emmanuel Garette
195abb4299 manage help with requires option + add fullpath option 2024-07-30 11:31:18 +02:00
Emmanuel Garette
285d7316be add remote example 2024-07-30 11:31:18 +02:00
Emmanuel Garette
be4aba17dc works now with tiramisu-json-api 2024-07-30 11:31:18 +02:00
Emmanuel Garette
3372a42524 add test for README example 2024-07-30 11:31:18 +02:00
Emmanuel Garette
b026f9755c update README.md 2024-07-30 11:31:18 +02:00
Emmanuel Garette
ba1dc5d8c4 tiramisu_parser => tiramisu_cmdline_parser 2024-07-30 11:31:18 +02:00
Emmanuel Garette
d963750cf1 add AUTHORS and license 2024-07-30 11:31:18 +02:00
Emmanuel Garette
7e8613e6fb update README.md 2024-07-30 11:31:18 +02:00
Emmanuel Garette
4c528c0a17 add a simple example 2024-07-30 11:31:16 +02:00
Emmanuel Garette
82d8186062 first commit 2024-07-30 11:30:21 +02:00
GnunuX
2b3470af3b Initial commit 2024-07-30 11:30:20 +02:00
40 changed files with 939 additions and 1 deletions

5
AUTHORS Normal file
View file

@ -0,0 +1,5 @@
Authors
--------
Emmanuel Garette <gnunux@gnunux.info> lead developer

163
README.md
View file

@ -1,3 +1,166 @@
# tiramisu-cmdline-parser
Python3 parser for command-line options and arguments using Tiramisu engine.
# example
Let us start with a simple example
```python
#!/usr/bin/env python3
from tiramisu_cmdline_parser import TiramisuCmdlineParser
from tiramisu import IntOption, StrOption, BoolOption, ChoiceOption, \
SymLinkOption, OptionDescription, Config
# build a Config with:
# * a choice for select a sub argument (str, list, int)
choiceoption = ChoiceOption('cmd',
'choice the sub argument',
('str', 'list', 'int'),
properties=('mandatory',
'positional'))
# * a boolean to pass script in verbosity mode with argument -v --verbosity
booloption = BoolOption('verbosity',
'increase output verbosity',
default=False)
short_booloption = SymLinkOption('v', booloption)
# * a string option if cmd is 'str'
str_ = StrOption('str',
'string option',
properties=('mandatory',),
requires=[{'option': choiceoption,
'expected': 'str',
'action': 'disabled',
'inverse': True}])
# * a list of strings option if cmd is 'list'
list_ = StrOption('list',
'list string option',
multi=True,
properties=('mandatory',),
requires=[{'option': choiceoption,
'expected': 'list',
'action': 'disabled',
'inverse': True}])
# * an integer option if cmd is 'int'
int_ = IntOption('int',
'int option',
properties=('mandatory',),
requires=[{'option': choiceoption,
'expected': 'int',
'action': 'disabled',
'inverse': True}])
# Now build Config
config = Config(OptionDescription('root',
'root',
[choiceoption,
booloption,
short_booloption,
str_,
list_,
int_
]))
# initialise the parser
config.property.read_write()
parser = TiramisuCmdlineParser(config)
# parse arguments of current script
parser.parse_args()
# now, print the result
print('result:')
config.property.read_only()
for key, value in config.value.dict().items():
print('- {} ({}): {}'.format(key,
config.option(key).option.doc(),
value))
```
Let's print help:
```bash
[gnunux@localhost tiramisu-parser]$ python3 prog.py str -h
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
```
The positional argument 'cmd' is mandatory:
```bash
[gnunux@localhost tiramisu-parser]$ python3 prog.py
usage: prog.py [-h] [-v] --str STR --list LIST [LIST ...] --int INT
{str,list,int}
prog.py: error: the following arguments are required: cmd
```
If 'cmd' is 'str', --str become mandatory:
```bash
[gnunux@localhost tiramisu-parser]$ python3 prog.py str
usage: prog.py [-h] [-v] --str STR --list LIST [LIST ...] --int INT
{str,list,int}
prog.py: error: the following arguments are required: --str
```
If 'cmd' is 'str', cannot set --int argument:
```bash
[gnunux@localhost tiramisu-parser]$ python3 prog.py str --int 3
usage: prog.py [-h] [-v] --str STR --list LIST [LIST ...] --int INT
{str,list,int}
prog.py: error: unrecognized arguments: --int
```
With all mandatories arguments:
```bash
[gnunux@localhost tiramisu-parser]$ python3 prog.py str --str value
result:
- cmd (choice the sub argument): str
- verbosity (increase output verbosity): False
- v (increase output verbosity): False
- str (string option): value
```
```bash
[gnunux@localhost tiramisu-parser]$ python3 prog.py int --int 3
result:
- cmd (choice the sub argument): int
- verbosity (increase output verbosity): False
- v (increase output verbosity): False
- int (int option): 3
```
```bash
[gnunux@localhost tiramisu-parser]$ python3 prog.py list --list a b c
result:
- cmd (choice the sub argument): list
- verbosity (increase output verbosity): False
- v (increase output verbosity): False
- list (list string option): ['a', 'b', 'c']
```
Add --verbosity argument:
```bash
[gnunux@localhost tiramisu-parser]$ python3 prog.py list --list a b c -v
result:
- cmd (choice the sub argument): list
- verbosity (increase output verbosity): True
- v (increase output verbosity): True
- list (list string option): ['a', 'b', 'c']
[gnunux@localhost tiramisu-parser]$ python3 prog.py list --list a b c --verbosity
result:
- cmd (choice the sub argument): list
- verbosity (increase output verbosity): True
- v (increase output verbosity): True
- list (list string option): ['a', 'b', 'c']
```

178
examples/Hangman/example.py Normal file
View file

@ -0,0 +1,178 @@
#!/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_cmdline_parser import TiramisuCmdlineParser
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 = TiramisuCmdlineParser(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()

View file

@ -0,0 +1,32 @@
#!/usr/bin/env python3
"""Hangman example
"""
from json import loads, dumps
from urllib.request import urlopen, Request
from tiramisu_json_api import Config
from tiramisu_cmdline_parser import TiramisuCmdlineParser
class RemoteConfig(Config):
def __init__(self,
url):
json = loads(urlopen(url).read())
super().__init__(json)
self.url = url
def send_data(self,
updates):
request = Request(self.url, dumps(updates).encode(), method='POST')
return loads(urlopen(request).read())
def main():
config = RemoteConfig('http://localhost:8000')
parser = TiramisuCmdlineParser(config)
parser.parse_args()
config = parser.get_config()
print(config.value.dict())
if __name__ == "__main__":
main()

29
setup.py Normal file
View file

@ -0,0 +1,29 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from setuptools import setup, find_packages
import tiramisu_cmdline_parser
setup(
name='tiramisu_cmdline_parser',
version=tiramisu_cmdline_parser.__version__,
packages=find_packages(),
author="Emmanuel Garette & Tiramisu Team",
description="command-line parser using Tiramisu.",
long_description="""
Python3 parser for command-line options and arguments using Tiramisu engine.
""",
include_package_data=True,
url='https://framagit.org/tiramisu/tiramisu-cmdline-parser',
classifiers=[
"Programming Language :: Python",
"Development Status :: 1 - Planning",
"License :: LGPLv3",
"Natural Language :: French",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Topic :: Communications",
],
)

View file

@ -0,0 +1 @@
parser.add_argument("echo", help="echo the string you use here")

View file

@ -0,0 +1 @@
parser.add_arguments(StrOption('echo', 'echo the string you use here', properties=('mandatory', 'positional')))

View file

@ -0,0 +1 @@
parser.add_argument("echo", help="echo the string you use here", default='blah', nargs='?')

View file

@ -0,0 +1 @@
parser.add_arguments(StrOption('echo', 'echo the string you use here', properties=('mandatory', 'positional'), default='blah'))

View file

@ -0,0 +1,2 @@
parser.add_argument("square", help="display a square of a given number",
type=int)

View file

@ -0,0 +1 @@
parser.add_arguments(IntOption('square', 'display a square of a given number', properties=('mandatory', 'positional')))

View file

@ -0,0 +1 @@
parser.add_argument("echo", help="echo the string you use here", nargs='+')

View file

@ -0,0 +1 @@
parser.add_arguments(StrOption('echo', 'echo the string you use here', properties=('mandatory', 'positional'), multi=True))

View file

@ -0,0 +1 @@
parser.add_argument('--verbosity', help='increase output verbosity', action='store_true')

View file

@ -0,0 +1 @@
parser.add_arguments(BoolOption('verbosity', 'increase output verbosity', default=False))

View file

@ -0,0 +1 @@
parser.add_argument('--verbosity', help='increase output verbosity', action='store_false')

View file

@ -0,0 +1 @@
parser.add_arguments(BoolOption('verbosity', 'increase output verbosity', default=True))

View file

@ -0,0 +1,2 @@
parser.add_argument('--door', help='Door numbers', choices=['1', '2', '3'])

View file

@ -0,0 +1 @@
parser.add_arguments(ChoiceOption('door', 'Door numbers', ('1', '2', '3')))

View file

@ -0,0 +1,2 @@
parser.add_argument('--int', help='integer', type=int)

View file

@ -0,0 +1 @@
parser.add_arguments(IntOption('int', 'integer'))

View file

@ -0,0 +1,2 @@
parser.add_argument('--foo', help='foo help')

View file

@ -0,0 +1,2 @@
parser.add_arguments(StrOption('foo', 'foo help'))

View file

@ -0,0 +1,2 @@
parser.add_argument('--foo', help='foo help', nargs='*')

View file

@ -0,0 +1,2 @@
parser.add_arguments(StrOption('foo', 'foo help', multi=True))

View file

@ -0,0 +1,2 @@
parser.add_argument('--door', help='Door numbers', choices=[1, 2, 3])

View file

@ -0,0 +1 @@
parser.add_arguments(ChoiceOption('door', 'Door numbers', (1, 2, 3)))

View file

@ -0,0 +1,2 @@
parser.add_argument('--foo', help='foo help', default='default', nargs='?')

View file

@ -0,0 +1,2 @@
parser.add_arguments(StrOption('foo', 'foo help', 'default'))

View file

@ -0,0 +1 @@
parser.add_argument('-f', '--foo', help='foo help')

View file

@ -0,0 +1,3 @@
str_long = StrOption('foo', 'foo help')
str_short = SymLinkOption('f', str_long)
parser.add_arguments([str_long, str_short])

View file

@ -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')

View file

@ -0,0 +1 @@
parser.add_arguments([BoolOption('v', 'increase output verbosity', default=False), BoolOption('s', 'second argument', default=False)])

View file

@ -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')

View file

@ -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)])

View file

@ -0,0 +1,2 @@
parser.add_argument('-v', help='increase output verbosity', action='store_true')
parser.add_argument('-i', '--int', help='integer', type=int)

View file

@ -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)])

193
test/test_readme.py Normal file
View file

@ -0,0 +1,193 @@
from io import StringIO
from contextlib import redirect_stdout, redirect_stderr
from tiramisu_cmdline_parser import TiramisuCmdlineParser
from tiramisu import IntOption, StrOption, BoolOption, ChoiceOption, \
SymLinkOption, OptionDescription, Config
def get_config():
choiceoption = ChoiceOption('cmd',
'choice the sub argument',
('str', 'list', 'int', 'none'),
properties=('mandatory',
'positional'))
booloption = BoolOption('verbosity',
'increase output verbosity',
default=False)
short_booloption = SymLinkOption('v', booloption)
str_ = StrOption('str',
'string option',
properties=('mandatory',),
requires=[{'option': choiceoption,
'expected': 'str',
'action': 'disabled',
'inverse': True}])
list_ = StrOption('list',
'list string option',
multi=True,
properties=('mandatory',),
requires=[{'option': choiceoption,
'expected': 'list',
'action': 'disabled',
'inverse': True}])
int_ = IntOption('int',
'int option',
properties=('mandatory',),
requires=[{'option': choiceoption,
'expected': 'int',
'action': 'disabled',
'inverse': True}])
config = Config(OptionDescription('root',
'root',
[choiceoption,
booloption,
short_booloption,
str_,
list_,
int_
]))
config.property.read_write()
return config
def test_readme_help():
output = """usage: prog.py [-h] [-v] {str,list,int,none}
positional arguments:
{str,list,int,none} choice the sub argument
optional arguments:
-h, --help show this help message and exit
-v, --verbosity increase output verbosity
"""
parser = TiramisuCmdlineParser(get_config(), 'prog.py')
f = StringIO()
with redirect_stdout(f):
parser.print_help()
assert f.getvalue() == output
def test_readme_positional_mandatory():
output = """usage: prog.py [-h] [-v] {str,list,int,none}
prog.py: error: the following arguments are required: cmd
"""
parser = TiramisuCmdlineParser(get_config(), 'prog.py')
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args([])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_readme_mandatory():
output = """usage: prog.py [-h] [-v] --str STR {str,list,int,none}
prog.py: error: the following arguments are required: --str
"""
parser = TiramisuCmdlineParser(get_config(), 'prog.py')
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['str'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_readme_cross():
output = """usage: prog.py [-h] [-v] {str,list,int,none}
prog.py: error: unrecognized arguments: --int
"""
parser = TiramisuCmdlineParser(get_config(), 'prog.py')
f = StringIO()
with redirect_stderr(f):
try:
parser.parse_args(['none', '--int'])
except SystemExit as err:
assert str(err) == "2"
else:
raise Exception('must raises')
assert f.getvalue() == output
def test_readme_int():
output = {'cmd': 'int',
'int': 3,
'verbosity': False,
'v': False}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['int', '--int', '3'])
# assert config.value.dict() == output
def test_readme_int_verbosity():
output = {'cmd': 'int',
'int': 3,
'verbosity': True,
'v': True}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['int', '--int', '3', '--verbosity'])
assert config.value.dict() == output
def test_readme_int_verbosity_short():
output = {'cmd': 'int',
'int': 3,
'verbosity': True,
'v': True}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['int', '--int', '3', '-v'])
assert config.value.dict() == output
def test_readme_str():
output = {'cmd': 'str',
'str': 'value',
'verbosity': False,
'v': False}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['str', '--str', 'value'])
assert config.value.dict() == output
def test_readme_str_int():
output = {'cmd': 'str',
'str': '3',
'verbosity': False,
'v': False}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['str', '--str', '3'])
assert config.value.dict() == output
def test_readme_list():
output = {'cmd': 'list',
'list': ['a', 'b', 'c'],
'verbosity': False,
'v': False}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['list', '--list', 'a', 'b', 'c'])
assert config.value.dict() == output
def test_readme_list_uniq():
output = {'cmd': 'list',
'list': ['a'],
'verbosity': False,
'v': False}
config = get_config()
parser = TiramisuCmdlineParser(config, 'prog.py')
parser.parse_args(['list', '--list', 'a'])
assert config.value.dict() == output

View file

@ -0,0 +1,4 @@
from .tiramisu_cmdline_parser import TiramisuCmdlineParser
__version__ = "0.0.1"
__all__ = ('TiramisuCmdlineParser',)

View file

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