From 24e24b49fb6375189cfa527a113eb965087a293a Mon Sep 17 00:00:00 2001 From: EuAndreh Date: Wed, 5 Apr 2023 16:14:00 -0300 Subject: v2: Support Atom feeds and collection translations --- v2/src/bin/absolute | 2 +- v2/src/bin/collections | 69 +++++++ v2/src/bin/conf | 166 +++++++++++++++ v2/src/bin/feed | 91 +++++++++ v2/src/bin/html | 219 ++++++++++++++++++++ v2/src/bin/lang-for | 73 +++++++ v2/src/bin/langs | 69 +++++++ v2/src/bin/makemake | 142 +++++++------ v2/src/bin/security-txt | 82 ++++++++ v2/src/bin/url-for | 30 +-- v2/src/bin/xmlentry | 81 ++++++++ v2/src/content/about.md | 17 -- v2/src/content/en/about.md | 11 + .../en/pastebins/raku-tuple-type-annotation.md | 31 +++ v2/src/content/en/pastebins/sicp-exercise-3-19.md | 107 ++++++++++ .../content/en/tils/lisp-three-way-conditional.md | 57 ++++++ v2/src/content/favicon.ico | Bin 0 -> 1150 bytes .../pastebins/raku-tuple-type-annotation.md | 37 ---- v2/src/content/pastebins/sicp-exercise-3-19.md | 113 ----------- .../content/pt/pastebins/exercicios-sicp-e-19.md | 103 ++++++++++ v2/src/content/pt/sobre.md | 9 + v2/src/content/tils/lisp-three-way-conditional.md | 63 ------ v2/src/development/genconf.sh | 150 -------------- v2/src/development/genhtml.sh | 224 --------------------- v2/src/development/lib.sh | 33 --- v2/src/development/security-txt.sh.in | 82 -------- v2/src/lib.sh | 33 +++ v2/src/lib/base-conf.in | 10 - v2/src/lib/base.conf | 9 + v2/src/lib/entry.xml | 22 ++ v2/src/lib/feed.xml | 15 ++ 31 files changed, 1343 insertions(+), 807 deletions(-) create mode 100755 v2/src/bin/collections create mode 100755 v2/src/bin/conf create mode 100755 v2/src/bin/feed create mode 100755 v2/src/bin/html create mode 100755 v2/src/bin/lang-for create mode 100755 v2/src/bin/langs create mode 100755 v2/src/bin/security-txt create mode 100755 v2/src/bin/xmlentry delete mode 100644 v2/src/content/about.md create mode 100644 v2/src/content/en/about.md create mode 100644 v2/src/content/en/pastebins/raku-tuple-type-annotation.md create mode 100644 v2/src/content/en/pastebins/sicp-exercise-3-19.md create mode 100644 v2/src/content/en/tils/lisp-three-way-conditional.md create mode 100644 v2/src/content/favicon.ico delete mode 100644 v2/src/content/pastebins/raku-tuple-type-annotation.md delete mode 100644 v2/src/content/pastebins/sicp-exercise-3-19.md create mode 100644 v2/src/content/pt/pastebins/exercicios-sicp-e-19.md create mode 100644 v2/src/content/pt/sobre.md delete mode 100644 v2/src/content/tils/lisp-three-way-conditional.md delete mode 100755 v2/src/development/genconf.sh delete mode 100755 v2/src/development/genhtml.sh delete mode 100644 v2/src/development/lib.sh delete mode 100755 v2/src/development/security-txt.sh.in create mode 100644 v2/src/lib.sh delete mode 100644 v2/src/lib/base-conf.in create mode 100644 v2/src/lib/base.conf create mode 100644 v2/src/lib/entry.xml create mode 100644 v2/src/lib/feed.xml (limited to 'v2/src') diff --git a/v2/src/bin/absolute b/v2/src/bin/absolute index 6434219..ecf5a64 100755 --- a/v2/src/bin/absolute +++ b/v2/src/bin/absolute @@ -62,6 +62,6 @@ done shift $((OPTIND - 1)) -. src/lib/base-conf +. src/lib/base.conf printf 'https://%s%s' "$domain" "$(cat)" diff --git a/v2/src/bin/collections b/v2/src/bin/collections new file mode 100755 index 0000000..1df0c39 --- /dev/null +++ b/v2/src/bin/collections @@ -0,0 +1,69 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + collections + collections -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -h, --help show this message + + + List the registered collections to STDOUT. + + This is done by emiting the content of the "$COLLECTIONS" + environment variable. + + + Examples: + + Just run it: + + $ collections + tils + pastebins + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + esac +done +shift $((OPTIND - 1)) + + +echo "$COLLECTIONS" | + tr ' ' '\n' | + grep . diff --git a/v2/src/bin/conf b/v2/src/bin/conf new file mode 100755 index 0000000..ac02d98 --- /dev/null +++ b/v2/src/bin/conf @@ -0,0 +1,166 @@ +#!/bin/sh +set -eu + + +usage() { + cat <<-'EOF' + Usage: + conf FILENAME + conf -h + EOF +} + +help() { + cat <<-'EOF' + + Options: + -h, --help show this message + + FILENAME the name of the input file, also to be used as + URL. + + + Separate the content from the "frontmatter", and emit the + selected one, given the FILENAME. + + + Examples: + + Get the "frontmatter" of src/f.conf: + + $ conf src/f.md > src/f.conf + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +. src/lib.sh + +FILENAME="${1:-}" +eval "$(assert_arg "$FILENAME" 'FILENAME')" + + +escape() { + sed 's|\([`"$]\)|\\\1|g' +} + +tee "$FILENAME".tmp < src/lib/base.conf +DELIMITER=0 +while read -r line; do + if [ "$line" = '---' ]; then + DELIMITER=$((DELIMITER + 1)) + continue + fi + if [ "$DELIMITER" = 2 ]; then + break + fi + if [ -z "$line" ]; then + continue + fi + + KEY="$( printf '%s' "$line" | cut -d: -f1)" + VALUE="$(printf '%s' "$line" | cut -d: -f2- | sed 's|^ ||' | escape)" + printf 'export %s="%s"\n' "$KEY" "$VALUE" +done < "$FILENAME" | tee -a "$FILENAME".tmp +# shellcheck source=/dev/null +. "$FILENAME".tmp +rm -f "$FILENAME".tmp + + +lang="$(lang-for "$FILENAME")" +export lang +printf 'export lang="%s"\n' "$lang" + +cat src/lib/base."${lang:?}".conf +# shellcheck source=/dev/null +. src/lib/base."$lang".conf +if [ -z "${title:-}" ]; then + title="${site_name:?}" + printf 'export title="%s"\n' "$(printf '%s' "$title" | escape)" +fi + +if [ -n "${date:-}" ]; then + date_iso="$(date -ud "${date:?}" -Is)" + printf 'export date_iso="%s"\n' "$date_iso" + + formatted_date="$(LANG="$lang" date -ud "${date:?}" +"${date_fmt:?}")" + export formatted_date + printf 'export date_html="%s"\n' "$(envsubst < src/lib/date."$lang".html | escape)" + + echo "${FILENAME%.md}.xmlentry" > "$(dirname "$FILENAME")/$date_iso.sortdata" + touch "${FILENAME%.md}.sortref" +fi + +if [ -n "${update:-}" ]; then + update_iso="$(date -ud "${update:?}" -Is)" + printf 'export update_iso="%s"\n' "$update_iso" + + formatted_update="$(LANG="$lang" date -ud "${update:?}" +"${date_fmt:?}")" + export formatted_update + printf 'export update_html="%s"\n' "$(envsubst < src/lib/update."$lang".html | escape)" + + printf 'export update_xml=" %s"\n' "$update_iso" +fi + + +url_part="$(printf '%s' "${FILENAME%.md}.html" | sed "s|^$CONTENT_PREFIX||")" +title_uri="$(uri "$title")" + +printf 'export title_html="%s"\n' "$(printf '%s' "$title" | htmlesc | escape)" +printf 'export filename="%s"\n' "$FILENAME" +printf 'export url_part="%s"\n' "$url_part" +printf 'export url="%s"\n' "$(url-for "$url_part" | absolute)" +printf 'export mailto_uri="%s%s"\n' "${mailto_uri_prefix:?}" "$title_uri" +printf 'export discussions_url="%s%s"\n' "${discussions_url_prefix:?}" "$title_uri" +printf 'export sourcecode_url="%s%s"\n' "${sourcecode_url_prefix:?}" "$FILENAME" + +printf 'export style_url="%s"\n' "$(url-for 'style.css')" +printf 'export favicon_url="%s"\n' "$(url-for 'favicon.svg')" +printf 'export pubkey_url="%s"\n' "$(url-for 'public.asc.txt')" + +for f in "$CONTENT_PREFIX"/img/*.svg; do + name="$(basename "$f" .svg | sed 's|-|_|g')" + printf 'export icon_%s_url="%s"\n' "$name" "$(url-for "img/$(basename "$f")")" +done + +# FIXME: special treatment of root +printf 'export homepage_url="%s"\n' "$(url-for '/')" + +printf 'export about_url="%s"\n' "$(url-for "${about_url_name:?}")" + + +if [ "${layout:-}" = 'post' ]; then + export mailto_uri="$mailto_uri_prefix$title_uri" + export discussions_url="$discussions_url_prefix$title_uri" + export sourcecode_url="$sourcecode_url_prefix$FILENAME" + printf 'export comment_html="%s"\n' "$(envsubst < src/lib/comment."$lang".html | escape)" +fi diff --git a/v2/src/bin/feed b/v2/src/bin/feed new file mode 100755 index 0000000..96c40a6 --- /dev/null +++ b/v2/src/bin/feed @@ -0,0 +1,91 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + feed FILENAME + feed -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -h, --help show this message + + FILENAME the target feed to be generated + + + Generate FILENAME as an Atom feed. The collection type and + language are inferred by the name of FILENAME. + + + Examples: + + Generate a feed for TILs: + + $ feed src/en/feeds/til.xml + EOF +} + + +for flag in "$@"; do + case "$flag" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +. src/lib.sh + +FILENAME="${1:-}" +eval "$(assert_arg "$FILENAME" 'FILENAME')" + + +COLLECTION="$(basename "$FILENAME" '.xml')" +LANGUAGE="$(lang-for "$FILENAME")" +DIR="$(dirname "$(dirname "$FILENAME")")/$COLLECTION" + + +. src/lib/base.conf +# shellcheck source=/dev/null +. src/lib/base."$LANGUAGE".conf + +now="$(date -uIs)" +url="$(url-for "${FILENAME#"$CONTENT_PREFIX"}" | absolute)" +site_name_html="$(htmlesc "${site_name:?}")" +export now url site_name_html + + +mkdir -p "$(dirname "$FILENAME")" +{ + envsubst < src/lib/feed.xml + find "$DIR"/*.sortdata | sort -nr | xargs cat | xargs cat + printf '\n' +} > "$FILENAME" diff --git a/v2/src/bin/html b/v2/src/bin/html new file mode 100755 index 0000000..7e6809c --- /dev/null +++ b/v2/src/bin/html @@ -0,0 +1,219 @@ +#!/bin/sh +set -eu + +usage() { + cat <<-'EOF' + Usage: + html FILENAME + html -h + EOF +} + +help() { + cat <<-'EOF' + + + Options: + -h, --help show this message + + FILENAME the name of the input file .md file + + + Process the FILENAME, and generate a full HTML page. + + + Examples: + + Generate the HTML for a pastebin: + + $ html src/a-paste.md > src/a-paste.html + EOF +} + + +for f in "$@"; do + case "$f" in + --) + break + ;; + --help) + usage + help + exit + ;; + *) + ;; + esac +done + +while getopts 'h' flag; do + case "$flag" in + h) + usage + help + exit + ;; + *) + usage >&2 + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +. src/lib.sh + +FILENAME="${1:-}" +eval "$(assert_arg "$FILENAME" 'FILENAME')" + + +# shellcheck source=/dev/null +. "${FILENAME%.md}.conf" + +# +# Utility functions +# + +SEEN_SLUGS="$(mkstemp)" +slugify_once() { + SLUG="$(printf '%s' "$1" | slugify)${2:+-$2}" + if grep -q "^$SLUG$" "$SEEN_SLUGS"; then + N="${2:-0}" + N=$((N + 1)) + slugify_once "$1" "$N" + else + printf '%s\n' "$SLUG" >> "$SEEN_SLUGS" + printf '%s' "$SLUG" + fi +} + +INDENT=' ' +markdown_to_html() { + md2html | awk -vINDENT="$INDENT" ' + BEGIN { + in_block = 0 + } + + { + if (in_block == 0) { + printf "%s", INDENT + } + print + } + + /^<\/code><\/pre>$/ { + in_block = 0 + } + + /^
 "$SNIPPETS"
