diff --git a/src/rougail/__init__.py b/src/rougail/__init__.py index 265b5936d..c350ae236 100644 --- a/src/rougail/__init__.py +++ b/src/rougail/__init__.py @@ -24,10 +24,13 @@ details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . """ + from .__version__ import __version__ + try: from .convert import Rougail from .config import RougailConfig + __all__ = ("Rougail", "RougailConfig", "__version__") except ModuleNotFoundError as err: __all__ = ("__version__",) diff --git a/src/rougail/annotator/variable.py b/src/rougail/annotator/variable.py index eea7912e3..7874a2b52 100644 --- a/src/rougail/annotator/variable.py +++ b/src/rougail/annotator/variable.py @@ -162,6 +162,16 @@ class Annotator(Walk): # pylint: disable=R0903 ) if calculated_variable is not None: if calculated_variable.multi is None: + if ( + isinstance(calculated_variable.default, VariableCalculation) + and variable.path == calculated_variable.default.path + ): + msg = _( + 'the "{0}" default value is a calculation with itself'.format( + variable.path + ) + ) + raise DictConsistencyError(msg, 75, variable.xmlfiles) self._convert_variable_multi(calculated_variable) variable.multi = calc_multi_for_type_variable( variable, diff --git a/src/rougail/config/__init__.py b/src/rougail/config/__init__.py index fad6c80b5..55bd2a58a 100644 --- a/src/rougail/config/__init__.py +++ b/src/rougail/config/__init__.py @@ -34,7 +34,6 @@ from ..tiramisu import normalize_family from ..convert import RougailConvert from ..convert.object_model import get_convert_option_types -#import rougail.structural_commandline.object_model RENAMED = { "dictionaries_dir": "main_dictionaries", @@ -179,7 +178,7 @@ class _RougailConfig: yield f"{option.path()}: {option.value.get()}" def __repr__(self): - print(self.config) + self.generate_config() self.config.property.read_write() try: values = "\n".join(self.parse(self.config)) diff --git a/src/rougail/convert/convert.py b/src/rougail/convert/convert.py index d7baf4750..9e5415552 100644 --- a/src/rougail/convert/convert.py +++ b/src/rougail/convert/convert.py @@ -1164,4 +1164,3 @@ class RougailConvert(ParserVariable): tiramisu.write(output) # print(output) return output - diff --git a/src/rougail/convert/object_model.py b/src/rougail/convert/object_model.py index 7908a4956..a1c8758e4 100644 --- a/src/rougail/convert/object_model.py +++ b/src/rougail/convert/object_model.py @@ -28,7 +28,12 @@ from pydantic import ( ) import tiramisu from tiramisu.config import get_common_path -from ..utils import get_jinja_variable_to_param, calc_multi_for_type_variable, undefined, PROPERTY_ATTRIBUTE +from ..utils import ( + get_jinja_variable_to_param, + calc_multi_for_type_variable, + undefined, + PROPERTY_ATTRIBUTE, +) from ..i18n import _ from ..error import DictConsistencyError, VariableCalculationDependencyError from ..tiramisu import CONVERT_OPTION diff --git a/src/rougail/tiramisu.py b/src/rougail/tiramisu.py index 024ca6c9a..7aff74792 100644 --- a/src/rougail/tiramisu.py +++ b/src/rougail/tiramisu.py @@ -35,6 +35,7 @@ from importlib.util import ( from unicodedata import normalize, combining from jinja2 import StrictUndefined, DictLoader from jinja2.sandbox import SandboxedEnvironment +from re import findall from tiramisu import DynOptionDescription, calc_value, function_waiting_for_error from tiramisu.error import ( ValueWarning, @@ -134,6 +135,16 @@ CONVERT_OPTION = { } +def get_identifier_from_dynamic_family(true_name, name) -> str: + if true_name == "{{ identifier }}": + return name + regexp = true_name.replace("{{ identifier }}", "(.*)") + finded = findall(regexp, name) + if len(finded) != 1 or not finded[0]: + return None + return finded[0] + + def raise_carry_out_calculation_error(subconfig, *args, **kwargs): try: ori_raise_carry_out_calculation_error(subconfig, *args, **kwargs) @@ -423,3 +434,11 @@ class ConvertDynOptionDescription(DynOptionDescription): self.convert_identifier_to_path(self.get_identifiers(subconfig)[-1]), ) return display + + def name_could_conflict(self, dynchild, child): + return ( + get_identifier_from_dynamic_family( + dynchild.impl_getname(), child.impl_getname() + ) + is not None + ) diff --git a/src/rougail/user_datas.py b/src/rougail/user_datas.py index 1d5f86cbd..037746d9f 100644 --- a/src/rougail/user_datas.py +++ b/src/rougail/user_datas.py @@ -20,7 +20,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ from typing import List -from re import findall from tiramisu import Calculation, owners from tiramisu.error import ( @@ -31,7 +30,11 @@ from tiramisu.error import ( CancelParam, ) from .utils import undefined -from .tiramisu import normalize_family, CONVERT_OPTION +from .tiramisu import ( + normalize_family, + CONVERT_OPTION, + get_identifier_from_dynamic_family, +) from .error import DictConsistencyError from .i18n import _ @@ -41,13 +44,14 @@ class UserDatas: def __init__(self, config) -> None: self.config = config - def user_datas(self, - user_datas: List[dict], - *, - return_values_not_error=False, - user_datas_type: str="user_datas", - only_default: bool=False, - ): + def user_datas( + self, + user_datas: List[dict], + *, + return_values_not_error=False, + user_datas_type: str = "user_datas", + only_default: bool = False, + ): self.values = {} self.errors = [] self.warnings = [] @@ -126,7 +130,9 @@ class UserDatas: if not tconfig.isdynamic(only_self=True): # it's not a dynamic variable continue - identifier = self._get_identifier(tconfig.name(), name) + identifier = get_identifier_from_dynamic_family( + tconfig.name(), name + ) if identifier != normalize_family(identifier): msg = _( 'cannot load variable path "{0}", the identifier "{1}" is not valid in {2}' @@ -240,7 +246,8 @@ class UserDatas: else: if "source" in self.values[path]: option_without_index.information.set( - "loaded_from", _("loaded from {0}").format(self.values[path]["source"]) + "loaded_from", + _("loaded from {0}").format(self.values[path]["source"]), ) # value is correctly set, remove variable to the set if index is not None: @@ -256,15 +263,6 @@ class UserDatas: if not value_is_set: break - def _get_identifier(self, true_name, name) -> str: - if true_name == "{{ identifier }}": - return name - regexp = true_name.replace("{{ identifier }}", "(.*)") - finded = findall(regexp, name) - if len(finded) != 1 or not finded[0]: - return None - return finded[0] - def _display_value(self, option, value): if not self.show_secrets and option.type() == "password": return "*" * 10 diff --git a/tests/dictionaries/60_8family_dynamic_same_name_1/tiramisu/base.py b/tests/dictionaries/60_8family_dynamic_same_name_1/tiramisu/base.py new file mode 100644 index 000000000..cf3149867 --- /dev/null +++ b/tests/dictionaries/60_8family_dynamic_same_name_1/tiramisu/base.py @@ -0,0 +1,19 @@ +from tiramisu import * +from tiramisu.setting import ALLOWED_LEADER_PROPERTIES +from re import compile as re_compile +from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription +load_functions('../rougail-tests/funcs/test.py') +try: + groups.namespace +except: + groups.addgroup('namespace') +ALLOWED_LEADER_PROPERTIES.add("basic") +ALLOWED_LEADER_PROPERTIES.add("standard") +ALLOWED_LEADER_PROPERTIES.add("advanced") +option_4 = StrOption(name="address", doc="{{ identifier }} address", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['../rougail-tests/structures/60_8family_dynamic_same_name_1/rougail/00-base.yml'], 'type': 'string'}) +optiondescription_3 = OptionDescription(name="https_proxy", doc="{{ identifier }} Proxy", children=[option_4], properties=frozenset({"basic"}), informations={'ymlfiles': ['../rougail-tests/structures/60_8family_dynamic_same_name_1/rougail/00-base.yml']}) +option_6 = StrOption(name="address", doc="{{ identifier }} address", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['../rougail-tests/structures/60_8family_dynamic_same_name_1/rougail/00-base.yml'], 'type': 'string'}) +optiondescription_5 = ConvertDynOptionDescription(name="{{ identifier }}_proxy", doc="{{ identifier }} Proxy", identifiers=["HTTPS", "SOCKS"], children=[option_6], properties=frozenset({"basic"}), informations={'ymlfiles': ['../rougail-tests/structures/60_8family_dynamic_same_name_1/rougail/00-base.yml']}) +optiondescription_2 = OptionDescription(name="manual", doc="manual", children=[optiondescription_3, optiondescription_5], properties=frozenset({"basic"}), informations={'ymlfiles': ['../rougail-tests/structures/60_8family_dynamic_same_name_1/rougail/00-base.yml']}) +optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[optiondescription_2], properties=frozenset({"basic"}), informations={'ymlfiles': ['']}) +option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1]) diff --git a/tests/dictionaries/80unknown_default_variable_same_name_1/tmp/base.py b/tests/dictionaries/80unknown_default_variable_same_name_1/tmp/base.py new file mode 100644 index 000000000..9506c925d --- /dev/null +++ b/tests/dictionaries/80unknown_default_variable_same_name_1/tmp/base.py @@ -0,0 +1,19 @@ +from tiramisu import * +from tiramisu.setting import ALLOWED_LEADER_PROPERTIES +from re import compile as re_compile +from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription +load_functions('../rougail-tests/funcs/test.py') +try: + groups.namespace +except: + groups.addgroup('namespace') +ALLOWED_LEADER_PROPERTIES.add("basic") +ALLOWED_LEADER_PROPERTIES.add("standard") +ALLOWED_LEADER_PROPERTIES.add("advanced") +option_4 = StrOption(name="address", doc="{{ identifier }} address", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['tests/errors/80unknown_default_variable_same_name_1/rougail/00-base.yml'], 'type': 'string'}) +optiondescription_3 = OptionDescription(name="https_proxy", doc="{{ identifier }} Proxy", children=[option_4], properties=frozenset({"basic"}), informations={'ymlfiles': ['tests/errors/80unknown_default_variable_same_name_1/rougail/00-base.yml']}) +option_6 = StrOption(name="address", doc="{{ identifier }} address", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['tests/errors/80unknown_default_variable_same_name_1/rougail/00-base.yml'], 'type': 'string'}) +optiondescription_5 = ConvertDynOptionDescription(name="{{ identifier }}_proxy", doc="{{ identifier }} Proxy", identifiers=["HTTPS", "SOCKS"], children=[option_6], properties=frozenset({"basic"}), informations={'ymlfiles': ['tests/errors/80unknown_default_variable_same_name_1/rougail/00-base.yml']}) +optiondescription_2 = OptionDescription(name="manual", doc="manual", children=[optiondescription_3, optiondescription_5], properties=frozenset({"basic"}), informations={'ymlfiles': ['tests/errors/80unknown_default_variable_same_name_1/rougail/00-base.yml']}) +optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[optiondescription_2], properties=frozenset({"basic"}), informations={'ymlfiles': ['']}) +option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1]) diff --git a/tests/errors/24_0family_dynamic_calc_identifier_multi/force_namespace b/tests/errors/24_0family_dynamic_calc_identifier_multi/force_namespace new file mode 100644 index 000000000..e69de29bb diff --git a/tests/errors/24_0family_dynamic_calc_identifier_multi_2/force_namespace b/tests/errors/24_0family_dynamic_calc_identifier_multi_2/force_namespace new file mode 100644 index 000000000..e69de29bb diff --git a/tests/errors/24_1family_dynamic_calc/force_namespace b/tests/errors/24_1family_dynamic_calc/force_namespace new file mode 100644 index 000000000..e69de29bb diff --git a/tests/errors/24_1family_dynamic_calc_submulti/force_namespace b/tests/errors/24_1family_dynamic_calc_submulti/force_namespace new file mode 100644 index 000000000..e69de29bb diff --git a/tests/errors/80unknown_default_variable_inside_dynamic_family/errno_75 b/tests/errors/80unknown_default_variable_inside_dynamic_family/errno_75 new file mode 100644 index 000000000..e69de29bb diff --git a/tests/errors/80unknown_default_variable_inside_dynamic_family/force_namespace b/tests/errors/80unknown_default_variable_inside_dynamic_family/force_namespace new file mode 100644 index 000000000..e69de29bb diff --git a/tests/errors/80unknown_default_variable_inside_dynamic_family/rougail/00-base.yml b/tests/errors/80unknown_default_variable_inside_dynamic_family/rougail/00-base.yml new file mode 100644 index 000000000..38a0f15af --- /dev/null +++ b/tests/errors/80unknown_default_variable_inside_dynamic_family/rougail/00-base.yml @@ -0,0 +1,25 @@ +--- +version: 1.1 +manual: + + use_for_https: + description: Also use this proxy for HTTPS + default: true + + "{{ identifier }}_proxy": + description: "{{ identifier }} Proxy" + dynamic: + - HTTPS + - SOCKS + hidden: + variable: rougail.manual.use_for_https + + address: + description: "{{ identifier }} address" + default: + variable: rougail.manual.http_proxy.address + + port: + description: "{{ identifier }} port" + default: + variable: rougail.manual.http_proxy.port diff --git a/tests/errors/80unknown_duplicate_dynamic_family_name/errno_75 b/tests/errors/80unknown_duplicate_dynamic_family_name/errno_75 new file mode 100644 index 000000000..e69de29bb diff --git a/tests/errors/80unknown_duplicate_dynamic_family_name/force_namespace b/tests/errors/80unknown_duplicate_dynamic_family_name/force_namespace new file mode 100644 index 000000000..e69de29bb diff --git a/tests/errors/80unknown_duplicate_dynamic_family_name/rougail/00-base.yml b/tests/errors/80unknown_duplicate_dynamic_family_name/rougail/00-base.yml new file mode 100644 index 000000000..38a0f15af --- /dev/null +++ b/tests/errors/80unknown_duplicate_dynamic_family_name/rougail/00-base.yml @@ -0,0 +1,25 @@ +--- +version: 1.1 +manual: + + use_for_https: + description: Also use this proxy for HTTPS + default: true + + "{{ identifier }}_proxy": + description: "{{ identifier }} Proxy" + dynamic: + - HTTPS + - SOCKS + hidden: + variable: rougail.manual.use_for_https + + address: + description: "{{ identifier }} address" + default: + variable: rougail.manual.http_proxy.address + + port: + description: "{{ identifier }} port" + default: + variable: rougail.manual.http_proxy.port diff --git a/tests/test_1_flattener.py b/tests/test_1_flattener.py index 99d2fa2e8..55c906f22 100644 --- a/tests/test_1_flattener.py +++ b/tests/test_1_flattener.py @@ -49,7 +49,7 @@ test_ok -= excludes test_raise -= excludes # test_ok = ['04_5validators_multi3'] #test_ok = [] -# test_raise = ['22_0calculation_variable_leader_follower_multi'] +# test_raise = ['80unknown_default_variable_inside_dynamic_family'] #test_raise = [] test_multi = True #test_multi = False @@ -181,7 +181,7 @@ def test_dictionary_namespace(test_dir): assert getcwd() == ORI_DIR -def test_error_dictionary(test_dir_error): +def test_error_dictionary_namespace(test_dir_error): assert getcwd() == ORI_DIR test_dir_ = join(errors_dirs, test_dir_error) errno = [] @@ -202,3 +202,28 @@ def test_error_dictionary(test_dir_error): if isdir(tiramisu_tmp_dir): rmtree(tiramisu_tmp_dir) assert getcwd() == ORI_DIR + + +def test_error_dictionary(test_dir_error): + assert getcwd() == ORI_DIR + test_dir_ = join(errors_dirs, test_dir_error) + if isfile(join(test_dir_, 'force_namespace')): + return + errno = [] + rougailconfig = RougailConfig.copy() + rougailconfig['main_namespace'] = None + eolobj = load_rougail_object(test_dir_, rougailconfig, namespace=False) + if eolobj is None: + return + for i in listdir(test_dir_): + if i.startswith('errno_'): + errno.append(int(i.split('_')[1])) + if not errno: + errno.append(0) + with raises(DictConsistencyError) as err: + save(test_dir_, eolobj, error=True) + assert err.value.errno in errno, f'expected errno: {errno}, errno: {err.value.errno}, msg: {err}' + tiramisu_tmp_dir = dirname(get_tiramisu_filename(test_dir_, 'tmp', False, True)) + if isdir(tiramisu_tmp_dir): + rmtree(tiramisu_tmp_dir) + assert getcwd() == ORI_DIR diff --git a/tests/test_others.py b/tests/test_others.py index f411406c3..38c7a8306 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -3,6 +3,7 @@ import logging from rougail import Rougail, RougailConfig from rougail.error import DictConsistencyError +from tiramisu.error import ConflictError from rougail_tests.utils import config_to_dict @@ -95,3 +96,74 @@ def test_namespace(): 'ns2.var2': 'NS2', 'ns3.var1': 'NS3', 'ns3.var2': 'NS3'} +# +# +#def test_duplicate_0(): +# rougailconfig = RougailConfig.copy() +# rougailconfig['main_namespace'] = None +# rougailconfig['dictionaries_dir'] = ['tests/duplicates/0/'] +# eolobj = Rougail(rougailconfig=rougailconfig) +# cfg = eolobj.run() +# cfg.value.get() +# cfg.option('od.od_val1').value.get() +# cfg.option('od.od_val2').value.get() + + +def test_duplicate_1(): + rougailconfig = RougailConfig.copy() + rougailconfig['main_namespace'] = None + rougailconfig['dictionaries_dir'] = ['tests/duplicates/1/'] + eolobj = Rougail(rougailconfig=rougailconfig) + cfg = eolobj.run() + with raises(ConflictError): + cfg.value.get() + with raises(ConflictError): + cfg.option('od.od_val').value.get() + + +def test_duplicate_2(): + rougailconfig = RougailConfig.copy() + rougailconfig['main_namespace'] = None + rougailconfig['dictionaries_dir'] = ['tests/duplicates/2/'] + eolobj = Rougail(rougailconfig=rougailconfig) + cfg = eolobj.run() + with raises(ConflictError): + cfg.value.get() + with raises(ConflictError): + cfg.option('od.od_val').value.get() + + +def test_duplicate_3(): + rougailconfig = RougailConfig.copy() + rougailconfig['main_namespace'] = None + rougailconfig['dictionaries_dir'] = ['tests/duplicates/3/'] + eolobj = Rougail(rougailconfig=rougailconfig) + cfg = eolobj.run() + with raises(ConflictError): + cfg.value.get() + with raises(ConflictError): + cfg.option('od.od_val').value.get() +# +# +#def test_duplicate_4(): +# rougailconfig = RougailConfig.copy() +# rougailconfig['main_namespace'] = None +# rougailconfig['dictionaries_dir'] = ['tests/duplicates/4/'] +# eolobj = Rougail(rougailconfig=rougailconfig) +# cfg = eolobj.run() +# with raises(ConflictError): +# cfg.value.get() +# with raises(ConflictError): +# cfg.option('od.od_val').value.get() + + +def test_duplicate_5(): + rougailconfig = RougailConfig.copy() + rougailconfig['main_namespace'] = None + rougailconfig['dictionaries_dir'] = ['tests/duplicates/5/'] + eolobj = Rougail(rougailconfig=rougailconfig) + cfg = eolobj.run() + with raises(ConflictError): + cfg.value.get() + with raises(ConflictError): + cfg.option('od.od_val').value.get()