qsgen3/bin/qsgen3
Stig-Ørjan Smelror c470ac40c0 feat: Implement robust theme system and document architecture
- Implement flexible theme switching via site.conf (site_theme, site_theme_css_file).
- Ensure correct copying of theme static assets, with theme assets overriding root assets.
- Resolve CSS linking issues by checking file existence after static copy and using correct paths for Pandoc.
- Refactor path construction to prevent duplication when using absolute/relative output paths.
- Create comprehensive how-it-works.md detailing system architecture, theme creation, and overall workflow.
- Clarify design philosophy: qsgen3 remains design-agnostic, only linking main theme CSS automatically.
2025-05-31 00:00:21 +02:00

1111 lines
49 KiB
Bash
Executable File

#!/usr/bin/env zsh
# qsgen3 - A Minimal Markdown Static Site Generator
# Version: 0.1.0
QSGEN_NAME="qsgen3"
VERSION="0.2.0"
# Author: Your Name/AI Assistant
# Strict mode
set -euo pipefail
# Locale and umask for robustness and security
LC_ALL=C
LANG=C
umask 0022
# --- Configuration ---
# Associative array to hold configuration values
typeset -A QSG_CONFIG
# --- Script Paths ---
# Get the directory where the script is located - SCRIPT_DIR might still be useful for finding layouts if not overridden by config
SCRIPT_DIR="$(cd "$(dirname "${(%):-%x}")" && pwd)"
# Project root is the current working directory
PROJECT_ROOT="$PWD"
# Default config file name; full path will be resolved in main after option parsing
CONFIG_FILE_NAME="site.conf"
# Full path to the configuration file, to be determined in main
CONFIG_FILE=""
# --- Usage Information ---
_usage() {
cat <<EOF
Usage: $QSGEN_NAME [options]
$QSGEN_NAME - A Minimal Markdown Static Site Generator (Version $VERSION)
Options:
-c, --config <file> Specify a custom configuration file.
(Default: site.conf in the project root)
-V, --version Show version information and exit.
-h, --help Show this help message and exit.
EOF
exit 0
}
# --- Logging ---
_log() {
local level=$1
shift
local message=$*
# ANSI color codes
local color_normal="\033[0m"
local color_red="\033[0;31m"
local color_green="\033[0;32m"
local color_yellow="\033[0;33m"
local color_blue="\033[0;34m"
local color_cyan="\033[0;36m"
# QSG_DEBUG can be set to true (e.g. export QSG_DEBUG=true) to enable debug logs
# By default, it's off unless explicitly set.
: ${QSG_DEBUG:=false}
# Prepare format string and arguments for printf
# The message itself is %s to prevent it from being interpreted as a format string.
local format_string="%s[%s]%s %s\n"
local color_code=""
local log_prefix=""
local output_stream=1 # stdout by default
case $level in
DEBUG)
if [[ "$QSG_DEBUG" != "true" ]]; then return 0; fi
color_code="$color_cyan"
log_prefix="DEBUG"
;;
INFO)
color_code="$color_blue"
log_prefix="INFO"
;;
SUCCESS)
color_code="$color_green"
log_prefix="SUCCESS"
;;
WARNING)
color_code="$color_yellow"
log_prefix="WARNING"
output_stream=2 # stderr
;;
ERROR)
color_code="$color_red"
log_prefix="ERROR"
output_stream=2 # stderr
;;
*)
color_code="$color_red"
log_prefix="LOG_ERROR"
message="Unknown log level: $level. Original Message: $message"
output_stream=2 # stderr
;;
esac
if [[ $output_stream -eq 1 ]]; then
printf "$format_string" "$color_code" "$log_prefix" "$color_normal" "$message"
else
printf "$format_string" "$color_code" "$log_prefix" "$color_normal" "$message" >&2
fi
}
# --- YAML Helper Functions ---
_yaml_escape_val() {
# Escape backslashes and double quotes for YAML string values
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
}
# --- Configuration Loading ---
_load_config() {
if [[ ! -f "$CONFIG_FILE" ]]; then
_log ERROR "Configuration file not found: $CONFIG_FILE"
exit 1
fi
_log INFO "Attempting to load configuration from: $CONFIG_FILE"
local current_section=""
local line_num=0
# Check if config file is readable
if [[ ! -r "$CONFIG_FILE" ]]; then
_log ERROR "Configuration file is not readable: $CONFIG_FILE"
exit 1
else
_log DEBUG "Configuration file is readable: $CONFIG_FILE"
fi
_log DEBUG "Attempting to enter config parsing loop. Disabling set -e temporarily."
set +e # Temporarily disable exit on error
# Read file line by line
while IFS= read -r line; do
((line_num++))
# _log DEBUG "Read raw line $line_num: '$line'" # Raw line logging can be noisy, comment for now
# Remove leading/trailing whitespace
line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# _log DEBUG "Trimmed line $line_num: '$line'"
# Skip empty lines and comments (lines starting with # or ;)
if [[ -z "$line" ]] || [[ "$line" == \#* ]] || [[ "$line" == \;* ]]; then
# _log DEBUG "Skipping line $line_num (empty or comment)"
continue
fi
# Check for section headers like [section_name]
if [[ "$line" == \[[a-zA-Z0-9_]+]\] ]]; then
current_section=$(echo "$line" | sed 's/\[//;s/\]//')
# _log DEBUG "Switched to section: $current_section"
QSG_CONFIG[${current_section}_exists]=true # Mark section as existing
continue
fi
# Process key-value pairs within a section
if [[ -n "$current_section" ]] && [[ "$line" == *=* ]]; then
local key=$(echo "$line" | cut -d'=' -f1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# Extract key and value
key="${line%%=*}"
local raw_value="${line#*=}"
# Strip trailing comments (anything after #). Ensure this is done BEFORE quote stripping.
local value_no_comment="${raw_value%%#*}"
# Remove potential quotes from value_no_comment
local value_no_quotes="${value_no_comment#\"}" # Remove leading quote
value_no_quotes="${value_no_quotes%\"}" # Remove trailing quote
# Trim leading/trailing whitespace from value_no_quotes
# Using standard parameter expansion for trimming whitespace
local temp_value="${value_no_quotes##[[:space:]]}" # Remove leading whitespace
value="${temp_value%%[[:space:]]}" # Remove trailing whitespace
local full_key="${current_section}_${key}"
QSG_CONFIG[$full_key]="$value"
# _log DEBUG "Loaded config: $full_key = $value"
elif [[ -z "$current_section" ]] && [[ "$line" == *=* ]]; then
# Handle global key-value pairs (not inside a section)
key="${line%%=*}" # Extract key
local raw_value="${line#*=}" # Extract raw value
# 1. Strip trailing comments
local value_no_comment="${raw_value%%#*}"
# 2. Trim leading/trailing whitespace from the comment-stripped value
local temp_trimmed_value="${value_no_comment##[[:space:]]}" # Remove leading whitespace
local value_trimmed="${temp_trimmed_value%%[[:space:]]}" # Remove trailing whitespace
# 3. Remove potential quotes from the trimmed, comment-stripped value
if [[ "${value_trimmed:0:1}" == "\"" && "${value_trimmed: -1}" == "\"" ]]; then
# It's double-quoted
value="${value_trimmed:1:-1}"
elif [[ "${value_trimmed:0:1}" == "'" && "${value_trimmed: -1}" == "'" ]]; then
# It's single-quoted
value="${value_trimmed:1:-1}"
else
# Not quoted or improperly quoted, use as is
value="$value_trimmed"
fi
# For specific path keys, ensure they are absolute
case "$key" in
paths_content_dir|paths_output_dir|paths_layouts_dir|paths_static_dir)
if [[ "$value" != /* && -n "$value" ]]; then # If not absolute and not empty
value="$PROJECT_ROOT/$value"
fi
;;
esac
QSG_CONFIG[$key]="$value"
# _log DEBUG "Loaded global config: $key = $value"
else
if [[ -n "$line" ]]; then # Only log if line is not empty (already handled above)
_log WARNING "Ignoring malformed line $line_num in $CONFIG_FILE: $line"
fi
fi
done < "$CONFIG_FILE"
local read_status=$?
set -e # Re-enable exit on error
_log DEBUG "Exited config parsing loop."
_log DEBUG "Exited config parsing loop. Read status: $read_status. Re-enabled set -e."
if [[ $read_status -ne 0 && $read_status -ne 1 ]]; then # read returns 1 on EOF, which is fine
_log WARNING "Potential issue reading configuration file. Read command exited with status: $read_status"
fi
# Validate required configuration
local required_config_keys=(
"paths_content_dir"
"paths_output_dir"
"paths_layouts_dir"
"paths_static_dir"
"site_name"
"site_url"
"site_theme"
)
local missing_configs=false
for key in "${required_config_keys[@]}"; do
if [[ -z "${QSG_CONFIG[$key]:-}" ]]; then
_log ERROR "Required configuration '$key' is missing from $CONFIG_FILE."
missing_configs=true
else
# Resolve path-related keys to absolute paths if not already absolute
if [[ "$key" == paths_* ]] && [[ "${QSG_CONFIG[$key]}" != /* ]]; then
QSG_CONFIG[$key]="$PROJECT_ROOT/${QSG_CONFIG[$key]}"
fi
_log DEBUG "Validated config: $key = ${QSG_CONFIG[$key]}"
fi
done
# Default build options if not set
: ${QSG_CONFIG[build_options_process_drafts]:=false}
: ${QSG_CONFIG[build_options_generate_rss]:=true}
# : ${QSG_CONFIG[build_options_generate_sitemap]:=true} # Example if sitemap is added
_log DEBUG "build_options_process_drafts set to: ${QSG_CONFIG[build_options_process_drafts]}"
_log DEBUG "build_options_generate_rss set to: ${QSG_CONFIG[build_options_generate_rss]}"
# _log DEBUG "build_options_generate_sitemap set to: ${QSG_CONFIG[build_options_generate_sitemap]}"
if [[ "$missing_configs" == true ]]; then
_log ERROR "One or more required configurations are missing. Please check $CONFIG_FILE."
exit 1
fi
_log INFO "Configuration loaded successfully."
return 0
}
# --- Dependency Checking ---
_check_dependencies() {
_log INFO "Checking dependencies..."
if ! command -v pandoc &> /dev/null; then
_log ERROR "Pandoc is not installed. Please install pandoc to continue."
_log ERROR "See: https://pandoc.org/installing.html"
exit 1
fi
_log INFO "All critical dependencies found."
}
# --- Core Functions ---
_clean_output_dir() {
_log INFO "Cleaning output directory: ${QSG_CONFIG[paths_output_dir]}"
# rm -rf "${QSG_CONFIG[paths_output_dir]}"/* # Be careful with this!
# For now, just ensure it exists
mkdir -p "${QSG_CONFIG[paths_output_dir]}"
}
_copy_static_files() {
local root_static_source_dir_rel="${QSG_CONFIG[paths_static_dir]:-}" # Relative path like "static"
local root_static_source_dir_abs=""
if [[ -n "$root_static_source_dir_rel" ]]; then
root_static_source_dir_abs="$PROJECT_ROOT/$root_static_source_dir_rel"
fi
local theme_static_source_dir_abs="${QSG_CONFIG[theme_static_source_dir]:-}" # Absolute path, set in main
# Target directory for all static files in the output.
local output_dir_from_config_csf="${QSG_CONFIG[paths_output_dir]:-output}" # Default to "output" if not set in _copy_static_files
local base_output_dir_abs_csf=""
if [[ "$output_dir_from_config_csf" == /* ]]; then
base_output_dir_abs_csf="$output_dir_from_config_csf"
else
base_output_dir_abs_csf="$PROJECT_ROOT/$output_dir_from_config_csf"
fi
local overall_target_static_dir_abs="$base_output_dir_abs_csf/static"
_log INFO "Preparing to copy static files to $overall_target_static_dir_abs"
if ! mkdir -p "$overall_target_static_dir_abs"; then
_log ERROR "Failed to create target static directory: $overall_target_static_dir_abs"
return 1
fi
local rsync_common_opts=(-a --delete --exclude '.gitkeep' --exclude '.DS_Store')
local cp_common_opts=(-R -p) # -p to preserve mode, ownership, timestamps
local copy_cmd=""
local cmd_opts=()
if command -v rsync &> /dev/null; then
copy_cmd="rsync"
cmd_opts=("${rsync_common_opts[@]}")
else
copy_cmd="cp"
# For cp, source needs trailing /., target needs to exist or be a dir name
cmd_opts=("${cp_common_opts[@]}")
_log WARNING "rsync not found. Falling back to 'cp'. Some features like --delete might not be available."
fi
local root_copy_ok=true
local theme_copy_ok=true
local any_files_copied=false
# 1. Copy from root static directory (if it exists)
if [[ -n "$root_static_source_dir_abs" && -d "$root_static_source_dir_abs" ]]; then
_log DEBUG "Copying from root static directory: $root_static_source_dir_abs using $copy_cmd"
local source_path_for_cmd="$root_static_source_dir_abs/"
local target_path_for_cmd="$overall_target_static_dir_abs/"
if [[ "$copy_cmd" == "cp" ]]; then source_path_for_cmd="$root_static_source_dir_abs/."; fi
if "$copy_cmd" "${cmd_opts[@]}" "$source_path_for_cmd" "$target_path_for_cmd"; then
_log DEBUG "Successfully copied files from $root_static_source_dir_abs"
any_files_copied=true
else
_log ERROR "Failed to copy files from $root_static_source_dir_abs to $overall_target_static_dir_abs using $copy_cmd"
root_copy_ok=false
fi
else
_log DEBUG "Root static directory '$root_static_source_dir_abs' not found or not specified. Skipping."
fi
# 2. Copy from theme static directory (if it exists), overwriting root static files
if [[ -n "$theme_static_source_dir_abs" && -d "$theme_static_source_dir_abs" ]]; then
_log DEBUG "Copying from theme static directory (will overwrite): $theme_static_source_dir_abs using $copy_cmd"
local source_path_for_cmd="$theme_static_source_dir_abs/"
local target_path_for_cmd="$overall_target_static_dir_abs/"
if [[ "$copy_cmd" == "cp" ]]; then source_path_for_cmd="$theme_static_source_dir_abs/."; fi
if "$copy_cmd" "${cmd_opts[@]}" "$source_path_for_cmd" "$target_path_for_cmd"; then
_log DEBUG "Successfully copied files from $theme_static_source_dir_abs"
any_files_copied=true
else
_log ERROR "Failed to copy files from $theme_static_source_dir_abs to $overall_target_static_dir_abs using $copy_cmd"
theme_copy_ok=false
fi
else
_log DEBUG "Theme static source directory '$theme_static_source_dir_abs' not found or not specified. Skipping."
fi
if ! $root_copy_ok || ! $theme_copy_ok; then
_log ERROR "One or more static file copy operations failed."
return 1
fi
if ! $any_files_copied; then
_log INFO "No static files found to copy (neither root nor theme static directories were specified, existed, or contained files)."
else
_log INFO "Static files copied successfully to $overall_target_static_dir_abs"
fi
return 0
}
_generate_index_page() {
_log DEBUG "Entered _generate_index_page"
local temp_yaml_file
temp_yaml_file=$(mktemp -t qsgen3_index_yaml.XXXXXX)
if [[ $? -ne 0 || -z "$temp_yaml_file" || ! -f "$temp_yaml_file" ]]; then
_log ERROR "Failed to create temporary file for index YAML. Exiting."
return 1
fi
trap "_log DEBUG \"Cleaning up temporary index YAML file: ${temp_yaml_file}\"; rm -f \"${temp_yaml_file}\"; trap - EXIT INT TERM HUP" EXIT INT TERM HUP
_log DEBUG "Created temporary YAML file for index: $temp_yaml_file"
_log INFO "Generating index page..."
local content_posts_dir="${QSG_CONFIG[paths_content_dir]}/posts"
local output_index_file="${QSG_CONFIG[paths_output_dir]}/index.html"
local layout_index_file="${QSG_CONFIG[paths_layouts_dir]}/index.html"
if [[ ! -d "$content_posts_dir" ]]; then
_log WARNING "Posts directory not found: $content_posts_dir. Skipping index page generation."
return 0
fi
if [[ ! -f "$layout_index_file" ]]; then
_log ERROR "Index layout file not found: $layout_index_file. Cannot generate index page."
return 1
fi
local post_details=()
# Collect post details
# This is a simplified approach. Robust YAML parsing is complex in pure shell.
# We'll extract title, date, and construct a URL.
# We assume date format is YYYY-MM-DD for sorting.
# A 'summary' field in frontmatter would also be good.
local md_file
for md_file in $(find "$content_posts_dir" -name '*.md' -type f); do
_log DEBUG "Processing metadata for $md_file"
# Extract frontmatter (lines between --- and ---)
# Use sed to print lines between the first '---' and the next '---'
# Then grep for title, date, summary, draft
local frontmatter=$(sed -n '/^---$/,/^---$/{/^---$/d;p;}' "$md_file")
if [[ -z "$frontmatter" ]]; then
_log WARNING "No YAML frontmatter found or frontmatter is empty in $md_file. Skipping."
continue
fi
# Extract title, date, summary, and draft from frontmatter
# Basic extraction: assumes 'key: value' format, strips quotes
local title=$(echo "$frontmatter" | grep -m1 -iE '^title:' | sed -E 's/^title:[[:space:]]*//i; s/^["\x27](.*)["\x27]$/\1/')
local date=$(echo "$frontmatter" | grep -m1 -iE '^date:' | sed -E 's/^date:[[:space:]]*//i; s/^["\x27](.*)["\x27]$/\1/')
local summary=$(echo "$frontmatter" | grep -m1 -iE '^summary:' | sed -E 's/^summary:[[:space:]]*//i; s/^["\x27](.*)["\x27]$/\1/')
local draft=$(echo "$frontmatter" | grep -m1 -iE '^draft:' | sed -E 's/^draft:[[:space:]]*//i' | tr '[:upper:]' '[:lower:]')
_log DEBUG "Extracted Title: [$title], Date: [$date], Summary: [$summary], Draft: [$draft] for $md_file"
if [[ "$draft" == "true" && "${QSG_CONFIG[build_options_process_drafts]}" != "true" ]]; then
_log DEBUG "Skipping draft post for index: $md_file (draft: $draft)"
continue
fi
if [[ -z "$title" ]]; then
_log WARNING "Post '$md_file' is missing a title in frontmatter. Skipping for index."
continue
fi
# Construct URL relative to site root
local relative_path="${md_file#${QSG_CONFIG[paths_content_dir]}/}" # e.g., posts/hello-world.md
local post_url="${QSG_CONFIG[site_url]}/${relative_path%.md}.html"
# Use date for sorting, default to a very old date if not found
local sort_key="${date:-0000-00-00}"
post_details+=("${sort_key}|${title}|${post_url}|${date:-N/A}|${summary:-}")
done
# Sort posts by date (descending)
# IFS=$'\n' sorted_posts=($(printf "%s\n" "${post_details[@]}" | sort -t'|' -k1,1r -k2,2))
# Using a loop for sorting to avoid IFS issues and handle empty array better
local sorted_posts=()
if [[ ${#post_details[@]} -gt 0 ]]; then
OIFS="$IFS"
IFS=$'\n'
sorted_posts=($(printf "%s\n" "${post_details[@]}" | sort -t'|' -k1,1r))
IFS="$OIFS"
fi
# Generate comprehensive YAML string including site-wide metadata and posts
local site_title_for_yaml="${QSG_CONFIG[site_title]:-${QSG_CONFIG[site_name]}}"
# Helper for YAML string escaping (handles backslashes and double quotes)
local yaml_lines=()
yaml_lines+=( "$(printf 'current_year: "%s"' "$(date +%Y)")" )
yaml_lines+=( "$(printf 'site_name: "%s"' "$(_yaml_escape_val "${QSG_CONFIG[site_name]}")")" )
yaml_lines+=( "$(printf 'site_tagline: "%s"' "$(_yaml_escape_val "${QSG_CONFIG[site_tagline]}")")" )
yaml_lines+=( "$(printf 'site_url: "%s"' "$(_yaml_escape_val "${QSG_CONFIG[site_url]}")")" )
yaml_lines+=( "$(printf 'title: "%s"' "$(_yaml_escape_val "$site_title_for_yaml")")" )
yaml_lines+=( "$(printf 'posts:')" )
if [[ ${#sorted_posts[@]} -eq 0 ]]; then
_log DEBUG "No posts found to add to index YAML."
yaml_lines+=( "$(printf ' [] # Explicitly empty list')" )
else
for detail_line in "${sorted_posts[@]}"; do
# Original detail_line format: "${sort_key}|${title}|${post_url}|${date:-N/A}|${summary:-}"
# Using Zsh specific split for robustness with special characters in fields
local parts_array=( ${(s[|])detail_line} )
local post_title_val="${parts_array[2]}"
local post_url_val="${parts_array[3]}"
local post_date_val="${parts_array[4]}"
local post_summary_val="${parts_array[5]}"
yaml_lines+=( "$(printf ' - title: "%s"' "$(_yaml_escape_val "$post_title_val")")" )
yaml_lines+=( "$(printf ' url: "%s"' "$(_yaml_escape_val "$post_url_val")")" )
yaml_lines+=( "$(printf ' date: "%s"' "$(_yaml_escape_val "$post_date_val")")" )
yaml_lines+=( "$(printf ' summary: "%s"' "$(_yaml_escape_val "$post_summary_val")")" )
done
fi
local temp_yaml_content
temp_yaml_content="$(IFS=$'\n'; echo "${yaml_lines[*]}")"
temp_yaml_content+=$'\n' # Ensure a final trailing newline for the whole block
_log DEBUG "Generated comprehensive YAML for index (first 300 chars):\n${temp_yaml_content:0:300}..."
# Write YAML to temporary file
_log DEBUG "Full comprehensive YAML for index before writing to file (raw, first 1000 chars to avoid excessive logging):\n${temp_yaml_content:0:1000}"
if ! printf '%s' "$temp_yaml_content" > "$temp_yaml_file"; then
_log ERROR "Failed to write comprehensive YAML to temporary file: $temp_yaml_file"
return 1 # Trap will clean up temp_yaml_file
fi
_log DEBUG "Successfully wrote comprehensive YAML to $temp_yaml_file"
# Theme CSS handling for index page
local source_theme_css_file="${QSG_CONFIG[paths_layouts_dir]}/css/${QSG_CONFIG[site_theme]}.css"
local target_theme_css_dir="${QSG_CONFIG[paths_output_dir]}/css"
local target_theme_css_file="$target_theme_css_dir/theme.css"
local pandoc_css_path_for_index="/css/theme.css"
_log DEBUG "In _generate_index_page: PROJECT_ROOT='$PROJECT_ROOT'"
local pandoc_cmd_index=(
pandoc
"--metadata-file" "$temp_yaml_file"
)
# Add CSS if specified (pandoc_css_path_arg is derived in main)
if [[ -n "${QSG_CONFIG[pandoc_css_path_arg]:-}" ]]; then
pandoc_cmd_index+=("--css" "${QSG_CONFIG[pandoc_css_path_arg]}")
_log DEBUG "Using CSS for index: ${QSG_CONFIG[pandoc_css_path_arg]}"
else
_log DEBUG "No CSS specified for index page."
fi
# Add remaining Pandoc options for index page
pandoc_cmd_index+=(
"--template=${layout_index_file}"
"--output=${output_index_file}"
"--standalone"
)
_log INFO "Generating $output_index_file using template $layout_index_file with YAML from $temp_yaml_file"
_log DEBUG "Pandoc command for index page will be constructed as follows:"
_log DEBUG "Base command: pandoc"
_log DEBUG "CSS arg if theme applies: --css ${pandoc_css_path_for_index}"
_log DEBUG "Metadata file arg: --metadata-file ${temp_yaml_file}"
_log DEBUG "Template arg: --template=${layout_index_file}"
_log DEBUG "Output arg: --output=${output_index_file}"
_log DEBUG "Other args: --standalone"
_log DEBUG "Final pandoc_cmd_index array: ${(q+)pandoc_cmd_index}"
local pandoc_run_stderr_file
pandoc_run_stderr_file=$(mktemp -t pandoc_stderr_index.XXXXXX)
if [[ -z "$pandoc_run_stderr_file" || ! -e "$pandoc_run_stderr_file" ]]; then # -e to check existence, could be pipe not file
_log CRITICAL "Failed to create temporary file for Pandoc stderr (index). mktemp failed."
# Attempt to clean up the main temp YAML file before exiting, if it was created.
if [[ -n "$temp_yaml_file" && -f "$temp_yaml_file" ]]; then
_log DEBUG "Cleaning up temporary index YAML file ($temp_yaml_file) due to mktemp failure for stderr file."
rm -f "$temp_yaml_file"
fi
return 1
fi
# This temp stderr file is short-lived, trap for it isn't strictly necessary if cleaned up reliably.
local pandoc_exit_code=0
# Execute Pandoc, redirecting its stderr to the temp file
_log DEBUG "Executing Pandoc command for index page with set -x..."
set -x # Enable command tracing
if "${pandoc_cmd_index[@]}" 2> "$pandoc_run_stderr_file"; then
pandoc_exec_status=0
else
pandoc_exec_status=$?
fi
set +x # Disable command tracing
_log DEBUG "Pandoc execution finished. Original exit status from Pandoc: $pandoc_exec_status"
pandoc_exit_code=$pandoc_exec_status # Use the captured status
local stderr_content=""
if [[ -s "$pandoc_run_stderr_file" ]]; then
stderr_content=$(<"$pandoc_run_stderr_file")
fi
rm -f "$pandoc_run_stderr_file"
if [[ $pandoc_exit_code -ne 0 ]]; then
_log ERROR "Pandoc command failed for '$output_index_file' with exit code: $pandoc_exit_code."
if [[ -n "$stderr_content" ]]; then
_log ERROR "Pandoc stderr:\n$stderr_content"
fi
_log ERROR "YAML content passed to Pandoc was in: $temp_yaml_file"
_log ERROR "YAML content dump (first 500 chars):\n$(head -c 500 "$temp_yaml_file" 2>/dev/null || echo 'Failed to read YAML dump')"
return $pandoc_exit_code # Return Pandoc's non-zero exit code
elif [[ -n "$stderr_content" ]]; then
# Pandoc exited 0 but wrote to stderr. Check for known fatal error patterns.
if echo "$stderr_content" | grep -q -iE 'YAML parse exception|template error|could not find|error reading file'; then
_log ERROR "Pandoc reported a critical error for '$output_index_file' (exit code 0). Treating as failure."
_log ERROR "Pandoc stderr:\n$stderr_content"
_log ERROR "YAML content passed to Pandoc was in: $temp_yaml_file"
_log ERROR "YAML content dump (first 500 chars):\n$(head -c 500 "$temp_yaml_file" 2>/dev/null || echo 'Failed to read YAML dump')"
return 1 # Force a failure status
else
_log WARNING "Pandoc succeeded for '$output_index_file' (exit code 0) but produced stderr (non-critical):\n$stderr_content"
# Continue, as it might be a non-fatal warning from Pandoc.
fi
else
_log DEBUG "Successfully generated $output_index_file"
fi
return 0 # If we reached here, it's success or a warning we decided to ignore.
}
_process_markdown_files() {
_log DEBUG "Entered _process_markdown_files"
_log INFO "Processing Markdown files..."
local content_dir="${QSG_CONFIG[paths_content_dir]}"
local output_dir="${QSG_CONFIG[paths_output_dir]}"
local layouts_dir="${QSG_CONFIG[paths_layouts_dir]}"
local default_post_template="$layouts_dir/post.html"
local default_page_template="$layouts_dir/page.html"
if [[ ! -f "$default_post_template" ]]; then
_log WARNING "Default post template not found: $default_post_template. Posts may not render correctly."
fi
if [[ ! -f "$default_page_template" ]]; then
_log WARNING "Default page template not found: $default_page_template. Pages may not render correctly."
fi
# Find all markdown files and process them
while IFS= read -r source_file; do
if [[ -z "$source_file" ]]; then continue; fi
_log DEBUG "Processing Markdown file: $source_file"
local relative_path="${source_file#$content_dir/}"
if [[ "$content_dir" == "$source_file" ]]; then
relative_path=$(basename "$source_file")
elif [[ "$content_dir" == "/" && "$source_file" == /* ]]; then
relative_path="${source_file#/}"
elif [[ "$content_dir" == "." && "$source_file" == ./* ]]; then
relative_path="${source_file#./}"
fi
local output_file_html_part="${relative_path%.md}.html"
local output_file_abs="$output_dir/$output_file_html_part"
mkdir -p "$(dirname "$output_file_abs")"
local frontmatter=$(sed -n '/^---$/,/^---$/{/^---$/d;p;}' "$source_file")
local title=$(echo "$frontmatter" | grep -m1 -iE '^title:' | sed -E 's/^title:[[:space:]]*//i; s/^["\x27](.*)["\x27]$/\1/')
local date=$(echo "$frontmatter" | grep -m1 -iE '^date:' | sed -E 's/^date:[[:space:]]*//i; s/^["\x27](.*)["\x27]$/\1/')
local draft=$(echo "$frontmatter" | grep -m1 -iE '^draft:' | sed -E 's/^draft:[[:space:]]*//i' | tr '[:upper:]' '[:lower:]')
local custom_layout_fm=$(echo "$frontmatter" | grep -m1 -iE '^layout:' | sed -E 's/^layout:[[:space:]]*//i; s/^["\x27](.*)["\x27]$/\1/')
if [[ "$draft" == "true" && "${QSG_CONFIG[build_options_process_drafts]}" != "true" ]]; then
_log DEBUG "Skipping draft file: $source_file"
continue
fi
if [[ -z "$title" ]]; then
_log WARNING "Markdown file '$source_file' is missing a title. Using filename as fallback."
local fn_no_ext=$(basename "$source_file")
title="${fn_no_ext%.md}"
fi
local template_to_use=""
if [[ -n "$custom_layout_fm" ]]; then
if [[ "$custom_layout_fm" != *.html && "$custom_layout_fm" != *.xml ]]; then custom_layout_fm+=".html"; fi
template_to_use="$layouts_dir/$custom_layout_fm"
if [[ ! -f "$template_to_use" ]]; then
_log WARNING "Custom layout '$custom_layout_fm' from '$source_file' not found at '$template_to_use'. Falling back."
template_to_use=""
fi
fi
if [[ -z "$template_to_use" ]]; then
if [[ "$relative_path" == posts/* && -f "$default_post_template" ]]; then
template_to_use="$default_post_template"
elif [[ -f "$default_page_template" ]]; then
template_to_use="$default_page_template"
elif [[ -f "$default_post_template" ]]; then
_log DEBUG "Default page template missing, falling back to default post template for $source_file"
template_to_use="$default_post_template"
else
_log ERROR "No suitable default template found (post.html or page.html) for '$source_file'. Skipping."
continue
fi
fi
if [[ ! -f "$template_to_use" ]]; then
_log ERROR "Template '$template_to_use' for '$source_file' is not a valid file. Skipping."
continue
fi
local pandoc_cmd=(
pandoc "$source_file"
--to html5
--output "$output_file_abs"
--standalone
--template "$template_to_use"
)
pandoc_cmd+=(--metadata "title=$title")
if [[ -n "$date" ]]; then
pandoc_cmd+=(--metadata "date=$date")
fi
for key val in "${(@kv)QSG_CONFIG}"; do
if [[ "$key" == site_* || "$key" == author_* ]]; then
pandoc_cmd+=(--metadata "$key=$val")
fi
done
pandoc_cmd+=(--metadata "current_year=$(date +%Y)")
# Add CSS if specified (pandoc_css_path_arg is derived in main)
if [[ -n "${QSG_CONFIG[pandoc_css_path_arg]:-}" ]]; then
pandoc_cmd+=("--css" "${QSG_CONFIG[pandoc_css_path_arg]}")
_log DEBUG "Using CSS for post $source_file: ${QSG_CONFIG[pandoc_css_path_arg]}"
else
_log DEBUG "No CSS specified for post $source_file."
fi
_log INFO "Generating $output_file_abs from $source_file using template $template_to_use"
if ! "${pandoc_cmd[@]}"; then
_log ERROR "Pandoc failed for $source_file. Command was: ${pandoc_cmd[*]}"
else
_log DEBUG "Successfully generated $output_file_abs"
fi
done < <(find "$content_dir" -type f -name '*.md' -print0 | xargs -0 -r realpath)
_log INFO "Finished processing Markdown files."
return 0
}
_generate_rss_feed() {
_log DEBUG "Entered _generate_rss_feed"
local temp_rss_yaml_file
temp_rss_yaml_file=$(mktemp -t qsgen3_rss_yaml.XXXXXX)
if [[ $? -ne 0 || -z "$temp_rss_yaml_file" || ! -f "$temp_rss_yaml_file" ]]; then
_log ERROR "Failed to create temporary file for RSS YAML. Exiting."
return 1
fi
trap "_log DEBUG \"Cleaning up temporary RSS YAML file: ${temp_rss_yaml_file}\"; rm -f \"${temp_rss_yaml_file}\"; trap - EXIT INT TERM HUP" EXIT INT TERM HUP
_log DEBUG "Created temporary YAML file for RSS: $temp_rss_yaml_file"
if [[ "${QSG_CONFIG[build_options_generate_rss]}" != "true" ]]; then
_log INFO "RSS feed generation is disabled in configuration. Skipping."
return 0
fi
_log INFO "Generating RSS feed..."
local content_posts_dir="${QSG_CONFIG[paths_content_dir]}/posts"
local output_rss_file="${QSG_CONFIG[paths_output_dir]}/rss.xml"
local layout_rss_file="${QSG_CONFIG[paths_layouts_dir]}/rss.xml"
if [[ ! -d "$content_posts_dir" ]]; then
_log WARNING "Posts directory for RSS ('$content_posts_dir') not found. Cannot generate RSS feed."
return 0
fi
if [[ ! -f "$layout_rss_file" ]]; then
_log ERROR "RSS layout file not found: $layout_rss_file. Cannot generate RSS feed."
return 1
fi
local post_details=()
local md_file
# Use find with xargs and realpath for robust path handling, then loop
while IFS= read -r md_file; do
if [[ -z "$md_file" ]]; then continue; fi
_log DEBUG "Processing $md_file for RSS feed."
local frontmatter=$(sed -n '/^---$/,/^---$/{/^---$/d;p;}' "$md_file")
if [[ -z "$frontmatter" ]]; then
_log DEBUG "No YAML frontmatter found in $md_file for RSS. Skipping."
continue
fi
local title=$(echo "$frontmatter" | grep -m1 -iE '^title:' | sed -E 's/^title:[[:space:]]*//i; s/^["\x27](.*)["\x27]$/\1/')
local date_iso=$(echo "$frontmatter" | grep -m1 -iE '^date:' | sed -E 's/^date:[[:space:]]*//i; s/^["\x27](.*)["\x27]$/\1/')
local summary=$(echo "$frontmatter" | grep -m1 -iE '^summary:' | sed -E 's/^summary:[[:space:]]*//i; s/^["\x27](.*)["\x27]$/\1/')
local draft=$(echo "$frontmatter" | grep -m1 -iE '^draft:' | sed -E 's/^draft:[[:space:]]*//i' | tr '[:upper:]' '[:lower:]')
if [[ "$draft" == "true" && "${QSG_CONFIG[build_options_process_drafts]}" != "true" ]]; then
_log DEBUG "Skipping draft post for RSS: $md_file"
continue
fi
if [[ -z "$title" ]]; then
_log WARNING "Post '$md_file' is missing a title. Skipping for RSS."
continue
fi
if [[ -z "$date_iso" ]]; then
_log WARNING "Post '$md_file' is missing a date. Skipping for RSS."
continue
fi
local relative_path_from_content_root="${md_file#${QSG_CONFIG[paths_content_dir]}/}"
local post_url="${QSG_CONFIG[site_url]}/${relative_path_from_content_root%.md}.html"
local rfc822_date
# Attempt to convert date to RFC-822 format for RSS
# GNU date syntax for -d and -R
if rfc822_date=$(date -d "$date_iso" +'%a, %d %b %Y %H:%M:%S %z' 2>/dev/null); then
_log DEBUG "Converted date '$date_iso' to RFC-822 '$rfc822_date' for $md_file"
else
_log WARNING "Could not convert date '$date_iso' to RFC-822 for $md_file. Using original."
rfc822_date="$date_iso" # Fallback to original if conversion fails
fi
local sort_key="${date_iso:-0000-00-00}" # Use ISO date for sorting
post_details+=("${sort_key}|${title}|${post_url}|${rfc822_date}|${summary:-No summary available.}")
done < <(find "$content_posts_dir" -type f -name '*.md' -print0 | xargs -0 -r realpath)
local sorted_posts=()
if [[ ${#post_details[@]} -gt 0 ]]; then
OIFS="$IFS"; IFS=$'\n'
# Sort by ISO date (first field), descending
sorted_posts=($(printf "%s\n" "${post_details[@]}" | sort -t'|' -k1,1r))
IFS="$OIFS"
fi
local yaml_lines=()
local site_title_for_feed="${QSG_CONFIG[site_title]:-${QSG_CONFIG[site_name]}}"
local feed_url="${QSG_CONFIG[site_url]}/rss.xml"
local current_rfc822_date=$(date -R)
yaml_lines+=( "$(printf 'site_name: "%s"' "$(_yaml_escape_val "${QSG_CONFIG[site_name]}")")" )
yaml_lines+=( "$(printf 'site_tagline: "%s"' "$(_yaml_escape_val "${QSG_CONFIG[site_tagline]}")")" )
yaml_lines+=( "$(printf 'site_url: "%s"' "$(_yaml_escape_val "${QSG_CONFIG[site_url]}")")" )
yaml_lines+=( "$(printf 'feed_url: "%s"' "$(_yaml_escape_val "$feed_url")")" )
yaml_lines+=( "$(printf 'feed_title: "%s"' "$(_yaml_escape_val "$site_title_for_feed Feed")")" ) # For <title> in RSS
yaml_lines+=( "$(printf 'current_rfc822_date: "%s"' "$(_yaml_escape_val "$current_rfc822_date")")" ) # For <pubDate> of the feed itself
yaml_lines+=( "$(printf 'current_year: "%s"' "$(date +%Y)")" )
yaml_lines+=( "$(printf 'pagetitle: "%s"' "$(_yaml_escape_val "$site_title_for_feed Feed")")" ) # For Pandoc's HTML writer
yaml_lines+=( "$(printf 'posts:')" )
if [[ ${#sorted_posts[@]} -eq 0 ]]; then
_log INFO "No posts found to include in RSS feed after filtering."
yaml_lines+=( "$(printf ' [] # Explicitly empty list')" )
else
for detail_line in "${sorted_posts[@]}"; do
local parts=( ${(s[|])detail_line} ) # Zsh specific split
local p_title="${parts[2]:-}"
local p_url="${parts[3]:-}"
local p_date_rfc822="${parts[4]:-}"
local p_summary="${parts[5]:-}"
yaml_lines+=( "$(printf ' - post_title: "%s"' "$(_yaml_escape_val "$p_title")")" )
yaml_lines+=( "$(printf ' post_url: "%s"' "$(_yaml_escape_val "$p_url")")" )
yaml_lines+=( "$(printf ' post_date_rfc822: "%s"' "$(_yaml_escape_val "$p_date_rfc822")")" )
yaml_lines+=( "$(printf ' post_summary: "%s"' "$(_yaml_escape_val "$p_summary")")" )
# Add other fields like guid if needed, e.g., using post_url as guid
yaml_lines+=( "$(printf ' post_guid: "%s"' "$(_yaml_escape_val "$p_url")")" )
done
fi
local temp_yaml_content
temp_yaml_content="$(IFS=$'\n'; echo "${yaml_lines[*]}")"
temp_yaml_content+=$'\n' # Ensure a final trailing newline
_log DEBUG "Generated comprehensive YAML for RSS (first 300 chars):\n${temp_yaml_content:0:300}..."
if ! printf '%s' "$temp_yaml_content" > "$temp_rss_yaml_file"; then
_log ERROR "Failed to write comprehensive RSS YAML to temporary file: $temp_rss_yaml_file"
return 1
fi
_log DEBUG "Successfully wrote comprehensive RSS YAML to $temp_rss_yaml_file"
local pandoc_cmd_rss=(
pandoc
"--metadata-file" "$temp_rss_yaml_file"
"--template=${layout_rss_file}"
"--output=${output_rss_file}"
"--to" "html5" # Use HTML5 writer for custom XML template processing
"--standalone"
)
_log INFO "Generating $output_rss_file using template $layout_rss_file with YAML from $temp_rss_yaml_file"
local pandoc_run_rss_stderr_file
pandoc_run_rss_stderr_file=$(mktemp -t pandoc_stderr_rss.XXXXXX)
if [[ -z "$pandoc_run_rss_stderr_file" || ! -e "$pandoc_run_rss_stderr_file" ]]; then
_log CRITICAL "Failed to create temporary file for Pandoc stderr (RSS). mktemp failed."
if [[ -n "$temp_rss_yaml_file" && -f "$temp_rss_yaml_file" ]]; then
_log DEBUG "Cleaning up temporary RSS YAML file ($temp_rss_yaml_file) due to mktemp failure for stderr file."
rm -f "$temp_rss_yaml_file"
fi
return 1
fi
local pandoc_exit_code_rss=0
# Provide a dummy input to Pandoc since content is from metadata, redirect stderr
local pandoc_exec_status_rss
if echo "<!-- RSS feed generated by qsgen3 -->" | "${pandoc_cmd_rss[@]}" 2> "$pandoc_run_rss_stderr_file"; then
pandoc_exec_status_rss=0
else
pandoc_exec_status_rss=$?
fi
pandoc_exit_code_rss=$pandoc_exec_status_rss
local stderr_content_rss=""
if [[ -s "$pandoc_run_rss_stderr_file" ]]; then
stderr_content_rss=$(<"$pandoc_run_rss_stderr_file")
fi
rm -f "$pandoc_run_rss_stderr_file"
if [[ $pandoc_exit_code_rss -ne 0 ]]; then
_log ERROR "Pandoc command failed for '$output_rss_file' with exit code: $pandoc_exit_code_rss."
if [[ -n "$stderr_content_rss" ]]; then
_log ERROR "Pandoc stderr:\n$stderr_content_rss"
fi
_log ERROR "YAML content passed to Pandoc for RSS was in: $temp_rss_yaml_file"
_log ERROR "RSS YAML content dump (first 500 chars):\n$(head -c 500 "$temp_rss_yaml_file" 2>/dev/null || echo 'Failed to read RSS YAML dump')"
return $pandoc_exit_code_rss # Return Pandoc's non-zero exit code
elif [[ -n "$stderr_content_rss" ]]; then
if echo "$stderr_content_rss" | grep -q -iE 'YAML parse exception|template error|could not find|error reading file'; then
_log ERROR "Pandoc reported a critical error for '$output_rss_file' (exit code 0). Treating as failure."
_log ERROR "Pandoc stderr:\n$stderr_content_rss"
_log ERROR "YAML content passed to Pandoc for RSS was in: $temp_rss_yaml_file"
_log ERROR "RSS YAML content dump (first 500 chars):\n$(head -c 500 "$temp_rss_yaml_file" 2>/dev/null || echo 'Failed to read RSS YAML dump')"
return 1 # Force a failure status
else
_log WARNING "Pandoc succeeded for '$output_rss_file' (exit code 0) but produced stderr (non-critical):\n$stderr_content_rss"
fi
else
_log DEBUG "Successfully generated $output_rss_file"
fi
return 0 # If we reached here, it's success or a warning we decided to ignore.
}
main() {
local custom_config_file_arg=""
# Option parsing
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
_usage
;;
-V|--version)
echo "$QSGEN_NAME Version: $VERSION"
exit 0
;;
-c|--config)
if [[ -n "$2" ]]; then
custom_config_file_arg="$2"
shift 2
else
_log ERROR "Option --config requires an argument."
_usage # Will exit
fi
;;
--)
shift
break
;;
-*)
_log ERROR "Unknown option: $1"
_usage # Will exit
;;
*)
break
;;
esac
done
# Resolve CONFIG_FILE path
# PROJECT_ROOT is already set globally to $PWD
if [[ -n "$custom_config_file_arg" ]]; then
if [[ "$custom_config_file_arg" == /* ]]; then # Absolute path
CONFIG_FILE="$custom_config_file_arg"
else # Relative path, resolve against PROJECT_ROOT
CONFIG_FILE="$PROJECT_ROOT/$custom_config_file_arg"
fi
else
# Use default CONFIG_FILE_NAME in PROJECT_ROOT
CONFIG_FILE="$PROJECT_ROOT/$CONFIG_FILE_NAME"
fi
_log INFO "Using Project Root: $PROJECT_ROOT"
_log INFO "Effective Configuration File: $CONFIG_FILE"
_check_dependencies
# set -x # Enable command tracing (use external zsh -x if needed)
_load_config
# --- Theme Configuration ---
_log DEBUG "Processing theme configuration..."
local theme_name="${QSG_CONFIG[site_theme]:-}"
QSG_CONFIG[theme_static_source_dir]="" # Initialize. Source for theme's static files.
# QSG_CONFIG[paths_layouts_dir] is loaded from site.conf. It will be overridden if theme provides layouts.
# QSG_CONFIG[paths_static_dir] is loaded from site.conf (e.g. "static"). This is the root static dir.
if [[ -n "$theme_name" ]]; then
_log INFO "Site theme specified: '$theme_name'"
local theme_base_path="$PROJECT_ROOT/themes/$theme_name"
if [[ ! -d "$theme_base_path" ]]; then
_log WARNING "Theme directory '$theme_base_path' not found for theme '$theme_name'. Theme will not be applied."
else
_log DEBUG "Theme directory found: '$theme_base_path'"
# 1. Theme Layouts: Override paths_layouts_dir if theme provides layouts
local theme_layouts_path="$theme_base_path/layouts"
if [[ -d "$theme_layouts_path" ]]; then
_log INFO "Using layouts from theme '$theme_name': $theme_layouts_path"
QSG_CONFIG[paths_layouts_dir]="$theme_layouts_path" # This now points to theme layouts
else
_log DEBUG "No 'layouts' directory in theme '$theme_name'. Using layouts from '${QSG_CONFIG[paths_layouts_dir]}'."
fi
# 2. Theme Static Files Source: Set theme_static_source_dir
local theme_static_standard_path="$theme_base_path/static"
if [[ -d "$theme_static_standard_path" ]]; then
_log INFO "Theme '$theme_name' provides static files from standard path: $theme_static_standard_path"
QSG_CONFIG[theme_static_source_dir]="$theme_static_standard_path"
elif [[ -d "$theme_base_path" ]]; then
# If themes/theme_name/static doesn't exist,
# check if themes/theme_name itself can serve as a base for static assets (e.g. themes/theme_name/css)
_log INFO "Theme '$theme_name' provides static files from theme base path: $theme_base_path"
QSG_CONFIG[theme_static_source_dir]="$theme_base_path"
else
_log DEBUG "No 'static' directory found at '$theme_static_standard_path' and theme base path '$theme_base_path' is not a valid fallback."
# QSG_CONFIG[theme_static_source_dir] remains as initialized (empty)
fi
fi
else
_log DEBUG "No site_theme specified. Using default project paths for layouts and static files."
fi
# For debugging: print loaded config
# for key val in "${(@kv)QSG_CONFIG}"; do
# _log DEBUG "Config: $key = $val"
# done
_clean_output_dir
if [[ $? -ne 0 ]]; then _log ERROR "Cleaning output directory failed."; exit 1; fi
_copy_static_files
if [[ $? -ne 0 ]]; then _log ERROR "Copying static files failed."; exit 1; fi
# --- Determine Final CSS path for Pandoc --- (Moved here to check after files are copied)
QSG_CONFIG[pandoc_css_path_arg]=""
local site_css_file_spec="${QSG_CONFIG[site_theme_css_file]:-}"
if [[ -n "$site_css_file_spec" ]]; then
# site_css_file_spec is the name/path of the CSS file as specified in site.conf,
# e.g., "minimaltemplate-v1.css" or "css/custom.css".
# This path is relative to what _copy_static_files copies into output/static/.
local css_path_in_output_static="$site_css_file_spec"
local output_dir_from_config_main="${QSG_CONFIG[paths_output_dir]:-output}" # Default to "output" if not set in main
local base_output_dir_abs_main=""
if [[ "$output_dir_from_config_main" == /* ]]; then
base_output_dir_abs_main="$output_dir_from_config_main"
else
base_output_dir_abs_main="$PROJECT_ROOT/$output_dir_from_config_main"
fi
local expected_css_file_abs_path="$base_output_dir_abs_main/static/$css_path_in_output_static"
if [[ -f "$expected_css_file_abs_path" ]]; then
QSG_CONFIG[pandoc_css_path_arg]="/static/$css_path_in_output_static"
_log DEBUG "Pandoc CSS path set to: ${QSG_CONFIG[pandoc_css_path_arg]} (file exists: $expected_css_file_abs_path)"
else
_log WARNING "Specified 'site_theme_css_file' ('$site_css_file_spec') not found at '$expected_css_file_abs_path' after copying static files. CSS may not be applied via this setting."
fi
fi
# Fallback to old site_theme_css_path if pandoc_css_path_arg is still empty
if [[ -z "${QSG_CONFIG[pandoc_css_path_arg]}" ]]; then
local old_direct_css_path="${QSG_CONFIG[site_theme_css_path]:-}"
if [[ -n "$old_direct_css_path" ]]; then
if [[ "$old_direct_css_path" == static/* || "$old_direct_css_path" == /static/* ]]; then
QSG_CONFIG[pandoc_css_path_arg]="$(echo "$old_direct_css_path" | sed 's|^/*static|/static|')"
else
QSG_CONFIG[pandoc_css_path_arg]="/$old_direct_css_path"
fi
_log DEBUG "Using fallback 'site_theme_css_path' for Pandoc CSS argument: '${QSG_CONFIG[pandoc_css_path_arg]}'"
_log WARNING "Consider migrating to 'site_theme_css_file' in site.conf for clearer CSS management."
elif [[ -n "$site_css_file_spec" ]]; then
# This case: site_theme_css_file was specified, but the file was not found, and no old_direct_css_path fallback.
_log DEBUG "Specified 'site_theme_css_file' ('$site_css_file_spec') was not found, and no fallback 'site_theme_css_path' provided. No --css flag for Pandoc."
else
# This case: neither site_theme_css_file nor old_direct_css_path were specified.
_log DEBUG "No CSS file specified via 'site_theme_css_file' or 'site_theme_css_path'. No --css flag for Pandoc."
fi
fi
_process_markdown_files
if [[ $? -ne 0 ]]; then _log ERROR "Processing Markdown files failed."; exit 1; fi
_generate_index_page
if [[ $? -ne 0 ]]; then _log ERROR "Index page generation failed."; exit 1; fi
_generate_rss_feed
if [[ $? -ne 0 ]]; then _log ERROR "RSS feed generation failed."; exit 1; fi
_log INFO "Final state of output directory (${QSG_CONFIG[paths_output_dir]}):
$(ls -R "${QSG_CONFIG[paths_output_dir]}" 2>&1)"
_log SUCCESS "Site generation complete! Output: ${QSG_CONFIG[paths_output_dir]}"
# set +x # Disable command tracing
}
# Run main function
main "$@"