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 202b0f07c..2015bdcaa 100644 --- a/sphinx_needs/directives/need.py +++ b/sphinx_needs/directives/need.py @@ -340,6 +340,13 @@ 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): + # the need is excluded as contained in an "only" directive evaluated to False for current tags + if hasattr(env, "needs_all_needs"): + del env.needs_all_needs[need_id] + if need_node.parent is not None and hasattr(need_node.parent, "remove"): + need_node.parent.remove(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 +363,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 is not None: + if hasattr(parent, "tagname") and parent.tagname == "only" and hasattr(parent, "attributes"): + # note: we do not manage nested only directive + only_tags = parent.attributes.get("expr", "") + return not 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..9a4831aff --- /dev/null +++ b/tests/doc_test/doc_directive_only/index.rst @@ -0,0 +1,53 @@ +Only directive Test +===================== + +.. req:: always_here_1 + :id: REQ_000 + + out of only is alwasy here + +.. only:: tag_a + + .. req:: only_tag_a + :id: REQ_001 + + I shall not appear if not running tag_a + + .. req:: only_tag_a_again + :id: REQ_001_1 + + need within need under only, shall neither appear if not running tag_a + + +.. only:: tag_b + + .. req:: only_tag_b + :id: REQ_002 + + I shall not appear if not running tag_b + + +.. only:: tag_a or tag_b + + .. req:: only_tag_a_or_b + :id: REQ_003 + + I shall not appear if not running either tag_a or tag_b + + +.. req:: always_here_2 + :id: REQ_004 + + I shall always appear + + +Needs table +-------------- + +.. needtable:: + + +Needs list +-------------- +.. needlist:: + diff --git a/tests/test_directive_only_exclusion.py b/tests/test_directive_only_exclusion.py new file mode 100644 index 000000000..8da7348b6 --- /dev/null +++ b/tests/test_directive_only_exclusion.py @@ -0,0 +1,100 @@ +import json +from pathlib import Path + +import pytest + + +def is_need_present(need_id, html, needs_list, is_present=True): + assert ( + is_need_in_html(need_id, html) is is_present + ), f"{need_id} should {'not ' if not is_present else ''}be present in html" + + assert ( + is_need_in_needtable(need_id, html) is is_present + ), f"{need_id} should {'not ' if not is_present else ''}be present in needtable" + + assert ( + is_need_in_needlist(need_id, html) is is_present + ), f"{need_id} should {'not ' if not is_present else ''}be present in needlist" + + assert ( + need_id in needs_list + ) is is_present, f"{need_id} should {'not ' if not is_present else ''}be present in needs.json" + + +def is_need_in_html(need_id, html): + return 'id="' + need_id + '">' in html + + +def is_need_in_needtable(need_id, html): + return ( + '