Skip to content

Commit f6b090a

Browse files
authored
🐛 FIX: Centralise splitting of need ID (useblocks#1101)
Ensure the splitting of a need ID, on a `.` into `main.part` IDs, cannot except (and remove duplication of the logic) closes useblocks#1096
1 parent d74f4de commit f6b090a

File tree

4 files changed

+59
-58
lines changed

4 files changed

+59
-58
lines changed

sphinx_needs/directives/need.py

+18-25
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from sphinx_needs.logging import get_logger
2929
from sphinx_needs.need_constraints import process_constraints
3030
from sphinx_needs.nodes import Need
31-
from sphinx_needs.utils import add_doc, profile, remove_node_from_tree
31+
from sphinx_needs.utils import add_doc, profile, remove_node_from_tree, split_need_id
3232

3333
logger = get_logger(__name__)
3434

@@ -440,17 +440,14 @@ def check_links(needs: Dict[str, NeedsInfoType], config: NeedsSphinxConfig) -> N
440440
for need in needs.values():
441441
for link_type in extra_links:
442442
dead_links_allowed = link_type.get("allow_dead_links", False)
443-
need_link_value = (
443+
need_link_value: List[str] = (
444444
[need[link_type["option"]]] if isinstance(need[link_type["option"]], str) else need[link_type["option"]] # type: ignore
445445
)
446-
for link in need_link_value:
447-
if "." in link:
448-
need_id, need_part_id = link.split(".")
449-
else:
450-
need_id = link
451-
need_part_id = None
452-
if need_id not in needs or (
453-
need_id in needs and need_part_id and need_part_id not in needs[need_id]["parts"]
446+
for need_id_full in need_link_value:
447+
need_id_main, need_id_part = split_need_id(need_id_full)
448+
449+
if need_id_main not in needs or (
450+
need_id_main in needs and need_id_part and need_id_part not in needs[need_id_main]["parts"]
454451
):
455452
need["has_dead_links"] = True
456453
if not dead_links_allowed:
@@ -469,23 +466,19 @@ def create_back_links(needs: Dict[str, NeedsInfoType], config: NeedsSphinxConfig
469466
option_back = f"{option}_back"
470467

471468
for key, need in needs.items():
472-
need_link_value = [need[option]] if isinstance(need[option], str) else need[option] # type: ignore[literal-required]
473-
for link in need_link_value:
474-
link_main = link.split(".")[0]
475-
try:
476-
link_part = link.split(".")[1]
477-
except IndexError:
478-
link_part = None
479-
480-
if link_main in needs:
481-
if key not in needs[link_main][option_back]: # type: ignore[literal-required]
482-
needs[link_main][option_back].append(key) # type: ignore[literal-required]
469+
need_link_value: List[str] = [need[option]] if isinstance(need[option], str) else need[option] # type: ignore[literal-required]
470+
for need_id_full in need_link_value:
471+
need_id_main, need_id_part = split_need_id(need_id_full)
472+
473+
if need_id_main in needs:
474+
if key not in needs[need_id_main][option_back]: # type: ignore[literal-required]
475+
needs[need_id_main][option_back].append(key) # type: ignore[literal-required]
483476

484477
# Handling of links to need_parts inside a need
485-
if link_part and link_part in needs[link_main]["parts"]:
486-
if option_back not in needs[link_main]["parts"][link_part].keys():
487-
needs[link_main]["parts"][link_part][option_back] = [] # type: ignore[literal-required]
488-
needs[link_main]["parts"][link_part][option_back].append(key) # type: ignore[literal-required]
478+
if need_id_part and need_id_part in needs[need_id_main]["parts"]:
479+
if option_back not in needs[need_id_main]["parts"][need_id_part].keys():
480+
needs[need_id_main]["parts"][need_id_part][option_back] = [] # type: ignore[literal-required]
481+
needs[need_id_main]["parts"][need_id_part][option_back].append(key) # type: ignore[literal-required]
489482

490483

491484
def _fix_list_dyn_func(list: List[str]) -> List[str]:

sphinx_needs/roles/need_outgoing.py

+16-19
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from sphinx_needs.data import SphinxNeedsData
99
from sphinx_needs.errors import NoUri
1010
from sphinx_needs.logging import get_logger
11-
from sphinx_needs.utils import check_and_calc_base_url_rel_path
11+
from sphinx_needs.utils import check_and_calc_base_url_rel_path, split_need_id
1212

1313
log = get_logger(__name__)
1414

@@ -41,24 +41,21 @@ def process_need_outgoing(
4141

4242
link_list = [links] if isinstance(links, str) else links
4343

44-
for index, link in enumerate(link_list):
45-
link_split = link.split(".")
46-
link = link_split[0]
47-
try:
48-
link_part = link_split[1]
49-
except IndexError:
50-
link_part = None
44+
for index, need_id_full in enumerate(link_list):
45+
need_id_main, need_id_part = split_need_id(need_id_full)
5146

5247
# If the need target exists, let's create the reference
53-
if (link in needs_all_needs and not link_part) or (
54-
link_part and link in needs_all_needs and link_part in needs_all_needs[link]["parts"]
48+
if (need_id_main in needs_all_needs and not need_id_part) or (
49+
need_id_part
50+
and need_id_main in needs_all_needs
51+
and need_id_part in needs_all_needs[need_id_main]["parts"]
5552
):
5653
try:
57-
target_need = needs_all_needs[link]
58-
if link_part and link_part in target_need["parts"]:
59-
part_content = target_need["parts"][link_part]["content"]
54+
target_need = needs_all_needs[need_id_main]
55+
if need_id_part and need_id_part in target_need["parts"]:
56+
part_content = target_need["parts"][need_id_part]["content"]
6057
target_title = part_content if len(part_content) < 30 else part_content[:27] + "..."
61-
target_id = ".".join([link, link_part])
58+
target_id = ".".join([need_id_main, need_id_part])
6259
else:
6360
target_title = target_need["title"]
6461
target_id = target_need["id"]
@@ -102,9 +99,9 @@ def process_need_outgoing(
10299
else:
103100
# Let's add a normal text here instead of a link.
104101
# So really each link set by the user gets shown.
105-
link_text = f"{link}"
106-
if link_part:
107-
link_text += f".{link_part}"
102+
link_text = f"{need_id_main}"
103+
if need_id_part:
104+
link_text += f".{need_id_part}"
108105
dead_link_text = nodes.Text(link_text)
109106
dead_link_para = nodes.inline(classes=["needs_dead_link"])
110107
dead_link_para.append(dead_link_text)
@@ -130,7 +127,7 @@ def process_need_outgoing(
130127
if node_need_ref and node_need_ref.line:
131128
log.log(
132129
log_level,
133-
f"linked need {link} not found "
130+
f"linked need {need_id_main} not found "
134131
f"(Line {node_need_ref.line} of file {node_need_ref.source}) [needs]",
135132
**kwargs,
136133
)
@@ -139,7 +136,7 @@ def process_need_outgoing(
139136
log_level,
140137
"outgoing linked need {} not found (document: {}, "
141138
"source need {} on line {} ) [needs]".format(
142-
link, ref_need["docname"], ref_need["id"], ref_need["lineno"]
139+
need_id_main, ref_need["docname"], ref_need["id"], ref_need["lineno"]
143140
),
144141
**kwargs,
145142
)

sphinx_needs/roles/need_ref.py

+9-14
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from sphinx_needs.data import NeedsInfoType, SphinxNeedsData
1111
from sphinx_needs.errors import NoUri
1212
from sphinx_needs.logging import get_logger
13-
from sphinx_needs.utils import check_and_calc_base_url_rel_path
13+
from sphinx_needs.utils import check_and_calc_base_url_rel_path, split_need_id
1414

1515
log = get_logger(__name__)
1616

@@ -71,25 +71,20 @@ def process_need_ref(app: Sphinx, doctree: nodes.document, fromdocname: str, fou
7171
prefix = "[["
7272
postfix = "]]"
7373

74-
ref_id_complete = node_need_ref["reftarget"]
74+
need_id_full = node_need_ref["reftarget"]
75+
need_id_main, need_id_part = split_need_id(need_id_full)
7576

76-
if "." in ref_id_complete:
77-
ref_id, part_id = ref_id_complete.split(".")
78-
else:
79-
ref_id = ref_id_complete
80-
part_id = None
81-
82-
if ref_id in all_needs:
83-
target_need = all_needs[ref_id]
77+
if need_id_main in all_needs:
78+
target_need = all_needs[need_id_main]
8479

8580
dict_need = transform_need_to_dict(target_need) # Transform a dict in a dict of {str, str}
8681

8782
# We set the id to the complete id maintained in node_need_ref["reftarget"]
88-
dict_need["id"] = ref_id_complete
83+
dict_need["id"] = need_id_full
8984

90-
if part_id:
85+
if need_id_part:
9186
# If part_id, we have to fetch the title from the content.
92-
dict_need["title"] = target_need["parts"][part_id]["content"]
87+
dict_need["title"] = target_need["parts"][need_id_part]["content"]
9388

9489
# Shorten title, if necessary
9590
max_length = needs_config.role_need_max_title_length
@@ -100,7 +95,7 @@ def process_need_ref(app: Sphinx, doctree: nodes.document, fromdocname: str, fou
10095

10196
ref_name: Union[None, str, nodes.Text] = node_need_ref.children[0].children[0] # type: ignore[assignment]
10297
# Only use ref_name, if it differs from ref_id
103-
if str(ref_id_complete) == str(ref_name):
98+
if str(need_id_full) == str(ref_name):
10499
ref_name = None
105100

106101
if ref_name and prefix in ref_name and postfix in ref_name:

sphinx_needs/utils.py

+16
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,22 @@ class NeedFunctionsType(TypedDict):
109109
]
110110

111111

112+
def split_need_id(need_id_full: str) -> Tuple[str, Optional[str]]:
113+
"""A need id can be a combination of a main id and a part id,
114+
split by a dot.
115+
This function splits them:
116+
If there is no dot, the part id is None,
117+
otherwise everything before the first dot is the main id,
118+
and everything after the first dot is the part id.
119+
"""
120+
if "." in need_id_full:
121+
need_id, need_part_id = need_id_full.split(".", maxsplit=1)
122+
else:
123+
need_id = need_id_full
124+
need_part_id = None
125+
return need_id, need_part_id
126+
127+
112128
def row_col_maker(
113129
app: Sphinx,
114130
fromdocname: str,

0 commit comments

Comments
 (0)