Skip to content

Commit

Permalink
👌 synchronise external/import need code (#1299)
Browse files Browse the repository at this point in the history
Synchronise the code for preparing needs parameters, in both `needimport` and `external_needs`.

Additionally, allow propagation of `signature`/`sections` fields for external needs.
  • Loading branch information
chrisjsewell authored Sep 13, 2024
1 parent db66f57 commit 44d7db9
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 92 deletions.
14 changes: 8 additions & 6 deletions sphinx_needs/api/need.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ def add_need(
status: str | None = None,
tags: None | str | list[str] = None,
constraints: None | str | list[str] = None,
signature: str = "",
sections: list[str] | None = None,
delete: None | bool = False,
jinja_content: None | bool = False,
hide: bool = False,
Expand Down Expand Up @@ -131,7 +133,7 @@ def run():
:param content: Content of the need, either as a ``str``
or a ``StringList`` (a string with mapping to the source text).
:param status: Status as string.
:param tags: Tags as single string.
:param tags: A list of tags, or a comma separated string.
:param constraints: Constraints as single, comma separated, string.
:param constraints_passed: Contains bool describing if all constraints have passed
:param delete: boolean value (Remove the complete need).
Expand Down Expand Up @@ -333,9 +335,9 @@ def run():
"modifications": 0,
"has_dead_links": False,
"has_forbidden_dead_links": False,
"sections": [],
"section_name": "",
"signature": "",
"sections": sections or [],
"section_name": sections[0] if sections else "",
"signature": signature,
"parent_need": "",
}
needs_extra_option_names = list(NEEDS_CONFIG.extra_options)
Expand Down Expand Up @@ -570,7 +572,7 @@ def add_external_need(
external_css: str = "external_link",
content: str = "",
status: str | None = None,
tags: str | None = None,
tags: str | list[str] | None = None,
constraints: str | None = None,
**kwargs: Any,
) -> list[nodes.Node]:
Expand All @@ -589,7 +591,7 @@ def add_external_need(
:param external_url: URL as string, which shall be used as link to the original need source
:param content: Content as single string.
:param status: Status as string.
:param tags: Tags as single string.
:param tags: A list of tags, or a comma separated string.
:param constraints: constraints as single, comma separated string.
:param external_css: CSS class name as string, which is set for the <a> tag.
Expand Down
92 changes: 37 additions & 55 deletions sphinx_needs/directives/needimport.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from sphinx_needs.filter_common import filter_single_need
from sphinx_needs.logging import log_warning
from sphinx_needs.needsfile import check_needs_file
from sphinx_needs.utils import add_doc, logger
from sphinx_needs.utils import add_doc, import_prefix_link_edit, logger


class Needimport(nodes.General, nodes.Element):
Expand Down Expand Up @@ -183,86 +183,68 @@ def run(self) -> Sequence[nodes.Node]:

needs_list = needs_list_filtered

# If we need to set an id prefix, we also need to manipulate all used ids in the imported data.
extra_links = needs_config.extra_links
if id_prefix:
for need in needs_list.values():
for id in needs_list:
# Manipulate links in all link types
for extra_link in extra_links:
if (
extra_link["option"] in need
and id in need[extra_link["option"]] # type: ignore[literal-required]
):
for n, link in enumerate(need[extra_link["option"]]): # type: ignore[literal-required]
if id == link:
need[extra_link["option"]][n] = "".join( # type: ignore[literal-required]
[id_prefix, id]
)
# Manipulate descriptions
# ToDo: Use regex for better matches.
need["description"] = need["description"].replace( # type: ignore[typeddict-item]
id, "".join([id_prefix, id])
)

# tags update
for need in needs_list.values():
need["tags"] = need["tags"] + tags

import_prefix_link_edit(needs_list, id_prefix, needs_config.extra_links)

override_options = (
"collapse",
"style",
"layout",
"template",
"pre_template",
"post_template",
)
known_options = (
# need general parameters
"need_type",
"title",
"status",
"content",
"id",
"content",
"status",
"tags",
"constraints",
# need render parameters
"jinja_content",
"hide",
"template",
"pre_template",
"post_template",
"collapse",
"style",
"layout",
"need_type",
"constraints",
*[x["option"] for x in extra_links],
"template",
"pre_template",
"post_template",
# note we omit locational parameters, such as signature and sections
# since these will be computed again for the new location
*[x["option"] for x in needs_config.extra_links],
*NEEDS_CONFIG.extra_options,
)
need_nodes = []
for need in needs_list.values():
# Set some values based on given option or value from imported need.
need["template"] = self.options.get("template", need.get("template"))
need["pre_template"] = self.options.get(
"pre_template", need.get("pre_template")
)
need["post_template"] = self.options.get(
"post_template", need.get("post_template")
)
need["layout"] = self.options.get("layout", need.get("layout"))
need["style"] = self.options.get("style", need.get("style"))

for need_params in needs_list.values():
for override_option in override_options:
if override_option in self.options:
need_params[override_option] = self.options[override_option] # type: ignore[literal-required]
if "hide" in self.options:
need["hide"] = True
else:
need["hide"] = need.get("hide", False)
need["collapse"] = self.options.get("collapse", need.get("collapse"))
need_params["hide"] = True

# The key needs to be different for add_need() api call.
need["need_type"] = need["type"] # type: ignore[typeddict-unknown-key]
need_params["need_type"] = need_params["type"] # type: ignore[typeddict-unknown-key]

# Replace id, to get unique ids
need["id"] = id_prefix + need["id"]
need_params["id"] = id_prefix + need_params["id"]

need["content"] = need["description"] # type: ignore[typeddict-item]
need_params["content"] = need_params["description"] # type: ignore[typeddict-item]

# Remove unknown options, as they may be defined in source system, but not in this sphinx project
for option in list(need):
for option in list(need_params):
if option not in known_options:
del need[option] # type: ignore
del need_params[option] # type: ignore

need["docname"] = self.docname
need["lineno"] = self.lineno
need_params["docname"] = self.docname
need_params["lineno"] = self.lineno

nodes = add_need(self.env.app, self.state, **need) # type: ignore[call-arg]
nodes = add_need(self.env.app, self.state, **need_params) # type: ignore[call-arg]
need_nodes.extend(nodes)

add_doc(self.env, self.env.docname)
Expand Down
57 changes: 31 additions & 26 deletions sphinx_needs/external_needs.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,31 +114,37 @@ def load_external_needs(app: Sphinx, env: BuildEnvironment, docname: str) -> Non
else {}
)

prefix = source.get("id_prefix", "").upper()
import_prefix_link_edit(needs, prefix, needs_config.extra_links)
id_prefix = source.get("id_prefix", "").upper()
import_prefix_link_edit(needs, id_prefix, needs_config.extra_links)

known_options = (
# need general parameters
"need_type",
"title",
"id",
"content",
"status",
"tags",
"constraints",
# need locational parameters
"signature",
"sections",
# note we omit render parameters, such as style and layout
"external_css",
"external_url",
*[x["option"] for x in needs_config.extra_links],
*needs_config.extra_options, # TODO should this be NEEDS_CONFIG.extra_options?
)

for need in needs.values():
need_params = {**defaults, **need}

extra_links = [x["option"] for x in needs_config.extra_links]
for key in list(need_params.keys()):
if (
key not in needs_config.extra_options
and key not in extra_links
and key
not in [
"title",
"type",
"id",
"description",
"tags",
"docname",
"status",
]
):
del need_params[key]

# The key needs to be different for add_need() api call.
need_params["need_type"] = need["type"]
need_params["id"] = f'{prefix}{need["id"]}'

# Replace id, to get unique ids
need_params["id"] = id_prefix + need["id"]

need_params["external_css"] = source.get("css_class")

if target_url:
Expand All @@ -152,12 +158,11 @@ def load_external_needs(app: Sphinx, env: BuildEnvironment, docname: str) -> Non
)

