123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405 |
- #!/bin/bash
- #
- # build
- #
- # Build script for the Dragora GNU/Linux-Libre website
- # (https://www.dragora.org)
- #
- #
- # Copyright (C) 2020, 2021 Michael Siegel
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- #### INCLUDES ####
- . include/constants || exit 1
- . include/subroutines || exit 1
- #### CONSTANTS ####
- #### GLOBAL VARIABLES ####
- backpath=
- build_testbed=0
- css_path=
- err_msg= # Try to do without that. It's currently ununsed.
- favicon_path=
- lang=
- logo_path=
- mod_date=
- page_title=
- pg=
- pg_basename_in=
- pg_basename_out=
- pg_path_in=
- pg_path_out=
- slashes=
- sublevel=
- title=
- #### FUNCTIONS ####
- _get_page_title() {
- ## Retrieve page title from input file
-
- sed -n '/^<!--PAGETITLE:.*-->$/ {
- s/<!--PAGETITLE://
- s/-->$//
- p
- }' "$1"
- }
- _get_regular_pages() {
- ## Find all HTML pages in "$PAGES_DIR"/"$lang", except those that belong to
- ## the Dragora Handbook
- find -- "$PAGES_DIR"/"$lang" -path "$PAGES_DIR"/"$lang"/doc/handbook -prune \
- -o -type f -name '*.html.in' -print
- }
- _mk_clean() {
- ## Remove page directories from $OUTPUT_DIR
- local dir=
- for dir in $(_get_lang_dirs)
- do
- rm -rf -- "${OUTPUT_DIR:?}/$dir" || \
- { _perr "cannot remove directory -- '$OUTPUT_DIR/$dir'"; return 1; }
- done
- unset dir
- }
- _mk_header() {
- ## Compile HTML page header
- printf '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"\n "http://www.w3.org/TR/html4/strict.dtd">\n'
- printf '<html lang="%s">\n<head>\n' "$lang"
- printf \
- '%s<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">\n' \
- "$IND1"
- printf '%s<link rel="stylesheet" type="text/css" href="%s">\n' "$IND1" \
- "$css_path"
- printf '%s<link rel="icon" type="image/gif" href="%s">\n' "$IND1" \
- "$favicon_path"
- printf '%s<title>%s</title>\n' "$IND1" "$title"
- printf '%s<meta name="description" content="%s">\n' "$IND1" \
- "$lang_description"
- printf '</head>\n<body>\n'
- printf '%s<div class="header" role="banner">\n' "$IND1"
- printf '%s<img src="%s" height="80" alt="Dragora logo">\n' "$IND2" \
- "$logo_path"
- printf '%s<div class="header_text">\n' "$IND2"
- printf '%s<h1>Dragora</h1>\n' "$IND3"
- printf '%s<p class="tagline">%s</p>\n' "$IND3" "$lang_tagline"
- printf '%s</div> <!-- close header_text -->\n' "$IND2"
- printf '%s</div> <!-- close header -->\n' "$IND1"
- printf '%s<div class="torso">\n' "$IND1"
- }
- _mk_indent() {
- ## Create an indentation string for a given level
- local ind=
- local level="$1"
- while [[ "$level" -gt 0 ]]
- do
- ind="${ind}${IND1}"
- ((--level))
- done
- printf '%s' "$ind"
- }
- _pick_navitems() {
- # Print out only those items that are to be shown in the site navigation on
- # the current page
- local current_pg="${pg_path_out#*/}/"
- local depth_current_pg=
- local depth_item=
- local fc=
- local fi=
- local field=
- local ignore=
- local slashes=
- slashes="${current_pg//[^\/]}"
- depth_current_pg="${#slashes}"
- for item in "${navtree[@]}"
- do
- [[ "$build_testbed" -eq 0 && "$item" = testbed/* ]] && continue
- slashes="${item//[^\/]}"
- depth_item="${#slashes}"
- # Ignore any item at a deeper nesting level than immediate sub-pages of
- # $current_pg.
- [[ "$depth_item" -gt "$((depth_current_pg + 1))" ]] && continue
- # The loop below can't catch those…
- ignore=0
- field=1
- while true
- do
- fc="$(cut -d / -f "$field" <<< "$current_pg")"
- fi="$(cut -d / -f "$field" <<< "$item")"
- [[ -n "$fi" && -n "$fc" ]] || break
-
- if [[ "$fi" != "$fc" ]]
- then
- [[ "$depth_item" -gt "$field" ]] && ignore=1 && break
- fi
- ((++field))
- done
- [[ "$ignore" -eq 1 ]] && continue
- printf '%s ' "$item"
- done
- }
- _mk_nav() {
- # Compile site navigation for the current page
- local current_pg="${pg_path_out#*/}/"
- local depth_curr=
- local depth_next=
- local ind=4
- local nav_label=
- local nav_path=
- local slashes=
- local sublists_opened=0
- printf '%s<div class="site_nav" role="navigation">\n' "$IND2"
- printf '%s<ul>\n' "$IND3"
- while [[ $# -gt 0 ]]
- do
- slashes="${1//[^\/]}"
- depth_curr="${#slashes}"
- slashes="${2//[^\/]}"
- depth_next="${#slashes}"
- nav_path="${backpath}${1}"
- nav_label="$(_get_page_title "${PAGES_DIR}/${lang}/${1}/index.html.in")"
- # Open list item:
- printf '%s<li>' "$(_mk_indent "$ind")"
- printf '<div class="site_nav_item'
- [[ "$1" = "$current_pg" ]] && printf ' selected'
- printf '"><a href="%s">%s</a></div>' "$nav_path" "$nav_label"
- # Next path has equal depth: Close list item:
- if [[ "$depth_curr" -eq "$depth_next" ]]
- then
- printf '</li>\n'
- # Next path runs deeper: Open sublist for next nav item, increase count of
- # opened sublists:
- elif [[ "$depth_curr" -lt "$depth_next" ]]
- then
- ((++ind))
- printf '\n%s<ul>\n' "$(_mk_indent "$ind")"
- ((++sublists_opened))
- ((++ind)) # for <li> in the sublist
- # Next path runs less deep: Close list item, close appropriate number of
- # opened sublists, close surrounding list item.
- elif [[ "$depth_curr" -gt "$depth_next" ]]
- then
- printf '</li>\n'
- while [[ "$sublists_opened" -gt 0 && \
- "$((sublists_opened - depth_next))" -ge 0 ]]
- do
- ((--ind))
- printf '%s</ul>\n' "$(_mk_indent "$ind")"
- ((--sublists_opened))
- ((--ind))
- printf '%s</li>\n' "$(_mk_indent "$ind")"
- done
- # The loop condition explained:
- # Part 1: Closing sublists runs up to and including
- # $sublists_opened - $depth_next being 0. But if $2 is empty
- # because we are already working on the final nav item, $depth_next
- # will also be 0. Subtracting that from sublists_opened being 0 would,
- # of course, itself be 0 and thus make the loop condition evaluate to
- # true and result in printing an additonal sublist closing when all
- # sublists have already been closed.
- # Part 2: You can't just close all open sublists at this point because
- # list items on lower (less deep) nesting levels might follow. So,
- # closing opened sublists can only go as far as the nesting level of
- # the next nav item.
- fi
- shift
- done
- printf '%s</ul>\n' "$IND3"
- printf '%s</div> <!-- close site_nav -->\n' "$IND2"
- printf '%s<div class="main" role="main">\n' "$IND2"
- }
- _mk_content() {
- ## Incorporate dynamic content modules, if any.
- local pg="$1"
- local lang="$2"
- local has_modules=1
- # local tmpfile_1=
- # local tmpfile_2=
- #
- # tmpfile_1="$(mktemp "$TMP_DIR"/XXXXXX)"
- # tmpfile_2="$(mktemp "$TMP_DIR"/XXXXXX)"
- #
- # Two temporary files will be needed for copying content back an forth, as
- # soon as there is more than one dynamic content module.
- if grep -q '^<!--LATEST_NEWS-->$' "$pg"
- then
- has_modules=0
- sed '1,/^<!--LATEST_NEWS-->$/ !d' "$pg" && \
- grep -A "$((LATEST_NEWS_ITEMS * 3))" -- '^<ul>$' \
- "${PAGES_DIR}/${lang}/$NEWS_PAGE" | sed '$ a <\/ul>' && \
- sed '1,/^<!--LATEST_NEWS-->$/ d' "$pg"
- fi
- [[ "$has_modules" -eq 1 ]] && cat "$pg"
- }
- _mk_footer() {
- ## Compile HTML page footer
- printf '\n<p class="back_top"><a href="#">%s</a></p>\n\n' "$lang_back_to_top"
- printf '%s</div> <!-- close main -->\n' "$IND2"
- printf '%s</div> <!-- close torso -->\n' "$IND1"
- printf '%s<hr class="footer_hr">\n' "$IND1"
- printf '%s<div class="footer" role="contentinfo">\n' "$IND1"
- printf '%s<p>%s: %s</p>\n' "$IND2" "$lang_mod_label" "$mod_date"
- printf '%s<p>Copyright © %s %s<br>\n' "$IND2" "$COPYRIGHT_YEARS" \
- "$COPYRIGHT_HOLDERS"
- printf '%s<a href="%slicense/">%s</a>\n' "$IND3" "${backpath}" \
- "$(_get_page_title "${PAGES_DIR}/${lang}/license/index.html.in")"
- printf '%s</p>\n' "$IND2"
- printf '%s</div> <!-- close footer -->\n' "$IND1"
- printf '</body>\n</html>\n'
- }
- _mk_pretty() {
- ## Remove meta comments from page source
- sed -e '/^<!--PAGETITLE:.*-->$/ d' \
- -e '/^<!--LATEST_NEWS-->$/ d' <<< "$1"
- }
- _set_backpath() {
- backpath= # If you don't, the string will grow with every invocation.
- while [[ "$sublevel" -gt 0 ]]
- do
- backpath="${backpath}$DIR_UP"
- ((--sublevel))
- done
- }
- #### MAIN ####
- # TODO: implement traps
- ## Environment checks
- _env_checks || _abort
- ## Parse command-line arguments
- [[ "$1" = '-t' ]] && build_testbed=1
- ## Remove page directories from $OUTPUT_DIR, then rsync static files
- ## Order matters!
- _mk_clean || _abort
- rsync -av --delete "$COMMON_DIR"/ "$OUTPUT_DIR" || _abort
- # Compile pages
- _get_navtree
- for lang in $(_get_lang_dirs)
- do
- # Get values for language-specific parameters in header and footer
- . "$PAGES_DIR/$lang/$HEADER_PARAMS" || _abort
- . "$PAGES_DIR/$lang/$FOOTER_PARAMS" || _abort
- # Compile pages
- for pg in $(_get_regular_pages)
- do
- # Set path and file names
- pg_path_in="${pg%/*}"
- pg_path_out="$(sed "s,$PAGES_DIR/,," <<< "$pg_path_in")"
- # echo "$pg_path_out" # DEBUG
- pg_basename_in="${pg##*/}"
- pg_basename_out="${pg_basename_in%.in}" # .html.in → .html
- if [[ "$build_testbed" -eq 0 ]]
- then
- # Skip testbed pages
- [[ "$(cut -d '/' -f 2 <<< "$pg_path_out")" = testbed ]] && continue
- fi
- slashes="${pg_path_out//[^\/]}"
- sublevel="${#slashes}"
- _set_backpath # makes use of $sublevel
- # these should, maybe, be 'local' to _mk_header
- css_path="${DIR_UP}${backpath}${STYLESHEET_PATH}"
- favicon_path="${DIR_UP}${backpath}${FAVICON_PATH}"
- logo_path="${DIR_UP}${backpath}${LOGO_PATH}"
- # Determine page title
- page_title="$(_get_page_title "$pg")"
- title="$SITE_TITLE $SEPARATOR $page_title"
- # Determine modification date (requires GNU version of `stat')
- mod_date="$(TZ="$TIMEZONE" stat -c '%y' -- "$pg" | cut -d '.' -f 1)"
- mod_date="$mod_date $TIMEZONE"
- # Action
- mkdir -p -- "$OUTPUT_DIR"/"$pg_path_out" || _abort
- { _mk_header && _mk_nav $(_pick_navitems) && \
- _mk_pretty "$(_mk_content "$pg" "$lang")" && _mk_footer; } \
- > "$OUTPUT_DIR"/"$pg_path_out"/"$pg_basename_out" || _abort
- done
- ln -s -- "$HOME_PAGE_DIR"/index.html "$OUTPUT_DIR"/"$lang"/index.html
- done
- ln -s -- "${PAGES_DIR_MASTER##*/}"/"$HOME_PAGE_DIR"/index.html \
- "$OUTPUT_DIR"/index.html
- _cleanup
|