Bagatto

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> 
<div>
  <nav>
    <ul class="list pl0 ma0">
      {% (each page (args :pages)
            (printf ```
                    <li class="dib mr2">
                    <a class="pv2 ph3 fw6 nav focus-black"
                     href="%s">%s</a>
                    </li>
                    ```
                    (page-path args page)
                    (page :basename))) %}
    </ul>
  </nav>
</div>
    
{% (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> 
<div>
  <nav>
    <ul class="list pl0 ma0">

{% (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)]))))) %}

     </ul>
  </nav>
</div>

{% (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:
    /usr/lib/janet/hypertext.temple
    /usr/lib/janet/hypertext.jimage
    /usr/lib/janet/hypertext.janet
    /usr/lib/janet/hypertext/init.janet
    /usr/lib/janet/hypertext.so
    hypertext.temple
    hypertext.jimage
    hypertext.janet
    hypertext/init.janet
    hypertext.so.
       
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 https://gitlab.com/louis.jackman/janet-hypertext.git
[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.
From https://gitlab.com/louis.jackman/janet-hypertext
 * branch            master     -> FETCH_HEAD
   ca43fef..23d5697  master     -> origin/master
Updating ca43fef..23d5697
Fast-forward
 README.md | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)
removing /usr/lib/janet/hypertext.janet
removing manifest /usr/lib/janet/.manifests/hypertext.jdn
Uninstalled.
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)
nil
repl:2:> (hypertext/markup p)
"<p></p>"
repl:4:> 

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 https://gitlab.com/louis.jackman/janet-hypertext.git
cloning repository https://gitlab.com/louis.jackman/janet-hypertext.git 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)
nil
repl:2:> (hypertext/markup p)
"<p></p>"
repl:3:> 

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.

Conclusion

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.  ↩︎