diff --git a/docs/directives/list2need.rst b/docs/directives/list2need.rst index 924a3182a..ccf210f10 100644 --- a/docs/directives/list2need.rst +++ b/docs/directives/list2need.rst @@ -169,6 +169,62 @@ tags The tags ``A`` and ``B`` are attached to all ``NEED-A``, ``NEED-B``, ``NEED-C`` and ``NEED-D``. + +hide +~~~~ + +``hide`` sets the hide-option globally to all items in the list. + +.. code-block:: rst + + .. list2need:: + :types: req + :tags: A + :hide: True + + * (NEED-A) Login user + * (NEED-B) Provide login screen + * (NEED-C) Create password hash + * (NEED-D) Recalculate hash and compare + +All ``NEED-A``, ``NEED-B``, ``NEED-C`` and ``NEED-D`` requirements will be marked as hidden. This allows to easily create a list of requirements and presenting them as a table in the final output. + +.. code-block:: rst + + .. list2need:: + :types: req + :tags: A + :hide: True + + * (NEED-A) Login user + * (NEED-B) Provide login screen + * (NEED-C) Create password hash + * (NEED-D) Recalculate hash and compare + + .. needtable:: + :types: req + :tags: A + :style: table + :columns: id, title, content, links + + +meta-data +~~~~~~~~~ + +Meta-data can be set directly in the related line via, for example: ``((status="open"))``. This meta-data option allows to define meta-data that will be affected to all needs in the list, including extra custom options. + +.. code-block:: rst + + .. list2need:: + :types: req + :tags: A + :meta-data: validation="Test, Review of Design", status="open" + + * (NEED-A) Login user + * (NEED-B) Provide login screen + * (NEED-C) Create password hash + * (NEED-D) Recalculate hash and compare + List examples ------------- diff --git a/pyproject.toml b/pyproject.toml index 60b0493c4..95df17e30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "sphinx-needs" # !! Don't miss updates in sphinx_needs.__version__, changelog.rst, and .github/workflows/docker !!! -version = "4.1.0" +version = "4.2.0" description = "Sphinx needs extension for managing needs/requirements and specifications" authors = ["team useblocks "] diff --git a/sphinx_needs/directives/list2need.py b/sphinx_needs/directives/list2need.py index ec50b6b12..0ebef1b41 100644 --- a/sphinx_needs/directives/list2need.py +++ b/sphinx_needs/directives/list2need.py @@ -17,8 +17,9 @@ {% if need_id is not none %}:id: {{need_id}}{%endif%} {% if set_links_down %}:{{links_down_type}}: {{ links_down|join(', ') }}{%endif%} {%- for name, value in options.items() %}:{{name}}: {{value}} - {% endfor %} - + {% endfor %}{% if need_hide %}:hide:{%endif%} + + {{content}} """ @@ -58,9 +59,18 @@ def presentation(argument: str) -> Any: "presentation": directives.unchanged, "links-down": directives.unchanged, "tags": directives.unchanged, + "hide": directives.unchanged, + "list_global_options": directives.unchanged, } def run(self) -> Sequence[nodes.Node]: + """_Implementation details_ + + The directive is used to create a list of needs (list_needs). Each list entry is used to create a single need using + a jinja2 template (Template). The template is defined in the NEED_TEMPLATE variable. The template is rendered for each list entry + + """ + env = self.env needs_config = NeedsSphinxConfig(env.config) @@ -110,6 +120,13 @@ def run(self) -> Sequence[nodes.Node]: # Retrieve tags defined at list level tags = self.options.get("tags", "") + + if "hide" in self.options: + hide = True + else: + hide = False + + global_options = self.options.get("list_global_options", "") list_needs = [] # Storing the data in a sorted list @@ -205,6 +222,25 @@ def run(self) -> Sequence[nodes.Node]: else: list_need["options"]["tags"] = tags + list_need["need_hide"] = hide + + if global_options: + if "options" not in list_need: + list_need["options"] = {} + global_options_items = re.findall( + r'([^=,]+)=["\']([^"\']+)["\']', global_options + ) + + for key, value in global_options_items: + current_options = list_need["options"].get(key.strip(), "") + + if current_options: + list_need["options"][key.strip()] = ( + current_options + "," + value + ) + else: + list_need["options"][key.strip()] = value + template = Template(NEED_TEMPLATE, autoescape=True) data = list_need diff --git a/tests/doc_test/doc_list2need_hide/conf.py b/tests/doc_test/doc_list2need_hide/conf.py new file mode 100644 index 000000000..df8b630a7 --- /dev/null +++ b/tests/doc_test/doc_list2need_hide/conf.py @@ -0,0 +1,43 @@ +extensions = ["sphinx_needs"] +project = "test for list2need hide" +author = 'Christophe SEYLER' + +needs_table_style = "TABLE" + +needs_id_regex = "^[A-Za-z0-9_]" + +needs_types = [ + { + "directive": "story", + "title": "User Story", + "prefix": "US_", + "color": "#BFD8D2", + "style": "node", + }, + { + "directive": "spec", + "title": "Specification", + "prefix": "SP_", + "color": "#FEDCD2", + "style": "node", + }, + { + "directive": "impl", + "title": "Implementation", + "prefix": "IM_", + "color": "#DF744A", + "style": "node", + }, + { + "directive": "test", + "title": "Test Case", + "prefix": "TC_", + "color": "#DCB239", + "style": "node", + }, +] + +needs_extra_links = [ + {"option": "checks", "incoming": "is checked by", "outgoing": "checks"}, + {"option": "triggers", "incoming": "is triggered by", "outgoing": "triggers"}, +] diff --git a/tests/doc_test/doc_list2need_hide/index.rst b/tests/doc_test/doc_list2need_hide/index.rst new file mode 100644 index 000000000..f2cff429b --- /dev/null +++ b/tests/doc_test/doc_list2need_hide/index.rst @@ -0,0 +1,28 @@ +TEST DOCUMENT LIST2NEED +======================= + + +.. list2need:: + :types: spec, spec + :tags: tag1 + :hide: + + * (NEED-A) Need example on level 1. fsdfsdf. ((status="closed",tags="tag2")) + * (NEED-B) Link example ((status="closed",tags="tag2")) + * (NEED-B-1) Need example on level 2 + * (NEED-C) New line example. ((status="closed",tags="tag2")) + With some content in the next line. + + + +List +==== + +.. needtable:: Example table + :tags: tag1 + :style: table + :columns: id + :show_filters: + + +.. _test: diff --git a/tests/doc_test/doc_list2need_list_global_options/conf.py b/tests/doc_test/doc_list2need_list_global_options/conf.py new file mode 100644 index 000000000..3c1c799aa --- /dev/null +++ b/tests/doc_test/doc_list2need_list_global_options/conf.py @@ -0,0 +1,46 @@ +extensions = ["sphinx_needs", "sphinxcontrib.plantuml"] +project = "test for list2need list_global_options" +author = 'Christophe SEYLER' + +needs_table_style = "TABLE" + +needs_id_regex = "^[A-Za-z0-9_]" + +needs_types = [ + { + "directive": "story", + "title": "User Story", + "prefix": "US_", + "color": "#BFD8D2", + "style": "node", + }, + { + "directive": "spec", + "title": "Specification", + "prefix": "SP_", + "color": "#FEDCD2", + "style": "node", + }, + { + "directive": "impl", + "title": "Implementation", + "prefix": "IM_", + "color": "#DF744A", + "style": "node", + }, + { + "directive": "test", + "title": "Test Case", + "prefix": "TC_", + "color": "#DCB239", + "style": "node", + }, +] + +needs_extra_links = [ + {"option": "checks", "incoming": "is checked by", "outgoing": "checks"}, + {"option": "triggers", "incoming": "is triggered by", "outgoing": "triggers"}, +] +needs_extra_options = [ + "aggregateoption" +] \ No newline at end of file diff --git a/tests/doc_test/doc_list2need_list_global_options/index.rst b/tests/doc_test/doc_list2need_list_global_options/index.rst new file mode 100644 index 000000000..1f57c9d40 --- /dev/null +++ b/tests/doc_test/doc_list2need_list_global_options/index.rst @@ -0,0 +1,16 @@ +TEST DOCUMENT LIST2NEED +======================= + + +.. list2need:: + :types: spec, spec + :list_global_options: status="open", aggregateoption="SomeValue" + + * (NEED-A) Need example on level 1 + * (NEED-B) Need example on level 1 + * (NEED-C) Link example + * (NEED-C-1) Need example on level 2 + * (NEED-D) New line example. ((aggregateoption="OtherValue")) + With some content in the next line. + +.. _test: diff --git a/tests/test_list2need.py b/tests/test_list2need.py index d4c272227..5b7452839 100644 --- a/tests/test_list2need.py +++ b/tests/test_list2need.py @@ -2,9 +2,12 @@ import pytest + from sphinx_needs.api import get_needs_view + + @pytest.mark.parametrize( "test_app", [{"buildername": "html", "srcdir": "doc_test/doc_list2need"}], @@ -68,3 +71,4 @@ def test_doc_list2need_html(test_app, snapshot): 'href="#NEED-B" title="NEED-C">NEED-B' in links_down_html ) + diff --git a/tests/test_list2need_hide.py b/tests/test_list2need_hide.py new file mode 100644 index 000000000..e16c0d760 --- /dev/null +++ b/tests/test_list2need_hide.py @@ -0,0 +1,40 @@ +from pathlib import Path + +import pytest +import json + +from sphinx_needs.api import get_needs_view + + + + +@pytest.mark.parametrize( + "test_app", + [{"buildername": "html", "srcdir": "doc_test/doc_list2need_hide"}], + indirect=True, +) +def test_doc_list2need_hide(test_app, snapshot): + """ + The test validates the list2need directive with the hide option. + The needs must be valid, but the rendered output must not contain the need content. + + To validate that the needs are valid, the needs are rendered using a needtable directive. + """ + app = test_app + app.build() + + + + index_html = Path(app.outdir, "index.html").read_text() + assert '

