update
This commit is contained in:
@@ -6,12 +6,15 @@ import parser
|
||||
import projekte
|
||||
|
||||
_UMLAUTE = str.maketrans({"ä": "ae", "ö": "oe", "ü": "ue", "ß": "ss"})
|
||||
# Dateisysteme lassen 255 Zeichen je Name zu. Der Titel darf länger sein — er steht
|
||||
# in der Datei, nicht im Namen. Ohne Deckel warf ein langer Titel einen Serverfehler.
|
||||
NAMENSLAENGE = 80
|
||||
|
||||
|
||||
def _slug(titel: str) -> str:
|
||||
s = titel.lower().translate(_UMLAUTE)
|
||||
s = re.sub(r"[^a-z0-9]+", "-", s).strip("-")
|
||||
return s or "feature"
|
||||
return s[:NAMENSLAENGE].strip("-") or "feature"
|
||||
|
||||
|
||||
def _eintrag(datei) -> dict:
|
||||
@@ -26,8 +29,14 @@ def _datei(projekt: str, id: str):
|
||||
return datei
|
||||
|
||||
|
||||
def liste(projekt: str) -> list[dict]:
|
||||
dateien = projekte.wurzel(projekt).glob("*.md")
|
||||
def liste(projekt: str, nur_stand: bool = False) -> list[dict]:
|
||||
"""Die Karten. Standardmäßig die, an denen gerade gearbeitet wird.
|
||||
|
||||
`nur_stand` liest stattdessen den Projektordner — die Übersicht soll sagen,
|
||||
was das Projekt heute kann, nicht was jemand gerade plant.
|
||||
"""
|
||||
ordner = projekte.stand(projekt) if nur_stand else projekte.wurzel(projekt)
|
||||
dateien = ordner.glob("*.md")
|
||||
eintraege = [_eintrag(d) for d in sorted(dateien)]
|
||||
return sorted(eintraege, key=lambda e: e["sektionen"]["titel"].lower())
|
||||
|
||||
@@ -38,17 +47,21 @@ def pruefbar(projekt: str) -> list[dict]:
|
||||
return [f for f in liste(projekt) if f["sektionen"]["art"] != parser.VISION]
|
||||
|
||||
|
||||
def anlegen(projekt: str, roh: str) -> dict:
|
||||
ordner = projekte.wurzel(projekt)
|
||||
titel = parser.zerlege(roh)["titel"]
|
||||
if not titel:
|
||||
raise ValueError("Titel fehlt")
|
||||
slug = _slug(titel)
|
||||
def _frei(ordner, slug: str):
|
||||
"""Der erste freie Dateiname zum Slug; gleiche Namen bekommen eine Nummer."""
|
||||
datei = ordner / f"{slug}.md"
|
||||
n = 2
|
||||
while datei.exists():
|
||||
datei = ordner / f"{slug}-{n}.md"
|
||||
n += 1
|
||||
return datei
|
||||
|
||||
|
||||
def anlegen(projekt: str, roh: str) -> dict:
|
||||
titel = parser.zerlege(roh)["titel"]
|
||||
if not titel:
|
||||
raise ValueError("Titel fehlt")
|
||||
datei = _frei(projekte.wurzel(projekt), _slug(titel))
|
||||
datei.write_text(roh)
|
||||
return _eintrag(datei)
|
||||
|
||||
@@ -58,11 +71,7 @@ def _folge_titel(datei, titel: str):
|
||||
slug = _slug(titel)
|
||||
if re.fullmatch(re.escape(slug) + r"(-\d+)?", datei.stem):
|
||||
return datei # passt schon (inkl. Kollisions-Suffix)
|
||||
ziel = datei.parent / f"{slug}.md"
|
||||
n = 2
|
||||
while ziel.exists():
|
||||
ziel = datei.parent / f"{slug}-{n}.md"
|
||||
n += 1
|
||||
ziel = _frei(datei.parent, slug)
|
||||
datei.rename(ziel)
|
||||
return ziel
|
||||
|
||||
@@ -72,9 +81,35 @@ def speichern(projekt: str, id: str, roh: str) -> dict:
|
||||
titel = parser.zerlege(roh)["titel"]
|
||||
if not titel:
|
||||
raise ValueError("Titel fehlt")
|
||||
vorher = datei.read_text()
|
||||
datei.write_text(roh)
|
||||
return _eintrag(_folge_titel(datei, titel))
|
||||
neu = _folge_titel(datei, titel)
|
||||
# Ein neuer Titel benennt die Datei um. Der Rückschritt muss beides kennen:
|
||||
# die neue Datei fällt weg, die alte kommt mit ihrem alten Inhalt zurück.
|
||||
schritt = {datei.stem: vorher}
|
||||
if neu.stem != datei.stem:
|
||||
schritt[neu.stem] = None
|
||||
return _eintrag(neu)
|
||||
|
||||
|
||||
def loeschen(projekt: str, id: str):
|
||||
_datei(projekt, id).unlink()
|
||||
datei = _datei(projekt, id)
|
||||
titel = parser.zerlege(datei.read_text())["titel"] or id
|
||||
datei.unlink()
|
||||
|
||||
|
||||
def verschieben(projekt: str, id: str, ziel: str) -> dict:
|
||||
"""Ein Feature in ein anderes Projekt umhängen.
|
||||
|
||||
Kein Kopieren: die Datei wandert, damit es sie nur einmal gibt. Der Verlauf
|
||||
hält beide Seiten fest — sonst ließe sich der Schritt nicht zurücknehmen.
|
||||
"""
|
||||
if ziel == projekt:
|
||||
raise ValueError("Feature liegt schon in diesem Projekt")
|
||||
datei = _datei(projekt, id)
|
||||
roh = datei.read_text()
|
||||
titel = parser.zerlege(roh)["titel"] or id
|
||||
# Erst das Ziel auflösen: gibt es das Projekt nicht, ist noch nichts passiert
|
||||
neu = _frei(projekte.wurzel(ziel), _slug(titel))
|
||||
datei.rename(neu)
|
||||
return _eintrag(neu)
|
||||
|
||||
Reference in New Issue
Block a user