diff --git a/ifex/models/ifex/ifex_parser.py b/ifex/models/ifex/ifex_parser.py index 0912f49..b8e9219 100644 --- a/ifex/models/ifex/ifex_parser.py +++ b/ifex/models/ifex/ifex_parser.py @@ -15,7 +15,53 @@ import yaml, dacite from typing import Dict, Any -from ifex.models.ifex.ifex_ast import AST +from ifex.models.ifex.ifex_ast import AST, Namespace + + +def _expand_dot_namespace(ns: Namespace) -> Namespace: + """Expand a dot-separated namespace name into nested Namespace objects. + + ``name: A.B.C`` is equivalent to nesting:: + + name: A + namespaces: + - name: B + namespaces: + - name: C + ...content... + + Children are expanded recursively before wrapping. + """ + expanded_children = [_expand_dot_namespace(child) for child in (ns.namespaces or [])] + + parts = ns.name.split(".") + if len(parts) == 1: + ns.namespaces = expanded_children + return ns + + # Innermost namespace carries all content from the dot-path namespace + inner = Namespace( + name=parts[-1], + description=ns.description, + major_version=ns.major_version, + minor_version=ns.minor_version, + version_label=ns.version_label, + events=ns.events, + methods=ns.methods, + typedefs=ns.typedefs, + includes=ns.includes, + structs=ns.structs, + enumerations=ns.enumerations, + properties=ns.properties, + namespaces=expanded_children, + interface=ns.interface, + ) + + # Wrap in empty intermediate layers + for part in reversed(parts[:-1]): + inner = Namespace(name=part, namespaces=[inner]) + + return inner def read_yaml_file(filename) -> str: @@ -53,6 +99,7 @@ def get_ast_from_yaml_file(filename: str) -> AST: #cfg = dacite.Config(strict=True) # Fail if unknown keys in dict cfg = dacite.Config(strict=False) # Fail if unknown keys in dict ast = dacite.from_dict(data_class=AST, data=yaml_dict, config=cfg) + ast.namespaces = [_expand_dot_namespace(ns) for ns in (ast.namespaces or [])] return ast except dacite.UnexpectedDataError as e: print(f"ERROR: Read error resulting from {filename}: {e}") diff --git a/tests/test_dot_path_namespace/dotpath.ifex b/tests/test_dot_path_namespace/dotpath.ifex new file mode 100644 index 0000000..d2bd186 --- /dev/null +++ b/tests/test_dot_path_namespace/dotpath.ifex @@ -0,0 +1,6 @@ +namespaces: + - name: com.example.vehicle + description: Vehicle namespace + methods: + - name: start + description: Start the vehicle diff --git a/tests/test_dot_path_namespace/nested.ifex b/tests/test_dot_path_namespace/nested.ifex new file mode 100644 index 0000000..f9d01c7 --- /dev/null +++ b/tests/test_dot_path_namespace/nested.ifex @@ -0,0 +1,10 @@ +namespaces: + - name: com + namespaces: + - name: example + namespaces: + - name: vehicle + description: Vehicle namespace + methods: + - name: start + description: Start the vehicle diff --git a/tests/test_dot_path_namespace/test_dot_path_namespace.py b/tests/test_dot_path_namespace/test_dot_path_namespace.py new file mode 100644 index 0000000..d877528 --- /dev/null +++ b/tests/test_dot_path_namespace/test_dot_path_namespace.py @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: MPL-2.0 + +"""Tests that a dot-separated namespace name expands to the same AST as +explicitly nested namespaces.""" + +from pathlib import Path +from ifex.models.ifex.ifex_parser import get_ast_from_yaml_file +from ifex.models.ifex.ifex_ast import Namespace + +HERE = Path(__file__).resolve().parent + + +def _names(ns: Namespace) -> list: + """Return a nested list of namespace names mirroring the tree structure.""" + return [ns.name, [_names(child) for child in ns.namespaces]] + + +def test_dot_path_expands_to_nested(): + dot = get_ast_from_yaml_file(str(HERE / "dotpath.ifex")) + nested = get_ast_from_yaml_file(str(HERE / "nested.ifex")) + + # Both should produce exactly one top-level namespace: com + assert len(dot.namespaces) == 1 + assert len(nested.namespaces) == 1 + + # The namespace tree shapes must match + assert _names(dot.namespaces[0]) == _names(nested.namespaces[0]) + + # The innermost namespace (vehicle) must carry the method + dot_vehicle = dot.namespaces[0].namespaces[0].namespaces[0] + assert dot_vehicle.name == "vehicle" + assert dot_vehicle.description == "Vehicle namespace" + assert len(dot_vehicle.methods) == 1 + assert dot_vehicle.methods[0].name == "start" + + +def test_plain_namespace_name_unchanged(): + """A namespace without dots must not be modified.""" + nested = get_ast_from_yaml_file(str(HERE / "nested.ifex")) + assert nested.namespaces[0].name == "com" + + +def test_partial_dot_path(): + """A two-part dot path produces exactly two namespace levels.""" + from ifex.models.ifex.ifex_parser import _expand_dot_namespace + from ifex.models.ifex.ifex_ast import Namespace, Method + + ns = Namespace(name="A.B", methods=[Method(name="foo")]) + expanded = _expand_dot_namespace(ns) + + assert expanded.name == "A" + assert len(expanded.namespaces) == 1 + assert expanded.namespaces[0].name == "B" + assert expanded.namespaces[0].methods[0].name == "foo"