Skip to content
18 changes: 11 additions & 7 deletions sphinx_needs/functions/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -432,7 +435,9 @@ def links_from_content(
This specification cares about the realisation of:

* :need:`CON_REQ_1`
* :need:`My speed <CON_REQ_1.speed>`
* :need:`My need <CON_REQ_2>`
* :need:`My need secound time <CON_REQ_2>`

.. spec:: Test spec 2
:id: CON_SPEC_2
Expand Down Expand Up @@ -466,13 +471,12 @@ 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:`[^<]*<([^>]+)>`"
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:
needs_config = NeedsSphinxConfig(app.config)
Expand Down
1 change: 1 addition & 0 deletions tests/doc_test/doc_df_links_from_content/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extensions = ["sphinx_needs"]
43 changes: 43 additions & 0 deletions tests/doc_test/doc_df_links_from_content/index.rst
Original file line number Diff line number Diff line change
@@ -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:`N_REF_1.1`
- :need:`N_REF_1.2`
- :need:`N_REF_1.2`
- :need:`n2 <N_REF_2>`
- :need:`n2 <N_REF_2>`
- :need:`n2.1 <N_REF_2.1>`
- :need:`n2.1 <N_REF_2.1>`
- :need:`n2.2 <N_REF_2.2>`
- :need:`n2.2 <N_REF_2.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
28 changes: 28 additions & 0 deletions tests/test_dynamic_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,34 @@ 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: <span class="links"><span><a class="reference internal" href="#N_REF_1" title="N_MAIN">N_REF_1</a>,'
' <a class="reference internal" href="#N_REF_1.1" title="N_MAIN">N_REF_1.1</a>,'
' <a class="reference internal" href="#N_REF_1.2" title="N_MAIN">N_REF_1.2</a>,'
' <a class="reference internal" href="#N_REF_2" title="N_MAIN">N_REF_2</a>,'
' <a class="reference internal" href="#N_REF_2.1" title="N_MAIN">N_REF_2.1</a>,'
' <a class="reference internal" href="#N_REF_2.2" title="N_MAIN">N_REF_2.2</a>'
in html
)


@pytest.mark.parametrize(
"test_app",
[
Expand Down