Skip to content

Commit

Permalink
exclude needs under only directive if required (#1103)
Browse files Browse the repository at this point in the history
  • Loading branch information
David-Le-Nir committed Feb 13, 2024
1 parent f6b090a commit eda2264
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.idea
*.iml
.venv*
.pvenv
.nox
Expand Down
19 changes: 19 additions & 0 deletions sphinx_needs/directives/need.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions tests/doc_test/doc_directive_only/conf.py
Original file line number Diff line number Diff line change
@@ -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
53 changes: 53 additions & 0 deletions tests/doc_test/doc_directive_only/index.rst
Original file line number Diff line number Diff line change
@@ -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::

100 changes: 100 additions & 0 deletions tests/test_directive_only_exclusion.py
Original file line number Diff line number Diff line change
@@ -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 (
'<td class="needs_id"><p><a class="reference internal" href="#' + need_id + '">' + need_id + "</a></p></td>"
in html
)


def is_need_in_needlist(need_id, html):
return '<div class="line"><a class="reference external" href="#' + need_id + '">' + need_id + ":" in html


def get_json_needs(needs_file: Path):
with open(needs_file) 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()
needs_list = get_json_needs(Path(app.outdir, "needs.json"))

is_need_present("REQ_000", html, needs_list)
is_need_present("REQ_001", html, needs_list)
is_need_present("REQ_001_1", html, needs_list)
is_need_present("REQ_002", html, needs_list, False)
is_need_present("REQ_003", html, needs_list)
is_need_present("REQ_004", html, needs_list)


@pytest.mark.parametrize(
"test_app",
[
{"buildername": "html", "srcdir": "doc_test/doc_directive_only", "tags": ["tag_b"]},
],
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 = get_json_needs(Path(app.outdir, "needs.json"))

is_need_present("REQ_000", html, needs_list)
is_need_present("REQ_001", html, needs_list, False)
is_need_present("REQ_001_1", html, needs_list, False)
is_need_present("REQ_002", html, needs_list)
is_need_present("REQ_003", html, needs_list)
is_need_present("REQ_004", html, 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 = get_json_needs(Path(app.outdir, "needs.json"))

is_need_present("REQ_000", html, needs_list)
is_need_present("REQ_001", html, needs_list, False)
is_need_present("REQ_001_1", html, needs_list, False)
is_need_present("REQ_002", html, needs_list, False)
is_need_present("REQ_003", html, needs_list, False)
is_need_present("REQ_004", html, needs_list)

0 comments on commit eda2264

Please sign in to comment.