7dd699dab5
Co-authored-by: Cursor <cursoragent@cursor.com>
135 lines
3.4 KiB
Python
135 lines
3.4 KiB
Python
"""Wiki page titles and transliterated wiki filenames."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import hashlib
|
||
import re
|
||
|
||
from export1c_help.discover import HelpPage
|
||
|
||
_INVALID = re.compile(r'[\\/:*?"<>|#\[\]]+')
|
||
_SPACES = re.compile(r"\s+")
|
||
_NON_SLUG = re.compile(r"[^a-z0-9._-]+")
|
||
_DASH_RUNS = re.compile(r"-+")
|
||
_MAX_SLUG = 48
|
||
|
||
_TRANSLIT = {
|
||
"а": "a",
|
||
"б": "b",
|
||
"в": "v",
|
||
"г": "g",
|
||
"д": "d",
|
||
"е": "e",
|
||
"ё": "e",
|
||
"ж": "zh",
|
||
"з": "z",
|
||
"и": "i",
|
||
"й": "y",
|
||
"к": "k",
|
||
"л": "l",
|
||
"м": "m",
|
||
"н": "n",
|
||
"о": "o",
|
||
"п": "p",
|
||
"р": "r",
|
||
"с": "s",
|
||
"т": "t",
|
||
"у": "u",
|
||
"ф": "f",
|
||
"х": "h",
|
||
"ц": "ts",
|
||
"ч": "ch",
|
||
"ш": "sh",
|
||
"щ": "sch",
|
||
"ъ": "",
|
||
"ы": "y",
|
||
"ь": "",
|
||
"э": "e",
|
||
"ю": "yu",
|
||
"я": "ya",
|
||
}
|
||
|
||
|
||
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 _short_hash(s: str) -> str:
|
||
return hashlib.sha1(s.encode("utf-8")).hexdigest()[:8]
|
||
|
||
|
||
def _translit_slug(title: str) -> str:
|
||
chars: list[str] = []
|
||
for ch in title.lower():
|
||
chars.append(_TRANSLIT.get(ch, ch))
|
||
raw = "".join(chars).replace(" ", "-")
|
||
raw = _NON_SLUG.sub("-", raw)
|
||
raw = _DASH_RUNS.sub("-", raw).strip("-._")
|
||
return raw or "page"
|
||
|
||
|
||
def wiki_filename(title: str, *, meta_key: str | None = None) -> str:
|
||
"""
|
||
Build ASCII transliterated ``slug.md`` with bounded slug length.
|
||
"""
|
||
slug = _translit_slug(sanitize_title(title))
|
||
if len(slug) <= _MAX_SLUG:
|
||
return f"{slug}.md"
|
||
suffix = "-" + _short_hash(meta_key or title)
|
||
keep = max(8, _MAX_SLUG - len(suffix))
|
||
slug = slug[:keep].rstrip("-._") or "page"
|
||
return f"{slug}{suffix}.md"
|
||
|
||
|
||
def decode_wiki_filename(name: str) -> str:
|
||
return name[:-3] if name.endswith(".md") else name
|
||
|
||
|
||
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:
|
||
fname = wiki_filename(
|
||
f"{title}-{_short_hash(p.meta_key)}",
|
||
meta_key=p.meta_key,
|
||
)
|
||
guard = 0
|
||
while fname.casefold() in used_file:
|
||
fname = f"page-{_short_hash(p.meta_key + str(guard))}.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
|