#!/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='
  • ${title}
  • ' 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 "$@"