need_params["content"] = need["description"]
need_params["links"] = need.get("links", [])
need_params["tags"] = ",".join(need.get("tags", []))
need_params["status"] = need.get("status")
need_params["constraints"] = need.get("constraints", [])

del need_params["description"]
# Remove unknown options, as they may be defined in source system, but not in this sphinx project
for option in list(need_params):
if option not in known_options:
del need_params[option]

# check if external needs already exist
ext_need_id = need_params["id"]
Expand Down
10 changes: 8 additions & 2 deletions tests/__snapshots__/test_external.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
'full_title': 'REQ_01',
'id': 'EXT_REQ_01',
'is_external': True,
'section_name': 'Title',
'sections': list([
'Title',
]),
'target_id': 'EXT_REQ_01',
'title': 'REQ_01',
'type': 'req',
Expand Down Expand Up @@ -598,11 +602,13 @@
'pre_template': None,
'prefix': '',
'query': '',
'section_name': '',
'section_name': 'Heading',
'sections': list([
'Heading',
'Sub-heading',
]),
'service': '',
'signature': '',
'signature': 'a signature',
'specific': '',
'status': 'open',
'style': None,
Expand Down
6 changes: 3 additions & 3 deletions tests/doc_test/external_doc/needs_test_small.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@
"post_template": null,
"pre_template": null,
"query": "",
"section_name": "",
"sections": [],
"section_name": "Heading",
"sections": ["Heading", "Sub-heading"],
"service": "",
"signature": "",
"signature": "a signature",
"specific": "",
"status": "open",
"style": null,
Expand Down

0 comments on commit 44d7db9

Please sign in to comment.