From e10c51ee3b97e35420385e7f5218ad21314c7e75 Mon Sep 17 00:00:00 2001 From: PhilipPartsch Date: Mon, 15 Dec 2025 22:20:38 +0100 Subject: [PATCH 1/7] =?UTF-8?q?=E2=9C=A8=20Add=20tests=20for=20dynamic=20f?= =?UTF-8?q?unction=20links=5Ffrom=5Fcontent=20with=20named=20need=5Fparts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../doc_df_links_from_content/conf.py | 1 + .../doc_df_links_from_content/index.rst | 43 +++++++++++++++++++ tests/test_dynamic_functions.py | 20 +++++++++ 3 files changed, 64 insertions(+) create mode 100644 tests/doc_test/doc_df_links_from_content/conf.py create mode 100644 tests/doc_test/doc_df_links_from_content/index.rst diff --git a/tests/doc_test/doc_df_links_from_content/conf.py b/tests/doc_test/doc_df_links_from_content/conf.py new file mode 100644 index 000000000..64873d961 --- /dev/null +++ b/tests/doc_test/doc_df_links_from_content/conf.py @@ -0,0 +1 @@ +extensions = ["sphinx_needs"] diff --git a/tests/doc_test/doc_df_links_from_content/index.rst b/tests/doc_test/doc_df_links_from_content/index.rst new file mode 100644 index 000000000..bbf259eb2 --- /dev/null +++ b/tests/doc_test/doc_df_links_from_content/index.rst @@ -0,0 +1,43 @@ +DYNAMIC FUNCTION: links_from_content +==================================== + +.. need:: Reference Need 1 + :id: N_REF_1 + + :np:`(1) sub 1` + :np:`(2) sub 1` + +.. need:: Reference Need 2 + :id: N_REF_2 + + :np:`(1) sub 1` + :np:`(2) sub 1` + +.. need:: Main Need + :id: N_MAIN + :links: [[links_from_content()]] + + This need links to + + - :need:`N_REF_1` + - :need:`N_REF_1` + - :need:`N_REF_1.1` + - :need:`_REF_1.1` + - :need:`N_REF_1.2` + - :need:`N_REF_1.2` + - :need:`n2 ` + - :need:`n2 ` + - :need:`n2.1 ` + - :need:`n2.1 ` + - :need:`n2.2 ` + - :need:`n2.2 ` + + using dynamic function links_from_content. + + Expected links: + - 1x N_REF_1 + - 1x N_REF_1.1 + - 1x N_REF_1.2 + - 1x N_REF_2 + - 1x N_REF_2.1 + - 1x N_REF_2.2 diff --git a/tests/test_dynamic_functions.py b/tests/test_dynamic_functions.py index 65f2e9f28..9e0c9006d 100644 --- a/tests/test_dynamic_functions.py +++ b/tests/test_dynamic_functions.py @@ -205,6 +205,26 @@ def test_doc_df_linked_values(test_app): assert "all_awesome" in html +@pytest.mark.parametrize( + "test_app", + [ + { + "buildername": "html", + "srcdir": "doc_test/doc_df_links_from_content", + "no_plantuml": True, + } + ], + indirect=True, +) +def test_doc_df_links_from_content_values(test_app): + app = test_app + app.build() + warnings = strip_colors(app._warning.getvalue()).splitlines() + assert warnings == [] + html = Path(app.outdir, "index.html").read_text() + assert "links outgoing: N_REF_1, N_REF_1.1, N_REF_1.2, N_REF_2, N_REF_2.1, N_REF_2.2" in html + + @pytest.mark.parametrize( "test_app", [ From ad453e226bf9eda9eb9e3bf149a25b96089d2a64 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 21:22:51 +0000 Subject: [PATCH 2/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_dynamic_functions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_dynamic_functions.py b/tests/test_dynamic_functions.py index 9e0c9006d..8c5d1b5da 100644 --- a/tests/test_dynamic_functions.py +++ b/tests/test_dynamic_functions.py @@ -222,7 +222,10 @@ def test_doc_df_links_from_content_values(test_app): warnings = strip_colors(app._warning.getvalue()).splitlines() assert warnings == [] html = Path(app.outdir, "index.html").read_text() - assert "links outgoing: N_REF_1, N_REF_1.1, N_REF_1.2, N_REF_2, N_REF_2.1, N_REF_2.2" in html + assert ( + "links outgoing: N_REF_1, N_REF_1.1, N_REF_1.2, N_REF_2, N_REF_2.1, N_REF_2.2" + in html + ) @pytest.mark.parametrize( From 964f0d194589a2265f57e348db43763228b6fc51 Mon Sep 17 00:00:00 2001 From: PhilipPartsch Date: Mon, 15 Dec 2025 22:31:43 +0100 Subject: [PATCH 3/7] =?UTF-8?q?=E2=9C=A8=20Enhance=20links=5Ffrom=5Fconten?= =?UTF-8?q?t=20to=20support=20need=5Fpart=20and=20improve=20link=20extract?= =?UTF-8?q?ion=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sphinx_needs/functions/common.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/sphinx_needs/functions/common.py b/sphinx_needs/functions/common.py index 73f4e3d2c..9f579cccf 100644 --- a/sphinx_needs/functions/common.py +++ b/sphinx_needs/functions/common.py @@ -416,12 +416,15 @@ def links_from_content( All need-links set by using ``:need:`NEED_ID``` get extracted. Same links are only added once. + need_part shall be supported. Example: .. req:: Requirement 1 :id: CON_REQ_1 + * :np:`(speed) An acceleration of 200 m/s² or much much more` + .. req:: Requirement 2 :id: CON_REQ_2 @@ -432,7 +435,9 @@ def links_from_content( This specification cares about the realisation of: * :need:`CON_REQ_1` + * :need:`My speed ` * :need:`My need ` + * :need:`My need secound time ` .. spec:: Test spec 2 :id: CON_SPEC_2 @@ -466,13 +471,9 @@ def links_from_content( if source_need is None: raise ValueError("No need found for links_from_content") - links = re.findall(r":need:`(\w+)`|:need:`.+\<(.+)\>`", source_need["content"]) - raw_links = [] - for link in links: - if link[0] and link[0] not in raw_links: - raw_links.append(link[0]) - elif link[1] and link[0] not in raw_links: - raw_links.append(link[1]) + pattern = r":need:`(\w+(?:\.\w+)?)`|:need:`[^<]*<([^>]+)>`" + links = re.findall(pattern, source_need["content"]) + raw_links = list(dict.fromkeys(links)) if filter: needs_config = NeedsSphinxConfig(app.config) From 110787ff9e3123c8928d65a9bfed73448d01b94f Mon Sep 17 00:00:00 2001 From: PhilipPartsch Date: Mon, 15 Dec 2025 22:42:48 +0100 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=90=9B=20Fix=20typo=20in=20expected?= =?UTF-8?q?=20links=20for=20N=5FREF=5F1.1=20in=20index.rst?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/doc_test/doc_df_links_from_content/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/doc_test/doc_df_links_from_content/index.rst b/tests/doc_test/doc_df_links_from_content/index.rst index bbf259eb2..d4328cf13 100644 --- a/tests/doc_test/doc_df_links_from_content/index.rst +++ b/tests/doc_test/doc_df_links_from_content/index.rst @@ -22,7 +22,7 @@ DYNAMIC FUNCTION: links_from_content - :need:`N_REF_1` - :need:`N_REF_1` - :need:`N_REF_1.1` - - :need:`_REF_1.1` + - :need:`N_REF_1.1` - :need:`N_REF_1.2` - :need:`N_REF_1.2` - :need:`n2 ` From 63293a06ff22707f146da8797445371c3757475f Mon Sep 17 00:00:00 2001 From: PhilipPartsch Date: Mon, 15 Dec 2025 23:09:48 +0100 Subject: [PATCH 5/7] =?UTF-8?q?=E2=9C=A8=20Refactor=20links=5Ffrom=5Fconte?= =?UTF-8?q?nt=20to=20improve=20link=20extraction=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sphinx_needs/functions/common.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sphinx_needs/functions/common.py b/sphinx_needs/functions/common.py index 9f579cccf..6079d7326 100644 --- a/sphinx_needs/functions/common.py +++ b/sphinx_needs/functions/common.py @@ -472,7 +472,10 @@ def links_from_content( raise ValueError("No need found for links_from_content") pattern = r":need:`(\w+(?:\.\w+)?)`|:need:`[^<]*<([^>]+)>`" - links = re.findall(pattern, source_need["content"]) + list_of_tuple = re.findall(pattern, source_need["content"]) + + links = [m[0] or m[1] for m in list_of_tuple] + raw_links = list(dict.fromkeys(links)) if filter: From 728e5b7acef194d84fba6754e02ed36ef192e39b Mon Sep 17 00:00:00 2001 From: PhilipPartsch Date: Mon, 15 Dec 2025 23:27:38 +0100 Subject: [PATCH 6/7] =?UTF-8?q?=E2=9C=A8=20Update=20test=5Fdoc=5Fdf=5Flink?= =?UTF-8?q?s=5Ffrom=5Fcontent=5Fvalues=20to=20assert=20HTML=20output=20wit?= =?UTF-8?q?h=20formatted=20links?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_dynamic_functions.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_dynamic_functions.py b/tests/test_dynamic_functions.py index 8c5d1b5da..7787b4e7d 100644 --- a/tests/test_dynamic_functions.py +++ b/tests/test_dynamic_functions.py @@ -223,7 +223,12 @@ def test_doc_df_links_from_content_values(test_app): assert warnings == [] html = Path(app.outdir, "index.html").read_text() assert ( - "links outgoing: N_REF_1, N_REF_1.1, N_REF_1.2, N_REF_2, N_REF_2.1, N_REF_2.2" + 'links outgoing: N_REF_1,'+ + ' N_REF_1.1,'+ + ' N_REF_1.2,'+ + ' N_REF_2,'+ + ' N_REF_2.1,'+ + ' N_REF_2.2' in html ) From f32f014ccd8ea51c8cbecea3cabc95b03bde7275 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 22:27:55 +0000 Subject: [PATCH 7/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_dynamic_functions.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_dynamic_functions.py b/tests/test_dynamic_functions.py index 7787b4e7d..4855feebf 100644 --- a/tests/test_dynamic_functions.py +++ b/tests/test_dynamic_functions.py @@ -223,11 +223,11 @@ def test_doc_df_links_from_content_values(test_app): assert warnings == [] html = Path(app.outdir, "index.html").read_text() assert ( - 'links outgoing: N_REF_1,'+ - ' N_REF_1.1,'+ - ' N_REF_1.2,'+ - ' N_REF_2,'+ - ' N_REF_2.1,'+ + 'links outgoing: N_REF_1,' + ' N_REF_1.1,' + ' N_REF_1.2,' + ' N_REF_2,' + ' N_REF_2.1,' ' N_REF_2.2' in html )