build 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. #!/usr/bin/env sh
  2. # SPDX-License-Identifier: GPL-3.0-or-later
  3. # SPDX-FileCopyrightText: 2017,2021-2023 Leah Rowe <leah@libreboot.org>
  4. # SPDX-FileCopyrightText: 2017 Alyssa Rosenzweig <alyssa@rosenzweig.io>
  5. # SPDX-FileCopyrightText: 2017 Michael Reed <michael@michaelreed.io>
  6. [ "${DEBUG+set}" = "set" ] && set -v
  7. set -u -e
  8. id -u 1>/dev/null 2>/dev/null || exit 1
  9. if [ "$(id -u)" = "0" ]; then
  10. printf "%s: Running as root not allowed, for security.\n" "$0" 1>&2
  11. exit 1
  12. fi
  13. export LC_COLLATE=C
  14. export LC_ALL=C
  15. . "include/err.sh"
  16. . "include/news.sh"
  17. . "include/option.sh"
  18. tmpdir=""
  19. tmpdir_was_set="y"
  20. set | grep TMPDIR 1>/dev/null 2>/dev/null || tmpdir_was_set="n"
  21. if [ "${tmpdir_was_set}" = "n" ]; then
  22. export TMPDIR="/tmp"
  23. tmpdir="$(mktemp -d -t untitledssg_XXXXXXXX)"
  24. export TMPDIR="${tmpdir}"
  25. else
  26. export TMPDIR="${TMPDIR}"
  27. tmpdir="${TMPDIR}"
  28. fi
  29. UVARS="TITLE CSS DOMAIN BLOGTITLE LAZY BLOGDESCRIPTION DEFAULTLANG BLOGDIR"
  30. UVARS="$UVARS SITEMAP"
  31. eval "$(setvars "" sitechanged $UVARS tmproll)"
  32. linkpath="${0}"
  33. linkname="${linkpath##*/}"
  34. main()
  35. {
  36. check_path d www || fail "invalid www/ directory"
  37. eval "$(setvars "" _cmd _args)"
  38. mode="sites"
  39. if [ "${linkname}" != "clean" ]; then
  40. [ $# -gt 0 ] && \
  41. mode="${1}" && shift 1
  42. if [ "${mode}" = "pages" ] && [ $# -lt 1 ]; then
  43. mode="sites"
  44. elif [ "${mode}" != "pages" ] && [ "${mode}" != "sites" ]; then
  45. printf "%s\n" "${mode}" > "${tmpdir}/args"
  46. _args="${mode}"
  47. mode="sites"
  48. fi
  49. fi
  50. if [ $# -lt 1 ] && [ -z "${_args}" ]; then
  51. for x in www/*; do
  52. printf "%s\n" "${x}" >> "${tmpdir}/args"
  53. done
  54. else
  55. for x in "$@"; do
  56. printf "%s\n" "${x}" >> "${tmpdir}/args"
  57. done
  58. fi
  59. [ "${linkname}" = "clean" ] && _cmd="cleansite"
  60. if [ "${linkname}" != "clean" ]; then
  61. [ "${mode}" = "sites" ] && _cmd="buildsite"
  62. [ "${mode}" = "pages" ] && _cmd="buildpage"
  63. fi
  64. [ -z "${_cmd}" ] && fail "unrecognised command"
  65. while read -r x; do
  66. [ -z "${x}" ] && continue
  67. eval "$(setvars "" $UVARS)"
  68. startdate=$(date +%s%N | cut -b1-13)
  69. $_cmd "$x"
  70. enddate=$(date +%s%N | cut -b1-13)
  71. endtime=$(( enddate - startdate ))
  72. printf "time: %d milliseconds\n" "${endtime}"
  73. done < "${tmpdir}/args"
  74. untitled_exit 0
  75. }
  76. buildsite()
  77. {
  78. eval "$(setvars "n" sitechanged tmproll)"
  79. _sitedir="${1#www/}"
  80. _sitedir="www/${_sitedir%%/*}"
  81. printf "\nProcessing files in site: %s\n" "${_sitedir##*/}"
  82. check_path d "${_sitedir}" || return 0
  83. check_symlinks "${_sitedir}" "sitedir" || return 0
  84. check_site_changed "${_sitedir}"
  85. check_site_config "${_sitedir}" || return 0
  86. find -L "${_sitedir}/site/" -name "*.md" > "${tmpdir}/pages"
  87. while read -r y; do
  88. mkhtml "${y}" "${_sitedir##*/}"
  89. done < "${tmpdir}/pages"
  90. mksitemap "${_sitedir}"
  91. mknews "$_sitedir"
  92. }
  93. buildpage()
  94. {
  95. [ $# -lt 1 ] && return 0
  96. _file=${1%.md}
  97. _file="${1}"
  98. _file="${_file#/}"
  99. _file="${_file#./}"
  100. _filelocal="${_file##*/}"
  101. [ "${_file%.md}" = "${_file}" ] && return 0
  102. # path translation
  103. # e.g. libreboot/index.md becomes www/libreboot/site/index.md
  104. # e.g. libreboot/site/index.md becomes www/libreboot/site/index.md
  105. # e.g. www/libreboot/index.md becomes www/libreboot/site/index.md
  106. # in all cases, the correct path is: www/libreboot/site/index.md
  107. _file="${_file#www/}"
  108. _realfile="${_file#*/}"
  109. [ "${_realfile#site/}" = "${_realfile}" ] && \
  110. _realfile="site/${_realfile}"
  111. _file="www/${_file%%/*}/${_realfile}"
  112. _sitedir="${_file#www/}"
  113. [ "${_sitedir}" = "${_file}" ] && return 0
  114. _sitename="${_sitedir%%/*}"
  115. [ "${_sitename}" = "${_sitedir}" ] && return 0
  116. _sitedir="www/${_sitename}"
  117. check_path d "${_sitedir}" || return 0
  118. check_symlinks "${_sitedir}" "sitedir" || return 0
  119. printf "\nPage: %s\n" "${_file}"
  120. printf "Processing page in site directory: %s/site/\n" "${_sitedir}"
  121. check_site_config "${_sitedir}" || return 0
  122. mkhtml "${_file}" "${_sitedir##*/}"
  123. mksitemap "${_sitedir}"
  124. mknews "$_sitedir"
  125. }
  126. cleansite() {
  127. SITENAME="${1##*/}"
  128. if [ ! -d "www/${SITENAME}/site" ]; then
  129. printf "Site '%s' has no site directory; skipping clean\n" \
  130. "${SITENAME}"
  131. return 0
  132. fi
  133. if [ -L "www/${SITENAME}" ] || [ -L "www/${SITENAME}/site" ]; then
  134. printf "Site '%s' is a symlink directory. skipping clean\n" \
  135. "${SITENAME}"
  136. return 0
  137. fi
  138. printf "Cleaning generated files from site: '%s'\n" "${SITENAME}"
  139. find -L "www/${SITENAME}/site/" -type f -name "*.html" \
  140. > "${tmpdir}/list"
  141. while read -r f; do
  142. if [ ! -f "${f}" ]; then continue; fi
  143. if [ -L "${f}" ]; then continue; fi
  144. if [ -f "${f%.html}.md" ] && [ ! -L "${f%.html}.md" ]; then
  145. rm -f "${f}"
  146. fi
  147. done < "${tmpdir}/list"
  148. find -L "www/${SITENAME}/site" -type f -name "*.date" \
  149. > "${tmpdir}/list"
  150. while read -r f; do
  151. if [ ! -f "${f}" ]; then continue; fi
  152. if [ -L "${f}" ]; then continue; fi
  153. if [ "${f##*/}" = "footer.include.date" ] \
  154. || [ "${f##*/}" = "nav.include.date" ] \
  155. || [ "${f##*/}" = "template.include.date" ]; then
  156. continue
  157. fi
  158. rm -f "${f}"
  159. done < "${tmpdir}/list"
  160. rm -f www/"${SITENAME}"/site/feed.xml
  161. find -L "www/${SITENAME}/site/" -type f -name "MANIFEST" \
  162. > "${tmpdir}/list"
  163. while read -r f; do
  164. if [ ! -f "${f}" ]; then continue; fi
  165. if [ -L "${f}" ]; then continue; fi
  166. for ff in "${f%MANIFEST}"{index.md,index.html,feed.xml}; do
  167. if [ ! -f "${ff}" ]; then continue; fi
  168. if [ -L "${ff}" ]; then continue; fi
  169. rm -f "${ff}"
  170. done
  171. done < "${tmpdir}/list"
  172. }
  173. mksitemap()
  174. {
  175. [ "${SITEMAP}" = "n" ] && return 0
  176. _sitedir="${1}"
  177. _tmpfile=$(mktemp -t untitled_www.XXXXXXXXXX)
  178. if [ -f "${_sitedir}/site/sitemap.include" ]; then
  179. cat "${_sitedir}/site/sitemap.include" > "${_tmpfile}"
  180. else
  181. printf "# Site map\n\nList of pages:\n" \
  182. > "${_tmpfile}"
  183. fi
  184. printf "\n\n" >> "${_tmpfile}"
  185. find -L "${_sitedir}/site/" -type f -name "*.md" > "${tmpdir}/toc"
  186. sort "${tmpdir}/toc" > "${tmpdir}/toc_sorted"
  187. mv "${tmpdir}/toc_sorted" "${tmpdir}/toc"
  188. printf "<div class='sitemap'>\n" >> "${_tmpfile}"
  189. while read -r y; do
  190. check_path f "${y}" || continue
  191. [ "${y}" = "${_sitedir}/site/sitemap.md" ] && continue
  192. _uri="${y##"${_sitedir}/site"}"
  193. _uri2="${_uri}"
  194. _uri="${_uri%index.md}"
  195. printf "* %s: [%s](%s)\n" "${_uri}" "$(mktitle "${y}")" \
  196. "${_uri}" >> "${_tmpfile}"
  197. done < "${tmpdir}/toc"
  198. printf "</div>\n\n" >> "${_tmpfile}"
  199. cp "${_tmpfile}" "${_sitedir}/site/sitemap.md"
  200. rm -f "${_tmpfile}"
  201. check_path f "${_sitedir}/site/sitemap.md" || return 0
  202. mkhtml "${_sitedir}/site/sitemap.md" "${_sitedir##*/}"
  203. }
  204. # convert pandoc-markdown file into html, using a template file
  205. mkhtml()
  206. {
  207. check_path f "${1}" || return 0
  208. eval "$(setvars "" _css _footer _nav _template)"
  209. _final="${tmpdir}/final"
  210. _file="${1%.md}"
  211. _sitedir="www/${2}"
  212. # will be set to y if page changes are detected
  213. _needchange="n"
  214. # e.g. file.md is default(english) and file.ru.md is russian
  215. _realpage="${_file##*/}"
  216. _realpagelang="${_realpage##*.}"
  217. if [ "${_realpagelang}" = "${_realpage}" ]; then
  218. _realpagelang="${DEFAULTLANG}"
  219. else
  220. _file="${_file%".${_realpagelang}"}"
  221. fi
  222. _strconf="lang/${_realpagelang}/strings.cfg"
  223. for p in "lang/${DEFAULTLANG}/strings.cfg" "lang/en/strings.cfg"; do
  224. [ -f "$_strconf" ] || _strconf="$p"
  225. done
  226. # This is the uri but without any extension, *and without language
  227. # extension*. This will be used extensively, especially for backlinks
  228. _uri="${_file##"${_sitedir}/site"}" # without file extension e.g. .html
  229. # Backlink text for each page. This helps with site navigation
  230. _backlink=""
  231. if [ "${_uri}" != "/index" ]; then # the homepage doesn't need one
  232. _backlink="$(getConfigValue "${_strconf}" "BACKLINK_PREVDIR")"
  233. [ "${_uri##*/}" = "index" ] || \
  234. _backlink="$(getConfigValue "${_strconf}" \
  235. "BACKLINK_CURRENTDIR")"
  236. fi
  237. # Allow a given page to override the footer/nav/template/css file
  238. for p in footer nav template css; do
  239. eval "$(setvars "" _${p})"
  240. [ -f "${_file}.${p}" ] && \
  241. eval "$(setvars "${_file}.${p}" _${p})"
  242. [ "${_realpagelang}" != "${DEFAULTLANG}" ] && \
  243. [ -f "${_file}.${_realpagelang}.${p}" ] && \
  244. eval "$(setvars "${_file}.${_realpagelang}.$p" _$p)"
  245. done
  246. [ -z "${_css}" ] || _css="${_css##*/}"
  247. if [ "${_realpagelang}" != "${DEFAULTLANG}" ]; then
  248. _file="${_file}.${_realpagelang}"
  249. _uri="${_uri}.${_realpagelang}"
  250. fi
  251. # allow overriding the global footer/nav/template file
  252. for p in footer nav template; do
  253. eval "[ -z \"\${_${p}}\" ] || continue"
  254. [ -f "${_sitedir}/site/${p}.include" ] && \
  255. eval "$(setvars "${_sitedir}/site/${p}.include" _${p})"
  256. [ "${_realpagelang}" != "${DEFAULTLANG}" ] && \
  257. [ -f "$_sitedir/site/${p}.${_realpagelang}.include" ] && \
  258. eval "_$p=\"$_sitedir/site/$p.$_realpagelang.include\""
  259. done
  260. for y in "${_file}.md" "$_footer" "$_nav" "$_template"; do
  261. [ -z "${y}" ] && continue
  262. filehasnotchanged "${y}" || _needchange="y"
  263. done
  264. if [ "${linkname}" = "roll" ] || [ "${tmproll}" = "y" ]; then
  265. _needchange="y"
  266. fi
  267. [ "${_needchange}" = "n" ] && check_path f "${_file}.html" && return 0
  268. # The news and sitemap function need to know this
  269. # because they will only be called if this variable is set to y
  270. sitechanged="y"
  271. _tmpfile=$(mktemp -t untitled_www.XXXXXXXXXX)
  272. eval "$(setvars "" _date _author)"
  273. # This code sucks. TODO: do it better
  274. _firstchar=$(head -c 1 "${1}")
  275. _firstthreechars=$(head -c 3 "${1}")
  276. if [ "${_firstchar}" = "#" ]; then
  277. filetitle="$(mktitle "${1}")"
  278. _tmp2=$(mktemp -t untitled_www.XXXXXXXXXX)
  279. head -n1 "title.example" >"$_tmp2"
  280. printf "title: %s\n" "${filetitle}" >> "${_tmp2}"
  281. tail -n-2 "title.example" >> "$_tmp2"
  282. _tmptitle="$(cat "${_tmp2}")"
  283. rm -f "${_tmp2}"
  284. _pagetext="$(tail -n+2 "${1}")"
  285. elif [ "${_firstchar}" = "%" ]; then
  286. _tmp2=$(mktemp -t untitled_www.XXXXXXXXXX)
  287. _tmptitle="$(head -n3 "${1}")"
  288. printf "%s\n" "${_tmptitle}" > "$_tmp2"
  289. chunk="$(head -n1 "$_tmp2")"
  290. chunk="$(tail -n+2 "$_tmp2")"
  291. printf "%s\n" "${chunk}" > "$_tmp2"
  292. _author="$(head -n1 "$_tmp2")"
  293. chunk="$(tail -n+2 "$_tmp2")"
  294. printf "%s\n" "${chunk}" > "$_tmp2"
  295. _date="$(head -n1 "$_tmp2")"
  296. rm -f "$_tmp2"
  297. _pagetext="$(tail -n+4 "${1}")"
  298. elif [ "${_firstthreechars}" = "---" ]; then
  299. _tmptitle="$(head -n4 "${1}")"
  300. eval "$(setvars "" _author _date)"
  301. _pagetext="$(tail -n+5 "${1}")"
  302. else
  303. printf "WARNING: no title. defaulting to first line.\n" 1>&2
  304. filetitle="$(mktitle "${1}")"
  305. _tmp2=$(mktemp -t untitled_www.XXXXXXXXXX)
  306. head -n1 "title.example" > "$_tmp2"
  307. printf "title: %s\n" "${filetitle}" >> "${_tmp2}"
  308. tail -n-2 "title.example" >> "$_tmp2"
  309. _tmptitle="$(cat "${_tmp2}")"
  310. rm -f "${_tmp2}"
  311. _pagetext="$(tail -n+2 "${1}")"
  312. fi
  313. printf "%s\n" "${_tmptitle}" > "$_tmpfile"
  314. if [ -n "${_nav}" ]; then
  315. printf "\n<div class='nav'>\n%s\n</div>\n" \
  316. "$(cat "${_nav}")" >> "${_tmpfile}"
  317. fi
  318. # insert the language select menu
  319. _langmenu=""
  320. _defaultpage="${_file##*/}"
  321. _defaultpage="${_defaultpage%.*}"
  322. if [ -n "${_defaultpage}" ]; then
  323. for y in "${_file%/*}/${_defaultpage}".*.md; do
  324. [ ! -f "${y}" ] && continue
  325. _langname=$(getlangname "${y}" "${DEFAULTLANG}")
  326. _langmenu="${_langmenu} | [${_langname}](${y##*/})"
  327. done
  328. fi
  329. if [ "${_langmenu}" != "" ]; then
  330. _langmenu="${_langmenu# | }"
  331. if [ -f "${_file%/*}/${_defaultpage}.md" ]; then
  332. _langname=$(getlangname \
  333. "${_file%/*}/${_defaultpage}.md" "${DEFAULTLANG}")
  334. if [ "${_defaultpage}" = "index" ]; then
  335. _langmenu="[${_langname}](./) | ${_langmenu}"
  336. else
  337. _langmenu="[$_langname]($_defaultpage.md) | $_langmenu"
  338. fi
  339. fi
  340. fi
  341. # TODO: display "select language" text
  342. [ -n "${_langmenu}" ] && \
  343. printf "\n%s\n" "${_langmenu}" >> "${_tmpfile}"
  344. [ -n "${_backlink}" ] && \
  345. printf "\n%s\n" "${_backlink}" >> "${_tmpfile}"
  346. # the directory was already checked. no need to
  347. # check the validity of it, because it's part
  348. # of untitled, not part of a given site, and
  349. # what goes in untitled is reviewed thoroughly
  350. if [ -n "${_author}" ] && [ -n "${_date}" ]; then
  351. _pubname="$(getConfigValue "${_strconf}" "PUBLISHED_BY")"
  352. printf "\n%s %s\n" "$_pubname" "${_author#% }" >> "$_tmpfile"
  353. _pubdate="$(getConfigValue "${_strconf}" "PUBLICATION_DATE")"
  354. printf "\n%s %s\n" "${_pubdate}" "${_date#% }" >> "${_tmpfile}"
  355. fi
  356. printf "\n%s\n" "${_pagetext}" >> "$_tmpfile"
  357. if [ -n "${_footer}" ]; then
  358. printf "\n<div id='footer'>\n%s\n</div>\n" \
  359. "$(cat "${_footer}")" >> "${_tmpfile}"
  360. fi
  361. eval "$(getConfigValues "${_strconf}" MARKDOWN_LINK RSS_LINK \
  362. SITEMAP_LINK SHAMELESS_PLUG PDIR)"
  363. printf "\n%s <%s%s.md>\n" "${MARKDOWN_LINK}" "${DOMAIN}" \
  364. "${_uri##/}" >> "${_tmpfile}"
  365. find -L "$_sitedir/site/" -type f -name "MANIFEST" | grep -q MANIFEST \
  366. && printf "\n%s\n" "${RSS_LINK}" >> "${_tmpfile}"
  367. check_path f "${_sitedir}/site/sitemap.md" && \
  368. printf "\n%s\n" "${SITEMAP_LINK}" >> "${_tmpfile}"
  369. # TODO: make this configurable to enable/disable in site.cfg
  370. printf "\n%s\n" "${SHAMELESS_PLUG}" >> "${_tmpfile}"
  371. # change out .md -> .html
  372. # but not for external links
  373. sed -e '/\/\//!{s/\.md\(#[a-zA-Z0-9_-]*\)\?\([])]*\)/.html\1\2/g}' \
  374. "${_tmpfile}" > "${_final}.md"
  375. # toc is always enabled on news pages
  376. _toc=$(grep -q "^x-toc-enable: true$" "$_tmpfile" && \
  377. printf '%s\n' "--toc --toc-depth=4") || _toc=""
  378. # TODO: make toc depth configurable in site.cfg
  379. [ -f "${_file%/*}/MANIFEST" ] && \
  380. _toc="--toc --toc-depth=4"
  381. printf "Generating '%s.html'\n" "${_file}"
  382. _html_link="${MARKDOWN_LINK%.md}"
  383. _html_link="${DOMAIN}${_uri##/}"
  384. if [ "${_html_link##*/}" = "index" ]; then
  385. _html_link="${_html_link%/*}/"
  386. else
  387. _html_link="${_html_link}.html"
  388. fi
  389. pandoc -V lang="$_realpagelang" -V dir="$PDIR" $_toc -f markdown+smart \
  390. -t html "${_final}.md" --preserve-tabs --tab-stop 8 -T "$TITLE" \
  391. -V antisocialurl="$_html_link" -s --css "$CSS" --css "$_css" \
  392. --template "$_template" --metadata return="" > "${_final}.html"
  393. # generate section title anchors as [link]
  394. sed -e 's_^<h\([123]\) id="\(.*\)">\(.*\)</h\1>_<div class="h"><h\1 id="\2">\3</h\1><a aria-hidden="true" href="#\2">[link]</a></div>_' \
  395. "${_final}.html" > "${_final}.md"
  396. mv "${_final}.md" "${_final}.html"
  397. # LAZY images, if enabled
  398. if [ "${LAZY}" = "y" ]; then
  399. for y in img iframe; do
  400. sed -e "s/<${y}/<${y}\ loading=\"lazy\"/g" \
  401. "${_final}.html" > "${_final}.md"
  402. mv "${_final}.md" "${_final}.html"
  403. done
  404. fi
  405. mv "${_final}.html" "${_file}.html"
  406. rm -f "$_tmpfile"
  407. }
  408. main "$@"