mirror of
https://github.com/safishamsi/graphify.git
synced 2026-09-02 19:55:59 +00:00
fix node label lookup normalization (#1353)
Fixes #1338 (Unicode NFD/NFC): serve._find_node now matches tokenized labels; affected.resolve_seed NFC-normalizes + casefolds. Reviewed: full suite 2087 passed, CLI smoke clean, no regressions. Thanks @balloon72.
This commit is contained in:
+10
-5
@@ -4,6 +4,7 @@ from collections import deque
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
import unicodedata
|
||||
|
||||
import networkx as nx
|
||||
|
||||
@@ -45,18 +46,22 @@ def _format_location(data: dict) -> str:
|
||||
|
||||
def _bare_name(label: str) -> str:
|
||||
"""Lowercased label with the callable decoration (trailing "()") removed."""
|
||||
label = label.lower()
|
||||
label = _normalize_label(label)
|
||||
return label[:-2] if label.endswith("()") else label
|
||||
|
||||
|
||||
def _normalize_label(label: str) -> str:
|
||||
return unicodedata.normalize("NFC", label).casefold()
|
||||
|
||||
|
||||
def resolve_seed(graph: nx.Graph, query: str) -> str | None:
|
||||
if query in graph:
|
||||
return query
|
||||
query_lower = query.lower()
|
||||
query_lower = _normalize_label(query)
|
||||
exact_label_matches = [
|
||||
str(node_id)
|
||||
for node_id, data in graph.nodes(data=True)
|
||||
if str(data.get("label", "")).lower() == query_lower
|
||||
if _normalize_label(str(data.get("label", ""))) == query_lower
|
||||
]
|
||||
if len(exact_label_matches) == 1:
|
||||
return exact_label_matches[0]
|
||||
@@ -74,14 +79,14 @@ def resolve_seed(graph: nx.Graph, query: str) -> str | None:
|
||||
exact_source_matches = [
|
||||
str(node_id)
|
||||
for node_id, data in graph.nodes(data=True)
|
||||
if str(data.get("source_file", "")).lower() == query_lower
|
||||
if _normalize_label(str(data.get("source_file", ""))) == query_lower
|
||||
]
|
||||
if len(exact_source_matches) == 1:
|
||||
return exact_source_matches[0]
|
||||
contains_matches = [
|
||||
str(node_id)
|
||||
for node_id, data in graph.nodes(data=True)
|
||||
if query_lower in str(data.get("label", "")).lower()
|
||||
if query_lower in _normalize_label(str(data.get("label", "")))
|
||||
]
|
||||
if len(contains_matches) == 1:
|
||||
return contains_matches[0]
|
||||
|
||||
+9
-3
@@ -464,12 +464,18 @@ def _find_node(G: nx.Graph, label: str) -> list[str]:
|
||||
for nid, d in G.nodes(data=True):
|
||||
norm_label = d.get("norm_label") or _strip_diacritics(d.get("label") or "").lower()
|
||||
bare_label = norm_label.rstrip("()")
|
||||
label_tokens = " ".join(_search_tokens(d.get("label") or ""))
|
||||
nid_lower = nid.lower()
|
||||
if term == norm_label or term == bare_label or term == nid_lower:
|
||||
if term == norm_label or term == bare_label or term == label_tokens or term == nid_lower:
|
||||
exact.append(nid)
|
||||
elif norm_label.startswith(term) or bare_label.startswith(term) or nid_lower.startswith(term):
|
||||
elif (
|
||||
norm_label.startswith(term)
|
||||
or bare_label.startswith(term)
|
||||
or label_tokens.startswith(term)
|
||||
or nid_lower.startswith(term)
|
||||
):
|
||||
prefix.append(nid)
|
||||
elif term in norm_label:
|
||||
elif term in norm_label or term in label_tokens:
|
||||
substring.append(nid)
|
||||
return exact + prefix + substring
|
||||
|
||||
|
||||
@@ -145,6 +145,27 @@ def test_resolve_seed_decorated_query_matches_bare_label():
|
||||
assert resolve_seed(graph, "Foo()") == "a"
|
||||
|
||||
|
||||
def test_resolve_seed_matches_unicode_normalized_label():
|
||||
import unicodedata
|
||||
|
||||
from graphify.affected import resolve_seed
|
||||
|
||||
graph = nx.DiGraph()
|
||||
graph.add_node("a", label="Auditoría", source_file="pkg/auditoria.py")
|
||||
|
||||
assert resolve_seed(graph, unicodedata.normalize("NFD", "Auditoría")) == "a"
|
||||
|
||||
|
||||
def test_resolve_seed_preserves_distinct_accents():
|
||||
from graphify.affected import resolve_seed
|
||||
|
||||
graph = nx.DiGraph()
|
||||
graph.add_node("a", label="resume", source_file="pkg/resume.py")
|
||||
graph.add_node("b", label="résumé", source_file="pkg/resume_accented.py")
|
||||
|
||||
assert resolve_seed(graph, "resume") == "a"
|
||||
|
||||
|
||||
def test_resolve_seed_bare_name_tie_still_returns_none():
|
||||
from graphify.affected import resolve_seed
|
||||
|
||||
|
||||
@@ -123,6 +123,13 @@ def test_find_node_ignores_trailing_punctuation():
|
||||
assert _find_node(G, "extract?") == ["n1"]
|
||||
|
||||
|
||||
def test_find_node_matches_full_punctuated_unicode_label():
|
||||
G = nx.Graph()
|
||||
G.add_node("n1", label="Skill /auditar — Auditoría inquisitiva de enlaces")
|
||||
|
||||
assert _find_node(G, "Skill /auditar — Auditoría inquisitiva de enlaces") == ["n1"]
|
||||
|
||||
|
||||
def test_query_terms_strips_search_punctuation():
|
||||
assert _query_terms("what calls extract?") == ["what", "calls", "extract"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user