Release 0.3.8: translit wiki slugs and relative md links
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+57
-26
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user