Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/directives/list2need.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------
Expand Down
24 changes: 24 additions & 0 deletions sphinx_needs/directives/list2need.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -170,6 +172,7 @@ def run(self) -> Sequence[nodes.Node]:
"content": content.lstrip(),
"level": level,
"options": {},
"list_options": {},
}
list_needs.append(need)
else:
Expand Down Expand Up @@ -205,6 +208,27 @@ 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)
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)

data = list_need
Expand Down
44 changes: 44 additions & 0 deletions tests/doc_test/doc_list2need_list_options/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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"]
20 changes: 20 additions & 0 deletions tests/doc_test/doc_list2need_list_options/index.rst
Original file line number Diff line number Diff line change
@@ -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:
40 changes: 40 additions & 0 deletions tests/test_list2need_list_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import json
from pathlib import Path

import pytest


@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"
)
Loading