Files
kewt/kewt.sh
2026-03-07 12:34:32 +01:00

372 lines
10 KiB
Bash
Executable File

#!/bin/sh
die() {
echo "Error: $1" >&2
exit 1
}
usage() {
cat <<EOF
Usage: $0 [--from <src>] [--to <out>]
$0 [src] [out]
$0 --new [title]
$0 --help
Options:
--help Show this help message.
--new [title] Create a new site directory (default: site)
--from <src> Source directory (default: site)
--to <out> Output directory (default: out)
EOF
}
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
awk_dir="$script_dir/awk"
ensure_root_defaults() {
if [ ! -f "./site.conf" ]; then
cat > "./site.conf" <<'EOF'
title = "kewt"
style = "kewt"
dir_indexes = true
single_file_index = true
flatten = false
footer = "made with <a href="https://kewt.krzak.org">kewt</a>"
logo = ""
display_logo = false
display_title = true
logo_as_favicon = true
favicon = ""
EOF
fi
if [ ! -f "./template.html" ]; then
cat > "./template.html" <<'EOF'
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>{{TITLE}}</title>
<link rel="stylesheet" href="{{CSS}}" type="text/css" />
{{HEAD_EXTRA}}
</head>
<body>
<header>
<h1>{{HEADER_BRAND}}</h1>
</header>
<nav id="side-bar">{{NAV}}</nav>
<article>{{CONTENT}}</article>
<footer>{{FOOTER}}</footer>
</body>
</html>
EOF
fi
}
create_new_site() {
new_title="$1"
new_dir="site"
[ -n "$new_title" ] && new_dir="$new_title"
[ -e "$new_dir" ] && die "Target '$new_dir' already exists."
ensure_root_defaults
mkdir -p "$new_dir"
cp "./site.conf" "$new_dir/site.conf"
printf "# _kewt_ website\n" > "$new_dir/index.md"
if [ -n "$new_title" ]; then
awk -v new_title="$new_title" '
BEGIN { done = 0 }
/^title[[:space:]]*=/ {
print "title = \"" new_title "\""
done = 1
next
}
{ print }
END {
if (!done) print "title = \"" new_title "\""
}
' "$new_dir/site.conf" > "$new_dir/site.conf.tmp" && mv "$new_dir/site.conf.tmp" "$new_dir/site.conf"
fi
echo "Created new site at '$new_dir'."
exit 0
}
generate_nav() {
dinfo=$(find "$1" -not -path '*/.*' | sort -r | awk -v src="$1" -f "$awk_dir/collect_dir_info.awk")
find "$1" -name "*.md" | sort | awk -v src="$1" -v single_file_index="$single_file_index" -v flatten="$flatten" -v dinfo="$dinfo" -f "$awk_dir/generate_sidebar.awk"
}
src=""
out=""
new_mode="false"
new_title=""
positional_count=0
while [ $# -gt 0 ]; do
case "$1" in
--help|-h)
usage
exit 0
;;
--new)
new_mode="true"
if [ $# -gt 1 ] && [ "${2#-}" = "$2" ]; then
new_title="$2"
shift
fi
;;
--from)
[ $# -lt 2 ] && die "--from requires a value."
src="$2"
shift
;;
--to)
[ $# -lt 2 ] && die "--to requires a value."
out="$2"
shift
;;
--*)
die "Unknown option: $1"
;;
*)
positional_count=$((positional_count + 1))
if [ "$positional_count" -eq 1 ]; then
[ -z "$src" ] && src="$1" || die "Source already set (use either positional or --from)."
elif [ "$positional_count" -eq 2 ]; then
[ -z "$out" ] && out="$1" || die "Output already set (use either positional or --to)."
else
die "Too many positional arguments."
fi
;;
esac
shift
done
[ "$new_mode" = "true" ] && create_new_site "$new_title"
ensure_root_defaults
[ -z "$src" ] && src="site"
[ -z "$out" ] && out="out"
[ -d "$src" ] || die "Source directory '$src' does not exist."
title="kewt"
style="kewt"
footer="made with <a href=\"https://kewt.krzak.org\">kewt</a>"
dir_indexes="true"
single_file_index="true"
flatten="false"
logo=""
display_logo="false"
display_title="true"
logo_as_favicon="true"
favicon=""
load_config() {
[ -f "$1" ] || return
while IFS= read -r line; do
case "$line" in
''|'#'*) continue ;;
*=*) ;;
*) continue ;;
esac
key=${line%%=*}
val=${line#*=}
key=$(printf '%s' "$key" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
val=$(printf '%s' "$val" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
case "$val" in
\"*\") val=${val#\"}; val=${val%\"} ;;
\'*\') val=${val#\'}; val=${val%\'} ;;
esac
case "$key" in
title) title="$val" ;;
style) style="$val" ;;
dir_indexes) dir_indexes="$val" ;;
single_file_index) single_file_index="$val" ;;
flatten) flatten="$val" ;;
footer) footer="$val" ;;
logo) logo="$val" ;;
display_logo) display_logo="$val" ;;
display_title) display_title="$val" ;;
logo_as_favicon) logo_as_favicon="$val" ;;
favicon) favicon="$val" ;;
esac
done < "$1"
}
load_config "./site.conf"
load_config "$src/site.conf"
template="$src/template.html"
[ -f "$template" ] || template="./template.html"
[ -f "$template" ] || die "Template '$template' not found."
[ -d "$out" ] && rm -rf "$out"
mkdir -p "$out"
nav=$(generate_nav "$src")
find_closest() {
target="$1"
start_dir="$2"
curr="$start_dir"
while [ "$curr" != "$src" ] && [ "$curr" != "." ] && [ "$curr" != "/" ]; do
if [ -f "$curr/$target" ]; then
echo "$curr/$target"
return
fi
curr=$(dirname "$curr")
done
if [ -f "$src/$target" ]; then
echo "$src/$target"
fi
}
copy_style_with_resolved_vars() {
src_style="$1"
out_style="$2"
awk -f "$awk_dir/replace_variables.awk" "$src_style" > "$out_style"
}
render_markdown() {
file="$1"
local_template=$(find_closest "template.html" "$(dirname "$file")")
[ -z "$local_template" ] && local_template="$template"
closest_style_src=$(find_closest "styles.css" "$(dirname "$file")")
[ -z "$closest_style_src" ] && closest_style_src=$(find_closest "style.css" "$(dirname "$file")")
if [ -n "$closest_style_src" ]; then
style_rel_to_src="${closest_style_src#$src/}"
case "$closest_style_src" in
"$src/styles.css") style_rel_to_src="styles.css" ;;
"$src/style.css") style_rel_to_src="style.css" ;;
esac
style_path="/${style_rel_to_src%styles.css}"
style_path="${style_path%style.css}styles.css"
else
style_path="/styles.css"
fi
logo_html=""
if [ "$display_logo" = "true" ] && [ -n "$logo" ]; then
logo_html="<img class=\"site-logo\" src=\"$logo\" alt=\"$title\" />"
fi
brand_text=""
if [ "$display_title" = "true" ]; then
brand_text="$title"
fi
if [ -n "$logo_html" ] && [ -n "$brand_text" ]; then
header_brand="<a href=\"/index.html\">$logo_html $brand_text</a>"
elif [ -n "$logo_html" ]; then
header_brand="<a href=\"/index.html\">$logo_html</a>"
elif [ -n "$brand_text" ]; then
header_brand="<a href=\"/index.html\">$brand_text</a>"
else
header_brand="<a href=\"/index.html\">$title</a>"
fi
favicon_src=""
if [ "$logo_as_favicon" = "true" ] && [ -n "$logo" ]; then
favicon_src="$logo"
elif [ -n "$favicon" ]; then
favicon_src="$favicon"
fi
head_extra=""
if [ -n "$favicon_src" ]; then
head_extra="<link rel=\"icon\" href=\"$favicon_src\" />"
fi
MARKDOWN_SITE_ROOT="$src" MARKDOWN_FALLBACK_FILE="styles/$style.css" sh "$script_dir/markdown.sh" "$file" | awk -v title="$title" -v nav="$nav" -v footer="$footer" -v style_path="$style_path" -v header_brand="$header_brand" -v head_extra="$head_extra" -f "$awk_dir/render_template.awk" "$local_template"
}
echo "Building site from '$src' to '$out'..."
find "$src" -type d | sort | while read -r dir; do
rel_dir="${dir#$src/}"
[ "$dir" = "$src" ] && rel_dir="."
out_dir="$out/$rel_dir"
mkdir -p "$out_dir"
if [ -f "$dir/styles.css" ]; then
copy_style_with_resolved_vars "$dir/styles.css" "$out_dir/styles.css"
elif [ -f "$dir/style.css" ]; then
copy_style_with_resolved_vars "$dir/style.css" "$out_dir/styles.css"
fi
[ "$dir_indexes" != "true" ] && continue
if [ ! -f "$dir/index.md" ]; then
if [ "$single_file_index" = "true" ]; then
md_count=$(find "$dir" -maxdepth 1 -name "*.md" | wc -l)
if [ "$md_count" -eq 1 ]; then
md_file=$(find "$dir" -maxdepth 1 -name "*.md")
render_markdown "$md_file" > "$out_dir/index.html"
continue
fi
fi
temp_index="/tmp/kewt_index_$$.md"
display_dir="${rel_dir#.}"
[ -z "$display_dir" ] && display_dir="/"
echo "# Index of $display_dir" > "$temp_index"
echo "" >> "$temp_index"
find "$dir" -maxdepth 1 -not -path '*/.*' -not -path "$dir" | sort | while read -r entry; do
name="${entry##*/}"
case "$name" in
template.html|site.conf|style.css|index.md) continue ;;
esac
if [ -d "$entry" ]; then
echo "- [${name}/](${name}/index.html)" >> "$temp_index"
elif [ "${entry%.md}" != "$entry" ]; then
echo "- [${name%.md}](${name%.md}.html)" >> "$temp_index"
else
echo "- [$name]($name)" >> "$temp_index"
fi
done
render_markdown "$temp_index" > "$out_dir/index.html"
rm "$temp_index"
fi
done
if [ ! -f "$out/styles.css" ] && [ -f "styles/$style.css" ]; then
copy_style_with_resolved_vars "styles/$style.css" "$out/styles.css"
fi
find "$src" -type f | sort | while IFS= read -r file; do
rel_path="${file#$src/}"
dir_rel=$(dirname "$rel_path")
out_dir="$out/$dir_rel"
case "${file##*/}" in
template.html|site.conf|style.css|styles.css) continue ;;
esac
if [ "$single_file_index" = "true" ] && [ "${file%.md}" != "$file" ] && [ ! -f "$(dirname "$file")/index.md" ]; then
md_count=$(find "$(dirname "$file")" -maxdepth 1 -name "*.md" | wc -l)
[ "$md_count" -eq 1 ] && continue
fi
if [ "${file%.md}" != "$file" ]; then
out_file="$out/${rel_path%.md}.html"
render_markdown "$file" > "$out_file"
else
cp "$file" "$out/$rel_path"
fi
done
echo "Build complete."