diff --git a/sphinx_needs/data.py b/sphinx_needs/data.py index a36e362e3..af9f2b3dd 100644 --- a/sphinx_needs/data.py +++ b/sphinx_needs/data.py @@ -200,6 +200,11 @@ class NeedsInfoType(NeedsBaseDataType): query: str url: str + only_expressions: list[str] + """List of parent only expressions, from outer to inner. + Note this key is only present if there are any only expressions. + """ + # Note there are also: # - dynamic default options that can be set by needs_extra_options config # - dynamic global options that can be set by needs_global_options config diff --git a/sphinx_needs/directives/need.py b/sphinx_needs/directives/need.py index 7863b161f..dae4cb1d7 100644 --- a/sphinx_needs/directives/need.py +++ b/sphinx_needs/directives/need.py @@ -7,6 +7,7 @@ from docutils import nodes from docutils.parsers.rst.states import RSTState, RSTStateMachine from docutils.statemachine import StringList +from sphinx import addnodes from sphinx.addnodes import desc_name, desc_signature from sphinx.application import Sphinx from sphinx.environment import BuildEnvironment @@ -309,7 +310,8 @@ def purge_needs(app: Sphinx, env: BuildEnvironment, docname: str) -> None: def analyse_need_locations(app: Sphinx, doctree: nodes.document) -> None: """Determine the location of each need in the doctree, - relative to its parent section(s) and need(s). + relative to its parent section(s) and need(s), + and also note any parent ``only`` expressions. This data is added to the need's data stored in the Sphinx environment, so that it can be used in tables and filters. @@ -361,6 +363,16 @@ def analyse_need_locations(app: Sphinx, doctree: nodes.document) -> None: need_info["parent_needs"] = parent_needs need_info["parent_need"] = parent_needs[0] + # find any parent only expressions, and note them on the need data item + expressions = [] + parent: nodes.Element = need_node + while parent := getattr(parent, "parent", None): # type: ignore + if isinstance(parent, addnodes.only): # noqa: SIM102 + if expr := parent.get("expr"): + expressions.append(expr) + if expressions: + need_info["only_expressions"] = expressions[::-1] + if need_node.get("hidden"): hidden_needs.append(need_node) diff --git a/tests/doc_test/doc_build_only/conf.py b/tests/doc_test/doc_build_only/conf.py new file mode 100644 index 000000000..dbe2ad57f --- /dev/null +++ b/tests/doc_test/doc_build_only/conf.py @@ -0,0 +1,4 @@ +version = "1" +extensions = ["sphinx_needs"] + +needs_build_json = True diff --git a/tests/doc_test/doc_build_only/index.rst b/tests/doc_test/doc_build_only/index.rst new file mode 100644 index 000000000..e66a47df0 --- /dev/null +++ b/tests/doc_test/doc_build_only/index.rst @@ -0,0 +1,20 @@ +Title +===== + +.. req:: Requirement 1 + :id: REQ_1 + +.. only:: html + + .. req:: Requirement 2 + :id: REQ_2 + + .. req:: Requirement 3 + :id: REQ_3 + +.. only:: not something + + .. only:: other + + .. req:: Requirement 4 + :id: REQ_4 diff --git a/tests/test_doc_build_only.py b/tests/test_doc_build_only.py new file mode 100644 index 000000000..7973ad5e5 --- /dev/null +++ b/tests/test_doc_build_only.py @@ -0,0 +1,27 @@ +import json +from pathlib import Path + +import pytest + + +@pytest.mark.parametrize( + "test_app", + [{"buildername": "html", "srcdir": "doc_test/doc_build_only", "no_plantuml": True}], + indirect=True, +) +def test_doc_build_only(test_app): + app = test_app + + app.build() + assert app._warning.getvalue() == "" + + needs = json.loads(Path(app.outdir, "needs.json").read_text("utf8")) + id_to_expr = { + k: v.get("only_expressions") for k, v in needs["versions"]["1"]["needs"].items() + } + assert id_to_expr == { + "REQ_1": None, + "REQ_2": ["html"], + "REQ_3": ["html"], + "REQ_4": ["not something", "other"], + }