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