#!/bin/bash # vars SELF="${BASH_SOURCE[0]}" SELF_DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd -P) SELF_DIR="${SELF_DIR:-$(pwd)}" CONFIG="${SELF_DIR}/config.yaml" SOURCE_DIR="${SELF_DIR}/source" PUBLIC_DIR="${SELF_DIR}/public" TEMPLATE_DIR="${SELF_DIR}/templates" TOOLS_BIN="${SELF_DIR}/tools/local/bin" # dependencies MARKDOWN="${TOOLS_BIN}/markdown" DEPS=("${MARKDOWN}") # optional dependencies PARALLEL=$(which parallel) # creates bash variables from yaml records # extended to support arrays, empty values, and more # https://gist.github.com/DinoChiesa/3e3c3866b51290f31243 # https://gist.github.com/epiloque/8cf512c6d64641bde388 # https://gist.github.com/pkuczynski/8665367 function parse_yaml { # parse_yaml yaml_string_or_file variables_prefix spaces_per_indentation local INPUT PREFIX INDENT S W FS INPUT="${1:-}" [[ "${INPUT}" == "" ]] && return 0 if [[ -f "${INPUT}" ]] && [[ -r "${INPUT}" ]]; then INPUT=$(<"${INPUT}") fi PREFIX="${2:-}" INDENT="${3:-}" [[ ! "${INDENT}" =~ '^[0-9]+$' ]] && INDENT=2 S='[[:space:]]*' W='[a-zA-Z0-9_]*' FS=$(echo @|tr @ '\034') echo "${INPUT}" | sed -n \ -e "s|^\(\(${S}\)\(-\) \)\{1\}\(${S}\)\(${W}\)${S}:${S}\"\(.*\)\"${S}\$|\3${FS}\2 \4${FS}\5${FS}\6|p" \ -e "s|^\(\(${S}\)\(-\) \)\{1\}\(${S}\)\(${W}\)${S}:${S}\(.*\)${S}\$|\3${FS}\2 \4${FS}\5${FS}\6|p" \ -e "s|^\(\(${S}\)\(-\) \)\{1\}\(${S}\)\"\(.*\)\"${S}\$|\3${FS}\2 \4${FS}${FS}\5|p" \ -e "s|^\(\(${S}\)\(-\) \)\{1\}\(${S}\)\([^:].*\)${S}\$|\3${FS}\2 \4${FS}${FS}\5|p" \ -e "s|^\(${S}\)\(${W}\)${S}:${S}\"\(.*\)\"${S}\$|${FS}\1${FS}\2${FS}\3|p" \ -e "s|^\(${S}\)\(${W}\)${S}:${S}\(.*\)${S}\$|${FS}\1${FS}\2${FS}\3|p" | awk -F"${FS}" \ 'BEGIN { prefix = "'"${PREFIX}"'"; indent = '"${INDENT}"'; prev_level = 0; } { type = $1; level = length($2)/indent; key = $3; val = $4; if (level < prev_level) { adepth[prev_level] = -1; } if (adepth[level] == "") { adepth[level] = -1; } if (type == "-") { adepth[level]++; } vname[level] = key; for (i in vname) { if (i > (level)) { vname[i] = ""; } } out = prefix; for (i = 0; i < level; i++) { if (vname[i] != "") { out = out "_" vname[i]; if (adepth[i] > -1) { out = out "_" adepth[i+1]; } } else { out = out "_" adepth[i+1]; } } out = out "_" key "="; gsub(/__/, "_", out); gsub(/_=/, "=", out); if (prefix == "") { out = substr(out, 2); } out = out "\"" if (length(val) > 0) { gsub(/"/, "\\\"", val) out = out val; } out = out "\"" print out; prev_level = level; }' } # checks for required external tools function check_dependencies { # check_dependencies $DEP1 $DEP2 ... local DEPS ERRORS DEPS=("${@}"); ERRORS=() for DEP in ${DEPS[@]}; do if echo "${DEP}" | grep '/' >/dev/null 2>&1 && [[ ! -x "${DEP}" ]]; then ERRORS+=("${DEP}") elif ! hash "${DEP}" >/dev/null 2>&1; then ERRORS+=("${DEP}") fi done if [[ "${#ERRORS[@]}" -ne 0 ]]; then echo "dependencies: ${DEPS[@]}" echo "unable to find command(s): ${ERRORS[*]}" >&2 return 1 fi } # transforms a string function string_filter { local STRING FILTERS STRING=("${1}"); shift 1 FILTERS=("${@}") for FILTER in ${FILTERS[@]}; do case "${FILTER}" in 'slug') STRING=$(echo "${STRING}" | sed -E -e 's/[ _]+/-/g' -e 's/[^-a-zA-Z0-9.]//g' -e 's/(^-+)|(-+$)//' | awk '{ print tolower($0) }') ;; esac done echo -n "${STRING}" } # builds document(s) from a single source file function build_source { local SOURCE BASE_RELPATH DEST DEST_NAME YAML DOCUMENT_Type DOCUMENT_State DOCUMENT_Title DOCUMENT_Project DOCUMENT_Project_URL DOCUMENT_Project_Version DOCUMENT_Language DOCUMENT_Language_Code DOCUMENT_Text_Encoding DOCUMENT_Authors DOCUMENT_Copyright DOCUMENT_License DOCUMENT_License_Abbr DOCUMENT_License_URL DOCUMENT_Redirect_URL DOCUMENT_Content NAVIGATION_PARTIAL NAVIGATION_RELPATH TEMPLATE_Scripts TEMPLATE_Styles SOURCE="${1}" BASE_RELPATH="${SOURCE#$PUBLIC_DIR/docs/}" # strip abs prefix BASE_RELPATH="${BASE_RELPATH//[^\/]}" # leave only slashes BASE_RELPATH="${BASE_RELPATH//[\/]/../}" # slashes to dirs BASE_RELPATH="${BASE_RELPATH:-./}" # empty to current dir DEST="${SOURCE%.markdown}" DEST_NAME="${DEST##*/}" DOCUMENT_Content="${DEST}.html.temp" # check for yaml header YAML=false if head -n 1 "${SOURCE}" | grep '^---$' >/dev/null 2>&1; then YAML=true fi if [[ "${YAML}" == true ]]; then # split yaml and markdown awk '{ drop = 0; } /^---$/ { if (NR==1) { drop = 1 } else if (NR>1) { exit } else { drop = 0; next } } drop == 0 { print }' "${SOURCE}" > "${DEST}.yaml" mv "${SOURCE}" "${DEST}.markdown.temp" tail -n +$(wc -l "${DEST}.yaml" | awk '{ print $1+3 }') "${DEST}.markdown.temp" > "${SOURCE}" rm -f "${DEST}.markdown.temp" # parse yaml eval $(parse_yaml "${DEST}.yaml" "DOCUMENT_") fi # process authors DOCUMENT_Authors=$(echo "${DOCUMENT_Authors}" | sed -e 's/,[^ ]/, /g' -e 's/[ ]*<[^,]*>//g' -e 's/\(.*\), /\1, and /' -e 's/\([^,]\) /\1\\\ /g') DELIM_NUM=$(grep -o ',' <<< "${DOCUMENT_Authors}" | wc -l) if [[ "${DELIM_NUM}" -eq 1 ]]; then DOCUMENT_Authors=$(echo "${DOCUMENT_Authors}" | sed -e 's/,//') fi # process state DOCUMENT_State=(${DOCUMENT_State//,/}) # preprocess markdown to add implicit figures sed -E \ -e 's|^!\[(.+)]\([ ]*([^ ]+)[ ]*"(.+)"[ ]*\)$|
\1
\3
|' \ -i.sedbak "${SOURCE}" # convert preprocessed markdown document to html "${MARKDOWN}" -fdlextra -ffencedcode -ffootnote -fgithubtags "${SOURCE}" > "${DOCUMENT_Content}" # select output type if [[ "${CONFIG_Embeddable:-}" != true ]]; then if [[ "${DOCUMENT_Redirect_URL:-}" != "" ]]; then # full html redirect only cp "${TEMPLATE_Partials_Redirect}" "${DEST}.html" else # full html content cp "${TEMPLATE_Partials_Base}" "${DEST}.html" fi else # partial html cp "${TEMPLATE_Partials_Body}" "${DEST}.html" fi # inject body template sed -E \ -e '/\{\{[ ]*template\.body[ ]*\}\}/{r '"${TEMPLATE_Partials_Body:-}" -e 'd;}' \ -i.sedbak "${DEST}.html" # inject header, document, footer templates sed -E \ -e '/\{\{[ ]*template\.header[ ]*\}\}/{r '"${TEMPLATE_Partials_Header:-}" -e 'd;}' \ -e '/\{\{[ ]*template\.document[ ]*\}\}/{r '"${TEMPLATE_Partials_Document:-}" -e 'd;}' \ -e '/\{\{[ ]*template\.footer[ ]*\}\}/{r '"${TEMPLATE_Partials_Footer:-}" -e 'd;}' \ -i.sedbak "${DEST}.html" # inject document content sed -E \ -e '/\{\{[ ]*document[ ]*\}\}/{r '"${DOCUMENT_Content:-}" -e 'd;}' \ -i.sedbak "${DEST}.html" # inject nav template sed -E \ -e '/\{\{[ ]*template\.nav[ ]*\}\}/{r '"${TEMPLATE_Partials_Nav:-}" -e 'd;}' \ -i.sedbak "${DEST}.html" # process includes NAVIGATION_PARTIAL="${PUBLIC_DIR}/docs/$(string_filter \"${DOCUMENT_Language_Code:-.}\" slug)/$(string_filter \"${DOCUMENT_Project_Version:-.}\" slug)/${CONFIG_Navigation%.markdown}.partial.html" NAVIGATION_RELPATH="${BASE_RELPATH#../../}" if [[ -e "${NAVIGATION_PARTIAL}" ]] && [[ "${DOCUMENT_Type}" == "article" ]]; then sed -E \ -e 's|

