noxz-sites

A collection of a builder and various scripts creating the noxz.tech sites
git clone https://noxz.tech/git/noxz-sites.git
Log | Files | README | LICENSE

build
1#!/bin/sh
2
3# ---------------------------- global variables ----------------------------- #
4SITE_DOMAIN="noxz.tech"
5SITE_TITLE="noxz.tech | "
6SITE_MAIN_TITLE="noxz.tech"
7SITE_AUTHOR="Chris Noxz"
8SITE_ICON_PNG="//[site]/pub/logo.png"
9SITE_ICON_SVG="//[site]/pub/logo.svg"
10SITE_CSS="//[site]/pub/style.css"
11SITE_GIT_REPO_ROOT="${HOME}/mnt/src/z0noxz"
12SITE_GIT_COMMIT_PH="repo-logs"
13SITE_GIT_COMMIT_COUNT="100"
14SITE_WWW=
15SITE_HTML=
16SITE_REPLACE_DOT=1
17# ------------------------------ config files ------------------------------- #
18CFG_DIR="./config"
19# ------------------------ groff configuration files ------------------------ #
20CFG_GROFF_WWW_MACRO="${CFG_DIR}/www.conf"
21CFG_GROFF_EQN="${CFG_DIR}/eqn.conf"
22CFG_GROFF_PIC="${CFG_DIR}/pic.conf"
23# ---------------------- main html configuration files ---------------------- #
24CFG_MAIN_HTML="${CFG_DIR}/main.html"
25# ---------------------- git html configuration files ----------------------- #
26CFG_GIT_REPOS_HTML="${CFG_DIR}/git_repos.html"
27CFG_GIT_HEAD_HTML="${CFG_DIR}/git_head.html"
28# --------------------- sitemap.xml configuration files --------------------- #
29CFG_SITEMAP_XML="${CFG_DIR}/sitemap.xml"
30CFG_SITEMAP_XML_ITEM="${CFG_DIR}/sitemap_item.xml"
31# ------------------------ pinned tag configuration  ------------------------ #
32CFG_PINNED_TAG="pinned"
33
34
35# helper function to fix dot files if asked to do so
36# shellcheck disable=SC2120
37fix_dot_file() {
38	if [ ${SITE_REPLACE_DOT} -ne 1 ]; then
39		if [ ! -t 0 ]; then
40			cat -
41		elif [ -n "${1}" ]; then
42			echo "${1}"
43		fi
44	else
45		if [ ! -t 0 ]; then
46			cat -
47		elif [ -n "${1}" ]; then
48			echo "${1}"
49		fi                                                                  \
50		| sed                                                               \
51			-e "s,/\.,/_\.,g"                                               \
52			-e "s,^\.,_\.,g"
53	fi
54}
55
56# helper function to html encode stdin
57# shellcheck disable=SC2120
58html_encode() {
59	if [ ! -t 0 ]; then
60		cat -
61	elif [ -n "$1" ]; then
62		echo "$1"
63	fi                                                                      \
64	| sed                                                                   \
65		-e 's/&/\&/g'                                                   \
66		-e 's/"/\"/g'                                                  \
67		-e 's/</\&lt;/g'                                                    \
68		-e 's/>/\&gt;/g'
69}
70
71# helper function to produce awk safe strings
72# shellcheck disable=SC2120
73awk_safe() {
74	if [ ! -t 0 ]; then
75		cat -
76	elif [ -n "$1" ]; then
77		echo "$1"
78	fi                                                                      \
79	|  sed                                                                  \
80		-e 's/\\/\\\\/g'                                                    \
81		-e 's/\&/\\\\\&/g'
82}
83
84print_loading() {
85	>&2 printf '[%s*%s] %-.100s '                                           \
86		"$(tput setaf 4)"                                                   \
87		"$(tput sgr 0)"                                                     \
88		"${1} $(for _ in $(seq 1 100); do printf '.'; done)"
89}
90
91print_done() {
92	>&2 printf '[%sdone%s]\n'                                               \
93		"$(tput setaf 2)"                                                   \
94		"$(tput sgr 0)"
95}
96
97# arg 1: site root eg. ../noxz-sites/noxz.tech
98# arg 2: current site eg. ../noxz-sites/noxz.tech/guides/groff
99render_main_menu() {
100	# variables used in main menu
101	menu_root="${1}"
102	menu_current="${2}"
103	menu_active=""
104	menu_output=""
105	menu_buffer=""
106	menu_home="$(printf '<li><a href="//%s/">home</a></li>' "${SITE_DOMAIN}")"
107
108	# check if both directories exist
109	[ ! -d "${menu_root}" ] && return
110	[ ! -d "${menu_current}" ] && return
111
112	# remove trailing slashes if existing
113	menu_root=$(dirname "${menu_root}/*")
114	menu_current=$(dirname "${menu_current}/*")
115
116	# make sure current directory is a subdirectory of root
117	echo "${menu_current}" | grep -qv "^${menu_root}" && return
118
119	# root menu
120	if [ "${menu_root}" = "${menu_current}" ]; then
121		menu_home="$(echo "${menu_home}" | sed 's/<li/<li class="active"/')"
122		for f in "${menu_current}"/*; do
123			[ ! -d "${f}" ] && continue                                     # only process directories
124			[ -f "${f}/.buildignore" ] && continue                          # skip buildignore
125
126			ff="$(echo "${f}" | sed "s?^${menu_root}?//${SITE_DOMAIN}?")"
127			fn="$(echo "${f##*/}" | sed -e 's/__/: /g' -e 's/_/ /g')"
128			menu_output=$(printf '%s<li><a href="%s">%s</a></li>'           \
129				"${menu_output}"                                            \
130				"${ff}"                                                     \
131				"${fn}")
132		done
133	fi
134
135	# run until root directory is the current one
136	while [ "${menu_root}" != "${menu_current}" ]; do
137
138		# handle children of first current
139		if [ "${menu_current}" = "${2}" ]; then
140			for f in "${menu_current}"/*; do
141				[ ! -d "${f}" ] && continue                                 # only process directories
142				[ -f "${f}/.buildignore" ] && continue                      # skip buildignore
143				menu_output="$(printf '%s<li' "${menu_output}")"
144				ff="$(echo "${f}" | sed "s?^${menu_root}?//${SITE_DOMAIN}?")"
145				fn="$(echo "${f##*/}" | sed -e 's/__/: /g' -e 's/_/ /g')"
146				menu_output=$(printf '%s><a href="%s">%s</a></li>'          \
147					"${menu_output}"                                        \
148					"${ff}"                                                 \
149					"${fn}")
150			done
151		fi
152
153		menu_active="${menu_current}"                                       # mark current as active
154		menu_current="${menu_current%/*}"                                   # mark parent as current
155
156		# handle all siblings
157		for f in "${menu_current}"/*; do
158			[ ! -d "${f}" ] && continue                                     # only process directories
159			[ -f "${f}/.buildignore" ] && continue                          # skip buildignore
160
161			menu_buffer=$(printf '%s<li' "${menu_buffer}")
162			ff="$(echo "${f}" | sed "s?^${menu_root}?//${SITE_DOMAIN}?")"
163			fn="$(echo "${f##*/}" | sed -e 's/__/: /g' -e 's/_/ /g')"
164			if [ "${f}" = "${menu_active}" ]; then                          # treat active different
165				menu_buffer=$(printf '%s class="active"><a href="%s">%s</a>'\
166					"${menu_buffer}"                                        \
167					"${ff}"                                                 \
168					"${fn}")
169				# dump output to buffer if existing (sub menu)
170				[ "${menu_output}" != "" ]                                  \
171				&& menu_buffer=$(printf '%s<ul>%s</ul>'                     \
172					"${menu_buffer}"                                        \
173					"${menu_output}")
174				menu_buffer=$(printf '%s</li>' "${menu_buffer}")
175			else
176				menu_buffer=$(printf '%s><a href="%s">%s</a></li>'          \
177					"${menu_buffer}"                                        \
178					"${ff}"                                                 \
179					"${fn}")
180			fi
181		done
182		menu_output="${menu_buffer}"
183		menu_buffer=""
184	done
185	printf '<ul id="main-nav">%s%s</ul>'                                    \
186		"${menu_home}"                                                      \
187		"${menu_output}"                                                    \
188	| sed                                                                   \
189		-e 's,\(<\(ul\|li\)[^>]*>\|</ul>\),\n\1,2g'                         \
190		-e 's,\(</ul>\)\(</li>\),\1\n\2,g'                                  \
191	| awk '
192		BEGIN {
193			level=0
194		}
195
196		/<\/ul>/ {
197			level--
198		}
199
200		/^<\/li>$/ {
201			level--
202		}
203
204		{
205			for (i=0; i<level; i++)
206				printf "\t"
207			print
208		}
209
210		/<ul[^>]*>/ {
211			level++
212		}
213
214		/<li[^>]*>.*<\/a>$/ {
215			level++
216		}
217	'
218}
219
220render_main_menu_extra() {
221	printf '<li><a class="%s" href="%s">%s</a></li>\n'                      \
222		"pin"    "//[site]/articles/tags/pinned" "pinned articles"          \
223		"howto"  "//[site]/articles/tags/howto"  "how to"                   \
224		"twtxt"  "//[site]/twtxt.txt" "twtxt"                               \
225		"source" "//[site]/git/" "source"                                   \
226	| awk                                                                   \
227	'
228		BEGIN {
229			printf "<ul id=\"extra-nav\">\n"
230		}
231
232		{
233			printf "\t%s\n", $0
234		}
235
236		END {
237			printf "</ul>\n"
238		}
239	'
240}
241
242render_articles() {
243	# copy tag list from tags page
244	[ -z "${2}" ] && sed -e 's,\.ARTTAG "\([^"]*\)",.ARTTAG "tags/\1",g'    \
245		"${1}"/articles/tags/index.www
246	echo .DIVS articles
247	[ -n "${2}" ] && printf '.ULS\n'
248	if [ "${2}" != "${CFG_PINNED_TAG}" ]; then
249		find "${1}"/articles -name .metadata | while read -r article
250		do
251			grep "^.ds YEAR" "${article}" | sed 's/^.ds YEAR[^0-9]*//g'
252		done | sort -ru
253	else
254		# merge every year for pinned articles, to sort them alphabetically
255		echo
256	fi | while read -r year
257	do
258		[ -z "${2}" ] && printf '.HnS 1\n%s\n.HnE\n\.ULS\n' "${year}"
259		if [ -z "${2}" ]; then
260			grep -l "^.ds YEAR.*${year}" "${1}"/articles/*/.metadata
261		else # filter by tag
262			grep -l "^${2}$" "${1}"/articles/*/.tags                        \
263				| sed 's/\.tags$/.metadata/g'                               \
264				| xargs -I{} grep -l "^.ds YEAR.*${year}" "{}"
265		fi | xargs -I{} awk -v name="{}" -v tag="${2}" -v cfg_pinned_tag="${CFG_PINNED_TAG}" '
266			BEGIN {
267				title = ""
268				year = ""
269				month = ""
270				day = ""
271				gsub(/\/.metadata/,"",name)
272				gsub(/^([^\/]|\/)*\//,"",name)
273				printf ".LI\\n "
274			}
275			/^\.ds TITLE.*$/ {
276				for(i=3;i<=NF;i++) {
277					title = title$i
278					if (i != NF)
279						title = title" "
280				}
281			}
282			/^\.ds YEAR.*$/ {
283				year = $3
284			}
285			/^\.ds MONTH.*$/ {
286				month = substr($3,1,3)
287			}
288			/^\.ds DAY.*$/ {
289				day = $3
290			}
291			END {
292				if (tag == cfg_pinned_tag)
293					printf ".URL \"../../%s\" \"%s\"\n", name, title
294				else if (tag != "")
295					printf "%s %s, %s \\(em\\n .URL \"../../%s\" \"%s\"\n", day, month, year, name, title
296				else
297					printf "%s %s \\(em\\n .URL \"%s\" \"%s\"\n", day, month, name, title
298			}
299		' "{}" | if [ "${2}" = "${CFG_PINNED_TAG}" ]; then
300			sort -k 4
301		else
302			sort -k 3Mr -k 2nr
303		fi | sed -e 's/\\n /\n/g'
304		[ -z "${2}" ] && printf '.ULE\n'
305	done
306	[ -n "${2}" ] && printf '.ULE\n'
307	echo .DIVE
308}
309
310
311
312# fix for grohtml not reliably working for picture extraction
313extract_pictures() {
314	base_path="${SITE_WWW%/*}"
315	pic=""
316	tex=""
317	tex_type=""
318	in_pic=0
319	in_tex=0
320	i=0
321
322	tex_template() {
323		case "$1" in
324		tikz)
325		cat <<-LATEX
326		\\documentclass[tikz,border=10pt]{standalone}
327		\\usepackage{xcolor}
328		\\usepackage{tikz}
329		\\usepackage{ifthen}
330		\\begin{document}
331		\\begin{tikzpicture}
332		${tex}
333		\\end{tikzpicture}
334		\\end{document}
335		LATEX
336		;;
337		equation|*)
338		cat <<-LATEX
339		\\documentclass{article}
340		\\usepackage{amsmath}
341		\\usepackage{amssymb}
342		\\usepackage{xcolor}
343		\\newcommand{\\hi}[1]{\\textcolor{red}{#1}}
344		\\pagestyle{empty}
345		\\begin{document}
346		${tex}
347		\\end{document}
348		LATEX
349		;;
350		esac
351	}
352
353	cat - | while read -r line; do
354		# extract LaTeX
355		if [ $in_tex -eq 0 ] && echo "${line}" | grep -q '^\.TEXS'; then
356			in_tex=1
357			tex_type=$(echo "${line}" | awk '{print $2}')
358			tex_type=${tex_type:-equation}
359			tex=""
360			continue
361		fi
362		# render and save TEX instance as a svg image
363		if [ $in_tex -eq 1 ] && [ "$(echo ${line} | grep '^\.TEXE')" = ".TEXE" ]; then
364			in_tex=0
365
366			# generate svg from tex
367			tex=$(printf '%s' "${tex}" | sed -e '1s/^\n*//' -e '$s/\n*$//')
368			tex_template "${tex_type}" | tex2svg - - | awk "
369			/<svg/ {
370				match(\$0, /viewBox='([0-9.e+-]+) ([0-9.e+-]+) ([0-9.e+-]+) ([0-9.e+-]+)'/, vb)
371				match(\$0, /width='([0-9.e+-]+)pt'/, w)
372				match(\$0, /height='([0-9.e+-]+)pt'/, h)
373
374				if (vb[0] && w[0] && h[0]) {
375					padding = 1
376					minx    = vb[1] - padding
377					miny    = vb[2] - padding
378					vbw     = vb[3] + padding * 2
379					vbh     = vb[4] + padding * 2
380					width   = w[1]  + padding * 2
381					height  = h[1]  + padding * 2
382
383					sub(/width='[0-9.e+-]+pt'/,  \"width='\" width \"pt'\")
384					sub(/height='[0-9.e+-]+pt'/, \"height='\" height \"pt'\")
385					sub(/viewBox='[^']+'/,       \"viewBox='\" minx \" \" miny \" \" vbw \" \" vbh \"'\")
386				}
387			}
388			{ print }" > "${base_path}/tex-${i}.svg"
389
390			printf '.SVG tex-%s.svg "tex tex-%s"\n' "${i}" "${tex_type}"
391
392			i=$((i+1))
393		fi
394		# catch and save every TEX instance
395		if [ $in_tex -eq 1 ]; then
396			if [ -z "${tex}" ]; then
397				tex="${line}"
398			else
399				tex="$(printf '%s\n%s' "${tex}" "${line}")"
400			fi
401		else
402			printf '%s\n' "${line}"
403		fi
404	done
405}
406
407prerender() {
408	base_path="${SITE_WWW%/*}"
409	title=""
410	author=""
411	date=""
412
413	[ -f "${base_path}"/.metadata ] && while read -r line; do
414		case "$line" in
415		\.ds\ TITLE*)   title="$(echo ${line#*TITLE})";;
416		\.ds\ DATE*)    date="$(echo ${line#*DATE})";;
417		\.ds\ AUTHOR*)  author="$(echo ${line#*AUTHOR})";;
418		esac
419		printf '%s\n' "${line}"
420	done < "${base_path}"/.metadata
421
422	[ -n "$title" ] && printf '\n.ll 10i\n\n.HnS 0\n\\*[TITLE]\n.HnE\n\n'
423	[ -n "$author" ] && printf '.P article-author\n\\*[AUTHOR]\n\n'
424	[ -n "$date" ] && printf '.P article-date\n\\*[DATE]\n\n'
425
426	# print tags, if existing, at bottom of page
427	[ -f "${base_path}"/.tags ]                                             \
428		&& printf '\n.DIVS wrap-list\n.ULS\n'                               \
429		&& while read -r tag; do
430		if [ "${tag}" != "${CFG_PINNED_TAG}" ]; then
431			tag_safe="$(echo "${tag}" | tr \  _)"
432			printf '.LI\n.CLURL "../tags/%s" art-tag "%s"\n' "${tag_safe}" "${tag}"
433		fi
434	done < "${base_path}"/.tags && printf '.ULE\n.DIVE\n\n'
435
436	[ -f "${base_path}"/.edit ] \
437		&& sed "${base_path}"/.edit                                         \
438		-e 's/^START \([0-9-]*\)$/.LI \1\n.ULS/g'                           \
439		-e 's/^\t\(.*\)$/.LI\n\1/g'                                         \
440		-e 's/^END$/.ULE/g'                                                 \
441		-e '1 i\.DIVS edit-note\n.HnS 2\nEdit notice\n.HnE\n.DLS'           \
442		-e '$ a .DLE\n.DIVE\n'
443
444	cat - | while read -r line; do
445		if echo "${line}" | grep -E '\{:articles(#[^#]*)?:\}' 2>/dev/null 1>&2; then
446			render_articles "$1"                                            \
447				"$(echo "${line}" | sed 's/\([^#]*#\?\)\(.*\)\(:.*$\)/\2/')"
448		else
449			printf '%s\n' "${line}"
450		fi
451	done
452}
453
454render_main_content() {
455	awk '
456		# Grohtml (post-html.cpp) has a hard coded max length:
457		# https://git.savannah.gnu.org/cgit/groff.git/tree/src/devices/grohtml/post-html.cpp
458		# 51: #define MAX_LINE_LENGTH                   60
459		# This creates a problem when printing words longer than 60 characters -
460		# creating an extra blank line before the word. Further more grohtml
461		# actually do not print code blocks using <code> or <pre> so blank lines
462		# that are supposed to be printed are instead ignored. These following
463		# awk scripts together with some extensions of the www-macros (see.
464		# www.conf) are supposed to fix these issues.
465
466		BEGIN { # initialize variables
467			_inblock=0
468		}
469
470		/\.COS/ {
471			_inblock=1
472		}
473
474		/\.COE/ {
475			_inblock=0
476		}
477
478		{ # normal: no fixes applied
479			if (!_inblock) {
480				print
481				next
482			}
483		}
484
485		## INSIDE CODE BLOCK ##
486
487		# replace blank lines with extended BR macro
488		/^$/ {
489			print ".COMMENT-BR"
490			next
491		}
492
493		/\047/ { # replace apostrophe
494			gsub(/\047/, "\\\[aq\]", $0);
495		}
496
497		/\~/ { # replace tilde
498			gsub(/\~/, "\\\[ti\]", $0);
499		}
500
501		/\^/ { # replace circumflex
502			gsub(/\^/, "\\\[ha\]", $0);
503		}
504
505		/\t/ { # replace tabs with 4 spaces
506			gsub(/\t/, "    ", $0);
507		}
508
509		{ # very ugly fix to preserve spaces (see if this can be imporoved upon)
510			# NOTE: angle brackets will be encoded by groff!
511			gsub(/\s{2}/, "<!--space--><!--space-->", $0)
512			gsub(/<!--space-->\s/, "<!--space--><!--space-->", $0)
513			print
514		}
515	' "${SITE_WWW}"                                                         \
516	| extract_pictures  "$1"                                                \
517	| prerender "$1"                                                        \
518	| cat "${CFG_GROFF_WWW_MACRO}" -                                        \
519	| groff                                                                 \
520		-Thtml -e -mwww -k                                                  \
521		-I"${SITE_WWW%/*}/grohtml-"                                         \
522	| awk '
523		# --------------------------------------------
524		# somewhat messy fix for code block formatting
525		# --------------------------------------------
526		BEGIN { # initialize variables
527			_inblock=0
528			_break=0
529		}
530
531		/<pre><code>/ { # signal start of code block
532			_inblock=1
533		}
534
535		/<\/code><\/pre>/ { # signal end of code block
536			_inblock=0
537		}
538
539		{ # normal: no fixes applied
540			if (!_inblock) {
541				print
542				next
543			}
544		}
545
546		## INSIDE CODE BLOCK ##
547
548		/^$/ { # fix: skip blank lines in code blocks
549			next
550		}
551
552		{ # handle general code block instances
553			# replace placeholders
554			gsub(/&lt;!--space--&gt;/, " ", $0);    # groff has now encoded angle brackets
555			gsub(/<!--break-->/, "", $0);
556
557			if (!_break)
558				printf " "
559			_break=0
560		}
561
562		/<br[^>]*>/ { # signal line break
563			_break=1
564		}
565
566		{
567			gsub(/<br[^>]*>/, "\n", $0);
568			printf "%s", $0
569		}
570	'                                                                       \
571	| sed                                                                   \
572		-e 's/<p style[^>]*>/<p>/g'                                         \
573		-e 's/<dd>/<dd><p>/g'                                               \
574		-e 's/<p><!--/<!--/g' -e 's/--><\/p>/-->/g'                         \
575		-e 's/\[at\]/<span>\&#64;<\/span>/g'                                \
576		-e 's/\(src\|alt\)="[^"]*\(\(eqn\|pic\)-[0-9]*.png\)/\1="\2/g'      \
577		-e 's/\(<img src="\(eqn\|pic\)-[0-9]*.\(png\|svg\)" alt="[^"]*"\)[^>]*>/<p class="\2">\1\/><\/p>/g' \
578		-e 's/<p align="\(left\|center\|right\)"[^>]*>/<p class="\1">/g'    \
579		-e 's/<pre><code>/<div class="code-wrap"><pre><code>/g'             \
580		-e 's/<\/code><\/pre>/<\/code><\/pre><\/div>/g'                     \
581	| tidy -asxhtml                                                         \
582	| sed -e '/^\s*<dd><\/dd>$/d'                                           \
583	| awk '
584		/<body>/{
585			flag=1
586			next
587		}
588
589		/<\/body>/{
590			flag=0
591		}
592
593		flag
594	'                                                                       \
595	| awk '
596		BEGIN { # initialize variables
597			_inblock=0
598		}
599
600		{ # normal: no fixes applied
601			if (NR > 1 && !_inblock) {
602				printf "<!--padding-->"
603			}
604			print
605		}
606		/<pre><code>/ { # signal start of code block
607			_inblock=1
608		}
609
610		/<\/code><\/pre>/ { # signal end of code block
611			_inblock=0
612		}
613	'
614}
615
616render_main() {
617	main_current="${2}"
618
619	[ ! -d "${main_current}" ] && return
620	main_current=$(dirname "${main_current}/*")
621
622	[ -f "${main_current}/index.www" ] && SITE_WWW="${main_current}/index.www" || return
623	[ -f "${main_current}/.metadata" ] && SITE_TITLE="${SITE_TITLE}$(grep '\.ds TITLE' "${main_current}/.metadata" | sed 's/\.ds\ TITLE\s*/.ll 10i\n/g' | groff -Tutf8 | sed -n '1,1p')"
624	SITE_HTML="${SITE_WWW%.*}.html"
625
626	print_loading "Rendering ${main_current}"
627
628	awk                                                                     \
629		-v var_author="$(awk_safe "${SITE_AUTHOR}")"                        \
630		-v var_title="$(awk_safe "${SITE_TITLE}")"                          \
631		-v var_favicon="$(awk_safe "${SITE_ICON_PNG}")"                     \
632		-v var_stylesheet="$(awk_safe "${SITE_CSS}")"                       \
633		-v var_logo="$(awk_safe "${SITE_ICON_SVG}")"                        \
634		-v var_main_title="$(awk_safe "${SITE_MAIN_TITLE}")"                \
635		-v var_menu="$(render_main_menu "$1" "$2" | awk_safe)"              \
636		-v var_menu_extra="$(render_main_menu_extra | awk_safe)"            \
637		-v var_content="$(render_main_content "$1" 2>/dev/null | awk_safe)" \
638		-v var_year="$(date +%Y)"                                           \
639	'
640		/\{:menu:\}/{
641			padding=$0
642			gsub(/{:.*:}$/, "", padding)
643			gsub(/\n/, "\n" padding, var_menu)
644		}
645
646		/\{:menu_extra:\}/{
647			padding=$0
648			gsub(/{:.*:}$/, "", padding)
649			gsub(/\n/, "\n" padding, var_menu_extra)
650		}
651
652		/\{:content:\}/{
653			padding=$0
654			gsub(/{:.*:}$/, "", padding)
655			gsub(/<!--padding-->/, padding, var_content)
656		}
657
658		{
659			gsub(/\{:author:\}/, var_author)
660			gsub(/\{:title:\}/, var_title)
661			gsub(/\{:favicon:\}/, var_favicon)
662			gsub(/\{:stylesheet:\}/, var_stylesheet)
663			gsub(/\{:logo:\}/, var_logo)
664			gsub(/\{:main_title:\}/, var_main_title)
665			gsub(/\{:sub_title:\}/, var_sub_title)
666			gsub(/\{:menu:\}/, var_menu)
667			gsub(/\{:menu_extra:\}/, var_menu_extra)
668			gsub(/\{:content:\}/, var_content)
669			gsub(/\{:year:\}/, var_year)
670			print
671		}
672	' "${CFG_MAIN_HTML}" > "${SITE_HTML}"
673
674	print_done
675}
676
677render_git_commit() {
678	git_commit_files="$(git -C "${1}" whatchanged "${2}"^! | grep '^:.*$' | cut -d' ' -f5 | sed 's/\t/:/g')"
679	git -C "${1}" show --stat=1000 --stat-graph-width=20 "${2}"             \
680		--format="format:%H%n%P%n%aN%n%aE%n%aD%n%B%H"                       \
681		| sed 's/{\(.*\) => \(.*\)}/\1/g'                                   \
682		| html_encode | awk -v flist="${git_commit_files}" -v base="//${1}" '
683	BEGIN {
684		marker=""; files=0; split(flist, farr, "\n");
685		for (i in farr) {
686			split(farr[i], ftmp, ":");
687			farr[i,1] = substr(ftmp[1], 1, 1);
688			farr[i,2] = ftmp[2];
689			farr[i,3] = ftmp[3];
690		}
691	}
692	{
693		switch (NR) {
694		case 1:
695			marker=$0
696			printf "<pre>\n"
697			printf "<b>commit</b>: <a href=\"%s/commit/%s.html\">%s</a>\n",base,$0,$0
698		break
699		case 2:
700			printf "<b>parent</b>: <a href=\"%s/commit/%s.html\">%s</a>\n",base,$0,$0
701		break
702		case 3:
703			printf "<b>author</b>: %s",$0
704		break
705		case 4:
706			if ($0 != "") { printf " &lt;<a href=\"mailto:%s\">%s</a>&gt;",$0,$0 }
707			printf "\n"
708		break
709		case 5:
710			printf "<b>date</b>:   %s\n",$0
711			printf "</pre>\n"
712		break
713		case 6:
714			printf "<pre class=\"body\">\n"
715		default:
716			if (files) {
717				for (i in farr) {
718					if ($1 == farr[i,2]) {
719						printf "<tr>"
720						printf "<td>%s</td><td><a href=\"#f%d\">%s</a>",farr[i,1],i,farr[i,2]
721						if (farr[i,3] != "") {
722							printf " &rarr; <a href=\"#f%d\">%s</a>",i,farr[i,3]
723						}
724						printf "</td>",$3
725						printf "<td>%s</td>",$3
726						printf "<td><span class=\"i\">"
727						p=1
728						for (j = 1; j <= length($4); j++) {
729							if (p && substr($4, j, 1) != "+") {
730								p=0; printf "</span><span class=\"d\">"
731							}
732							printf "%s",substr($4,j,1)
733						}
734						printf "</span></td>"
735						print "</tr>"
736						next
737					}
738				}
739				sub(/^[ \t\r\n]+/, "", $0)
740				printf "</table>\n%s\n",$0
741			} else {
742				if (marker != "" && marker == $0) { printf "</pre>\n<table>\n"; files=1 }
743				else { print }
744			}
745		break
746		}
747	}'
748	echo "<hr />"
749	git -C "${1}" show "${2}" --format="" | html_encode | awk -v flist="${git_commit_files}" '
750	BEGIN {
751		indiff=0; files=0; h=0; l=0 split(flist, farr, "\n");
752		for (i in farr) {
753			split(farr[i], ftmp, ":");
754			farr[i,1] = ftmp[1];
755			farr[i,2] = ftmp[2];
756		}
757		print "<pre>"
758	}
759	/^diff --git/ {
760		indiff=1
761		if (match($0, /^diff --git a\/(.*) b\/(.*)$/, m)) {
762			for (i in farr) {
763				if (m[1] == farr[i,2]) {
764					printf "<b>diff --git"
765					printf " a/<a id=\"f%d\" href=\"#f%d\">%s</a>",i,i,m[1]
766					printf " b/<a href=\"#f%d\">%s</a>",i,m[2]
767					printf "</b>\n"
768					if (m[1] != m[2]) {
769						printf "<b class=\"r\">rename %s &rarr; ",m[1]
770						printf "%s</b>\n",m[2]
771					}
772					next
773				}
774			}
775		}
776		next
777	}
778	/^@@/ {
779		indiff=0
780		printf "<a href=\"#h%d\" id=\"h%d\" class=\"h\">%s</a>\n",h,h++,$0
781		next
782	}
783	/^\+.*$/ {
784		if (indiff) { next }
785		printf "<a href=\"#l%d\" id=\"l%d\" class=\"i\">%s</a>\n",l,l++,$0
786		next
787	}
788	/^\-.*$/ {
789		if (indiff) { next }
790		printf "<a href=\"#l%d\" id=\"l%d\" class=\"d\">%s</a>\n",l,l++,$0
791		next
792	}
793	{
794		if (indiff) { next }
795		print
796	}
797	END {
798		print "</pre>"
799	}'
800}
801
802render_git_file() {
803	printf '\t<span class="git-file">%s</span>\n\t<hr />\n' "${2}"
804	if git -C "${1}" diff 4b825dc642cb6eb9a060e54bf8d69288fbee4904          \
805		--numstat HEAD -- "${2}" | grep '^-' >/dev/null 2>&1; then
806		printf '\t%s\n' "binary file."
807	else
808		git -C "${1}" cat-file "HEAD:${2}" -p                               \
809		| html_encode                                                       \
810		| awk '
811			BEGIN {
812				print "<pre>"
813			}
814
815			{
816				print "<a class=\"line\" href=\"#l" NR "\" id=\"l" NR "\">" NR "</a>" $0
817			}
818
819			END {
820				print "</pre>"
821			}
822		'
823	fi
824}
825
826render_git_repo() {
827	repo_path="${SITE_GIT_REPO_ROOT}/$2"
828	repo_path_head="${1}/${2}/head.html.tmp"
829	repo_path_log="${1}/${2}/log.html.tmp"
830	repo_path_files="${1}/${2}/files.html.tmp"
831	repo_path_file="${1}/${2}/file"
832	repo_path_commit="${1}/${2}/commit"
833	repo_path_tags="${1}/${2}/tags.html.tmp"
834	repo_path_archive="${1}/${2}/archive"
835	repo_name="${2%.*}"
836	repo_description=""
837	repo_menu="$(
838		printf '<a href="//%s/log.html">Log</a>' "${1}/${2}"
839		printf ' | <a href="//%s/files.html">Files</a>' "${1}/${2}"
840		git -C "${1}/${2}" describe --tags >/dev/null 2>&1 &&               \
841		printf ' | <a href="//%s/tags.html">Tags</a>' "${1}/${2}"
842		git -C "${1}/${2}" show HEAD:README >/dev/null 2>&1 &&              \
843		printf ' | <a href="//%s/file/README.html">README</a>' "${1}/${2}"
844		git -C "${1}/${2}" show HEAD:LICENSE >/dev/null 2>&1 &&             \
845		printf ' | <a href="//%s/file/LICENSE.html">LICENSE</a>' "${1}/${2}"
846	)"
847
848	rsync -a --delete-before "${repo_path}/" "${1}/${2##*/}"
849	git -C "${1}/${2##*/}" --bare update-server-info
850	ln -s "refs" "${1}/${2##*/}/info/refs?service=git-upload-pack"
851
852	repo_description="$(< "${1}/${2}"/description html_encode)"
853
854	# create archive files (for each tag)
855	mkdir "${repo_path_archive}"
856	git -C "${1}/${2}" tag                                                  \
857		| xargs -I{} git -C "${1}/${2}" archive                             \
858			--format=tar.gz --prefix="${repo_name}-{}/"                     \
859			-o "archive/${repo_name}-{}.tar.gz" "{}"
860
861	# create preview files
862	mkdir "${repo_path_file}"
863	git -C "${1}/${2}" ls-tree                                              \
864		-r --format="%(objectmode)%x0a%(path)" HEAD                         \
865		| grep '^100[0-9][0-9][0-9]$' -A1                                   \
866		| grep -v '^--$\|^100[0-9][0-9][0-9]$'                              \
867		| while read -r file; do
868			file_path="$(echo "${repo_path_file}/${file}" | fix_dot_file)"
869			mkdir -p "${file_path%/*}"
870			render_git_file "${1}/${2}" "${file}" > "${file_path}.html.tmp"
871		done
872
873	# create commit files
874	mkdir "${repo_path_commit}"
875	git -C "${1}/${2}" log --pretty="format:%H"                             \
876		| grep .                                                            \
877		| while read -r file; do
878			file_path="$(echo "${repo_path_commit}/${file}")"
879			mkdir -p "${file_path%/*}"
880			render_git_commit "${1}/${2}" "${file}" > "${file_path}.html.tmp"
881		done
882
883
884	# render common html head
885	awk                                                                     \
886		-v var_site="$(awk_safe "${1}")"                                    \
887		-v var_repo="$(awk_safe "${2}")"                                    \
888		-v var_logo="$(awk_safe "${SITE_ICON_SVG}")"                        \
889		-v var_name="$(awk_safe "${repo_name}")"                            \
890		-v var_description="$(awk_safe "${repo_description}")"              \
891		-v var_menu="$(awk_safe "${repo_menu}")"                            \
892	'
893		{
894			gsub(/\{:site:\}/,          var_site)
895			gsub(/\{:repo:\}/,          var_repo)
896			gsub(/\{:logo:\}/,          var_logo)
897			gsub(/\{:name:\}/,          var_name)
898			gsub(/\{:description:\}/,   var_description)
899			gsub(/\{:menu:\}/,          var_menu)
900			print
901		}
902	' "${CFG_GIT_HEAD_HTML}" > "${repo_path_head}"
903
904	# render log
905	git -C "${1}/${2}" log                                                  \
906		--pretty="format:%ai%n%s%n%an%n%H"                                  \
907	| html_encode                                                           \
908	| awk '
909		BEGIN {
910			printf "\t<table id=\"log\">\n"
911			printf "\t\t<tr class=\"nohi\">"
912			printf "<td>Date</td>"
913			printf "<td>Commit message</td>"
914			printf "<td>Author</td>"
915			printf "<td>Commit</td>"
916			printf "</tr>\n"
917			printf "\t\t<tbody>\n"
918		}
919
920		{
921			entry[(NR-1) % 4] = $0
922			if (NR % 4 == 0) {
923				printf "\t\t\t<tr>"
924				for (i = 0; i < 4; i++) {
925					if (i == 1) {
926						printf "<td><a href=\"commit/%s.html\">%s</a></td>",entry[3],entry[i]
927					} else if (i == 3) {
928						printf "<td><a href=\"commit/%s.html\">%*.*s</a></td>",entry[i],8,8,entry[i]
929					} else {
930						printf "<td>%s</td>",entry[i]
931					}
932				}
933				printf "</tr>\n"
934			}
935		}
936
937		END {
938			printf "\t\t</tbody>\n"
939			printf "\t</table>\n"
940		}
941	' > "${repo_path_log}"
942
943	# render files
944	git -C "${1}/${2}" ls-tree                                              \
945		-r --format="%(objectmode)%x0a%(path)%x0a%(objectsize)" HEAD        \
946	| html_encode                                                           \
947	| fix_dot_file                                                          \
948	| awk '
949		function dotrep(data)
950		{
951			output = data
952			gsub(/\/_\./, "/.", output)
953			gsub(/^_\./, ".", output)
954			return output
955		}
956
957		function oct2sym(data)
958		{
959			type = substr(data, 0, 3)
960			if (type == "100") {
961				type = "-"
962			} else if (type == "060") {
963				type = "b"
964			} else if (type == "020") {
965				type = "c"
966			} else if (type == "040") {
967				type = "d"
968			} else if (type == "010") {
969				type = "p"
970			} else if (type == "120") {
971				type = "l"
972			} else if (type == "140") {
973				type = "s"
974			} else {
975				type = "?"
976			}
977
978			sym = substr(data, 4, 3)
979			gsub("0", "---", sym)
980			gsub("1", "--x", sym)
981			gsub("2", "-w-", sym)
982			gsub("3", "-wx", sym)
983			gsub("4", "r--", sym)
984			gsub("5", "r-x", sym)
985			gsub("6", "rw-", sym)
986			gsub("7", "rwx", sym)
987			return type sym
988		}
989
990		BEGIN {
991			printf "\t<table id=\"files\">\n"
992			printf "\t\t<tr class=\"nohi\">"
993			printf "<td>Mode</td>"
994			printf "<td>Name</td>"
995			printf "<td>Size</td>"
996			printf "</tr>\n"
997			printf "\t\t<tbody>\n"
998		}
999
1000		{
1001			entry[(NR-1) % 3] = $0
1002			if (NR % 3 == 0) {
1003				printf "\t\t\t<tr>"
1004				for (i = 0; i < 3; i++) {
1005					if (i == 1 && substr(entry[0], 0, 3) == "100") {
1006						printf "<td><a href=\"file/%s.html\">%s</a></td>", entry[i], dotrep(entry[i])
1007					} else {
1008						printf "<td>%s</td>", i == 0 ? oct2sym(entry[i]) : entry[i]
1009					}
1010				}
1011				printf "</tr>\n"
1012			}
1013		}
1014
1015		END {
1016			printf "\t\t</tbody>\n"
1017			printf "\t</table>\n"
1018		}
1019	' > "${repo_path_files}"
1020
1021	# render tags
1022	git -C "${1}/${2}" log                                                  \
1023		--no-walk --tags --pretty="format:%S%n%ai%n%an%n%h"                 \
1024	| html_encode                                                           \
1025	| awk '
1026		BEGIN {
1027			printf "\t<table id=\"tags\">\n"
1028			printf "\t\t<tr class=\"nohi\">"
1029			printf "<td>Name</td>"
1030			printf "<td>Date</td>"
1031			printf "<td>Author</td>"
1032			printf "<td>Commit</td>"
1033			printf "</tr>\n"
1034			printf "\t\t<tbody>\n"
1035		}
1036
1037		{
1038			entry[(NR-1) % 4] = $0
1039			if (NR % 4 == 0) {
1040				printf "\t\t\t<tr>"
1041				for (i = 0; i < 4; i++) {
1042					if (i == 0) {
1043						printf "<td><a href=\"./archive/__REPONAME__-%s.tar.gz\">%s</a></td>",entry[i],entry[i]
1044					} else {
1045						printf "<td>%s</td>",entry[i]
1046					}
1047				}
1048				printf "</tr>\n"
1049			}
1050		}
1051
1052		END {
1053			printf "\t\t</tbody>\n"
1054			printf "\t</table>\n"
1055		}
1056	' | sed "s/__REPONAME__/${repo_name}/g" > "${repo_path_tags}"
1057
1058	# create redirection to README if it exists, else symlink to log
1059	git -C "${1}/${2}" show HEAD:README >/dev/null 2>&1 &&                  \
1060	printf '<meta http-equiv="refresh" content="0; url=%s" />'              \
1061		"${repo_path_file##*/}/README.html"                                 \
1062		> "${1}/${2}/index.html"
1063
1064	[ ! -f "${1}/${2}/index.html" ] && ln -s "log.html" "${1}/${2}/index.html"
1065}
1066
1067render_git() {
1068	git_repos=
1069	git_forks=
1070	git_discs=
1071	git_buffer=
1072	git_pp=
1073	git_owner=
1074	git_description=
1075	git_index="${1}/index.html"
1076
1077	for repo in "${SITE_GIT_REPO_ROOT}"/*; do
1078		git_pp="${repo##*/}"
1079		git_pp="${git_pp%.*}"
1080		print_loading "Rendering repo ${git_pp}"
1081		git_owner="$(< "${repo}"/owner html_encode)"
1082		git_description="$(< "${repo}"/description html_encode)"
1083		git_buffer=$(
1084			printf '<tr><td><a href="%s">%s</a></td><td>%s</td><td>%s</td><td>%s</td></tr>' \
1085				"//${1}/${repo##*/}"                                        \
1086				"${git_pp}"                                                 \
1087				"${git_description}"                                        \
1088				"${git_owner}"                                              \
1089				"$(git -C "${repo}" log -1 --format=%ai)")
1090
1091		if [ -f "$repo/fork" ]; then
1092			git_forks="$(printf '%s%s' "${git_forks}" "${git_buffer}")"
1093		elif [ -f "$repo/discontinued" ]; then
1094			git_discs="$(printf '%s%s' "${git_discs}" "${git_buffer}")"
1095		else
1096			git_repos="$(printf '%s%s' "${git_repos}" "${git_buffer}")"
1097		fi
1098
1099		render_git_repo "${1}" "${repo##*/}"
1100		print_done
1101	done
1102
1103	awk                                                                     \
1104		-v var_author="$(awk_safe "${SITE_AUTHOR}")"                        \
1105		-v var_title="$(awk_safe "Repositories")"                           \
1106		-v var_favicon="$(awk_safe "${SITE_ICON_PNG}")"                     \
1107		-v var_stylesheet="$(awk_safe "${SITE_CSS}")"                       \
1108		-v var_logo="$(awk_safe "${SITE_ICON_SVG}")"                        \
1109		-v var_repos="$(awk_safe "${git_repos}" | sed 's/<tr>/\n\0/2g')"    \
1110		-v var_forks="$(awk_safe "${git_forks}" | sed 's/<tr>/\n\0/2g')"    \
1111		-v var_discs="$(awk_safe "${git_discs}" | sed 's/<tr>/\n\0/2g')"    \
1112	'
1113		/\{:repos:\}/{
1114			padding=$0
1115			gsub(/{:.*:}$/, "", padding)
1116			gsub(/\n/, "\n" padding, var_repos)
1117		}
1118
1119		/\{:forks:\}/{
1120			padding=$0
1121			gsub(/{:.*:}$/, "", padding)
1122			gsub(/\n/, "\n" padding, var_forks)
1123		}
1124
1125		/\{:discs:\}/{
1126			padding=$0
1127			gsub(/{:.*:}$/, "", padding)
1128			gsub(/\n/, "\n" padding, var_dics)
1129		}
1130
1131		{
1132			gsub(/\{:author:\}/, var_author)
1133			gsub(/\{:title:\}/, var_title)
1134			gsub(/\{:favicon:\}/, var_favicon)
1135			gsub(/\{:stylesheet:\}/, var_stylesheet)
1136			gsub(/\{:logo:\}/, var_logo)
1137			gsub(/\{:repos:\}/, var_repos)
1138			gsub(/\{:forks:\}/, var_forks)
1139			gsub(/\{:discs:\}/, var_discs)
1140			print
1141		}
1142	' "${CFG_GIT_REPOS_HTML}" > "${git_index}"
1143
1144	# complete html-files from html.tmp files
1145	find "${1}" -mindepth 2 -type f -name "*.html.tmp" | while read -r file; do
1146		[ "${file##*/}" = "head.html.tmp" ] && continue
1147		{
1148			awk '1;/<body>/{exit}' "${git_index}"
1149			cat "${file%%.git/*}.git/head.html.tmp"
1150			cat "${file}"
1151			awk '/<\/body>/,0' "${git_index}"
1152		} > "${file%.*}"
1153	done
1154
1155	# remove all html.tmp files
1156	find "${1}" -mindepth 2 -type f -name "*.html.tmp" -exec rm -f {} \;
1157}
1158
1159render_git_commits() {
1160	_tmp_file="$(mktemp "${TMPDIR:-/tmp/}$(basename "$0").repolog.XXXXXX")"
1161
1162	for repo in "${SITE_GIT_REPO_ROOT}"/*; do
1163		a=${repo##*/}
1164		c="//${1}/git/${a}/commit"
1165		a="<a href=\"//${1}/git/${a}\">${a%.*}</a>"
1166		git -C "${repo}" log -n "${SITE_GIT_COMMIT_COUNT}"                  \
1167			--no-merges                                                     \
1168			--pretty="format:%at:%ai [$a] <a href=\"$c/%H.html\">%s</a>"
1169		echo
1170	done                                                                    \
1171	| sort -r                                                               \
1172	| sed "${SITE_GIT_COMMIT_COUNT}q"                                       \
1173	| sed -e 's/^[^:]*://g'                                                 \
1174	| awk -v var_domain="$1" '
1175		BEGIN {
1176			print "<ul class=\"repo-log\">";
1177		}
1178
1179		{
1180			printf "<li><span class=\"log-date\">%s %s %s</span>",$1,$2,$3;
1181			$1=$2=$3="";
1182			$0=$0;
1183		}
1184
1185		NF=NF {
1186			printf "%s</li>\n",$0
1187		}
1188
1189		END {
1190			printf "<li>[<a href=\"//%s/git/\">...</a>]</li></ul>\n",var_domain
1191		}
1192	' > "${_tmp_file}"
1193
1194	find "${1}" -type f -name 'index.html' -exec                            \
1195		sed -i {} -e "
1196			/^.*<!--placeholder:${SITE_GIT_COMMIT_PH}-->.*$/ {
1197				r ${_tmp_file}
1198				d
1199			}
1200		" \;
1201
1202	rm -f "${_tmp_file}"
1203}
1204
1205generate_sitemap() {
1206	_sitemap="${1}/pub/sitemap.xml"
1207
1208	awk                                                                     \
1209		-v var_items="$(
1210			find -L "${1}" -type f -name 'index.html'                       \
1211			| html_encode                                                   \
1212			| awk                                                           \
1213				-v format_item="$(cat "${CFG_SITEMAP_XML_ITEM}")\n"         \
1214			'
1215				{
1216					gsub(/[^\/]*$/, "")
1217					printf format_item, $0
1218				}
1219			' | awk_safe
1220		)"                                                                  \
1221	'
1222		{
1223			gsub(/\{:items:\}/, var_items)
1224			print
1225		}
1226	' "${CFG_SITEMAP_XML}" > "${_sitemap}"
1227}
1228
1229main() {
1230	# check if build is possible
1231	if [ "$1" = "check" ]; then
1232		[ -d "${SITE_GIT_REPO_ROOT}" ] || exit 50
1233		
1234		missing=0
1235		for cmd in tex2svg awk; do
1236			if ! command -v "$cmd" >/dev/null 2>&1; then
1237				echo "ERROR: '$cmd' is missing"
1238				missing=1
1239			fi
1240		done
1241		[ "$missing" -eq 0 ] || exit 52
1242	fi
1243
1244	# make sure both arguments, if existing, are directories
1245	[ -n "$1" ] && [ ! -d "$1" ] && return
1246	[ -n "$2" ] && [ ! -d "$2" ] && return
1247
1248	if [ -z "$2" ] && [ -z "${BUILDONLY}" ]; then
1249		print_loading "Running the prerequisite script"
1250		# make sure site/git is removed before rendering (to avoid it in menu)
1251		rm -rf "${1}/git"
1252
1253		# make sure tag pages are removed before creating them
1254		rm -rf "${1}"/articles/tags
1255
1256		# create tags directory
1257		mkdir -p "${1}"/articles/tags
1258		touch "${1}"/articles/tags/.buildignore
1259		echo "index.html" > "${1}"/articles/tags/.assemble
1260		echo ".ds TITLE   Tags" > "${1}"/articles/tags/.metadata
1261
1262		printf '.DIVS wrap-list\n.ULS\n' > "${1}"/articles/tags/index.www
1263		# manage tags
1264		find "${1}"/articles -name .tags -exec cat {} \; | sort -u | while read -r tag
1265		do
1266			tag_safe="$(echo "${tag}" | tr \  _)"
1267			mkdir -p "${1}"/articles/tags/"${tag_safe}"
1268			touch "${1}"/articles/tags/"${tag_safe}"/.buildignore
1269			[ "${tag}" != "${CFG_PINNED_TAG}" ] && \
1270			printf '.URL ../.. Articles\ntagged "%s"\n' "${tag}" > "${1}"/articles/tags/"${tag_safe}"/index.www
1271			echo "{:articles#${tag}:}" >> "${1}"/articles/tags/"${tag_safe}"/index.www
1272			echo "index.html" > "${1}"/articles/tags/"${tag_safe}"/.assemble
1273			echo ".ds TITLE   #${tag}" > "${1}"/articles/tags/"${tag_safe}"/.metadata
1274
1275			if [ "${tag}" != "${CFG_PINNED_TAG}" ]; then
1276				# append tag to tags page
1277				printf '.LI\n.ARTTAG "%s" "%s" "%s"\n'                      \
1278					"${tag_safe}" "${tag}"                                  \
1279					"$(grep "^${tag}$" "${1}"/articles/*/.tags | wc -l)"    \
1280					>> "${1}"/articles/tags/index.www
1281			fi
1282		done
1283		printf '.ULE\n' >> "${1}"/articles/tags/index.www
1284		printf '.DIVE\n' >> "${1}"/articles/tags/index.www
1285		print_done
1286
1287		# main render, executes sub render (see below)
1288		find "$1" -depth -type d                                            \
1289			-exec test -e '{}/index.www' \;                                 \
1290			-exec "$0" "$1" {} \;
1291
1292		# render git commits into place holders
1293		print_loading "Rendering git commits"
1294		render_git_commits "$1"
1295		print_done
1296
1297		# render git
1298		[ "${SKIPGIT-SKIP}" = "SKIP" ] && render_git "git.${1}"
1299
1300		# move git.site to site/git
1301		cp -r "git.${1}" "${1}/git"
1302
1303		# create symlinks for each repo, as such: {repo} -> {repo}.git
1304		find "${1}/git" -type d -name "*.git"                               \
1305			-execdir sh -c 'ln -s "${1}" "${1%.*}"' sh {} \;
1306
1307		# translate git.site to site/git
1308		print_loading "Translate git site into ./git"
1309		find "${1}/git" -type f -name "*.html" | while read -r file; do
1310			sed -i "s,\"//\[site\]/,\"//[site]/git/,g" "$file"
1311			sed -i "s,\"//git.${1}/,\"//${1}/git/,g" "$file"
1312			sed -i "s,https://git.${1}/,https://${1}/git/,g" "$file"
1313			sed -i "s,\"//\[root\]/,\"//[site]/,g" "$file"
1314		done
1315		print_done
1316
1317		# convert absolute paths to relative, so that they work with IPFS
1318		print_loading "Converting absolute paths to relative"
1319		find "${1}" -type f -name "*.html" | while read -r file; do
1320			count="$(echo "$file" | tr -cd '/' | wc -c)"
1321			sed -i "s,\"//\(${1}\|\[site\]\)/,\"$(
1322					seq -f "../%g" -s '' 2 "$count" | sed 's/[0-9]//g'
1323				),g" "$file"
1324
1325			# remove redundant prefix from references
1326			grep -o '="../[^"]*"' "$file"                                   \
1327			| sed -e 's/^="//g' -e 's/"$//g' | while read -r path; do
1328				rel="$(realpath --relative-to="${file%/*}" "${file%/*}/${path}")"
1329				rel="$(echo $rel | sed                                      \
1330					-e 's/\./\\./g'                                         \
1331					-e 's/\&/\\&/g'                                         \
1332				)"
1333				ref="$(echo $path | sed                                     \
1334					-e 's/\./\\./g'                                         \
1335					-e 's/\&/\\&/g'                                         \
1336				)"
1337				sed "s,=\"$ref\",=\"$rel\",g" -i "$file"
1338			done
1339
1340		done
1341		print_done
1342
1343		# generate sitemaps
1344		print_loading "Generating sitemaps"
1345		generate_sitemap "${1}"
1346		print_done
1347	elif [ -z "$2" ] && [ ! -z "${BUILDONLY}" ]; then
1348
1349		render_main "$1" "${BUILDONLY}"
1350
1351		# convert absolute paths to relative, so that they work with IPFS
1352		print_loading "Converting absolute paths to relative"
1353		find "${BUILDONLY}" -type f -name "*.html" | while read -r file; do
1354			count="$(echo "$file" | tr -cd '/' | wc -c)"
1355			sed -i "s,\"//\(${1}\|\[site\]\)/,\"$(
1356					seq -f "../%g" -s '' 2 "$count" | sed 's/[0-9]//g'
1357				),g" "$file"
1358
1359			# remove redundant prefix from references
1360			grep -o '="../[^"]*"' "$file"                                   \
1361			| sed -e 's/^="//g' -e 's/"$//g' | while read -r path; do
1362				rel="$(realpath --relative-to="${file%/*}" "${file%/*}/${path}")"
1363				rel="$(echo $rel | sed                                      \
1364					-e 's/\./\\./g'                                         \
1365					-e 's/\&/\\&/g'                                         \
1366				)"
1367				ref="$(echo $path | sed                                     \
1368					-e 's/\./\\./g'                                         \
1369					-e 's/\&/\\&/g'                                         \
1370				)"
1371				sed "s,=\"$ref\",=\"$rel\",g" -i "$file"
1372			done
1373
1374		done
1375		print_done
1376
1377	elif [ -n "$2" ]; then
1378		[ ! -z ${BUILDONLY} ] && [ "${BUILDONLY}" != "${2}" ] && return
1379
1380		# sub render
1381		render_main "$1" "$2"
1382	fi
1383}
1384
1385main "${1:-$SITE_DOMAIN}" "$2"