From c63794d3da3c599cf08854d30c0cf6f2b4ed1986 Mon Sep 17 00:00:00 2001 From: historia Date: Tue, 8 Sep 2026 17:31:49 -0400 Subject: feat: explicitly listed and orderable categories --- README.md | 10 +---- demo/example.webp | Bin 15022 -> 0 bytes ssg-blog | 107 +++++++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 87 insertions(+), 30 deletions(-) delete mode 100644 demo/example.webp diff --git a/README.md b/README.md index 03ab255..d5e4db5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ chmod +x /usr/bin/ssg-blog ssg-blog src dst 'https://my-website.net' ``` -ssg-blog checks for subdirectories in the src directory, each of which becomes a category heading (non-recursively). It will generate a sorted list of links to any .md file in those directory (including nested directories). +Blog categories are hard-coded in the `CATEGORIES` variable at the top of the script. Each one becomes a category heading in the order listed. Blog posts should be in a directory with the same name. ``` ssg-blog/ @@ -60,14 +60,6 @@ Note a few things about the output: 2. Files named index.md will get have a link generated without .html at the end. 3. Files outside subdirectories (e.g. about.md) will be rendered but won't show up on the index. -## Change how categories are sorted - -The category headings are sorted alphabetically. If you want to change this, you have to edit the `sort` on this line: - -``` -find "$src" -mindepth 1 -maxdepth 1 -type d ! -path "*/.*" | sort | -``` - # Differences from ssg The title argument from ssg is unnecessary so I've removed it. `render_html_file` used it to modify the first `

` tag and auto-populate the `` if none is specified, which is functionality I've never needed. Instead I check the frontmatter of each blog post and update the `<title>` tag with the `title:` field. diff --git a/demo/example.webp b/demo/example.webp deleted file mode 100644 index d9153d9..0000000 Binary files a/demo/example.webp and /dev/null differ diff --git a/ssg-blog b/ssg-blog index 398aa77..44997e0 100755 --- a/ssg-blog +++ b/ssg-blog @@ -16,6 +16,13 @@ # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # +# Blog categories: top-level source directories that make up the blog, +# in the order they appear on the index page. .html files inside these +# directories get the site header and footer; .html files anywhere else +# are copied verbatim (e.g. standalone pages like JS tools). If empty, +# every top-level directory is a category, sorted alphabetically. +CATEGORIES="games lists money technology tutorials" + main() { test -n "$1" || usage test -n "$2" || usage @@ -73,9 +80,13 @@ main() { fi echo "$files" | grep '\.html$' | + wrap_html | render_html_files "$src" "$dst" - echo "$files" | grep -Ev '\.md$|\.html$' | + { + echo "$files" | grep '\.html$' | verbatim_html + echo "$files" | grep -Ev '\.md$|\.html$' + } | (cd "$src" && cpio -pu "$dst") fi @@ -192,6 +203,45 @@ render_html_file() { } } +in_category_dir() { + f="${1#./}" + dir="${f%%/*}" + if [ "$dir" = "$f" ]; then + return 1 + fi + for category in $CATEGORIES; do + if [ "$dir" = "$category" ]; then + return 0 + fi + done + return 1 +} + +wrap_html() { + if [ -z "$CATEGORIES" ]; then + cat + return 0 + fi + while read -r f; do + if in_category_dir "$f"; then + echo "$f" + fi + done + return 0 +} + +verbatim_html() { + if [ -z "$CATEGORIES" ]; then + return 0 + fi + while read -r f; do + if ! in_category_dir "$f"; then + echo "$f" + fi + done + return 0 +} + list_pages() { e="\\( -name '*.html' -o -name '*.md' \\)" cd "$1" && eval "find . -type f ! -path '*/.*' ! -path '*/_*' $IGNORE $e" | @@ -206,25 +256,40 @@ list_pages() { sed 's#^./##;s#.md$#.html#;s#/index.html$#/#' } +list_category_articles() { + # $1 = src, $2 = category, $3 = category order + find "$1/$2" -type f -name "*.md" ! -path "*/.*" | + while read -r article; do + test "$(get_frontmatter_field "$article" "publish")" != "false" || continue + date=$(get_frontmatter_field "$article" "date") + test -n "$date" || continue + title=$(get_frontmatter_field "$article" "title") + url="${article#"$1"}" + url="${url%.md}.html" + if [ "${url##*/}" = "index.html" ]; then + url="${url%/index.html}/" + fi + url="${url#/}" + echo "$3|$2|$date|$title|$url" + done +} + list_articles() { - find "$1" -mindepth 1 -maxdepth 1 -type d ! -path "*/.*" | sort | - while read -r category_dir; do - category=$(basename "$category_dir") - find "$category_dir" -type f \( -name "*.md" -o -name "index.md" \) ! -path "*/.*" | - while read -r article; do - test "$(get_frontmatter_field "$article" "publish")" != "false" || continue - date=$(get_frontmatter_field "$article" "date") - test -n "$date" || continue - title=$(get_frontmatter_field "$article" "title") - url="${article#"$1"}" - url="${url%.md}.html" - if [ "${url##*/}" = "index.html" ]; then - url="${url%/index.html}/" + if [ -n "$CATEGORIES" ]; then + n=0 + for category in $CATEGORIES; do + if [ -d "$1/$category" ]; then + n=$((n+1)) + list_category_articles "$1" "$category" "$n" fi - url="${url#/}" - echo "$category|$date|$title|$url" done - done + else + find "$1" -mindepth 1 -maxdepth 1 -type d ! -path "*/.*" | sort | + while read -r category_dir; do + n=$((n+1)) + list_category_articles "$1" "$(basename "$category_dir")" "$n" + done + fi } render_index() { @@ -232,8 +297,8 @@ render_index() { echo "$HEADER" > "$output" last_category="" - list_articles "$src" | sort -t'|' -k1,1 -k2,2r | - while IFS='|' read -r category date title url; do + list_articles "$src" | sort -t'|' -k1,1n -k3,3r | + while IFS='|' read -r order category date title url; do if [ "$category" != "$last_category" ]; then display_category=$(echo "$category" | sed 's/_/ /g' | awk '{for(i=1;i<=NF;i++){s=$i;$i=toupper(substr(s,1,1)) substr(s,2)}; print}') echo "<h1>$display_category</h1>" >> "$output" @@ -258,7 +323,7 @@ rfc822_date() { } render_rss() { - list_articles "$src" | sort -t'|' -k2,2r | + list_articles "$src" | sort -t'|' -k3,3r | { echo '<?xml version="1.0" encoding="UTF-8"?>' echo '<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">' @@ -268,7 +333,7 @@ render_rss() { echo "<description>$base_url</description>" echo "<atom:link href=\"$base_url/rss.xml\" rel=\"self\" type=\"application/rss+xml\"/>" - while IFS='|' read -r category date title url; do + while IFS='|' read -r order category date title url; do escaped_title=$(echo "$title" | xml_escape) escaped_url=$(echo "$url" | xml_escape) pub_date=$(rfc822_date "$date") -- cgit v1.2.3