Skip to content
Draft
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
54 changes: 48 additions & 6 deletions sphinx_needs/api/need.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from collections.abc import Iterable, Iterator
from contextlib import contextmanager
from pathlib import Path
from typing import Any
from typing import Any, cast

from docutils import nodes
from docutils.parsers.rst.states import RSTState
Expand All @@ -18,13 +18,19 @@

from sphinx_needs.api.exceptions import InvalidNeedException
from sphinx_needs.config import NeedsSphinxConfig
from sphinx_needs.data import NeedsInfoType, NeedsPartType, SphinxNeedsData
from sphinx_needs.data import (
NeedsInfoType,
NeedsInfoTypeDelete,
NeedsPartType,
SphinxNeedsData,
)
from sphinx_needs.defaults import string_to_boolean
from sphinx_needs.directives.needuml import Needuml, NeedumlException
from sphinx_needs.filter_common import filter_single_need
from sphinx_needs.logging import get_logger, log_warning
from sphinx_needs.nodes import Need
from sphinx_needs.roles.need_part import find_parts, update_need_with_parts
from sphinx_needs.utils import jinja_parse
from sphinx_needs.utils import jinja_parse, match_variants
from sphinx_needs.views import NeedsView

logger = get_logger(__name__)
Expand Down Expand Up @@ -58,6 +64,7 @@ def generate_need(
sections: list[str] | None = None,
jinja_content: None | bool = False,
hide: bool = False,
delete: None | str = None,
collapse: None | bool = None,
style: None | str = None,
layout: None | str = None,
Expand All @@ -70,7 +77,7 @@ def generate_need(
external_css: str = "external_link",
full_title: str | None = None,
**kwargs: str,
) -> NeedsInfoType:
) -> NeedsInfoTypeDelete:
"""Creates a validated need data entry, without adding it to the project.

.. important:: This function does not parse or analyse the content,
Expand Down Expand Up @@ -113,6 +120,7 @@ def generate_need(
:param constraints: Constraints as single, comma separated, string.
:param constraints_passed: Contains bool describing if all constraints have passed
:param hide: boolean value.
:param delete: String value of delete option.
:param collapse: boolean value.
:param style: String value of class attribute of node.
:param layout: String value of layout definition to use
Expand Down Expand Up @@ -191,7 +199,7 @@ def generate_need(
)

# Add the need and all needed information
needs_info: NeedsInfoType = {
needs_info: NeedsInfoTypeDelete = {
"docname": docname,
"lineno": lineno,
"lineno_content": lineno_content,
Expand All @@ -217,6 +225,8 @@ def generate_need(
"pre_template": pre_template,
"post_template": post_template,
"hide": hide,
"delete": delete,
"deleted": False,
"jinja_content": jinja_content or False,
"parts": parts or {},
"is_part": False,
Expand All @@ -236,6 +246,25 @@ def generate_need(
"parent_need": "",
}

# validate delete
if delete is None:
delete = "false"
else:
# Check for variant logic
need_context: dict[str, Any] = {**needs_info}
need_context.update(
**needs_config.filter_data
) # Add needs_filter_data to filter context
need_context.update(
**{tag: True for tag in tags}
) # Add sphinx tags to filter context
delete_var = match_variants(delete, need_context, needs_config.variants)
if delete_var is not None:
delete = delete_var

# Finally get a boolean out of a set string
needs_info["deleted"] = string_to_boolean(delete) or False

_add_extra_fields(needs_info, kwargs, needs_config)
_add_link_fields(needs_info, kwargs, needs_config, location)
_set_field_defaults(needs_info, needs_config)
Expand Down Expand Up @@ -298,6 +327,7 @@ def add_need(
sections: list[str] | None = None,
jinja_content: None | bool = False,
hide: bool = False,
delete: None | str = None,
collapse: None | bool = None,
style: None | str = None,
layout: None | str = None,
Expand Down Expand Up @@ -355,6 +385,7 @@ def add_need(
:param constraints: Constraints as single, comma separated, string.
:param constraints_passed: Contains bool describing if all constraints have passed
:param hide: boolean value.
:param delete: String value of delete option.
:param collapse: boolean value.
:param style: String value of class attribute of node.
:param layout: String value of layout definition to use
Expand All @@ -374,7 +405,7 @@ def add_need(
if doctype is None and not is_external and docname:
doctype = os.path.splitext(app.env.doc2path(docname))[1]

needs_info = generate_need(
needs_info_deleted = generate_need(
needs_config=NeedsSphinxConfig(app.config),
need_type=need_type,
title=title,
Expand All @@ -394,6 +425,7 @@ def add_need(
sections=sections,
jinja_content=jinja_content,
hide=hide,
delete=delete,
collapse=collapse,
style=style,
layout=layout,
Expand All @@ -407,6 +439,16 @@ def add_need(
**kwargs,
)

if needs_info_deleted["deleted"]:
# needs_info gets not set to SphinxNeedsData
return []

# Remove 'delete' and 'deleted' info, as from here on it is always "false"
needs_info: NeedsInfoType = cast(
NeedsInfoType,
{k: v for k, v in needs_info_deleted.items() if k not in ["delete", "deleted"]},
)

if SphinxNeedsData(app.env).has_need(needs_info["id"]):
if id is None:
# this is a generated ID
Expand Down
7 changes: 7 additions & 0 deletions sphinx_needs/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,13 @@ class NeedsInfoType(TypedDict, total=False):
# and in turn means they are added to every need via ``add_need`` (as strings)


class NeedsInfoTypeDelete(NeedsInfoType, total=False):
"""Holds temporary 'delete' related data"""

delete: Required[None | str]
deleted: Required[bool]


class NeedsBaseDataType(TypedDict):
"""A base type for data items collected from directives."""

Expand Down
2 changes: 1 addition & 1 deletion sphinx_needs/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ def string_to_boolean(argument: str | None) -> bool | None:
"tags": directives.unchanged_required,
"links": directives.unchanged_required,
"collapse": string_to_boolean,
"delete": string_to_boolean,
"delete": directives.unchanged_required,
"jinja_content": string_to_boolean,
"hide": directives.flag,
"title_from_content": directives.flag,
Expand Down
5 changes: 2 additions & 3 deletions sphinx_needs/directives/need.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,12 @@ class NeedDirective(SphinxDirective):

@measure_time("need")
def run(self) -> Sequence[nodes.Node]:
if self.options.get("delete"):
return []

needs_config = NeedsSphinxConfig(self.env.config)

collapse = self.options.get("collapse")
jinja_content = self.options.get("jinja_content")
hide = "hide" in self.options
delete = self.options.get("delete")

id = self.options.get("id")
status = self.options.get("status")
Expand Down Expand Up @@ -101,6 +99,7 @@ def run(self) -> Sequence[nodes.Node]:
status=status,
tags=tags,
hide=hide,
delete=delete,
template=template,
pre_template=pre_template,
post_template=post_template,
Expand Down
6 changes: 6 additions & 0 deletions tests/doc_test/doc_need_delete/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,9 @@ Need Delete Option

Need with ``:delete:`` equal to ``true``.


.. spec:: Test variants
:id: DELVAR
:status: open
:tags: user;login
:delete: ['var_a' in tags]:false, true
2 changes: 2 additions & 0 deletions tests/test_need_delete_option.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ def test_doc_need_delete(test_app):
assert "DELID126" not in html
assert "Second Spec Need" not in html
assert "Nested Implemented Need" not in html

assert "DELVAR" not in html
Loading