Skip to content

Commit

Permalink
#947 filter_warning option to replace default "No needs passed the fi…
Browse files Browse the repository at this point in the history
…lters" text (#1093)

Add option `:filter_warning:` to directives (e.g. needtable) to show no
text or given text instead of the default text.
  • Loading branch information
kreuzberger committed Jan 24, 2024
1 parent c27bc64 commit 73b961e
Show file tree
Hide file tree
Showing 18 changed files with 375 additions and 79 deletions.
19 changes: 19 additions & 0 deletions docs/filter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -453,3 +453,22 @@ Example:
results.append(cnt_x)
results.append(cnt_y)
Filter matches nothing
----------------------

Depending on the directive used a filter that matches no needs may add text to inform that no needs are found.

The default text "No needs passed the filter".

If this is not intended, add the option

.. _option_filter_warning:

filter_warning
~~~~~~~~~~~~~~

Add specific text with this option or add no text to display nothing. The default text will not be shown.

The specified output could be styled with the css class ``needs_filter_warning``

5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ module = [
]
disable_error_code = ["attr-defined", "no-any-return"]

[[tool.mypy.overrides]]
module = [
"sphinx_needs.directives.needextract",
]
disable_error_code = "no-untyped-call"

[build-system]
requires = ["setuptools", "poetry_core>=1.0.8"] # setuptools for deps like plantuml
Expand Down
2 changes: 2 additions & 0 deletions sphinx_needs/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ class NeedsFilteredBaseType(NeedsBaseDataType):
filter_code: list[str]
filter_func: None | str
export_id: str
filter_warning: str
"""If set, the filter is exported with this ID in the needs.json file."""


Expand Down Expand Up @@ -346,6 +347,7 @@ class NeedsPieType(NeedsBaseDataType):
text_color: None | str
shadow: bool
filter_func: None | str
filter_warning: str


