Up to Main Index                               Up to Journal for May, 2025

                     JOURNAL FOR WEDNESDAY 21ST MAY, 2025
______________________________________________________________________________

SUBJECT: mdinkling - Bash static site generator from Markdown
   DATE: Wed 21 May 14:11:10 BST 2025

I’ve had a lot of responses to my Bash static site generator, after making it
available in the journal. There have been  many requests for enhancements and
some helpful suggestions. I initially wrote the script for myself, many asked
for it to be more agnostic to my specific use case.

As a result I’ve been quietly working away to make improvements. First of all,
the name. A lot of people didn’t like “sitegen”. I’ve since had a thought or
two and the script is now called “mdinkling”. The “md” to signify Markdown,
“ink” as in writing and “inkling” as in a thought or an idea.

Other improvements worthy of note:

  • Configurable input/output directories
  • Configurable and managed CSS
  • Support for sub-directories
  • Optional GZip’ed copies of HTML and CSS
  • Configurable marker for when to use alternate includes
  • Alternate header and footer includes
  • Dry run only
  • Verbose logging output
  • Include placeholder/variable substitution

For the placeholder/variable substitution, placeholders are specified in the
includes using “%%name%%”. The values are specified in the Markdown header as
normal Markdown metadata “name: value” pairs. See the quick guide below for an
example.

For processing Markdown, mdinkling uses the excellent lowdown[1] Markdown
processor. lowdown supports a lot of CommonMark[2], Multimarkdown[3] and
GitHub Flavored Markdown[4]: headers, paragraphs, bold, emphasis, monospace,
super-script, sub-script, strike through, links (with IDs and CSS classes),
raw HTML, typography replacement, numbered lists, unordered lists, definition
lists, task lists, block quotes, annotated code blocks, rules, tables and
footnotes. More features can be enabled, or disabled, by editing “options” in
the mdinkling script. All development and testing has used the Debian lowdown
package v2.0.2-2.

Due to the improvements, and better comments, the mdinkling script is now 266
lines long, the original was just 50 lines. All of the flags are listed in the
help:


  >mdinkling -h
  Usage: mdinkling [-a] [-c CSS] [-d] [-h] [-i DIR] [-m MARKER] [-n] [-o DIR] [-v]

  Flags:
    -a  rebuild all HTML files
    -c  CSS file to use (relative to input directory)
    -d  debug tracing
    -h  display this help
    -i  input directory for Markdown files, defaults current directory
    -m  marker, if found in generated HTML use alternate includes
    -n  dry run only
    -o  output directory for generated HTML, default current directory
    -v  verbose output
    -V  display version and exit
    -z  generate gzip'ed copy of HTML output
  >


You can grab a copy of the new and improved version here: mdinkling
Just right click the link, save as “mdinkling”. For the security conscious the
SHA256 of the script is:

       b5ad4d8b0038dc19ba09c394d9fdd829558595a95e394c4e5437b5c014eee3fe

I will be setting up a Git repository for mdinkling, complete with a tutorial.
For now, a really quick “how to” guide:


                           mdinkling: QUICK GUIDE
                           ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
As a quick introduction we are going to create a small site for a developer
journal.

Create two directories. One for the public site files. The other for the CSS,
includes and Markdown files. For now I’m going to put both directories in a
project directory called “mysite”:


    > mkdir -p mysite/public
    > mkdir -p mysite/src
    >


The minimum files required are a header include, a footer include and at least
one Markdown file:


  • mysite/src/header.inc:

      <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="UTF-8">
          <title>My Journal</title>
          <meta name="viewport" content="width=device-width">
        </head>
        <body>


  • mysite/src/footer.inc:

          <footer>Evil Genius, 2025</footer>
        </body>
      </html>


  • mysite/src/index.md:

      # My Journal

      Welcome to my developer journal!


With those files written, we can generate the initial site:


    > cd mysite
    > mdinkling -v -i src -o public
    2025-05-16T20:45:34+0100: mdinkling v0.0.2, run started.
    2025-05-16T20:45:34+0100: PATH=/home/diddymus/bin:/usr/local/bin:/usr/bin
    2025-05-16T20:45:34+0100: PWD=/home/diddymus/mdsite
    2025-05-16T20:45:34+0100:       input path: /home/diddymus/mdsite/src
    2025-05-16T20:45:35+0100: output directory: /home/diddymus/mdsite/public
    2025-05-16T20:45:35+0100: common root path: /home/diddymus/mdsite/
    2025-05-16T20:45:35+0100: Processing: src/index.md
    2025-05-16T20:45:35+0100:   No alternate marker found: public/index.html
    2025-05-16T20:45:35+0100:   Writing updated HTML: public/index.html
    2025-05-16T20:45:35+0100: No CSS specified.
    2025-05-16T20:45:35+0100: Done!
    >

