Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion ifex/models/ifex/ifex_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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}")
Expand Down
6 changes: 6 additions & 0 deletions tests/test_dot_path_namespace/dotpath.ifex
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespaces:
- name: com.example.vehicle
description: Vehicle namespace
methods:
- name: start
description: Start the vehicle
10 changes: 10 additions & 0 deletions tests/test_dot_path_namespace/nested.ifex
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespaces:
- name: com
namespaces:
- name: example
namespaces:
- name: vehicle
description: Vehicle namespace
methods:
- name: start
description: Start the vehicle
54 changes: 54 additions & 0 deletions tests/test_dot_path_namespace/test_dot_path_namespace.py
Original file line number Diff line number Diff line change
@@ -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"