summaryrefslogtreecommitdiff
path: root/mkdeps.sh
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2026-09-03 09:19:35 -0300
committerEuAndreh <eu@euandre.org>2026-09-07 04:46:50 -0300
commit6ca718aca59eac9f250badd26f9b118e705d0c20 (patch)
treecd74733352a79303d23dd6029cabfaca2fafbc33 /mkdeps.sh
parentsrc/static.conf ($discussions_url_prefix): Fix typo in address (diff)
downloadeuandre.org-6ca718aca59eac9f250badd26f9b118e705d0c20.tar.gz
euandre.org-6ca718aca59eac9f250badd26f9b118e705d0c20.tar.xz
m
Diffstat (limited to '')
-rwxr-xr-xmkdeps.sh383
1 files changed, 280 insertions, 103 deletions
diff --git a/mkdeps.sh b/mkdeps.sh
index 9df3e5a..496c13e 100755
--- a/mkdeps.sh
+++ b/mkdeps.sh
@@ -4,163 +4,340 @@ set -eu
export LANG=POSIX.UTF-8
+## -m so a path need not exist: po4a's outputs are named here
+## before po4a has written them --- naming them is what the rules
+## being generated are for --- and realpath without it fails on
+## the first one, taking the whole script with it under set -e.
+## For a path that does exist the answer is the same either way.
normalize() {
- xargs realpath --relative-to=. | sort
+ ## realpath(1) did this, but it is a page of string work and
+ ## this runs it dozens of times: doing it here saves a
+ ## process and most of the time.
+ awk '
+ {
+ n = split($0, c, "/")
+ top = 0
+ for (i = 1; i <= n; i++) {
+ if (c[i] == "" || c[i] == ".") {
+ continue
+ }
+ if (c[i] == ".." && top > 0 &&
+ out[top] != "..") {
+ top--
+ continue
+ }
+ out[++top] = c[i]
+ }
+ p = ""
+ for (i = 1; i <= top; i++) {
+ p = p (i > 1 ? "/" : "") out[i]
+ }
+ if (p != "") {
+ print p
+ }
+ }
+ ' | sort
}
-pages() {
- find src/pages/*/*.adoc -type f | normalize
+## Every page there is: what the repository carries, plus what
+## po4a writes from it. A translation need not exist yet -- on a
+## fresh clone none of them do -- so it is named, not found.
+universe() {
+ {
+ find src/content -type f -name '*.adoc'
+ page_translations
+ } | normalize | uniq
}
-articles() {
- find src/collections/*/*/*/ -type f -name '*.adoc' | normalize
+## The classifying rules read the masters, which the repository
+## always carries; a translation is whatever its master was.
+homes() {
+ find src/content/*/index.adoc -type f | normalize |
+ only_masters | with_translations | normalize
}
-slides() {
- find src/slides/*/*/ -type f -name '*.adoc' | normalize
+collections() {
+ grep -l '^:type: collection$' src/content/*/*/index.adoc |
+ normalize | only_masters | xargs -n1 dirname
}
indexes() {
- find src/collections/*/*/index.adoc | normalize
+ all_collections | sed 's|$|/index.adoc|' | normalize
+}
+
+## Every collection there is, translations included: the rules
+## below emit a listing, a feed and a sortdata list for each, and
+## a translated collection needs all three as much as its master.
+all_collections() {
+ collections | sed 's|$|/index.adoc|' | with_translations |
+ sed 's|/index.adoc$||' | normalize
}
categories() {
- grep -l '^:type: categories$' src/collections/*/*/*.adoc | normalize
+ grep -l '^:type: categories$' src/content/*/*/*.adoc |
+ normalize | only_masters | with_translations | normalize
+}
+
+articles() {
+ collections | xargs -I% find % -mindepth 3 -name '*.adoc' |
+ normalize | only_masters | with_translations | normalize
+}
+
+slides() {
+ find src/content -type f -name '*.eslaides' | normalize
+}
+
+## Whatever no other rule claimed, at any depth.
+pages() {
+ claimed="$(mktemp)"
+ { homes; indexes; categories; articles; } | sort -u > "$claimed"
+ universe | grep -vxF -f "$claimed"
+ rm -f "$claimed"
}
media() {
- find src/content/ \( -type f -and \! -type l \) -and \( \
+ # src/content/music is excluded: those .ogg files are score
+ # renderings, not published media, and the old site never
+ # offered them over BitTorrent.
+ find src/content/ -path 'src/content/music' -prune -or \
+ \( \( -type f -and \! -type l \) -and \( \
-name '*.flac' -or \
-name '*.ogg' -or \
-name '*.webm' \
- \)
+ \) -print \) | normalize
+}
+
+## A glob that matches nothing makes find exit non-zero, which
+## under set -e would end the script. There is no music yet.
+music() {
+ find src/content/music/*.ly 2>/dev/null | normalize || true
}
tarballs() {
find src/content/ \( -type f -and \! -type l \) -and \( \
-name '*.tar.gz' \
- \)
+ \) | normalize
+}
+
+## Short paths that stand in for a page's own, kept as symlinks
+## beside it. Guarded like music() above: a site need not have any,
+## and find exits non-zero on a directory that is not there.
+shortener() {
+ find src/content/l -type l 2>/dev/null | normalize || true
}
extras() {
media
tarballs
+ shortener
}
-listings() {
- indexes
- categories
+## Which pages are one another's translations. po4a already says
+## so, set by set, so read it there rather than keep a second list
+## that can quietly disagree with the first.
+## The languages po4a translates into: whichever .po files exist.
+langs() {
+ if [ -z "${LANGS_CACHE:-}" ]; then
+ LANGS_CACHE="$(
+ find po -maxdepth 1 -name '*.po' |
+ sed 's|.*/||; s|\.po$||' | sort
+ )"
+ export LANGS_CACHE
+ fi
+ echo "$LANGS_CACHE"
}
-files() {
- pages
- articles
- listings
+## One stanza per line, master first, then the files po4a writes
+## from it. "$lang:" stands for every language, and "add_$lang:"
+## names an addendum rather than an output.
+po4a_sets() {
+ langs | tr '\n' ' ' | {
+ ## read hits EOF on the last field and reports it, which
+ ## under set -e would end the block before awk ran.
+ read -r all || true
+ awk -v langs="$all" '
+ /^\[type:/ { set = "" }
+ /^\[type:/ || cont {
+ line = $0
+ sub(/^\[type:[^]]*\][ \t]*/, "", line)
+ cont = (line ~ /\\$/)
+ sub(/[ \t]*\\$/, "", line)
+ n = split(line, f, /[ \t]+/)
+ nl = split(langs, L, " ")
+ for (i = 1; i <= n; i++) {
+ if (f[i] == "" || f[i] ~ /^add_/) continue
+ if (f[i] ~ /^\$lang:/) {
+ sub(/^\$lang:/, "", f[i])
+ for (j = 1; j <= nl; j++) {
+ t = f[i]
+ gsub(/\$lang/, L[j], t)
+ set = set " " t
+ }
+ continue
+ }
+ sub(/^[a-z]+:/, "", f[i])
+ set = set (set == "" ? "" : " ") f[i]
+ }
+ if (!cont && set != "") { print set; set = "" }
+ }
+ ' po/po4a.cfg
+ }
}
+## Everything po4a writes: every field of a stanza but the first.
+translations() {
+ cut -d' ' -f2- "$(po4a_cache)" |
+ tr ' ' '\n' | grep . | normalize
+}
+
+## Just the pages among them. The switcher offers a reader the
+## same page in another language; the data files under src/i18n
+## are not pages.
+page_translations() {
+ translations | grep '\.adoc$'
+}
+
+## Drops the translations from a list of paths, so a rule reads
+## the masters whether or not po4a has run.
+## po4a.cfg does not change while this runs, so parse it once:
+## every selector below asks for the same answer.
+po4a_cache() {
+ if [ -z "${PO4A_CACHE:-}" ]; then
+ PO4A_CACHE="$(mktemp)"
+ po4a_sets > "$PO4A_CACHE"
+ export PO4A_CACHE
+ fi
+ echo "$PO4A_CACHE"
+}
+
+translations_cache() {
+ if [ -z "${TRANSLATIONS_CACHE:-}" ]; then
+ TRANSLATIONS_CACHE="$(mktemp)"
+ translations > "$TRANSLATIONS_CACHE"
+ export TRANSLATIONS_CACHE
+ fi
+ echo "$TRANSLATIONS_CACHE"
+}
+
+only_masters() {
+ awk 'NR == FNR { t[$0]; next } !($0 in t)' \
+ "$(translations_cache)" -
+}
+
+## Reads masters on stdin, writes them and their translations.
+with_translations() {
+ awk -v sets="$(po4a_cache)" '
+ BEGIN {
+ while ((getline line < sets) > 0) {
+ n = split(line, f, /[ \t]+/)
+ for (i = 2; i <= n; i++) {
+ t[f[1]] = t[f[1]] " " f[i]
+ }
+ }
+ }
+ {
+ print
+ if ($0 in t) {
+ n = split(t[$0], g, " ")
+ for (i = 1; i <= n; i++) {
+ print g[i]
+ }
+ }
+ }
+ '
+}
+
+## Prime the caches here, in this shell. Every pipeline stage
+## below runs in a subshell, so a cache filled inside one is gone
+## by the next: filling them once, up here, is what makes them
+## caches rather than seven identical computations.
+PO4A_CACHE="$(mktemp)"
+po4a_sets > "$PO4A_CACHE"
+TRANSLATIONS_CACHE="$(mktemp)"
+translations > "$TRANSLATIONS_CACHE"
+LANGS_CACHE="$(find po -maxdepth 1 -name '*.po' |
+ sed 's|.*/||; s|\.po$||' | sort)"
+export PO4A_CACHE TRANSLATIONS_CACHE LANGS_CACHE
+trap 'rm -f "$PO4A_CACHE" "$TRANSLATIONS_CACHE"' EXIT
pages | varlist 'pages.adoc'
+homes | varlist 'homes.adoc'
articles | varlist 'articles.adoc'
-slides | varlist 'slides.adoc'
+slides | varlist 'slides.eslaides'
categories | varlist 'categories.adoc'
indexes | varlist 'indexes.adoc'
indexes | sed 's|/index\.adoc$|/feed.xml|' | varlist 'feeds.xml'
-find src/content/img/ -name '*.svg' | varlist 'images.svg'
+find src/content/img/ -name '*.svg' | sort | varlist 'images.svg'
media | varlist 'sources.media'
tarballs | varlist 'sources.tarballs'
extras | varlist 'sources.extras'
find po/*.po po/*.pot | varlist 'sources.po'
+music | varlist 'music.ly'
+find src/content/music/*.ogg 2>/dev/null | varlist 'music.ogg'
+find src/content/music/*.ly.include 2>/dev/null | \
+ varlist 'music.include'
+# lilypond emits the score alongside the MIDI, from one run.
+music | sed 's/^\(.*\)\.ly$/\1.pdf:\t\1.midi/'
-{
- files | sed 's/^\(.*\)\.adoc$/\1.html/'
- files | sed 's/^\(.*\)\.adoc$/\1.snippets/'
- slides | sed 's/^\(.*\)\.adoc$/\1.pdf/'
- indexes | sed 's|^\(.*\)/index\.adoc$|\1/feed.xml|'
- media | sed 's/^\(.*\)$/\1.torrent/'
-} | sed 's/^\(.*\)$/\1.gz:\t\1/'
-printf '\n'
-
-files | sed 's/^\(.*\)\.adoc$/\1.htmlbody\t\1.snippets\t\1.conf:\t\1.adoc/'
-files | sed 's/^\(.*\)\.adoc$/\1.html:\t\1.conf\t\1.htmlbody/'
-printf '\n'
-
-files | sed 's/^\(.*\)\.adoc$/\1.updatedat-check:\t\1.conf/'
-files | sed 's/^\(.*\)\.adoc$/\1.links-internal-check:\t\1.links/'
-files | sed 's/^\(.*\)\.adoc$/\1.caslinks:\t\1.links/'
-printf '\n'
-
-articles | sed 's/^\(.*\)\.adoc$/\1.feedentry:\t\1.conf\t\1.htmlbody/'
-articles | sed 's/^\(.*\)\.adoc$/\1.sortdata:\t\1.conf/'
-articles | sed 's/^\(.*\)\.adoc$/\1.categorydata:\t\1.conf/'
-printf '\n'
-
-listings | sed 's/^\(.*\)\.adoc$/\1.htmlheader\t\1.htmlfooter:\t\1.conf/'
-listings | sed 's/^\(.*\)\.adoc$/\1.htmllisting:\t\1.conf/'
-listings | sed 's/^\(.*\)\.adoc$/\1.html:\t\1.htmlheader\t\1.htmlfooter/'
-listings | sed 's/^\(.*\)\.adoc$/\1.html:\t\1.htmllisting\t\1.htmlbody/'
-printf '\n'
-
-media | sed 's/^\(.*\)$/\1.torrent:\t\1/'
-printf '\n'
-
-slides | sed 's/^\(.*\)\.adoc$/\1.ps:\t\1.adoc/'
-slides | sed 's/^\(.*\)\.adoc$/\1.pdf:\t\1.ps/'
-printf '\n'
-
-
+## A short link points into the built tree, so on a fresh clone it
+## dangles, and make --- which stats through a symlink --- reports no
+## rule for a file that is right there. Name the page each one points
+## at as what the link is made from.
+shortener | while read -r link; do
+ target="$(dirname "$link")/$(readlink "$link")"
+ printf '%s:\t%s\n' "$link" \
+ "$(printf '%s\n' "$target" | normalize)"
+done
-collectionentries() {
- langlink="$1"
- colllink="$2"
- lang="$(basename "$langlink")"
- c="$(printf '%s' "$colllink" | normalize)"
- plural="$(cat src/names/categories/"$lang".txt)"
- printf '\n\n'
- name="$(basename "$c")"
- art=articles."$lang"
- find "$c"/*/ -type f -name '*.adoc' | varlist "$art.$name.adoc"
- echo "$art.$name.sortdata = \$($art.$name.adoc:.adoc=.sortdata)"
- echo "$art.$name.indexentry = \$($art.$name.adoc:.adoc=.indexentry)"
- echo "$art.$name.feedentry = \$($art.$name.adoc:.adoc=.feedentry)"
- echo "$art.$name.categorydata = \$($art.$name.adoc:.adoc=.categorydata)"
+## What this site has, for mkwb to say what each artifact is
+## built from. Finding them is the site's half: only it knows
+## where its pages live, or that it keeps decks and no music.
+{
+ pages | sed 's/^/page\t/'
+ homes | sed 's/^/home\t/'
+ articles | sed 's/^/article\t/'
+ indexes | sed 's/^/index\t/'
+ categories | sed 's/^/category\t/'
+ slides | sed 's/^/slide\t/'
+ media | sed 's/^/media\t/'
+} | mkwb deps src/config.json
- printf '%s/sortdata.txt:\tdeps.mk\n' "$c"
- printf '\tprintf %s $(%s.%s.sortdata) > $@\n\n' "'%s\n'" "$art" "$name"
+## What po4a writes. Naming them here is what lets them be
+## derived: deps.mk is written before make runs, so a translation
+## that is not named cannot be built, and would have to be
+## committed instead.
+translations | varlist 'translations'
- listings="
- feed.xml
- index.htmllisting
- $plural.htmllisting
- $plural.txt
- "
- for lst in $listings; do
- printf '%s/%s:\t%s/sortdata.txt\n' "$c" "$lst" "$c"
- printf '%s/%s:\t$(%s.%s.sortdata)\n' "$c" "$lst" "$art" "$name"
- done
+## One run writes all of them, so they hang off a stamp rather
+## than each invoking po4a for itself.
+printf 'src/i18n.stamp: po/po4a.cfg %s\n' "$(find po -maxdepth 1 -name '*.po' | sort | tr '\n' ' ')"
+printf '\tpo4a po/po4a.cfg\n'
+printf '\ttouch $@\n\n'
+printf '$(translations): src/i18n.stamp\n\n'
- printf '%s/index.htmllisting\t' "$c"
- printf '%s/%s.htmllisting:\t' "$c" "$plural"
- printf '$(%s.%s.indexentry)\n' "$art" "$name"
- printf '%s/%s.txt:\t' "$c" "$plural"
- printf '$(%s.%s.categorydata)\n' "$art" "$name"
+## The map mkwb conf reads to link a page to its translations,
+## written out here so it cannot drift from po4a.cfg.
+printf '\ntranslations.txt = src/translations.txt\n\n'
+printf 'src/translations.txt: deps.mk\n'
+printf '\tprintf %s \\\n' "'%s\\n'"
+po4a_sets | grep '\.adoc' | sed "s/^/\t\t'/; s/\$/' \\\\/"
+printf '\t\t> $@\n\n'
- printf '%s/%s.txt\t' "$c" "$plural"
- printf '%s/feed.xml:\t' "$c"
- printf '$(%s.%s.feedentry)\n' "$art" "$name"
- printf '%s/%s.htmllisting\t' "$c" "$plural"
- printf '%s/%s.xml:\t' "$c" "$plural"
- printf '%s/%s.txt\n' "$c" "$plural"
+## The canonical rules, inlined so no include is needed
+## at make time.
+echo
+mkwb rules
- printf '%s/%s.xml.gz:\t%s/%s.xml\n' "$c" "$plural" "$c" "$plural"
-}
-
-for langlink in src/collections/*; do
- for colllink in "$langlink"/*; do
- collectionentries "$langlink" "$colllink"
- done
-done
+## Everything mkwb renders reads the site's wording table, which
+## po4a writes. The rules above define the variables, so this
+## comes after them. It has to name what renders rather than
+## what a page is made of: an article never writes a .htmlheader
+## of its own, so naming that missed every one of them.
+printf '\n$(sources.html) $(sources.json): src/i18n.stamp\n'
+printf '$(listings.htmllisting) $(categories.xml): src/i18n.stamp\n'
+printf '$(articles.feedentry): src/i18n.stamp\n'