diff --git a/sphinx_needs/directives/need.py b/sphinx_needs/directives/need.py index b5b8a3f6b..667aedd23 100644 --- a/sphinx_needs/directives/need.py +++ b/sphinx_needs/directives/need.py @@ -340,6 +340,9 @@ def analyse_need_locations(app: Sphinx, doctree: nodes.document) -> None: if need_node.get("hidden"): hidden_needs.append(need_node) + if need_node_excluded_by_only_directive(env, need_node): + hidden_needs.append(need_node) + # now we have gathered all the information we need, # we can remove the hidden needs from the doctree for need_node in hidden_needs: @@ -356,6 +359,16 @@ def previous_sibling(node: nodes.Node) -> Optional[nodes.Node]: return node.parent[i - 1] if i > 0 else None # type: ignore +def need_node_excluded_by_only_directive(env: BuildEnvironment, node: nodes.Node) -> bool: + """Return True if the node is under an "only" directive that shall be excluded given to the current tags""" + if hasattr(node.parent, "tagname") and node.parent.tagname == "only": + # note that we only check for a direct parent, + # maybe we should look recursively until we reach the root of the document + only_tags = node.parent.attributes.get("expr", "") + return env.app.builder.tags.eval_condition(only_tags) + return False + + @profile("NEEDS_POST_PROCESS") @measure_time("need_post_process") def post_process_needs_data(app: Sphinx) -> None: diff --git a/tests/doc_test/doc_directive_only/conf.py b/tests/doc_test/doc_directive_only/conf.py new file mode 100644 index 000000000..bd0af22b1 --- /dev/null +++ b/tests/doc_test/doc_directive_only/conf.py @@ -0,0 +1,4 @@ +extensions = ["sphinx_needs"] + +# also build the needs.json as some needs shall be hidden in it too +needs_build_json = True diff --git a/tests/doc_test/doc_directive_only/index.rst b/tests/doc_test/doc_directive_only/index.rst new file mode 100644 index 000000000..d4b288a2c --- /dev/null +++ b/tests/doc_test/doc_directive_only/index.rst @@ -0,0 +1,29 @@ +Only directive Test +===================== + +.. only:: tag_a + + .. req:: req_001 + + I shall not appear if not running tag_a + +.. only:: tag_b + + .. req:: req_002 + + I shall not appear if not running tag_b + +.. only:: tag_a or tag_b + + .. req:: req_003 + + I shall not appear if not running either tag_a or tag_b + + +.. req:: req_004 + + I shall always appear + + +.. needtable:: + types: req diff --git a/tests/test_directive_only_exclusion.py b/tests/test_directive_only_exclusion.py new file mode 100644 index 000000000..faefb73fc --- /dev/null +++ b/tests/test_directive_only_exclusion.py @@ -0,0 +1,70 @@ +import json +from pathlib import Path + +import pytest + + +@pytest.mark.parametrize( + "test_app", + [ + {"buildername": "html", "srcdir": "doc_test/doc_directive_only", "tags": ["tag_a"]}, + ], + indirect=True, +) +def test_need_excluded_under_only_a(test_app): + app = test_app + app.build() + html = Path(app.outdir, "index.html").read_text() + needs_list = json.loads(Path(app.outdir, "needs.json").read_text()) + + assert "req_001" in html + assert "req_002" not in html + assert "req_003" in html + assert "req_004" in html + + assert "req_001" in needs_list + assert "req_002" not in needs_list + assert "req_003" in needs_list + assert "req_004" in needs_list + + +@pytest.mark.parametrize( + "test_app", + [ + {"buildername": "html", "srcdir": "doc_test/doc_directive_only", "tags": ["tag_a"]}, + ], + indirect=True, +) +def test_need_excluded_under_only_b(test_app): + app = test_app + app.build() + html = Path(app.outdir, "index.html").read_text() + needs_list = json.loads(Path(app.outdir, "needs.json").read_text()) + + assert "req_001" not in html + assert "req_002" in html + assert "req_003" in html + assert "req_004" in html + + assert "req_001" not in needs_list + assert "req_002" in needs_list + assert "req_003" in needs_list + assert "req_004" in needs_list + + +@pytest.mark.parametrize("test_app", [{"buildername": "html", "srcdir": "doc_test/doc_directive_only"}], indirect=True) +def test_need_excluded_under_only_no_tag(test_app): + app = test_app + app.build() + html = Path(app.outdir, "index.html").read_text() + needs_list = json.loads(Path(app.outdir, "needs.json").read_text()) + + assert "req_001" not in html + assert "req_002" not in html + assert "req_003" not in html + assert "req_004" in html + + assert "req_001" not in needs_list + assert "req_002" not in needs_list + assert "req_003" not in needs_list + assert "req_004" in needs_list