Document build script (build.sh)

By dkl9, written 2023-170, revised 2024-187 (8 revisions)


This is my overengineered static site generator. There are many like it, but this one is mine. If you want to run this for yourself, download the source and run it as a shell script.

§ getfield

Given stdin = simplified Recfile, $1 = key, output "key: value" line

getfield() {
    FLINE=`grep "^${1}:"`
    FLINE="${FLINE#*: }"
    echo -n "$FLINE"
}

§ varsubs

Given $1 = base filename, $2 = simplified Recfile of substitutions, replace $var references in base file with values

varsubs() {
    while read LINE
    do
        KEY="${LINE%%:*}"
        VALUE="${LINE#*: }"
        sed -i "s@$${KEY}@${VALUE}@g" "$1"
    done <"$2"
}

§ havesource

Given $1 = sources field, $2, $3, etc = desired sources, succeed iff $2, $3, etc are all in $1

havesource() {
    SL="$1"
    shift
    while [ -n "$1" ]
    do
        echo "$SL" | grep -Fqw "$1" || return 1
        shift
    done
}

§ recfill

Given $1 = Markdown template filename, $2 = simplified Recfile of entries, overwrite $1 with expanded-and-substituted Markdown

recfill() {
    REC_PARTS=`mktemp -d`
    CUR_REC=0
    ON_REC=false
    MD_TEMP=`mktemp`
    FULL_MD=`mktemp`
    echo "${P}splitting rec$R"
    while read REC_LINE
    do
        if [ -n "$REC_LINE" ]
        then
            if ! "$ON_REC"
            then
                ON_REC=true
                CUR_REC=$((CUR_REC+1))
            fi
            echo "$REC_LINE" >>"${REC_PARTS}/${CUR_REC}"
        else
            ON_REC=false
        fi
    done <"$2"
    MAX_REC="$CUR_REC"
    CUR_REC=1
    echo "${P}processing sections$R"
    while [ "$CUR_REC" -le "$MAX_REC" ]
    do
        cp "$1" "$MD_TEMP"
        varsubs "$MD_TEMP" "${REC_PARTS}/${CUR_REC}"
        cat "$MD_TEMP" >>"$FULL_MD"
        CUR_REC=$((CUR_REC+1))
    done
    echo "${P}processed$R"
    mv "$FULL_MD" "$1"
    rm -r "$REC_PARTS" "$MD_TEMP"
}

§ md2html

Given stdin = Markdown, output HTML in the formatting we want

md2html() {
    cmark --unsafe | while read L
    do
        if [ "${L#<h?>}" != "$L" ]
        then
            RH="${L#<h}"
            RH="${RH%%>*}"
            HT="${L#<h?>}"
            HT="${HT%</h?>}"

cf old commonplace (unpublished) #486: normalise the headings for fragment slugs

            ID=`echo "$HT" | sed -E 's/<[^>]+>//g' | tr A-Z a-z | tr -cd 'a-z0-9 - -' | tr ' ' '-' | sed 's/--+/-/g'`
            echo "<h${RH}><a name="${ID}" href="#${ID}">§</a> $HT</h${RH}>"
        else
            echo "$L"
        fi
    done
}

§ Formatting variables

P=`printf 'x1b[92m'`
Q=`printf 'x1b[91m'`
R=`printf 'x1b[0m'`

§ Argument loop

For each argument (a document slug), build the document.

while [ -n "$1" ]
do
    echo "${P}building $1$R"
    TARGETS=`getfield targets <"${1}.."`
    TARGETS="${TARGETS} "
    SOURCES=`getfield sources <"${1}.."`

For each target (a file extension), build slug.target from the sources, by a procedure that varies depending on the target and source types.

    while [ -n "$TARGETS" ]
    do
        TARGET="${TARGETS%% *}"
        TARGETS="${TARGETS#* }"
        HF=`getfield header <"${1}.."`
        HF="${HF:-header_en}.${TARGET}"
        FF=`getfield footer <"${1}.."`
        FF="${FF:-footer_en}.${TARGET}"

§ html from sh

Run the script slug.sh, saving its standard output to slug.html. (Special case for build.sh itself.)

        if [ "$TARGET" = html ] && havesource "$SOURCES" "sh"
        then
            echo "${P}sh to html$R"
            if [ "$1" = build ]
            then
                cp "$HF" "${1}.html"
                varsubs "${1}.html" "${1}.."
                ICB=false
                IFS=''
                while read -r L
                do
                    if [ "$L" = '' ] || [ "${L###!}" != "$L" ]
                    then
                        continue
                    fi
                    TL=`echo "$L" | sed 's/^ +//'`
                    if [ "${TL###}" != "$TL" ] && "$ICB"
                    then
                        echo '```'
                        ICB=false
                    elif [ "${TL###}" = "$TL" ] && ! "$ICB"
                    then
                        echo '```sh'
                        ICB=true
                    fi
                    if "$ICB"
                    then
                        echo "$L"
                    else
                        echo "${TL### }"
                    fi
                done <"${1}.sh" | tee build.md | md2html | cat - "$FF" >>"${1}.html"
            else
                "./${1}.sh" >"${1}.html"
            fi

