// Package render ist zuständig für alles rund ums Lesen und Umwandeln von Markdown-Dateien. package render import ( "bytes" "os" "strings" "github.com/yuin/goldmark" "gopkg.in/yaml.v3" ) // Page repräsentiert eine einzelne Wiki-Seite. // Ein Struct in Go ist wie eine Datenklasse: es bündelt zusammengehörige Felder. type Page struct { Title string // Titel aus dem Frontmatter Password string // Passwort aus dem Frontmatter (leer = öffentlich) Tags []string // Tags aus dem Frontmatter (optional) Hidden bool // Wenn true: Seite erscheint nicht in der Navigation (aber URL bleibt erreichbar) Body string // Der fertig gerenderte HTML-Inhalt der Seite RawBody string // Der originale Markdown-Text ohne Frontmatter — wird für die Volltextsuche genutzt } // frontmatter ist ein internes Struct nur zum Parsen des YAML-Blocks. // Wir brauchen es nur kurz, deshalb ist es kleingeschrieben (= nicht exportiert, // also nur innerhalb dieses Packages sichtbar). type frontmatter struct { Title string `yaml:"title"` Password string `yaml:"password"` Tags []string `yaml:"tags"` Hidden bool `yaml:"hidden"` } // LoadPage liest eine Markdown-Datei vom Dateisystem, parst das Frontmatter // und wandelt den Markdown-Inhalt in HTML um. // // In Go geben Funktionen oft zwei Werte zurück: das Ergebnis und einen Fehler. // Der Aufrufer muss den Fehler prüfen — Go hat keine Exceptions. func LoadPage(filePath string) (*Page, error) { // os.ReadFile liest die komplette Datei in einen Byte-Slice ([]byte). // Ein Byte-Slice ist einfach eine Liste von Bytes — rohe Dateidaten. rawContent, err := os.ReadFile(filePath) if err != nil { // Wir geben nil (kein Ergebnis) und den Fehler zurück. return nil, err } // Wir wandeln den Byte-Slice in einen String um, damit wir damit arbeiten können. content := string(rawContent) // Frontmatter und Markdown-Body trennen. fm, body, err := splitFrontmatter(content) if err != nil { return nil, err } // Den Markdown-Body in HTML umwandeln. htmlBody, err := markdownToHTML(body) if err != nil { return nil, err } // Das fertige Page-Struct befüllen und zurückgeben. // Das * vor Page bedeutet: wir geben einen Zeiger zurück (eine Referenz auf // das Struct im Speicher), nicht eine Kopie davon. return &Page{ Title: fm.Title, Password: fm.Password, Tags: fm.Tags, Hidden: fm.Hidden, Body: htmlBody, RawBody: body, // Rohtext für die BM25-Volltextsuche — HTML-Tags verfälschen die Tokenisierung }, nil } // splitFrontmatter trennt den YAML-Frontmatter-Block vom Markdown-Inhalt. // // Eine typische Datei sieht so aus: // // --- // title: Meine Seite // password: geheim // --- // // # Überschrift // Inhalt hier... func splitFrontmatter(content string) (frontmatter, string, error) { var fm frontmatter // Frontmatter beginnt und endet mit "---". // Wir prüfen ob die Datei überhaupt Frontmatter hat. if !strings.HasPrefix(content, "---") { // Kein Frontmatter — leeres Struct zurückgeben, ganzer Inhalt ist Body. return fm, content, nil } // Den ersten "---" abschneiden und nach dem zweiten suchen. rest := strings.TrimPrefix(content, "---\n") closingIndex := strings.Index(rest, "---") if closingIndex == -1 { // Öffnendes "---" gefunden, aber kein schließendes — Datei ist kaputt. return fm, content, nil } // Den YAML-Block und den Body-Teil extrahieren. yamlBlock := rest[:closingIndex] body := rest[closingIndex+4:] // +4 um "---\n" zu überspringen // Den YAML-Block parsen und in unser frontmatter-Struct füllen. // yaml.Unmarshal erwartet einen Byte-Slice, deshalb []byte(yamlBlock). err := yaml.Unmarshal([]byte(yamlBlock), &fm) if err != nil { return fm, body, err } return fm, body, nil } // markdownToHTML wandelt einen Markdown-String in einen HTML-String um. // Dafür nutzen wir die goldmark-Bibliothek. func markdownToHTML(markdownContent string) (string, error) { // bytes.Buffer ist ein beschreibbarer Puffer im Speicher — wie eine Datei, // aber im RAM. Goldmark schreibt das HTML-Ergebnis hinein. var htmlBuffer bytes.Buffer // goldmark.Convert macht die eigentliche Umwandlung. // []byte(markdownContent) wandelt den String zurück in Bytes, // weil die Bibliothek Bytes erwartet. err := goldmark.Convert([]byte(markdownContent), &htmlBuffer) if err != nil { return "", err } // htmlBuffer.String() holt den fertigen HTML-String aus dem Puffer. return htmlBuffer.String(), nil }