Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Improve logic for process_needextend #1037

Merged
merged 8 commits into from
Sep 26, 2023
Merged
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
14 changes: 12 additions & 2 deletions sphinx_needs/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,10 @@ class NeedsInfoType(NeedsBaseDataType):
"""Hexadecimal color code of the type."""
type_style: str

# Used by needextend
is_modified: bool
"""Whether the need was modified by needextend."""
modifications: int
"""Number of modifications by needextend."""

# parts information
parts: dict[str, NeedsPartType]
Expand Down Expand Up @@ -257,11 +258,20 @@ class NeedsBarType(NeedsBaseDataType):


class NeedsExtendType(NeedsBaseDataType):
"""Data to modify an existing need."""
"""Data to modify existing need(s)."""

filter: None | str
"""Single need ID or filter string to select multiple needs."""
modifications: dict[str, str]
"""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).
"""
strict: bool
"""If ``filter`` conforms to ``needs_id_regex``,
and is not an existing need ID,
whether to except the build (otherwise log-info message is written).
"""


class NeedsFilteredBaseType(NeedsBaseDataType):
Expand Down
100 changes: 48 additions & 52 deletions sphinx_needs/directives/needextend.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) -
if not workflow["needs_extended"]:
workflow["needs_extended"] = True

list_names = (
list_values = (
["tags", "links"]
+ [x["option"] for x in needs_config.extra_links]
+ [f"{x['option']}_back" for x in needs_config.extra_links]
Expand All @@ -93,20 +93,20 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) -
all_needs = data.get_or_create_needs()

for current_needextend in data.get_or_create_extends().values():
# Check if filter is just a need-id.
# In this case create the needed filter string
need_filter = current_needextend["filter"]
if need_filter in all_needs:
# a single known ID
found_needs = [all_needs[need_filter]]
# If it looks like a need id, but we haven't found one, raise an exception
elif need_filter is not None and re.fullmatch(needs_config.id_regex, need_filter):
# an unknown ID
error = f"Provided id {need_filter} for needextend does not exist."
if current_needextend["strict"]:
raise NeedsInvalidFilter(error)
else:
logger.info(error)
continue
else:
# a filter string
try:
found_needs = filter_needs(app, all_needs.values(), need_filter)
except NeedsInvalidFilter as e:
Expand All @@ -123,65 +123,61 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) -
for option, value in current_needextend["modifications"].items():
if option.startswith("+"):
option_name = option[1:]

# If we need to handle a list
if option_name in list_names:
for link in re.split(";|,", value):
# Remove whitespaces
link = link.strip()
if link not in need[option_name]:
need[option_name].append(link)

# If we manipulate links, we need to set all the reference in the target need
# under e.g. links_back
if option_name in link_names:
for ref_need in re.split(";|,", value):
# Remove whitespaces
ref_need = ref_need.strip()
if found_need["id"] not in all_needs[ref_need][f"{option_name}_back"]:
all_needs[ref_need][f"{option_name}_back"] += [found_need["id"]]

# else it must be a normal string
if option_name in link_names:
# If we add links, then add all corresponding back links
for ref_need in [i.strip() for i in re.split(";|,", value)]:
if ref_need not in all_needs:
logger.warning(
f"Provided link id {ref_need} for needextend does not exist. [needs]",
type="needs",
location=(current_needextend["docname"], current_needextend["lineno"]),
)
continue
if ref_need not in need[option_name]:
need[option_name].append(ref_need)
if found_need["id"] not in all_needs[ref_need][f"{option_name}_back"]:
all_needs[ref_need][f"{option_name}_back"] += [found_need["id"]]
elif option_name in list_values:
for item in [i.strip() for i in re.split(";|,", value)]:
if item not in need[option_name]:
need[option_name].append(item)
else:
# If content is already stored, we need to add some whitespace
if need[option_name]:
# If content is already stored, we need to add some whitespace
need[option_name] += " "
need[option_name] += value

elif option.startswith("-"):
option_name = option[1:]
if option_name in list_names:
old_content = need[option_name] # Save it, as it may be need to identify referenced needs
if option_name in link_names:
# If we remove links, then remove all corresponding back links
for ref_need in (i for i in need[option_name] if i in all_needs):
all_needs[ref_need][f"{option_name}_back"].remove(found_need["id"])
need[option_name] = []
if option_name in list_values:
need[option_name] = []

# If we manipulate links, we need to delete the reference in the target need as well
if option_name in link_names:
for ref_need in old_content: # There may be several links
all_needs[ref_need][f"{option_name}_back"].remove(found_need["id"])

else:
need[option_name] = ""
else:
if option in list_names:
old_content = need[option].copy()

if option in link_names:
# If we change links, then modify all corresponding back links
for ref_need in (i for i in need[option] if i in all_needs):
all_needs[ref_need][f"{option}_back"].remove(found_need["id"])
need[option] = []
for link in re.split(";|,", value):
# Remove whitespaces
link = link.strip()
if link not in need[option]:
need[option].append(link)

# If add new links also as "link_s_back" to the referenced need.
if option in link_names:
# Remove old links
for ref_need in old_content: # There may be several links
all_needs[ref_need][f"{option}_back"].remove(found_need["id"])

# Add new links
for ref_need in need[option]: # There may be several links
if found_need["id"] not in all_needs[ref_need][f"{option}_back"]:
all_needs[ref_need][f"{option}_back"] += [found_need["id"]]

for ref_need in [i.strip() for i in re.split(";|,", value)]:
if ref_need not in all_needs:
logger.warning(
f"Provided link id {ref_need} for needextend does not exist. [needs]",
type="needs",
location=(current_needextend["docname"], current_needextend["lineno"]),
)
continue
need[option].append(ref_need)
for ref_need in need[option]:
if found_need["id"] not in all_needs[ref_need][f"{option}_back"]:
all_needs[ref_need][f"{option}_back"] += [found_need["id"]]
elif option in list_values:
need[option] = [i.strip() for i in re.split(";|,", value)]
else:
need[option] = value

Expand Down
Loading