From 6b5dd0eb9feeeeb4c71cc9f908b0b6ab8d8ae294 Mon Sep 17 00:00:00 2001 From: Case Duckworth Date: Tue, 27 Sep 2022 23:20:10 -0500 Subject: vienna --- vienna | 210 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100755 vienna diff --git a/vienna b/vienna new file mode 100755 index 0000000..f7daabc --- /dev/null +++ b/vienna @@ -0,0 +1,210 @@ +#!/bin/sh +# vienna --- a tiny, tasty ssg +# by C. Duckworth + +### Entry point + +configure() { # configure ARG... + ## Set up environment + DOMAIN="${VIENNA_DOMAIN:-https://www.example.com}" + TMPD="${VIENNA_TMPD:-/tmp/vienna}" + WORKD="${VIENNA_WORKD:-$PWD}" + OUTD="${VIENNA_OUTD:-out}" + CONFIG="${VIENNA_CONFIG:-./.vienna.sh}" + # Templates + PAGE_TEMPLATE="${VIENNA_PAGE_TEMPLATE:-.page.tmpl.html}" + INDEX_TEMPLATE="${VIENNA_INDEX_TEMPLATE:-.index.tmpl.html}" + FEED_TEMPLATE="${VIENNA_FEED_TEMPLATE:-.feed.tmpl.xml}" + # File extensions + PAGE_RAW_EXT="${VIENNA_PAGE_RAW_EXT:-htm}" + ## Source ./.vienna.sh, if it exists. + # Sourcing it here lets it override any environment variables, and it to + # be overridden by command-line flags. + test -f "$CONFIG" && . "$CONFIG" + ## Parse command line arguments + while getopts d:C:o: opt; do + case "$opt" in + C) WORKD="$OPTARG" ;; + d) DOMAIN="$OPTARG" ;; + o) OUTD="$OPTARG" ;; + *) exit 1 ;; + esac + done + ## Initialize state + FILE= + ## Cleanup after we're done + trap cleanup INT KILL +} + +main() { + # State predicates + alias pagep=false indexp=false feedp=false + # Convenience aliases + alias body=cat title='meta title' pubdate='meta date' + # Configure + configure "$@" + shift "$((OPTIND - 1))" + # Prepare + cd "$WORKD" || exit 2 + mkdir -p "$OUTD" || exit 2 + mkdir -p "$TMPD" || exit 2 + # Build pages + alias pagep=true + build *."$PAGE_RAW_EXT" || exit 2 + alias pagep=false + # Build index + alias indexp=true + index *."$PAGE_RAW_EXT" || exit 2 + alias indexp=false + # Build feed + alias feedp=true + feed *."$PAGE_RAW_EXT" || exit 2 + alias feedp=false + # Copy static files + static * || exit 2 + # If $1 is 'publish', yeet the out/ directory somewhere + if test "x$1" = xpublish; then + publish "$OUTD" + fi +} + +cleanup() { + test -z "$DEBUG" && + test -z "$NODEP_RM" && + rm -r "$TMPD" +} + +publish() { + echo >&2 "I want to publish your website but I don't know how." + echo >&2 "Make a $CONFIG file in this directory and write a" + echo >&2 "\`publish' function telling me what to do. I rec-" + echo >&2 "ommend using \`rsync', but you live your life." + exit 3 +} + +### File processing + +## Building block functions + +shellfix() { # shellfix FILE... + ## Replace ` with \`, $ with \$, and $$ with $ + sed -E \ + -e 's/`/\\`/g' \ + -e 's/(^|[^\$])\$([^\$]|$)/\1\\$\2/g' \ + -e 's/\$\$/$/g' \ + "$@" +} + +expand() { # expand TEMPLATE... < INPUT + ## Print TEMPLATE to stdout, expanding shell constructs. + end="expand_:_${count:=0}_:_end" + eval "$( + echo "cat<<$end" + shellfix "$@" + echo + echo "$end" + )" && count=$((count + 1)) +} + +phtml() { # phtml < INPUT + ## Output HTML, pretty much. + # Paragraphs unadorned with html tags will be wrapped in

tags, and + # &, <, > will be escaped unless prepended with \. Paragraphs where the + # first character is < will be left as-is, excepting indentation on the + # first line (an implementation detail). + sed -E \ + '/./{H;1h;$!d;}; x; + s#^[ \n\t]\+##; + t ok; :ok; + s#^[^<].*#&#; + t par; b; + :par; + s#([^\\])&#\1\&#g; s#\\&#\&#g; + s#([^\\])<#\1\<#g; s#\\<#<#g; + s#([^\\])>#\1\>#g; s#\\>#>#g;' +} + +meta() { # meta FIELD [FILE] < INPUT + ## Extract metadata FIELDS from INPUT. + # FILE gives the filename to save metadata to in the $WORKD. It + # defaults to the current value for $FILE. + # + # Metadata should exist as colon-separated data in an HTML comment at + # the beginning of an input file. + field="$1" + file="${2:-$FILE}" + metafile="$TMPD/${file}.meta" + test -f "$metafile" || + sed '//q' >"$metafile" + sed -n "s/^[ \t]*$1:[ \t]*//p" <"$metafile" +} + +## Customizable bits + +filters() { # filters < INPUT + ## The filters to run input through. + # This is a good candidate for customization in .vienna.sh. + expand | phtml +} + +### Site building + +build() { # build PAGE... + ## Compile PAGE(s) into $OUTD for publication. + # Outputs a file of the format $OUTD//index.html. + test -f "$PAGE_TEMPLATE" || return 1 + for FILE; do + echo >&2 "[build] $FILE" + outd="$OUTD/${FILE%.$PAGE_RAW_EXT}" + outf="$outd/index.html" + tmpf="$TMPD/$FILE.tmp" + mkdir -p "$outd" + filters <"$FILE" >"$tmpf" + expand "$PAGE_TEMPLATE" <"$tmpf" >"$outf" + done +} + +index() { # index PAGE... + ## Build a site index from all PAGE(s) passed to it. + # Wraps each PAGE in a

  • structure. + test -f "$INDEX_TEMPLATE" || return 1 + for FILE; do + echo >&2 "[index] $FILE" + link="$DOMAIN${DOMAIN:+/}${FILE%.$PAGE_RAW_EXT}" + echo "
  • $(meta title "$FILE")
  • " + done | expand "$INDEX_TEMPLATE" >"$OUTD/index.html" +} + +feed() { # feed PAGE... + ## Build an RSS 2.0 feed from PAGE(s). + test -f "$FEED_TEMPLATE" || return 1 + for FILE; do + echo >&2 "[feed] $FILE" + link="$DOMAIN${DOMAIN:+/}${FILE%.$PAGE_RAW_EXT}" + date="$(meta pubdate "$FILE")" + echo "" + echo "$(meta title "$FILE")" + echo "$link" + echo "$link" + test -n "$date" && echo "$date" + echo "" + done | expand "$FEED_TEMPLATE" >"$OUTD/feed.xml" +} + +static() { # static FILE... + ## Copy static FILE(s) to $OUTD as-is. + # Performs a simple heuristic to determine whether to copy a file or + # not. + for FILE; do + case "$FILE" in + .*) continue ;; + "$OUTD") continue ;; + *) cp -r "$FILE" "$OUTD/" ;; + esac + done +} + +### Do the thing! + +test -n "$DEBUG" && set -x +test -n "$SOURCE" || main "$@" -- cgit 1.4.1-21-gabe81