From fd0ebb5957beea84bd45615e9f6a06fe6b77bba8 Mon Sep 17 00:00:00 2001 From: cod3warrior <69142142+cod3warrior@users.noreply.github.com> Date: Sat, 2 May 2026 15:15:09 +0200 Subject: [PATCH] wiki: sanitize Windows-reserved characters in filenames (#594) On Windows, to_wiki crashes with OSError [Errno 22] when a node label contains any of < > : " / \ | ? * (Windows reserved chars in filenames). Repro: an Obsidian note titled "Czy Wii jeszcze ok?.md" becomes a community label, then write_text fails because Windows rejects the question mark in the path. The previous _safe_filename only handled three characters (/ space :) which is enough on Linux/macOS but not on Windows. Extended the substitution to cover all Windows-reserved chars plus trailing dots and spaces (also reserved on Windows), and added a 200-char length cap to stay well under MAX_PATH-segment limits. Falls back to 'unnamed' if sanitization produces an empty string, so we never try to create a file named "" or ".". Tested on a 1773-file vault with mixed Polish/English content where ~12 community labels and several god node labels contained reserved characters. Generates 763 wiki articles cleanly. --- graphify/wiki.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/graphify/wiki.py b/graphify/wiki.py index 25304c8a..8cc845f7 100644 --- a/graphify/wiki.py +++ b/graphify/wiki.py @@ -7,7 +7,18 @@ import networkx as nx def _safe_filename(name: str) -> str: - return name.replace("/", "-").replace(" ", "_").replace(":", "-") + """Make a label safe for use as a filename across platforms. + + Substitutes characters that Windows reserves in filenames + (< > : " / \\ | ? *) and strips trailing dots/spaces, also reserved. + Falls back to 'unnamed' for empty results and caps length at 200 + chars to stay well under common filesystem limits. + """ + import re + s = name.replace("/", "-").replace(" ", "_").replace(":", "-") + s = re.sub(r'[<>:"/\\|?*]', '_', s) + s = s.strip('. ') + return s[:200] if s else 'unnamed' def _cross_community_links(G: nx.Graph, nodes: list[str], own_cid: int, labels: dict[int, str]) -> list[tuple[str, int]]: