# 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 . 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): self._config.property.read_only() 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) 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 TiramisuCmdlineParser(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_subparsers(self, *args, **kwargs): raise NotImplementedError('do not use add_subparsers') def _config_to_argparser(self, _forhelp: bool): actions = {} for obj in self.config.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 _forhelp and '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 _forhelp and '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 add_arguments(self, tiramisu: Union[Config, Option, List[Option], OptionDescription], _forhelp: bool=False) -> 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 self._config_to_argparser(_forhelp) def parse_args(self, *args, **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.unrestraint.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)) return namespaces def format_usage(self, *args, _forhelp=False, **kwargs): if _forhelp: return super().format_usage(*args, **kwargs) help_formatter = TiramisuCmdlineParser(self.prog) help_formatter.add_arguments(self.config, _forhelp=True) return help_formatter.format_usage(*args, **kwargs, _forhelp=True) def format_help(self, *args, _forhelp=False, **kwargs): if _forhelp: return super().format_help(*args, **kwargs) help_formatter = TiramisuCmdlineParser(self.prog) help_formatter.add_arguments(self.config, _forhelp=True) return help_formatter.format_help(*args, **kwargs, _forhelp=True) def get_config(self): return self.config