NEED-A

' in index_html + assert '

NEED-B

' in index_html + assert '

NEED-C

' in index_html + + + assert 'class="need_container docutils container"' not in index_html + + + + + + \ No newline at end of file diff --git a/tests/test_list2need_list_global_options.py b/tests/test_list2need_list_global_options.py new file mode 100644 index 000000000..4f9617688 --- /dev/null +++ b/tests/test_list2need_list_global_options.py @@ -0,0 +1,43 @@ +from pathlib import Path + +import pytest +import json + +from sphinx_needs.api import get_needs_view + + + + +@pytest.mark.parametrize( + "test_app", + [ + { + "buildername": "needs", + "srcdir": "doc_test/doc_list2need_list_global_options", + "confoverrides": {"needs_reproducible_json": True}, + } + ], + indirect=True, +) +def test_doc_list2need_list_global_options(test_app, snapshot): + app = test_app + app.build() + + needs_list = json.loads(Path(app.outdir, "needs.json").read_text()) + + needs = needs_list["versions"][""]["needs"] + + # Check that all entries have a status item equal to "open" + for need_id, need in needs.items(): + assert need.get("status") == "open", f"Need {need_id} does not have status 'open'" + assert "SomeValue" in need.get("aggregateoption", ""), f"Need {need_id} does not have 'SomeValue' in aggregateoption" + + # Check that NEED-D has "OtherValue" in its aggregateoption + need_d = needs.get("NEED-D") + assert need_d is not None, "NEED-D is missing" + assert "OtherValue" in need_d.get("aggregateoption", ""), "NEED-D does not have 'OtherValue' in aggregateoption" + + + + + \ No newline at end of file