Skip to content

Instantly share code, notes, and snippets.

@rascul
Last active October 21, 2022 08:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rascul/108d357078b4f794cee00400143d886e to your computer and use it in GitHub Desktop.
Save rascul/108d357078b4f794cee00400143d886e to your computer and use it in GitHub Desktop.
bash static site generator
# Feel free to edit this config. Don't remove any options.
declare -A config=(
# Directories. No trailing slashes.
["content"]="content"
["output"]="www"
["templates"]="templates"
# Add .html extention to generated files
["extensions"]="no"
# Markdown command, must read from stdin and write to stdout
["markdown"]="markdown"
# This is a command to deploy the site, or possibly launch another script
# to deploy it if deployment is complicated.
["deploy"]="rsync"
# If another server is desired, change this command to start it.
["server"]="python -m SimpleHTTPServer 8000 &"
# use nginx and the provided config to serve extensionless files as html
#["server"]="nginx -c /home/rascul/site/nginx.conf"
)
#!/usr/bin/env bash
shopt -s nullglob
server_pid=
# Load the config
if [[ ! -e rc ]]; then
echo "rc file not found."
exit 1
fi
. rc
stop_server() {
if [[ "$server_pid" ]]; then
kill $server_pid
fi
pkill -P $$
exit
}
# checks if a source file is newer than the destination file, to determine if
# the source files should be generated or copied to the destination, or not
# $1 :: source file
# $2 :: destination file
file_newer() {
if [[ -e "$1" && -e "$2" ]]; then
if [[ $(stat -c %Y "$1") > $(stat -c %Y "$2") ]]; then
return 0
else
return 1
fi
else
return 0
fi
}
# render a template
# $1 :: input file
# $2 :: output file
render_template() {
declare -A matter
content=
m=
# get the front matter, then the content
while IFS= read -r line; do
if [[ $line =~ ^---*$ ]]; then
m=1
elif [[ "$m" ]]; then
content+="$line\n"
else
matter[${line/: *}]="${line/${line/: *}: }"
fi
done < "$1"
if [[ ! "${matter[template]}" ]]; then
printf 'no template in front matter\n' >&2
return 1
fi
# convert the markdown
content=$(printf '%b' "$content" | ${config[markdown]})
t="${config[output]}"
matter[path]="${2#$t}"
rm -f "$2"
run=
# fill in the template
while IFS= read -r line; do
if [[ $line =~ ^[[:blank:]]*\#\ ]]; then
t=${line#${line%%[![:space:]]*}}
run+="${t:2}\n"
line=
else
if [[ "$run" ]]; then
printf '%b' "$run" | /usr/bin/env bash
run=
fi
while [[ $line =~ \{\{\ ([[:alpha:]]+)\ \}\} ]]; do
m=${BASH_REMATCH[1]}
if [[ $m = "content" ]]; then
line=${line//\{\{ $m \}\}/$content}
else
line=${line//\{\{ $m \}\}/${matter[$m]}}
fi
done
fi
if [[ "$line" ]]; then
printf '%b\n' "$line"
fi
done < "${config[templates]}/${matter[template]}" > "$2"
}
# recursively build the site
# $1 :: content directory
# $2 :: output directory
build_dir() {
if [[ ! -d "$2" ]]; then
mkdir -p "$2"
fi
for f in "$1"/*; do
o="${f/${config[content]}/${config[output]}}"
if [[ -d "$f" ]]; then
build_dir "$f" "$o"
else
if [[ ${f##*.} = md ]]; then
if [[ "${config[extensions]}" = "yes" ]]; then
o="${o/%.md/.html}"
else
o="${o/%.md}"
fi
if file_newer "$f" "$o"; then
printf '> %s\n' "$o"
render_template "$f" "$o"
fi
elif file_newer "$f" "$o"; then
printf '+ %s\n' "$o"
cp "$f" "$o"
fi
fi
done
}
# build command
command_build() {
if [[ $1 = "full" ]]; then
rm -rf "${config[output]}"
fi
build_dir "${config[content]}" "${config[output]}"
}
# serve command
command_serve() {
echo "* full build and serve"
command_build full
echo "* press ctrl+c to quit"
cd "${config[output]}"
${config[server]} &
server_pid=$!
cd ..
while true; do
trap stop_server int
inotifywait -qqr -e modify,move,create,delete \
"${config[content]}" "${config[templates]}" &&
command_build
done
}
# what to do?
case $1 in
help)
shift
command_help
exit
;;
build)
shift
command_build "$@"
exit
;;
serve)
shift
command_serve
exit
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment