Release 0.3.8: translit wiki slugs and relative md links

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
mihailkudravcev
2026-07-29 11:12:38 +03:00
parent 98cbb88461
commit 7dd699dab5
6 changed files with 105 additions and 48 deletions
+57 -26
View File
@@ -1,18 +1,53 @@
"""Wiki page titles and Gitea filename encoding."""
"""Wiki page titles and transliterated wiki filenames."""
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+")
# Encoded «Slug.md» must leave room under Windows MAX_PATH (~260) for deep
# project roots (e.g. D:\SynologyDrive\projects_syn\…\out\).
_MAX_ENCODED_FILENAME = 100
_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:
@@ -26,35 +61,31 @@ def _short_hash(s: str) -> str:
return hashlib.sha1(s.encode("utf-8")).hexdigest()[:8]
def _encoded_filename(slug: str) -> str:
return quote(slug, safe="-_.") + ".md"
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:
"""
Gitea wiki stores non-ASCII page names as percent-encoded «Slug.md».
Encoded length is capped so typical Windows project paths still fit
under MAX_PATH; when truncated, a short hash of meta_key (or title)
is appended for stability.
Build ASCII transliterated ``slug.md`` with bounded slug length.
"""
slug = sanitize_title(title).replace(" ", "-")
encoded = _encoded_filename(slug)
if len(encoded) <= _MAX_ENCODED_FILENAME:
return encoded
slug = _translit_slug(sanitize_title(title))
if len(slug) <= _MAX_SLUG:
return f"{slug}.md"
suffix = "-" + _short_hash(meta_key or title)
# ASCII suffix: encoded length == visible length
budget = _MAX_ENCODED_FILENAME - len(suffix) - 3 # ".md"
while slug and len(quote(slug, safe="-_.")) > budget:
slug = slug[:-1]
slug = slug.rstrip("-") or "page"
return quote(slug, safe="-_.") + suffix + ".md"
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:
base = name[:-3] if name.endswith(".md") else name
return unquote(base)
return name[:-3] if name.endswith(".md") else name
def assign_titles(
@@ -92,7 +123,7 @@ def assign_titles(
)
guard = 0
while fname.casefold() in used_file:
fname = _encoded_filename(f"page-{_short_hash(p.meta_key + str(guard))}")
fname = f"page-{_short_hash(p.meta_key + str(guard))}.md"
guard += 1
used_title.add(title.casefold())