--- Build: pass | Tests: pass - 603 passed (44 files) --- Build: pass | Tests: pass — Tests 603 passed (603)
322 lines
14 KiB
Bash
Executable file
322 lines
14 KiB
Bash
Executable file
#!/bin/sh
|
|
# docs-compile.sh: Markdown-to-HTML compilation pipeline
|
|
#
|
|
# Converts markdown source in docs/public/ to HTML, applying .docignore filtering
|
|
# and version-dated output directories.
|
|
#
|
|
# Usage:
|
|
# docs-compile.sh [OPTIONS] <output-dir>
|
|
#
|
|
# Options:
|
|
# --semver VERSION Semantic version (e.g., 0.8.2). Defaults to git tag or 0.0.0
|
|
# --date DATE Build date DD.mon.YYYY (e.g., 24.mar.2026). Defaults to today
|
|
# --filter FILE Path to .docignore filter file. Defaults to docs/public/.docignore
|
|
# --source DIR Source markdown directory. Defaults to docs/public/
|
|
# --language LANG Compile single language (e.g., sl, en, de). If omitted, compiles docs/public/ root
|
|
# --help Print usage
|
|
#
|
|
# Example:
|
|
# docs-compile.sh --semver 0.8.2 /usr/local/www/docs.clawdie.si/
|
|
# # Outputs to: /usr/local/www/docs.clawdie.si/docs-v0.8.2_24.mar.2026/
|
|
#
|
|
# docs-compile.sh --semver 0.8.2 --language sl /usr/local/www/docs.clawdie.si/
|
|
# # Outputs to: /usr/local/www/docs.clawdie.si/docs-v0.8.2_24.mar.2026/sl/
|
|
|
|
set -e
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
DATE_FORMAT_SCRIPT="${REPO_DIR}/scripts/date-format.sh"
|
|
|
|
# Defaults
|
|
SEMVER=""
|
|
BUILD_DATE=$(bash "$DATE_FORMAT_SCRIPT" display-date | tr 'A-Z' 'a-z')
|
|
FILTER_FILE="docs/public/.docignore"
|
|
SOURCE_DIR="docs/public"
|
|
OUTPUT_BASE=""
|
|
LANGUAGE=""
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
log_msg() { echo "$(bash "$DATE_FORMAT_SCRIPT" display-ts) [compile] $1"; }
|
|
log_err() { echo "$(bash "$DATE_FORMAT_SCRIPT" display-ts) ${RED}[ERROR]${NC} $1" >&2; }
|
|
|
|
# Parse arguments
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--semver) SEMVER="$2"; shift 2 ;;
|
|
--date) BUILD_DATE="$2"; shift 2 ;;
|
|
--filter) FILTER_FILE="$2"; shift 2 ;;
|
|
--source) SOURCE_DIR="$2"; shift 2 ;;
|
|
--language) LANGUAGE="$2"; shift 2 ;;
|
|
--help) cat <<EOF
|
|
Usage: $(basename "$0") [OPTIONS] <output-base-dir>
|
|
|
|
Options:
|
|
--semver VERSION Semantic version (e.g., 0.8.2). Auto-detect from git tag if omitted
|
|
--date DATE Build date DD.mon.YYYY (e.g., 24.mar.2026). Defaults to today
|
|
--filter FILE Path to .docignore filter. Defaults to docs/public/.docignore
|
|
--source DIR Source markdown directory. Defaults to docs/public/
|
|
--language LANG Compile single language (e.g., sl, en). Omit for root docs/public/
|
|
--help Print this message
|
|
|
|
Example:
|
|
$(basename "$0") --semver 0.8.2 /usr/local/www/docs.clawdie.si/
|
|
# Output: /usr/local/www/docs.clawdie.si/docs-v0.8.2_20260324/
|
|
EOF
|
|
exit 0
|
|
;;
|
|
-*) log_err "Unknown option: $1"; exit 1 ;;
|
|
*) OUTPUT_BASE="$1"; shift ;;
|
|
esac
|
|
done
|
|
|
|
# Validate required arguments
|
|
[ -z "$OUTPUT_BASE" ] && { log_err "output-dir required"; exit 1; }
|
|
|
|
# Adjust SOURCE_DIR for language-specific compilation
|
|
if [ -n "$LANGUAGE" ]; then
|
|
SOURCE_DIR="${SOURCE_DIR}/${LANGUAGE}"
|
|
fi
|
|
|
|
[ -d "$SOURCE_DIR" ] || { log_err "Source directory not found: $SOURCE_DIR"; exit 1; }
|
|
|
|
# Auto-detect SEMVER from git tag if not provided
|
|
if [ -z "$SEMVER" ]; then
|
|
SEMVER=$(git describe --tags --match 'v*' 2>/dev/null | sed 's/^v//' | cut -d'-' -f1)
|
|
[ -z "$SEMVER" ] && SEMVER="0.0.0"
|
|
log_msg "Auto-detected semver: $SEMVER"
|
|
fi
|
|
|
|
# Validate date format (DD.mon.YYYY, e.g., 24.mar.2026)
|
|
if ! echo "$BUILD_DATE" | grep -qE '^[0-3][0-9]\.(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\.[0-9]{4}$'; then
|
|
log_err "Invalid BUILD_DATE: $BUILD_DATE (expected DD.mon.YYYY, e.g., 24.mar.2026)"; exit 1
|
|
fi
|
|
|
|
# Create versioned output directory (with language subdirectory if specified)
|
|
VERSION_DIR="${OUTPUT_BASE}/docs-v${SEMVER}_${BUILD_DATE}"
|
|
if [ -n "$LANGUAGE" ]; then
|
|
VERSION_DIR="${VERSION_DIR}/${LANGUAGE}"
|
|
fi
|
|
mkdir -p "$VERSION_DIR"
|
|
log_msg "Output: $VERSION_DIR"
|
|
|
|
# No external dependencies required — using simple HTML wrapper
|
|
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
# Build filter: Convert .docignore patterns (glob) to rsync exclude list
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
|
|
EXCLUDE_ARGS=""
|
|
if [ -f "$FILTER_FILE" ]; then
|
|
while IFS= read -r pattern; do
|
|
# Skip empty lines and comments
|
|
[ -z "$pattern" ] && continue
|
|
echo "$pattern" | grep -q '^[[:space:]]*#' && continue
|
|
|
|
# Convert glob to rsync exclude pattern
|
|
EXCLUDE_ARGS="$EXCLUDE_ARGS --exclude=$pattern"
|
|
done < "$FILTER_FILE"
|
|
log_msg "Loaded filter: $FILTER_FILE"
|
|
else
|
|
log_msg "Warning: Filter file not found: $FILTER_FILE (compiling all files)"
|
|
fi
|
|
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
# Copy markdown files, applying exclusions
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
|
|
# Temporarily stage filtered files to a work directory for processing
|
|
mkdir -p "${REPO_DIR}/tmp"
|
|
WORK_DIR=$(mktemp -d "${REPO_DIR}/tmp/docs-compile.XXXXXXXX")
|
|
trap "rm -rf $WORK_DIR" EXIT
|
|
|
|
# Use rsync to copy with exclusions (safer than cp with complicated patterns)
|
|
rsync -av \
|
|
--include='*.md' \
|
|
--exclude='*.md' \
|
|
--exclude='*' \
|
|
$EXCLUDE_ARGS \
|
|
"$SOURCE_DIR/" "$WORK_DIR/" 2>&1 | grep -E "^\.md|/$" || true
|
|
|
|
log_msg "Filtered markdown copied to work directory"
|
|
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
# Compile markdown → HTML
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
|
|
# Helper: Wrap markdown in HTML (simple, no external dependencies)
|
|
wrap_markdown_html() {
|
|
local md_file="$1"
|
|
local html_file="$2"
|
|
local title=$(basename "$md_file" .md)
|
|
|
|
# Extract first heading for title
|
|
title=$(grep -m1 '^#' "$md_file" | sed 's/^#+[[:space:]]*//' || echo "$title")
|
|
|
|
# Generate table of contents from markdown headings
|
|
local toc=""
|
|
toc=$(grep '^##[^#]' "$md_file" | sed 's/^## *//' | while read line; do
|
|
echo "<li><a href=\"#$(echo "$line" | sed 's/ /-/g')\">$line</a></li>"
|
|
done)
|
|
|
|
cat > "$html_file" << HTMLEOF
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>$title — Clawdie-AI Docs</title>
|
|
<style>
|
|
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; line-height: 1.6; max-width: 900px; margin: 0 auto; padding: 2em; color: #333; }
|
|
h1 { color: #0088ff; border-bottom: 2px solid #0088ff; padding-bottom: 0.5em; }
|
|
h2, h3, h4 { color: #0088ff; margin-top: 1.5em; }
|
|
code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', monospace; }
|
|
pre { background: #f4f4f4; padding: 1em; border-radius: 5px; overflow-x: auto; }
|
|
blockquote { border-left: 4px solid #0088ff; margin: 1em 0; padding-left: 1em; color: #666; }
|
|
a { color: #0088ff; text-decoration: none; }
|
|
a:hover { text-decoration: underline; }
|
|
#toc { background: #f9f9f9; padding: 1em; border-radius: 5px; margin: 1em 0; }
|
|
#toc ul { list-style: none; padding-left: 1em; }
|
|
table { border-collapse: collapse; width: 100%; margin: 1em 0; }
|
|
table th, table td { border: 1px solid #ddd; padding: 0.75em; text-align: left; }
|
|
table th { background: #f4f4f4; font-weight: bold; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>$title</h1>
|
|
<div id="toc">
|
|
<strong>Contents:</strong>
|
|
<ul>
|
|
$toc
|
|
</ul>
|
|
</div>
|
|
<div id="content">
|
|
$(cat "$md_file" | sed -e 's/^#[^#]//' -e 's/^## \(.*\)/<h2 id="\1">\1<\/h2>/' -e 's/^### \(.*\)/<h3>\1<\/h3>/' | sed -e 's/\*\*\([^*]*\)\*\*/<strong>\1<\/strong>/g' -e 's/\*\([^*]*\)\*/<em>\1<\/em>/g' -e 's/^\- /<li>/g' | awk '
|
|
BEGIN { in_code = 0; in_list = 0 }
|
|
/^```/ { in_code = !in_code; if (in_code) print "<pre><code>"; else print "</code></pre>"; next }
|
|
in_code { print; next }
|
|
/^<li>/ {
|
|
if (!in_list) { print "<ul>"; in_list = 1 }
|
|
gsub(/<li>/, "<li>"); print $0; next
|
|
}
|
|
/^$/ { if (in_list) { print "</ul>"; in_list = 0 }; next }
|
|
/^> / { gsub(/^> /, ""); print "<blockquote>" $0 "</blockquote>"; next }
|
|
{ if (NF > 0) print "<p>" $0 "</p>" }
|
|
END { if (in_list) print "</ul>" }
|
|
')
|
|
</div>
|
|
</body>
|
|
</html>
|
|
HTMLEOF
|
|
}
|
|
|
|
FILE_COUNT=0
|
|
for md_file in $(find "$WORK_DIR" -name "*.md" -type f); do
|
|
# Compute relative path
|
|
rel_path="${md_file#${WORK_DIR}/}"
|
|
|
|
# Output HTML path (same structure, .html extension)
|
|
html_file="${VERSION_DIR}/${rel_path%.md}.html"
|
|
html_dir=$(dirname "$html_file")
|
|
|
|
mkdir -p "$html_dir"
|
|
|
|
# Wrap markdown in HTML
|
|
if wrap_markdown_html "$md_file" "$html_file" 2>&1; then
|
|
FILE_COUNT=$((FILE_COUNT + 1))
|
|
log_msg "✓ $rel_path → $(basename "$html_file")"
|
|
else
|
|
log_err "Failed to compile: $rel_path"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
[ $FILE_COUNT -eq 0 ] && { log_err "No markdown files found after filtering"; exit 1; }
|
|
|
|
log_msg "Compiled $FILE_COUNT files"
|
|
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
# Create index.html (table of contents for directory listing)
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
|
|
INDEX_FILE="${VERSION_DIR}/index.html"
|
|
cat > "$INDEX_FILE" << 'INDEXEOF'
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Clawdie-AI Documentation Index</title>
|
|
<style>
|
|
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; max-width: 800px; margin: 2em auto; padding: 1em; color: #333; }
|
|
h1 { color: #0088ff; }
|
|
.doc-list { list-style: none; padding: 0; }
|
|
.doc-list li { margin: 0.5em 0; }
|
|
.doc-list a { color: #0088ff; text-decoration: none; }
|
|
.doc-list a:hover { text-decoration: underline; }
|
|
.meta { color: #666; font-size: 0.9em; margin-top: 2em; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>📚 Clawdie-AI Documentation</h1>
|
|
<p>Generated: GENERATED_AT | Version: VERSION_STR</p>
|
|
<ul class="doc-list">
|
|
INDEXEOF
|
|
|
|
# Find all generated HTML files and create list
|
|
find "$VERSION_DIR" -name "*.html" ! -name "index.html" -type f | sort | while read html; do
|
|
rel_path="${html#${VERSION_DIR}/}"
|
|
# Convert to display name (remove .html, replace dashes with spaces)
|
|
display_name=$(basename "$rel_path" .html | sed 's/-/ /g')
|
|
echo " <li><a href=\"${rel_path}\">$display_name</a></li>" >> "$INDEX_FILE"
|
|
done
|
|
|
|
cat >> "$INDEX_FILE" << 'INDEXEOF'
|
|
</ul>
|
|
<div class="meta">
|
|
<p><strong>Note:</strong> Internal documentation (marked with -INTERNAL, -SENSITIVE, or in private/ directories) is excluded from public deployment.</p>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
INDEXEOF
|
|
|
|
# Replace placeholders (use | as separator to avoid issues with / and : in dates)
|
|
GENERATED_AT=$(date +'%Y-%m-%d %H:%M:%S')
|
|
sed -i '' "s|GENERATED_AT|$GENERATED_AT|g" "$INDEX_FILE"
|
|
sed -i '' "s|VERSION_STR|v${SEMVER} (${BUILD_DATE})|g" "$INDEX_FILE"
|
|
|
|
log_msg "✓ Created index.html"
|
|
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
# Validation
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
|
|
# Verify directory structure
|
|
if [ ! -f "${VERSION_DIR}/index.html" ]; then
|
|
log_err "index.html not created"; exit 1
|
|
fi
|
|
|
|
HTML_COUNT=$(find "$VERSION_DIR" -name "*.html" | wc -l)
|
|
log_msg "Validation: $HTML_COUNT HTML files"
|
|
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
# Output summary
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
|
|
echo ""
|
|
echo -e "${GREEN}✓ Compilation complete${NC}"
|
|
echo " Version: v${SEMVER}"
|
|
echo " Date: ${BUILD_DATE}"
|
|
echo " Output: $VERSION_DIR"
|
|
echo " Files: $HTML_COUNT"
|
|
echo ""
|
|
echo "Next step: Deploy with symlink swap"
|
|
echo " ln -sfn docs-v${SEMVER}_${BUILD_DATE} ${OUTPUT_BASE}/docs-current"
|
|
echo ""
|
|
|
|
exit 0
|