§ html from ..., rec, md

Generate a temporary Recfile by merging metadata for all documents. Then fill in templates from slug.md according to slug.rec and the temporary Recfile, respectively. Combine all that, converted to HTML, into slug.html.

        elif [ "$TARGET" = html ] && havesource "$SOURCES" "..." "rec" "md"
        then
            echo "${P}... + rec + md to html$R"
            cp "$HF" "${1}.html"
            varsubs "${1}.html" "${1}.."
            K=0
            TPLA=`mktemp`
            TPLB=`mktemp`
            while read SL
            do
                if [ "$SL" = '---' ]
                then
                    K="$((K+1))"
                else
                    case "$K" in
                        0) echo "$SL" ;;
                        1) echo "$SL" >>"$TPLA" ;;
                        2) echo "$SL" >>"$TPLB" ;;
                    esac
                fi
            done <"${1}.md" | md2html >>"${1}.html"
            recfill "$TPLA" "${1}.rec"
            md2html <"$TPLA" >>"${1}.html"
            RECB=`mktemp`
            for A in *..
            do
                if [ -z `getfield unlisted <"$A"` ]
                then
                    echo -e "$(getfield pub_date <"$A")t$A"
                fi
            done | sort -r | cut -f 2 | while read B
            do
                echo "slug: ${B%..}"
                cat "$B"
                echo
            done >"$RECB"
            recfill "$TPLB" "$RECB"
            echo '---' | md2html >>"${1}.html"
            md2html <"$TPLB" >>"${1}.html"
            rm "$TPLA" "$TPLB" "$RECB"
            cat "$FF" >>"${1}.html"

§ html from rec, md

Take the part of slug.md before --- as a one-time introduction. Take the part after --- as a template to be filled in with values from each record in slug.rec. Combine all that, converted to HTML, into slug.html.

        elif [ "$TARGET" = html ] && havesource "$SOURCES" "rec" "md"
        then
            echo "${P}rec + md to html$R"
            cp "$HF" "${1}.html"
            varsubs "${1}.html" "${1}.."
            sed '/^---$/q' <"${1}.md" | md2html >>"${1}.html"
            TEMPLATE=`mktemp`
            sed '1,/^---$/d' <"${1}.md" >"$TEMPLATE"
            recfill "$TEMPLATE" "${1}.rec"
            md2html <"$TEMPLATE" >>"${1}.html"
            rm "$TEMPLATE"
            cat "$FF" >>"${1}.html"

§ html from md

Convert Markdown (slug.md) to HTML with the CommonMark cmark utility.

        elif [ "$TARGET" = html ] && havesource "$SOURCES" "md"
        then
            echo "${P}md to html$R"
            cp "$HF" "${1}.html"
            varsubs "${1}.html" "${1}.."
            md2html <"${1}.md" >>"${1}.html"
            cat "$FF" >>"${1}.html"

§ pdf from md

Convert Markdown to PDF with cmark and groff.

        elif [ "$TARGET" = pdf ] && havesource "$SOURCES" "md"
        then
            echo "${P}md to pdf$R"
            MS_FILE=`mktemp`
            cp "${HF%.pdf}.ms" "$MS_FILE"
            varsubs "$MS_FILE" "${1}.."
            sed 's/^##/#/' <"${1}.md" |                 cmark -t man |                 sed 's/^.SH$/.SH 2/; s/^.SS$/.SH 3/; s/^.PP$/.LP/' >>"$MS_FILE"
            cat "${FF%.pdf}.ms" >>"$MS_FILE"
            groff -D utf8 -T pdf -ms <"$MS_FILE" >"${1}.pdf"
            rm "$MS_FILE"

§ atom from ..., xml

Generate a temporary Recfile by merging metadata for all documents. Then fill in templates from slug.xml according to the temporary Recfile. Combine those filled templates with a header and footer into slug.atom.

        elif [ "$TARGET" = atom ] && havesource "$SOURCES" "xml"
        then
            echo "${P}... + xml to atom$R"

review this copied code

            cp "$HF" "${1}.atom"
            varsubs "${1}.atom" "${1}.."
            K=0
            TPLA=`mktemp`
            cp "${1}.xml" "$TPLA"
            RECB=`mktemp`
            for A in *..
            do
                if [ -z `getfield unlisted <"$A"` ]
                then
                    echo -e "$(getfield pub_date <"$A")t$A"
                fi
            done | sort -r | cut -f 2 | while read B
            do
                echo "slug: ${B%..}"
                cat "$B"
                echo
            done >"$RECB"
            recfill "$TPLA" "$RECB"
            cat "$TPLA" >>"${1}.atom"
            rm "$TPLA" "$RECB"
            cat "$FF" >>"${1}.atom"
        else
            echo "${Q}can't build to target '$TARGET'$R"
        fi
    done
    shift
done