#!/bin/sh # Copyright (C) 2018 Amin Bandali # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # ssng is a fork of Roman Zolotarev's ssg. See end of file for ssg's # license notice. : "${WEBSITE_TITLE:=Roman Zolotarev}" : "${SERVER_NAME:=www.romanzolotarev.com}" : "${SERVER_PROTO:=https}" : "${RSS_AUTHOR:=hi@romanzolotarev.com (Roman Zolotarev)}" : "${RSS_DESCRIPTION:=Personal website}" : "${COPYRIGHT_YEAR:=2016}" ########################################################################## [ -n "$DOCS" ] || { echo "export DOCS "; 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" CSS_FILE="$TEMP_DIR/styles.css" RSS_FILE="$TEMP_DIR/rss.xml" RSS_URL="$SERVER_PROTO://$SERVER_NAME/rss.xml" SITEMAP="$TEMP_DIR/sitemap.xml" ANNOUNCEMENT_FILE="$PWD/announcement.html" FOOTER_FILE="$PWD/footer.html" HEADER_FILE="$PWD/header.html" [ -f "$ANNOUNCEMENT_FILE" ] && ANNOUNCEMENT_TEXT=$(cat "$ANNOUNCEMENT_FILE") [ -f "$HEADER_FILE" ] && HEADER=$(cat "$HEADER_FILE") || HEADER=$(cat << EOF Home - Twitter EOF ) [ -f "$FOOTER_FILE" ] && FOOTER=$(cat "$FOOTER_FILE") || FOOTER=$(cat << EOF Copyright $COPYRIGHT_YEAR–$(date +%Y) $WEBSITE_TITLE EOF ) ########################################################################## 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 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 article=$(sed "s/\\([hrefsc]*\\)=\"\\//\\1=\"$prefix/g" "$file") echo $(cat << EOF $title $SERVER_PROTO://${SERVER_NAME}$url $SERVER_PROTO://${SERVER_NAME}$url $date 00:00:00 +0000 EOF )|sed 's/\ /\ /'>>"$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 $SERVER_PROTO://$SERVER_NAME/ $date_rfc_822 $RSS_AUTHOR EOF prefix="$SERVER_PROTO:\\/\\/$SERVER_NAME\\/" 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 )" # save sitemap in html and xml formats date=$(date +%Y-%m-%dT%H:%M:%S%z) 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 cat >> "$SITEMAP" << EOF $SERVER_PROTO://${SERVER_NAME}$url $date 1.0 EOF done echo '' >> "$SITEMAP" # generate html pages styles=$(cat "$CSS_FILE") [ -n "$ANNOUNCEMENT_TEXT" ] && announcement="$(cat << EOF
    $ANNOUNCEMENT_TEXT
    EOF )" 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" header__home='' ;; *) title="${line#*====}" head_title="$title - $WEBSITE_TITLE" header__home="$HEADER" ;; esac # merge page with html template cat > "$page" < $head_title
    $announcement
    $header__home
    $article
    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 '*.md' \ -or -name '*.html' \ -or -name '*.css' \ -or -name '*.txt' \ -or -name '*.jpeg' \ -or -name '*.png' \)\ ! -name ".*" \ ! -path "*/.*" \ ! -path "${DOCUMENT_ROOT}*" | $cmd done ;; *) usage;; esac ## ssg's license: ## # 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.