diff --git a/sphinx_needs/directives/needuml.py b/sphinx_needs/directives/needuml.py index f63555585..1a7d182f4 100644 --- a/sphinx_needs/directives/needuml.py +++ b/sphinx_needs/directives/needuml.py @@ -1,6 +1,5 @@ from __future__ import annotations -import copy import html import os import time @@ -19,11 +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.directives.needflow import make_entity_name -from sphinx_needs.filter_common import filter_needs -from sphinx_needs.roles.need_ref import transform_need_to_dict -from sphinx_needs.utils import add_doc, split_need_id +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 @@ -403,59 +400,37 @@ def flow(self, need_id: str) -> str: return need_uml - def ref(self, need_id: str, option: str = "", text: str = "") -> str: + def ref( + self, need_id: str, option: None | str = None, text: None | str = None + ) -> str: 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}'." ) - - target_need = self.needs[need_id_main] - - if option != "" and text != "": + if (option and text) and (not option and not text): raise NeedumlException( - "Jinja function ref requires 'option' or 'text', not both" + "Jinja function ref requires exactly one entry 'option' or 'text'" ) - if option != "" and option not in target_need: - raise NeedumlException( - f"Jinja function ref is called with undefined option '{option}' for need '{need_id}'." - ) + need_info = self.needs[need_id_main] if need_id_part: - if need_id_part not in target_need["parts"]: + if need_id_part not in need_info["parts"]: raise NeedumlException( f"Jinja function ref is called with undefined need_id part: '{need_id}'." ) - # We are changing the target_need, so we need a deepcopy, to not change the original data. - target_need = copy.deepcopy(target_need) - - # This algorithm is following the implementation of needref - target_need["id"] = need_id_part - target_need["title"] = target_need["parts"][need_id_part]["content"] - target_need["is_part"] = True - target_need["is_need"] = False - - # needref is using a dict of {str, str} to parse it later with user input. - # Here we do the same. - dict_need = transform_need_to_dict( - target_need - ) # Transform a dict in a dict of {str, str} - - link = calculate_link(self.app, target_need, self.fromdocname) - link_text: str = dict_need.get(option, "") if option != "" else text - # We have to strip the link_text, as leading and following whitespace character - # will break the plantuml rendering. - link_text = link_text.strip(" \t\n\r") - - need_uml = "[[{link}{seperator}{link_text}]]".format( - link=link, - seperator=" " if link_text != "" else "", - link_text=link_text, - ) + 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 763bc407a..3a39501ca 100644 --- a/tests/__snapshots__/test_needuml.ambr +++ b/tests/__snapshots__/test_needuml.ambr @@ -1253,7 +1253,7 @@ 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="title")}} + 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/doc_test/doc_needuml_jinja_func_ref/index.rst b/tests/doc_test/doc_needuml_jinja_func_ref/index.rst index 8013f8eca..e60aa7510 100644 --- a/tests/doc_test/doc_needuml_jinja_func_ref/index.rst +++ b/tests/doc_test/doc_needuml_jinja_func_ref/index.rst @@ -22,6 +22,6 @@ TEST DOCUMENT NEEDUML JINJA FUNCTION REF 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="title")}} + Marvel --> DC: {{ref("ST_001.np_id", option="content")}} DC -> Marvel: {{ref("ST_001.np_id", text="Different text to explain the story 2")}}