([^<]+)

|

\1

|' \ -e 's|(]*)>([^<>]+)|\2|g' \ "${NAVIGATION_PARTIAL}" > "${DEST}.nav.partial.html" sed -E \ -e '/\{\{[ ]*navigation[ ]*\}\}/{r '"${DEST}.nav.partial.html" -e 'd;}' \ -i.sedbak "${DEST}.html" else sed -E \ -e '/\{\{[ ]*navigation[ ]*\}\}/d' \ -i.sedbak "${DEST}.html" fi # process template tags TEMPLATE_SCRIPTS="" for SCRIPT in ${TEMPLATE_Assets_Scripts[@]}; do TEMPLATE_SCRIPTS+="" done TEMPLATE_STYLES="" for STYLE in ${TEMPLATE_Assets_Styles[@]}; do TEMPLATE_STYLES+="" done sed -E \ -e 's|\{\{[ ]*template\.scripts[ ]*\}\}|'"${TEMPLATE_SCRIPTS:-}"'|g' \ -e 's|\{\{[ ]*template\.styles[ ]*\}\}|'"${TEMPLATE_STYLES:-}"'|g' \ -e 's|\{\{[ ]*document\.type[ ]*\}\}|'"${DOCUMENT_Type:-}"'|g' \ -e 's|\{\{[ ]*document\.state[ ]*\}\}|'"${DOCUMENT_State:-}"'|g' \ -e 's|\{\{[ ]*document\.title[ ]*\}\}|'"${DOCUMENT_Title:-}"'|g' \ -e 's|\{\{[ ]*document\.project[ ]*\}\}|'"${DOCUMENT_Project:-}"'|g' \ -e 's|\{\{[ ]*document\.project[ ]+[\|]?[ ]*slug[ ]*\}\}|'$(string_filter "${DOCUMENT_Project:-}" slug)'|g' \ -e 's|\{\{[ ]*document\.project-url[ ]*\}\}|'"${DOCUMENT_Project_URL:-}"'|g' \ -e 's|\{\{[ ]*document\.project-version[ ]*\}\}|'"${DOCUMENT_Project_Version:-}"'|g' \ -e 's|\{\{[ ]*document\.project-version[ ]+[\|]?[ ]*slug[ ]*\}\}|'$(string_filter "${DOCUMENT_Project_Version:-}" slug)'|g' \ -e 's|\{\{[ ]*document\.language[ ]*\}\}|'"${DOCUMENT_Language:-}"'|g' \ -e 's|\{\{[ ]*document\.language[ ]+[\|]?[ ]*slug[ ]*\}\}|'$(string_filter "${DOCUMENT_Language:-}" slug)'|g' \ -e 's|\{\{[ ]*document\.language-code[ ]*\}\}|'"${DOCUMENT_Language_Code:-}"'|g' \ -e 's|\{\{[ ]*document\.language-code[ ]+[\|]?[ ]*slug[ ]*\}\}|'$(string_filter "${DOCUMENT_Language_Code:-}" slug)'|g' \ -e 's|\{\{[ ]*document\.text-encoding[ ]*\}\}|'"${DOCUMENT_Text_Encoding:-}"'|g' \ -e 's|\{\{[ ]*document\.authors[ ]*\}\}|'"${DOCUMENT_Authors:-}"'|g' \ -e 's|\{\{[ ]*document\.copyright[ ]*\}\}|'"${DOCUMENT_Copyright:-}"'|g' \ -e 's|\{\{[ ]*document\.license[ ]*\}\}|'"${DOCUMENT_License:-}"'|g' \ -e 's|\{\{[ ]*document\.license-abbr[ ]*\}\}|'"${DOCUMENT_License_Abbr:-}"'|g' \ -e 's|\{\{[ ]*document\.license-url[ ]*\}\}|'"${DOCUMENT_License_URL:-}"'|g' \ -e 's|\{\{[ ]*document\.credits-url[ ]*\}\}|'"${DOCUMENT_Credits_URL:-}"'|g' \ -e 's|\{\{[ ]*document\.redirect-url[ ]*\}\}|'"${DOCUMENT_Redirect_URL:-}"'|g' \ -e 's|\{\{[ ]*base\.relpath[ ]*\}\}|'"${BASE_RELPATH:-}"'|g' \ -i.sedbak "${DEST}.html" # process conditionals for STATE in ${DOCUMENT_State[@]}; do case "${STATE}" in draft) cp "${DEST}.html" "${DEST}.html.temp" awk '/\{\% if document.state contains "draft"/ {k=1;i=1;delete a} {a[i++]=$0} (k==1 && /endif \%\}/) {k=0;for (j=2;j "${DEST}.html" ;; obsolete) cp "${DEST}.html" "${DEST}.html.temp" awk '/\{\% if document.state contains "obsolete"/ {k=1;i=1;delete a} {a[i++]=$0} (k==1 && /endif \%\}/) {k=0;for (j=2;j "${DEST}.html" ;; esac done cp "${DEST}.html" "${DEST}.html.temp" awk '/\{\% if/ {k=1;i=1;delete a} {a[i++]=$0} (k==1 && /endif \%\}/) {k=0;next} (k==0) { print }' "${DEST}.html.temp" > "${DEST}.html" # process comment tags sed -E \ -e 's||
|g' \ -e 's||
|g' \ -e 's||
|g' \ -i.sedbak "${DEST}.html" # postprocess sed -E \ -e 's|

|
|' \ -e 's|

|
|' \ -i.sedbak "${DEST}.html" # clean up rm -f "${DEST}.yaml" rm -f "${DEST}.markdown" rm -f "${DEST}.markdown.sedbak" rm -f "${DEST}.nav.partial.html" rm -f "${DEST}.html.temp" rm -f "${DEST}.html.sedbak" } # base directory (absolute) cd "${SELF_DIR}" # check deps check_dependencies "${DEPS[@]}" || exit 1 # parse config if [[ -e "${CONFIG}" ]]; then eval $(parse_yaml "${CONFIG}" "CONFIG_") else echo "Configuration file not found." >&2 exit 1 fi # parse template config TEMPLATE_DIR+="/${CONFIG_Template}" TEMPLATE_CONFIG="${TEMPLATE_DIR}/template.yaml" if [[ -e "${TEMPLATE_CONFIG}" ]]; then eval $(parse_yaml "${TEMPLATE_CONFIG}" "TEMPLATE_") TEMPLATE_Partials_Base="${TEMPLATE_DIR}/${TEMPLATE_Partials_Base}" TEMPLATE_Partials_Header="${TEMPLATE_DIR}/${TEMPLATE_Partials_Header}" TEMPLATE_Partials_Nav="${TEMPLATE_DIR}/${TEMPLATE_Partials_Nav}" TEMPLATE_Partials_Body="${TEMPLATE_DIR}/${TEMPLATE_Partials_Body}" TEMPLATE_Partials_Document="${TEMPLATE_DIR}/${TEMPLATE_Partials_Document}" TEMPLATE_Partials_Footer="${TEMPLATE_DIR}/${TEMPLATE_Partials_Footer}" TEMPLATE_Partials_Redirect="${TEMPLATE_DIR}/${TEMPLATE_Partials_Redirect}" TEMPLATE_Assets_Fonts=() INDEX=0 ASSET="TEMPLATE_Assets_Fonts_${INDEX}" ASSET="${!ASSET}" while [[ "${ASSET}" != "" ]]; do TEMPLATE_Assets_Fonts+=("${ASSET}") INDEX=$((INDEX+1)) ASSET="TEMPLATE_Assets_Fonts_${INDEX}" ASSET="${!ASSET}" done TEMPLATE_Assets_Styles=() INDEX=0 ASSET="TEMPLATE_Assets_Styles_${INDEX}" ASSET="${!ASSET}" while [[ "${ASSET}" != "" ]]; do TEMPLATE_Assets_Styles+=("${ASSET}") INDEX=$((INDEX+1)) ASSET="TEMPLATE_Assets_Styles_${INDEX}" ASSET="${!ASSET}" done TEMPLATE_Assets_Scripts=() INDEX=0 ASSET="TEMPLATE_Assets_Scripts_${INDEX}" ASSET="${!ASSET}" while [[ "${ASSET}" != "" ]]; do TEMPLATE_Assets_Scripts+=("${ASSET}") INDEX=$((INDEX+1)) ASSET="TEMPLATE_Assets_Scripts_${INDEX}" ASSET="${!ASSET}" done else echo "Template configuration file not found." >&2 exit 1 fi # build if [[ "${1:-}" != "" ]]; then # single source build_source "${1}" else # everything # assets mkdir -p "${PUBLIC_DIR}"/docs $(GLOBIGNORE='*.gitkeep'; rm -rf "${PUBLIC_DIR}"/docs/*) cp -R "${SOURCE_DIR}"/docs/* "${PUBLIC_DIR}"/docs/ for REF in ${TEMPLATE_Assets_Fonts[@]} ${TEMPLATE_Assets_Scripts[@]} ${TEMPLATE_Assets_Styles[@]}; do FILE="${REF%\?*}" mkdir -p "${PUBLIC_DIR}/docs/${FILE%/*}" cp "${TEMPLATE_DIR}/${FILE}" "${PUBLIC_DIR}/docs/${FILE}" done # navigation cd "${SOURCE_DIR}"/docs LANGUAGES=() while IFS= read -r -d '' x; do LANGUAGES+=("$(basename "${x}")") done < <(find . -maxdepth 1 -type d -not -name '.*' -print0) for LANGUAGE in "${LANGUAGES[@]}"; do cd "${LANGUAGE}" VERSIONS=() while IFS= read -r -d '' x; do VERSIONS+=("$(basename "${x}")") done < <(find . -maxdepth 1 -type d -not -name '.*' -print0) for VERSION in "${VERSIONS[@]}"; do cd "${VERSION}" if [[ -e "${CONFIG_Navigation}" ]]; then NAVIGATION_PARTIAL="${PUBLIC_DIR}/docs/${LANGUAGE}/${VERSION}/${CONFIG_Navigation%.markdown}.partial.html" cp "${CONFIG_Navigation}" "${NAVIGATION_PARTIAL}" if head -n 1 "${NAVIGATION_PARTIAL}" | grep '^---$' >/dev/null 2>&1; then # remove yaml tail -n +$(awk '{ drop = 0; } /^---$/ { if (NR==1) { drop = 1 } else if (NR>1) { exit } else { drop = 0; next } } drop == 0 { print }' "${NAVIGATION_PARTIAL}" | wc -l | awk '{ print $1+3 }') "${NAVIGATION_PARTIAL}" > "${NAVIGATION_PARTIAL}.temp" else cp "${NAVIGATION_PARTIAL}" "${NAVIGATION_PARTIAL}.temp" fi "${MARKDOWN}" -fdlextra -ffencedcode -ffootnote -fgithubtags "${NAVIGATION_PARTIAL}.temp" > "${NAVIGATION_PARTIAL}" rm -f "${NAVIGATION_PARTIAL}.temp" fi cd "${SOURCE_DIR}/docs/${LANGUAGE}" done cd "${SOURCE_DIR}"/docs done cd "${SELF_DIR}" # sources SOURCES=($(find "${PUBLIC_DIR}"/docs | sed 's/^\.\///' | grep -i '.markdown')) if [[ "${PARALLEL:-}" != "" ]]; then export -f build_source "${PARALLEL}" "${SELF}" ::: "${SOURCES[@]}" else for SOURCE in ${SOURCES[@]}; do build_source "${SOURCE}" done fi # clean up find "${PUBLIC_DIR}"/docs -name '*.partial.html' -exec rm -f {} \; fi # done exit 0