By dkl9, written 2023-170, revised 2023-234 (6 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 -Fq "$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
}
P=`printf 'x1b[92m'`
Q=`printf 'x1b[91m'`
R=`printf 'x1b[0m'`
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"
else
echo "${Q}can't build to target '$TARGET'$R"
fi
done
shift
done