Skip to content

Commit fd3343b

Browse files
committed
exclude needs under only directive if required (useblocks#1103)
1 parent 73b961e commit fd3343b

File tree

5 files changed

+175
-0
lines changed

5 files changed

+175
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.idea
2+
*.iml
23
.venv*
34
.pvenv
45
.nox

sphinx_needs/directives/need.py

+17
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,11 @@ def analyse_need_locations(app: Sphinx, doctree: nodes.document) -> None:
340340
if need_node.get("hidden"):
341341
hidden_needs.append(need_node)
342342

343+
if need_node_excluded_by_only_directive(env, need_node):
344+
# the need is excluded as contained in an "only" directive evaluated to False for current tags
345+
del env.needs_all_needs[need_id]
346+
need_node.parent.remove(need_node)
347+
343348
# now we have gathered all the information we need,
344349
# we can remove the hidden needs from the doctree
345350
for need_node in hidden_needs:
@@ -356,6 +361,18 @@ def previous_sibling(node: nodes.Node) -> Optional[nodes.Node]:
356361
return node.parent[i - 1] if i > 0 else None # type: ignore
357362

358363

364+
def need_node_excluded_by_only_directive(env: BuildEnvironment, node: nodes.Node) -> bool:
365+
"""Return True if the node is under an "only" directive that shall be excluded given to the current tags"""
366+
parent = node.parent
367+
while parent is not None:
368+
if hasattr(parent, "tagname") and parent.tagname == "only" and hasattr(parent, "attributes"):
369+
# note: we do not manage nested only directive
370+
only_tags = parent.attributes.get("expr", "")
371+
return not env.app.builder.tags.eval_condition(only_tags)
372+
parent = parent.parent
373+
return False
374+
375+
359376
@profile("NEEDS_POST_PROCESS")
360377
@measure_time("need_post_process")
361378
def post_process_needs_data(app: Sphinx) -> None:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
extensions = ["sphinx_needs"]
2+
3+
# also build the needs.json as some needs shall be hidden in it too
4+
needs_build_json = True
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
Only directive Test
2+
=====================
3+
4+
.. req:: always_here_1
5+
:id: REQ_000
6+
7+
out of only is alwasy here
8+
9+
.. only:: tag_a
10+
11+
.. req:: only_tag_a
12+
:id: REQ_001
13+
14+
I shall not appear if not running tag_a
15+
16+
.. req:: only_tag_a_again
17+
:id: REQ_001_1
18+
19+
need within need under only, shall neither appear if not running tag_a
20+
21+
22+
.. only:: tag_b
23+
24+
.. req:: only_tag_b
25+
:id: REQ_002
26+
27+
I shall not appear if not running tag_b
28+
29+
30+
.. only:: tag_a or tag_b
31+
32+
.. req:: only_tag_a_or_b
33+
:id: REQ_003
34+
35+
I shall not appear if not running either tag_a or tag_b
36+
37+
38+
.. req:: always_here_2
39+
:id: REQ_004
40+
41+
I shall always appear
42+
43+
44+
Needs table
45+
--------------
46+
47+
.. needtable::
48+
49+
50+
Needs list
51+
--------------
52+
.. needlist::
53+
+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import json
2+
from pathlib import Path
3+
4+
import pytest
5+
6+
7+
def is_need_present(need_id, html, needs_list, is_present=True):
8+
assert (
9+
is_need_in_html(need_id, html) is is_present
10+
), f"{need_id} should {'not ' if not is_present else ''}be present in html"
11+
12+
assert (
13+
is_need_in_needtable(need_id, html) is is_present
14+
), f"{need_id} should {'not ' if not is_present else ''}be present in needtable"
15+
16+
assert (
17+
is_need_in_needlist(need_id, html) is is_present
18+
), f"{need_id} should {'not ' if not is_present else ''}be present in needlist"
19+
20+
assert (
21+
need_id in needs_list
22+
) is is_present, f"{need_id} should {'not ' if not is_present else ''}be present in needs.json"
23+
24+
25+
def is_need_in_html(need_id, html):
26+
return 'id="' + need_id + '">' in html
27+
28+
29+
def is_need_in_needtable(need_id, html):
30+
return (
31+
'<td class="needs_id"><p><a class="reference internal" href="#' + need_id + '">' + need_id + "</a></p></td>"
32+
in html
33+
)
34+
35+
36+
def is_need_in_needlist(need_id, html):
37+
return '<div class="line"><a class="reference external" href="#' + need_id + '">' + need_id + ":" in html
38+
39+
40+
def get_json_needs(needs_file: Path):
41+
with open(needs_file) as file:
42+
data = json.load(file)
43+
return data["versions"][data["current_version"]]["needs"]
44+
45+
46+
@pytest.mark.parametrize(
47+
"test_app",
48+
[
49+
{"buildername": "html", "srcdir": "doc_test/doc_directive_only", "tags": ["tag_a"]},
50+
],
51+
indirect=True,
52+
)
53+
def test_need_excluded_under_only_a(test_app):
54+
app = test_app
55+
app.build()
56+
html = Path(app.outdir, "index.html").read_text()
57+
needs_list = get_json_needs(Path(app.outdir, "needs.json"))
58+
59+
is_need_present("REQ_000", html, needs_list)
60+
is_need_present("REQ_001", html, needs_list)
61+
is_need_present("REQ_001_1", html, needs_list)
62+
is_need_present("REQ_002", html, needs_list, False)
63+
is_need_present("REQ_003", html, needs_list)
64+
is_need_present("REQ_004", html, needs_list)
65+
66+
67+
@pytest.mark.parametrize(
68+
"test_app",
69+
[
70+
{"buildername": "html", "srcdir": "doc_test/doc_directive_only", "tags": ["tag_b"]},
71+
],
72+
indirect=True,
73+
)
74+
def test_need_excluded_under_only_b(test_app):
75+
app = test_app
76+
app.build()
77+
html = Path(app.outdir, "index.html").read_text()
78+
needs_list = get_json_needs(Path(app.outdir, "needs.json"))
79+
80+
is_need_present("REQ_000", html, needs_list)
81+
is_need_present("REQ_001", html, needs_list, False)
82+
is_need_present("REQ_001_1", html, needs_list, False)
83+
is_need_present("REQ_002", html, needs_list)
84+
is_need_present("REQ_003", html, needs_list)
85+
is_need_present("REQ_004", html, needs_list)
86+
87+
88+
@pytest.mark.parametrize("test_app", [{"buildername": "html", "srcdir": "doc_test/doc_directive_only"}], indirect=True)
89+
def test_need_excluded_under_only_no_tag(test_app):
90+
app = test_app
91+
app.build()
92+
html = Path(app.outdir, "index.html").read_text()
93+
needs_list = get_json_needs(Path(app.outdir, "needs.json"))
94+
95+
is_need_present("REQ_000", html, needs_list)
96+
is_need_present("REQ_001", html, needs_list, False)
97+
is_need_present("REQ_001_1", html, needs_list, False)
98+
is_need_present("REQ_002", html, needs_list, False)
99+
is_need_present("REQ_003", html, needs_list, False)
100+
is_need_present("REQ_004", html, needs_list)

0 commit comments

Comments
 (0)