diff --git a/docs/directives/needextend.rst b/docs/directives/needextend.rst index b6799ce1b..b2f56651f 100644 --- a/docs/directives/needextend.rst +++ b/docs/directives/needextend.rst @@ -92,16 +92,22 @@ Default: false We have a configuration (conf.py) option called :ref:`needs_needextend_strict` that deactivates or activates the ``:strict:`` option behaviour for all ``needextend`` directives in a project. -Setting default option values ------------------------------ +Extending needs in current page +------------------------------- + +.. versionadded:: 4.3.0 + +Additionally, to common :ref:`filter_string` variables, the ``c.this_doc()`` function is made available, +to filter for needs only in the same document as the ``needextend``. + You can use ``needextend``'s filter string to set default option values for a group of needs. -The following example would set the status of all needs in the document -``docs/directives/needextend.rst``, which do not have the status set explicitly, to ``open``. +The following example would set the status of all needs in the current document, +which do not have the status set explicitly, to ``open``. -.. code-block:: rst +.. need-example:: - .. needextend:: (docname == "docs/directives/needextend") and (status is None) + .. needextend:: c.this_doc() and status is None :status: open See also: :ref:`needs_global_options` for setting a default option value for all needs. @@ -177,5 +183,5 @@ Also filtering for these values is supported: .. needtable:: :filter: "needextend" in title - :columns: id, title, is_modified, modifications + :columns: id, title, status, is_modified, modifications :style: table diff --git a/sphinx_needs/directives/needextend.py b/sphinx_needs/directives/needextend.py index e1c485415..4ef01ffe9 100644 --- a/sphinx_needs/directives/needextend.py +++ b/sphinx_needs/directives/needextend.py @@ -120,7 +120,11 @@ def extend_needs_data( else: try: found_needs = filter_needs_mutable( - all_needs, needs_config, need_filter, location=location + all_needs, + needs_config, + need_filter, + location=location, + origin_docname=current_needextend["docname"], ) except Exception as e: log_warning( diff --git a/sphinx_needs/filter_common.py b/sphinx_needs/filter_common.py index 48821b8da..eb4143fba 100644 --- a/sphinx_needs/filter_common.py +++ b/sphinx_needs/filter_common.py @@ -283,6 +283,7 @@ def filter_needs_mutable( *, location: tuple[str, int | None] | nodes.Node | None = None, append_warning: str = "", + origin_docname: str | None = None, ) -> list[NeedsInfoType]: return filter_needs( needs.values(), @@ -291,6 +292,7 @@ def filter_needs_mutable( current_need, location=location, append_warning=append_warning, + origin_docname=origin_docname, ) @@ -489,6 +491,7 @@ def filter_needs( *, location: tuple[str, int | None] | nodes.Node | None = None, append_warning: str = "", + origin_docname: str | None = None, ) -> list[NeedsInfoType]: """ Filters given needs based on a given filter string. @@ -520,6 +523,7 @@ def filter_needs( needs, current_need, filter_compiled=filter_compiled, + origin_docname=origin_docname, ): found_needs.append(filter_need) except Exception as e: @@ -549,16 +553,20 @@ def filter_single_need( needs: Iterable[NeedsInfoType] | None = None, current_need: NeedsInfoType | None = None, filter_compiled: CodeType | None = None, + *, + origin_docname: str | None = None, ) -> bool: """ Checks if a single need/need_part passes a filter_string :param need: the data for a single need :param config: NeedsSphinxConfig object - :param filter_compiled: An already compiled filter_string to safe time - :param need: need or need_part :param filter_string: string, which is used as input for eval() :param needs: list of all needs + :param current_need: set the current_need in the filter context as this, otherwise the need itself + :param filter_compiled: An already compiled filter_string to save time + :param origin_docname: The origin docname that the filter was called from, if any + :return: True, if need passes the filter_string, else False """ filter_context: dict[str, Any] = need.copy() # type: ignore @@ -573,6 +581,9 @@ def filter_single_need( filter_context.update(config.filter_data) filter_context["search"] = need_search + + filter_context["c"] = NeedCheckContext(need, origin_docname) + result = False try: # Set filter_context as globals and not only locals in eval()! @@ -588,3 +599,18 @@ def filter_single_need( except Exception as e: raise NeedsInvalidFilter(f"Filter {filter_string!r} not valid. Error: {e}.") return result + + +class NeedCheckContext: + """A namespace for filter checks of the current need.""" + + __slots__ = ("_need", "_origin_docname") + + def __init__(self, need: NeedsInfoType, origin_docname: str | None) -> None: + self._need = need + self._origin_docname = origin_docname + + def this_doc(self) -> bool: + if self._origin_docname is None: + raise ValueError("`this_doc` can not be used in this context") + return self._need["docname"] == self._origin_docname diff --git a/tests/doc_test/doc_needextend/page_1.rst b/tests/doc_test/doc_needextend/page_1.rst index 3b0d7e8c0..8223c98a0 100644 --- a/tests/doc_test/doc_needextend/page_1.rst +++ b/tests/doc_test/doc_needextend/page_1.rst @@ -7,6 +7,6 @@ need objects :status: open :tags: tag_1 -.. needextend:: extend_test_page +.. needextend:: "c.this_doc()" :status: closed diff --git a/tests/test_needextend.py b/tests/test_needextend.py index 2dc278692..2cc7cb8cf 100644 --- a/tests/test_needextend.py +++ b/tests/test_needextend.py @@ -10,13 +10,15 @@ @pytest.mark.parametrize( "test_app", - [{"buildername": "html", "srcdir": "doc_test/doc_needextend"}], + [{"buildername": "html", "srcdir": "doc_test/doc_needextend", "no_plantuml": True}], indirect=True, ) def test_doc_needextend_html(test_app: Sphinx, snapshot): app = test_app app.build() + assert not app._warning.getvalue() + needs_data = json.loads(Path(app.outdir, "needs.json").read_text()) assert needs_data == snapshot(exclude=props("created", "project", "creator"))