From 05652f70eab4fe05327b8d58531be2a6f6f39683 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Mon, 27 Jan 2025 14:12:11 +0100 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Make=20`needextend`=20argu?= =?UTF-8?q?ment=20deterministic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/directives/needextend.rst | 46 +++++++++--------- sphinx_needs/data.py | 8 +-- sphinx_needs/directives/needextend.py | 70 +++++++++++++-------------- 3 files changed, 61 insertions(+), 63 deletions(-) diff --git a/docs/directives/needextend.rst b/docs/directives/needextend.rst index dd0e4ed64..b6799ce1b 100644 --- a/docs/directives/needextend.rst +++ b/docs/directives/needextend.rst @@ -4,7 +4,7 @@ needextend ========== .. versionadded:: 0.7.0 -``needextend`` allows to modify existing needs. It doesn’t provide any output, as the modifications +``needextend`` allows to modify existing needs. It doesn't provide any output, as the modifications get presented at the original location of the changing need, for example: .. code-block:: rst @@ -20,7 +20,12 @@ The following modifications are supported: * ``+option``: add new value to an existing value of an option. * ``-option``: delete a complete option. -The argument of ``needextend`` must be a :ref:`filter_string` which defines the needs to modify. +The argument of ``needextend`` will be taken as, by order of priority: + +- a single need ID, if it is enclosed by ``<>``, +- a :ref:`filter_string` if it is enclosed by ``""``, +- a single need ID, if it is a single word (no spaces), +- a :ref:`filter_string` otherwise. ``needextend`` can modify all string-based and list-based options. Also, you can add links or delete tags. @@ -40,11 +45,26 @@ Also, you can add links or delete tags. | And a tag was added. | Finally all links got removed. - .. needextend:: id == "extend_test_001" + .. req:: needextend Example 2 + :id: extend_test_002 + :tags: extend_example + :status: open + + Contents + + .. needextend:: extend_test_001 :status: closed :+author: and me + + .. needextend:: :+tags: new_tag + .. needextend:: id == "extend_test_002" + :status: New status + + .. needextend:: ""extend_example" in tags" + :+tags: other + Options ------- @@ -72,26 +92,6 @@ 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. - -Single need modification ------------------------- -If only one single need shall get modified, the argument of ``needextend`` can just be the need-id. - -.. need-example:: - - .. req:: needextend Example 2 - :id: extend_test_002 - :status: open - - .. needextend:: extend_test_002 - :status: New status - -.. attention:: - - The given argument must fully match the regular expression defined in - :ref:`needs_id_regex` and a need with this ID must exist! - Otherwise the argument is taken as normal filter string. - Setting default option values ----------------------------- You can use ``needextend``'s filter string to set default option values for a group of needs. diff --git a/sphinx_needs/data.py b/sphinx_needs/data.py index 2260572cd..ec248864b 100644 --- a/sphinx_needs/data.py +++ b/sphinx_needs/data.py @@ -566,9 +566,11 @@ class NeedsBarType(NeedsBaseDataType): class NeedsExtendType(NeedsBaseDataType): """Data to modify existing need(s).""" - filter: None | str - """Single need ID or filter string to select multiple needs.""" - modifications: dict[str, str] + filter: str + """Filter string to select needs to extebd.""" + filter_is_id: bool + """Whether the filter is a single need ID.""" + modifications: dict[str, Any] """Mapping of field name to new value. If the field name starts with a ``+``, the new value is appended to the existing value. If the field name starts with a ``-``, the existing value is cleared (new value is ignored). diff --git a/sphinx_needs/directives/needextend.py b/sphinx_needs/directives/needextend.py index 9db1e9d08..c2620c561 100644 --- a/sphinx_needs/directives/needextend.py +++ b/sphinx_needs/directives/needextend.py @@ -1,6 +1,5 @@ from __future__ import annotations -import re from collections.abc import Sequence from typing import Any, Callable @@ -54,20 +53,37 @@ def run(self) -> Sequence[nodes.Node]: f"Filter of needextend must be set. See {env.docname}:{self.lineno}" ) - strict = NeedsSphinxConfig(self.env.app.config).needextend_strict + needs_config = NeedsSphinxConfig(self.env.app.config) + strict = needs_config.needextend_strict strict_option: str = self.options.get("strict", "").upper() if strict_option == "TRUE": strict = True elif strict_option == "FALSE": strict = False + modifications = self.options.copy() + modifications.pop("strict", None) + + extend_filter = extend_filter.strip() + if extend_filter.startswith("<") and extend_filter.endswith(">"): + filter_is_id = True + extend_filter = extend_filter[1:-1] + elif extend_filter.startswith('"') and extend_filter.endswith('"'): + filter_is_id = False + extend_filter = extend_filter[1:-1] + elif len(extend_filter.split()) == 1: + filter_is_id = True + else: + filter_is_id = False + data = SphinxNeedsData(env).get_or_create_extends() data[targetid] = { "docname": env.docname, "lineno": self.lineno, "target_id": targetid, - "filter": self.arguments[0] if self.arguments else None, - "modifications": self.options, + "filter": extend_filter, + "filter_is_id": filter_is_id, + "modifications": modifications, "strict": strict, } @@ -91,48 +107,28 @@ def extend_needs_data( for current_needextend in extends.values(): need_filter = current_needextend["filter"] - if need_filter and need_filter in all_needs: - # a single known ID - found_needs = [all_needs[need_filter]] - elif need_filter is not None and re.fullmatch( - needs_config.id_regex, need_filter - ): - # an unknown ID - error = f"Provided id {need_filter!r} for needextend does not exist." - if current_needextend["strict"]: - raise NeedsInvalidFilter(error) - else: - log_warning( - logger, - error, - "needextend", - location=( - current_needextend["docname"], - current_needextend["lineno"], - ), - ) - continue + location = (current_needextend["docname"], current_needextend["lineno"]) + if current_needextend["filter_is_id"]: + try: + found_needs = [all_needs[need_filter]] + except KeyError: + error = f"Provided id {need_filter!r} for needextend does not exist." + if current_needextend["strict"]: + raise NeedsInvalidFilter(error) + else: + log_warning(logger, error, "needextend", location=location) + continue else: - # a filter string try: found_needs = filter_needs_mutable( - all_needs, - needs_config, - need_filter, - location=( - current_needextend["docname"], - current_needextend["lineno"], - ), + all_needs, needs_config, need_filter, location=location ) except NeedsInvalidFilter as e: log_warning( logger, f"Invalid filter {need_filter!r}: {e}", "needextend", - location=( - current_needextend["docname"], - current_needextend["lineno"], - ), + location=location, ) continue