davideisinger.com

My personal website
Log | Files | Refs | README

commit 07d70951be90774505295492b981fef2d551b20c
parent 16856010180348b21200b4ec0e3a7aca931de133
Author: David Eisinger <[email protected]>
Date:   Tue,  8 Sep 2026 23:27:28 -0400

Remove Gemini link numbers

Diffstat:
Abin/check-gemtext | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Athemes/v2/layouts/partials/gemtext-links.gmi | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mthemes/v2/layouts/partials/gemtext.gmi | 20++++----------------
3 files changed, 142 insertions(+), 16 deletions(-)

diff --git a/bin/check-gemtext b/bin/check-gemtext @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +"""Exercise the Gemini converter with Hugo, without the dither server.""" + +from pathlib import Path +import shutil +import subprocess +import tempfile + + +repo = Path(__file__).resolve().parent.parent +with tempfile.TemporaryDirectory(prefix="gemtext-check-") as directory: + root = Path(directory) + (root / "layouts/partials").mkdir(parents=True) + (root / "content").mkdir() + for name in ("gemtext.gmi", "gemtext-links.gmi"): + shutil.copy(repo / "themes/v2/layouts/partials" / name, root / "layouts/partials" / name) + (root / "hugo.toml").write_text('baseURL = "https://example.org/"\ndisableKinds = ["home", "taxonomy", "term", "RSS", "sitemap"]\n') + (root / "layouts/single.html").write_text('{{ partial "gemtext.gmi" (dict "content" .RawContent "page" .) }}') + (root / "content/check.md").write_text('''--- +title: Check +references: +- url: https://example.org/guide + title: A useful guide +--- +[First $1][1] and [again][1] start a paragraph +that continues with [second][2]. + +Another paragraph uses [first again][1] ([via][3]). + +* [First $1][1] ([via][3]) + + > Quoted text. + +* [First $1][1] + +* [First $1][1] with commentary +* Read [this article][4] + +> A quote containing [second][2]. + +Read [inline](https://example.org/inline) too. + +```markdown +Literal [first][1]. + +[99]: https://example.org/code-only +``` + +An [unresolved][98] reference. + +[1]: https://example.org/first +[2]: https://example.org/second +[3]: https://source.org/post +[4]: https://example.org/guide + +Last paragraph with [_a book_][2].''') + subprocess.run(["hugo", "build", "--source", str(root)], check=True) + result = (root / "public/check/index.html").read_text() + expected = [ + "First $1 and again start a paragraph\nthat continues with second.\n\n" + "=> https://example.org/first First $1\n=> https://example.org/second second", + "Another paragraph uses first again.\n\n=> https://example.org/first first again\n" + "=> https://source.org/post via source.org", + "=> https://example.org/first First $1\n=> https://source.org/post via source.org\n\n> Quoted text.", + "* First $1 with commentary\n* Read this article\n\n" + "=> https://example.org/first First $1\n=> https://example.org/guide A useful guide", + "> A quote containing second.\n\n=> https://example.org/second second", + "Read inline too.\n\n=> https://example.org/inline inline", + "```markdown\nLiteral [first][1].\n\n[99]: https://example.org/code-only\n```", + "An [unresolved][98] reference.", + "Last paragraph with _a book_.\n\n=> https://example.org/second a book", + ] + for fragment in expected: + assert fragment in result, f"Missing expected output:\n{fragment}\n\nActual:\n{result}" + assert result.count("=> https://example.org/first ") == 5, result + assert "[1]:" not in result, result + assert "(via)" not in result, result + print("Gemtext conversion checks passed.") diff --git a/themes/v2/layouts/partials/gemtext-links.gmi b/themes/v2/layouts/partials/gemtext-links.gmi @@ -0,0 +1,60 @@ +{{- $references := .references -}} +{{- $titles := dict -}} +{{- range .page.Params.references -}} + {{- $titles = merge $titles (dict .url .title) -}} +{{- end -}} +{{- $linkPattern := `\[([^\]]+)\](?:\[(\d+)\]|\(([^)\n]+)\))` -}} +{{- $output := slice -}} +{{- $links := slice -}} +{{- $seen := dict -}} +{{- $preformatted := false -}} +{{- /* The final blank line flushes links from a paragraph at EOF. */ -}} +{{- range split (printf "%s\n" .content) "\n" -}} + {{- $line := . -}} + {{- $fence := hasPrefix $line "```" -}} + {{- if and (not $preformatted) (findRE `^\[\d+\]:[ \t]+` $line) -}} + {{- $line = "" -}} + {{- end -}} + {{- if and (not $preformatted) (or (eq (strings.TrimSpace $line) "") $fence (hasPrefix $line "=>")) -}} + {{- with $links -}} + {{- $output = $output | append "" -}} + {{- range . -}} + {{- $output = $output | append . -}} + {{- end -}} + {{- end -}} + {{- $links = slice -}} + {{- $seen = dict -}} + {{- end -}} + {{- if and (not $preformatted) (not $fence) (not (hasPrefix $line "=>")) -}} + {{- range findRE $linkPattern $line -}} + {{- $text := replaceRE $linkPattern `$1` . -}} + {{- $refNum := replaceRE $linkPattern `$2` . -}} + {{- $url := replaceRE $linkPattern `$3` . -}} + {{- with $refNum -}} + {{- $url = index $references . -}} + {{- end -}} + {{- with $url -}} + {{- $label := (index $titles . | default $text) | replaceRE `^[_*]+|[_*]+$` "" -}} + {{- if eq $text "via" -}} + {{- $label = printf "via %s" ((urls.Parse .).Host | default .) -}} + {{- end -}} + {{- if not (index $seen .) -}} + {{- $links = $links | append (printf "=> %s %s" . $label) -}} + {{- $seen = merge $seen (dict . true) -}} + {{- end -}} + {{- end -}} + {{- if $url -}} + {{- if eq $text "via" -}} + {{- /* Attribution lives on its link line, not as a dangling '(via)'. */ -}} + {{- $line = replace $line (printf " (%s)" .) "" -}} + {{- end -}} + {{- $line = replace $line . $text -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- $output = $output | append $line -}} + {{- if $fence -}} + {{- $preformatted = not $preformatted -}} + {{- end -}} +{{- end -}} +{{- return (delimit $output "\n") -}} diff --git a/themes/v2/layouts/partials/gemtext.gmi b/themes/v2/layouts/partials/gemtext.gmi @@ -3,11 +3,10 @@ {{- /* Promote standalone list links, with an optional attribution link. */ -}} {{- $references := dict -}} -{{- $refPattern := `(?m)^\[(\d+)\]: (.+)$` -}} +{{- $refPattern := `(?m)^\[(\d+)\]:[ \t]+(.+)$` -}} {{- range findRE $refPattern $content -}} - {{- $references = merge $references (dict (replaceRE $refPattern `$1` .) (replaceRE $refPattern `$2` .)) -}} + {{- $references = merge $references (dict (replaceRE $refPattern `$1` .) (replaceRE $refPattern `$2` . | strings.TrimSpace)) -}} {{- end -}} -{{- $promoted := dict -}} {{- $listPattern := `(?m)^[*-] \[([^\]]+)\]\[(\d+)\](?:[ ]+\(\[via\]\[(\d+)\]\))?[ ]*$` -}} {{- $lines := slice -}} {{- range split $content "\n" -}} @@ -20,11 +19,9 @@ {{- $viaURL := index $references $viaNum -}} {{- if and $url (or (eq $viaNum "") $viaURL) -}} {{- $newLine := printf "=> %s %s" $url $text -}} - {{- $promoted = merge $promoted (dict $refNum true) -}} {{- with $viaURL -}} {{- $label := (urls.Parse .).Host | default . -}} {{- $newLine = printf "%s\n=> %s via %s" $newLine . $label -}} - {{- $promoted = merge $promoted (dict $viaNum true) -}} {{- end -}} {{- $line = $newLine -}} {{- end -}} @@ -34,17 +31,8 @@ {{- $content = delimit $lines "\n" -}} -{{- /* Keep definitions that are still referenced elsewhere in the prose. */ -}} -{{- range $refNum, $_ := $promoted -}} - {{- if not (findRE (printf `\[[^\]]+\]\[%s\]` $refNum) $content) -}} - {{- $content = replaceRE (printf `(?m)^\[%s\]: .+$` $refNum) "" $content -}} - {{- end -}} -{{- end -}} - -{{- /* Convert links */ -}} -{{- $content = $content | replaceRE `([^\w])\[([^\]]+)\]\[(\d+)\]` "$1$2[$3]" -}} -{{- $content = $content | replaceRE `(?m)^\[(\d+)\]: (.*)$` "=> $2 [$1] $2" -}} -{{- $content = $content | replaceRE `\[([^\]]+)\]\([^\)]+\)` "$1" -}} +{{- /* Place descriptive links after the paragraph that uses them. */ -}} +{{- $content = partial "gemtext-links.gmi" (dict "content" $content "references" $references "page" $page) -}} {{- /* Replace audio tags */ -}} {{- $audioRE := `<audio controls src="(.*/([^"]+))"></audio>` -}}