all repos — aaoth.xyz @ 8d962aabf74c42c3afdc38f2f85fa7b06fd04ef0

aaoth.xyz website

bin/ssg (view raw)

  1#!/bin/sh -e
  2#
  3# https://rgz.ee/bin/ssg6
  4# Copyright 2018-2019 Roman Zolotarev <hi@romanzolotarev.com>
  5# Copyright 2022 la-ninpre <aaoth@aaoth.xyz>
  6#
  7# Permission to use, copy, modify, and/or distribute this software for any
  8# purpose with or without fee is hereby granted, provided that the above
  9# copyright notice and this permission notice appear in all copies.
 10#
 11# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 12# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 13# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 14# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 15# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 16# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 17# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 18#
 19
 20main() {
 21	test -n "$1" || usage
 22	test -n "$2" || usage
 23	test -n "$3" || usage
 24	test -n "$4" || usage
 25    test -n "$5" || usage
 26	test -d "$1" || no_dir "$1"
 27	test -d "$2" || no_dir "$2"
 28	test -d "$3" || no_dir "$3"
 29
 30	src=$(readlink_f "$1")
 31	dst=$(readlink_f "$2")
 32    gemdst=$(readlink_f "$3")
 33
 34	IGNORE=$(
 35		if ! test -f "$src/.ssgignore"; then
 36			printf ' ! -path "*/.*"'
 37			return
 38		fi
 39		while read -r x; do
 40			test -n "$x" || continue
 41			printf ' ! -path "*/%s*"' "$x"
 42		done <"$src/.ssgignore"
 43	)
 44
 45	# files
 46
 47	title="$4"
 48
 49	h_file="$src/_header.html"
 50	f_file="$src/_footer.html"
 51    gf_file="$src/_gemfooter.gmi"
 52	test -f "$f_file" && FOOTER=$(cat "$f_file") && export FOOTER
 53	test -f "$h_file" && HEADER=$(cat "$h_file") && export HEADER
 54    test -f "$gf_file" && GEMFOOTER=$(cat "$gf_file") && export GEMFOOTER
 55
 56	list_dirs "$src" |
 57		(cd "$src" && cpio -pdu "$dst")
 58	list_dirs "$src" |
 59		(cd "$src" && cpio -pdu "$gemdst")
 60
 61	fs=$(
 62		if test -f "$dst/.files"; then
 63			list_affected_files "$src" "$dst/.files"
 64            if test -f "$gemdst/.files"; then
 65                list_affected_files "$src" "$gemdst/.files"
 66            fi
 67		else
 68			list_files "$1"
 69		fi
 70	)
 71
 72	if test -n "$fs"; then
 73		echo "$fs" | tee "$dst/.files"
 74		echo "$fs" | tee "$gemdst/.files"
 75
 76		if echo "$fs" | grep -q '\.md$'; then
 77			if test -x "$(which lowdown 2>/dev/null)"; then
 78				echo "$fs" | grep '\.md$' |
 79					render_md_files_lowdown "$src" "$dst" "$title"
 80				echo "$fs" | grep '\.md$' |
 81					render_md_files_lowdown_gemini "$src" "$gemdst"
 82
 83            else
 84                echo "couldn't find lowdown nor Markdown.pl"
 85                exit 3
 86            fi
 87		fi
 88
 89		echo "$fs" | grep '\.html$' |
 90			render_html_files "$src" "$dst" "$title"
 91
 92		echo "$fs" | grep -Ev '\.md$|\.html$' |
 93			(cd "$src" && cpio -pu "$dst")
 94		echo "$fs" | grep -Ev '\.md$|\.gmi$' |
 95			(cd "$src" && cpio -pu "$gemdst")
 96	fi
 97
 98	printf '[ssg] ' >&2
 99	print_status 'file, ' 'files, ' "$fs" >&2
100
101	# sitemap
102
103	base_url="$5"
104	date=$(date +%Y-%m-%d)
105	urls=$(list_pages "$src")
106
107	test -n "$urls" &&
108		render_sitemap "$urls" "$base_url" "$date" >"$dst/sitemap.xml"
109
110	print_status 'url' 'urls' "$urls" >&2
111	echo >&2
112}
113
114readlink_f() {
115	file="$1"
116	cd "$(dirname "$file")"
117	file=$(basename "$file")
118	while test -L "$file"; do
119		file=$(readlink "$file")
120		cd "$(dirname "$file")"
121		file=$(basename "$file")
122	done
123	dir=$(pwd -P)
124	echo "$dir/$file"
125}
126
127print_status() {
128	test -z "$3" && printf 'no %s' "$2" && return
129
130	echo "$3" | awk -v singular="$1" -v plural="$2" '
131	END {
132		if (NR==1) printf NR " " singular
133		if (NR>1) printf NR " " plural
134	}'
135}
136
137usage() {
138	echo "usage: ${0##*/} src dst gemdst title base_url" >&2
139	exit 1
140}
141
142no_dir() {
143	echo "${0##*/}: $1: No such directory" >&2
144	exit 2
145}
146
147list_dirs() {
148	cd "$1" && eval "find . -type d ! -name '.' ! -path '*/_*' $IGNORE"
149}
150
151list_files() {
152	cd "$1" && eval "find . -type f ! -name '.' ! -path '*/_*' $IGNORE"
153}
154
155list_dependant_files() {
156	e="\\( -name '*.html' -o -name '*.md' -o -name '*.css' -o -name '*.js' \\)"
157	cd "$1" && eval "find . -type f ! -name '.' ! -path '*/_*' $IGNORE $e"
158}
159
160list_newer_files() {
161	cd "$1" && eval "find . -type f ! -name '.' $IGNORE -newer $2"
162}
163
164has_partials() {
165	grep -qE '^./_.*\.html$|^./_.*\.js$|^./_.*\.css$'
166}
167
168list_affected_files() {
169	fs=$(list_newer_files "$1" "$2")
170
171	if echo "$fs" | has_partials; then
172		list_dependant_files "$1"
173	else
174		echo "$fs"
175	fi
176}
177
178render_html_files() {
179	while read -r f; do
180		render_html_file "$3" <"$1/$f" >"$2/$f"
181	done
182}
183
184render_md_files_lowdown() {
185	while read -r f; do
186		lowdown \
187			--html-no-escapehtml \
188			--html-no-skiphtml \
189			--parse-no-metadata \
190			--parse-no-autolink <"$1/$f" |
191			render_html_file "$3" \
192				>"$2/${f%\.md}.html"
193	done
194}
195
196render_md_files_lowdown_gemini() {
197	while read -r f; do
198		lowdown \
199			-Tgemini <"$1/$f" |
200			render_gmi_file \
201				>"$2/${f%\.md}.gmi"
202    done
203}
204
205render_html_file() {
206	# h/t Devin Teske
207	awk -v title="$1" '
208	{ body = body "\n" $0 }
209	END {
210		body = substr(body, 2)
211		if (body ~ /<\/?[Hh][Tt][Mm][Ll]/) {
212			print body
213			exit
214		}
215		if (match(body, /<[[:space:]]*[Hh]1(>|[[:space:]][^>]*>)/)) {
216			t = substr(body, RSTART + RLENGTH)
217			sub("<[[:space:]]*/[[:space:]]*[Hh]1.*", "", t)
218			gsub(/^[[:space:]]*|[[:space:]]$/, "", t)
219			if (t) title = t " &mdash; " title
220		}
221		n = split(ENVIRON["HEADER"], header, /\n/)
222		for (i = 1; i <= n; i++) {
223			if (match(tolower(header[i]), "<title></title>")) {
224				head = substr(header[i], 1, RSTART - 1)
225				tail = substr(header[i], RSTART + RLENGTH)
226				print head "<title>" title "</title>" tail
227			} else print header[i]
228		}
229		print body
230		print ENVIRON["FOOTER"]
231	}'
232}
233
234render_gmi_file() {
235	awk '
236	{ body = body "\n" $0 }
237	END {
238        body = substr(body, 2)
239        n = split(body, body_n, /\n/)
240        for (i = 1; i <= n; i++) {
241            if (!match(body_n[i], /^=>[[:space:]]*[Hh][Tt]{2}[Pp][Ss]?:\/\/.*/)) {
242                sub(/\.html[[:space:]]*/, ".gmi ", body_n[i])
243            }
244            print body_n[i]
245        }
246		#print body
247		print ENVIRON["GEMFOOTER"]
248	}'
249}
250
251list_pages() {
252	e="\\( -name '*.html' -o -name '*.md' \\)"
253	cd "$1" && eval "find . -type f ! -path '*/.*' ! -path '*/_*' $IGNORE $e" |
254		sed 's#^./##;s#.md$#.html#;s#/index.html$#/#'
255}
256
257render_sitemap() {
258	urls="$1"
259	base_url="$2"
260	date="$3"
261
262	echo '<?xml version="1.0" encoding="UTF-8"?>'
263	echo '<urlset'
264	echo 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
265	echo 'xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9'
266	echo 'http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"'
267	echo 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'
268	echo "$urls" |
269		sed -E 's#^(.*)$#<url><loc>'"$base_url"'/\1</loc><lastmod>'"$date"'</lastmod><priority>1.0</priority></url>#'
270	echo '</urlset>'
271}
272
273main "$@"