build 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948
  1. #!/usr/bin/env bash
  2. #
  3. # Copyright (C) 2017,2021 Leah Rowe <info@minifree.org>
  4. # Copyright (C) 2017 Alyssa Rosenzweig <alyssa@rosenzweig.io>
  5. # Copyright (C) 2017 Michael Reed <michael@michaelreed.io>
  6. #
  7. # This program is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. [ "x${DEBUG+set}" = 'xset' ] && set -v
  20. set -u -e
  21. # TODO: speed optimization: use ***GIT*** to detect site changes.
  22. # init .git in www/ but already have a .gitignore (part of
  23. # untitled) in there, which only allows .md files. that way,
  24. # git add -A . will always, always only add .md files. then
  25. # git-whatchanged can be used, always, to just detect changes
  26. # one thing:
  27. # it is not possible, using this method, to detect all changes.
  28. # for example, .include files (and their per-page equivalents)
  29. # so on some files, modification dates will still be used
  30. # TODO: support MANIFEST.xx for translated news indexes. Individual news pages
  31. # are still supported, without this change
  32. # TODO: make site change detection more robust. certain things are missed
  33. # AUDIT NEEDED
  34. # TODO: make untitled work on subdirectories. e.g. https://website.com/~user/
  35. # TODO: gemtext support
  36. # TODO: write an optimized C replacement for pandoc markdown->html conversion
  37. # pandoc is written is haskell, and C will probably be faster
  38. # TODO: ensure that /tmp is a tmpfs, and recommend this in the documentation
  39. # TODO: in mkhtml, allow auto-nav if no .nav file specified
  40. # but disable this by default, to maintain old behaviour
  41. # make it configurable in site.cfg
  42. # TODO: next/previous links on news pages
  43. # TODO: allow per-directory .include overrides, including for translations
  44. # right now, only global ones and per-page ones are supported
  45. SITECHANGED="n"
  46. # usage: title file
  47. title() {
  48. firstchar=$(head -c 1 ${1})
  49. firstthreechars=$(head -c 3 ${1})
  50. outputstring=""
  51. if [ "${firstchar}" = "%" ] || [ "${firstchar}" = "#" ]; then
  52. outputstring="$(sed -n 1p "${1}" | sed -e s-^..--)"
  53. elif [ "${firstthreechars}" = "---" ]; then
  54. outputstring="$(sed -n 2p "${1}" | sed -e s-^..--)"
  55. outputstring="${outputstring#* }"
  56. else
  57. outputstring="$(head -n1 "${1}")"
  58. fi
  59. printf "%s\n" "${outputstring}"
  60. return 0
  61. }
  62. # usage: meta file
  63. meta() {
  64. file="${1}"
  65. SITEDIR="${2}"
  66. BLOGDIR="${3}"
  67. htmlfile=$(printf '%s\n' "${file%.md}.html")
  68. printf '%s\n' \
  69. "[$(title "${SITEDIR}/site/${BLOGDIR}${file}")](/${BLOGDIR}${file}){.title}"
  70. printf '%s\n' \
  71. "[$(sed -n 3p "${SITEDIR}/site/${BLOGDIR}${file}" | sed -e s-^..--)]{.date}"
  72. # printf '\n'
  73. # tail -n +5 "${SITEDIR}/news/${file}" | perl -p0e 's/(\.|\?|\!)( |\n)(.|\n)*/.../g'
  74. # todo: make this configurable via enable/disable, and make it less cursed
  75. printf '\n'
  76. printf '\n'
  77. }
  78. # usage: rss_header
  79. rss_header() {
  80. BLOGTITLE="${1}"
  81. DOMAIN="${2}" # without a / at the end, but with http:// or https://
  82. BLOGDESCRIPTION="${3}"
  83. BLOGDIR="${4}"
  84. printf '%s\n' '<rss version="2.0">'
  85. printf '%s\n' '<channel>'
  86. printf '%s\n' "<title>${BLOGTITLE}</title>"
  87. printf '%s\n' "<link>${DOMAIN}${BLOGDIR}</link>"
  88. printf '%s\n' "<description>$BLOGDESCRIPTION</description>"
  89. }
  90. # usage: rss_main file
  91. rss_main() {
  92. file="${1}"
  93. SITEDIR="${2}"
  94. DOMAIN="${3}"
  95. BLOGDIR="${4}"
  96. # render content and escape
  97. htmlfile="${file%.md}.html"
  98. PAGETITLE=$(title "${SITEDIR}/site/${BLOGDIR}${file}")
  99. PAGEURL="${DOMAIN}${BLOGDIR}${htmlfile}"
  100. printf '%s\n' '<item>'
  101. printf '%s\n' "<title>${PAGETITLE}</title>"
  102. printf '%s\n' "<link>${PAGEURL}</link>"
  103. printf '%s\n' \
  104. "<description><p>Article: ${PAGETITLE}</p><p>Web link: ${PAGEURL}</p></description>"
  105. printf '%s\n' '</item>'
  106. }
  107. # usage: rss_footer
  108. rss_footer() {
  109. printf '%s\n' '</channel>'
  110. printf '%s\n' '</rss>'
  111. }
  112. # uses the last modified date
  113. # returns 1 if the file has changed
  114. filehasnotchanged() {
  115. # return 1 means yes, needs to be changed
  116. # 0 means no, doesn't need to be changed
  117. f="${1}"
  118. if [ ! -f "${f}" ]; then
  119. return 0
  120. fi
  121. if [ -d "${f}.date" ]; then
  122. return 1
  123. fi
  124. if [ ! -f "${f}.date" ]; then
  125. date -r "${f}" +%s > "${f}.date"
  126. return 1
  127. fi
  128. moddate=$(date -r "${f}" +%s)
  129. if [ "$(cat "${f}.date")" = "${moddate}" ]; then
  130. return 0
  131. fi
  132. printf "%s\n" "${moddate}" > "${f}.date"
  133. return 1
  134. }
  135. getlangname() {
  136. FILE="${1%.md}" # path to markdown file
  137. DEFAULTLANG="${2}"
  138. REALPAGE="${FILE##*/}"
  139. PAGELANG="${REALPAGE##*.}"
  140. if [ "${PAGELANG}" = "${REALPAGE}" ]; then
  141. PAGELANG="${DEFAULTLANG}"
  142. fi
  143. STRINGSCFGPATH="lang/${PAGELANG}/strings.cfg"
  144. if [ ! -f "${STRINGSCFGPATH}" ]; then
  145. printf "%s\n" "${PAGELANG}"
  146. return 0
  147. fi
  148. LANGNAME="$(getConfigValue "${STRINGSCFGPATH}" "LANGNAME")"
  149. if [ "${LANGNAME}" != "" ]; then
  150. printf "%s\n" "${LANGNAME}"
  151. return 0
  152. fi
  153. printf "%s\n" "${PAGELANG}"
  154. return 0
  155. }
  156. # convert pandoc-markdown file into html, using a template file
  157. mkhtml() {
  158. FILE=${1%.md}
  159. SITEDIR="www/${2}"
  160. TITLE="${3}"
  161. CSS="${4}"
  162. DEFAULTLANG="${5}"
  163. # will be set to y if page changes are detected
  164. NEEDCHANGE="n"
  165. if [ -d "${FILE}.html" ]; then
  166. printf "%s: %s.html is a DIRECTORY. skipping %s.md\n" \
  167. "${SITEDIR}" "${FILE}" "${FILE}"
  168. return 0
  169. fi
  170. # e.g. file.md is default(english) and file.ru.md is russian
  171. REALPAGE="${FILE##*/}"
  172. PAGELANGUAGE="${REALPAGE##*.}"
  173. if [ "${PAGELANGUAGE}" = "${REALPAGE}" ]; then
  174. PAGELANGUAGE="${DEFAULTLANG}"
  175. else
  176. FILE="${FILE%.${PAGELANGUAGE}}"
  177. fi
  178. STRINGSCFGPATH="lang/${PAGELANGUAGE}/strings.cfg"
  179. if [ ! -f "${STRINGSCFGPATH}" ]; then
  180. STRINGSCFGPATH="lang/${DEFAULTLANG}/strings.cfg"
  181. fi
  182. if [ ! -f "${STRINGSCFGPATH}" ]; then
  183. STRINGSCFGPATH="lang/en/strings.cfg"
  184. fi
  185. # This is the URI but without any extension, *and without language
  186. # extension*. This will be used extensively, especially for backlinks
  187. URI="${FILE##${SITEDIR}/site}" # without file extension e.g. .html
  188. # Allow a given page to override the default footer
  189. FOOTERFILE=""
  190. if [ -f "${FILE}.footer" ]; then
  191. FOOTERFILE="${FILE}.footer"
  192. fi
  193. if [ "${PAGELANGUAGE}" != "${DEFAULTLANG}" ]; then
  194. if [ -f "${FILE}.${PAGELANGUAGE}.footer" ]; then
  195. FOOTERFILE="${FILE}.${PAGELANGUAGE}.footer"
  196. fi
  197. fi
  198. # Backlink text for each page. This helps with site navigation
  199. BACKLINK=""
  200. if [ "${URI}" != "/index" ]; then # the home page doesn't need a backlink
  201. if [ "${URI##*/}" = "index" ]; then
  202. BACKLINK="$(getConfigValue "${STRINGSCFGPATH}" "BACKLINK_PREVDIR")"
  203. else
  204. BACKLINK="$(getConfigValue "${STRINGSCFGPATH}" "BACKLINK_CURRENTDIR")"
  205. fi
  206. fi
  207. # Allow a given page to override the default navigation menu
  208. NAVFILE=""
  209. if [ -f "${FILE}.nav" ]; then
  210. NAVFILE="${FILE}.nav"
  211. fi
  212. if [ "${PAGELANGUAGE}" != "${DEFAULTLANG}" ]; then
  213. if [ -f "${FILE}.${PAGELANGUAGE}.nav" ]; then
  214. NAVFILE="${FILE}.${PAGELANGUAGE}.nav"
  215. fi
  216. fi
  217. # Allow a given page to override the default template
  218. TEMPLATE=""
  219. if [ -f "${FILE}.template" ]; then
  220. TEMPLATE="${FILE}.template"
  221. fi
  222. if [ "${PAGELANGUAGE}" != "${DEFAULTLANG}" ]; then
  223. if [ -f "${FILE}.${PAGELANGUAGE}.template" ]; then
  224. TEMPLATE="${FILE}.${PAGELANGUAGE}.template"
  225. fi
  226. fi
  227. # Allow a given page to override the default CSS
  228. CSSOVERRIDE=""
  229. if [ -f "${FILE}.css" ]; then
  230. CSSOVERRIDE="--css ${URI}.css"
  231. fi
  232. # DO NOT override the main override! add it instead
  233. if [ "${PAGELANGUAGE}" != "${DEFAULTLANG}" ]; then
  234. if [ -f "${FILE}.${PAGELANGUAGE}.css" ]; then
  235. if [ "${CSSOVERRIDE}" != "" ]; then
  236. CSSOVERRIDE="${CSSOVERRIDE} --css ${URI}.${PAGELANGUAGE}.css"
  237. else
  238. CSSOVERRIDE="--css ${URI}.${PAGELANGUAGE}.css"
  239. fi
  240. fi
  241. fi
  242. if [ "${PAGELANGUAGE}" != "${DEFAULTLANG}" ]; then
  243. FILE="${FILE}.${PAGELANGUAGE}"
  244. URI="${URI}.${PAGELANGUAGE}"
  245. fi
  246. for f in "${FILE}.md" "${FOOTERFILE}" "${NAVFILE}" "${TEMPLATE}"; do
  247. if [ "${f}" = "" ]; then continue; fi
  248. filehasnotchanged "${f}" || NEEDCHANGE="y"
  249. done
  250. if [ "${NEEDCHANGE}" = "n" ] && [ -f "${FILE}.html" ]; then
  251. return 0
  252. fi
  253. if [ "${FOOTERFILE}" = "" ]; then
  254. if [ -f "${SITEDIR}/site/footer.include" ]; then
  255. FOOTERFILE="${SITEDIR}/site/footer.include"
  256. fi
  257. if [ "${PAGELANGUAGE}" != "${DEFAULTLANG}" ]; then
  258. if [ -f "${SITEDIR}/site/footer.${PAGELANGUAGE}.include" ]; then
  259. FOOTERFILE="${SITEDIR}/site/footer.${PAGELANGUAGE}.include"
  260. fi
  261. fi
  262. fi
  263. if [ "${NAVFILE}" = "" ]; then
  264. if [ -f "${SITEDIR}/site/nav.include" ]; then
  265. NAVFILE="${SITEDIR}/site/nav.include"
  266. fi
  267. if [ "${PAGELANGUAGE}" != "${DEFAULTLANG}" ]; then
  268. if [ -f "${SITEDIR}/site/nav.${PAGELANGUAGE}.include" ]; then
  269. NAVFILE="${SITEDIR}/site/nav.${PAGELANGUAGE}.include"
  270. fi
  271. fi
  272. fi
  273. if [ "${TEMPLATE}" = "" ]; then
  274. if [ -f "${SITEDIR}/site/template.include" ]; then
  275. TEMPLATE="${SITEDIR}/site/template.include"
  276. fi
  277. if [ "${PAGELANGUAGE}" != "${DEFAULTLANG}" ]; then
  278. if [ -f "${SITEDIR}/site/template.${PAGELANGUAGE}.include" ]; then
  279. TEMPLATE="${SITEDIR}/site/template.${PAGELANGUAGE}.include"
  280. fi
  281. fi
  282. fi
  283. # The news and sitemap function need to know this
  284. # because they will only be called if this variable is set to y
  285. SITECHANGED="y"
  286. TMPFILE=$(mktemp -t untitled_www.XXXXXXXXXX)
  287. date=""
  288. author=""
  289. # title="Webpage" # dirty hack
  290. firstchar=$(head -c 1 ${1})
  291. firstthreechars=$(head -c 3 ${1})
  292. if [ "${firstchar}" = "#" ]; then
  293. filetitle="$(title "${1}")"
  294. TMPFILE2=$(mktemp -t untitled_www.XXXXXXXXXX)
  295. head -n1 title.example > "${TMPFILE2}"
  296. printf "title: %s\n" "${filetitle}" >> "${TMPFILE2}"
  297. tail -n-2 title.example >> "${TMPFILE2}"
  298. title="$(cat "${TMPFILE2}")"
  299. rm -f "${TMPFILE2}"
  300. pagetext="$(tail -n+2 "${1}")"
  301. elif [ "${firstchar}" = "%" ]; then
  302. TMPFILE2=$(mktemp -t untitled_www.XXXXXXXXXX)
  303. title="$(head -n3 ${1})"
  304. printf "%s\n" "${title}" > $TMPFILE2
  305. chunk="$(head -n1 $TMPFILE2)"
  306. chunk="$(tail -n+2 $TMPFILE2)"
  307. printf "%s\n" "${chunk}" > $TMPFILE2
  308. author="$(head -n1 $TMPFILE2)"
  309. chunk="$(tail -n+2 $TMPFILE2)"
  310. printf "%s\n" "${chunk}" > $TMPFILE2
  311. date="$(head -n1 $TMPFILE2)"
  312. rm -f "$TMPFILE2"
  313. pagetext="$(tail -n+4 ${1})"
  314. elif [ "${firstthreechars}" = "---" ]; then
  315. title="$(head -n4 ${1})"
  316. author=""
  317. date=""
  318. pagetext="$(tail -n+5 ${1})"
  319. else
  320. printf "WARNING: no title detected. defaulting to first line as title\n"
  321. filetitle="$(title "${1}")"
  322. TMPFILE2=$(mktemp -t untitled_www.XXXXXXXXXX)
  323. head -n1 title.example > "${TMPFILE2}"
  324. printf "title: %s\n" "${filetitle}" >> "${TMPFILE2}"
  325. tail -n-2 title.example >> "${TMPFILE2}"
  326. title="$(cat "${TMPFILE2}")"
  327. rm -f "${TMPFILE2}"
  328. pagetext="$(tail -n+2 "${1}")"
  329. fi
  330. printf "%s\n" "${title}" > "$TMPFILE"
  331. if [ "${NAVFILE}" != "" ]; then
  332. NAVFILECONTENTS="$(cat "${NAVFILE}")"
  333. printf "\n<div class='nav'>\n%s\n</div>\n" \
  334. "${NAVFILECONTENTS}" >> "${TMPFILE}"
  335. fi
  336. # insert the language select menu
  337. LANGMENU=""
  338. DEFAULTPAGE="${FILE##*/}"
  339. DEFAULTPAGE="${DEFAULTPAGE%.*}"
  340. if [ "${DEFAULTPAGE}" != "" ]; then
  341. for page in "${FILE%/*}/${DEFAULTPAGE}".*.md; do
  342. if [ ! -f "${page}" ]; then continue; fi
  343. LANGNAME=$(getlangname "${page}" "${DEFAULTLANG}")
  344. LANGMENU="${LANGMENU} | [${LANGNAME}](${page##*/})"
  345. done
  346. fi
  347. if [ "${LANGMENU}" != "" ]; then
  348. LANGMENU="${LANGMENU# | }"
  349. if [ -f "${FILE%/*}/${DEFAULTPAGE}.md" ]; then
  350. LANGNAME=$(getlangname "${FILE%/*}/${DEFAULTPAGE}.md" "${DEFAULTLANG}")
  351. if [ "${DEFAULTPAGE}" = "index" ]; then
  352. LANGMENU="[${LANGNAME}](./) | ${LANGMENU}"
  353. else
  354. LANGMENU="[${LANGNAME}](${DEFAULTPAGE}.md) | ${LANGMENU}"
  355. fi
  356. fi
  357. fi
  358. if [ "${LANGMENU}" != "" ]; then
  359. # TODO: display "select language" text
  360. printf "\n%s\n" "${LANGMENU}" >> "${TMPFILE}"
  361. fi
  362. if [ "${BACKLINK}" != "" ]; then
  363. printf "\n%s\n" "${BACKLINK}" >> "${TMPFILE}"
  364. fi
  365. # the directory was already checked. no need to
  366. # check the validity of it, because it's part
  367. # of untitled, not part of a given site, and
  368. # what goes in untitled is reviewed thoroughly
  369. if [ "${author}" != "" ] && [ "${date}" != "" ]; then
  370. PUBLISHED_BY="$(getConfigValue "${STRINGSCFGPATH}" "PUBLISHED_BY")"
  371. printf "\n%s %s\n" "${PUBLISHED_BY}" "${author#% }" >> "${TMPFILE}"
  372. PUBLICATION_DATE="$(getConfigValue "${STRINGSCFGPATH}" "PUBLICATION_DATE")"
  373. printf "\n%s %s\n" "${PUBLICATION_DATE}" "${date#% }" >> "${TMPFILE}"
  374. fi
  375. printf "\n%s\n" "${pagetext}" >> "$TMPFILE"
  376. if [ "${FOOTERFILE}" != "" ]; then
  377. FOOTERFILECONTENTS="$(cat "${FOOTERFILE}")"
  378. printf "\n<div id='footer'>\n%s\n</div>\n" \
  379. "${FOOTERFILECONTENTS}" >> "${TMPFILE}"
  380. fi
  381. MARKDOWN_LINK="$(getConfigValue "${STRINGSCFGPATH}" "MARKDOWN_LINK")"
  382. printf "\n%s <%s%s.md>\n" "${MARKDOWN_LINK}" "${DOMAIN}" \
  383. "${URI##/}" >> "${TMPFILE}"
  384. RSS_LINK="$(getConfigValue "${STRINGSCFGPATH}" "RSS_LINK")"
  385. if [ -f "${SITEDIR}/site/feed.xml" ]; then
  386. printf "\n%s\n" "${RSS_LINK}" >> "${TMPFILE}"
  387. fi
  388. SITEMAP_LINK="$(getConfigValue "${STRINGSCFGPATH}" "SITEMAP_LINK")"
  389. if [ -f "${SITEDIR}/site/sitemap.md" ]; then
  390. printf "\n%s\n" "${SITEMAP_LINK}" >> "${TMPFILE}"
  391. fi
  392. # TODO: make this configurable to enable/disable in site.cfg
  393. SHAMELESS_PLUG="$(getConfigValue "${STRINGSCFGPATH}" "SHAMELESS_PLUG")"
  394. printf "\n%s\n" "${SHAMELESS_PLUG}" >> "${TMPFILE}"
  395. # change out .md -> .html
  396. # but not for external links
  397. sed -i -e \
  398. '/\/\//!{s/\.md\(#[a-zA-Z0-9_-]*\)\?\([])]*\)/.html\1\2/g}' \
  399. "$TMPFILE"
  400. # TOC is always enabled on news pages
  401. TOC=$(grep -q "^x-toc-enable: true$" "$TMPFILE" && \
  402. printf '%s\n' "--toc --toc-depth=4") || TOC=""
  403. # TODO: make toc depth configurable in site.cfg
  404. if [ -f "${FILE%/*}/MANIFEST" ]; then
  405. TOC="--toc --toc-depth=4"
  406. fi
  407. # work around heterogenous pandoc versions
  408. SMART=$(pandoc -v | grep -q '2\.0' || \
  409. printf '%s\n' "-f markdown+smart -t html") || SMART=""
  410. PDIR="$(getConfigValue "${STRINGSCFGPATH}" "PDIR")"
  411. printf "Generating '%s.html'\n" "${FILE}"
  412. HTMLLINK="${MARKDOWN_LINK%.md}"
  413. HTMLLINK="${DOMAIN}${URI##/}"
  414. if [ "${HTMLLINK##*/}" = "index" ]; then
  415. HTMLLINK="${HTMLLINK%/*}/"
  416. else
  417. HTMLLINK="${HTMLLINK}.html"
  418. fi
  419. # chuck through pandoc
  420. #
  421. # $CSS must not be quoted, otherwise pandoc interprets '--css /headercenter.css'
  422. # as one argument, when it is actually two.
  423. # Same for $TITLE
  424. pandoc -V lang=${PAGELANGUAGE} -V dir=${PDIR} $TOC $SMART "$TMPFILE" \
  425. -V antisocialurl="${HTMLLINK}" -s ${CSS} ${CSSOVERRIDE} ${TITLE} \
  426. --template ${TEMPLATE} --metadata return="" > "$FILE.html"
  427. # generate section title anchors as [link]
  428. sed -i \
  429. -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>_' \
  430. "$FILE.html"
  431. # clean up temporary file
  432. rm -f "$TMPFILE"
  433. }
  434. getConfigValue() {
  435. CONFIGFILE="${1}" # e.g. www/foo/site.cfg
  436. ITEM="${2}" # e.g. TITLE
  437. TMPCFG=$(mktemp -t untitled_www.XXXXXXXXXX)
  438. grep "${ITEM}" "${CONFIGFILE}" > "${TMPCFG}"
  439. VALUE="$(head -n1 $TMPCFG)"
  440. VALUE="${VALUE##*$ITEM=\"}"
  441. VALUE="${VALUE%\"*}"
  442. printf "%s\n" "${VALUE}"
  443. rm -f "${TMPCFG}"
  444. }
  445. # ensure that it's just a file name, no directories specified
  446. # if the path includes directories, it just means that this will be mangled.
  447. # code that uses this function is: mostly the mknews function
  448. # should be safe
  449. sanitizefilename() {
  450. page="${1##*/}"
  451. page="${page%/}" # avoid refering to directory
  452. page="$(printf "%s\n" "${1}" | sed -e 's/\ //g')"
  453. page="$(printf "%s\n" "${1}" | sed -e 's/\.\.//g')"
  454. printf "${page}\n"
  455. }
  456. mknews() {
  457. SITEDIR="${1}"
  458. SITENAME="${2}"
  459. BLOGDIR="${3}"
  460. DOMAIN="${4}"
  461. TITLE="${5}"
  462. CSS="${6}"
  463. DEFAULTLANG="${7}"
  464. #if [ -f "${SITEDIR}/site/${BLOGDIR}MANIFEST" ]; then
  465. for MANIFEST in $(find -L "${SITEDIR}/site/" -type f -name "MANIFEST"); do
  466. if [ ! -f "${MANIFEST}" ]; then continue; fi
  467. BLOGTITLE="News"
  468. BLOGDESCRIPTION="News"
  469. MANIFESTDIR="${MANIFEST##${SITEDIR}/site/}"
  470. MANIFESTDIR="${MANIFESTDIR%MANIFEST}"
  471. if [ -f "${SITEDIR}/site/${MANIFESTDIR}news.cfg" ]; then
  472. BLOGTITLE="$(getConfigValue "${SITEDIR}/site/${MANIFESTDIR}news.cfg" "BLOGTITLE")"
  473. BLOGDESCRIPTION="$(getConfigValue "${SITEDIR}/site/${MANIFESTDIR}news.cfg" "BLOGDESCRIPTION")"
  474. if [ "${BLOGTITLE}" = "" ]; then BLOGTITLE="News"; fi
  475. if [ "${BLOGDESCRIPTION}" = "" ]; then BLOGDESCRIPTION="News"; fi
  476. fi
  477. # Dermine the order of news articles in news/index.md and news/feed.xml
  478. FILES="$(cat "${MANIFEST}")"
  479. TMPFILE=$(mktemp -t untitled_www.XXXXXXXXXX)
  480. printf "\n" > "${TMPFILE}"
  481. for f in ${FILES}; do
  482. page="$(sanitizefilename "${f}")"
  483. if [ ! -f "${SITEDIR}/site/${MANIFESTDIR}${page}" ]; then
  484. printf "build-html: news file '%s' does not exist in site '%s' for news section '%s'. Skipping this news section\n" \
  485. "${page}" "${SITENAME}" "${MANIFESTDIR}" \
  486. >> "${TMPFILE}"
  487. fi
  488. if [ -L "${SITEDIR}/site/${MANIFESTDIR}${page}" ]; then
  489. printf "build-html: news file '%s' is a symlink in news section '%s' for site '%s'. Skipping this news section.\n" \
  490. "${page}" "${MANIFESTDIR}" "${SITENAME}" \
  491. >> "${TMPFILE}"
  492. fi
  493. if [ "$(readlink -f "$(pwd)/${SITEDIR}/site/${MANIFESTDIR}${page}" 2>&1)" \
  494. != "$(pwd)/${SITEDIR}/site/${MANIFESTDIR}${page}" ]; then
  495. printf "build-html: news file '%s' different when running readlink -f. skipping this news section\n" \
  496. "${page}" \
  497. >> "${TMPFILE}"
  498. fi
  499. done
  500. if [ "$(cat "${TMPFILE}")" != "" ]; then
  501. cat "${TMPFILE}"
  502. rm -f "${TMPFILE}"
  503. continue
  504. fi
  505. rm -f "${TMPFILE}"
  506. # generate the index file
  507. if [ -f "${SITEDIR}/site/${MANIFESTDIR}news-list.md.include" ]; then
  508. cat "${SITEDIR}/site/${MANIFESTDIR}news-list.md.include" \
  509. > "${SITEDIR}/site/${MANIFESTDIR}index.md"
  510. else
  511. printf "MANIFEST present for %s in %s but no news-list.md.include present with that MANIFEST. Skipping this news section.\n" \
  512. "${SITEDIR}" "${MANIFESTDIR}"
  513. continue
  514. fi
  515. if [ "${MANIFESTDIR%/}" = "${BLOGDIR%/}" ]; then
  516. printf "\nSubscribe to RSS: [/feed.xml](/feed.xml)\n\n" \
  517. >> "${SITEDIR}/site/${MANIFESTDIR}index.md"
  518. else
  519. printf "\nSubscribe to RSS: [/%sfeed.xml](/%sfeed.xml)\n\n" \
  520. "${MANIFESTDIR}" "${MANIFESTDIR}" \
  521. >> "${SITEDIR}/site/${MANIFESTDIR}index.md"
  522. fi
  523. for f in $FILES
  524. do
  525. page="$(sanitizefilename "${f}")"
  526. meta "${page}" "${SITEDIR}" "${MANIFESTDIR}" \
  527. >> "${SITEDIR}/site/${MANIFESTDIR}index.md"
  528. done
  529. # generate the RSS index
  530. rss_header "$BLOGTITLE" "$DOMAIN" "$BLOGDESCRIPTION" \
  531. "${MANIFESTDIR}" > "${SITEDIR}/site/${MANIFESTDIR}feed.xml"
  532. for f in $FILES
  533. do
  534. page="$(sanitizefilename "${f}")"
  535. rss_main "$page" "${SITEDIR}" "${DOMAIN}" "${MANIFESTDIR}" \
  536. >> "${SITEDIR}/site/${MANIFESTDIR}feed.xml"
  537. done
  538. rss_footer >> "${SITEDIR}/site/${MANIFESTDIR}feed.xml"
  539. if [ "${SITEDIR}/site/${MANIFESTDIR}feed.xml" \
  540. != "${SITEDIR}/site/feed.xml" ] \
  541. && [ "${MANIFESTDIR}" = "${BLOGDIR}" ]; then
  542. rm -f "${SITEDIR}/site/feed.xml"
  543. cp "${SITEDIR}/site/${MANIFESTDIR}feed.xml" \
  544. "${SITEDIR}/site/feed.xml"
  545. fi
  546. if [ -f "${SITEDIR}/site/${MANIFESTDIR}index.md" ]; then
  547. mkhtml "${SITEDIR}/site/${MANIFESTDIR}index.md" \
  548. "${SITEDIR##*/}" "${TITLE}" "${CSS}" \
  549. "${DEFAULTLANG}"
  550. fi
  551. done
  552. #fi
  553. }
  554. mksitemap() {
  555. SITEDIR="${1}"
  556. TITLE="${2}"
  557. CSS="${3}"
  558. DEFAULTLANG="${4}"
  559. if [ ! -f "${SITEDIR}/site/sitemap.include" ]; then return 0; fi
  560. # only generate a sitemap if one is specified
  561. TMPFILE=$(mktemp -t untitled_www.XXXXXXXXXX)
  562. cat "${SITEDIR}/site/sitemap.include" > "${TMPFILE}"
  563. printf "\n\n" >> "${TMPFILE}"
  564. printf "<div class='sitemap'>\n" >> "${TMPFILE}"
  565. for MDFILE in $(find -L "${SITEDIR}/site/" -type f -name "*.md"); do
  566. if [ ! -f "${MDFILE}" ]; then continue; fi
  567. if [ "${MDFILE}" = "${SITEDIR}/site/sitemap.md" ]; then continue; fi
  568. URI="${MDFILE##${SITEDIR}/site}"
  569. URI="${URI%index*}"
  570. printf "* %s: [%s](%s)\n" "${URI}" "$(title "${MDFILE}")" \
  571. "${URI}" \
  572. >> "${TMPFILE}"
  573. done
  574. printf "</div>\n\n" >> "${TMPFILE}"
  575. cp "${TMPFILE}" "${SITEDIR}/site/sitemap.md"
  576. rm -f "${TMPFILE}"
  577. if [ -f "${SITEDIR}/site/sitemap.md" ]; then
  578. mkhtml "${SITEDIR}/site/sitemap.md" \
  579. "${SITEDIR##*/}" "${TITLE}" "${CSS}" "${DEFAULTLANG}"
  580. fi
  581. }
  582. buildsite() {
  583. SITECHANGED="n"
  584. SITEDIR="${1}"
  585. printf "\nProcessing files in site: %s\n" "${SITEDIR##*/}"
  586. if [ ! -d "${SITEDIR}" ]; then return 0; fi
  587. if [ -f "${SITEDIR}" ]; then return 0; fi
  588. SITENAME="${SITEDIR}"
  589. # check if there are symlinks in the site directory
  590. if [ -L "${SITEDIR}" ]; then
  591. printf "%s site dir is a symlink. skipping this site\n" \
  592. "${SITEDIR}"
  593. return 0
  594. fi
  595. TMPFILE=$(mktemp -t untitled.XXXXXXXXXX)
  596. printf "\n" > "${TMPFILE}"
  597. for i in $(find -L "${SITEDIR}" ! -path "${SITEDIR}/.git"); do
  598. if [ -L "${i}" ]; then
  599. printf "%s\n" "${i}" 2>&1 >> "${TMPFILE}"
  600. fi
  601. done
  602. if [ "$(cat "${TMPFILE}")" != "" ]; then
  603. printf "ERROR: symbolic links present in the %s directory. skipping this site. List:\n" \
  604. "${SITEDIR}"
  605. cat "${TMPFILE}" | sort > "${TMPFILE}"
  606. rm -f "${TMPFILE}"
  607. return 0
  608. fi
  609. rm -f "${TMPFILE}"
  610. TEMPLATE="${SITEDIR}/site/template.include"
  611. # Check whether the required files are present.
  612. # ALSO: Clean the site if site-wide changes are detected
  613. # For example: if a template changed, purge the whole site and re-build
  614. FILELIST=$(mktemp -t untitled_www.XXXXXXXXXX)
  615. printf "%s\n" "${TEMPLATE}" > "${FILELIST}"
  616. printf "%s/site.cfg\n" "${SITEDIR}" >> "${FILELIST}"
  617. for f in $(cat "${FILELIST}"); do
  618. if [ ! -f "${f}" ]; then
  619. printf "%s missing for site '%s'. Skipping this site\n" \
  620. "${f}" "${SITEDIR}"
  621. rm -f "${FILELIST}"
  622. return 0
  623. fi
  624. done
  625. for f in "${SITEDIR}"/site/footer*.include "${SITEDIR}"/site/nav*.include; do
  626. if [ -f "${f}" ]; then
  627. printf "%s\n" "${f}" >> "${FILELIST}"
  628. fi
  629. done
  630. for f in $(cat "${FILELIST}"); do
  631. filehasnotchanged "${f}" || SITECHANGED="y"
  632. done
  633. rm -f "${FILELIST}"
  634. if [ "${SITECHANGED}" = "y" ]; then
  635. printf "Site-wide changes detected in '%s'. Cleaning now.\n" \
  636. "${SITEDIR}"
  637. ./clean "${SITENAME}"
  638. fi
  639. SITECHANGED="n"
  640. # TODO: allow per-page override of site.cfg
  641. TITLE="$(getConfigValue "${SITEDIR}/site.cfg" "TITLE")"
  642. if [ "${TITLE}" = "" ]; then TITLE="-T untitled_output"; fi
  643. CSS="$(getConfigValue "${SITEDIR}/site.cfg" "CSS")"
  644. # optional. if empty, there just won't be any css
  645. DOMAIN="$(getConfigValue "${SITEDIR}/site.cfg" "DOMAIN")"
  646. if [ "${DOMAIN}" = "" ]; then
  647. printf "%s/site.cfg does not specify DOMAIN. Exiting\n" "${SITEDIR}"
  648. return 1
  649. fi
  650. BLOGTITLE="$(getConfigValue "${SITEDIR}/site.cfg" "BLOGTITLE")"
  651. if [ "${BLOGTITLE}" = "" ]; then BLOGTITLE="News"; fi
  652. BLOGDESCRIPTION="$(getConfigValue "${SITEDIR}/site.cfg" "BLOGDESCRIPTION")"
  653. if [ "${BLOGDESCRIPTION}" = "" ]; then BLOGDESCRIPTION="News"; fi
  654. DEFAULTLANG="$(getConfigValue "${SITEDIR}/site.cfg" "DEFAULTLANG")"
  655. DEFAULTLANG="${DEFAULTLANG##*/}"
  656. if [ "${DEFAULTLANG}" = "" ]; then DEFAULTLANG="en"; fi
  657. if [ ! -d "lang/${DEFAULTLANG}" ]; then DEFAULTLANG="en"; fi
  658. BLOGDIR="$(getConfigValue "${SITEDIR}/site.cfg" "BLOGDIR")"
  659. # optional. if not set, this will be empty, and blog will be homepage
  660. TESTDIR="$(pwd)/${SITEDIR}/site/${BLOGDIR%/}"
  661. if [ "$(readlink -f "${TESTDIR}" 2>&1)" != "${TESTDIR%/}" ]; then
  662. printf "Invalid BLOGDIR in site.cfg for %s. skipping this site\n" "${SITEDIR}"
  663. return 0
  664. fi
  665. if [ -L "${TESTDIR}" ]; then
  666. printf "BLOGDIR in site.cfg for %s is a symlink. skipping the site\n" "${SITEDIR}"
  667. return 0
  668. fi
  669. if [ -f "${TESTDIR}" ]; then
  670. printf "BLOGDIR in site.cfg for %s is a file. skipping this site\n" "${SITEDIR}"
  671. return 0
  672. fi
  673. TESTDIR="${TESTDIR##$(pwd)/${SITEDIR}/site/}"
  674. for mdfile in $(find -L ${SITEDIR}/site/ -name '*.md'); do
  675. mkhtml "${mdfile}" "${SITEDIR##*/}" "${TITLE}" "${CSS}" \
  676. "${DEFAULTLANG}"
  677. done
  678. if [ "${SITECHANGED}" = "y" ]; then
  679. mksitemap "${SITEDIR}" "${TITLE}" "${CSS}" "${DEFAULTLANG}"
  680. if [ ! -d "${SITEDIR}/site/${TESTDIR}" ]; then
  681. printf "BLOGDIR does not exist in %s. not building news sections for this site\n" "${SITEDIR}"
  682. else
  683. mknews "${SITEDIR}" "${SITENAME}" "${BLOGDIR}" "${DOMAIN}" "${TITLE}" "${CSS}" "${DEFAULTLANG}"
  684. fi
  685. else
  686. printf "this website has not been changed. skipping.\n"
  687. fi
  688. }
  689. buildpage() {
  690. [[ $# -lt 1 ]] && return 0
  691. MDFILE="${1}"
  692. MDFILE="${MDFILE#/}"
  693. MDFILE="${MDFILE#./}"
  694. MDFILELOCAL="${MDFILE##*/}"
  695. [[ "${MDFILE%.md}" = "${MDFILE}" ]] && return 0
  696. # path translation
  697. # e.g. libreboot/index.md becomes www/libreboot/site/index.md
  698. # e.g. libreboot/site/index.md becomes www/libreboot/site/index.md
  699. # e.g. www/libreboot/index.md becomes www/libreboot/site/index.md
  700. # in all cases, the correct path is: www/libreboot/site/index.md
  701. MDFILE="${MDFILE#www/}"
  702. SITESITE="${MDFILE#*/}"
  703. [[ "${SITESITE#site/}" = "${SITESITE}" ]] && SITESITE="site/${SITESITE}"
  704. MDFILE="www/${MDFILE%%/*}/${SITESITE}"
  705. [[ ! -f "${MDFILE}" ]] && return 0
  706. [[ -L "${MDFILE}" ]] && return 0
  707. # Do not allow if the file is contained within a symlinked directory
  708. absolutepath="$(pwd)/${MDFILE}"
  709. absolutepath_readlink="$(readlink -f "${absolutepath}" 2>&1)"
  710. if [ "${absolutepath_readlink}" != "${absolutepath}" ]; then
  711. return 0
  712. fi
  713. SITEDIR="${MDFILE#www/}"
  714. [[ "${SITEDIR}" = "${MDFILE}" ]] && return 0
  715. SITENAME="${SITEDIR%%/*}"
  716. [[ "${SITENAME}" = "${SITEDIR}" ]] && return 0
  717. SITEDIR="www/${SITENAME}"
  718. [[ ! -d "${SITEDIR}" ]] && return 0
  719. printf "\nPage: %s\n" "${MDFILE}"
  720. printf "Processing page in site directory: %s/site/\n" "${SITEDIR}"
  721. if [ ! -f "${SITEDIR}/site.cfg" ]; then
  722. printf "%s missing for site '%s'. Skipping this page\n" \
  723. "${f}" "${SITEDIR}"
  724. return 0
  725. fi
  726. DOMAIN="$(getConfigValue "${SITEDIR}/site.cfg" "DOMAIN")"
  727. # it doesn't matter if this is empty
  728. TITLE="$(getConfigValue "${SITEDIR}/site.cfg" "TITLE")"
  729. if [ "${TITLE}" = "" ]; then TITLE="-T untitled_output"; fi
  730. CSS="$(getConfigValue "${SITEDIR}/site.cfg" "CSS")"
  731. # optional. if empty, there just won't be any css
  732. DEFAULTLANG="$(getConfigValue "${SITEDIR}/site.cfg" "DEFAULTLANG")"
  733. DEFAULTLANG="${DEFAULTLANG##*/}"
  734. if [ "${DEFAULTLANG}" = "" ]; then DEFAULTLANG="en"; fi
  735. if [ ! -d "lang/${DEFAULTLANG}" ]; then DEFAULTLANG="en"; fi
  736. mkhtml "${MDFILE}" "${SITEDIR##*/}" "${TITLE}" "${CSS}" \
  737. "${DEFAULTLANG}"
  738. }
  739. if [ -f "www" ]; then
  740. printf "untitled error: www is a file. should be a directory. exiting.\n"
  741. exit 1
  742. fi
  743. if [ ! -d "www" ]; then
  744. printf "untitled error: no www/ directory. Exiting\n"
  745. exit 1
  746. fi
  747. if [ -L "www" ]; then
  748. printf "untitled error: www/ directory is a symlink. Exiting\n"
  749. exit 1
  750. fi
  751. mode=""
  752. if [ $# -lt 1 ]; then
  753. mode="sites"
  754. else
  755. mode="${1}"
  756. shift 1
  757. fi
  758. if [ "${mode}" = "pages" ] && [ $# -lt 1 ]; then
  759. mode="sites"
  760. fi
  761. if [ "${mode}" = "sites" ]; then
  762. if [ $# -gt 0 ]; then
  763. for sitename in "${@}"; do
  764. startdate=$(date +%s%N | cut -b1-13)
  765. buildsite "www/${sitename##*/}"
  766. enddate=$(date +%s%N | cut -b1-13)
  767. endtime=$(( enddate - startdate ))
  768. printf "time: %d milliseconds\n" "${endtime}"
  769. done
  770. else
  771. for sitedir in www/*; do
  772. startdate=$(date +%s%N | cut -b1-13)
  773. buildsite "${sitedir}"
  774. enddate=$(date +%s%N | cut -b1-13)
  775. endtime=$(( enddate - startdate ))
  776. printf "time: %d milliseconds\n" "${endtime}"
  777. done
  778. fi
  779. elif [ "${mode}" = "pages" ]; then
  780. for pagepath in "$@"; do
  781. startdate=$(date +%s%N | cut -b1-13)
  782. buildpage "${pagepath}"
  783. enddate=$(date +%s%N | cut -b1-13)
  784. endtime=$(( enddate - startdate ))
  785. printf "time: %d milliseconds\n" "${endtime}"
  786. done
  787. else
  788. printf "error: unrecognized command: %s\n\n" "${mode}"
  789. printf "available commands:\n\tsites\n\tpages\n\n"
  790. printf "Consult the documentation to understand these commands.\n"
  791. exit 1
  792. fi