diff --git a/.gitignore b/.gitignore index 4ab112d7d..c51bb9687 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea +*.iml .venv* .pvenv .nox diff --git a/sphinx_needs/directives/need.py b/sphinx_needs/directives/need.py index b5b8a3f6b..7c914fac8 100644 --- a/sphinx_needs/directives/need.py +++ b/sphinx_needs/directives/need.py @@ -340,6 +340,8 @@ def analyse_need_locations(app: Sphinx, doctree: nodes.document) -> None: if need_node.get("hidden"): hidden_needs.append(need_node) + need_info["excluded_by_only"] = need_node_excluded_by_only_directive(env, 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 +358,18 @@ 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""" + parent = node.parent + while parent: + if hasattr(parent, "tagname") and parent.tagname == "only": + # note: we do not manage nested only directive + only_tags = parent.attributes.get("expr", "") + return env.app.builder.tags.eval_condition(only_tags) + parent = parent.parent + 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..bb723a975 --- /dev/null +++ b/tests/test_directive_only_exclusion.py @@ -0,0 +1,78 @@ +import json +from pathlib import Path + +import pytest + + +def get_json_needs(needs_file: Path): + with open(needs_file, "r") as file: + data = json.load(file) + return data["versions"][data["current_version"]]["needs"] + +@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() + + assert "req_001" in html + assert "req_002" not in html + assert "req_003" in html + assert "req_004" in html + + needs_list = get_json_needs(Path(app.outdir, "needs.json")) + + assert not needs_list["req_001"]["excluded_by_only"] + assert needs_list["req_002"]["excluded_by_only"] + assert not needs_list["req_003"]["excluded_by_only"] + assert not needs_list["req_004"]["excluded_by_only"] + + +@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() + + assert "req_001" not in html + assert "req_002" in html + assert "req_003" in html + assert "req_004" in html + + needs_list = get_json_needs(Path(app.outdir, "needs.json")) + + assert needs_list["req_001"]["excluded_by_only"] + assert not needs_list["req_002"]["excluded_by_only"] + assert not needs_list["req_003"]["excluded_by_only"] + assert not needs_list["req_004"]["excluded_by_only"] + + +@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() + + assert "req_001" not in html + assert "req_002" not in html + assert "req_003" not in html + assert "req_004" in html + + needs_list = get_json_needs(Path(app.outdir, "needs.json")) + + assert needs_list["req_001"]["excluded_by_only"] + assert needs_list["req_002"]["excluded_by_only"] + assert needs_list["req_003"]["excluded_by_only"] + assert not needs_list["req_004"]["excluded_by_only"]