Regelwerk, Agenten, Skills und Werkzeuge veröffentlichen
Erster Stand des öffentlichen Ausschnitts aus dem Vault. Die .gitignore arbeitet als Positivliste: ausgeschlossen ist alles, eingeschlossen nur, was ausdrücklich wieder aufgenommen wird. Notizen, Projekte, Finanzen, das Sitzungsgedächtnis und settings.local.json bleiben damit außen vor. Der Kundenname in AI/Regelwerk/seo.md ist durch eine neutrale Bezeichnung ersetzt; die Regeln selbst sind unverändert. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
commit
2c9a4475fc
97 files changed
+14627
No files matched your search
@@ -0,0 +1,103 @@
|
||||
"""Übersetzt Obsidian-Eigenheiten in Markdown, das BookStack versteht."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
EINBETTUNG = re.compile(r"!\[\[([^\]|]+?)(?:\|[^\]]*)?\]\]")
|
||||
WIKILINK = re.compile(r"\[\[([^\]|]+?)(?:\|([^\]]*))?\]\]")
|
||||
REVIEW_TAG = re.compile(r"(?<![\w#-])#review(?:-done)?(?![\w-])[ \t]*")
|
||||
CALLOUT = re.compile(r"^>\s*\[!(\w+)\][ \t]*(.*)$", re.MULTILINE)
|
||||
ZAUN = re.compile(r"(```.*?```|~~~.*?~~~)", re.DOTALL)
|
||||
TITELZEILE = re.compile(r"^#\s+(.+?)[ \t]*$", re.MULTILINE)
|
||||
MAX_TITEL = 255
|
||||
|
||||
|
||||
@dataclass
|
||||
class Umwandlung:
|
||||
markdown: str
|
||||
hinweise: list[str] = field(default_factory=list)
|
||||
bildnamen: list[str] = field(default_factory=list)
|
||||
offene_verweise: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Sammler:
|
||||
verweise: Mapping[str, str]
|
||||
bilder: Mapping[str, str]
|
||||
hinweise: list[str] = field(default_factory=list)
|
||||
bildnamen: list[str] = field(default_factory=list)
|
||||
offene_verweise: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
def _ersetze_ausserhalb_von_code(text: str, sammler: "Sammler") -> str:
|
||||
teile = ZAUN.split(text)
|
||||
for stelle, teil in enumerate(teile):
|
||||
if stelle % 2 == 1: # ungerade Indizes sind die Codeblöcke
|
||||
continue
|
||||
teile[stelle] = _ersetze(teil, sammler)
|
||||
return "".join(teile)
|
||||
|
||||
|
||||
def _ersetze(text: str, sammler: Sammler) -> str:
|
||||
def bild(treffer: re.Match[str]) -> str:
|
||||
name = treffer.group(1).strip()
|
||||
if name not in sammler.bildnamen:
|
||||
sammler.bildnamen.append(name)
|
||||
url = sammler.bilder.get(name)
|
||||
if url:
|
||||
return f""
|
||||
sammler.hinweise.append(f"Bild nicht übertragen: {name}")
|
||||
return f"*(Bild „{name}“ wurde nicht übertragen)*"
|
||||
|
||||
def link(treffer: re.Match[str]) -> str:
|
||||
ziel, alias = treffer.group(1), treffer.group(2)
|
||||
kurz = ziel.split("/")[-1].split("#")[0].strip()
|
||||
text_teil = (alias or kurz).strip()
|
||||
url = sammler.verweise.get(kurz.casefold())
|
||||
if url:
|
||||
return f"[{text_teil}]({url})"
|
||||
if kurz not in sammler.offene_verweise:
|
||||
sammler.offene_verweise.append(kurz)
|
||||
return text_teil
|
||||
|
||||
def callout(treffer: re.Match[str]) -> str:
|
||||
titel = treffer.group(2).strip() or treffer.group(1).capitalize()
|
||||
return f"> **{titel}**"
|
||||
|
||||
text = EINBETTUNG.sub(bild, text)
|
||||
text = WIKILINK.sub(link, text)
|
||||
text = CALLOUT.sub(callout, text)
|
||||
return REVIEW_TAG.sub("", text)
|
||||
|
||||
|
||||
def ohne_titelueberschrift(rumpf: str, titel: str) -> str:
|
||||
"""Entfernt die erste H1, wenn sie den Seitentitel wiederholt.
|
||||
|
||||
BookStack stellt den Seitennamen ohnehin als Überschrift dar. Bliebe die H1
|
||||
im Text stehen, stünde die Überschrift zweimal auf der Seite.
|
||||
Weicht die H1 vom Titel ab, bleibt sie unangetastet.
|
||||
"""
|
||||
treffer = TITELZEILE.search(rumpf)
|
||||
if treffer is None or treffer.group(1).strip()[:MAX_TITEL] != titel:
|
||||
return rumpf
|
||||
return rumpf[: treffer.start()] + rumpf[treffer.end() :].lstrip("\n")
|
||||
|
||||
|
||||
def konvertiere(
|
||||
rumpf: str,
|
||||
*,
|
||||
verweise: Mapping[str, str] | None = None,
|
||||
bilder: Mapping[str, str] | None = None,
|
||||
) -> Umwandlung:
|
||||
sammler = Sammler(verweise=verweise or {}, bilder=bilder or {})
|
||||
markdown = _ersetze_ausserhalb_von_code(rumpf, sammler)
|
||||
markdown = re.sub(r"\n{3,}", "\n\n", markdown).strip() + "\n"
|
||||
return Umwandlung(
|
||||
markdown=markdown,
|
||||
hinweise=sammler.hinweise,
|
||||
bildnamen=sammler.bildnamen,
|
||||
offene_verweise=sammler.offene_verweise,
|
||||
)
|
||||
Reference in new issue
Block a user