diff --git a/sphinx_needs/directives/needuml.py b/sphinx_needs/directives/needuml.py index c57b1d0fe..1a7d182f4 100644 --- a/sphinx_needs/directives/needuml.py +++ b/sphinx_needs/directives/needuml.py @@ -18,7 +18,9 @@ from sphinx_needs.directives.needflow._plantuml import make_entity_name from sphinx_needs.filter_common import filter_needs_view from sphinx_needs.logging import log_warning -from sphinx_needs.utils import add_doc, logger +from sphinx_needs.roles.need_part import create_need_from_part +from sphinx_needs.roles.need_ref import value_to_string +from sphinx_needs.utils import add_doc, logger, split_need_id if TYPE_CHECKING: from sphinxcontrib.plantuml import plantuml @@ -401,24 +403,34 @@ def flow(self, need_id: str) -> str: def ref( self, need_id: str, option: None | str = None, text: None | str = None ) -> str: - if need_id not in self.needs: + need_id_main, need_id_part = split_need_id(need_id) + + if need_id_main not in self.needs: raise NeedumlException( - f"Jinja function ref is called with undefined need_id: '{need_id}'." + f"Jinja function ref is called with undefined need_id: '{need_id_main}'." ) if (option and text) and (not option and not text): raise NeedumlException( "Jinja function ref requires exactly one entry 'option' or 'text'" ) - need_info = self.needs[need_id] - link = calculate_link(self.app, need_info, self.fromdocname) + need_info = self.needs[need_id_main] - need_uml = "[[{link} {content}]]".format( - link=link, - content=need_info.get(option, "") if option else text, - ) + if need_id_part: + if need_id_part not in need_info["parts"]: + raise NeedumlException( + f"Jinja function ref is called with undefined need_id part: '{need_id}'." + ) + need_info = create_need_from_part( + need_info, need_info["parts"][need_id_part] + ) - return need_uml + link = calculate_link(self.app, need_info, self.fromdocname) + link_text = ( + value_to_string(need_info.get(option, "")) if option else str(text or "") + ).strip() + + return f"[[{link}{' ' if link_text else ''}{link_text}]]" def filter(self, filter_string: str) -> list[NeedsInfoType]: """ diff --git a/sphinx_needs/roles/need_ref.py b/sphinx_needs/roles/need_ref.py index bd0453a0b..59fd26471 100644 --- a/sphinx_needs/roles/need_ref.py +++ b/sphinx_needs/roles/need_ref.py @@ -2,6 +2,7 @@ import contextlib from collections.abc import Iterable +from typing import Any from docutils import nodes from sphinx.application import Sphinx @@ -38,19 +39,23 @@ def transform_need_to_dict(need: NeedsInfoType) -> dict[str, str]: dict_need = {} for element, value in need.items(): - if isinstance(value, str): - # As string are iterable, we have to handle strings first. - dict_need[element] = value - elif isinstance(value, dict): - dict_need[element] = ";".join([str(i) for i in value.items()]) - elif isinstance(value, (Iterable, list, tuple)): - dict_need[element] = ";".join([str(i) for i in value]) - else: - dict_need[element] = str(value) + dict_need[element] = value_to_string(value) return dict_need +def value_to_string(value: Any) -> str: + if isinstance(value, str): + # As string are iterable, we have to handle strings first. + return value + elif isinstance(value, dict): + return ";".join([str(i) for i in value.items()]) + elif isinstance(value, (Iterable, list, tuple)): + return ";".join([str(i) for i in value]) + + return str(value) + + def process_need_ref( app: Sphinx, doctree: nodes.document, diff --git a/tests/__snapshots__/test_needuml.ambr b/tests/__snapshots__/test_needuml.ambr index 2007a5a90..3a39501ca 100644 --- a/tests/__snapshots__/test_needuml.ambr +++ b/tests/__snapshots__/test_needuml.ambr @@ -1251,12 +1251,22 @@ 'content': ''' DC -> Marvel: {{ref("ST_001", option="title")}} Marvel --> DC: {{ref("ST_002", text="Different text to explain the story")}} + + DC -> Marvel: {{ref("ST_001.np_id", option="id")}} + Marvel --> DC: {{ref("ST_001.np_id", option="content")}} + + DC -> Marvel: {{ref("ST_001.np_id", text="Different text to explain the story 2")}} ''', 'content_calculated': ''' @startuml DC -> Marvel: [[../index.html#ST_001 Test story]] Marvel --> DC: [[../index.html#ST_002 Different text to explain the story]] + + DC -> Marvel: [[../index.html#ST_001.np_id np_id]] + Marvel --> DC: [[../index.html#ST_001.np_id np_content]] + + DC -> Marvel: [[../index.html#ST_001.np_id Different text to explain the story 2]] @enduml ''', @@ -1266,7 +1276,7 @@ }), 'is_arch': False, 'key': None, - 'lineno': 17, + 'lineno': 19, 'save': None, 'scale': '', 'target_id': 'needuml-index-0', diff --git a/tests/doc_test/doc_needuml_jinja_func_ref/index.rst b/tests/doc_test/doc_needuml_jinja_func_ref/index.rst index 5e7efd80d..e60aa7510 100644 --- a/tests/doc_test/doc_needuml_jinja_func_ref/index.rst +++ b/tests/doc_test/doc_needuml_jinja_func_ref/index.rst @@ -6,10 +6,12 @@ TEST DOCUMENT NEEDUML JINJA FUNCTION REF Some content + :np:`(np_id) np_content` + .. story:: Another story :id: ST_002 - Different conftent content + Different content content .. int:: Test needuml jinja func ref :id: INT_001 @@ -18,3 +20,8 @@ TEST DOCUMENT NEEDUML JINJA FUNCTION REF DC -> Marvel: {{ref("ST_001", option="title")}} Marvel --> DC: {{ref("ST_002", text="Different text to explain the story")}} + + DC -> Marvel: {{ref("ST_001.np_id", option="id")}} + Marvel --> DC: {{ref("ST_001.np_id", option="content")}} + + DC -> Marvel: {{ref("ST_001.np_id", text="Different text to explain the story 2")}} diff --git a/tests/test_needuml.py b/tests/test_needuml.py index a0ebfe699..04b6c5ea3 100644 --- a/tests/test_needuml.py +++ b/tests/test_needuml.py @@ -277,6 +277,14 @@ def test_needuml_jinja_func_ref(test_app, snapshot): assert "Marvel: [[../index.html#ST_001 Test story]]" in html assert "DC: [[../index.html#ST_002 Different text to explain the story]]" in html + assert "Marvel: [[../index.html#ST_001.np_id np_id]]" in html + assert "DC: [[../index.html#ST_001.np_id np_content]]" in html + + assert ( + "Marvel: [[../index.html#ST_001.np_id Different text to explain the story 2]]" + in html + ) + srcdir = Path(app.srcdir) out_dir = srcdir / "_build"