build 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. #!/bin/bash
  2. #
  3. # build
  4. #
  5. # Build script for the Dragora GNU/Linux-Libre website
  6. # (https://www.dragora.org)
  7. #
  8. #
  9. # Copyright (C) 2020, 2021 Michael Siegel
  10. #
  11. # Licensed under the Apache License, Version 2.0 (the "License");
  12. # you may not use this file except in compliance with the License.
  13. # You may obtain a copy of the License at
  14. #
  15. # http://www.apache.org/licenses/LICENSE-2.0
  16. #
  17. # Unless required by applicable law or agreed to in writing, software
  18. # distributed under the License is distributed on an "AS IS" BASIS,
  19. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  20. # See the License for the specific language governing permissions and
  21. # limitations under the License.
  22. #### INCLUDES ####
  23. . include/constants || exit 1
  24. . include/subroutines || exit 1
  25. #### CONSTANTS ####
  26. #### GLOBAL VARIABLES ####
  27. backpath=
  28. build_testbed=0
  29. css_path=
  30. err_msg= # Try to do without that. It's currently ununsed.
  31. favicon_path=
  32. lang=
  33. logo_path=
  34. mod_date=
  35. page_title=
  36. pg=
  37. pg_basename_in=
  38. pg_basename_out=
  39. pg_path_in=
  40. pg_path_out=
  41. slashes=
  42. sublevel=
  43. title=
  44. #### FUNCTIONS ####
  45. _get_page_title() {
  46. ## Retrieve page title from input file
  47. sed -n '/^<!--PAGETITLE:.*-->$/ {
  48. s/<!--PAGETITLE://
  49. s/-->$//
  50. p
  51. }' "$1"
  52. }
  53. _get_regular_pages() {
  54. ## Find all HTML pages in "$PAGES_DIR"/"$lang", except those that belong to
  55. ## the Dragora Handbook
  56. find -- "$PAGES_DIR"/"$lang" -path "$PAGES_DIR"/"$lang"/doc/handbook -prune \
  57. -o -type f -name '*.html.in' -print
  58. }
  59. _mk_clean() {
  60. ## Remove page directories from $OUTPUT_DIR
  61. local dir=
  62. for dir in $(_get_lang_dirs)
  63. do
  64. rm -rf -- "${OUTPUT_DIR:?}/$dir" || \
  65. { _perr "cannot remove directory -- '$OUTPUT_DIR/$dir'"; return 1; }
  66. done
  67. unset dir
  68. }
  69. _mk_header() {
  70. ## Compile HTML page header
  71. printf '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"\n "http://www.w3.org/TR/html4/strict.dtd">\n'
  72. printf '<html lang="%s">\n<head>\n' "$lang"
  73. printf \
  74. '%s<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">\n' \
  75. "$IND1"
  76. printf '%s<link rel="stylesheet" type="text/css" href="%s">\n' "$IND1" \
  77. "$css_path"
  78. printf '%s<link rel="icon" type="image/gif" href="%s">\n' "$IND1" \
  79. "$favicon_path"
  80. printf '%s<title>%s</title>\n' "$IND1" "$title"
  81. printf '%s<meta name="description" content="%s">\n' "$IND1" \
  82. "$lang_description"
  83. printf '</head>\n<body>\n'
  84. printf '%s<div class="header" role="banner">\n' "$IND1"
  85. printf '%s<img src="%s" height="80" alt="Dragora logo">\n' "$IND2" \
  86. "$logo_path"
  87. printf '%s<div class="header_text">\n' "$IND2"
  88. printf '%s<h1>Dragora</h1>\n' "$IND3"
  89. printf '%s<p class="tagline">%s</p>\n' "$IND3" "$lang_tagline"
  90. printf '%s</div> <!-- close header_text -->\n' "$IND2"
  91. printf '%s</div> <!-- close header -->\n' "$IND1"
  92. printf '%s<div class="torso">\n' "$IND1"
  93. }
  94. _mk_indent() {
  95. ## Create an indentation string for a given level
  96. local ind=
  97. local level="$1"
  98. while [[ "$level" -gt 0 ]]
  99. do
  100. ind="${ind}${IND1}"
  101. ((--level))
  102. done
  103. printf '%s' "$ind"
  104. }
  105. _pick_navitems() {
  106. # Print out only those items that are to be shown in the site navigation on
  107. # the current page
  108. local current_pg="${pg_path_out#*/}/"
  109. local depth_current_pg=
  110. local depth_item=
  111. local fc=
  112. local fi=
  113. local field=
  114. local ignore=
  115. local slashes=
  116. slashes="${current_pg//[^\/]}"
  117. depth_current_pg="${#slashes}"
  118. for item in "${navtree[@]}"
  119. do
  120. [[ "$build_testbed" -eq 0 && "$item" = testbed/* ]] && continue
  121. slashes="${item//[^\/]}"
  122. depth_item="${#slashes}"
  123. # Ignore any item at a deeper nesting level than immediate sub-pages of
  124. # $current_pg.
  125. [[ "$depth_item" -gt "$((depth_current_pg + 1))" ]] && continue
  126. # The loop below can't catch those…
  127. ignore=0
  128. field=1
  129. while true
  130. do
  131. fc="$(cut -d / -f "$field" <<< "$current_pg")"
  132. fi="$(cut -d / -f "$field" <<< "$item")"
  133. [[ -n "$fi" && -n "$fc" ]] || break
  134. if [[ "$fi" != "$fc" ]]
  135. then
  136. [[ "$depth_item" -gt "$field" ]] && ignore=1 && break
  137. fi
  138. ((++field))
  139. done
  140. [[ "$ignore" -eq 1 ]] && continue
  141. printf '%s ' "$item"
  142. done
  143. }
  144. _mk_nav() {
  145. # Compile site navigation for the current page
  146. local current_pg="${pg_path_out#*/}/"
  147. local depth_curr=
  148. local depth_next=
  149. local ind=4
  150. local nav_label=
  151. local nav_path=
  152. local slashes=
  153. local sublists_opened=0
  154. printf '%s<div class="site_nav" role="navigation">\n' "$IND2"
  155. printf '%s<ul>\n' "$IND3"
  156. while [[ $# -gt 0 ]]
  157. do
  158. slashes="${1//[^\/]}"
  159. depth_curr="${#slashes}"
  160. slashes="${2//[^\/]}"
  161. depth_next="${#slashes}"
  162. nav_path="${backpath}${1}"
  163. nav_label="$(_get_page_title "${PAGES_DIR}/${lang}/${1}/index.html.in")"
  164. # Open list item:
  165. printf '%s<li>' "$(_mk_indent "$ind")"
  166. printf '<div class="site_nav_item'
  167. [[ "$1" = "$current_pg" ]] && printf ' selected'
  168. printf '"><a href="%s">%s</a></div>' "$nav_path" "$nav_label"
  169. # Next path has equal depth: Close list item:
  170. if [[ "$depth_curr" -eq "$depth_next" ]]
  171. then
  172. printf '</li>\n'
  173. # Next path runs deeper: Open sublist for next nav item, increase count of
  174. # opened sublists:
  175. elif [[ "$depth_curr" -lt "$depth_next" ]]
  176. then
  177. ((++ind))
  178. printf '\n%s<ul>\n' "$(_mk_indent "$ind")"
  179. ((++sublists_opened))
  180. ((++ind)) # for <li> in the sublist
  181. # Next path runs less deep: Close list item, close appropriate number of
  182. # opened sublists, close surrounding list item.
  183. elif [[ "$depth_curr" -gt "$depth_next" ]]
  184. then
  185. printf '</li>\n'
  186. while [[ "$sublists_opened" -gt 0 && \
  187. "$((sublists_opened - depth_next))" -ge 0 ]]
  188. do
  189. ((--ind))
  190. printf '%s</ul>\n' "$(_mk_indent "$ind")"
  191. ((--sublists_opened))
  192. ((--ind))
  193. printf '%s</li>\n' "$(_mk_indent "$ind")"
  194. done
  195. # The loop condition explained:
  196. # Part 1: Closing sublists runs up to and including
  197. # $sublists_opened - $depth_next being 0. But if $2 is empty
  198. # because we are already working on the final nav item, $depth_next
  199. # will also be 0. Subtracting that from sublists_opened being 0 would,
  200. # of course, itself be 0 and thus make the loop condition evaluate to
  201. # true and result in printing an additonal sublist closing when all
  202. # sublists have already been closed.
  203. # Part 2: You can't just close all open sublists at this point because
  204. # list items on lower (less deep) nesting levels might follow. So,
  205. # closing opened sublists can only go as far as the nesting level of
  206. # the next nav item.
  207. fi
  208. shift
  209. done
  210. printf '%s</ul>\n' "$IND3"
  211. printf '%s</div> <!-- close site_nav -->\n' "$IND2"
  212. printf '%s<div class="main" role="main">\n' "$IND2"
  213. }
  214. _mk_content() {
  215. ## Incorporate dynamic content modules, if any.
  216. local pg="$1"
  217. local lang="$2"
  218. local has_modules=1
  219. # local tmpfile_1=
  220. # local tmpfile_2=
  221. #
  222. # tmpfile_1="$(mktemp "$TMP_DIR"/XXXXXX)"
  223. # tmpfile_2="$(mktemp "$TMP_DIR"/XXXXXX)"
  224. #
  225. # Two temporary files will be needed for copying content back an forth, as
  226. # soon as there is more than one dynamic content module.
  227. if grep -q '^<!--LATEST_NEWS-->$' "$pg"
  228. then
  229. has_modules=0
  230. sed '1,/^<!--LATEST_NEWS-->$/ !d' "$pg" && \
  231. grep -A "$((LATEST_NEWS_ITEMS * 3))" -- '^<ul>$' \
  232. "${PAGES_DIR}/${lang}/$NEWS_PAGE" | sed '$ a <\/ul>' && \
  233. sed '1,/^<!--LATEST_NEWS-->$/ d' "$pg"
  234. fi
  235. [[ "$has_modules" -eq 1 ]] && cat "$pg"
  236. }
  237. _mk_footer() {
  238. ## Compile HTML page footer
  239. printf '\n<p class="back_top"><a href="#">%s</a></p>\n\n' "$lang_back_to_top"
  240. printf '%s</div> <!-- close main -->\n' "$IND2"
  241. printf '%s</div> <!-- close torso -->\n' "$IND1"
  242. printf '%s<hr class="footer_hr">\n' "$IND1"
  243. printf '%s<div class="footer" role="contentinfo">\n' "$IND1"
  244. printf '%s<p>%s: %s</p>\n' "$IND2" "$lang_mod_label" "$mod_date"
  245. printf '%s<p>Copyright © %s %s<br>\n' "$IND2" "$COPYRIGHT_YEARS" \
  246. "$COPYRIGHT_HOLDERS"
  247. printf '%s<a href="%slicense/">%s</a>\n' "$IND3" "${backpath}" \
  248. "$(_get_page_title "${PAGES_DIR}/${lang}/license/index.html.in")"
  249. printf '%s</p>\n' "$IND2"
  250. printf '%s</div> <!-- close footer -->\n' "$IND1"
  251. printf '</body>\n</html>\n'
  252. }
  253. _mk_pretty() {
  254. ## Remove meta comments from page source
  255. sed -e '/^<!--PAGETITLE:.*-->$/ d' \
  256. -e '/^<!--LATEST_NEWS-->$/ d' <<< "$1"
  257. }
  258. _set_backpath() {
  259. backpath= # If you don't, the string will grow with every invocation.
  260. while [[ "$sublevel" -gt 0 ]]
  261. do
  262. backpath="${backpath}$DIR_UP"
  263. ((--sublevel))
  264. done
  265. }
  266. #### MAIN ####
  267. # TODO: implement traps
  268. ## Environment checks
  269. _env_checks || _abort
  270. ## Parse command-line arguments
  271. [[ "$1" = '-t' ]] && build_testbed=1
  272. ## Remove page directories from $OUTPUT_DIR, then rsync static files
  273. ## Order matters!
  274. _mk_clean || _abort
  275. rsync -av --delete "$COMMON_DIR"/ "$OUTPUT_DIR" || _abort
  276. # Compile pages
  277. _get_navtree
  278. for lang in $(_get_lang_dirs)
  279. do
  280. # Get values for language-specific parameters in header and footer
  281. . "$PAGES_DIR/$lang/$HEADER_PARAMS" || _abort
  282. . "$PAGES_DIR/$lang/$FOOTER_PARAMS" || _abort
  283. # Compile pages
  284. for pg in $(_get_regular_pages)
  285. do
  286. # Set path and file names
  287. pg_path_in="${pg%/*}"
  288. pg_path_out="$(sed "s,$PAGES_DIR/,," <<< "$pg_path_in")"
  289. # echo "$pg_path_out" # DEBUG
  290. pg_basename_in="${pg##*/}"
  291. pg_basename_out="${pg_basename_in%.in}" # .html.in → .html
  292. if [[ "$build_testbed" -eq 0 ]]
  293. then
  294. # Skip testbed pages
  295. [[ "$(cut -d '/' -f 2 <<< "$pg_path_out")" = testbed ]] && continue
  296. fi
  297. slashes="${pg_path_out//[^\/]}"
  298. sublevel="${#slashes}"
  299. _set_backpath # makes use of $sublevel
  300. # these should, maybe, be 'local' to _mk_header
  301. css_path="${DIR_UP}${backpath}${STYLESHEET_PATH}"
  302. favicon_path="${DIR_UP}${backpath}${FAVICON_PATH}"
  303. logo_path="${DIR_UP}${backpath}${LOGO_PATH}"
  304. # Determine page title
  305. page_title="$(_get_page_title "$pg")"
  306. title="$SITE_TITLE $SEPARATOR $page_title"
  307. # Determine modification date (requires GNU version of `stat')
  308. mod_date="$(TZ="$TIMEZONE" stat -c '%y' -- "$pg" | cut -d '.' -f 1)"
  309. mod_date="$mod_date $TIMEZONE"
  310. # Action
  311. mkdir -p -- "$OUTPUT_DIR"/"$pg_path_out" || _abort
  312. { _mk_header && _mk_nav $(_pick_navitems) && \
  313. _mk_pretty "$(_mk_content "$pg" "$lang")" && _mk_footer; } \
  314. > "$OUTPUT_DIR"/"$pg_path_out"/"$pg_basename_out" || _abort
  315. done
  316. ln -s -- "$HOME_PAGE_DIR"/index.html "$OUTPUT_DIR"/"$lang"/index.html
  317. done
  318. ln -s -- "${PAGES_DIR_MASTER##*/}"/"$HOME_PAGE_DIR"/index.html \
  319. "$OUTPUT_DIR"/index.html
  320. _cleanup