Zollkiesel statt Meilensteine
Digitaler Zettelkasten eines chronisch Neugierigen
Zollkiesel statt Meilensteine
Digitaler Zettelkasten eines chronisch Neugierigen
Wie viele andere Blogger habe ich für mein Blog ebenfalls einen RSS-Feed. Falls Du jetzt neugierig bist: gibt in Deinem Feed-Reader die URL https://arminhanisch.de/index.xml ein. Das ist ein Volltext-Feed, d.h. darin sind nicht nur die Titel und eine Zusammenfassung, sondern der komplette Text der jeweiligen Blogposts und Du kannst damit alles im Feed-Reader lesen.
Vor ein paar Tragen schrieb mir der Thomas (er liest mein Blog über einen Feed-Reader und nicht direkt im Browser) zu einem meiner letzten Blogposts: “Feine Sache! Der Link zu pocketmod.tex funktioniert nicht, aber das Script steht ja im Text, damit ging es…”
WAT? 😲
Ich habe das doch ausprobiert und mit meinem Feedreader (“Reeder” für macOS, https://reederapp.com) klappt das wunderbar. Thomas benutzt eine andere Software (inoreader.com) und da klappt es nicht. Falls Du Dich fragst, wie Nerds oder technik-interessierte Blogger ihre Nächte verbringen, hier kommt die Antwort… 😉
Ich hatte in meinem Blogpost eine Datei verlinkt, das ebenso wie die Bilder zu diesem Blogpost über einen relativen Link. Also nicht per https://arminhanisch.de/.... und den ganzen Sums, sondern einfach per ./datei.ext im Link. Bei mir ist ein Blogpost ein Ordner und da liegt die Markdown-Datei mit dem Text drin und alle Bilder, Assets etc., die zu diesem Blogpost gehöre. Damit reicht ein relative Link problemlos aus.
Im Browser. Direkt im Blog. Im RSS-Feed hingegen… 🤪
Da steht auch ein relativer Link. Und was macht der Feed-Reader von Thomas? Anstatt wie mein Programm oder der Browser ein “Aha! Ein relativer Link! Da nehme ich die URL vom Blogpost und ersetze nur den Dateinamen” durch seine Programmzeilen zirkulieren zu lassen übernimmt das Ding die Basis-URL meines Blogs und pappt da den Dateinamen dran, erzeugt also ein https://arminhanisch.de/datei.ext.
Jetzt kann ich natürlich sagen, “works on my machine” 😎 und bei den meisten Feed-Readern funktioniert die Auflösung des Links richtig, also kommt die Standardantwort wie bei der Hotline “nicht reproduzierbar”. Aber ich wurde neugierig. Vielleicht habe ich ja einen Fehler in der Konfiguration meines Blogs oder mein Template baut den RSS-Feed tatsächlich irgendwie falsch. Und ich jemand, der dann schon wissen, was die rationale Erklärung für sowas ist. Also warten bis spätabends, wenn ich Zeit habe, einen frischen Kaffee geholt und los geht’s…
Die Spezifikation für RSS in der aktuellen Version 2 liegt hier oder hier (und noch an ein paar Stellen im Web). Also geladen, gelesen und gestaunt: Es gibt keinerlei Erwähnung, wie mit relativen Verweisen in einem RSS-Dokument umgegangen werden soll!
Das Alternativ-Format zu RSS ist Atom und das hat in seiner Spezifikation tatsächlich einen Absatz, wi mit relativen Verweisen verfahren werden soll:
Any element defined by this specification MAY have an xml:base attribute [W3C.REC-xmlbase-20010627]. When xml:base is used in an Atom Document, it serves the function described in section 5.1.1 of [RFC3986], establishing the base URI (or IRI) for resolving any relative references found within the effective scope of the xml:base attribute.
Ja, hätt’ste mal einen Atom-Feed gebaut, Armin. 😆
OK. Dann ist die Lösung aber relativ einfach. Immer wenn ich in meinen Markdown-Dateien einen relativen Link habe, muss ich den einfach während der Erzeugung der HTML-Dateien und der XML-Datei für den RSS-Feed einfach durch den absoluten Link ersetzen.
Ich verwende für mein Blog Hugo einen sogenannten static site generator. Das ist ein Programm, dass aus Markdown-Dateien einfach HTML erzeugt, ich pumpe das per rsync auf meinen Webauftritt und fertig. Kein Wordpress-Monster, ich muss mich nicht darum kümmern, die Datenbank oder PHP aktuell zuhalten, es braucht nur minimale Ressourcen, alles super. Meistens… 😎
Ich bin immer noch von Hugo begeistert, aber einige Male war ich kurz davor, mir für jedes Mitglied des Entwicklungs-Teams eine Voodoo-Puppe zu kaufen. Der Grund? Hugo ist mächtig. Was als kleines, einfaches SSG-Tool begann, ist mittlerweile eine Art eierlegende Wollmilchsau geworden. Und es gibt Versionen, die machen Deine bisherige Installation bzw. die Templates zur Konvertierung von Markdown nach HTML einfach kaputt. Die Lösung ist entweder, öfter als einmal im Jahr Stunden an Suchen (in der nicht tollen) Dokumentation und mit Bastelei zu verbringen, bis alles wieder läuft. Oder einfach eine bestimmte Version von Hugo zu nutzen, die alles kann und dann dieser auf dem Rechner zu sperren, d.h. Deinem Update-Tool bzw. der Paketverwaltung zu sagen, “wage es ja nicht, auch nur an ein Update dieser Version zu denken!”. So mache ich das seit einiger Zeit und alle ein, zwei Jahre gucke ich mal, ob Hugo etwas kann, das mich interessiert.
So, genug gejammert. Zurück zum Blogpost. 😉
Hugo kann etwas, das auch andere Tools können. Man kann mit sogenannten “Hooks” (Haken) in den Verarbeitungsprozess eingreifen und an eigene Bedürfnisse anpassen. Kann auch schon die Version, die bei mir läuft. Allerdings ist die Dokumentation wie gesagt nicht allzu lesefreundlich. Und ich war irgendwie der Meinung, für dieses Allerweltsproblem (ich kann nicht der einzige sein, der dieses Problem hat) muss es doch was bei Ratiopharm, ähm bei Hugo im Standard geben.
Während meiner Suche hat Florian im Fediverse gepostet, dass er mit OpenAI Codex eine Migration von WordPress zu Hugo durchgeführt hat: “openAIs Codex kann im Coding deutlich besser mit Hugo umgehen als Gemini in Antigravity. Dank Codex konnte ich unsere gesamte Schulhomepage von Wordpress zu Hugo migrieren - und dabei deutlich verbessern”. Oha! Also habe ich gleich mal gebeten, ob er testweise mal mein Problem der KI vorwirft.
Die Antwort von Codex zeigt in die richtige Ecke der Dokumentation:
render Hooks
Render Hook für Links: render-link.html
Datei anlegen:
layouts/_default/_markup/render-link.html
{{- $dest := .Destination -}}
{{- if or (hasPrefix $dest "http://") (hasPrefix $dest "https://") (hasPrefix $dest "mailto:") -}}
<a href="{{ $dest }}"{{ with .Title }} title="{{ . }}"{{ end }}>{{ .Text }}</a>
{{- else -}}
{{- $clean := $dest | strings.TrimPrefix "./" -}}
{{- /* Versuch: Link zeigt auf PageBundle-Resource (PDF, ZIP, etc.) */ -}}
{{- with .Page.Resources.GetMatch $clean -}}
<a href="{{ .Permalink }}"{{ with $.Title }} title="{{ . }}"{{ end }}>{{ $.Text }}</a>
{{- else -}}
{{- /* Sonst: normal absolut machen */ -}}
<a href="{{ $dest | absURL }}"{{ with .Title }} title="{{ . }}"{{ end }}>{{ .Text }}</a>
{{- end -}}
{{- end -}}
Damit werden auch […](./datei.pdf) oder […](./slides.zip) im RSS absolut.
Dazu wurde auch eine Lösung für das Rendern von Bildern empfohlen, aber der Reihe nach (Tipp aus alten Debugging-Tagen: immer nur eine Sache auf einmal verändern!). Eingebaut, Hugo angeworfen und:
ERROR Rebuild failed: render:
failed to render pages:
render of "/" failed:
"/Users/armin/Sites/arminhanisch.de/themes/kakao/layouts/_default/rss.xml:56:63":
execute of template failed at <.Content>:
error calling Content: "/Users/armin/Sites/arminhanisch.de/content/post/2025/altwerden-oder-vintage-sein/index.md:1:1":
"/Users/armin/Sites/arminhanisch.de/themes/kakao/layouts/_default/_markup/render-link.html:1:363":
execute of template failed at <.Page.Resources.GetMatch>:
error calling GetMatch: unexpected end of input
WAT? 😲
OK, einen versuch war’s wert. Aber auch wenn die vorgeschlagene Lösung nicht funktioniert hat, hat sie mich doch in die richtige Ecke der Dokumentation geschubst. Wie ich heute im Fediverse gepostet habe:
So, jetzt habe ich mir das mal angesehen. Und die Kurzfassung: wie bei allem mit KI. Wenn Du keine Ahnung hast, musst Du das glauben und es stellt sich als falsch heraus.
Wenn Du Ahnung hast, stupst Dich das Ding in die richtige Richtung und Du kannst die Lösung finden.
Was versucht der Code denn zu machen?
http:, https: oder mailto: als Protokoll beginnt, dann wird der Link so eingebaut./ beginntGetMatch die Datei geladen und dann der Link gebaut.GetMatch nichts liefert, dann wird per Funktion absURL aus dem Link ein absoluter Link gebaut.Ich habe mich die Dokumentation vertieft und die Logik angepasst. Warum so kompliziert? Wenn es sich um einen relativen Link handelt, also der Hyperlink mit einem ./ beginnt, dann muss ich da einen absoluten Link draus bauen. Alle anderen Hyperlinks (z.B. auch solche mit data: als Protokoll) werden per absURL umgewandelt.
Wenn ich den Präfix ./ nicht entferne, dann habe ich einen Link auf ein spezifisches Ziel und muss nicht GetMatch verwenden, sondern kann einfach Get verwenden (.Page.Resources.Get). Also habe ich den Code umgebaut auf:
{{- $dest := .Destination -}}
{{- if (hasPrefix $dest "./") -}}
{{- with .Page.Resources.Get $dest -}}
<a href="{{ .Permalink }}"{{ with $.Title }} title="{{ . }}"{{ end }}>{{ $.Text }}</a>
{{- else -}}
<a href="{{ $dest | absURL }}"{{ with .Title }} title="{{ . }}"{{ end }}>{{ .Text }}</a>
{{- end -}}
{{- else -}}
<a href="{{ $dest }}"{{ with .Title }} title="{{ . }}"{{ end }}>{{ .Text }}</a>
{{- end -}}
Und – tadah! – es funktioniert! 🤗👍🏽
Wie oben geschrieben, das ist aber nur möglich, weil ich mich halbwegs mit Hugo auskenne und schon ein, zwei Mal ein eigenes Thema gebaut habe und einige graue Haare der Template-Logik von Hugo und der Doku verdanke. 😎 Ansonsten hätte ich jetzt weiter rumratenrumprompten müssen, bis es zufällig funktioniert hätte. Und wenn es funktioniert, bedeutet das noch nicht, dass der Code gut, lesbar und wartbar ist oder nicht den einen oder anderen unbeabsichtigten Nebeneffekt hat.
Ebenso lief es mit dem zweiten Teil der Lösung. Der lieferte keinen Fehler, aber die Logik war auch “durch die Brust ins Auge” und der von mir nach dieser Anregung gebaute Code ist kleine und IMHO auch lesbarer.
Auf der anderen Seite hat die Frage an die KI, die mich auf die richtige Spur brachte, wahrscheinlich nur einige Sekunden gedauert und ich musste nicht eine Stunde durch die Dokumentation lesen. Allerdings hätte ich dabei möglicherweise noch ein oder zwei andere Dinge gelernt bzw. wüsste mich besser in den Tiefen der Dokumentation zu Hugo zurecht zu finden. Nichts ist nur schwarz oder weiß.
Ich habe jetzt einen RSS-Feed, der saubere absolute Links baut, damit kommen auch Feed-Reader mit der Datei zurecht, die die Lücke in der Spezifikation anders ausgelegt haben. Ich habe festgestellt, dass es Spaß macht, im Fediverse unterwegs zu sein, weil Leute wie Thomas oder Florian wertvolles Feedback geben, nett und hilfsbereit sind und das ist eine Menge wert! Und ich werde für das nächste solche Problem sehr wahrscheinlich (aus Lern- und Ethik- und Umwelt-Gründen) weiterhin auf den Einsatz von KI bei mir verzichten. Danke an dieser Stelle an Florian, der mir durch seine Abfrage den Weg in die richtige Richtung wies!
Lizenz für diesen Post CC-BY-SA 4.0