A transparent, extensible static site generator

Extending Bagatto with Janet Libraries and jpm.

Bagatto is designed to be transparent and extensible. These two features complement each other: by exposing as much of the functionality as we can in the form of ordinary functions and function signatures, we render the act of writing a website obvious and inspectable, and make it pratical for site authors to write their own logic or introduce libraries from the wider Janet ecosystem.

In this guide we’ll see an example of how to use jpm to manage janet dependencies.

The library we’ll introduce is Janet Hypertext, which allows a programmer to easily generate HTML using Janet s-expressions.

Here’s some template code from the Bagatto basic-site demo project:

{% (bagatto/include "templates/head") %}

<h1>{{ (get-in args [:config :title]) }}</h1> 
      {% (each page (args :pages)
            (printf ```
                    <a href="%s">%s</a>
                    (page-path args page)
                    (page :basename))) %}
{% (bagatto/include "templates/foot") %}

Here’s how we could express the same template using Hypertext1:

{$ (import hypertext) $}
{% (bagatto/include "templates/head") %}

<h1>{{ (get-in args [:config :title]) }}</h1> 

{% (each page (args :pages)
     (def path (page-path args page))
     (print (hypertext/markup
              (li :class "dib mr2"
                (a :class "pv2 ph3 fw6 nav focus-black"
                   :href path
                   [(page :basename)]))))) %}


{% (bagatto/include "templates/foot") %}

We make a compile-time call to (import hypertext) (like with a normal library), and then we access the library’s functions in the template.

hypertext is not included in Bagatto nor in the Janet standard library, so if we try to compile that template without any other changes we get an error:

code-src/bagatto/demo/basic-site [new-demos !] ⊕ bag index.janet
in thread <core/thread 0x7F74EC0BBF10>: error: 
Encountered error rendering output for site spec index:

could not find module hypertext:
Current render context:
@{:current-template "templates/index"}

By default it tries to import the hypertext library from two places:

  1. the global Janet package directory;
  2. the current working directory,

and the library is not available.

Luckily for us, Bagatto is designed to be extensible with the rest of the Janet ecosystem.

jpm, the Janet Package Manager

jpm allows us to manage dependencies and external libraries. There are two ways to install a library using jpm: globally, or locally.

Installing a package globally

By default, jpm install will install a Janet package so that any Janet runtime instance on the system can access it. We can see that in action here:

code-src/bagatto/demo/basic-site [new-demos !] ⊕ sudo jpm install
[sudo] password for zax: 
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 656 bytes | 164.00 KiB/s, done.
 * branch            master     -> FETCH_HEAD
   ca43fef..23d5697  master     -> origin/master
Updating ca43fef..23d5697
Fast-forward | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)
removing /usr/lib/janet/hypertext.janet
removing manifest /usr/lib/janet/.manifests/hypertext.jdn
generating /usr/lib/janet/.manifests/hypertext.jdn...
Installed as 'hypertext'.
copying src/hypertext.janet to /usr/lib/janet...
code-src/bagatto/demo/basic-site [new-demos !] ⊕ janet
Janet 1.12.2-5df8ac5 linux/x64 - '(doc)' for help
repl:1:> (import hypertext)
repl:2:> (hypertext/markup p)

After running jpm install, I can launch a normal janet instance and import that library.

Installing a package locally

On the other hand, I generally prefer to keep my package namespaces separate by project. To do this, we simply set the JANET_PATH environment variable. Here’s the same sequence of events, run locally:

code-src/bagatto/demo/basic-site [new-demos !] ⊕ mkdir vendor
code-src/bagatto/demo/basic-site [new-demos !] ⊕ JANET_PATH=vendor jpm install
cloning repository to vendor/.cache/https___gitlab.com_louis.jackman_janet-hypertext.git
Cloning into 'vendor/.cache/https___gitlab.com_louis.jackman_janet-hypertext.git'...
remote: Enumerating objects: 59, done.
remote: Counting objects: 100% (59/59), done.
remote: Compressing objects: 100% (24/24), done.
remote: Total 59 (delta 27), reused 54 (delta 25), pack-reused 0
Receiving objects: 100% (59/59), 23.68 KiB | 4.74 MiB/s, done.
Resolving deltas: 100% (27/27), done.
generating /home/zax/code-src/bagatto/demo/basic-site/vendor/.manifests/hypertext.jdn...
Installed as 'hypertext'.
copying src/hypertext.janet to /home/zax/code-src/bagatto/demo/basic-site/vendor...
code-src/bagatto/demo/basic-site [new-demos !] ⊕ JANET_PATH=vendor janet
Janet 1.12.2-5df8ac5 linux/x64 - '(doc)' for help
repl:1:> (import hypertext)
repl:2:> (hypertext/markup p)

By prefacing the calls to jpm and janet with JANET_PATH=vendor, we can specify a dependency context that’s local to our project.

Packages with Bagatto

Bagatto is designed to behave like a normal Janet instance whenever possible. Therefore, we should expect the same behaviour to work with the bag executable.

code-src/bagatto/demo/basic-site [new-demos !] ⊕ JANET_PATH=vendor/ bag index.janet
Reading config data spec...
Reading pages data spec...
Reading css data spec...
Beginning 3 jobs...
Loaded config
Loading pages...
[pages] Loading 2 files
Loading css (styles.css)...
Finished jobs.
Reading pages site spec...
Reading index site spec...
Reading css site spec...
Beginning 3 jobs...
Rendering pages...
Rendering index...
Generating path for css...
Finished jobs.
Starting worker pool with 4 workers...
[WRITE] site/index.html
[COPY] site/css/styles.css
[WRITE] site/pages/bagatto.html
[WRITE] site/pages/about.html
Terminated worker pool.

I shall leave it as an exercise to the reader to verify that the output is the same as in the demo site.


In short, you should be able to use packages with jpm inside of Bagatto in exactly the same fashion as you do outside of it.

Bagatto is designed so that you don’t have to call janet or jpm when using it; however, you should be able to rely on all of your expectations and assumptions about the operation of Janet when managing your Bagatto source documents as well as leveraging external Janet code to build them.

  1. I should establish for the record that I’m not particularly experienced with Janet Hypertext and there is probably a more elegant way to write the above. Hopefully it gives you a taste of what the library does; the purpose of this guide is to demonstrate how to use any library you might like.