+	F="$(mkstemp)"
+	cat > "$F"
+	(
+		IFS=''
+		BLOCK_NUMBER=0
+		IN_BLOCK=
+		while read -r line; do
+			if [ "$line" = '
' ]; then + IN_BLOCK= + fi + + if [ -n "$IN_BLOCK" ]; then + printf '%s\n' "$line" | htmlesc -d >> "$OUT" + fi + + if printf '%s' "$line" | grep -q "^$INDENT
\)\(.*\)$|\2|" |
+					htmlesc -d > "$OUT"
+				printf '%s\n' "$OUT" >> "$SNIPPETS"
+			fi
+		done < "$F"
+
+		BLOCK_NUMBER=0
+		while read -r line; do
+			printf '%s\n' "$line"
+
+			if [ "$line" = '
' ]; then + printf '%s\n' \ + "$INDENT" \ + "$(basename "${url_part:?}").$BLOCK_NUMBER.txt" + BLOCK_NUMBER=$((BLOCK_NUMBER + 1)) + fi + done < "$F" + ) + +} + +add_line_numbers() { + awk ' + /^<\/code><\/pre>$/ { + in_block = 0 + printf "%s\n", $0 + next + } + + match($0, /^( +
)(.*)$/, a) {
+			printf "%s", a[1]
+
+			n = 1
+			block_count++
+			printf "\n", block_count, n, block_count, n, n, a[2]
+			in_block = 1
+			next
+		}
+
+		in_block == 1 {
+			n++
+			printf "\n", block_count, n, block_count, n, n, $0
+			next
+		}
+
+		{ print }
+	'
+}
+
+add_headings_anchors() {
+	(
+		IFS=''
+		while read -r line; do
+			if ! printf '%s' "$line" | grep -q "^$INDENT"; then
+				printf '%s\n' "$line"
+				continue
+			fi
+			LVL="$(printf '%s' "$line" | sed "s|^$INDENT.*|\1|")"
+			HEADING="$(printf '%s' "$line" | sed "s|^$INDENT\(.*\)$|\1|")"
+			SLUG="$(slugify_once "$HEADING")"
+			printf '%s%s\n' \
+				"$INDENT"  \
+				"$LVL"     \
+				"$SLUG"    \
+				"$HEADING" \
+				"$SLUG"    \
+				"${icon_link_url:?}" \
+				"$LVL"
+		done
+	)
+}
+
+emit_body() {
+	< "${FILENAME%.md}.content" \
+		markdown_to_html           |
+		extract_plaintext_snippets |
+		add_line_numbers           |
+		add_headings_anchors
+}
+
+envsubst < src/lib/preamble.html
+emit_body | tee "${FILENAME%.md}.htmlbody"
+envsubst < src/lib/postamble.html
diff --git a/v2/src/bin/lang-for b/v2/src/bin/lang-for
new file mode 100755
index 0000000..f7c57a9
--- /dev/null
+++ b/v2/src/bin/lang-for
@@ -0,0 +1,73 @@
+#!/bin/sh
+set -eu
+
+usage() {
+	cat <<-'EOF'
+		Usage:
+		  lang-for FILE
+		  lang-for -h
+	EOF
+}
+
+help() {
+	cat <<-'EOF'
+
+
+		Options:
+		  -h, --help    show this message
+
+		  FILE          the path of the file to get the language for
+
+
+		Say the language of the given file, using the path of FILE.
+
+
+		Examples:
+
+		  Get "en" for "src/en/some-pt.md":
+
+		    $ lang-for src/en/some-pt.md
+		    en
+	EOF
+}
+
+
+for flag in "$@"; do
+	case "$flag" in
+		--)
+			break
+			;;
+		--help)
+			usage
+			help
+			exit
+			;;
+		*)
+			;;
+	esac
+done
+
+while getopts 'h' flag; do
+	case "$flag" in
+		h)
+			usage
+			help
+			exit
+			;;
+		*)
+			usage >&2
+			exit 2
+			;;
+	esac
+done
+shift $((OPTIND - 1))
+
+. src/lib.sh
+
+
+FILE="${1:-}"
+eval "$(assert_arg "$FILE" 'FILE')"
+
+
+echo "${FILE#"$CONTENT_PREFIX"/}" |
+	cut -d/ -f1
diff --git a/v2/src/bin/langs b/v2/src/bin/langs
new file mode 100755
index 0000000..8e8aa63
--- /dev/null
+++ b/v2/src/bin/langs
@@ -0,0 +1,69 @@
+#!/bin/sh
+set -eu
+
+usage() {
+	cat <<-'EOF'
+		Usage:
+		  langs
+		  langs -h
+	EOF
+}
+
+help() {
+	cat <<-'EOF'
+
+
+		Options:
+		  -h, --help    show this message
+
+
+		List the supported languages, including english, to STDOUT.
+
+		This is done in the same way that po4a(1) does it: by listing
+		the po/*.po files, and getting the name from it.
+
+
+		Examples:
+
+		  Just run it:
+
+		    $ langs
+		    en
+		    pt
+	EOF
+}
+
+
+for flag in "$@"; do
+	case "$flag" in
+		--)
+			break
+			;;
+		--help)
+			usage
+			help
+			exit
+			;;
+		*)
+			;;
+	esac
+done
+
+while getopts 'h' flag; do
+	case "$flag" in
+		h)
+			usage
+			help
+			exit
+			;;
+		*)
+			usage >&2
+			exit 2
+	esac
+done
+shift $((OPTIND - 1))
+
+
+echo po/en.po po/*.po |
+	tr ' ' '\n' |
+	sed 's|^po/\(.*\)\.po$|\1|'
diff --git a/v2/src/bin/makemake b/v2/src/bin/makemake
index e95a040..fdd0e68 100755
--- a/v2/src/bin/makemake
+++ b/v2/src/bin/makemake
@@ -64,84 +64,110 @@ varlist() {
 	printf '\n'
 }
 
-html_deps() {
-	"$@" | sed 's/^\(.*\)\.md$/\1.conf \1.content: \1.md/'
-	"$@" | sed 's/^\(.*\)\.md$/\1.snippets \1.html: \1.conf \1.content/'
-	printf '\n'
-}
-
-
-content_mds() {
-	{
-		if [ "$r" = 'pages' ]; then
-			echo src/content/*.md
-		else
-			echo src/content/"$r"/*.md
-		fi
-	} | tr ' ' '\n'
-}
-
-RESOURCES='
-pages
-pastebins
-tils
-'
 
 EXTENSIONS='
-.md
 .conf
 .content
 .html
 .snippets
+.htmlbody
 '
 
-for r in $RESOURCES; do
-	content_mds "$r" | varlist "$r".md
-	for e in $EXTENSIONS; do
-		if [ "$e" = '.md' ]; then
-			continue
+COLL_EXTENSIONS='
+.sortref
+.xmlentry
+'
+
+page_ext_filter="^($(echo "$COLL_EXTENSIONS" |
+	tr ' ' '\n' |
+	grep . |
+	paste -sd'|'
+))\$"
+
+
+extensions() {
+	echo "$EXTENSIONS" "$COLL_EXTENSIONS" "$@" |
+		tr ' ' '\n' |
+		grep .
+}
+
+
+printf '.POSIX:\n\n\n'
+for lang in $(langs); do
+	for c in pages $(collections); do
+		if [ "$c" = 'pages' ]; then
+			filter="$page_ext_filter"
+			dir="$CONTENT_PREFIX/$lang"
+		else
+			filter='^$'
+			dir="$CONTENT_PREFIX/$lang/$c"
 		fi
-		# shellcheck disable=2016
-		printf '%s%s = $(%s.md:.md=%s)\n' "$r" "$e" "$r" "$e"
-	done
-	printf '%s =' "$r"
-	for e in $EXTENSIONS; do
-		if [ "$e" = '.md' ]; then
+
+		mds() {
+			# find "$dir"/*.md # FIXME
+			find "$dir"/*.md 2>/dev/null
+		}
+
+		exts() {
+			extensions | grep -Ev "$filter"
+		}
+
+		mds | varlist "$c.$lang.md"
+		exts | sed "s|^\(.*\)\$|$c.$lang\1 = \$($c.$lang.md:.md=\1)|"
+		exts | sed "s|^\(.*\)\$|\$($c.$lang\1)|" | varlist "$c.$lang"
+
+		mds | sed 's/^\(.*\)\.md$/\1.conf \1.content: \1.md/'
+		mds | sed 's/^\(.*\)\.md$/\1.snippets \1.htmlbody \1.html: \1.conf \1.content/'
+		if [ "$c" = 'pages' ]; then
 			continue
 		fi
-		# shellcheck disable=2016
-		printf ' $(%s%s)' "$r" "$e"
+
+		mds | sed 's/^\(.*\)\.md$/\1.sortref: \1.md/'
+		mds | sed 's/^\(.*\)\.md$/\1.xmlentry: \1.html/'
+
+		echo "$CONTENT_PREFIX/$lang/feeds/$c.xml: \$($c.$lang.xmlentry)"
+
+		printf '\n\n'
 	done
-	printf '\n'
-	html_deps content_mds "$r"
-	printf '\n'
-done
 
+	for e in $(extensions); do
+		{
+			if ! printf '%s\n' "$e" | grep -qE "$page_ext_filter"; then
+				echo pages
+			fi
+			collections
+		} |
+		sed "s|^\(.*\)\$|\$(\1.$lang$e)|" |
+		varlist "all-generated.$lang$e"
+	done
 
-all_resources() {
-	echo "$RESOURCES" | tr ' ' '\n' | grep .
-}
+	collections |
+		sed "s|^\(.*\)\$|$CONTENT_PREFIX/$lang/feeds/\1.xml|" |
+		varlist "all-generated.$lang.xml"
 
-all_vars() {
-	EXT="$1"
-	# shellcheck disable=2016
-	all_resources |
-		sed 's|^|$(|' |
-		sed "s|$|$EXT)|" |
-		varlist all-generated"$EXT"
-}
+	extensions '.xml' |
+		sed "s|^\(.*\)\$|\$(all-generated.$lang\1)|" |
+		varlist "all-generated.$lang"
 
-for e in $EXTENSIONS; do
-	all_vars "$e"
+	printf '\n'
+done
+
+for e in $(extensions .xml); do
+	langs |
+		sed "s|^\(.*\)\$|\$(all-generated.\1$e)|" |
+		varlist "all-generated$e"
 done
-all_vars ''
+
+# shellcheck disable=2016
+langs |
+	sed 's|^\(.*\)$|$(all-generated.\1)|' |
+	varlist 'all-generated'
 
 
-git ls-files |
-	grep -v '^src/content/' |
+git ls-files src/ |
+	grep -v ^"$CONTENT_PREFIX"/ |
 	varlist 'non-content'
 
-git ls-files src/content/ |
+git ls-files "$CONTENT_PREFIX"/ |
 	grep -v '\.md$' |
-	grep -Ev "src/content/($(all_resources | paste -sd'|'))/" |
 	varlist 'static-content'
diff --git a/v2/src/bin/security-txt b/v2/src/bin/security-txt
new file mode 100755
index 0000000..7026969
--- /dev/null
+++ b/v2/src/bin/security-txt
@@ -0,0 +1,82 @@
+#!/bin/sh
+set -eu
+
+usage() {
+	cat <<-'EOF'
+		Usage:
+		  security-txt
+		  security-txt -h
+	EOF
+}
+
+help() {
+	cat <<-'EOF'
+
+
+		Options:
+		  -h, --help    show this message
+
+
+		Generate the RFC 9116 "security.txt" file from data in the
+		repository.
+
+
+		Examples:
+
+		  Just run it:
+
+		    $ security-txt > .well-known/security.txt
+	EOF
+}
+
+
+for flag in "$@"; do
+	case "$flag" in
+		--)
+			break
+			;;
+		--help)
+			usage
+			help
+			exit
+			;;
+		*)
+			;;
+	esac
+done
+
+while getopts 'h' flag; do
+	case "$flag" in
+		h)
+			usage
+			help
+			exit
+			;;
+		*)
+			usage >&2
+			exit 2
+	esac
+done
+shift $((OPTIND - 1))
+
+
+
+EXPIRES="$(
+	LANG=C.UTF-8 gpg --list-key "$EMAIL" |
+		awk '/^pub/ { print substr($(NF), 1, 10) }'
+)T00:00:00z"
+
+LANGUAGES="$(
+	langs |
+		sed 's|^|, |' |
+		tr -d '\n' |
+		sed 's|^, ||'
+)"
+
+
+cat <<-EOF
+	Contact: mailto:$EMAIL
+	Encryption: $(url-for 'public.asc.txt' | absolute)
+	Expires: $EXPIRES
+	Preferred-Languages: $LANGUAGES
+EOF
diff --git a/v2/src/bin/url-for b/v2/src/bin/url-for
index d4099bc..adaccd7 100755
--- a/v2/src/bin/url-for
+++ b/v2/src/bin/url-for
@@ -4,7 +4,7 @@ set -eu
 usage() {
 	cat <<-'EOF'
 		Usage:
-		  url-for [-g] FILE
+		  url-for FILE
 		  url-for -h
 	EOF
 }
@@ -13,7 +13,6 @@ help() {
 	cat <<-'EOF'
 
 		Options:
-		  -g            global file, not specific to a single language
 		  -h, --help    show this message
 
 		  FILE          the path for the URL to be constructed
@@ -25,7 +24,7 @@ help() {
 
 		Examples:
 
-		  Get the URL for "about.html", when $base_url is "v2" and $lang is "en":
+		  Get the URL for "en/about.html", when $base_url is "v2":
 
 		    $ url-for 'about.html'
 		    /v2/en/about.html
@@ -33,8 +32,8 @@ help() {
 
 		  Get the URL for "static/favicon.svg", when $base_url is empty:
 
-		    $ url-for -g 'static/favicon.svg'
-		    /static/favicon.svg
+		    $ url-for 'img/link.svg'
+		    /img/link.svg
 	EOF
 }
 
@@ -54,12 +53,8 @@ for flag in "$@"; do
 	esac
 done
 
-GLOBAL=false
-while getopts 'gh' flag; do
+while getopts 'h' flag; do
 	case "$flag" in
-		g)
-			GLOBAL=true
-			;;
 		h)
 			usage
 			help
@@ -73,22 +68,19 @@ while getopts 'gh' flag; do
 done
 shift $((OPTIND - 1))
 
-. src/development/lib.sh
-
+. src/lib.sh
 
 FILE="${1:-}"
+eval "$(assert_arg "$FILE" 'FILE')"
+
+
 if [ "$FILE" = '/' ]; then
 	FILE=''
 fi
 
-. src/lib/base-conf
+. src/lib/base.conf
 # shellcheck source=/dev/null
 . src/lib/base."${lang:?}".conf
 
-if [ "$GLOBAL" = true ]; then
-	L=''
-else
-	L="${lang:?}/"
-fi
 
-printf '%s%s%s' "${base_url:-/}" "$L" "$FILE"
+printf '%s%s' "${base_url:-/}" "$FILE"
diff --git a/v2/src/bin/xmlentry b/v2/src/bin/xmlentry
new file mode 100755
index 0000000..b0760ae
--- /dev/null
+++ b/v2/src/bin/xmlentry
@@ -0,0 +1,81 @@
+#!/bin/sh
+set -eu
+
+usage() {
+	cat <<-'EOF'
+		Usage:
+		  xmlentry FILENAME
+		  xmlentry -h
+	EOF
+}
+
+help() {
+	cat <<-'EOF'
+
+
+		Options:
+		  -h, --help    show this message
+
+		  FILENAME      the name of the input .md file
+
+
+		Process FILE, and generate an Atom feed entry.
+
+
+		Examples:
+
+		  Generate the XML entry for a TIL:
+
+		    $ xmlentry src/tils/a-til.md > src/tils/a-til.xml
+	EOF
+}
+
+
+for flag in "$@"; do
+	case "$flag" in
+		--)
+			break
+			;;
+		--help)
+			usage
+			help
+			exit
+			;;
+		*)
+			;;
+	esac
+done
+
+while getopts 'h' flag; do
+	case "$flag" in
+		h)
+			usage
+			help
+			exit
+			;;
+		*)
+			usage >&2
+			exit 2
+			;;
+	esac
+done
+shift $((OPTIND - 1))
+
+. src/lib.sh
+
+FILENAME="${1:-}"
+eval "$(assert_arg "$FILENAME" 'FILENAME')"
+
+
+# shellcheck source=/dev/null
+. "${FILENAME%.md}.conf"
+
+envsubst < src/lib/entry.xml
+
+head -n1 < "${FILENAME%.md}.htmlbody" | htmlesc
+printf '    \n'
+printf '    \n' "${url:?}"
+
+htmlesc < "${FILENAME%.md}.htmlbody"
+printf '    \n'
+printf '  \n'
diff --git a/v2/src/content/about.md b/v2/src/content/about.md
deleted file mode 100644
index b10f26b..0000000
--- a/v2/src/content/about.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-
-title: About
-
-layout: page
-
-lang: en
-
-ref: aboud
-
----
-
-It's all about me, baby!
-
-```
-xablau
-```
diff --git a/v2/src/content/en/about.md b/v2/src/content/en/about.md
new file mode 100644
index 0000000..1e39407
--- /dev/null
+++ b/v2/src/content/en/about.md
@@ -0,0 +1,11 @@
+---
+
+title: About
+
+---
+
+It's all about me, baby!
+
+```
+xablau
+```
diff --git a/v2/src/content/en/pastebins/raku-tuple-type-annotation.md b/v2/src/content/en/pastebins/raku-tuple-type-annotation.md
new file mode 100644
index 0000000..5877470
--- /dev/null
+++ b/v2/src/content/en/pastebins/raku-tuple-type-annotation.md
@@ -0,0 +1,31 @@
+---
+
+title: Raku tuple type annotation
+
+date: 2019-12-29
+
+---
+
+```perl
+# Single Str return value: this works
+sub f1(Str $in --> Str) {
+  $in;
+}
+
+# Tuple of Str as return value: this works
+sub f2(Str $in) {
+  ($in, $in);
+}
+
+# Tuple of Str as return value with type annotation: this doesn't works
+sub f2(Str $in --> (Str, Str)) {
+  ($in, $in);
+}
+```
+
+Error log is:
+
+```perl
+===SORRY!=== Error while compiling /path/to/my/file
+Malformed return value
+```
diff --git a/v2/src/content/en/pastebins/sicp-exercise-3-19.md b/v2/src/content/en/pastebins/sicp-exercise-3-19.md
new file mode 100644
index 0000000..3188dce
--- /dev/null
+++ b/v2/src/content/en/pastebins/sicp-exercise-3-19.md
@@ -0,0 +1,107 @@
+---
+
+title: SICP exercise 3.19
+
+date: 2021-09-02
+
+---
+
+Some content here, before:
+
+```scheme
+(define (cycle? l)
+  (define (rec l x)
+    (cond
+      ((null? x) false)
+      ((eq? l x) true)
+      (true (rec l (cdr x)))))
+  (rec l (cdr l)))
+```
+
+Sample interactive session:
+
+```scheme
+scheme@(guile-user)> (define true #t)
+scheme@(guile-user)> (define false #f)
+scheme@(guile-user)>
+(define (cycle? l)
+  (define (rec l x)
+    (cond
+      ((null? x) false)
+      ((eq? l x) true)
+      (true (rec l (cdr x)))))
+  (rec l (cdr l)))
+scheme@(guile-user)> (cycle? '(1 2 3))
+$9 = #f
+scheme@(guile-user)> (cycle? (make-cycle '(1 2 3)))
+$10 = #t
+```
+
+# An h1
+
+a list:
+
+1. one
+2. two
+3. three
+
+some content.
+
+- item
+- another
+- yet another
+
+## An h2
+
+Xablau:
+
+```
+xupliu 1
+
+3
+4
+
+
+
+
+
+dez
+```
+
+Foi `wikiwiu`.
+
+a very long code block:
+
+```
+wef
+wef               wef            wef       wef
+wef                                                   wef       wef wef we fwef                wef wef wef            wef 
+```
+
+Someone said:
+
+> Xablau, xupliu.
+
+### A repeated header
+### A repeated header
+
+a big list:
+
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
diff --git a/v2/src/content/en/tils/lisp-three-way-conditional.md b/v2/src/content/en/tils/lisp-three-way-conditional.md
new file mode 100644
index 0000000..cd7a034
--- /dev/null
+++ b/v2/src/content/en/tils/lisp-three-way-conditional.md
@@ -0,0 +1,57 @@
+---
+
+title: Three-way conditional for number signs on Lisp
+
+date: 2021-04-24 3
+
+update: 2021-08-14
+
+---
+
+A useful macro from Paul Graham's [On Lisp][on-lisp] book:
+
+```lisp
+(defmacro nif (expr pos zero neg)
+  (let ((g (gensym)))
+    `(let ((,g ,expr))
+       (cond ((plusp ,g) ,pos)
+             ((zerop ,g) ,zero)
+             (t ,neg)))))
+```
+
+After I looked at this macro, I started seeing opportunities to using it in many places, and yet I didn't see anyone else using it.
+
+The latest example I can think of is section 1.3.3 of [Structure and Interpretation of Computer Programs][sicp], which I was reading recently:
+
+```scheme
+(define (search f neg-point pos-point)
+  (let ((midpoint (average neg-point pos-point)))
+    (if (close-enough? neg-point post-point)
+        midpoint
+        (let ((test-value (f midpoint)))
+          (cond ((positive? test-value)
+                 (search f neg-point midpoint))
+                ((negative? test-value)
+                 (search f midpoint pos-point))
+                (else midpoint))))))
+```
+
+Not that the book should introduce such macro this early, but I couldn't avoid feeling bothered by not using the `nif` macro, which could even remove the need for the intermediate `test-value` variable:
+
+```scheme
+(define (search f neg-point pos-point)
+  (let ((midpoint (average neg-point pos-point)))
+    (if (close-enough? neg-point post-point)
+        midpoint
+        (nif (f midpoint)
+             (search f neg-point midpoint)
+             (midpoint)
+             (search f midpoint pos-point)))))
+```
+
+It also avoids `cond`'s extra clunky parentheses for grouping, which is unnecessary but built-in.
+
+As a macro, I personally feel it tilts the balance towards expressivenes despite its extra cognitive load toll.
+
+[on-lisp]: http://www.paulgraham.com/onlisptext.html
+[sicp]: https://mitpress.mit.edu/sites/default/files/sicp/index.html
diff --git a/v2/src/content/favicon.ico b/v2/src/content/favicon.ico
new file mode 100644
index 0000000..8f2130a
Binary files /dev/null and b/v2/src/content/favicon.ico differ
diff --git a/v2/src/content/pastebins/raku-tuple-type-annotation.md b/v2/src/content/pastebins/raku-tuple-type-annotation.md
deleted file mode 100644
index 3d5ff34..0000000
--- a/v2/src/content/pastebins/raku-tuple-type-annotation.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-
-title: Raku tuple type annotation
-
-date: 2019-12-29
-
-layout: post
-
-lang: en
-
-ref: raku-tuple-type-annotation
-
----
-
-```perl
-# Single Str return value: this works
-sub f1(Str $in --> Str) {
-  $in;
-}
-
-# Tuple of Str as return value: this works
-sub f2(Str $in) {
-  ($in, $in);
-}
-
-# Tuple of Str as return value with type annotation: this doesn't works
-sub f2(Str $in --> (Str, Str)) {
-  ($in, $in);
-}
-```
-
-Error log is:
-
-```perl
-===SORRY!=== Error while compiling /path/to/my/file
-Malformed return value
-```
diff --git a/v2/src/content/pastebins/sicp-exercise-3-19.md b/v2/src/content/pastebins/sicp-exercise-3-19.md
deleted file mode 100644
index 8834889..0000000
--- a/v2/src/content/pastebins/sicp-exercise-3-19.md
+++ /dev/null
@@ -1,113 +0,0 @@
----
-
-title: SICP exercise 3.19
-
-date: 2021-09-02
-
-layout: post
-
-lang: en
-
-id: sicp-exercise-3-19
-
----
-
-Some content here, before:
-
-```scheme
-(define (cycle? l)
-  (define (rec l x)
-    (cond
-      ((null? x) false)
-      ((eq? l x) true)
-      (true (rec l (cdr x)))))
-  (rec l (cdr l)))
-```
-
-Sample interactive session:
-
-```scheme
-scheme@(guile-user)> (define true #t)
-scheme@(guile-user)> (define false #f)
-scheme@(guile-user)>
-(define (cycle? l)
-  (define (rec l x)
-    (cond
-      ((null? x) false)
-      ((eq? l x) true)
-      (true (rec l (cdr x)))))
-  (rec l (cdr l)))
-scheme@(guile-user)> (cycle? '(1 2 3))
-$9 = #f
-scheme@(guile-user)> (cycle? (make-cycle '(1 2 3)))
-$10 = #t
-```
-
-# An h1
-
-a list:
-
-1. one
-2. two
-3. three
-
-some content.
-
-- item
-- another
-- yet another
-
-## An h2
-
-Xablau:
-
-```
-xupliu 1
-
-3
-4
-
-
-
-
-
-dez
-```
-
-Foi `wikiwiu`.
-
-a very long code block:
-
-```
-wef
-wef               wef            wef       wef
-wef                                                   wef       wef wef we fwef                wef wef wef            wef 
-```
-
-Someone said:
-
-> Xablau, xupliu.
-
-### A repeated header
-### A repeated header
-
-a big list:
-
-1. a
-1. a
-1. a
-1. a
-1. a
-1. a
-1. a
-1. a
-1. a
-1. a
-1. a
-1. a
-1. a
-1. a
-1. a
-1. a
-1. a
-1. a
diff --git a/v2/src/content/pt/pastebins/exercicios-sicp-e-19.md b/v2/src/content/pt/pastebins/exercicios-sicp-e-19.md
new file mode 100644
index 0000000..d4db88d
--- /dev/null
+++ b/v2/src/content/pt/pastebins/exercicios-sicp-e-19.md
@@ -0,0 +1,103 @@
+---
+
+title: SICP exercise 3.19
+
+date: 2021-09-02
+
+---
+
+Some content here, before:
+
+```scheme
+(define (cycle? l)
+  (define (rec l x)
+    (cond
+      ((null? x) false)
+      ((eq? l x) true)
+      (true (rec l (cdr x)))))
+  (rec l (cdr l)))
+```
+
+Sample interactive session:
+
+```scheme
+scheme@(guile-user)> (define true #t)
+scheme@(guile-user)> (define false #f)
+scheme@(guile-user)>
+(define (cycle? l)
+  (define (rec l x)
+    (cond
+      ((null? x) false)
+      ((eq? l x) true)
+      (true (rec l (cdr x)))))
+  (rec l (cdr l)))
+scheme@(guile-user)> (cycle? '(1 2 3))
+$9 = #f
+scheme@(guile-user)> (cycle? (make-cycle '(1 2 3)))
+$10 = #t
+```
+
+# An h1
+
+a list:
+
+1. one
+2. two
+3. three
+
+some content.
+
+- item
+- another
+- yet another
+
+## An h2
+
+Xablau:
+
+``` xupliu 1
+
+3 4
+
+
+
+
+
+dez ```
+
+Foi `wikiwiu`.
+
+a very long code block:
+
+```
+wef
+wef               wef            wef       wef
+wef                                                   wef       wef wef we fwef                wef wef wef            wef 
+```
+
+Someone said:
+
+> Xablau, xupliu.
+
+### A repeated header ### A repeated header
+
+a big list:
+
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
+1. a
diff --git a/v2/src/content/pt/sobre.md b/v2/src/content/pt/sobre.md
new file mode 100644
index 0000000..97eecf8
--- /dev/null
+++ b/v2/src/content/pt/sobre.md
@@ -0,0 +1,9 @@
+---
+
+title: About
+
+---
+
+It's all about me, baby!
+
+``` xablau ```
diff --git a/v2/src/content/tils/lisp-three-way-conditional.md b/v2/src/content/tils/lisp-three-way-conditional.md
deleted file mode 100644
index 81920c4..0000000
--- a/v2/src/content/tils/lisp-three-way-conditional.md
+++ /dev/null
@@ -1,63 +0,0 @@
----
-
-title: Three-way conditional for number signs on Lisp
-
-date: 2021-04-24 3
-
-update: 2021-08-14
-
-layout: post
-
-lang: en
-
-ref: three-way-conditional-for-number-signs-on-lisp
-
----
-
-A useful macro from Paul Graham's [On Lisp][on-lisp] book:
-
-```lisp
-(defmacro nif (expr pos zero neg)
-  (let ((g (gensym)))
-    `(let ((,g ,expr))
-       (cond ((plusp ,g) ,pos)
-             ((zerop ,g) ,zero)
-             (t ,neg)))))
-```
-
-After I looked at this macro, I started seeing opportunities to using it in many places, and yet I didn't see anyone else using it.
-
-The latest example I can think of is section 1.3.3 of [Structure and Interpretation of Computer Programs][sicp], which I was reading recently:
-
-```scheme
-(define (search f neg-point pos-point)
-  (let ((midpoint (average neg-point pos-point)))
-    (if (close-enough? neg-point post-point)
-        midpoint
-        (let ((test-value (f midpoint)))
-          (cond ((positive? test-value)
-                 (search f neg-point midpoint))
-                ((negative? test-value)
-                 (search f midpoint pos-point))
-                (else midpoint))))))
-```
-
-Not that the book should introduce such macro this early, but I couldn't avoid feeling bothered by not using the `nif` macro, which could even remove the need for the intermediate `test-value` variable:
-
-```scheme
-(define (search f neg-point pos-point)
-  (let ((midpoint (average neg-point pos-point)))
-    (if (close-enough? neg-point post-point)
-        midpoint
-        (nif (f midpoint)
-             (search f neg-point midpoint)
-             (midpoint)
-             (search f midpoint pos-point)))))
-```
-
-It also avoids `cond`'s extra clunky parentheses for grouping, which is unnecessary but built-in.
-
-As a macro, I personally feel it tilts the balance towards expressivenes despite its extra cognitive load toll.
-
-[on-lisp]: http://www.paulgraham.com/onlisptext.html
-[sicp]: https://mitpress.mit.edu/sites/default/files/sicp/index.html
diff --git a/v2/src/development/genconf.sh b/v2/src/development/genconf.sh
deleted file mode 100755
index f9dfc14..0000000
--- a/v2/src/development/genconf.sh
+++ /dev/null
@@ -1,150 +0,0 @@
-#!/bin/sh
-set -eu
-
-
-usage() {
-	cat <<-'EOF'
-		Usage:
-		  src/development/genconf.sh FILENAME
-		  src/development/genconf.sh -h
-	EOF
-}
-
-help() {
-	cat <<-'EOF'
-
-		Options:
-		  -h, --help    show this message
-
-		  FILENAME      the name of the input file, also to be used as
-		                URL.
-
-
-		Separate the content from the "frontmatter", and emit the
-		selected one, given the FILENAME.
-
-
-		Examples:
-
-		  Get the "frontmatter" of src/f.conf:
-
-		    $ src/development/genconf.sh src/f.md > src/f.conf
-	EOF
-}
-
-
-for flag in "$@"; do
-	case "$flag" in
-		--)
-			break
-			;;
-		--help)
-			usage
-			help
-			exit
-			;;
-		*)
-			;;
-	esac
-done
-
-while getopts 'h' flag; do
-	case "$flag" in
-		h)
-			usage
-			help
-			exit
-			;;
-		*)
-			usage >&2
-			exit 2
-			;;
-	esac
-done
-shift $((OPTIND - 1))
-
-. src/development/lib.sh
-
-FILENAME="${1:-}"
-eval "$(assert_arg "$FILENAME" 'FILENAME')"
-
-
-escape() {
-	sed 's|\([`"$]\)|\\\1|g'
-}
-
-tee "$FILENAME".tmp < src/lib/base-conf
-DELIMITER=0
-while read -r line; do
-	if [ "$line" = '---' ]; then
-		DELIMITER=$((DELIMITER + 1))
-		continue
-	fi
-	if [ "$DELIMITER" = 2 ]; then
-		break
-	fi
-	if [ -z "$line" ]; then
-		continue
-	fi
-
-	KEY="$(  printf '%s' "$line" | cut -d: -f1)"
-	VALUE="$(printf '%s' "$line" | cut -d: -f2- | sed 's|^ ||' | escape)"
-	printf 'export %s="%s"\n' "$KEY" "$VALUE"
-done < "$FILENAME" | tee -a "$FILENAME".tmp
-# shellcheck source=/dev/null
-. "$FILENAME".tmp
-rm -f "$FILENAME".tmp
-
-cat src/lib/base."${lang:?}".conf
-# shellcheck source=/dev/null
-. src/lib/base."$lang".conf
-if [ -z "${title:-}" ]; then
-	title="${site_name:?}"
-	printf 'export title="%s"\n' "$(printf '%s' "$title" | escape)"
-fi
-
-if [ -n "${date:-}" ]; then
-	formatted_date="$(LANG="$lang" date -d "${date:?}" +"${date_fmt:?}")"
-	export formatted_date
-	printf 'export date_html="%s"\n' "$(envsubst < src/lib/date."$lang".html | escape)"
-fi
-
-if [ -n "${update:-}" ]; then
-	formatted_update="$(LANG="$lang" date -d "${update:?}" +"${date_fmt:?}")"
-	export formatted_update
-	printf 'export update_html="%s"\n' "$(envsubst < src/lib/update."$lang".html | escape)"
-fi
-
-
-url_part="$(printf '%s' "${FILENAME%.md}.html" | sed 's|^src/content/||')"
-title_uri="$(uri "$title")"
-
-printf 'export title_html="%s"\n' "$(printf '%s' "$title" | htmlesc | escape)"
-printf 'export filename="%s"\n' "$FILENAME"
-printf 'export url_part="%s"\n' "$url_part"
-printf 'export url="%s"\n' "$(url-for "$url_part" | absolute)"
-printf 'export mailto_uri="%s%s"\n' "${mailto_uri_prefix:?}" "$title_uri"
-printf 'export discussions_url="%s%s"\n' "${discussions_url_prefix:?}" "$title_uri"
-printf 'export sourcecode_url="%s%s"\n' "${sourcecode_url_prefix:?}" "$FILENAME"
-
-printf 'export style_url="%s"\n'   "$(url-for -g 'style.css')"
-printf 'export favicon_url="%s"\n' "$(url-for -g 'favicon.svg')"
-printf 'export pubkey_url="%s"\n'  "$(url-for -g 'public.asc.txt')"
-
-for f in src/content/img/*.svg; do
-	name="$(basename "$f" .svg | sed 's|-|_|g')"
-	printf 'export icon_%s_url="%s"\n' "$name" "$(url-for -g "img/$(basename "$f")")"
-done
-
-# FIXME: special treatment of root
-printf 'export homepage_url="%s"\n' "$(url-for '/')"
-
-printf 'export about_url="%s"\n' "$(url-for "${about_url_name:?}")"
-
-
-if [ "${layout:-}" = 'post' ]; then
-	export mailto_uri="$mailto_uri_prefix$title_uri"
-	export discussions_url="$discussions_url_prefix$title_uri"
-	export sourcecode_url="$sourcecode_url_prefix$FILENAME"
-	printf 'export comment_html="%s"\n' "$(envsubst < src/lib/comment."$lang".html | escape)"
-fi
diff --git a/v2/src/development/genhtml.sh b/v2/src/development/genhtml.sh
deleted file mode 100755
index 1b2718a..0000000
--- a/v2/src/development/genhtml.sh
+++ /dev/null
@@ -1,224 +0,0 @@
-#!/bin/sh
-set -eu
-
-usage() {
-	cat <<-'EOF'
-		Usage:
-		  genhtml.sh FILENAME
-		  genhtml.sh -h
-	EOF
-}
-
-help() {
-	cat <<-'EOF'
-
-		Options:
-		  -h, --help    show this message
-
-		  FILENAME      the name of the input file, to also be used as
-		                URL
-
-
-		Process the FILENAME, and generate a full HTML page.
-
-		The FILENAME is used to infer the output URL, by removing the
-		`src/content/` prefix, and replacing the trailing `.md` with
-		`.html`.  This URL is used to build the self-referencing
-		"canonical" link, extracting plaintext snippets, etc.
-
-
-		Examples:
-
-		  Generate the HTML for a pastebin:
-
-		    $ sh genhtml.sh src/content/a-paste.md > src/content/a-paste.html
-	EOF
-}
-
-
-for f in "$@"; do
-	case "$f" in
-		--)
-			break
-			;;
-		--help)
-			usage
-			help
-			exit
-			;;
-		*)
-			;;
-	esac
-done
-
-while getopts 'h' flag; do
-	case "$flag" in
-		h)
-			usage
-			help
-			exit
-			;;
-		*)
-			usage >&2
-			exit 2
-			;;
-	esac
-done
-shift $((OPTIND - 1))
-
-. src/development/lib.sh
-
-FILENAME="${1:-}"
-eval "$(assert_arg "$FILENAME" 'FILENAME')"
-
-
-# shellcheck source=/dev/null
-. "${FILENAME%.md}.conf"
-
-#
-# Utility functions
-#
-
-SEEN_SLUGS="$(mkstemp)"
-slugify_once() {
-	SLUG="$(printf '%s' "$1" | slugify)${2:+-$2}"
-	if grep -q "^$SLUG$" "$SEEN_SLUGS"; then
-		N="${2:-0}"
-		N=$((N + 1))
-		slugify_once "$1" "$N"
-	else
-		printf '%s\n' "$SLUG" >> "$SEEN_SLUGS"
-		printf '%s' "$SLUG"
-	fi
-}
-
-INDENT='        '
-markdown_to_html() {
-	md2html | awk -vINDENT="$INDENT" '
-		BEGIN {
-			in_block = 0
-		}
-
-		{
-			if (in_block == 0) {
-				printf "%s", INDENT
-			}
-			print
-		}
-
-		/^<\/code><\/pre>$/ {
-			in_block = 0
-		}
-
-		/^
 "$F"
-	(
-		IFS=''
-		BLOCK_NUMBER=0
-		IN_BLOCK=
-		while read -r line; do
-			if [ "$line" = '
' ]; then - IN_BLOCK= - fi - - if [ -n "$IN_BLOCK" ]; then - printf '%s\n' "$line" | htmlesc -d >> "$OUT" - fi - - if printf '%s' "$line" | grep -q "^$INDENT
\)\(.*\)$|\2|" |
-					htmlesc -d > "$OUT"
-				printf '%s\n' "$OUT" >> "$SNIPPETS"
-			fi
-		done < "$F"
-
-		BLOCK_NUMBER=0
-		while read -r line; do
-			printf '%s\n' "$line"
-
-			if [ "$line" = '
' ]; then - printf '%s\n' \ - "$INDENT" \ - "$(basename "${url_part:?}").$BLOCK_NUMBER.txt" - BLOCK_NUMBER=$((BLOCK_NUMBER + 1)) - fi - done < "$F" - ) - -} - -add_line_numbers() { - awk ' - /^<\/code><\/pre>$/ { - in_block = 0 - printf "
%s%s
%s%s
%s\n", $0 - next - } - - match($0, /^( +
)(.*)$/, a) {
-			printf "%s", a[1]
-
-			n = 1
-			block_count++
-			printf "\n", block_count, n, block_count, n, n, a[2]
-			in_block = 1
-			next
-		}
-
-		in_block == 1 {
-			n++
-			printf "\n", block_count, n, block_count, n, n, $0
-			next
-		}
-
-		{ print }
-	'
-}
-
-add_headings_anchors() {
-	(
-		IFS=''
-		while read -r line; do
-			if ! printf '%s' "$line" | grep -q "^$INDENT"; then
-				printf '%s\n' "$line"
-				continue
-			fi
-			LVL="$(printf '%s' "$line" | sed "s|^$INDENT.*|\1|")"
-			HEADING="$(printf '%s' "$line" | sed "s|^$INDENT\(.*\)$|\1|")"
-			SLUG="$(slugify_once "$HEADING")"
-			printf '%s%s\n' \
-				"$INDENT"  \
-				"$LVL"     \
-				"$SLUG"    \
-				"$HEADING" \
-				"$SLUG"    \
-				"${icon_link_url:?}" \
-				"$LVL"
-		done
-	)
-}
-
-emit_body() {
-	< "${FILENAME%.md}.content" \
-		markdown_to_html           |
-		extract_plaintext_snippets |
-		add_line_numbers           |
-		add_headings_anchors
-}
-
-envsubst < src/lib/preamble.html
-emit_body
-envsubst < src/lib/postamble.html
diff --git a/v2/src/development/lib.sh b/v2/src/development/lib.sh
deleted file mode 100644
index 9d183f9..0000000
--- a/v2/src/development/lib.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/sh
-
-assert_arg() {
-	if [ -z "$1" ]; then
-		printf 'Missing %s.\n\n' "$2" >&2
-		cat <<-'EOF'
-			usage >&2
-			exit 2
-		EOF
-	fi
-}
-
-uuid() {
-        od -xN20 /dev/urandom |
-                head -n1 |
-                awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}'
-}
-
-tmpname() {
-        echo "${TMPDIR:-/tmp}/uuid-tmpname with spaces.$(uuid)"
-}
-
-mkstemp() {
-	name="$(tmpname)"
-	touch "$name"
-	echo "$name"
-}
-
-mkdtemp() {
-        name="$(tmpname)"
-	mkdir "$name"
-        echo "$name"
-}
diff --git a/v2/src/development/security-txt.sh.in b/v2/src/development/security-txt.sh.in
deleted file mode 100755
index 8f6613f..0000000
--- a/v2/src/development/security-txt.sh.in
+++ /dev/null
@@ -1,82 +0,0 @@
-#!/bin/sh
-set -eu
-
-usage() {
-	cat <<-'EOF'
-		Usage:
-		  src/development/security-txt.sh
-		  src/development/security-txt.sh -h
-	EOF
-}
-
-help() {
-	cat <<-'EOF'
-
-
-		Options:
-		  -h, --help    show this message
-
-
-		Generate the RFC 9116 "security.txt" file from data in the
-		repository.
-
-
-		Examples:
-
-		  Just run it:
-
-		    $ sh src/development/security-txt.sh > .well-known/security.txt
-	EOF
-}
-
-
-for flag in "$@"; do
-	case "$flag" in
-		--)
-			break
-			;;
-		--help)
-			usage
-			help
-			exit
-			;;
-		*)
-			;;
-	esac
-done
-
-while getopts 'h' flag; do
-	case "$flag" in
-		h)
-			usage
-			help
-			exit
-			;;
-		*)
-			usage >&2
-			exit 2
-	esac
-done
-shift $((OPTIND - 1))
-
-
-
-EXPIRES="$(
-	LANG=C.UTF-8 gpg --list-key eu@euandre.org |
-		awk '/^pub/ { print substr($(NF), 1, 10) }'
-)T00:00:00z"
-
-LANGS="en$(
-	echo po/??.po |
-		sed 's|\.po$||'  |
-		sed 's|^po/|, |' |
-		paste -sd,
-)"
-
-
-cat <<-EOF
-	Contact: mailto:@EMAIL@
-	Encryption: https://@DOMAIN@/public.asc.txt
-	Expires: $EXPIRES
-	Preferred-Languages: $LANGS
-EOF
diff --git a/v2/src/lib.sh b/v2/src/lib.sh
new file mode 100644
index 0000000..9d183f9
--- /dev/null
+++ b/v2/src/lib.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+assert_arg() {
+	if [ -z "$1" ]; then
+		printf 'Missing %s.\n\n' "$2" >&2
+		cat <<-'EOF'
+			usage >&2
+			exit 2
+		EOF
+	fi
+}
+
+uuid() {
+        od -xN20 /dev/urandom |
+                head -n1 |
+                awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}'
+}
+
+tmpname() {
+        echo "${TMPDIR:-/tmp}/uuid-tmpname with spaces.$(uuid)"
+}
+
+mkstemp() {
+	name="$(tmpname)"
+	touch "$name"
+	echo "$name"
+}
+
+mkdtemp() {
+        name="$(tmpname)"
+	mkdir "$name"
+        echo "$name"
+}
diff --git a/v2/src/lib/base-conf.in b/v2/src/lib/base-conf.in
deleted file mode 100644
index 4ec09cb..0000000
--- a/v2/src/lib/base-conf.in
+++ /dev/null
@@ -1,10 +0,0 @@
-export domain='@DOMAIN@'
-export email='@EMAIL@'
-export base_url=''
-export list_addr='~euandreh/public-inbox@lists.sr.ht'
-export mailto_uri_prefix="mailto:$list_addr?Subject=Re%3A%20"
-export discussions_url_prefix="https://lists.sr.ht/~euandreh/public-inbox?search="
-export sourcecode_url_prefix="https://$domain/git/$domain/tree/"
-export author='EuAndreh'
-export lang='en'
-export pubkey_id='81F90EC3CD356060' # FIXME
diff --git a/v2/src/lib/base.conf b/v2/src/lib/base.conf
new file mode 100644
index 0000000..319d08c
--- /dev/null
+++ b/v2/src/lib/base.conf
@@ -0,0 +1,9 @@
+export domain="$DOMAIN"
+export email="$EMAIL"
+export base_url=''
+export list_addr='~euandreh/public-inbox@lists.sr.ht'
+export mailto_uri_prefix="mailto:$list_addr?Subject=Re%3A%20"
+export discussions_url_prefix="https://lists.sr.ht/~euandreh/public-inbox?search="
+export sourcecode_url_prefix="https://$domain/git/$domain/tree/"
+export author='EuAndreh'
+export pubkey_id='81F90EC3CD356060' # FIXME
diff --git a/v2/src/lib/entry.xml b/v2/src/lib/entry.xml
new file mode 100644
index 0000000..e4a47c7
--- /dev/null
+++ b/v2/src/lib/entry.xml
@@ -0,0 +1,22 @@
+  
+    
+      $title_html
+    
+    
+    
+      $date_iso
+    
+$update_xml
+    
+      $url
+    
+    
+      
+        $author
+      
+      
+        $email
+      
+    
+
+    
diff --git a/v2/src/lib/feed.xml b/v2/src/lib/feed.xml
new file mode 100644
index 0000000..6fd82a4
--- /dev/null
+++ b/v2/src/lib/feed.xml
@@ -0,0 +1,15 @@
+
+  
+  
+  $site_name_html
+  {{ site.t[include.kind].feed.title[include.lang] | smartify | xml_escape }}
+  $url
+  $now
+
+  
+    $author
+    $email
+  
-- 
cgit v1.2.3

%s%s
%s%s