Manchmal ist eine Gemini-Kapsel doch sehr unsichtbar und man wünscht sich den Text auch auf dem Pelican-Blog, ohne dass man viel ändern muss. Mit Pelican ist man es ja schon gewöhnt eine einfache Auszeichnungssprache zu benutzen (markdown oder rst, wobei ich wegen Sphinx lieber rst benutze), so dass der Gedanke gemini als Input zu benutzen nicht sehr fern liegt. Natürlich muss man wegen der Struktur von Gemini-Text mit Einschränkungen leben – Links können nicht inline sein. Die Link-Section unten sieht in HTML ungewohnt aus.
Wie in der Dokumentation beschrieben wird ist das für das Einlesen von Text ein Reader zuständig. Auch ein Beispiel findet dort. Ein Reader wird einem Dateityp zugeordnet (also einem oder mehreren Suffixen). Die Rückgabe eines Readers ist ein Tupel aus einem HTML-String und Metadaten. Da gemini-Text zeilenorientiert ist lässt sich die Umwandlung mit einem regulären Ausdruck pro Zeile und einem Mrker für vorformatierte Blöcke erledigen.
Bei den Metadaten war eine Entscheidung nötig: Sollen sie am Anfang der gmi-Datei stehen oder kommen sie in eine eigene Datei? Ich habe mich für letzteres entschieden, denn damit kann ich einen Beitrag nur für die Gemini-Kapsel schreiben ohne die Gemini-Datei mit überflüssigen Daten zu belasten. Im Verzeichnis des Pelican-Blogs kann ich einfach eine Datei mit den Metadaten und einen Link zu Gemini-Datei anlegen.
Etwas fehleranfällig war beim Erzeugen des HTML waren die reservierten Zeichen < und >, die ersetzt werden mussten, wenn sie nicht gerade Bedeutung im Gemini-Text haben (bei Link- und Blockquote-Zeilen).
Alles in allem hat der Reader Stand heute 98 Zeilen, ist also nicht wirklich komplex. Das sehe ich als Zeichen dass das Versprechen „Gemtext is carefully designed to be very, very easy to parse and render.“ eingehalten wird; und auch Pelican macht da einen guten Job.
Pelican internals - Reader, Writer & Co. Mit Beispiel.
https://docs.getpelican.com/en/latest/internals.html
Als Inspiration genutzt, aber letztlich komplett anders gemacht.
https://github.com/khoulihan/pelican-gemini
Inspiration für das Konvertieren. Viel Nacharbeit nötig.
https://github.com/huntingb/gemtext-html-converter
from pelican import signals from pelican.readers import BaseReader import logging import re class GeminiReader(BaseReader): enabled = True logger = logging.getLogger(__name__) file_extensions = ["gmi", "gemini"] def read(self, filename): metadata = {} content = "" # Metadaten sind in Dateiname.pelican_meta with open(filename + ".pelican_meta", mode="r") as f: while current := f.readline(): current = current.strip() split = current.split(": ", 1) metadata[split[0].lower()] = split[1] with open(filename, mode="r") as f: # After the first blank line, there is the title current = f.readline() if match := re.match(r"^#\s*(.*)$", current): # use if no title, otherwise ignore if not metadata.get("title"): self.logger.info(f"No title, using {current}") metadata["title"] = match.groups()[0] else: content = current # The rest is content. content = content + f.read() parsed = {} for key, value in metadata.items(): parsed[key] = self.process_metadata(key, value) return self._convert2html(content), parsed def _convert2html(self, content): result = "" preformat = False gemini_footer_delim = self.settings.get("GEMINI_FOOTER_DELIM") for line in content.splitlines(): if line.startswith("```"): preformat = not preformat result += "<pre>\n" if preformat else "</pre>\n" continue # Meine Art von gemini-Footer – Alles danach ignorieren if ( gemini_footer_delim and line.startswith(gemini_footer_delim) and not preformat ): return result result += self._parse_gmi_line(line, preformat) return result def _parse_gmi_line(self, gmi_line, preformat): tags_dict = { r"^#\s*([^#].*)": "h1", r"^##\s*([^#].*)": "h2", r"^###\s*([^#].*)": "h3", r"^\* (.*)": "li", r"^> (.*)": "blockquote", r"^=>\s+(\S+)(\s+.*)?": "a", } # HTML special chars gmi_line = gmi_line.replace("&", "&") gmi_line = gmi_line.replace("<", "<") # keep > for blockqoute or link if not (gmi_line.startswith(">") or gmi_line.startswith("=>")): gmi_line = gmi_line.replace(">", ">") if preformat: return gmi_line + "\n" for pattern in tags_dict.keys(): if match := re.match(pattern, gmi_line): tag = tags_dict[pattern] groups = match.groups() if tag == "a": href = groups[0] text = groups[1].strip() if len(groups) > 1 and groups[1] else href # text might be empty after strip - not by spec, but in practice text = href if not text else text return f"<p><a href='{href}'>{text}</a></p>\n" else: text = groups[0].strip() return f"<{tag}>{text}</{tag}>\n" return f"<p>{gmi_line}</p>\n" def add_reader(readers): for ext in GeminiReader.file_extensions: readers.reader_classes[ext] = GeminiReader def register(): signals.readers_init.connect(add_reader)
════════════════════════
2023-05-31T00:53+02:00