diff options
Diffstat (limited to 'build.sh')
| -rwxr-xr-x | build.sh | 914 |
1 files changed, 914 insertions, 0 deletions
diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..b5a9a42 --- /dev/null +++ b/build.sh @@ -0,0 +1,914 @@ +#!/bin/bash +# build.sh - gumx.cc static site generator +# Licensed under the MIT License (see LICENSES/MIT) + +# Must be run directly, not sourced +[[ "$(basename "${0}")" == "build.sh" ]] || { echo "Do not source build.sh"; exit 1; } + +set -euo pipefail + +# --------------------------------------------------------------------------- +# Source mo mustache renderer +# --------------------------------------------------------------------------- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [[ ! -f "${SCRIPT_DIR}/mo" ]]; then + echo "[error] mo not found. Run: curl -sL https://raw.githubusercontent.com/tests-always-included/mo/master/mo -o mo && chmod +x mo" >&2 + exit 1 +fi +# shellcheck source=mo +. "${SCRIPT_DIR}/mo" + +# --------------------------------------------------------------------------- +# Defaults +# --------------------------------------------------------------------------- +VERBOSE=false +DRAFTS=false +FORCE=false +DO_CLEAN=false +DO_PREP=false +DO_SERVE=false + +LIST_COUNT=5 + +TMPDIR_BUILD="$(mktemp -d)" + +# --------------------------------------------------------------------------- +# Cleanup trap +# --------------------------------------------------------------------------- +cleanup() { + rm -rf "${TMPDIR_BUILD}" +} +trap cleanup EXIT INT TERM + +# --------------------------------------------------------------------------- +# CLI parsing +# --------------------------------------------------------------------------- +usage() { + cat <<EOF +Usage: $(basename "$0") [OPTIONS] + +Options: + -h, --help Show this help message + -v, --verbose Verbose output + -d, --drafts Include undated posts (marked [DRAFT]) + -f, --force Force rebuild of all pages + -c, --clean Remove output/ and exit + -p, --prep Optimize untracked images and fix markdown trailing newlines + -s, --serve After building, serve output/ on port 8080 with darkhttpd +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) usage; exit 0 ;; + -v|--verbose) VERBOSE=true ;; + -d|--drafts) DRAFTS=true ;; + -f|--force) FORCE=true ;; + -c|--clean) DO_CLEAN=true ;; + -p|--prep) DO_PREP=true ;; + -s|--serve) DO_SERVE=true ;; + *) echo "[error] Unknown option: $1" >&2; usage; exit 1 ;; + esac + shift +done + +# --------------------------------------------------------------------------- +# --clean +# --------------------------------------------------------------------------- +if [[ "${DO_CLEAN}" == true ]]; then + rm -rf output + echo "[clean] output/ removed" + exit 0 +fi + +# --------------------------------------------------------------------------- +# --prep (absorbed from prep.sh) +# --------------------------------------------------------------------------- +if [[ "${DO_PREP}" == true ]]; then + echo "[prep] Optimizing untracked images..." + for img in $(git ls-files -o images/ 2>/dev/null); do + if identify "${img}" > /dev/null 2>&1; then + mogrify -strip -resize 800x "${img}" + echo "[prep:processed] ${img}" + else + echo "[prep:ignored] ${img}" + fi + done + + echo "[prep] Adding trailing newlines to .md files..." + local _prep_list + _prep_list="$(mktemp)" + find . -name '*.md' -not -path './.git/*' -not -path './mo/*' -print0 > "${_prep_list}" + while IFS= read -r -d '' f; do + if [[ -s "${f}" ]]; then + last_char="$(tail -c1 "${f}")" + if [[ "${last_char}" != "" ]]; then + printf '\n' >> "${f}" + echo "[prep:newline] ${f}" + fi + fi + done < "${_prep_list}" + rm -f "${_prep_list}" +fi + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- +log() { + if [[ "${VERBOSE}" == true ]]; then echo "$*"; fi +} + +# needs_rebuild src dst [template] +# Returns 0 (true / do rebuild) if dst is missing or older than src/template +# Returns 1 (false / skip) if dst is up-to-date and --force is not set +needs_rebuild() { + local src="$1" dst="$2" tmpl="${3:-}" + [[ "${FORCE}" == true ]] && return 0 + [[ ! -f "${dst}" ]] && return 0 + [[ "${src}" -nt "${dst}" ]] && return 0 + [[ -n "${tmpl}" && -f "${tmpl}" && "${tmpl}" -nt "${dst}" ]] && return 0 + return 1 +} + +# render_page template outfile +# All variables exported before calling this function are available. +render_page() { + local template="$1" + local outfile="$2" + mkdir -p "$(dirname "${outfile}")" + # Copy template into partials/ so mo resolves {{> partial}} relative to that dir, + # then remove the copy. Avoids any /dev/stdin or /dev/fd dependency. + local _tmpl_name="${template##templates/}" + cp "${template}" "templates/partials/${_tmpl_name}" + (cd templates/partials && mo "${_tmpl_name}") > "${outfile}" + rm -f "templates/partials/${_tmpl_name}" +} + +# build_breadcrumbs path -> sets BREADCRUMBS variable +build_breadcrumbs() { + local path="$1" + BREADCRUMBS="" + local components="${path}" + while [[ "${components}" != "." ]]; do + BREADCRUMBS=" / <a href=\"/${components}\">$(basename "${components}")</a>${BREADCRUMBS}" + components="$(dirname "${components}")" + done +} + +# get_frontmatter key file +get_frontmatter() { + local key="$1" file="$2" + # lowdown extracts YAML frontmatter fields + lowdown -X "${key}" "${file}" 2>/dev/null || true +} + +# check_has_code content_html -> sets HAS_CODE ("true" or "") +check_has_code() { + local html="$1" + if echo "${html}" | grep -qE '<pre><code|<code class='; then + HAS_CODE="true" + else + HAS_CODE="" + fi +} + +# render_content_page src_md out_html template_file [header_extra] [lang] +render_content_page() { + local src_md="$1" + local out_html="$2" + local template_file="$3" + local header_extra="${4:-}" + local lang="${5:-en}" + + if ! needs_rebuild "${src_md}" "${out_html}" "${template_file}"; then + log "[skip] ${out_html}" + return + fi + + local page_title + page_title="$(get_frontmatter title "${src_md}")" + + local content + content="$(lowdown "${src_md}")" + + check_has_code "${content}" + + # Derive output path without "output/" prefix and without "/index.html" suffix + local rel_path + rel_path="${out_html#output/}" # e.g. blog/hello/index.html + rel_path="${rel_path%/index.html}" # e.g. blog/hello + + build_breadcrumbs "${rel_path}" + + # Export variables for mo + export PAGE_TITLE="${page_title}" + export CONTENT="${content}" + export BREADCRUMBS + export HEADER_EXTRA="${header_extra}" + export META_DATE="${header_extra}" + export META_DESCRIPTION="" + export HAS_CODE + export LANG="${lang}" + export DIR="" + [[ "${lang}" == "ar" ]] && export DIR="rtl" + export IS_HOME="" + export ARCHIVE_TITLE="" + + render_page "${template_file}" "${out_html}" + echo "[built] ${out_html}" +} + +# --------------------------------------------------------------------------- +# process_content dir template_type archive_title date_required +# Handles blog, talks, code, recipes +# --------------------------------------------------------------------------- +process_content() { + local dir="$1" + local template_type="$2" + local archive_title="$3" + local date_required="$4" # "true" or "false" + + if [[ ! -d "${dir}" ]]; then + log "[info] ${dir}/ directory not found, skipping" + return + fi + + local list_file="${TMPDIR_BUILD}/${dir}.list.md" + : > "${list_file}" + + local _find_list="${TMPDIR_BUILD}/${dir}.find.list" + find "${dir}" -mindepth 1 -maxdepth 1 -name '*.md' -not -name '.*' -print0 | sort -z > "${_find_list}" + + while IFS= read -r -d '' src; do + local filename + filename="$(basename "${src}")" + local title date lang event header_extra + title="$(get_frontmatter title "${src}")" + date="$(get_frontmatter date "${src}")" + lang="$(get_frontmatter lang "${src}")" + event="$(get_frontmatter event "${src}")" + + # Derive URL path + local rel="${src#${dir}/}" # e.g. hello.md + local slug="${rel%.md}" # e.g. hello + local url_path="/${dir}/${slug}" + local out_dir="output/${dir}/${slug}" + local out_html="${out_dir}/index.html" + + # Draft handling + if [[ "${date_required}" == "true" && -z "${date}" ]]; then + if [[ "${DRAFTS}" == true ]]; then + title="[DRAFT] ${title}" + printf -- '- [DRAFT] [%s](%s)\n' "${title}" "${url_path}" >> "${list_file}" + echo "[draft] ${src}" + else + log "[skipped-draft] ${src}" + continue + fi + else + if [[ -n "${date}" ]]; then + if [[ -n "${event}" ]]; then + printf -- '- [%s] [%s](%s) @ %s\n' "${date}" "${title}" "${url_path}" "${event}" >> "${list_file}" + else + printf -- '- [%s] [%s](%s)\n' "${date}" "${title}" "${url_path}" >> "${list_file}" + fi + else + printf -- '- [%s](%s)\n' "${title}" "${url_path}" >> "${list_file}" + fi + fi + + # Build the page + header_extra="${date}" + [[ -n "${event}" ]] && header_extra="${date}${date:+ — }${event}" + + local tmpl="templates/${template_type}.html" + render_content_page "${src}" "${out_html}" "${tmpl}" "${header_extra}" "${lang:-en}" + echo "[${template_type}] ${url_path}" + + done < "${_find_list}" + + # Build archive page + local archive_md="${TMPDIR_BUILD}/${dir}-archive.md" + printf '# %s\n\n' "${archive_title}" > "${archive_md}" + sort -r "${list_file}" >> "${archive_md}" + + local archive_html="output/${dir}/index.html" + export ARCHIVE_TITLE="${archive_title}" + export PAGE_TITLE="${archive_title}" + export CONTENT + CONTENT="$(lowdown "${archive_md}")" + export BREADCRUMBS=" / <a href=\"/${dir}\">${dir}</a>" + export HEADER_EXTRA="" + export META_DATE="" + export META_DESCRIPTION="" + export HAS_CODE="" + export LANG="en" + export DIR="" + export IS_HOME="" + + if needs_rebuild "${list_file}" "${archive_html}" "templates/archive.html"; then + render_page "templates/archive.html" "${archive_html}" + echo "[archive] ${archive_html}" + else + log "[skip] ${archive_html}" + fi +} + +# --------------------------------------------------------------------------- +# process_projects +# --------------------------------------------------------------------------- +process_projects() { + local list_file="${TMPDIR_BUILD}/projects.list.md" + : > "${list_file}" + + if [[ ! -d "projects" ]]; then + echo "[warning] projects/ directory not found" + return + fi + + local _proj_list="${TMPDIR_BUILD}/projects.find.list" + find "projects" -mindepth 1 -maxdepth 1 -name '*.md' -not -name '.*' -print0 | sort -z > "${_proj_list}" + + while IFS= read -r -d '' src; do + local title url demo sources + title="$(get_frontmatter title "${src}")" + url="$(get_frontmatter url "${src}")" + demo="$(get_frontmatter demo "${src}")" + sources_raw="$(get_frontmatter sources "${src}")" + + local slug + slug="$(basename "${src}" .md)" + local url_path="/projects/${slug}" + local out_html="output/projects/${slug}/index.html" + local tmpl="templates/project.html" + + # Build sources HTML links from "name:url,name:url" pairs + local sources_html="" + if [[ -n "${sources_raw}" ]]; then + IFS=',' read -ra pairs <<< "${sources_raw}" + for pair in "${pairs[@]}"; do + local sname surl + sname="${pair%%:*}" + surl="${pair#*:}" + # Handle URLs with double-colon (http://) + # The format is name:scheme://rest — re-join after first ':' + surl="${pair#${sname}:}" + sources_html+="<a href=\"${surl}\">${sname}</a> " + done + sources_html="${sources_html% }" + fi + + # Archive entry + printf -- '- [%s](%s)\n' "${title}" "${url_path}" >> "${list_file}" + + if ! needs_rebuild "${src}" "${out_html}" "${tmpl}"; then + log "[skip] ${out_html}" + continue + fi + + local content + content="$(lowdown "${src}")" + check_has_code "${content}" + + build_breadcrumbs "projects/${slug}" + + export PAGE_TITLE="${title}" + export CONTENT="${content}" + export BREADCRUMBS + export HEADER_EXTRA="" + export META_DATE="" + export META_DESCRIPTION="" + export HAS_CODE + export LANG="en" + export DIR="" + export IS_HOME="" + export PROJECT_URL="${url}" + export PROJECT_DEMO="${demo}" + export PROJECT_SOURCES="${sources_html}" + export ARCHIVE_TITLE="" + + render_page "${tmpl}" "${out_html}" + echo "[project] ${url_path}" + + done < "${_proj_list}" + + # Archive + local archive_md="${TMPDIR_BUILD}/projects-archive.md" + printf '# projects\n\n' > "${archive_md}" + cat "${list_file}" >> "${archive_md}" + + local archive_html="output/projects/index.html" + export ARCHIVE_TITLE="projects" + export PAGE_TITLE="projects" + CONTENT="$(lowdown "${archive_md}")" + export CONTENT + export BREADCRUMBS=" / <a href=\"/projects\">projects</a>" + export HEADER_EXTRA="" + export META_DATE="" + export META_DESCRIPTION="" + export HAS_CODE="" + export LANG="en" + export DIR="" + export IS_HOME="" + export PROJECT_URL="" + export PROJECT_DEMO="" + export PROJECT_SOURCES="" + + if needs_rebuild "${list_file}" "${archive_html}" "templates/archive.html"; then + render_page "templates/archive.html" "${archive_html}" + echo "[archive] ${archive_html}" + else + log "[skip] ${archive_html}" + fi +} + +# --------------------------------------------------------------------------- +# process_slides +# --------------------------------------------------------------------------- +process_slides() { + if [[ ! -d "slides" ]]; then + log "[info] slides/ directory not found, skipping" + return + fi + + local list_file="${TMPDIR_BUILD}/slides.list.md" + : > "${list_file}" + + local _slides_list="${TMPDIR_BUILD}/slides.find.list" + find "slides" -mindepth 1 -maxdepth 1 -name '*.md' -not -name '.*' -print0 | sort -z > "${_slides_list}" + + while IFS= read -r -d '' src; do + local title date theme + title="$(get_frontmatter title "${src}")" + date="$(get_frontmatter date "${src}")" + theme="$(get_frontmatter theme "${src}")" + [[ -z "${theme}" ]] && theme="black" + + local slug + slug="$(basename "${src}" .md)" + local url_path="/slides/${slug}" + local out_html="output/slides/${slug}/index.html" + local tmpl="templates/slides.html" + + if [[ -n "${date}" ]]; then + printf -- '- [%s] [%s](%s)\n' "${date}" "${title}" "${url_path}" >> "${list_file}" + else + printf -- '- [%s](%s)\n' "${title}" "${url_path}" >> "${list_file}" + fi + + if ! needs_rebuild "${src}" "${out_html}" "${tmpl}"; then + log "[skip] ${out_html}" + continue + fi + + # Split markdown on '---' slide separators into <section data-markdown> blocks + local slides_content + slides_content="$(awk ' + BEGIN { in_slide=0; buf="" } + /^---$/ { + if (in_slide) { + print "<section data-markdown><textarea data-template>" buf "</textarea></section>" + buf="" + } else { + in_slide=1 + } + next + } + in_slide { buf = buf "\n" $0 } + END { + if (buf != "") { + print "<section data-markdown><textarea data-template>" buf "</textarea></section>" + } + } + ' "${src}")" + + mkdir -p "output/slides/${slug}" + + export TITLE="${title}" + export THEME="${theme}" + export SLIDES_CONTENT="${slides_content}" + + cp "templates/slides.html" "templates/partials/slides.html" + (cd templates/partials && mo "slides.html") > "${out_html}" + rm -f "templates/partials/slides.html" + echo "[slides] ${url_path}" + + done < "${_slides_list}" + + # Archive listing + local archive_md="${TMPDIR_BUILD}/slides-archive.md" + printf '# slides\n\n' > "${archive_md}" + sort -r "${list_file}" >> "${archive_md}" + + local archive_html="output/slides/index.html" + export ARCHIVE_TITLE="slides" + export PAGE_TITLE="slides" + CONTENT="$(lowdown "${archive_md}")" + export CONTENT + export BREADCRUMBS=" / <a href=\"/slides\">slides</a>" + export HEADER_EXTRA="" + export META_DATE="" + export META_DESCRIPTION="" + export HAS_CODE="" + export LANG="en" + export DIR="" + export IS_HOME="" + + if needs_rebuild "${list_file}" "${archive_html}" "templates/archive.html"; then + render_page "templates/archive.html" "${archive_html}" + echo "[archive] ${archive_html}" + else + log "[skip] ${archive_html}" + fi +} + +# --------------------------------------------------------------------------- +# process_garden +# --------------------------------------------------------------------------- +process_garden() { + local dir="${1:-garden}" + local garden_list="${TMPDIR_BUILD}/${dir}.garden.list.md" + : > "${garden_list}" + + local _garden_find="${TMPDIR_BUILD}/${dir//\//_}.find.list" + find "${dir}" -mindepth 1 -maxdepth 1 -not -name '.*' -print0 | sort -z > "${_garden_find}" + + while IFS= read -r -d '' node; do + if [[ -d "${node}" ]]; then + local subnode_title + subnode_title="$(get_frontmatter title "${node}/index.md" 2>/dev/null || true)" + [[ -z "${subnode_title}" ]] && subnode_title="$(basename "${node}")" + printf -- '- [%s/](/%s)\n' "${subnode_title}" "${node}" >> "${garden_list}" + process_garden "${node}" + else + if [[ "${node}" == "${node%.md}" ]]; then + # Non-markdown asset — copy verbatim + cp -a --parent "${node}" output/ + elif [[ "$(basename "${node}")" != "index.md" ]]; then + local title date lang + title="$(get_frontmatter title "${node}")" + [[ -z "${title}" ]] && title="$(basename "${node}" .md)" + date="$(get_frontmatter date "${node}")" + lang="$(get_frontmatter lang "${node}")" + + local rel="${node#${dir}/}" + local slug="${rel%.md}" + local url_path="/${dir}/${slug}" + local out_html="output/${dir}/${slug}/index.html" + local tmpl="templates/garden.html" + + render_content_page "${node}" "${out_html}" "${tmpl}" "${date}" "${lang:-en}" + printf -- '- [%s](%s)\n' "${title}" "${url_path}" >> "${garden_list}" + garden_count=$(( garden_count + 1 )) + fi + fi + done < "${_garden_find}" + + # Build / update the index for this directory + local garden_index_md="${TMPDIR_BUILD}/${dir//\//_}.index.md" + local garden_title + if [[ -f "${dir}/index.md" ]]; then + garden_title="$(get_frontmatter title "${dir}/index.md")" + # Append the listing to a copy of the index + cp "${dir}/index.md" "${garden_index_md}" + else + garden_title="$(basename "${dir}")" + printf '# %s\n\n' "${garden_title}" > "${garden_index_md}" + fi + printf '\n\n' >> "${garden_index_md}" + cat "${garden_list}" >> "${garden_index_md}" + + local out_html="output/${dir}/index.html" + local tmpl="templates/garden.html" + + if needs_rebuild "${garden_index_md}" "${out_html}" "${tmpl}"; then + local content + content="$(lowdown "${garden_index_md}")" + check_has_code "${content}" + build_breadcrumbs "${dir}" + + export PAGE_TITLE="${garden_title}" + export CONTENT="${content}" + export BREADCRUMBS + export HEADER_EXTRA="" + export META_DATE="" + export META_DESCRIPTION="" + export HAS_CODE + export LANG="en" + export DIR="" + export IS_HOME="" + export ARCHIVE_TITLE="" + + render_page "${tmpl}" "${out_html}" + echo "[garden] output/${dir}/index.html" + else + log "[skip] output/${dir}/index.html" + fi +} + +# --------------------------------------------------------------------------- +# generate_rss_feed +# --------------------------------------------------------------------------- +generate_rss_feed() { + local list_file="${TMPDIR_BUILD}/blog.list.md" + [[ ! -f "${list_file}" ]] && return + + local out="output/feed.xml" + mkdir -p output + + { + cat <<'RSSHEAD' +<?xml version="1.0" encoding="UTF-8"?> +<rss version="2.0"> + <channel> + <title>gumx</title> + <link>https://gumx.cc</link> + <description>gumx blog</description> + <language>en</language> +RSSHEAD + + # Top 20 entries, sorted newest first + sort -r "${list_file}" | head -n 20 | while IFS= read -r line; do + # Line format: - [DATE] [TITLE](URL) + local date title url + date="$(echo "${line}" | grep -oP '\[\K[0-9]{4}-[0-9]{2}-[0-9]{2}(?=\])')" + title="$(echo "${line}" | grep -oP '\]\s+\[\K[^\]]+(?=\]\()')" + url="$(echo "${line}" | grep -oP '\]\(\K[^)]+(?=\))')" + [[ -z "${date}" || -z "${title}" || -z "${url}" ]] && continue + + # RFC 2822 date from YYYY-MM-DD + local rfc_date + rfc_date="$(date -d "${date}" -R 2>/dev/null || date -jf '%Y-%m-%d' "${date}" '+%a, %d %b %Y 00:00:00 +0000' 2>/dev/null || echo "${date}")" + + cat <<ITEM + <item> + <title>${title}</title> + <link>https://gumx.cc${url}</link> + <guid>https://gumx.cc${url}</guid> + <pubDate>${rfc_date}</pubDate> + </item> +ITEM + done + + echo " </channel>" + echo "</rss>" + } > "${out}" + + echo "[rss] ${out}" +} + +# generate_sitemap +# --------------------------------------------------------------------------- +generate_sitemap() { + local out="output/sitemap.xml" + + { + echo '<?xml version="1.0" encoding="UTF-8"?>' + echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' + find output -name 'index.html' | sort | while read -r html; do + local path + path="${html#output}" # /blog/hello/index.html + path="${path%index.html}" # /blog/hello/ + echo " <url><loc>https://gumx.cc${path}</loc></url>" + done + echo '</urlset>' + } > "${out}" + + echo "[sitemap] ${out}" +} + +# --------------------------------------------------------------------------- +# build_includes +# --------------------------------------------------------------------------- +build_includes() { + local inc_md="${TMPDIR_BUILD}/includes.md" + printf '# includes\n\n' > "${inc_md}" + for inc in $(find includes -type f ! -name '.*' | sort); do + printf -- '- [%s](/%s)\n' "$(basename "${inc}")" "${inc}" >> "${inc_md}" + done + + local out_html="output/includes/index.html" + local tmpl="templates/base.html" + + if needs_rebuild "${inc_md}" "${out_html}" "${tmpl}"; then + local content + content="$(lowdown "${inc_md}")" + check_has_code "${content}" + + export PAGE_TITLE="includes" + export CONTENT="${content}" + export BREADCRUMBS=" / <a href=\"/includes\">includes</a>" + export HEADER_EXTRA="" + export META_DATE="" + export META_DESCRIPTION="" + export HAS_CODE + export LANG="en" + export DIR="" + export IS_HOME="" + export ARCHIVE_TITLE="" + + render_page "${tmpl}" "${out_html}" + echo "[built] ${out_html}" + else + log "[skip] ${out_html}" + fi +} + +# --------------------------------------------------------------------------- +# build_license +# --------------------------------------------------------------------------- +build_license() { + local src="license.md" + local out_html="output/license/index.html" + local tmpl="templates/base.html" + + if needs_rebuild "${src}" "${out_html}" "${tmpl}"; then + local content + content="$(lowdown "${src}")" + check_has_code "${content}" + + export PAGE_TITLE="license" + export CONTENT="${content}" + export BREADCRUMBS=" / <a href=\"/license\">license</a>" + export HEADER_EXTRA="" + export META_DATE="" + export META_DESCRIPTION="" + export HAS_CODE + export LANG="en" + export DIR="" + export IS_HOME="" + export ARCHIVE_TITLE="" + + render_page "${tmpl}" "${out_html}" + echo "[built] ${out_html}" + else + log "[skip] ${out_html}" + fi +} + +# --------------------------------------------------------------------------- +# build_home +# --------------------------------------------------------------------------- +build_home() { + local src="index.md" + local out_html="output/index.html" + local tmpl="templates/home.html" + + # We always rebuild home (it aggregates counts/lists from other passes) + local home_md="${TMPDIR_BUILD}/index.home.md" + cp "${src}" "${home_md}" + + # Inject garden count + sed -i "s/\[%garden\]/${garden_count}/g" "${home_md}" + + # Recent blog entries + { + printf '\n\n## recent entries:\n\n' + local blog_list="${TMPDIR_BUILD}/blog.list.md" + if [[ -f "${blog_list}" && -s "${blog_list}" ]]; then + sort -r "${blog_list}" | head -n "${LIST_COUNT}" + local total + total="$(wc -l < "${blog_list}")" + [[ "${total}" -gt "${LIST_COUNT}" ]] && printf -- '- [all ..](/blog)\n' + else + printf -- '- stuff I wrote should be listed here\n' + fi + } >> "${home_md}" + + # Recent talks + { + printf '\n\n## talks & workshops:\n\n' + local talks_list="${TMPDIR_BUILD}/talks.list.md" + if [[ -f "${talks_list}" && -s "${talks_list}" ]]; then + sort -r "${talks_list}" | head -n "${LIST_COUNT}" + local total + total="$(wc -l < "${talks_list}")" + [[ "${total}" -gt "${LIST_COUNT}" ]] && printf -- '- [all ..](/talks)\n' + else + printf -- '- stuff I said should be listed here\n' + fi + } >> "${home_md}" + + # Projects + { + printf '\n\n## projects:\n\n' + local proj_list="${TMPDIR_BUILD}/projects.list.md" + if [[ -f "${proj_list}" && -s "${proj_list}" ]]; then + head -n "${LIST_COUNT}" "${proj_list}" + local total + total="$(wc -l < "${proj_list}")" + [[ "${total}" -gt "${LIST_COUNT}" ]] && printf -- '- [all ..](/projects)\n' + else + printf -- '- stuff I made should be listed here\n' + fi + } >> "${home_md}" + + # Code snippets + { + local code_list="${TMPDIR_BUILD}/code.list.md" + if [[ -f "${code_list}" && -s "${code_list}" ]]; then + printf '\n\n## code snippets:\n\n' + head -n "${LIST_COUNT}" "${code_list}" + local total + total="$(wc -l < "${code_list}")" + [[ "${total}" -gt "${LIST_COUNT}" ]] && printf -- '- [all ..](/code)\n' + fi + } >> "${home_md}" + + # Recipes + { + local recipes_list="${TMPDIR_BUILD}/recipes.list.md" + if [[ -f "${recipes_list}" && -s "${recipes_list}" ]]; then + printf '\n\n## recipes:\n\n' + head -n "${LIST_COUNT}" "${recipes_list}" + local total + total="$(wc -l < "${recipes_list}")" + [[ "${total}" -gt "${LIST_COUNT}" ]] && printf -- '- [all ..](/recipes)\n' + fi + } >> "${home_md}" + + # Slides + { + local slides_list="${TMPDIR_BUILD}/slides.list.md" + if [[ -f "${slides_list}" && -s "${slides_list}" ]]; then + printf '\n\n## slides:\n\n' + sort -r "${slides_list}" | head -n "${LIST_COUNT}" + local total + total="$(wc -l < "${slides_list}")" + [[ "${total}" -gt "${LIST_COUNT}" ]] && printf -- '- [all ..](/slides)\n' + fi + } >> "${home_md}" + + local content + content="$(lowdown "${home_md}")" + check_has_code "${content}" + + export PAGE_TITLE="" + export CONTENT="${content}" + export BREADCRUMBS="" + export HEADER_EXTRA="" + export META_DATE="" + export META_DESCRIPTION="" + export HAS_CODE + export LANG="en" + export DIR="" + export IS_HOME="true" + export ARCHIVE_TITLE="" + + render_page "${tmpl}" "${out_html}" + echo "[built] ${out_html}" +} + +# --------------------------------------------------------------------------- +# Main build +# --------------------------------------------------------------------------- + +mkdir -p output + +# Copy static assets +cp -ar static/* output/ 2>/dev/null || true +cp -ar includes output/ 2>/dev/null || true +cp -ar images output/ 2>/dev/null || true +echo "[static] assets copied" + +# Content sections +process_content "blog" "blog" "blog archive" "true" +process_content "talks" "talk" "talks & workshops" "true" +process_content "code" "code" "code snippets" "false" +process_content "recipes" "recipe" "recipes" "false" + +# (list files already live in TMPDIR_BUILD with canonical names) + +process_projects +process_slides + +# Garden (count pages for home page injection) +garden_count=0 +process_garden garden + +# Includes + license +build_includes +build_license + +# Home (uses all collected list files) +build_home + +# RSS + Sitemap +generate_rss_feed +generate_sitemap + +echo "" +echo "[done] Build complete. Output in output/" + +# --------------------------------------------------------------------------- +# --serve +# --------------------------------------------------------------------------- +if [[ "${DO_SERVE}" == true ]]; then + if command -v darkhttpd &>/dev/null; then + echo "[serve] Starting darkhttpd on http://localhost:8080" + darkhttpd output --port 8080 + else + echo "[error] darkhttpd not found. Install it to use --serve." >&2 + exit 1 + fi +fi |
