Up to Main Index                             Up to Journal for April, 2025

                    JOURNAL FOR WEDNESDAY 30TH APRIL, 2025
______________________________________________________________________________

SUBJECT: Bash static site generator for tutorial rewrite
   DATE: Wed 30 Apr 21:52:24 BST 2025

Last time in the journal I wrote about re-writing the Mere ICE tutorial. I had
a few goals for the re-write:

  • Tutorial should be proper web pages, not source in the editor.
  • The tutorial should have live editable, executable examples.
  • Allow multiple examples per page.
  • It should look nice and fit the WolfMUD website style.
  • Tutorial and examples must be easy to write and low maintenance.

At the time I also said:

    I don’t think I can make the pages of the tutorial simpler than that. The
    code is in <code></code> blocks. There are call-outs for “try this” and
    “tips” sections using <blockquote></blockquote>.

    Unfortunately, even with includes, there is quite a bit of boiler-plate at
    the start of each page. By the way, I am hand-coding everything - no
    frameworks.

I’ve since had time to mull over my approach to the re-write. As a result I’m
now using Markdown. Specifically, on Debian, I am using the “lowdown” Markdown
processor. And… I’ve written a tiny static site generator (sitegen) in Bash to
help. Even handles the includes/boilerplate. The full source code, 50 lines:


    #!/bin/bash
    # sitegen: A simple static site generator.
    #
    # Released into the public domain under “The Unlicense” license.
    # SPDX-License-Identifier: Unlicense

    hlp=$(cat <<EOT
    Usage: ./sitegen [-a] [-v]

      -a = rebuild all
      -v = verbose output
    EOT
    )

    options="--html-no-escapehtml --html-no-skiphtml"
    all=0
    verbose=0

    while getopts ":ahv" Option; do
      case $Option in
        a ) all=1;;
        v ) verbose=1;;
        h ) echo -e "$hlp"; exit;;
        * ) echo -e "Invalid option: ${Option}\n${hlp}"; exit;;
      esac
    done
    shift $(($OPTIND - 1))

    for md in `ls ./*.md`; do
      html=${md/#./..}
      html=${html/%.md/.html}

      if [ "$all" != "1" ] && [ "$md" -ot "$html"  ]; then
        [ "$verbose" == "1" ] && echo "Not modified: $md → ${html}"
        continue
      fi

      [ "$verbose" == "1" ] && echo "Processing: ${md} → ${html}"
      lowdown $options $md > ./body.inc
      grep -q "</code>" ./body.inc
      code=$?
      if [ "$code" -eq 0 ]; then
        [ "$verbose" == "1" ] && echo "  Including Javascript and wasm."
        cat head.inc body.inc foot.inc > $html
      else
        [ "$verbose" == "1" ] && echo "  No code found."
        cat nojs-head.inc body.inc foot.inc > $html
      fi
    done
    [ "$verbose" == "1" ] && echo "Finished!"


My working directory structure then looks like this, note I’ve only included
the .md and .html for the index and whats-new pages here for brevity:


    ice
    |-- tutorial.css
    |-- index.html
    |-- whats-new.html
    |-- ice.js
    |-- wasm_exec.js
    |-- ice.wasm
    `-- md-src
        |-- sitegen
        |-- body.inc
        |-- foot.inc
        |-- head.inc
        |-- nojs-head.inc
        |-- index.md
        `-- whats-new.md


I write Markdown in the md-src directory. This directory also contains the
head.inc, nojs-head.inc and foot.inc include files. The main ice directory is
where the HTML, CSS, Javascript and WASM live. Development is then as easy as:


    >vim index.md
    >./sitegen
    >


When sitegen is run it finds *.md newer than the corresponding *.html,
processes the *.md to create body.inc HTML, top and tails body.inc with the
relevant includes and saves the new HTML in the parent directory. An example
rebuild with verbose output turned on that shows the checking and rebuilding:


    >./sitegen -v
    Not modified: ./contents.md → ../contents.html
    Not modified: ./debugging-programs.md → ../debugging-programs.html
    Processing: ./index.md → ../index.html
      Including Javascript and wasm.
    Not modified: ./variables.md → ../variables.html
    Not modified: ./what-is-mere.md → ../what-is-mere.html
    Processing: ./whats-new.md → ../whats-new.html
      No code found.
    Not modified: ./white-space-and-punctuation.md → ../white-space-and-pu…
    Not modified: ./writing-code.md → ../writing-code.html
    Finished!
    >


A single HTML page takes less than 50ms to build. Building the current 8 pages
takes 125ms. When I run sitegen it only rebuilds pages where the *.md has been
changed, unless I pass in the ‘-a’ flag to rebuild all files.

The script isn’t perfect. If I modify one of the *.inc include files I have to
remember to use ‘-a’ and rebuild everything. If I delete or rename a *.md file
I have to delete or rename the associated *.html file. Besides fixing those
issues, there are a few nice things I can do in the future. For instance,
making a Git hook to automatically generate the HTML when the Markdown is
pushed to the live site. Eventually md-src will be moved outside of the ice
directory, which will be public on the live web server.

I’m currently treating this as an experiment. Depending on how it goes, and so
far it’s going pretty well, I might revisit the main WolfMUD website ;)

The sitegen script is free to all — Take it, hack it, make it yours. If you
find sitegen useful, please drop me an email and let me know. Any enhancements
sent my way are greatly appreciated as well: diddymus@wolfmud.org

Oh! I guess I am using a framework now after all… *sigh*

--
Diddymus


  Up to Main Index                             Up to Journal for April, 2025