#!/bin/bash # Render a site from site.yaml + template # Usage: ./render.sh sites/example.de/site.yaml templates/person-dark.html # Outputs rendered HTML to stdout set -euo pipefail SITE_YAML="$1" TEMPLATE_FILE="$2" SITE_DIR=$(dirname "$SITE_YAML") SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) # Check dependencies if ! command -v yq &>/dev/null; then echo "ERROR: yq is required. Install: https://github.com/mikefarah/yq" >&2 exit 1 fi # Read site config domain=$(yq -r '.domain' "$SITE_YAML") title=$(yq -r '.title // .domain' "$SITE_YAML") description=$(yq -r '.description // ""' "$SITE_YAML") lang=$(yq -r '.lang // "de"' "$SITE_YAML") favicon=$(yq -r '.vars.favicon // ""' "$SITE_YAML") name=$(yq -r '.vars.name // .title // ""' "$SITE_YAML") role=$(yq -r '.vars.role // ""' "$SITE_YAML") initials=$(yq -r '.vars.initials // ""' "$SITE_YAML") tagline=$(yq -r '.vars.tagline // ""' "$SITE_YAML") accent=$(yq -r '.vars.accent // "#c9a84c"' "$SITE_YAML") accent_light=$(yq -r '.vars.accent_light // "rgba(201, 168, 76, 0.1)"' "$SITE_YAML") font_primary=$(yq -r '.vars.font_primary // "Inter"' "$SITE_YAML") font_secondary=$(yq -r '.vars.font_secondary // "Inter"' "$SITE_YAML") emoji=$(yq -r '.vars.emoji // ""' "$SITE_YAML") cta_text=$(yq -r '.vars.cta.text // ""' "$SITE_YAML") cta_href=$(yq -r '.vars.cta.href // "#"' "$SITE_YAML") year=$(date +%Y) # i18n: helper to generate data-de/data-en attribute string i18n_attrs() { local de="$1" en="$2" if [ -n "$en" ] && [ "$en" != "null" ]; then de=$(echo "$de" | sed 's/"/\"/g') en=$(echo "$en" | sed 's/"/\"/g') echo " data-de=\"${de}\" data-en=\"${en}\"" fi } # i18n: read English overrides title_en=$(yq -r '.title_en // ""' "$SITE_YAML") description_en=$(yq -r '.description_en // ""' "$SITE_YAML") role_en=$(yq -r '.vars.role_en // ""' "$SITE_YAML") tagline_en=$(yq -r '.vars.tagline_en // ""' "$SITE_YAML") cta_text_en=$(yq -r '.vars.cta.text_en // ""' "$SITE_YAML") # i18n: build attribute strings for simple fields title_i18n=$(i18n_attrs "$title" "$title_en") description_i18n=$(i18n_attrs "$description" "$description_en") role_i18n=$(i18n_attrs "$role" "$role_en") tagline_i18n=$(i18n_attrs "$tagline" "$tagline_en") cta_i18n=$(i18n_attrs "$cta_text" "$cta_text_en") # Build tags HTML tags_html="" tag_count=$(yq -r '.vars.tags | length' "$SITE_YAML" 2>/dev/null || echo "0") if [ "$tag_count" -gt 0 ]; then for i in $(seq 0 $((tag_count - 1))); do tag=$(yq -r ".vars.tags[$i]" "$SITE_YAML") tag_en=$(yq -r ".vars.tags_en[$i] // \"\"" "$SITE_YAML" 2>/dev/null || echo "") tag_i18n="" if [ -n "$tag_en" ] && [ "$tag_en" != "null" ]; then tag_i18n="$(i18n_attrs "$tag" "$tag_en")" fi tags_html="${tags_html} ${tag}\n" done fi # Build sections HTML sections_html="" section_count=$(yq -r '.vars.sections | length' "$SITE_YAML" 2>/dev/null || echo "0") if [ "$section_count" -gt 0 ]; then for i in $(seq 0 $((section_count - 1))); do sec_type=$(yq -r ".vars.sections[$i].type" "$SITE_YAML") sec_title=$(yq -r ".vars.sections[$i].title // \"\"" "$SITE_YAML") if [ "$sec_type" = "features" ]; then sec_title_en=$(yq -r ".vars.sections[$i].title_en // \"\"" "$SITE_YAML") sections_html="${sections_html}
${item_desc}
\n" sections_html="${sections_html}${bio}
\n" sections_html="${sections_html}/g' | sed '1s/^/
/' | sed '$s/$/<\/p>/') content_en=$(yq -r '.vars.content_en // ""' "$SITE_YAML") if [ -n "$content_en" ] && [ "$content_en" != "null" ]; then content_html_en=$(echo "$content_en" | sed 's/^$/<\/p>
/g' | sed '1s/^/
/' | sed '$s/$/<\/p>/') content_i18n=$(i18n_attrs "$content_html" "$content_html_en") fi fi # Read template file and extract CSS/body sections template_content=$(cat "$TEMPLATE_FILE") # Extract template CSS (between markers) template_css=$(echo "$template_content" | sed -n '/{{template_css_start}}/,/{{template_css_end}}/p' | sed '1d;$d') # Extract template body (between markers) template_body=$(echo "$template_content" | sed -n '/{{template_body_start}}/,/{{template_body_end}}/p' | sed '1d;$d') # Read base template base=$(cat "$SCRIPT_DIR/templates/base.html") # Read shared CSS css_variables=$(cat "$SCRIPT_DIR/shared/css/variables.css") css_responsive=$(cat "$SCRIPT_DIR/shared/css/responsive.css") css_animations=$(cat "$SCRIPT_DIR/shared/css/animations.css") css_noise=$(cat "$SCRIPT_DIR/shared/css/noise.css") fonts=$(cat "$SCRIPT_DIR/shared/fonts.html") # Additional font links if non-Inter fonts are used extra_fonts="" if [ "$font_primary" != "Inter" ]; then font_url=$(echo "$font_primary" | tr ' ' '+') extra_fonts="${extra_fonts} \n" fi if [ "$font_secondary" != "Inter" ] && [ "$font_secondary" != "$font_primary" ]; then font_url=$(echo "$font_secondary" | tr ' ' '+') extra_fonts="${extra_fonts} \n" fi fonts="${fonts}${extra_fonts}" # Assemble: substitute into base template output="$base" # Replace shared includes output=$(echo "$output" | sed "s|{{fonts}}|$(echo "$fonts" | sed 's/[&/\]/\\&/g; s/$/\\/' | sed '$ s/\\$//')|") # For CSS blocks, use temp files to handle multiline tmpdir=$(mktemp -d) trap 'rm -rf "$tmpdir"' EXIT echo "$css_variables" > "$tmpdir/css_variables" echo "$css_responsive" > "$tmpdir/css_responsive" echo "$css_animations" > "$tmpdir/css_animations" echo "$css_noise" > "$tmpdir/css_noise" echo "$template_css" > "$tmpdir/template_css" printf '%b' "$template_body" > "$tmpdir/template_body" # Use awk for reliable multiline substitution awk -v vars="$tmpdir/css_variables" \ -v resp="$tmpdir/css_responsive" \ -v anim="$tmpdir/css_animations" \ -v noise="$tmpdir/css_noise" \ -v tcss="$tmpdir/template_css" \ -v tbody="$tmpdir/template_body" \ -v fonts_file=<(echo "$fonts") \ ' function read_file(path, line, content) { content = "" while ((getline line < path) > 0) content = content line "\n" close(path) return content } BEGIN { cv = read_file(vars) cr = read_file(resp) ca = read_file(anim) cn = read_file(noise) tc = read_file(tcss) tb = read_file(tbody) } { gsub(/\{\{css_variables\}\}/, cv) gsub(/\{\{css_responsive\}\}/, cr) gsub(/\{\{css_animations\}\}/, ca) gsub(/\{\{css_noise\}\}/, cn) gsub(/\{\{template_css\}\}/, tc) gsub(/\{\{body\}\}/, tb) print } ' <<< "$output" | sed \ -e "s|{{title}}|${title}|g" \ -e "s|{{description}}|${description}|g" \ -e "s|{{lang}}|${lang}|g" \ -e "s|{{favicon}}|${favicon}|g" \ -e "s|{{name}}|${name}|g" \ -e "s|{{role}}|${role}|g" \ -e "s|{{initials}}|${initials}|g" \ -e "s|{{tagline}}|${tagline}|g" \ -e "s|{{accent}}|${accent}|g" \ -e "s|{{accent_light}}|${accent_light}|g" \ -e "s|{{font_primary}}|${font_primary}|g" \ -e "s|{{font_secondary}}|${font_secondary}|g" \ -e "s|{{emoji}}|${emoji}|g" \ -e "s|{{cta_text}}|${cta_text}|g" \ -e "s|{{cta_href}}|${cta_href}|g" \ -e "s|{{year}}|${year}|g" \ -e "s|{{domain}}|${domain}|g" \ -e "s|{{title_i18n}}|$(echo "$title_i18n" | sed 's/[&/\]/\\&/g')|g" \ -e "s|{{description_i18n}}|$(echo "$description_i18n" | sed 's/[&/\]/\\&/g')|g" \ -e "s|{{role_i18n}}|$(echo "$role_i18n" | sed 's/[&/\]/\\&/g')|g" \ -e "s|{{tagline_i18n}}|$(echo "$tagline_i18n" | sed 's/[&/\]/\\&/g')|g" \ -e "s|{{cta_i18n}}|$(echo "$cta_i18n" | sed 's/[&/\]/\\&/g')|g" \ -e "s|{{content_i18n}}|$(echo "$content_i18n" | sed 's/[&/\]/\\&/g')|g" | \ sed "s|{{tags_html}}|$(printf '%b' "$tags_html" | sed 's/[&/\]/\\&/g; s/$/\\/' | sed '$ s/\\$//')|g" | \ sed "s|{{sections_html}}|$(printf '%b' "$sections_html" | sed 's/[&/\]/\\&/g; s/$/\\/' | sed '$ s/\\$//')|g" | \ sed "s|{{content_html}}|$(printf '%b' "$content_html" | sed 's/[&/\]/\\&/g; s/$/\\/' | sed '$ s/\\$//')|g"