class NeedsSequenceType(NeedsFilteredDiagramBaseType):
Expand Down
2 changes: 1 addition & 1 deletion sphinx_needs/directives/needextract.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def process_needextract(
content.append(need_extract)

if len(content) == 0:
content.append(no_needs_found_paragraph())
content.append(no_needs_found_paragraph(current_needextract.get("filter_warning")))

if current_needextract["show_filters"]:
content.append(used_filter_paragraph(current_needextract))
Expand Down
7 changes: 2 additions & 5 deletions sphinx_needs/directives/needfilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from sphinx_needs.config import NeedsSphinxConfig
from sphinx_needs.data import SphinxNeedsData
from sphinx_needs.diagrams_common import create_legend
from sphinx_needs.directives.utils import no_needs_found_paragraph
from sphinx_needs.filter_common import FilterBase, process_filters
from sphinx_needs.utils import add_doc, remove_node_from_tree, row_col_maker

Expand Down Expand Up @@ -228,11 +229,7 @@ def process_needfilters(
content.append(puml_node)

if len(content) == 0:
nothing_found = "No needs passed the filters"
para = nodes.line()
nothing_found_node = nodes.Text(nothing_found)
para += nothing_found_node
content.append(para)
content.append(no_needs_found_paragraph(current_needfilter.get("filter_warning")))
if current_needfilter["show_filters"]:
para = nodes.paragraph()
filter_text = "Used filter:"
Expand Down
7 changes: 2 additions & 5 deletions sphinx_needs/directives/needflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
)
from sphinx_needs.debug import measure_time
from sphinx_needs.diagrams_common import calculate_link, create_legend
from sphinx_needs.directives.utils import no_needs_found_paragraph
from sphinx_needs.filter_common import FilterBase, filter_single_need, process_filters
from sphinx_needs.logging import get_logger
from sphinx_needs.utils import (
Expand Down Expand Up @@ -457,11 +458,7 @@ def process_needflow(app: Sphinx, doctree: nodes.document, fromdocname: str, fou

content.append(puml_node)
else: # no needs found
nothing_found = "No needs passed the filters"
para = nodes.paragraph()
nothing_found_node = nodes.Text(nothing_found)
para += nothing_found_node
content.append(para)
content.append(no_needs_found_paragraph(current_needflow.get("filter_warning")))

if current_needflow["show_filters"]:
para = nodes.paragraph()
Expand Down
13 changes: 6 additions & 7 deletions sphinx_needs/directives/needgantt.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
get_filter_para,
no_plantuml,
)
from sphinx_needs.directives.utils import SphinxNeedsLinkTypeException
from sphinx_needs.directives.utils import (
SphinxNeedsLinkTypeException,
no_needs_found_paragraph,
)
from sphinx_needs.filter_common import FilterBase, filter_single_need, process_filters
from sphinx_needs.logging import get_logger
from sphinx_needs.utils import MONTH_NAMES, add_doc, remove_node_from_tree
Expand Down Expand Up @@ -312,12 +315,8 @@ def process_needgantt(app: Sphinx, doctree: nodes.document, fromdocname: str, fo

content.append(puml_node)

if len(content) == 0:
nothing_found = "No needs passed the filters"
para = nodes.paragraph()
nothing_found_node = nodes.Text(nothing_found)
para += nothing_found_node
content.append(para)
if len(found_needs) == 0:
content = [no_needs_found_paragraph(current_needgantt.get("filter_warning"))]
if current_needgantt["show_filters"]:
content.append(get_filter_para(current_needgantt))

Expand Down
80 changes: 40 additions & 40 deletions sphinx_needs/directives/needlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,48 +83,48 @@ def process_needlist(app: Sphinx, doctree: nodes.document, fromdocname: str, fou
all_needs = list(SphinxNeedsData(env).get_or_create_needs().values())
found_needs = process_filters(app, all_needs, current_needfilter)

line_block = nodes.line_block()

# Add lineno to node
line_block.line = current_needfilter["lineno"]
for need_info in found_needs:
para = nodes.line()
description = "{}: {}".format(need_info["id"], need_info["title"])

if current_needfilter["show_status"] and need_info["status"]:
description += " (%s)" % need_info["status"]

if current_needfilter["show_tags"] and need_info["tags"]:
description += " [%s]" % "; ".join(need_info["tags"])

title = nodes.Text(description)

# Create a reference
if need_info["hide"]:
para += title
elif need_info["is_external"]:
assert need_info["external_url"] is not None, "External need without URL"
ref = nodes.reference("", "")

ref["refuri"] = check_and_calc_base_url_rel_path(need_info["external_url"], fromdocname)

ref["classes"].append(need_info["external_css"])
ref.append(title)
para += ref
else:
target_id = need_info["target_id"]
ref = nodes.reference("", "")
ref["refdocname"] = need_info["docname"]
ref["refuri"] = builder.get_relative_uri(fromdocname, need_info["docname"])
ref["refuri"] += "#" + target_id
ref.append(title)
para += ref
line_block.append(para)
content.append(line_block)
if 0 < len(found_needs):
line_block = nodes.line_block()

# Add lineno to node
line_block.line = current_needfilter["lineno"]
for need_info in found_needs:
para = nodes.line()
description = "{}: {}".format(need_info["id"], need_info["title"])

if current_needfilter["show_status"] and need_info["status"]:
description += " (%s)" % need_info["status"]

if current_needfilter["show_tags"] and need_info["tags"]:
description += " [%s]" % "; ".join(need_info["tags"])

title = nodes.Text(description)

# Create a reference
if need_info["hide"]:
para += title
elif need_info["is_external"]:
assert need_info["external_url"] is not None, "External need without URL"
ref = nodes.reference("", "")

ref["refuri"] = check_and_calc_base_url_rel_path(need_info["external_url"], fromdocname)

ref["classes"].append(need_info["external_css"])
ref.append(title)
para += ref
else:
target_id = need_info["target_id"]
ref = nodes.reference("", "")
ref["refdocname"] = need_info["docname"]
ref["refuri"] = builder.get_relative_uri(fromdocname, need_info["docname"])
ref["refuri"] += "#" + target_id
ref.append(title)
para += ref
line_block.append(para)
content.append(line_block)

if len(content) == 0:
content.append(no_needs_found_paragraph())

content.append(no_needs_found_paragraph(current_needfilter.get("filter_warning")))
if current_needfilter["show_filters"]:
content.append(used_filter_paragraph(current_needfilter))

Expand Down
8 changes: 7 additions & 1 deletion sphinx_needs/directives/needpie.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from sphinx_needs.config import NeedsSphinxConfig
from sphinx_needs.data import SphinxNeedsData
from sphinx_needs.debug import measure_time
from sphinx_needs.directives.utils import no_needs_found_paragraph
from sphinx_needs.filter_common import FilterBase, filter_needs, prepare_need_list
from sphinx_needs.logging import get_logger
from sphinx_needs.utils import (
Expand Down Expand Up @@ -47,6 +48,7 @@ class NeedpieDirective(FilterBase):
"text_color": directives.unchanged_required,
"shadow": directives.flag,
"filter-func": FilterBase.base_option_spec["filter-func"],
"filter_warning": FilterBase.base_option_spec["filter_warning"],
}

# Update the options_spec only with value filter-func defined in the FilterBase class
Expand Down Expand Up @@ -94,6 +96,7 @@ def run(self) -> Sequence[nodes.Node]:
"shadow": shadow,
"text_color": text_color,
"filter_func": self.collect_filter_attributes()["filter_func"],
"filter_warning": self.collect_filter_attributes()["filter_warning"],
}
add_doc(env, env.docname)

Expand Down Expand Up @@ -273,7 +276,10 @@ def process_needpie(app: Sphinx, doctree: nodes.document, fromdocname: str, foun
# Add lineno to node
image_node.line = current_needpie["lineno"]

node.replace_self(image_node)
if len(sizes) == 0 or all(s == 0 for s in sizes):
node.replace_self(no_needs_found_paragraph(current_needpie.get("filter_warning")))
else:
node.replace_self(image_node)

# Cleanup matplotlib
# Reset the style configuration:
Expand Down
9 changes: 3 additions & 6 deletions sphinx_needs/directives/needsequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
get_filter_para,
no_plantuml,
)
from sphinx_needs.directives.utils import no_needs_found_paragraph
from sphinx_needs.filter_common import FilterBase
from sphinx_needs.logging import get_logger
from sphinx_needs.utils import add_doc, remove_node_from_tree
Expand Down Expand Up @@ -209,12 +210,8 @@ def process_needsequence(

content.append(puml_node)

if len(content) == 0:
nothing_found = "No needs passed the filters"
para = nodes.paragraph()
nothing_found_node = nodes.Text(nothing_found)
para += nothing_found_node
content.append(para)
if len(c_string) == 0 and p_string.count("participant") == 1: # no connections and just one (start) participant
content = [(no_needs_found_paragraph(current_needsequence.get("filter_warning")))]
if current_needsequence["show_filters"]:
content.append(get_filter_para(current_needsequence))

Expand Down
20 changes: 10 additions & 10 deletions sphinx_needs/directives/needtable.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,15 @@ def sort(need: NeedsInfoType) -> Any:
tbody += row

if len(filtered_needs) == 0:
table_node.append(no_needs_found_paragraph())

content = no_needs_found_paragraph(current_needtable.get("filter_warning"))
else:
# Put the table in a div-wrapper, so that we can control overflow / scroll layout
if style == "TABLE":
table_wrapper = nodes.container(classes=["needstable_wrapper"])
table_wrapper.insert(0, table_node)
content = table_wrapper
else:
content = table_node
# add filter information to output
if current_needtable["show_filters"]:
table_node.append(used_filter_paragraph(current_needtable))
Expand All @@ -329,11 +336,4 @@ def sort(need: NeedsInfoType) -> Any:
title = nodes.title(title_text, "", nodes.Text(title_text))
table_node.insert(0, title)

# Put the table in a div-wrapper, so that we can control overflow / scroll layout
if style == "TABLE":
table_wrapper = nodes.container(classes=["needstable_wrapper"])
table_wrapper.insert(0, table_node)
node.replace_self(table_wrapper)

else:
node.replace_self(table_node)
node.replace_self(content)
7 changes: 4 additions & 3 deletions sphinx_needs/directives/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import re
from typing import Any, Dict, List, Tuple
from typing import Any, Dict, List, Optional, Tuple

from docutils import nodes
from sphinx.environment import BuildEnvironment
Expand All @@ -9,9 +9,10 @@
from sphinx_needs.defaults import TITLE_REGEX


def no_needs_found_paragraph() -> nodes.paragraph:
nothing_found = "No needs passed the filters"
def no_needs_found_paragraph(message: Optional[str]) -> nodes.paragraph:
nothing_found = "No needs passed the filters" if message is None else message
para = nodes.paragraph()
para["classes"].append("needs_filter_warning")
nothing_found_node = nodes.Text(nothing_found)
para += nothing_found_node
return para
Expand Down
3 changes: 3 additions & 0 deletions sphinx_needs/filter_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class FilterAttributesType(TypedDict):
filter_code: list[str]
filter_func: str
export_id: str
filter_warning: str
"""If set, the filter is exported with this ID in the needs.json file."""


Expand All @@ -48,6 +49,7 @@ class FilterBase(SphinxDirective):
"filter-func": directives.unchanged_required,
"sort_by": directives.unchanged,
"export_id": directives.unchanged,
"filter_warning": directives.unchanged,
}

def collect_filter_attributes(self) -> FilterAttributesType:
Expand Down Expand Up @@ -83,6 +85,7 @@ def collect_filter_attributes(self) -> FilterAttributesType:
"filter_code": self.content,
"filter_func": self.options.get("filter-func"),
"export_id": self.options.get("export_id", ""),
"filter_warning": self.options.get("filter_warning"),
}
return collected_filter_options

Expand Down
23 changes: 22 additions & 1 deletion tests/doc_test/filter_doc/conf.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
extensions = ["sphinx_needs"]
import os

extensions = ["sphinx_needs", "sphinxcontrib.plantuml"]

# note, the plantuml executable command is set globally in the test suite
plantuml_output_format = "svg"

needs_id_regex = "^[A-Za-z0-9_]"

Expand All @@ -8,4 +13,20 @@
{"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"},
{"directive": "user", "title": "User", "prefix": "U_", "color": "#777777", "style": "node"},
{"directive": "action", "title": "Action", "prefix": "A_", "color": "#FFCC00", "style": "node"},
]

needs_extra_links = [
{
"option": "triggers",
"incoming": "triggered by",
"outgoing": "triggers",
"copy": False,
"style": "#00AA00",
"style_part": "solid,#777777",
"allow_dead_links": True,
},
]

needs_css = os.path.join(os.path.dirname(__file__), "filter.css")
5 changes: 5 additions & 0 deletions tests/doc_test/filter_doc/filter.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

p.needs_filter_warning {
background-color: grey;
font-weight: bolder;
}

0 comments on commit 73b961e

Please sign in to comment.