123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443 |
- #!/bin/sh
- #
- # build
- #
- # Build script for the Dragora GNU/Linux-Libre website
- # (https://www.dragora.org)
- #
- #
- # Copyright (C) 2020 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.
- #### "CONSTANTS" ####
- PROGNAME=build
- TOOLBOX='cat cmp cut find grep head mkdir sed sort stat rm rsync wc'
- ## Source directories and files
- SOURCE_DIR=source
- COMMON_DIR="$SOURCE_DIR"/common
- PAGES_DIR="$SOURCE_DIR"/pages
- PAGES_DIR_MASTER="$PAGES_DIR"/en
- TMP_DIR="$SOURCE_DIR"/tmp
- FOOTER_PARAMS=footer_params
- HEADER_PARAMS=header_params
- INDEX_PAGE=index.html.in
- ## Common directories and files
- CSS_DIR=css
- STYLESHEET=main.css
- STYLESHEET_PATH="$CSS_DIR/$STYLESHEET"
- IMG_DIR=img
- FAVICON=dragora.ico
- FAVICON_PATH="$IMG_DIR/$FAVICON"
- #LOGO=dragora_logo.png
- LOGO=dragora_logo-211x211.png
- LOGO_PATH="$IMG_DIR/$LOGO"
- ## Output directory
- OUTPUT_DIR=output
- ## Misc
- COPYRIGHT_HOLDERS='The Dragora Team'
- COPYRIGHT_YEARS='2019, 2020'
- DIR_UP='../'
- SEPARATOR='|' # Used in <title></title>.
- SITE_TITLE=Dragora
- TZ=UTC
- #### GLOBAL VARIABLES ####
- backpath=
- css_path=
- err_msg=
- favicon_path=
- header_type=
- lang=
- logo_path=
- subheader_class= # make "local" to _mk_header?
- mod_date=
- page_title=
- pg=
- pg_basename_in=
- pg_basename_out=
- pg_path_in=
- pg_path_out=
- sublevel=
- title=
- #### FUNCTIONS ####
- _abort() {
- ## Run _cleanup if "$TMP_DIR" exists and exit the script with a return code
- ## indicating an error
- [ -d "$TMP_DIR" ] && _cleanup
- exit 1
- }
- _cleanup() {
- ## Remove any temporary files
- rm -rf "${TMP_DIR:?}"/*
- }
- _get_lang_dirs() {
- ## Find all language-specific page directories
- find "$PAGES_DIR" -maxdepth 1 -type d -not -name '.*' -name '??' | \
- sed 's/^.*\///'
- }
- _get_page_title() {
- ## Retrieve page title from input file
- grep '^<!--PAGETITLE:.*-->$' "$1" | cut -d ':' -f 2 | sed 's/-->$//'
- }
- _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/manual -prune -o \
- -type f -name '*.html.in' -print
- }
- _mk_header() {
- ## Compile HTML page header
- case "$header_type" in
- frontpage)
- subheader_class='subheader_frontpage'
- ;;
- regular)
- subheader_class='subheader_regular'
- ;;
- esac
- printf '<!DOCTYPE html>\n<html lang="%s">\n<head>\n' "$lang"
- printf ' <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">\n'
- printf ' <link rel="stylesheet" type="text/css" href="%s">\n' "$css_path"
- printf ' <link rel="icon" type="image/gif" href="%s">\n' "$favicon_path"
- printf ' <title>%s</title>\n' "$title"
- printf ' <meta name="description" content="%s">\n' "$lang_description"
- printf '</head>\n<body>\n'
- printf ' <div class="header" role="banner">\n'
- printf ' <img src="%s" height="160" alt="Dragora logo">\n' "$logo_path"
- printf ' <div class="header_text">\n'
- printf ' <h1>Dragora</h1>\n'
- printf ' <p class="tagline">%s</p>\n' "$lang_tagline"
- printf ' </div> <!-- close header_text -->\n'
- printf ' </div> <!-- close header -->\n'
- printf ' <div class="page_wrapper">\n'
- printf ' <div class="torso">\n'
- }
- _mk_nav() {
- ## Compile HTML navigation menu
-
- printf ' <div class="site_nav" role="navigation">\n'
- printf ' <ul>\n'
- printf ' <li><div class="site_nav_item"><a href="%s/">%s</a></div></li>\n' "${DIR_UP}${backpath}${lang}" "$(_get_page_title "${PAGES_DIR}/${lang}/index.html.in")"
- printf ' <li><div class="site_nav_item"><a href="%snews/">%s</a></div></li>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/news/index.html.in")"
- printf ' <li><div class="site_nav_item"><a href="%sget/">%s</a></div>\n' "${backpath%}" "$(_get_page_title "${PAGES_DIR}/${lang}/get/index.html.in")"
- if [ "$pg_branch" = get ]
- then
- printf ' <ul>\n'
- printf ' <li><div class="site_nav_item"><a href="%sget/mirrors/">%s</a></div></li>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/get/mirrors/index.html.in")"
- printf ' </ul>\n'
- fi
- printf ' </li>\n'
- printf ' <li><div class="site_nav_item"><a href="%sdoc/">%s</a></div></li>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/doc/index.html.in")"
- printf ' <li><div class="site_nav_item"><a href="%scontribute/">%s</a></div></li>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/contribute/index.html.in")"
- printf ' <li><div class="site_nav_item"><a href="%scommunity/">%s</a></div></li>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/community/index.html.in")"
- printf ' <li><div class="site_nav_item"><a href="%sreport_bug/">%s</a></div></li>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/report_bug/index.html.in")"
- printf ' <li><div class="site_nav_item"><a href="%sdevel/">%s</a></div></li>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/devel/index.html.in")"
- printf ' <li><div class="site_nav_item"><a href="%stestbed/">%s</a></div>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/testbed/index.html.in")"
- if [ "$pg_branch" = testbed ]
- then
- printf ' <ul>\n'
- printf ' <li><div class="site_nav_item"><a href="%stestbed/coffee/">%s</a></div></li>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/testbed/coffee/index.html.in")"
- printf ' <li><div class="site_nav_item"><a href="%stestbed/icecream/">%s</a></div></li>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/testbed/icecream/index.html.in")"
- printf ' </ul>\n'
- fi
- printf ' </li>\n'
- printf ' </ul>\n'
- printf ' </div> <!-- close site_nav -->\n'
- printf ' <div class="main" role="main">\n'
- }
- _mk_footer() {
- ## Compile HTML page footer
- printf '\n <p class="back_top"><a href="#">%s</a></p>\n' "$lang_back_to_top"
- printf ' </div> <!-- close main -->\n'
- printf ' </div> <!-- close torso -->\n'
- printf ' <hr class="footer_hr">\n'
- printf ' <div class="footer" role="contentinfo">\n'
- printf ' <p>%s: %s</p>\n' "$lang_mod_label" "$mod_date"
- printf ' <p>Copyright © %s %s<br>\n' "$COPYRIGHT_YEARS" "$COPYRIGHT_HOLDERS"
- printf ' This work is licensed under a\n'
- printf ' <a href="https://creativecommons.org/licenses/by-sa/4.0/">\n'
- printf ' Creative Commons Attribution-ShareAlike 4.0 International\n'
- printf ' License</a>.\n'
- printf ' </p>\n'
- printf ' </div> <!-- close footer -->\n'
- printf ' </div> <!-- close page_wrapper -->\n'
- printf '</body>\n</html>\n'
- }
- _perr() {
- ## Print a message to stderr
- printf '%s\n' "$PROGNAME: $1" >&2
- }
- _probe_dirs_files() {
- ## Probe for existence of needed directories and files
- # Printing error messages right away and only returning at the end because we
- # want to catch all missing files and directories at once.
- # Local variables
- d=
- sd=
- err=0
- # Probe for top-level source directory
- if [ ! -d "$SOURCE_DIR" ]
- then
- _perr "directory not found -- '$SOURCE_DIR'"
- err=1
- else
- # Probe for sub-directories and files
- for d in "$COMMON_DIR" "$PAGES_DIR"
- do
- if [ ! -d "$d" ]
- then
- _perr "directory not found -- '$d'"
- err=1
- continue
- fi
- case "$d" in
- "$COMMON_DIR")
- for sd in "$d/$CSS_DIR" "$d/$IMG_DIR"
- do
- if [ ! -d "$sd" ]
- then
- _perr "directory not found -- '$sd'"
- err=1
- continue
- fi
- case "$sd" in
- "$d/$CSS_DIR")
- f="$d/$CSS_DIR/$STYLESHEET"
- [ -f "$f" ] || { _perr "stylesheet not found -- '$f'"; err=1; }
- ;;
- "$d/$IMG_DIR")
- f="$d/$IMG_DIR/$FAVICON"
- [ -f "$f" ] || { _perr "favicon not found -- '$f'"; err=1; }
-
- f="$d/$IMG_DIR/$LOGO"
- [ -f "$f" ] || { _perr "logo not found -- '$f'"; err=1; }
- ;;
- esac
- done
- ;;
- "$PAGES_DIR")
- [ -z "$(find "$PAGES_DIR" -mindepth 1 -maxdepth 1)" ] && \
- { _perr "directory must not be empty -- '$PAGES_DIR'"; err=1; }
- if [ ! -d "$PAGES_DIR_MASTER" ]
- then
- _perr "master page directory not found -- '$PAGES_DIR_MASTER'"
- err=1
- else
- # Check whether master tree is basically sane
- for f in "$HEADER_PARAMS" "$FOOTER_PARAMS" "$INDEX_PAGE"
- do
- f="$PAGES_DIR_MASTER/$f"
- [ -f "$f" ] || { _perr "file not found -- '$f'"; err=1; }
- # Improve error message.
- done
- # Determine if subtrees of all page directories are identical to
- # master subtree.
- find "$PAGES_DIR_MASTER" -printf '%P\n' | sort \
- > "$TMP_DIR"/treemaster
- for pd in $(_get_lang_dirs)
- do
- find "$PAGES_DIR/$pd" -printf '%P\n' | sort \
- > "$TMP_DIR"/treecopy
- cmp -s "$TMP_DIR"/treemaster "$TMP_DIR"/treecopy || { _perr \
- "'$PAGES_DIR/$pd': subtree not identical to '$PAGES_DIR_MASTER'";
- err=1; }
- # Improve error message
- done
- fi
- ;;
- esac
- done
- fi
-
- [ "$err" -gt 0 ] && { unset d sd f pd err; return 1; }
- unset d sd f pd err
- return 0
- }
- _probe_toolbox() {
- ## Probe for external commands
- err_msg=
- cmd=
- cmd_first_char=
- for cmd in $TOOLBOX
- do
- command -v "$cmd" >/dev/null || \
- { err_msg="command not found -- '$cmd'"; break; }
- cmd_first_char="$(printf '%s' "$cmd" | cut -c 1)"
- if [ "$cmd_first_char" = '/' ]
- then
- [ -x "$cmd" ] || \
- { err_msg="command found, but not executable -- '$cmd'"; break; }
- fi
- done
- unset cmd cmd_first_char
-
- [ -n "$err_msg" ] && return 1
-
- # Find GNU version commands that need it
-
- for cmd in $TOOLBOX
- do
- case "$cmd" in
- find)
- is_gnu="$(find --version | head -n1 | \
- sed 's/.*\(GNU findutils\).*/\1/')"
- [ -n "$is_gnu" ] || { err_msg="GNU version of '$cmd' required"; break; }
- ;;
- stat)
- is_gnu="$(stat --version | head -n1 | \
- sed 's/.*\(GNU coreutils\).*/\1/')"
- [ -n "$is_gnu" ] || { err_msg="GNU version of '$cmd' required"; break; }
- ;;
- esac
- done
- unset cmd is_gnu
-
- [ -n "$err_msg" ] && return 1
- return 0
- }
- _set_backpath() {
- backpath= # If you don't, the string will grow with every invocation.
- while [ "$sublevel" -gt 0 ]
- do
- backpath="${backpath}$DIR_UP"
- sublevel="$((sublevel-1))"
- done
- }
- #### MAIN ####
- # TODO: implement traps
- ## Environment checks
- _probe_toolbox || { _perr "$err_msg"; _abort; }
- _probe_dirs_files || { _cleanup; _abort; }
- mkdir -p "$TMP_DIR" "$OUTPUT_DIR" || _abort
- # Sync static common files
- rsync -av "$COMMON_DIR"/ "$OUTPUT_DIR" || _abort
- # Compile pages
- 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="$(printf '%s' "$pg_path_in" | sed "s,$PAGES_DIR/,,")"
- pg_branch="$(printf '%s' "$pg_path_in" | \
- sed -e "s,$PAGES_DIR/$lang,," -e 's,/,,' -e 's,/.*,,')"
- # The language-specific index page is not in any branch (i.e., $pg_path_in
- # has no trailing slash in this case). The 'sed' command above ensures that
- # 'branch' will be empty for the index page but have a meaningful value
- # otherwise.
- pg_basename_in="${pg##*/}"
- pg_basename_out="${pg_basename_in%.in}" # .html.in → .html
- sublevel="$(printf '%s' "$pg_path_out" | sed 's/[^\/]//g' | wc -m)"
- _set_backpath
- # 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 header type
-
- if [ "$pg" = "$PAGES_DIR"/"$lang"/index.html.in ]
- then
- header_type=frontpage
- else
- header_type=regular
- fi
- page_title="$(_get_page_title "$pg")"
-
- title="$SITE_TITLE $SEPARATOR $page_title"
- # Determine modification date (requires GNU version of `stat')
- mod_date="$(TZ="$TZ" stat -c '%y' "$pg" | cut -d '.' -f 1) $TZ"
- # Action
-
- mkdir -p "$OUTPUT_DIR"/"$pg_path_out" || _abort
- _mk_header > "$TMP_DIR"/header.html.in || _abort
- _mk_nav "$pg_path_in" > "$TMP_DIR/nav.html.in" || _abort
- _mk_footer > "$TMP_DIR"/footer.html.in || _abort
- cat "$TMP_DIR"/header.html.in "$TMP_DIR"/nav.html.in "$pg" \
- "$TMP_DIR"/footer.html.in \
- > "$OUTPUT_DIR"/"$pg_path_out"/"$pg_basename_out" || _abort
- done
- done
- _cleanup
|