Files
export_1c_help/export1c_help/naming.py
T
mihailkudravcev 92fd17689c Fix Gitea wiki filenames: use percent-encoded Cyrillic slugs.
Unicode names caused HTTP 500 in the wiki UI; match Gitea storage format.
2026-07-23 14:58:58 +03:00

106 lines
3.2 KiB
Python

"""Wiki page titles and Gitea filename encoding."""
from __future__ import annotations
import hashlib
import re
from urllib.parse import quote, unquote
from export1c_help.discover import HelpPage
_INVALID = re.compile(r'[\\/:*?"<>|#\[\]]+')
_SPACES = re.compile(r"\s+")
# Keep slug short so percent-encoded form fits in 255-byte FS names
_MAX_SLUG_BYTES = 72
def sanitize_title(title: str) -> str:
title = _SPACES.sub(" ", title).strip()
title = _INVALID.sub(" ", title)
title = _SPACES.sub(" ", title).strip()
return title or "untitled"
def _truncate_utf8(s: str, max_bytes: int) -> str:
raw = s.encode("utf-8")
if len(raw) <= max_bytes:
return s
raw = raw[:max_bytes]
while raw:
try:
return raw.decode("utf-8").rstrip("- ")
except UnicodeDecodeError:
raw = raw[:-1]
return "page"
def _short_hash(s: str) -> str:
return hashlib.sha1(s.encode("utf-8")).hexdigest()[:8]
def wiki_filename(title: str, *, meta_key: str | None = None) -> str:
"""
Gitea wiki stores non-ASCII page names as percent-encoded «Slug.md».
Slug = title with spaces → «-», truncated so encoded length ≤ 255.
"""
slug = sanitize_title(title).replace(" ", "-")
slug = _truncate_utf8(slug, _MAX_SLUG_BYTES)
if meta_key:
# stable disambiguator when truncated
pass
encoded = quote(slug, safe="-_.") + ".md"
if len(encoded) > 255 and meta_key:
slug = _truncate_utf8(slug, 50) + "-" + _short_hash(meta_key)
encoded = quote(slug, safe="-_.") + ".md"
return encoded
def decode_wiki_filename(name: str) -> str:
base = name[:-3] if name.endswith(".md") else name
return unquote(base)
def assign_titles(
pages: list[HelpPage],
h1_by_meta: dict[str, str | None],
) -> tuple[dict[str, str], dict[str, str]]:
"""Returns (meta_key → title, meta_key → filename)."""
used_title: set[str] = set()
used_file: set[str] = set()
titles: dict[str, str] = {}
files: dict[str, str] = {}
for p in pages:
h1 = h1_by_meta.get(p.meta_key)
base = sanitize_title(h1 or p.synonym or p.meta_key)
title = base
n = 2
while title.casefold() in used_title:
if p.is_form and p.form_name and n == 2:
title = sanitize_title(f"{base}{p.form_name}")
elif n == 2:
title = sanitize_title(f"{base}{p.object_name}")
else:
title = sanitize_title(f"{base} ({n})")
n += 1
if n > 5000:
title = sanitize_title(p.meta_key)
break
fname = wiki_filename(title, meta_key=p.meta_key)
if fname.casefold() in used_file:
slug = _truncate_utf8(sanitize_title(title).replace(" ", "-"), 50)
fname = quote(f"{slug}-{_short_hash(p.meta_key)}", safe="-_.") + ".md"
guard = 0
while fname.casefold() in used_file:
fname = quote(f"page-{_short_hash(p.meta_key + str(guard))}", safe="-_.") + ".md"
guard += 1
used_title.add(title.casefold())
used_file.add(fname.casefold())
titles[p.meta_key] = title
files[p.meta_key] = fname
return titles, files