JOPA

Simple thus hackable static site generator

Internals

The idea of Jopa is extendability through simplicity. Everything in Jopa's world is a bash script, and almost everything is controlled through the use of environment variables.

This is done for you to be in control of what is going on and be able to apply any logic at any step of site generation. You can do anything bash scripts can, and modify your content as you wish - no need for unnecessary plugins, libs, yadda-yadda-yadda. Just you and your shell - grep, cat and cut, anything.

But how does Jopa actually work?

Pages

First, it goes through the list of pages, specified in $pages variable, and, to render them, does source them as a bash script. This leads the environment for further processing. The sourcing is done on a per-page scope, to avoid interference.

By default, $pages equals to pages/*.jsh wildcard. The extension is short for "Jopa shell", to differ your posts from regular shell scripts (remember, everything is a script).

Pages can have any arbitrary logic, but most of the time they just define metadata such as title, description and, obviously, the actual page content. To make text easier to write, Jopa defines multiline helper with an optional "filter" that can be used to transform content from one format to another (we are generating a website, after all, so we probably want the content to be in HTML). Here is an example of a page:

title="My wonderful page"

multiline markdown content << 'JOPA'

# Hello World!

Just my blog post, how are you?

JOPA

Here, we define two env variables: $title and $content (names are arbitrary), but $content is defined with multiline helper that passes text through markdown utility (which you should install independently) to generate HTML content. Notice << 'JOPA' on the line with multiline call and JOPA at the very end. These are so-called "heredocs" and in bash to define multiline strings. You can read about them somewhere else, just note, that most of the time you want the first delimiting identifier to be in single quotes, otherwise your text is subject of command evaluation which is a dangerous thing... Imagine the following page:

multiline content << JOPA

Don't do $(rm -rf ~/) in your scripts!

JOPA

You probably don't want this to be executed... But sometimes you do want the evaluation in these strings (for example, for in-line variable expansion), okay, it's up to you. Just be warned.

Another note is that you can use any thing as your delimiter, even Emojis. Isn't this fun?

Layouts

Next, after a page is sourced env variables are (re)defined, but if $layout is not defined yet while $layout_file is, the latter is also sourced. The script in this file must define $layout (and can do this conditionally). Again, you can have any logic, for example, have defaults for $title:

website_title="My Website"

# prepend website title to any page's title
if [[ "$title" ]]; then
  head_title="$title | $website_title"
else
  head_title="$website_title"
fi

# define mandatory layout variable
multiline layout << 'JOPA'

<!doctype html>
<meta charset=utf-8>
<title>${head_title}</title>
<h1>${title}</h1>
${content}

JOPA

Then, the string in $layout is used as a template to render the resulting file using envsubst utility, which basically replaces all variables in the template with their actual values. Logic-less templates you say? Kind of.

Well, is that simple.

Indexes

But here is one more thing. What about the index page, table of contents, Atom/RSS feeds and all alike? We need to display links to our wonderful pages somehow, in most cases on the main page - the website's index. For this purpose, there are indexes and the each helper.

Indexes are regular pages, except that they are processed separately from other pages and, obviously, are not "indexed", i. e. will not be present in the resulting list of pages. You must specify indexes in space-separated $indexes env variable, for example:

# add Table Of Contents page to the list of indexes
indexes="$indexes $from/toc.jsh"

By default, only $from/index.jsh is in this list.

Below is an example of such a page:

multiline indexer_main << 'JOPA'
  <li>
    <a href="/${target}">${title}</a>
  </li>
JOPA
index_main="$(each "$pages" render "$indexer_main")"

multiline content << JOPA
  My posts:
  <ol>${index_main}</ol>
JOPA

Here, we have regular page content defined, but it uses $index_main variable, which is defined as the result of each function call on a set of $pages, and each page is then fed into render "$indexer_main" function, i. e. rendered with the defined template. You also see that $target variable is used - this is a file name for a page being processed and you can use it to refer to the page.

Of course, instead of calling render you can call your own function which can do filtering, skip some pages you don't want to be displayed, and only then call render from.

Hope this sheds some light on how this pretty simple thing is working for you to be able to move to the next (but optional!) step - extending Jopa.