Skip to content

Commit 6abd389

Browse files
authored
🧪 Add test for needreport directive (useblocks#1105)
Currently there is no test for this directive, this PR adds one. This PR also fixes the directive: - Make the options flags - Change errors in the directive to emit warnings, rather than excepting the whole build - Allow for `template` to be specified as a directive option - Allow the the `dropdown` directive used in the default template, which requires an external sphinx extension, to be overriden using `needs_render_context = {"report_directive": "admonition"}` (I left the default as `dropdown`, so as not to introduce a breaking change)
1 parent 73b961e commit 6abd389

File tree

7 files changed

+91
-87
lines changed

7 files changed

+91
-87
lines changed

‎docs/conf.py

-33
Original file line numberDiff line numberDiff line change
@@ -82,43 +82,11 @@
8282
{% endif %}
8383
"""
8484

85-
EXTRA_CONTENT_TEMPLATE_COLLAPSE = """
86-
.. _{{id}}:
87-
88-
{% if hide == false -%}
89-
.. role:: needs_tag
90-
.. role:: needs_status
91-
.. role:: needs_type
92-
.. role:: needs_id
93-
.. role:: needs_title
94-
95-
.. rst-class:: need
96-
.. rst-class:: need_{{type_name}}
97-
98-
.. dropdown::
99-
:class: need
100-
101-
:needs_type:`{{type_name}}`: {% if title %}:needs_title:`{{title}}`{% endif %} :needs_id:`{{id}}`
102-
103-
{% if status and status|upper != "NONE" and not hide_status %} | status: :needs_status:`{{status}}`{% endif %}
104-
{% if tags and not hide_tags %} | tags: :needs_tag:`{{tags|join("` :needs_tag:`")}}`{% endif %}
105-
{% if my_extra_option != "" %} | my_extra_option: {{ my_extra_option }}{% endif %}
106-
{% if another_option != "" %} | another_option: {{ another_option }}{% endif %}
107-
| links incoming: :need_incoming:`{{id}}`
108-
| links outgoing: :need_outgoing:`{{id}}`
109-
110-
{{content|indent(4) }}
111-
112-
{% endif -%}
113-
"""
114-
11585
DEFAULT_DIAGRAM_TEMPLATE = (
11686
"<size:12>{{type_name}}</size>\\n**{{title|wordwrap(15, wrapstring='**\\\\n**')}}**\\n<size:10>{{id}}</size>"
11787
)
11888

11989
# You can uncomment some of the following lines to override the default configuration for Sphinx-Needs.
120-
121-
# needs_template = TITLE_TEMPLATE
12290
# needs_diagram_template = DEFAULT_DIAGRAM_TEMPLATE
12391

12492
# Absolute path to the needs_report_template_file based on the conf.py directory
@@ -251,7 +219,6 @@
251219
needs_table_style = "datatables"
252220
needs_table_columns = "ID;TITLE;STATUS;OUTGOING"
253221

254-
needs_template_collapse = EXTRA_CONTENT_TEMPLATE_COLLAPSE
255222
needs_extra_options = [
256223
"my_extra_option",
257224
"another_option",

‎docs/directives/needreport.rst

+10
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ The :ref:`needs_report_template` value is a path to the
2121
`jinja2 <https://jinja.palletsprojects.com/en/2.11.x/templates/>`_ template file.
2222
You can use the template file to customise the content generated by ``needreport``.
2323

24+
.. note::
25+
26+
The default needs report template is set to use ``dropdown`` directives for containing each configuration type, which requires the ``dropdown`` directive to be available in your Sphinx environment. If you do not have the ``dropdown`` directive available, you can use the following configuration to set the default needs report template to use ``admonition`` directives instead:
27+
28+
.. code-block:: python
29+
30+
needs_render_context = {
31+
"report_directive": "admonition",
32+
}
33+
2434
|ex|
2535

2636
.. code-block:: rst

‎sphinx_needs/directives/needreport.py

+37-50
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,71 @@
1-
import os
1+
from pathlib import Path
22
from typing import Sequence
33

44
from docutils import nodes
55
from docutils.parsers.rst import directives
66
from jinja2 import Template
7-
from sphinx.errors import SphinxError
7+
from sphinx.util import logging
88
from sphinx.util.docutils import SphinxDirective
99

1010
from sphinx_needs.config import NeedsSphinxConfig
1111
from sphinx_needs.directives.utils import analyse_needs_metrics
1212
from sphinx_needs.utils import add_doc
1313

14-
15-
class NeedsReportException(SphinxError):
16-
pass
14+
LOGGER = logging.getLogger(__name__)
1715

1816

1917
class NeedReportDirective(SphinxDirective):
2018
final_argument_whitespace = True
2119
option_spec = {
22-
"types": directives.unchanged,
23-
"links": directives.unchanged,
24-
"options": directives.unchanged,
25-
"usage": directives.unchanged,
20+
"types": directives.flag,
21+
"links": directives.flag,
22+
"options": directives.flag,
23+
"usage": directives.flag,
24+
"template": directives.unchanged,
2625
}
2726

2827
def run(self) -> Sequence[nodes.raw]:
2928
env = self.env
3029
needs_config = NeedsSphinxConfig(env.config)
3130

32-
if len(self.options.keys()) == 0: # Check if options is empty
33-
error_file, error_line = self.state_machine.input_lines.items[0]
34-
error_msg = "{}:{}: NeedReportError: No options specified to generate need report.".format(
35-
error_file, error_line + self.state_machine.input_lines.data.index(".. needreport::") + 1
31+
if not set(self.options).intersection({"types", "links", "options", "usage"}):
32+
LOGGER.warning(
33+
"No options specified to generate need report [needs.report]",
34+
location=self.get_location(),
35+
type="needs",
36+
subtype="report",
3637
)
37-
raise NeedsReportException(error_msg)
38-
39-
types = self.options.get("types")
40-
extra_links = self.options.get("links")
41-
extra_options = self.options.get("options")
42-
usage = self.options.get("usage")
43-
44-
needs_types = []
45-
needs_extra_links = []
46-
needs_extra_options = []
47-
needs_metrics = {}
48-
49-
if types is not None and isinstance(types, str):
50-
needs_types = needs_config.types
51-
if extra_links is not None and isinstance(extra_links, str):
52-
needs_extra_links = needs_config.extra_links
53-
if extra_options is not None and isinstance(extra_options, str):
54-
needs_extra_options = needs_config.extra_options
55-
if usage is not None and isinstance(usage, str):
56-
needs_metrics = analyse_needs_metrics(env)
38+
return []
5739

5840
report_info = {
59-
"types": needs_types,
60-
"options": needs_extra_options,
61-
"links": needs_extra_links,
62-
"usage": needs_metrics,
41+
"types": needs_config.types if "types" in self.options else [],
42+
"options": needs_config.extra_options if "options" in self.options else [],
43+
"links": needs_config.extra_links if "links" in self.options else [],
44+
"usage": analyse_needs_metrics(env) if "usage" in self.options else {},
45+
"report_directive": "dropdown",
6346
}
6447
report_info.update(**needs_config.render_context)
6548

66-
need_report_template_path: str = needs_config.report_template
67-
# Absolute path starts with /, based on the conf.py directory. The / need to be striped
68-
correct_need_report_template_path = os.path.join(env.app.srcdir, need_report_template_path.lstrip("/"))
69-
70-
if len(need_report_template_path) == 0:
71-
default_template_path = "needreport_template.rst"
72-
correct_need_report_template_path = os.path.join(os.path.dirname(__file__), default_template_path)
73-
74-
if not os.path.exists(correct_need_report_template_path):
75-
raise ReferenceError(f"Could not load needs report template file {correct_need_report_template_path}")
49+
if "template" in self.options:
50+
need_report_template_path = Path(self.env.relfn2path(self.options["template"], self.env.docname)[1])
51+
elif needs_config.report_template:
52+
# Absolute path starts with /, based on the conf.py directory. The / need to be striped
53+
need_report_template_path = Path(str(env.app.srcdir)) / needs_config.report_template.lstrip("/")
54+
else:
55+
need_report_template_path = Path(__file__).parent / "needreport_template.rst"
56+
57+
if not need_report_template_path.is_file():
58+
LOGGER.warning(
59+
f"Could not load needs report template file {need_report_template_path} [needs.report]",
60+
location=self.get_location(),
61+
type="needs",
62+
subtype="report",
63+
)
64+
return []
7665

77-
with open(correct_need_report_template_path) as needs_report_template_file:
78-
needs_report_template_file_content = needs_report_template_file.read()
66+
needs_report_template_file_content = need_report_template_path.read_text(encoding="utf8")
7967

8068
template = Template(needs_report_template_file_content, autoescape=True)
81-
8269
text = template.render(**report_info)
8370
self.state_machine.insert_input(text.split("\n"), self.state_machine.document.attributes["source"])
8471

‎sphinx_needs/directives/needreport_template.rst

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{# Output for needs_types #}
22
{% if types|length != 0 %}
33

4-
.. dropdown:: Need Types
4+
.. {{ report_directive }}:: Need Types
55
66
.. list-table::
77
:widths: 40 20 20 20
@@ -23,7 +23,7 @@
2323
{# Output for needs_extra_links #}
2424
{% if links|length != 0 %}
2525

26-
.. dropdown:: Need Extra Links
26+
.. {{ report_directive }}:: Need Extra Links
2727
2828
.. list-table::
2929
:widths: 10 30 30 5 20
@@ -47,7 +47,7 @@
4747
{# Output for needs_options #}
4848
{% if options|length != 0 %}
4949

50-
.. dropdown:: Need Extra Options
50+
.. {{ report_directive }}:: Need Extra Options
5151
5252
{% for option in options %}
5353
* {{ option }}
@@ -58,7 +58,7 @@
5858
{# Output for needs metrics #}
5959
{% if usage|length != 0 %}
6060

61-
.. dropdown:: Need Metrics
61+
.. {{ report_directive }}:: Need Metrics
6262
6363
.. list-table::
6464
:widths: 40 40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
extensions = ["sphinx_needs"]
2+
needs_extra_options = ["other"]
3+
needs_render_context = {
4+
"report_directive": "admonition",
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Needs Report
2+
============
3+
4+
.. req:: Requirement 1
5+
6+
.. needreport::
7+
8+
.. needreport::
9+
:types:
10+
:template: unknown.rst
11+
12+
.. needreport::
13+
:types:
14+
:links:
15+
:options:
16+
:usage:
17+

‎tests/test_needreport.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from pathlib import Path
2+
3+
import pytest
4+
5+
6+
@pytest.mark.parametrize("test_app", [{"buildername": "html", "srcdir": "doc_test/doc_needreport"}], indirect=True)
7+
def test_doc_needarch(test_app):
8+
app = test_app
9+
app.build()
10+
# check for warning about missing options
11+
warnings = app._warning.getvalue()
12+
assert "index.rst:6: WARNING: No options specified to generate need report [needs.report]" in warnings
13+
assert "index.rst:8: WARNING: Could not load needs report template file" in warnings
14+
html = Path(app.outdir, "index.html").read_text(encoding="utf8")
15+
assert "Need Types" in html
16+
assert "Need Extra Links" in html
17+
assert "Need Extra Options" in html
18+
assert "Need Metrics" in html

0 commit comments

Comments
 (0)