${title}
diff --git a/ht b/ht
new file mode 100755
index 0000000..c8cef5c
--- /dev/null
+++ b/ht
@@ -0,0 +1,287 @@
+#!/bin/sh
+
+### Code:
+
+usage() {
+ cat <<\EOF
+ht: Build a website
+Usage: ht -h
+ ht [OPTIONS...] FILE...
+
+FLAGS:
+ -h Show this help and exit.
+ -B Treat all targets as "new" (like in `make').
+
+OPTIONS:
+ -u BASEURL The base URL of the built site. Default: https://example.com
+ -o OUTD Output built files to OUTD. Default: ./out/
+ -w WORKD Use WORKD as the working directory. Default: /tmp/ht/
+ -p PROC Use PROC to process each input file. Default: ./ht.awk
+ -P TEMPLATE Use TEMPLATE as the page template. Default: ./page.tmpl.htm
+ -i ITEMFMT Format individual items with ITEMFMT in the site index.
+ -I TEMPLATE Use TEMPLATE as the index template. Default: ./index.tmpl.htm
+ -f ITEMFMT Format individual items with ITEMFMT in the site feed.
+ -F TEMPLATE Use TEMPLATE as the feed template. Default: ./feed.tmpl.xml
+ -s DIRECTORY The DIRECTORY holding static files (images, css, etc.).
+ Default: ./static/
+ -S DIRECTORY The DIRECTORY to copy static files to. Default: ./out/static.
+
+PARAMETERS:
+ FILE... The list of files to include in the website.
+EOF
+ exit "${1:-0}"
+}
+
+main() {
+ configure "$@"
+ shift $((OPTIND - 1))
+ prepare
+ static_copy
+
+ for input; do
+ page_write "$PAGE_TEMPLATE" "$input"
+ done
+
+ if [ -n "$INDEXEACH" ]; then
+ index_write "$INDEX_TEMPLATE" "$INDEXEACH" "$OUTD/$INDEXNAME"
+ fi
+ if [ -n "$FEEDEACH" ]; then
+ index_write "$FEED_TEMPLATE" "$FEEDEACH" "$OUTD/$FEEDNAME"
+ fi
+ printf 'Done. ' >&2
+ if test -f "$WORKD/ok"; then
+ eprint "No errors reported."
+ else
+ eprint "There were errors."
+ fi
+}
+
+configure() {
+ OUTD="${HT_OUT_DIR:-./out}"
+ WORKD="${HT_WORKD:-/tmp/ht}"
+ PROC="${HT_PROC:-./ht.awk}" # XXX: Needs better resolution
+ BASEURL="${HT_BASEURL:-https://example.com}"
+ PAGE_TEMPLATE="${HT_PAGE_TEMPLATE:-./page.tmpl.htm}"
+ INDEX_TEMPLATE="${HT_INDEX_TEMPLATE:-./index.tmpl.htm}"
+ FEED_TEMPLATE="${HT_FEED_TEMPLATE:-./feed.tmpl.xml}"
+ ALLNEW=false
+ STATICD="${HT_STATIC_DIR:-./static}"
+ STATICOUT="${HT_STATIC_OUTPUT_DIR:-${OUTD}/static}"
+
+ INDEXNAME="${HT_INDEX_NAME:-index.html}"
+ if [ -n "$HT_INDEX_ITEM_FORMAT" ]; then
+ INDEXEACH="$HT_INDEX_ITEM_FORMAT"
+ else
+ # shellcheck disable=2016
+ INDEXEACH='
'
+ fi
+
+ FEEDNAME="${HT_FEED_NAME:-feed.xml}"
+ if [ -n "$HT_FEED_ITEM_FORMAT" ]; then
+ FEEDEACH="$HT_FEED_ITEM_FORMAT"
+ else
+ # shellcheck disable=2016
+ FEEDEACH='
+ $BASEURL/$file
+
+ ${title}
+ $(date -u -d "@${time}" -R)
+ $AUTHOR
+
+
+
+ '
+ fi
+
+ # shellcheck disable=2034 # BASEURL is used in templates
+ while getopts hBo:w:p:i:I:f:F:u:s:S: opt; do
+ case "$opt" in
+ h) usage ;;
+ o) OUTD="$OPTARG" ;;
+ w) WORKD="$OPTARG" ;;
+ p) PROC="$OPTARG" ;;
+ P) PAGE_TEMPLATE="$OPTARG" ;;
+ i) INDEXEACH="$OPTARG" ;;
+ I) INDEX_TEMPLATE="$OPTARG" ;;
+ f) FEEDEACH="$OPTARG" ;;
+ F) FEED_TEMPLATE="$OPTARG" ;;
+ u) BASEURL="$OPTARG" ;;
+ B) ALLNEW=true ;;
+ s) STATICD="$OPTARG" ;;
+ S) STATICOUT="$OPTARG" ;;
+ *) usage 1 ;;
+ esac
+ done
+
+ INDEX="$WORKD/index.txt"
+}
+
+prepare() {
+ test -n "$WORKD" && rm -rf "$WORKD"
+ mkdir -p "$OUTD" "$WORKD"
+ test -x "$PROC" || {
+ eprint "Can't find processor: $PROC"
+ exit 2
+ }
+ touch "$WORKD/ok"
+}
+
+### Utilities
+
+print() {
+ printf '%s\n' "$*"
+}
+
+eprint() {
+ print "$@" >&2
+}
+
+olderp() { # olderp REF OTHER
+ # Is REF older than OTHER ? Necessary b/c test -ot is not POSIX.
+ a="$1"
+ b="$2"
+
+ if a_age="$(stat -c%Y "$a" 2>/dev/null)"; then
+ a_exist=true
+ else
+ a_exist=false
+ fi
+ if b_age="$(stat -c%Y "$b" 2>/dev/null)"; then
+ b_exist=true
+ else
+ b_exist=false
+ fi
+
+ if $a_exist && ! $b_exist; then
+ # B doesn't exist -- so B is newer
+ # eprint "$a is older than $b (doesn't exist)"
+ return 0
+ elif ! $a_exist && $b_exist; then
+ # A doesn't exist -- so A is newer
+ # eprint "$a (doesn't exist) is newer than $b"
+ return 1
+ else
+ # A's age > B's age ?
+ # eprint "$a ($a_age) <= $b ($b_age)?"
+ test "$a_age" -le "$b_age"
+ fi
+}
+
+uptodate() { # uptodate FILE DEPENDENCIES... # && return
+ # Check if FILE is up-to-date with DEPENDENCIES.
+ $ALLNEW && return 1
+ uptodate=1
+ file="$1"
+ shift
+ for dep in "$@"; do
+ olderp "$dep" "$file" && uptodate=0
+ done
+ return $uptodate
+}
+
+### File conversion
+
+template_expand() { # template_expand TEMPLATES...
+ end="tmpl_$(date +%s)_${count:=0}"
+ eval "$(
+ print "cat <<$end"
+ cat "$@"
+ print
+ print "$end"
+ )"
+ count=$((count + 1))
+}
+
+meta_save() { # meta_save [-c COMMENTCH] [-m METACH] META_FILE < INPUT
+ COMMENTCH=';'
+ METACH='@'
+ while getopts c:m: opt; do
+ case "$opt" in
+ c) COMMENTCH="$OPTARG" ;;
+ m) METACH="$OPTARG" ;;
+ *) ;;
+ esac
+ done
+ shift $((OPTIND - 1))
+ sed -n "s/^${COMMENTCH}${COMMENTCH}${METACH}//p" 2>/dev/null | tee "$1"
+}
+
+meta_clear() { # meta_clear META_FILE
+ while read -r line; do
+ case "$line" in
+ *'()'*) unset -f "${line%()*}" ;;
+ *=*) unset -v "${line%=*}" ;;
+ *) ;;
+ esac
+ done <"$1" 2>/dev/null
+}
+
+page_write() { # html_write TEMPLATE INPUT
+ template="$1"
+ file="$2"
+
+ fn="${file##*/}"
+ fn="${fn%%.*}"
+ out="${OUTD}/${fn}/index.html"
+ mkdir -p "${out%/*}"
+ meta="$WORKD/${fn}.meta"
+
+ eval "$(meta_save "$meta" <"$file")"
+ stat -c "%Y ${fn} $meta" "$file" >>"$INDEX"
+
+ uptodate "$out" "$template" "$file" && return
+
+ eprint "Page: $file -> $out"
+ if
+ ! "$PROC" "$file" |
+ tee "$WORKD/${fn}.body" |
+ template_expand "$template" >"$out"
+ then
+ eprint "$file -> $out ... ERROR!"
+ rm "$WORKD/ok"
+ return
+ fi
+ meta_clear "$meta"
+}
+
+index_write() { # index_write TEMPLATE EACH OUTFILE
+ template="$1"
+ each="$2"
+ out="$3"
+
+ test -f "$INDEX" || return 1
+ uptodate "$out" "$template" "$INDEX" && return
+
+ eprint "Index: $out"
+ # shellcheck disable=2034 # file and time can be used in `each'
+ sort -k1,1 -nr "$INDEX" |
+ while IFS=' ' read -r time file meta; do
+ # shellcheck disable=1090
+ . "$meta"
+ if ! item="$(eval print "\"$each\"")"; then
+ eprint "ERROR: couldn't add '$file' to $out"
+ rm "$WORKD/ok"
+ fi
+ print "$item"
+ meta_clear "$meta"
+ done |
+ template_expand "$template" >"$out"
+}
+
+### Static files
+
+static_copy() { # static_copy
+ test -d "$STATICD" || return 1
+ if command -v rsync 2>/dev/null; then
+ eprint rsync -avz "$STATICD/" "$STATICOUT/"
+ rsync -avz "$STATICD/" "$STATICOUT/"
+ else
+ eprint cp -r "$STATICD"/* "$STATICOUT/"
+ cp -r "$STATICD"/* "$STATICOUT/"
+ fi
+}
+
+### Do the thing
+
+test "$DEBUG" && set -x
+test "$SOURCE" || main "$@"
diff --git a/ht.sh b/ht.sh
deleted file mode 100755
index 7f20255..0000000
--- a/ht.sh
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/bin/sh
-# ht.sh
-# *.ht -> *html
-
-# config
-header_file=header.htm
-footer_file=footer.htm
-meta_file=meta.sh
-
-print() {
- printf '%s\n' "$*"
-}
-
-htt() { # htt FILE
- # Like `cat`, but with templating.
- : "${HT_TMPL_COUNT:=0}"
- ht_end="ht_main_$(date +%s)_${HT_TMPL_COUNT}" # be extra double sure
- eval "$(
- print "cat <<$ht_end"
- cat "$@"
- print
- print "$ht_end"
- )"
- HT_TMPL_COUNT=$((HT_TMPL_COUNT + 1))
-}
-
-htmeta_clear() {
- # Generate metadata-clearing commands from $meta_file.
- while read -r line; do
- case "$line" in
- *'()'*) # function
- unset -f "${line%()*}"
- ;;
- *=*) # variable assignment
- unset -v "${line%=*}"
- ;;
- *) # other -- XXX: Don't know what to do
- ;;
- esac
- done <"$meta_file" 2>/dev/null
-}
-
-htmeta() { # htmeta FILE
- # Collect metadata from FILE.
- # Metadata looks like this: `;;@`
- sed -n 's/^;;@//p' "$1" 2>/dev/null | tee "$meta_file"
-}
-
-main() {
- # Make two passes over each input file, collecting metadata and content.
- # Of course, this isn't safe, but you trust yourself, right?
- for file; do
- eval "$(htmeta_clear)"
- eval "$(htmeta "$file")"
- ./ht.awk <"$file" | htt "$header_file" - "$footer_file"
- done
-}
-
-test "$DEBUG" && set -x
-test "$SOURCE" || main "$@"
diff --git a/index.tmpl.htm b/index.tmpl.htm
new file mode 100644
index 0000000..5884af5
--- /dev/null
+++ b/index.tmpl.htm
@@ -0,0 +1,12 @@
+
+
+
+ acdw!
+
+
+
+ $(cat)
+
+
+
+
diff --git a/page.tmpl.htm b/page.tmpl.htm
new file mode 100644
index 0000000..6655494
--- /dev/null
+++ b/page.tmpl.htm
@@ -0,0 +1,10 @@
+
+
+
+ ${title}
+
+
+ $(cat)
+
+
+
diff --git a/test.ht b/test.ht
deleted file mode 100644
index 58125a8..0000000
--- a/test.ht
+++ /dev/null
@@ -1,32 +0,0 @@
-# ht: a bespoke document preparation system
-;;@title="ht: a bespoke document preparation system"
-;; comments are like this.
-;; they're a good time.
-
-`ht
-is a quasi-line-based markup language that takes inspiration from
-@https://gemini.circumlunar.space/docs/gemtext.gmi gemtext\
-,
-@https://daringfireball.net/projects/markdown/ markdown\
-, and others.
-Its aim is to be somewhat easy to read while being fairly easy to parse.
-
-In fact,
-`ht
-is a simple awk script.
-
-## Usage
-
-- one
-- two
-- three
-
-ordered list:
-
-% one
-% two
-% three
-
-```
-./ht.awk source.ht
-```
--
cgit 1.4.1-21-gabe81