Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR - Add 'hide' support and a new meta-data option to List2need directive #1345

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
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
56 changes: 56 additions & 0 deletions docs/directives/list2need.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should rename meta-data to something with global or option in the name because we already have needs_global_options as a config parameter, which does something similar: setting values globally for specific needs.
That's the same here.

So, what about naming the parameter just global?`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to stay in the same mind, I've renamed to list_global_options

~~~~~~~~~

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
-------------

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
version = "4.2.0"
version = "4.1.0"

you don't need to change the version in a PR


description = "Sphinx needs extension for managing needs/requirements and specifications"
authors = ["team useblocks <[email protected]>"]
Expand Down
40 changes: 38 additions & 2 deletions sphinx_needs/directives/list2need.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}}

"""
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
43 changes: 43 additions & 0 deletions tests/doc_test/doc_list2need_hide/conf.py
Original file line number Diff line number Diff line change
@@ -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"},
]
28 changes: 28 additions & 0 deletions tests/doc_test/doc_list2need_hide/index.rst
Original file line number Diff line number Diff line change
@@ -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:
46 changes: 46 additions & 0 deletions tests/doc_test/doc_list2need_list_global_options/conf.py
Original file line number Diff line number Diff line change
@@ -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"
]
16 changes: 16 additions & 0 deletions tests/doc_test/doc_list2need_list_global_options/index.rst
Original file line number Diff line number Diff line change
@@ -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:
4 changes: 4 additions & 0 deletions tests/test_list2need.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"}],
Expand Down Expand Up @@ -68,3 +71,4 @@ def test_doc_list2need_html(test_app, snapshot):
'href="#NEED-B" title="NEED-C">NEED-B</a></span></span></div>'
in links_down_html
)

40 changes: 40 additions & 0 deletions tests/test_list2need_hide.py
Original file line number Diff line number Diff line change
@@ -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 '<tr class="need row-even"><td class="needs_id"><p><a class="reference internal" href="#NEED-A">NEED-A</a></p></td>' in index_html
assert '<tr class="need row-odd"><td class="needs_id"><p><a class="reference internal" href="#NEED-B">NEED-B</a></p></td>' in index_html
assert '<tr class="need row-even"><td class="needs_id"><p><a class="reference internal" href="#NEED-C">NEED-C</a></p></td>' in index_html


assert 'class="need_container docutils container"' not in index_html






43 changes: 43 additions & 0 deletions tests/test_list2need_list_global_options.py
Original file line number Diff line number Diff line change
@@ -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"