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