From 07430e7f8e53402648e980e375322083abc7a653 Mon Sep 17 00:00:00 2001 From: "OFFICE\\cs" Date: Mon, 3 Mar 2025 11:36:32 +0100 Subject: [PATCH 1/3] wip --- sphinx_needs/directives/list2need.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sphinx_needs/directives/list2need.py b/sphinx_needs/directives/list2need.py index ec50b6b12..268991d21 100644 --- a/sphinx_needs/directives/list2need.py +++ b/sphinx_needs/directives/list2need.py @@ -58,6 +58,7 @@ def presentation(argument: str) -> Any: "presentation": directives.unchanged, "links-down": directives.unchanged, "tags": directives.unchanged, + "list-options": directives.unchanged, } def run(self) -> Sequence[nodes.Node]: @@ -110,6 +111,7 @@ def run(self) -> Sequence[nodes.Node]: # Retrieve tags defined at list level tags = self.options.get("tags", "") + list_options = self.options.get("list-options", "") list_needs = [] # Storing the data in a sorted list @@ -205,6 +207,16 @@ def run(self) -> Sequence[nodes.Node]: else: list_need["options"]["tags"] = tags + + if list_options: + if "options" not in list_need: + list_need["options"] = {} + current_list_options = list_need["options"] + if current_list_options: + list_need["options"] = current_list_options + "," + list_options + else: + list_need["options"] = list_options + template = Template(NEED_TEMPLATE, autoescape=True) data = list_need From 1f155d1c1455fc49eda2eb89bf1c9f1b7235d1e3 Mon Sep 17 00:00:00 2001 From: "OFFICE\\cs" Date: Mon, 3 Mar 2025 15:22:48 +0100 Subject: [PATCH 2/3] Add list-options option to list2need, allowing to set common options for all needs in the list --- docs/directives/list2need.rst | 21 +++++++++ sphinx_needs/directives/list2need.py | 30 +++++++++--- .../doc_list2need_list_options/conf.py | 46 +++++++++++++++++++ .../doc_list2need_list_options/index.rst | 20 ++++++++ tests/test_list2need_list_options.py | 43 +++++++++++++++++ 5 files changed, 153 insertions(+), 7 deletions(-) create mode 100644 tests/doc_test/doc_list2need_list_options/conf.py create mode 100644 tests/doc_test/doc_list2need_list_options/index.rst create mode 100644 tests/test_list2need_list_options.py diff --git a/docs/directives/list2need.rst b/docs/directives/list2need.rst index 924a3182a..6656e397a 100644 --- a/docs/directives/list2need.rst +++ b/docs/directives/list2need.rst @@ -168,6 +168,27 @@ tags The tags ``A`` and ``B`` are attached to all ``NEED-A``, ``NEED-B``, ``NEED-C`` and ``NEED-D``. +list-options +~~~~~~~~~~~~ + +``list-options`` allows to set common options for all needs in the list. + +.. code-block:: rst + + .. list2need:: + :types: req, spec + :list-options: + :hide: + :status: open + :validated_by: TEST-005 + + * (NEED-A)Login user + * (NEED-B)Provide login screen + * (NEED-C)Create password hash ((validated_by="TEST-006")) + * (NEED-D)Recalculate hash and compare + +All the ``hide``, ``status=open`` and ``validated_by=TEST-005-A`` and are attached to all ``NEED-A``, ``NEED-B``, ``NEED-C`` and ``NEED-D``. +Same options are aggregate: the ``NEED-C`` has a ``validated_by`` option set to ``TEST-005,TEST-006``. List examples ------------- diff --git a/sphinx_needs/directives/list2need.py b/sphinx_needs/directives/list2need.py index 268991d21..38296db36 100644 --- a/sphinx_needs/directives/list2need.py +++ b/sphinx_needs/directives/list2need.py @@ -172,6 +172,7 @@ def run(self) -> Sequence[nodes.Node]: "content": content.lstrip(), "level": level, "options": {}, + "list_options": {}, } list_needs.append(need) else: @@ -209,13 +210,28 @@ def run(self) -> Sequence[nodes.Node]: if list_options: - if "options" not in list_need: - list_need["options"] = {} - current_list_options = list_need["options"] - if current_list_options: - list_need["options"] = current_list_options + "," + list_options - else: - list_need["options"] = list_options + pattern = r":(\w+):\s*([^\n:]*)" + matches = re.findall(pattern, list_options) + for key, value in matches: + if "options" not in list_need: + list_need["options"] = {} + current_key = list_need["options"].get(key, "") + if current_key: + list_need["options"][key] = current_key + "," + value.strip() + else: + list_need["options"][key] = value.strip() + + + + +# if "options" not in list_need: +# list_need["options"] = {} +# current_list_options = list_need["options"] +# +# if current_list_options: +# list_need["options"] = current_list_options + "," + list_options +# else: +# list_need["options"] = list_options template = Template(NEED_TEMPLATE, autoescape=True) diff --git a/tests/doc_test/doc_list2need_list_options/conf.py b/tests/doc_test/doc_list2need_list_options/conf.py new file mode 100644 index 000000000..3c1c799aa --- /dev/null +++ b/tests/doc_test/doc_list2need_list_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_options/index.rst b/tests/doc_test/doc_list2need_list_options/index.rst new file mode 100644 index 000000000..36f1dab22 --- /dev/null +++ b/tests/doc_test/doc_list2need_list_options/index.rst @@ -0,0 +1,20 @@ +TEST DOCUMENT LIST2NEED +======================= + + +.. list2need:: + :types: spec, spec + :tags: list_of_needs + :list-options: + :hide: + :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_list_options.py b/tests/test_list2need_list_options.py new file mode 100644 index 000000000..09ea49255 --- /dev/null +++ b/tests/test_list2need_list_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_options", + "confoverrides": {"needs_reproducible_json": True}, + } + ], + indirect=True, +) +def test_doc_list2need_list_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 From 6d98b880b702abf5461772294eab0b11d4fa0de9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 14:29:25 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- sphinx_needs/directives/list2need.py | 22 +++++++-------- .../doc_list2need_list_options/conf.py | 6 ++--- tests/test_list2need_list_options.py | 27 +++++++++---------- 3 files changed, 23 insertions(+), 32 deletions(-) diff --git a/sphinx_needs/directives/list2need.py b/sphinx_needs/directives/list2need.py index 38296db36..b44cb75a3 100644 --- a/sphinx_needs/directives/list2need.py +++ b/sphinx_needs/directives/list2need.py @@ -208,7 +208,6 @@ def run(self) -> Sequence[nodes.Node]: else: list_need["options"]["tags"] = tags - if list_options: pattern = r":(\w+):\s*([^\n:]*)" matches = re.findall(pattern, list_options) @@ -220,18 +219,15 @@ def run(self) -> Sequence[nodes.Node]: list_need["options"][key] = current_key + "," + value.strip() else: list_need["options"][key] = value.strip() - - - - -# if "options" not in list_need: -# list_need["options"] = {} -# current_list_options = list_need["options"] -# -# if current_list_options: -# list_need["options"] = current_list_options + "," + list_options -# else: -# list_need["options"] = list_options + + # if "options" not in list_need: + # list_need["options"] = {} + # current_list_options = list_need["options"] + # + # if current_list_options: + # list_need["options"] = current_list_options + "," + list_options + # else: + # list_need["options"] = list_options template = Template(NEED_TEMPLATE, autoescape=True) diff --git a/tests/doc_test/doc_list2need_list_options/conf.py b/tests/doc_test/doc_list2need_list_options/conf.py index 3c1c799aa..db49ce342 100644 --- a/tests/doc_test/doc_list2need_list_options/conf.py +++ b/tests/doc_test/doc_list2need_list_options/conf.py @@ -1,6 +1,6 @@ extensions = ["sphinx_needs", "sphinxcontrib.plantuml"] project = "test for list2need list_global_options" -author = 'Christophe SEYLER' +author = "Christophe SEYLER" needs_table_style = "TABLE" @@ -41,6 +41,4 @@ {"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 +needs_extra_options = ["aggregateoption"] diff --git a/tests/test_list2need_list_options.py b/tests/test_list2need_list_options.py index 09ea49255..afc282748 100644 --- a/tests/test_list2need_list_options.py +++ b/tests/test_list2need_list_options.py @@ -1,11 +1,7 @@ +import json from pathlib import Path import pytest -import json - -from sphinx_needs.api import get_needs_view - - @pytest.mark.parametrize( @@ -24,20 +20,21 @@ def test_doc_list2need_list_options(test_app, snapshot): 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" + 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 + assert "OtherValue" in need_d.get("aggregateoption", ""), ( + "NEED-D does not have 'OtherValue' in aggregateoption" + )