NOTE: For the rest of the examples I’ll be omitting the timestamps from the
output of mdinkling for brevity.

The HTML page index.html should now exist under the public directory:


    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <title>My Journal</title>
        <meta name="viewport" content="width=device-width">
      </head>
      <body>
    <h1 id="my-journal">My Journal</h1>
    <p>Welcome to my developer journal!</p>
        <footer>Evil Genius, 2025</footer>
      </body>
    </html>


Let’s create an entry for the journal. The paths will take the following
format src/year/month/date.html for the entries. First we create the paths for
the year and month:


    > mkdir -p src/2025/4
    >


Next, we create the following src/2025/4/30.md Markdown entry for the Journal:


    # Wrote a basic static site generator
    ## Wednesday, 30th April 2025

    Today I wrote a basic static site generator in 50 lines of Bash and
    unleashed it upon the world!

    [Back](/)


This new page needs linking to from our homepage in mysite/src/index.md:


    # My Journal

    Welcome to my developer journal site!

    (30th April)[/2025/4/30.html]


We then generate the new HTML page and update the existing index page:


    > mdinkling -v -i src -o public
    …: mdinkling v0.0.2, run started.
    …: PATH=/home/diddymus/bin:/usr/local/bin:/usr/bin:/bin
    …: PWD=/home/diddymus/mdsite
    …:       input path: /home/diddymus/mdsite/src
    …: output directory: /home/diddymus/mdsite/public
    …: common root path: /home/diddymus/mdsite/
    …: Processing: src/index.md
    …:   No alternate marker found: public/index.html
    …:   Writing updated HTML: public/index.html
    …: Processing: src/2025/4/30.md
    …:   Creating dir: /home/diddymus/mdsite/public/2025/4/
    …:   No alternate marker found: public/2025/4/30.html
    …:   Writing updated HTML: public/2025/4/30.html
    …: No CSS specified.
    …: Done!
    >


For good measure we’ll create another entry for src/2025/5/16.md:


    # Re-wrote site generator
    ## Friday, 16th May 2025

    Sadly, the world was not ready for my site generator. The heathens and
    infidels didn’t recognise my genius and had the audacity to ask for more!

    [Back](/)


We then need to add a new link to the end of the home page src/index.md


    ## May, 2025
     * [16th - Re-wrote site generator](/2025/5/16.html)


And, regenerate:


    > mdinkling -v -i src -o public
    …: mdinkling v0.0.2, run started.
    …: PATH=/home/diddymus/bin:/usr/local/bin:/usr/bin:/bin
    …: PWD=/home/diddymus/mdsite
    …:       input path: /home/diddymus/mdsite/src
    …: output directory: /home/diddymus/mdsite/public
    …: common root path: /home/diddymus/mdsite/
    …: Processing: src/index.md
    …:   No alternate marker found: public/index.html
    …:   Writing updated HTML: public/index.html
    …: Processing: src/2025/5/16.md
    …:   Creating dir: /home/diddymus/mdsite/public/2025/5/
    …:   No alternate marker found: public/2025/5/16.html
    …:   Writing updated HTML: public/2025/5/16.html
    …: Not modified: src/2025/4/30.md -> public/2025/4/30.html
    …: No CSS specified.
    …: Done!
    >


Our rendered homepage should now look something like:


    My Journal

    Welcome to my developer journal!

    April, 2025
     • 30th - Wrote a basic static site generator

    May, 2025
     • 16th - Re-wrote site generator

    Evil Genius, 2025


We also have the two journal entries:


    Wrote a basic static site generator

    Wednesday, 30th April 2025

    Today I wrote a basic static site generator in 50 lines of Bash and
    unleashed it upon the world!

    Back

    Evil Genius, 2025


And:


    Re-wrote site generator

    Friday, 16th May 2025

    Sadly, the world was not ready for my site generator. The heathens and
    infidels didn’t recognise my genius and had the audacity to ask for more!

    Back

    Evil Genius, 2025


Hrm, that’s not going to set the world on fire. Maybe if we add some CSS to
spruce things up? First create a directory for the CSS:


    > mkdir -p src/css
    >


Then we add the CSS as src/css/main.css:


    body {
      color: darkslategrey;
      font-size: 1.4em;
      margin-left: auto;
      margin-right: auto;
      max-width: 800px;
      padding: 1em;
    }

    h1 {
      text-align: center;
      text-decoration: underline;
      text-transform: capitalize;
    }

    h2 {
      font-size: 1.1em;
    }

    h1 + h2 {
      margin-top: 0;
      text-align: right;
    }

    p {
      text-align: justify;
    }

    footer {
      color: grey;
      font-size: 0.75em;
      margin-top: 4em;
      text-align: center;
    }


