moved to new static site generator

This website has been powered by poole for a long time. I don’t think I have any real reasons to migrate from poole.

Do you know there are static site generators written in UNIX Shell? I think it’s a good choice for old-school programmers, who feel uncomfortable with Ruby, Python and even Node.js.

All that shell madness started with werc, a nice tool used by suckless and cat-v. Then sw appeared.

I just made another one static site generator in pure UNIX Shell. I called it “”. This website is now generated with Stash.


hg clone


Stash is a single script. For the minimal setup the following files should be created:

`-- src
    `-- layouts
        `-- default.html

Here default.html is a layout template. You can use <% .. %> tags to specify where to render page title, description, keywords, content:

<!doctype html>
    <title> <%title%> </title>
    <% content %>

Content pages are mostly written in Markdown and consist of two parts: header and body. Header defines a list of page variables, like title, description etc. Body is a markdown text that can include <% ... %> tags to define custom logic. Tag can be either a page variable (like in layout templates) or a shell function/command:

title: Test page
description: My test page geneated with Stash
author: Serge Zaitsev
keywords: stash, static site, generator

# <% title %>

This is a test page. Today is <% date %>.

Generated with []( site generator.

Now if you run you’ll get a rendered page src.static/index.html:

<!doctype html>
    <title> Test page </title>
    <h1>Test page</h1>
    <p>This is a test page. Today is Fri Jul 19 11:52:54 EEST 2013.</p>
    <p>Generated with <a href=""></a> site generator.</p>


Stash loads script on start and allows you to easily extend it. Every function defined in can be called from within <% ... %> tags.

Also, there are special “hook” functions: ongen and onexit. The first one is called for every page generated, and the second one is called after the whole site is processed allowing you to cleanup everything.

I used “hooks” to build a list of blog pages with ongen and then render that list into a single blog index page in onexit.

how it works

Inside it’s ugly. Yet simple. Pages are parsed with AWK. AWK produces shell script that generates markdown when executed. During parsing every page variable is converted into export statement:

title: Hello world   =>   export page_title='Hello world'
date:  18 Jul 2013   =>   export page_date='18 Jul 2013'

Next, when parsing body the idea is to generate printf and echo statements for every piece of text and raw shell commands for every <% .. %> tag:

# Title              =>   printf '# Title'
Hello world. Today is <% date %>.
                     =>   printf 'Hello world. Today is '; date

Statements are now separated with semicolons, which brings some not obvious limitations:

This won't work (semicolons after "do" are not allowed):

<% for i in $(seq 1 10) ; do %> Foo <% echo $i %> <% done %>
for i in $(seq 1 10) ; do ; printf 'Foo' ; echo $i ; done

This will work:

<% for i in $(seq 1 10) ; do true %> Foo <% echo $i %> <% done %>
for i in $(seq 1 10) ; do true ; printf 'Foo' ; echo $i ; done

Also beware of semicolons in the end of tag:

<% date ; %>
will be converted to
date ;;
and ";;" is an invalid token here

I’m sure there are much more pitfalls, so do everything with extreme care. No need to say that NEVER EVER generate such sites:

Hello <% sudo rm -rf /usr %> world

So, to sum up the whole beauty of this approach here is a long way a page goes during generation:

Page -> Shell -> AWK -> Shell -> eval -> Markdown -> HTML


So, now there is one more static site generator in Shell. Less than 100 LOC, pure UNIX Shell, extensible, MIT-licensed. Enjoy!

I hope you’ve enjoyed this article. You can follow – and contribute to – on Github, Twitter or subscribe via rss.

Jul 18, 2013