#!/bin/sh # # https://www.romanzolotarev.com/bin/ssg # Copyright 2018 Roman Zolotarev # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # CONF_FILE="$PWD/_ssg.conf" # shellcheck disable=SC1090 [ -f "$CONF_FILE" ] && . "$CONF_FILE" [ -n "$DOCS" ] || { echo 'to build set DOCS in _ssg.conf'; exit 1; } [ -n "$ROOT" ] || { echo 'to build set ROOT in _ssg.conf'; exit 1; } ########################################################################## DOCUMENT_ROOT=$(readlink -fn "$DOCS") TEMP_DIR=$(mktemp -d) # shellcheck disable=SC2064 trap 'clean_up' EXIT trap exit HUP INT TERM [ "$2" = '--clean' ] && RSYNC_FLAGS='--delete-excluded' || RSYNC_FLAGS='' INDEX_HTML_FILE="$TEMP_DIR/index.html" RSS_FILE="$TEMP_DIR/rss.xml" RSS_URL="$ROOT/rss.xml" SITEMAP="$TEMP_DIR/sitemap.xml" SITEMAP_HTML="$TEMP_DIR/sitemap.html" CSS_FILE="$PWD/_styles.css" JS_FILE="$PWD/_scripts.js" HEADER_FILE="$PWD/_header.html" FOOTER_FILE="$PWD/_footer.html" [ -f "$CSS_FILE" ] && CSS="" [ -f "$JS_FILE" ] && JS="" [ -f "$HEADER_FILE" ] && HEADER="$(cat "$HEADER_FILE")" [ -f "$FOOTER_FILE" ] && FOOTER="$(cat "$FOOTER_FILE")" ########################################################################## usage() { echo 'usage: DOCS=' echo echo ' ssg build [--clean]' echo ' | watch [--clean]' exit 1 } copy_to_temp_dir() { rsync -a --delete-excluded \ --exclude '.*' \ --exclude '_*' \ '.' "$TEMP_DIR" } copy_to_document_root() { [ "$(dirname "$DOCUMENT_ROOT")" = "$PWD" ] && self="/$(basename "$DOCUMENT_ROOT")/" || self="$DOCUMENT_ROOT" rsync -a $RSYNC_FLAGS \ --exclude "$self" \ --exclude '.*' \ --exclude '_*' \ "$TEMP_DIR/" "$DOCUMENT_ROOT" } md_to_html() { find "$TEMP_DIR" -type f -name '*.md'| while read -r file; do lowdown -D html-skiphtml -D smarty -d metadata \ "$file" > "${file%\.md}.html" && rm "$file" done } # filter first 20 lines with links and link titles (dates) # shellcheck disable=SC2016 fst_h1='/<[h1]*( id=".*")?>/{gsub(/<[^>]*>/,"");print($0);exit;}' a='^
  • .*.*<\/a>.*<\/li>.*' line_to_rss_item() { url=$(echo "$line"|sed "s/$a/\\1/g") date=$(echo "$line"|sed "s/$a/\\2/g") file="${TEMP_DIR}${url}" [ ! -f "$file" ] && return title="$(awk "$fst_h1" "$file")" # replace relative URIs with absolute URIs href='s#href="#href="'"$ROOT"'/#g' src='s#src="#src="'"$ROOT"'#g' article=$(sed "$href;$src" "$file") printf "%s" "$(cat << EOF $title ${ROOT}$url ${ROOT}$url $date 00:00:00 +0000 EOF )"|sed 's/\ / /g' >> "$RSS_FILE" } index_to_rss() { date_rfc_822=$(date "+%a, %d %b %Y %H:%M:%S %z") cat > "$RSS_FILE" << EOF $WEBSITE_TITLE $RSS_DESCRIPTION $ROOT/ $date_rfc_822 $RSS_AUTHOR EOF grep "$a" "$INDEX_HTML_FILE" | head -n20 | while read -r line; do line_to_rss_item "$line"; done echo '' >> "$RSS_FILE" } wrap_html() { # generate sorted sitemap find_h1_tag='/<[h1]*( id=".*")?>/' # shellcheck disable=SC2016 tag_content='{gsub(/<[^>]*>/,"");print(FILENAME"===="$0);exit;}' sitemap="$( find "$TEMP_DIR" -type f -name '*.html'| while read -r file; do awk "${find_h1_tag}${tag_content}" "$file" done|sort )" [ -z "$sitemap" ] && { echo && return; } # save sitemap in html and xml formats date=$(date +%Y-%m-%dT%H:%M:%S%z) echo '' > "$SITEMAP_HTML" cat > "$SITEMAP" << EOF EOF echo "$sitemap"|while read -r line; do page=${line%====*} url=${page#$TEMP_DIR} case "$url" in /index.html) title='Home';; *) title="${line#*====}";; esac { printf '\n' "$title" } >> "$SITEMAP_HTML" cat >> "$SITEMAP" << EOF ${ROOT}$url $date 1.0 EOF done echo '' >> "$SITEMAP" # generate html pages echo "$sitemap"| while read -r line; do page=${line%====*} url=${page#$TEMP_DIR} article=$(cat "$page") case "$url" in /index.html) title='Home' head_title="$WEBSITE_TITLE" ;; *) title="${line#*====}" head_title="$title - $WEBSITE_TITLE" ;; esac # merge page with html template cat > "$page" < $head_title $CSS $JS $HEADER $article $FOOTER EOF done echo "$date $(echo "$sitemap"|wc -l|tr -d ' ')pp" } clean_up() { rm -rf "$TEMP_DIR"; } ########################################################################## case "$1" in build) ls index.* >/dev/null 2>&1 || { echo 'no index.* found in the directory'; exit 1; } [ ! -x "$(which rsync)" ] && { echo 'rsync(1) should be installed'; exit 1; } [ ! -x "$(which lowdown)" ] && { echo 'lowdown(1) should be installed'; exit 1; } printf 'building %s %s ' "$DOCUMENT_ROOT" "$2" copy_to_temp_dir md_to_html index_to_rss wrap_html copy_to_document_root clean_up ;; watch) cmd="entr -d env DOCS=$DOCS $(basename "$0") build $2" pgrep -qf "$cmd" && { echo "already watching $DOCS"; exit 1; } echo "watching $PWD" [ ! -x "$(which entr)" ] && { echo 'entr(1) should be installed'; exit 1; } while true; do find "$PWD" -type f \ \( -name "$(basename "$0")" \ -or -name '*.conf' \ -or -name '*.md' \ -or -name '*.html' \ -or -name '*.css' \ -or -name '*.js' \ -or -name '*.txt' \ -or -name '*.jpg' \ -or -name '*.png' \)\ ! -name ".*" \ ! -path "*/.*" \ ! -path "${DOCUMENT_ROOT}*" | $cmd done ;; *) usage;; esac
    ' printf '%s' "$url" "${url#/*}" printf '%s