Finally we include the CSS in the header include src/header.inc file:


    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <title>My Journal</title>
        <meta name="viewport" content="width=device-width">
        <link rel="stylesheet" type="text/css" href="/css/main.css">
      </head>
      <body>


Then regenerate, specifying our CSS file:


    > mdinkling -v -i src -o public -c css/main.css
    …: mdinkling v0.0.2, run started.
    …: PATH=/home/diddymus/bin:/usr/local/bin:/usr/bin:/bin
    …: PWD=/home/diddymus/mdsite
    …:       input path: /home/diddymus/mdsite/src
    …: output directory: /home/diddymus/mdsite/public
    …: common root path: /home/diddymus/mdsite/
    …:         CSS file: /home/diddymus/mdsite/src/css/main.css
    …: Processing: src/index.md
    …:   No alternate marker found: public/index.html
    …:   Writing updated HTML: public/index.html
    …: Not modified: src/2025/5/16.md -> public/2025/5/16.html
    …: Not modified: src/2025/4/30.md -> public/2025/4/30.html
    …: Copying CSS: public/css/main.css
    …:   Creating CSS dir: /home/diddymus/mdsite/public/css/
    …: Done!
    >


One thing left to sort out, Metadata. Without SEO no world domination. First
we edit the header include src/header.inc to include some placeholders. We
will also make the title a placeholder. Placeholders take the form “%%name%%”.
The names are case sensitive and can contain a-z, A-Z, 0-9, _ and - only. In
this case we add placeholders for title, description, and keywords:


    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <title>%%title%%</title>
        <meta name="description" content="%%description%%">
        <meta name="keywords" content="%%keywords%%">
        <meta name="viewport" content="width=device-width">
        <link rel="stylesheet" type="text/css" href="/css/main.css">
      </head>
      <body>


Next we need to edit src/index.md, src/2025/4/30.md and src/2025/5/16.md to
add the metadata values:


    • src/index.md:

      title: My Journal
      description: The developer journal of Diddymus.
      keywords: diddymus, journal, home, index

      # My Journal

      Welcome to my developer journal!

      ## April, 2025
       * [30th - Wrote a basic static site generator](/2025/4/30.html)

      ## May, 2025
       * [16th - Re-wrote site generator](/2025/5/16.html)


    • src/2025/4/30.md

      title: Journal for 30th April 2025
      description: Wrote a basic static site generator.
      keywords: site generator, static, markdown

      # Wrote a basic static site generator
      ## Wednesday, 30th April 2025

      Today I wrote a basic static site generator in 50 lines of Bash and
      unleashed it upon the world!

      [Back](/)


    • src/2025/5/16.md

      title: Journal for 16th May 2025
      description: Re-wrote site generator.
      keywords: site generator, static, markdown, re-write

      # Re-wrote site generator
      ## Friday, 16th May 2025

      Sadly, the world was not ready for my site generator. The heathens and
      infidels didn’t recognise my genius and had the audacity to ask for
      more!

      [Back](/)


Then we regenerate, as the header.inc was modified everything will be rebuilt:


  > mdinkling -v -i src -o public -c css/main.css
  …: mdinkling v0.0.2, run started.
  …: PATH=/home/diddymus/bin:/usr/local/bin:/usr/bin:/bin
  …: PWD=/home/diddymus/mdsite
  …:       input path: /home/diddymus/mdsite/src
  …: output directory: /home/diddymus/mdsite/public
  …: common root path: /home/diddymus/mdsite/
  …:         CSS file: /home/diddymus/mdsite/src/css/main.css
  …: Processing: src/index.md
  …:   Found metadata, tag: 'title' content: 'My Journal'
  …:   Found metadata, tag: 'description' content: 'The developer journal of
         Diddymus.'
  …:   Found metadata, tag: 'keywords' content: 'diddymus, journal, home,
         index'
  …:   No alternate marker found: public/index.html
  …:   Replacing '%%description%%' with 'The developer journal of Diddymus.'
  …:   Replacing '%%title%%' with 'My Journal'
  …:   Replacing '%%keywords%%' with 'diddymus, journal, home, index'
  …:   Writing updated HTML: public/index.html
  …: Processing: src/2025/5/16.md
  …:   Found metadata, tag: 'title' content: 'Journal for 16th May 2025'
  …:   Found metadata, tag: 'description' content: 'Re-wrote site generator.'
  …:   Found metadata, tag: 'keywords' content: 'site generator, static,
         markdown, re-write'
  …:   No alternate marker found: public/2025/5/16.html
  …:   Replacing '%%description%%' with 'Re-wrote site generator.'
  …:   Replacing '%%title%%' with 'Journal for 16th May 2025'
  …:   Replacing '%%keywords%%' with 'site generator, static, markdown,
         re-write'
  …:   Writing updated HTML: public/2025/5/16.html
  …: Processing: src/2025/4/30.md
  …:   Found metadata, tag: 'title' content: 'Journal for 30th April 2025'
  …:   Found metadata, tag: 'description' content: 'Wrote a basic static site
         generator.'
  …:   Found metadata, tag: 'keywords' content: 'site generator, static,
         markdown'
  …:   No alternate marker found: public/2025/4/30.html
  …:   Replacing '%%description%%' with 'Wrote a basic static site generator.'
  …:   Replacing '%%title%%' with 'Journal for 30th April 2025'
  …:   Replacing '%%keywords%%' with 'site generator, static, markdown'
  …:   Writing updated HTML: public/2025/4/30.html
  …: Not modified: src/css/main.css -> public/css/main.css
  …: Done!
  >


