#!/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" DOCS_DIR="${SELF_DIR}/docs" SOURCE_DIR="${SELF_DIR}/source" 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 # https://gist.github.com/pkuczynski/8665367 function parse_yaml { local prefix=$2 local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034') sed -ne "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \ -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" $1 | awk -F$fs '{ indent = length($1)/2; vname[indent] = $2; for (i in vname) {if (i > indent) {delete vname[i]}} if (length($3) > 0) { vn=""; for (i=0; i/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_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_URL DOCUMENT_Redirect_URL DOCUMENT_Content NAVIGATION_PARTIAL NAVIGATION_RELPATH TEMPLATE_Scripts TEMPLATE_Styles SOURCE="${1}" BASE_RELPATH="${SOURCE#$DOCS_DIR/}" # 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 # 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_Redirect}" "${DEST}.html" else # full html content cp "${TEMPLATE_Base}" "${DEST}.html" fi else # partial html cp "${TEMPLATE_Document}" "${DEST}.html" fi # inject meta, title, styles, document, scripts sed -E \ -e '/\{\{[ ]*template\.meta[ ]*\}\}/{r '"${TEMPLATE_Meta:-}" -e 'd;}' \ -e '/\{\{[ ]*template\.title[ ]*\}\}/{r '"${TEMPLATE_Title:-}" -e 'd;}' \ -e '/\{\{[ ]*template\.document[ ]*\}\}/{r '"${TEMPLATE_Document:-}" -e 'd;}' \ -i.sedbak "${DEST}.html" # inject header, content, footer sed -E \ -e '/\{\{[ ]*template\.header[ ]*\}\}/{r '"${TEMPLATE_Header:-}" -e 'd;}' \ -e '/\{\{[ ]*document\.content[ ]*\}\}/{r '"${DOCUMENT_Content:-}" -e 'd;}' \ -e '/\{\{[ ]*template\.footer[ ]*\}\}/{r '"${TEMPLATE_Footer:-}" -e 'd;}' \ -i.sedbak "${DEST}.html" # inject nav sed -E \ -e '/\{\{[ ]*template\.nav[ ]*\}\}/{r '"${TEMPLATE_Nav:-}" -e 'd;}' \ -i.sedbak "${DEST}.html" # process includes NAVIGATION_PARTIAL="${DOCS_DIR}/$(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 '/\{\{[ ]*include\.navigation[ ]*\}\}/{r '"${DEST}.nav.partial.html" -e 'd;}' \ -i.sedbak "${DEST}.html" else sed -E \ -e '/\{\{[ ]*include\.navigation[ ]*\}\}/d' \ -i.sedbak "${DEST}.html" fi # process template tags TEMPLATE_Scripts="" for SCRIPT in ${TEMPLATE_Scripts_array[@]}; do TEMPLATE_Scripts+="" done TEMPLATE_Styles="" for STYLE in ${TEMPLATE_Styles_array[@]}; do TEMPLATE_Styles+="" done sed -E \ -e 's|\{\{[ ]*template\.scripts[ ]*\}\}|'"${TEMPLATE_Scripts:-}"'|g' \ -e 's|\{\{[ ]*template\.styles[ ]*\}\}|'"${TEMPLATE_Styles:-}"'|g' \ -e 's|\{\{[ ]*document\.title[ ]*\}\}|'"${DOCUMENT_Title:-}"'|g' \ -e 's|\{\{[ ]*document\.type[ ]*\}\}|'"${DOCUMENT_Type:-}"'|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\.license-url rel[ ]*\}\}|'"${BASE_RELPATH}${CONFIG_License%.markdown}.html"'|g' \ -e 's|\{\{[ ]*document\.redirect-url[ ]*\}\}|'"${DOCUMENT_Redirect_URL:-}"'|g' \ -e 's|\{\{[ ]*base\.relpath[ ]*\}\}|'"${BASE_RELPATH:-}"'|g' \ -i.sedbak "${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_Base="${TEMPLATE_DIR}/${TEMPLATE_Base}" TEMPLATE_Meta="${TEMPLATE_DIR}/${TEMPLATE_Meta}" TEMPLATE_Title="${TEMPLATE_DIR}/${TEMPLATE_Title}" TEMPLATE_Document="${TEMPLATE_DIR}/${TEMPLATE_Document}" TEMPLATE_Header="${TEMPLATE_DIR}/${TEMPLATE_Header}" TEMPLATE_Footer="${TEMPLATE_DIR}/${TEMPLATE_Footer}" TEMPLATE_Nav="${TEMPLATE_DIR}/${TEMPLATE_Nav}" TEMPLATE_Redirect="${TEMPLATE_DIR}/${TEMPLATE_Redirect}" TEMPLATE_Fonts_array=($(echo "${TEMPLATE_Fonts}" | sed -e "s/,[ ]*/ /g")) TEMPLATE_Scripts_array=($(echo "${TEMPLATE_Scripts}" | sed -e "s/,[ ]*/ /g")) TEMPLATE_Styles_array=($(echo "${TEMPLATE_Styles}" | sed -e "s/,[ ]*/ /g")) 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 "${DOCS_DIR}" $(GLOBIGNORE='*.gitkeep'; rm -rf "${DOCS_DIR}"/*) cp -R "${SOURCE_DIR}"/* "${DOCS_DIR}"/ for REF in ${TEMPLATE_Fonts_array[@]} ${TEMPLATE_Scripts_array[@]} ${TEMPLATE_Styles_array[@]}; do FILE="${REF%\?*}" mkdir -p "${DOCS_DIR}/${FILE%/*}" cp "${TEMPLATE_DIR}/${FILE}" "${DOCS_DIR}/${FILE}" done # navigation cd "${SOURCE_DIR}" 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="${DOCS_DIR}/${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}/${LANGUAGE}" done cd "${SOURCE_DIR}" done cd "${SELF_DIR}" # sources SOURCES=($(find "${DOCS_DIR}" | 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 "${DOCS_DIR}" -name '*.partial.html' -exec rm -f {} \; fi # done exit 0