#!/bin/sh
set -eu
# FIXMEs:
# - feeds
# - link to next and/or previous in <head>
# - translation support
# - validate input variables: regex for date (same as _plugins/linter.rb)
# - `date -d` isn't POSIX
# - parse commonmark and use a custom HTML emitter over <pre><code> regex
# - handle mixture of personal scripts
# - sitemap? How does it even work?
# - dark mode
# - generate security.txt dynamically
# - make url_for a standalone executable
# - config.env should depend on dynamic.mk?
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))
FILENAME="${1:-}"
eval "$(assert-arg "$FILENAME" 'FILENAME')"
#
# Utility functions
#
url_for() {
printf '%s/%s' "$BASE_URL" "$1"
}
absolute() {
printf 'https://%s%s' "$FQDN" "$(cat)"
}
translate() {
printf '%s' "$1"
}
_() {
translate "$1" | html
}
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
}
extract_content() {
awk '
separator >= 2
/^---$/ { separator++ }
'
}
add_preamble() {
printf '%s\n%s\n' "$PREAMBLE" "$(cat -)"
}
markdown_to_html() {
md2html
}
extract_plaintext_snippets() {
F="$(mkstemp)"
cat > "$F"
(
IFS=''
BLOCK_NUMBER=0
IN_BLOCK=
while read -r line; do
if [ "$line" = '</code></pre>' ]; then
IN_BLOCK=
fi
if [ -n "$IN_BLOCK" ]; then
printf '%s\n' "$line" | html -d >> "$OUT"
fi
if printf '%s' "$line" | grep -q '^<pre><code.*>'; then
IN_BLOCK=1
OUT="${FILENAME%.md}.html.$BLOCK_NUMBER.txt"
BLOCK_NUMBER=$((BLOCK_NUMBER + 1))
printf '%s\n' "$line" |
sed 's|^\(<pre><code.*>\)\(.*\)$|\2|' |
html -d > "$OUT"
fi
done < "$F"
BLOCK_NUMBER=0
while read -r line; do
printf '%s\n' "$line"
if [ "$line" = '</code></pre>' ]; then
printf '<p class="plaintext-link"><a href="%s.%s.txt">plaintext</a></p>\n' "$(url_for "$URL")" "$BLOCK_NUMBER"
BLOCK_NUMBER=$((BLOCK_NUMBER + 1))
fi
done < "$F"
)
}
add_line_numbers() {
awk '
/^<\/code><\/pre>$/ {
in_block = 0
printf "</tbody></table>%s\n", $0
next
}
match($0, /^(<pre><code.*>)(.*)$/, a) {
printf "%s<table rules=columns class=\"code-block\"><tbody>", a[1]
n = 1
block_count++
printf "<tr><td class=\"line-number\"><a id=\"B%s-L%s\" href=\"#B%s-L%s\">%s</a></td><td class=\"code-line\">%s</td></tr>\n", block_count, n, block_count, n, n, a[2]
in_block = 1
next
}
in_block == 1 {
n++
printf "<tr><td class=\"line-number\"><a id=\"B%s-L%s\" href=\"#B%s-L%s\">%s</a></td><td class=\"code-line\">%s</td></tr>\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 '^<h[2-6]>'; then
printf '%s\n' "$line"
continue
fi
LVL="$(printf '%s' "$line" | sed 's|^<h\(.\)>.*|\1|')"
HEADING="$(printf '%s' "$line" | sed 's|^<h.>\(.*\)</h.>$|\1|')"
SLUG="$(slugify_once "$HEADING")"
printf '<h%s class="header-anchor" id="%s">%s<a href="#%s" aria-hidden="true"><img class="svg-icon" src="%s" /></a></h%s>\n' \
"$LVL" \
"$SLUG" \
"$HEADING" \
"$SLUG" \
"$(url_for 'static/link.svg')" \
"$LVL"
done
)
}
emit_body() {
cat "$FILENAME" |
extract_content |
add_preamble |
markdown_to_html |
extract_plaintext_snippets |
add_line_numbers |
add_headings_anchors
}
#
# Environment variables
#
. src/development/config.env
eval "$(
awk '
/^---$/ { if (++separator > 1) exit; else next; }
{ print }
' "$FILENAME"
)"
SITE_NAME="$(_ "EuAndreh's website")"
TITLE="$(_ "${TITLE:-$SITE_NAME}")"
URI_TITLE="$(printf '%s' "$TITLE" | uri)"
URL="$(printf '%s' "$FILENAME" | sed -e 's|^src/content/||' -e 's|md$|html|')"
PREAMBLE="$(
cat <<-EOF
# $TITLE
<p class="timestamp">
Posted on <time datetime="$DATE">$(LANG="$LANGUAGE" date -d "$DATE" "$DATE_FMT")</time>
</p>
EOF
)"
#
# Main: generate the HTML to STDOUT.
#
cat <<-EOF
<!DOCTYPE html>
<html lang="$LANGUAGE">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" type="text/css" href="$(url_for 'static/styles.css')" />
<link rel="icon" type="image/svg+xml" href="$(url_for 'static/favicon.svg')" />
<title>$TITLE</title>
<meta name="author" content="EuAndreh" />
<meta property="og:site_name" content="$SITE_NAME" />
<meta property="og:locale" content="$LANGUAGE" />
<meta property="og:title" content="$TITLE" />
<link rel="canonical" href="$(url_for "$URL" | absolute)" />
<meta property="og:url" content="$(url_for "$URL" | absolute)" />
</head>
<body>
<header>
<nav>
<ul>
<a href="$(url_for "$LANGUAGE/")">$(_ 'EuAndreh')</a>
<a href="$(url_for "$(_ 'about.html')")">$(_ 'About')</a>
</ul>
</nav>
<hr />
</header>
<main>
<article>
$(emit_body)
<hr />
<p class="post-footer">
<a href="mailto:~euandreh/public-inbox@lists.sr.ht?Subject=Re%3A%20$URI_TITLE">Comment</a>
and see
<a href="https://lists.sr.ht/~euandreh/public-inbox?search=$URI_TITLE">existing discussions</a>
|
<a href="https://euandreh.xyz/euandre.org.git/tree/$FILENAME">view source</a>
</p>
</article>
</main>
<footer>
<hr />
<ul>
<li>
<img class="svg-icon" src="$(url_for 'static/envelope.svg')" alt="$(_ 'a envelope icon representing an email address')" />
<a href="mailto:eu@euandre.org">eu@euandre.org</a>
</li>
<li>
<img class="svg-icon" src="$(url_for 'static/lock.svg')" alt="$(_ 'a lock icon representing a GPG public key')" />
<a href="$(url_for 'static/public.asc.txt')">81F90EC3CD356060</a>
</li>
</ul>
<p>
$(translate 'The content for this site is licensed under <a rel="license" href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a>. The <a href="https://euandreh.xyz/euandre.org.git">code</a> is <a rel="license" href="https://euandreh.xyz/euandre.org.git/tree/COPYING">AGPLv3 or later</a>. Patches welcome.')
</p>
</footer>
</body>
</html>
EOF