#!/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
}
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" | htmlesc -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|' |
htmlesc -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() {
< "${FILENAME%.md}.content" \
markdown_to_html |
extract_plaintext_snippets |
add_line_numbers |
add_headings_anchors
}
if false; then
emit_body
fi
#
# Main: generate the HTML to STDOUT.
#
cat <<-EOF
<!DOCTYPE html>
<html lang="${lang:?}">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" type="text/css" href="${style_url:?}" />
<link rel="icon" type="image/svg+xml" href="${favicon_url:?}" />
<title>$$title_html</title>
<meta name="author" content="EuAndreh" />
<meta property="og:site_name" content="${site_name:?}" />
<meta property="og:locale" content="${lang:?}" />
<meta property="og:title" content="${title_html:?}" />
<link rel="canonical" href="${url:?}" />
<meta property="og:url" content="${url:?}" />
</head>
<body>
<header>
<nav>
<ul>
<a href="$(url-for "$lang/")">EuAndreh</a>
<a href="$(url-for 'about.html')">About</a>
</ul>
</nav>
<hr />
</header>
<main>
<article>
\$(emit_body)
<hr />
EOF
exit
# .md.rehtml:
# F="$<"; . "$${F%.md}.conf"; envsubst < src/lib/reamble."$$lang".html > $@
#
# .md.osthtml:
# F="$<"; . "$${F%.md}.conf"; envsubst < src/lib/ostamble."$$lang".html > $@
#
#
# <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://euandre.org/git/euandre.org/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:$email">$email</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>
# 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://euandre.org/git/euandre.org">code</a> is <a rel="license" href="https://euandre.org/git/euandre.org/tree/COPYING">AGPLv3 or later</a>. Patches welcome.
# </p>
# </footer>
# </body>
# </html>
# EOF
# 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
# - config.env should depend on dynamic.mk?