Finally, we want our dastardly deeds to be delivered post-haste. We generate
one final time with the -z flag, which produces static Gzip’ed versions of the
HTML and CSS:


    > mdinkling -z -v -i src -o public -c css/main.css
    …: mdinkling v0.0.2, run started.
    …: PATH=/home/diddymus/bin:/usr/local/bin:/usr/bin:/bin
    …: PWD=/home/diddymus/mdsite
    …:       input path: /home/diddymus/mdsite/src
    …: output directory: /home/diddymus/mdsite/public
    …: common root path: /home/diddymus/mdsite/
    …:         CSS file: /home/diddymus/mdsite/src/css/main.css
    …: +++ GZIP'ED HTML +++
    …: Not modified: src/index.md -> public/index.html
    …:   Creating GZIP: public/index.html.gz
    …: Not modified: src/2025/5/16.md -> public/2025/5/16.html
    …:   Creating GZIP: public/2025/5/16.html.gz
    …: Not modified: src/2025/4/30.md -> public/2025/4/30.html
    …:   Creating GZIP: public/2025/4/30.html.gz
    …: Not modified: src/css/main.css -> public/css/main.css
    …: Compressing CSS: public/css/main.css.gz
    …: Done!
    >


The home page now looks like this, but with style:


                                  My Journal
                                  ‾‾‾‾‾‾‾‾‾‾
    Welcome to my developer journal!

    April, 2025

         • 30th - Wrote a basic static site generator

    May, 2025

         • 16th - Re-wrote site generator

                              Evil Genius, 2025



Our final project directories look like this:


    mysite
    |-- public
    |   |-- 2025
    |   |   |-- 4
    |   |   |   |-- 30.html
    |   |   |   `-- 30.html.gz
    |   |   `-- 5
    |   |       |-- 16.html
    |   |       `-- 16.html.gz
    |   |-- css
    |   |   |-- main.css
    |   |   `-- main.css.gz
    |   |-- index.html
    |   `-- index.html.gz
    `-- src
        |-- 2025
        |   |-- 4
        |   |   `-- 30.md
        |   `-- 5
        |       `-- 16.md
        |-- body.inc
        |-- css
        |   `-- main.css
        |-- footer.inc
        |-- header.inc
        `-- index.md


Everything under mysite/src I would put into a Git repository. Everything
under mysite/public is generated. You can either clone the src repository to a
live server and re-generate there. An alternative is to put the generated
mysite/public into a repository and clone that on a live server.

Note, it is also possible to put everything into a single directory. Just
specify the same input/output directories, or omit them and run mdinkling in
the public directory. In which case the project directory would look like:


    mysite
    `-- public
        |-- 2025
        |   |-- 4
        |   |   |-- 30.html
        |   |   |-- 30.html.gz
        |   |   `-- 30.md
        |   `-- 5
        |       |-- 16.html
        |       |-- 16.html.gz
        |       `-- 16.md
        |-- body.inc
        |-- css
        |   |-- a.css
        |   |-- main.css
        |   `-- main.css.gz
        |-- footer.inc
        |-- header.inc
        |-- index.html
        |-- index.html.gz
        |-- index.md
        |-- test.html
        |-- test.html.gz
        `-- test.md


Would I recommend this? Not unless you want to make the raw Markdown public.
You can always configure your web server to prohibit access to *.md and *.inc
files.

I was very tempted to re-write mdinkling in Go, I still might. As a simple
Bash script, mdinkling is easier for others to quickly hack on and tailor to
their own needs. As it stands, I need to handle other assets such as fonts and
images in the next iteration.

Comments? Found a bug? Feature request?, email me: diddymus@wolfmud.org

--
Diddymus

  [1] lowdown Markdown processor: https://kristaps.bsd.lv/lowdown/

  [2] CommonMark: http://commonmark.org/

  [3] Multimarkdown (MMD): http://fletcherpenney.net/multimarkdown

  [4] GitHub Flavored Markdown (GFM): https://github.github.com/gfm


  Up to Main Index                               Up to Journal for May, 2025