~subsetpark/bagatto

84175f7b304a6b8e839ba60daf54f78b06424a3d — Zach Smith 4 months ago f5c14aa
Update manual
4 files changed, 1090 insertions(+), 529 deletions(-)

A MANUAL.md
M README.md
M bagatto.janet
M src/core.janet
A MANUAL.md => MANUAL.md +1086 -0
@@ 0,0 1,1086 @@
# The Bagatto Manual

Z. D. Smith, Brooklyn, NY, 2020.

# Usage

Bagatto operates in two phases: the **data** phase and the **site** phase.

In the data phase, Bagatto will read a data specification and use it
to create a table containing all the input data. Data specifications
that consist of literal attributes will result in site data containing
those same attributes. Specifications of a single file path will
result in site data pertaining to the one file. Specifications of a
file wildcard will result in an array of data objects, one for each
file that matches the wildcard.

In the site phase, Bagatto will read the site specification and use it
to generate a sequence of files. Each site specification will
ultimately specify the **path** and **contents** of one or more
files. Bagatto will then ensure the presence of each file path and
ensure that its contents are as specified.

## The Bagatto API

The `bag` command accepts a single filename as an argument. This is
known as the **index module**, and it should be syntactically correct
Janet. One of the principles of Bagatto is to go as far as is
practicable to make the operation of the Janet language inside the
index module as similar as possible to any other use of the Janet
interpreter or compiler. 

Thus, there's only one real difference between programming inside an
index module and writing a normal Janet module: Bagatto inserts a
couple useful libraries into the namespace so that we, as the site
authors, don't need to manage these libraries in order to use them
inside the module.

They are:

- The [`bagatto`](#api) library itself, a collection of useful functions
  designed to reduce boilerplate in Bagatto modules, whose API is
  listed below;
- The [`path`](https://github.com/janet-lang/path) library, which
  exposes functions for manipulating file paths;
- The [`janet-sh`](https://github.com/andrewchambers/janet-sh)
  library, which exposes a useful DSL for shelling out to the command
  line.

In addition to the helper functions exposed in the `bagatto/`
namespace, there is one more feature that can be accessed directly
inside of index modules:

### Defaults Handling

Bagatto exposes the `bagattto/set-defaults!` function, which can be
called at any point inside an index module. It takes a single
argument: a struct or dictionary specifying the default value for any
of the specification attributes: `:src`, `:attrs`, `:path`,
`:contents`.

## Data

Your Bagatto module should expose a **data specification** like this:

```clj
(def data ... )
```

This value will be used as the starting point by the Bagatto
application. Its job is to specify all the inputs that should go into
the system.

The `data` value should be a struct where the keys are **data specification names**
and the values are the specifications themselves. The data
specification names are meaningful, as they are referred to by the
site specifications, as we'll see.

### Data Literals

The simplest form of a data specification is a literal struct or
table, like this:

```clj
(def data {:config {:attrs {:title "A Demo Bagatto Config"}}})
```

When Bagatto creates the *site data* for this specification, it will
consist of a single key-value pair:

```clj
repl:13:> (bagatto/eval-data data)
@{:config {:title "A Demo Bagatto Config"}}
```

### File References

The next type of data specification is a reference to a single file in
the project. These will consist of two attributes, `:src`, which
specifies the location of the file with respect to the current working
directory, and `:attrs`, which contains a function that will be called
with the file contents, like this:

```clj
(def data {:config-json {:src "config.json"
                         :attrs bagatto/parse-json}}
```

(Theoretically, you could pass in a data literal as above in this case
too, but in that case the file would be ignored and there wouldn't be
much point.)

In this case, Bagatto will look for a file called `config.json` in the
current directory, load its contents, and then call
`bagatto/parse-json` on them. The resulting attributes will then be
the content of the site data associated with `:config-json`.

```clj
repl:17:> (bagatto/eval-data data)
@{:config-json @{"subtitle" "A Very Good Blog." 
                 :path "config.json" 
                 :src @"{\"subtitle\":\"A Very Good Blog.\"}\n"}}
```

We see that the resulting site data has a single entry,
`:config-json`. The table associated with this entry has the two
attributes we get for free---`:path` and `:src`, which are the file
path and contents, respectively---but that the call to `parse-json`
has resulted in the key/value pairs inside the JSON file have been
parsed and put in the site data too.

### File Wildcards

The last way to specify data inputs is with wildcard references to
multiple (potential) files. Under the hood, this relies on the `glob`
function of [janet-sh][sh]. There are two wildcard methods:
`bagatto/*` and `bagatto/slurp-*`.

[sh]: https://github.com/andrewchambers/janet-sh

#### List matching files

Use `bagatto/*` to provide a all the filenames that match the wildcard.

```clj
repl:24:> (bagatto/eval-loader (bagatto/* "demo/static/*"))
@["demo/static/hello.png"]
```

Thus, we can use it as a data specification:

```clj
(def data {:static {:src (bagatto/* "demo/static/*")
                    :attrs bagatto/parse-base}})
```
```clj
repl:27:> (bagatto/eval-data data)
@{:static @[@{:path "demo/static/hello.png"}]}
```

Since we specified the `parse-base` parser, and used the basic form
`bagatto/*` (which only lists files), we get an array of tables with
the `:path` attribute only.

This is the minimal case for listing files, but for files like the
above, that only need to be copied into place, it's all we need.

#### Slurp matching files

`bagatto/slurp-*` has the same wildcard functionality, but it also
includes the contents of the matching files. We can use this to
process files in more interesting ways.

```clj
repl:28:> (bagatto/eval-loader (bagatto/slurp-* "demo/posts/*.md"))
@[("demo/posts/post.md" @"## A Post That You Might Be Interested In.\n\nI'm writing this in *Markdown*. Therefore, in my page template, I'll\ncall `bagatto/markdown->html` in order to render this markdown into\nHTML.\n") ("demo/posts/post2.md" @"title: A Really Good Title\nstatus: post\n---\n\n## The Other Post on My Blog.\n\nThis post is very similar to the other post. \n\nHowever, it contains footnotes[^1].\n\n[^1]: Bagatto has built-in support for Markdown via the\n    [moondown](https://github.com/joy-framework/moondown)\n    library. However, moondown doesn't have support for footnotes, so\n    we should use multimarkdown to render this instead. Bagatto has\n    the `mmarkdown->html` (notice the extra `m`!) function which\n    shells out to the multimarkdown application. So if you install\n    that, you can take advantage of more features.\n\n    Of course, if you want to use some other markup or text processing\n    application, you can write your own function, either in your index\n    or a helper module, and use that instead.\n")]
```

The return value of the loader for each matching file is a two-tuple
of the file's path and contents. Notice that the contents of the
markdown files include their own metadata in the form of YAML
frontmatter.

We can define a data specification based off of this loader. In this
case we'll specify the multimarkdown parser as the `attrs`
callback. That will be able to extract the YAML frontmatter as
additional metadata.

```clj
(def data {:posts {:src (bagatto/slurp-* "demo/posts/*.md")
                   :attrs parse-mmarkdown}})
```

```clj
repl:33:> (bagatto/eval-data data)
@{:posts @[@{:path "demo/posts/post.md" :src @"..."} 
           @{"status" "post" 
             :path "demo/posts/post2.md" 
             :src @"..." 
             "title" "A Really Good Title"}]}
```

Having evaluated the data specification, we can see that `:posts` is
an array with one element for each file that matched the
wildcard. Unlike with the single-file example above, `parse-mmarkdown`
was then called for *each* post. I've collapsed the full contents so
we can see that, since `post2.md` included a `title:` and `status:`
attribute in its metadata, the `parse-mmarkdown` function has pulled
that out and put it in the attributes for that post. `post.md` didn't
have any frontmatter, so it has no additional attributes.

#### Transforms

Since the wildcard loaders offer the ability to load multiple files,
and the `attrs` callback operates on each file individually, Bagatto
exposes one more element of the data specification: the `transform`
callback. A transform, if specified, is called on the whole set of
elements after each one has been parsed. This allows us to, for
instance, sort a list of blog posts after they've been loaded and
parsed.

```clj
(def data {:notes {:src (bagatto/slurp-* "notes/*.md")
                   :attrs parse-note
                   :transform (bagatto/attr-sorter "topic")}})
```

[`bagatto/attr-sorter`](#bagattoattr-sorter) is exposed as a part of
the Bagatto library and allows us to specify a key present in all the
items, and sort the collection by it.

## Site

The second and last value that your index module should define is `site`:

```clj
(def site ...)
```

This is the **site specification**, which defines all the outputs of
the system. Every site specification entry specifies either a single
file, or a sequence of files, to be generated. To specify a file we
must output the **path** it should be created at and the **contents**
of the created file.

The structure of the site specification is quite similar to the data
specification: it's an association between names and specification
values. However, in this case the names don't have any effect on the
generated site; they're just useful for the site author to organize
their code.

The relationship between `data` and `site` is simple but important to
understand. The site specification is evaluated in the *context* of
the site data, which is the output of the data specification (it's
what we see when we run `bagatto/eval-data` above). 

A site specification isn't actually a mapping data entries to pages;
in most websites of any size, any given page will require data from
more than one input (for instance, to display a *recent posts* sidebar
on every page), and may well create more than one page out of the same
input. Thus it's useful to understand the overall flow of data in the
system: Bagatto uses the data specification to create the site data,
and then iterates through the site specification using the site data
as the context, or evaluation evaluation environment, as it evaluates
each entry in the specification. Each entry results in a sequence of
one or more files to be written.

### Path and Contents Literals

As noted above, every site specification specifies the path and
contents for one or more files to be created. Therefore, perhaps the
simplest possible site is one consisting of a static path and
contents:

```clj
repl:2:> (def site {:_ {:path "out.txt" :contents "Welcome to my website"}})
{:_ {:path "out.txt" :contents "Welcome to my website"}}
```

We can use `bagatto/eval-site` to get an output of the path and
contents of each file to be created. This is useful for debugging; in
reality, site contents are generated using fibers, so this lets us
peek under the hood to understand what our site specifications produce.

Site specifications are evaluated in the context of site data, but in
this case our only specification is completely static. therefore we
can pass in an empty struct as the site data.

```cli
repl:4:> (bagatto/eval-site site {})
@[(:write "out.txt" "Welcome to my website")]
```

We can see that it plans to write a single file, with the specified
path and contents.

### Renderer functions

More useful is to pass a function as the specification contents,
rather than a static value. This allows us to dynamically act on the
input data in useful ways.

A renderer function is simply any function which takes in the site
data and outputs some file contents. We can write our own extremely
simple one, which looks for a secret in the site data and outputs it
in JDN format to a file. Then we can define a simple site
specification with a static path that passes that function in directly
as the `:contents` attribute.

```clj
repl:6:> (defn renderer [data] (string/format "%j" (data :secret)))
<function renderer>
repl:7:> (def site {:_ {:path "out.txt" :contents renderer}})
{:_ {:path "out.txt" :contents <function renderer>}}
```

Now, of course, we need to ensure that `:secret` is present in the
site data. While, in practice, we'd have a data entry that defined
`:secret`, it's useful to note that for the purposes of inspecting our
functions, we don't need to use the output of the `eval-data`
command. We can construct a struct directly.

```clj
repl:8:> (bagatto/eval-site site {:secret "p@ssw0rd"})
@[(:write "out.txt" "\"p@ssw0rd\"")]
```

The fact that the site data is a simple key-value structure, and the
renderer output is just a string, makes it very simple to understand
how data flows through the application and to extend it.

Perhaps a slightly more realistic example would be one that combines
data from more than one source.

```clj
repl:12:> (defn renderer [data] string/format "%s:%j:%f" 
                                              (get-in data [:config :prefix]) 
                                              (get-in data [:personal :password]) 
                                              (math/random)))
<function renderer>
repl:14:> (def site {:_ {:path "out.txt" :contents renderer}})
{:_ {:path "out.txt" :contents <function renderer>}}
repl:15:> (bagatto/eval-site site {:personal {:password "p@ssw0rd"} 
                                   :config {:prefix "md5"}})
@[(:write "out.txt" "md5:\"p@ssw0rd\":0.487181")]
``` 

Here we see the very common case of combining data from multiple
sources into a single file's contents.

### Site Selectors

We saw that, using data specifications, we can select *source files*
to be included in our site data with wildcards. A very common example
of this would be building a blog; in addition to all the static
content and any config files, we'd want to load up all the blog posts
in a directory, and to be able to add a new post simple by adding a
file to the directory, without changing the config.

Thus, it will be very common that in addition to rendering pages based
on static config data or files, we'll want to iterate through all the
files that match a wildcard and render one output file for each (eg.,
rendering a `post.html` for each source file).

For that we can use **site selectors**.

#### `:each`

Given some site data with a series of named data entries, we can use
the `each` attribute to refer to one of those entries. Bagatto will
then call the `path` and `contents` functions on each file in the
entry.

Because there's now an additional piece of data in addition to the
site data, the renderer and path generator functions in an `each`
specification take two arguments: the site data `data`, and then the
individual element `item`.

We can see an example. First let's define a data specification with a
wildcard, so we have something to iterate over:

```clj
repl:55:> (def data {:users {:src (bagatto/slurp-* "users/*") 
                             :attrs bagatto/parse-json} 
                     :config {:attrs {:prefix "pw::"}}})
{:config {:attrs {:prefix "pw::"}} :users {:attrs <function parse-json> :src <fiber 0x55F89DCCE930>}}
```

The `users` directory will have two JSON files in it. Therefore, since
we specify `bagatto/parse-json` as the parser for `users`, we can
expect the `users` site data to contain an array of 2 tables that have
been decoded from the JSON.

Next we'll define a renderer function. Like above, it will draw from
multiple sources; but this time, it will take two arguments, because
we intend it to be called on each element in `users`.

```clj
repl:34:> (defn renderer [data item] (string/format "%s%s" 
                                      (get-in data [:config :prefix]) 
                                      (item "password")))
```

As before we expect `data`, but now we expect `item` as well. For each
call `data` will be the same site data, and item will be a different
element.

Finally we will define a site specification that uses `:each` to refer
to the `users` site data.

```clj
repl:35:> (def site {:_ {:each :users 
                         :path (bagatto/path-copier "passwords/") 
                         :contents renderer}})
{:_ {:path <function 0x55F89DCBC880> :contents <function renderer> :each :users}}
```

`:each :users` will cause Bagatto to call the renderer once for each
item in `:users`. In addition, we now need to specify an actual
function for `:path`. If we left it as a static value, the contents
would be repeatedly written to the same file, which is obviously not
what we want. Here we use the
[`bagatto/path-copier`](#bagattopath-copier) helper, which gives us a
function that will accept any file and return a new path with the base
we specify.

We can evaluate the data spec, and use that to evaluate the site spec:

```clj
repl:56:> (bagatto/eval-site site (bagatto/eval-data data))
@[(:write "passwords/alice.json" "pw::1234") 
  (:write "passwords/bob.json" "pw::snoopy")]
```

It's produced two write plans, one for each user file, whose contents
are interpolated from the contents of their respective source files.

#### Copying Files

A very common operation when generating a website is to copy a source
file without touching it. If Bagatto receives a site specification
with a site selector and a `:path` entry, but no `:contents` entry, it
will interpret that as a **copy** operation. It will read the `:path`
of whatever item or item it receives (this attribute is always
present), and copy it to the `:path` attribute of the site
specification.

Here's a super simple data spec:

```clj
repl:62:> (def data {:users {:src (bagatto/* "users/*") :attrs bagatto/parse-base}})
{:users {:attrs <function parse-base> :src <fiber 0x55F89DD25A00>}}
```

We use `bagatto/*` instead of `bagatto/slurp-*`, which just lists the
files, but doesn't read them. We also use `bagatto/parse-base` as our
parser, which just returns the base `:path` attribute.

We can now define a `site` that simply refers to `:users` and
specifies a path without specifying contents.

```clj
repl:58:> (def site {:_ {:each :users :path (bagatto/path-copier "passwords/")}})
{:_ {:path <function 0x55F89DCD8B30> :each :users}}
```

Evaluating the site produces two copy instructions to the new paths:

```clj
repl:63:> (bagatto/eval-site site (bagatto/eval-data data))
@[(:copy "users/alice.json" "passwords/alice.json") 
  (:copy "users/bob.json" "passwords/bob.json")]
```

## Templates

Of course, most websites are not made by `string/format`ing HTML
together; they use HTML templates. The template system used by Bagatto
is [Temple](https://git.sr.ht/~bakpakin/temple). Temple is a
wonderfully powerful and simple templating system that should be very
enjoyable to use.

### Temple Basics

Here's the contents of `post.temple` in the Bagatto demo directory:

```html
{$ (import ./base_top) $}
{% (base_top/render-dict args) %}
      
      <h1>{{ (get-in args [:_item :title]) }}</h1>
      <p class="post-info">
        {{ (get-in args [:_item :date]) }}
      </p>
      {- (bagatto/mmarkdown->html (get-in args [:_item :src])) -}

{$ (import ./base_bottom :as base_bottom) $}
{% (base_bottom/render-dict args) %}
```

The appeal of Temple is in its simplicity. It consists of four types
of expression, all of which are seen here.

- `{$ ... $}`: Evaluate the expression between the $s at compile time;
- `{% ... %}`: Evaluate the expression between the %s at runtime,
  escape and interpolate the output;
- `{- ... -}`: Evaluate the expression between the -s at runtime,
  interpolate the output without escaping it.
- `{{ ... }}`: Evaluate and interpolate the expression inside the curly braces.

While many other templating languages differentiate between capturing
and non-capturing by differentiating between their escape brace types
(which means having to change brace types from line to line, even
within the same syntactic expression), Temple is non-capturing by
default, and we interpolate into the surrounding template by printing to
stdout. In other words, to interpolate something into a Temple
template, simply use `print`:

```html
Welcome to my web page. Here's a pretty-printed example 
of one of my favorite data structures:

{% (print (string/format "%q" {:name "Bowler Cat" 
                               :species "Felis Domesticus"})) %}

Ain't she a beaut?
```

We can think of `{{ foo }}` as syntactic sugar for `{% (print foo) %}`.

Temple templates accept a single dictionary of arguments, which is
bound inside the template to `args`.

### Using Temple in Bagatto

Bagatto adds a very thin layer of functionality and convenience on top
of Temple. The first thing it does is it extends the Temple
environment with the same libraries that are listed at the beginning
of this manual. Thus we can call `bagatto/` helper functions from
within a template.

The only other change it makes is to ensure the presence, if
applicable of the `item` passed in as the second argument to site spec
functions, which contains the attributes of the individual element of
an `:each` selection. Those attributes are made available at `(args
:_item)`. For instance, in the example above, we expect the attributes
of the specific blog post being rendered to be present in the `:_item`
value, and so we refer to it to get the title, date and contents of
the post.

### Rendering a Template

The basic call to render a template is
[`bagatto/render`](#bagattorender). This allows us to directly invoke
a template by name, with site data and an optional item, and returns
the fully rendered template. For instance, if we have a simple
template at `templates/simple.temple`:

```
I am known for my {{ (args "topic") }} skills.
```

Then we can render out page contents like so:

```
repl:5:> (bagatto/render "templates/simple" {"topic" "Web Design"})
@"I am known for my Web Design skills.\n"
```

In a proper web page, of course, our template file would contain HTML
with placeholders for the values to be interpolated.

### Renderer Generators

Because `bagatto/render` is such a common operation, Bagatto offers a
convenience function that will generate a **renderer** that will make
the above call. For instance, if I wanted to specify the above
template in a site specification, I'd probably write this:

```clj
repl:6:> (def site {:_ {:path "out.txt" 
                        :contents (bagatto/renderer "templates/simple")}})
{:_ {:path "out.txt" :contents <function 0x55DAC976C660>}}
```

Thus I avoid having to write a new renderer function for each
`:contents` entry, if I'm just going to pass on the data to a specific
template. Evaluating the site we get the same thing:

```clj
repl:7:> (bagatto/eval-site site {"topic" "Web Design"})
@[(:write "out.txt" @"I am known for my Web Design skills.\n")]
```

# bagatto API

[bagatto/%p](#bagatto%p)
, [bagatto/*](#bagatto*)
, [bagatto/attr-sorter](#bagattoattr-sorter)
, [bagatto/datestr-&gt;date](#bagattodatestr-&gt;date)
, [bagatto/datestr-&gt;secs](#bagattodatestr-&gt;secs)
, [bagatto/epp](#bagattoepp)
, [bagatto/eval-data](#bagattoeval-data)
, [bagatto/eval-loader](#bagattoeval-loader)
, [bagatto/eval-site](#bagattoeval-site)
, [bagatto/format](#bagattoformat)
, [bagatto/get-default](#bagattoget-default)
, [bagatto/item-getter](#bagattoitem-getter)
, [bagatto/jdn-data](#bagattojdn-data)
, [bagatto/json-data](#bagattojson-data)
, [bagatto/markdown-&gt;html](#bagattomarkdown-&gt;html)
, [bagatto/mmarkdown-&gt;html](#bagattommarkdown-&gt;html)
, [bagatto/mmarkdown-data](#bagattommarkdown-data)
, [bagatto/parse-base](#bagattoparse-base)
, [bagatto/parse-jdn](#bagattoparse-jdn)
, [bagatto/parse-json](#bagattoparse-json)
, [bagatto/parse-mmarkdown](#bagattoparse-mmarkdown)
, [bagatto/path-copier](#bagattopath-copier)
, [bagatto/render](#bagattorender)
, [bagatto/renderer](#bagattorenderer)
, [bagatto/set-defaults!](#bagattoset-defaults)
, [bagatto/site-getter](#bagattosite-getter)
, [bagatto/slug-from-attr](#bagattoslug-from-attr)
, [bagatto/slugify](#bagattoslugify)
, [bagatto/slurp-*](#bagattoslurp-*)
, [bagatto/write-site](#bagattowrite-site)

## bagatto/%p

**function**  | [source][1]

```janet
(%p & elements)
```

Simple DSL for generating paths. 

Given any number of string or keyword arguments, will return a
function that takes an item and returns a file path.

The arguments make up the components of the path. To include an
element directly in the path, put it as an argument. To indicate
that an element should be treated as a key in `site`, precede it by
a `'%s`. To indicate that an element should be treated as a key in
`item`, precede it by a `'%i`. To join two elements directly,
without a path separator, put a `'%` between them. For instance,

```clj
repl:9:> (def f (bagatto/%p "site" '%s "author" '%i :title '% ".html"))
<function 0x55F38B3B3880>
repl:10:> (f {"author" "Z. D. Smith"} {:title "My first post"})
"site/z-d-smith/my-first-post.html"
repl:11:> (f {"author" "Z. D. Smith"} {:title "My second post"})
"site/z-d-smith/my-second-post.html"
```

[1]: /usr/lib/janet/bagatto.janet#L244

## bagatto/*

**function**  | [source][2]

```janet
(* pattern)
```

Generate a fiber that will return all the filenames that match a given
file blob.

[2]: /usr/lib/janet/bagatto.janet#L104

## bagatto/attr-sorter

**function**  | [source][3]

```janet
(attr-sorter key &opt descending?)
```

Return a sorter function that, given a list of items, sorts
according to the items' values at the specified key.

[3]: /usr/lib/janet/bagatto.janet#L327

## bagatto/datestr-&gt;date

**function**  | [source][4]

```janet
(datestr->date datestr &opt for-display?)
```

Given a string representation of a date or datetime, return a struct
of the kind returned by `os/date`. Pass a truthy value in the second
argument to adjust for human display (setting values like day and
month to be 1-indexed).

[4]: /usr/lib/janet/bagatto.janet#L77

## bagatto/datestr-&gt;secs

**function**  | [source][5]

```janet
(datestr->secs datestr)
```

Given a string representation of a date or datetime, return the
epoch seconds value for that date.

[5]: /usr/lib/janet/bagatto.janet#L69

## bagatto/epp

**function**  | [source][6]

```janet
(epp x)
```

Pretty-print to stderr. Since Temple templates operate over stdout,
we should use stderr instead if we need to print something to the
console for debugging purposes.

[6]: /usr/lib/janet/bagatto.janet#L413

## bagatto/eval-data

**function**  | [source][7]

```janet
(eval-data data)
```

Evaluate an object according to the Bagatto *site data specification*.

Not necessary to define a module, but can be useful to debug your
configuration from within the REPL.

[7]: /usr/lib/janet/bagatto.janet#L340

## bagatto/eval-loader

**function**  | [source][8]

```janet
(eval-loader fiber)
```

Evaluate a loader fiber to view the list of filenames, or filename
and contents, it produces.

Not necessary to define a module, but can be useful to debug your
configuration from within the REPL.

[8]: /usr/lib/janet/bagatto.janet#L350

## bagatto/eval-site

**function**  | [source][9]

```janet
(eval-site site data)
```

Evaluate an object according to the Bagatto *site generation specification*,
given a site data object as context.

Not necessary to define a module, but can be useful to debug your
configuration from within the REPL.

[9]: /usr/lib/janet/bagatto.janet#L361

## bagatto/format

**function**  | [source][10]

```janet
(format templ & xs)
```

A simple wrapper around `string/format` to ease development. If one
of `xs` is nil, it will output an empty string rather than crashing
the template.

[10]: /usr/lib/janet/bagatto.janet#L403

## bagatto/get-default

**function**  | [source][11]

```janet
(get-default attribute)
```

Get the value set in the bagatto defaults for the specified key. Can
be used for a simple form of inheritence; if you have a default
callback or value set, and you have some specific handler that needs
to build on top of that functionality, you can call
`bagatto/get-default` to get or evaluate it inside of the handler
and then implement your more specific logic.

(see `bagatto/set-defaults!` for more information.)

[11]: /usr/lib/janet/bagatto.janet#L26

## bagatto/item-getter

**function**  | [source][12]

```janet
(item-getter path)
```

Return a function that, given site and item data, gets the value at
the given path in the item.

The path should be a tuple of keys, eg.: `[:user :full-name]`. 

[12]: /usr/lib/janet/bagatto.janet#L234

## bagatto/jdn-data

**function**  | [source][13]

```janet
(jdn-data src)
```

Get metadata from a JDN-formatted string.

[13]: /usr/lib/janet/bagatto.janet#L49

## bagatto/json-data

**function**  | [source][14]

```janet
(json-data src)
```

Get metadata from a JSON-formatted string.

[14]: /usr/lib/janet/bagatto.janet#L54

## bagatto/markdown-&gt;html

**function**  | [source][15]

```janet
(markdown->html md)
```

Render a markdown string into HTML.

[15]: /usr/lib/janet/bagatto.janet#L389

## bagatto/mmarkdown-&gt;html

**function**  | [source][16]

```janet
(mmarkdown->html md)
```

Render a markdown string using multimarkdown.

(requires the presence of the multimarkdown executable.)

[16]: /usr/lib/janet/bagatto.janet#L394

## bagatto/mmarkdown-data

**function**  | [source][17]

```janet
(mmarkdown-data md)
```

Get metadata from a markdown string using multimarkdown.

[17]: /usr/lib/janet/bagatto.janet#L44

## bagatto/parse-base

**function**  | [source][18]

```janet
(parse-base _src attrs)
```

Base attribute parser: bypasses the input source and returns the
default attributes that are provided for every file. These are:
- `:path`: the file path
- `:src`: (if the file was read) the contents of the file 

[18]: /usr/lib/janet/bagatto.janet#L122

## bagatto/parse-jdn

**function**  | [source][19]

```janet
(parse-jdn src attrs)
```

Attribute parser for JDN.

[19]: /usr/lib/janet/bagatto.janet#L141

## bagatto/parse-json

**function**  | [source][20]

```janet
(parse-json src attrs)
```

Attribute parser for JSON.

[20]: /usr/lib/janet/bagatto.janet#L146

## bagatto/parse-mmarkdown

**function**  | [source][21]

```janet
(parse-mmarkdown src attrs)
```

Attribute parser for multimarkdown.

(requires the presence of the multimarkdown executable.)

[21]: /usr/lib/janet/bagatto.janet#L132

## bagatto/path-copier

**function**  | [source][22]

```janet
(path-copier base &opt key)
```

Return a function that will generate a new path with the same
base, from the same key, for any item.

eg.:

```clj
repl:2:> (def f (bagatto/path-copier "destination"))
<function 0x55F38B3AE640>
repl:3:> (f {} {:path "original/source/directory/filename.md"})
"destination/filename.md"
repl:4:> (f {} {:path "original/source/directory/newfile.png"})
"destination/newfile.png"
```

[22]: /usr/lib/janet/bagatto.janet#L306

## bagatto/render

**function**  | [source][23]

```janet
(render template site &opt item)
```

 
Given site data and an optional item, render the specified Temple template.

All the attributes will be available in the template under `args`.

If a single item was passed in in addition to the site, that item
will be available at `(args :_item)`.

In addition to the standard Janet library, the same additional
libraries that Bagatto makes available when writing an index module
are also available (without explicitly importing them) from within a
template.

See the [Temple](https://git.sr.ht/~bakpakin/temple) site for more details.

[23]: /usr/lib/janet/bagatto.janet#L155

## bagatto/renderer

**function**  | [source][24]

```janet
(renderer template-path &opt extra-attrs)
```

Return a function, given a template and optional extra attributes,
that will call `render` with that template on any input.

(see `bagatto/render` for more details.)

[24]: /usr/lib/janet/bagatto.janet#L210

## bagatto/set-defaults!

**function**  | [source][25]

```janet
(set-defaults! defaults)
```

Set the defaults for the values specified in data and site
specifications. For instance, setting the default of `:attrs` to
`bagatto/parse-json` will attempt to set the attributes for any file
by parsing it as JSON. `:attrs` could still be set on any entry in
the data spec in order to override the default.

[25]: /usr/lib/janet/bagatto.janet#L15

## bagatto/site-getter

**function**  | [source][26]

```janet
(site-getter path)
```

Return a function that, given site data or site and item data, gets
the value at the given path in the site.

The path should be a tuple of keys, eg.: `[:blog :description]`. 

[26]: /usr/lib/janet/bagatto.janet#L224

## bagatto/slug-from-attr

**function**  | [source][27]

```janet
(slug-from-attr item key)
```

Given an object and a key, get the value of that key in the object
it make the value into a slug.

(see `bagatto/slugify` for more details.)

[27]: /usr/lib/janet/bagatto.janet#L196

## bagatto/slugify

**function**  | [source][28]

```janet
(slugify s)
```

Normalize a string for use as a slug, eg., in a file path.

[28]: /usr/lib/janet/bagatto.janet#L182

## bagatto/slurp-*

**function**  | [source][29]

```janet
(slurp-* pattern)
```

Generate a fiber that will slurp all the files that match a given
file blob.

[29]: /usr/lib/janet/bagatto.janet#L111

## bagatto/write-site

**function**  | [source][30]

```janet
(write-site writer-specs)
```

Given the output of a site generation specification, trigger the
actual file generation.

Not necessary to define a module, but can be useful to debug your
configuration from within the REPL.

[30]: /usr/lib/janet/bagatto.janet#L372


M README.md => README.md +2 -527
@@ 92,531 92,6 @@ around. Beware: it's pretty ugly. Both `index.janet` and the template
files it references are very thoroughly commented; hopefully they can
provide a whirlwind introduction.

# Usage

Bagatto operates in two phases: the **data** phase and the **site** phase.

In the data phase, Bagatto will read a data specification and use it
to create a table containing all the input data. Data specifications
that consist of literal attributes will result in site data containing
those same attributes. Specifications of a single file path will
result in site data pertaining to the one file. Specifications of a
file wildcard will result in an array of data objects, one for each
file that matches the wildcard.

In the site phase, Bagatto will read the site specification and use it
to generate a sequence of files. Each site specification will
ultimately specify the **path** and **contents** of one or more
files. Bagatto will then ensure the presence of each file path and
ensure that its contents are as specified.

## Data

Your Bagatto module should expose a **data specification** like this:

```clj
(def data ... )
```

This value will be used as the starting point by the Bagatto
application. Its job is to specify all the inputs that should go into
the system.

The `data` value should be a struct where the keys are **data specification names**
and the values are the specifications themselves. The data
specification names are meaningful, as they are referred to by the
site specifications, as we'll see.

### Data Literals

The simplest form of a data specification is a literal struct or
table, like this:

```clj
(def data {:config {:attrs {:title "A Demo Bagatto Config"}}})
```

When Bagatto creates the *site data* for this specification, it will
consist of a single key-value pair:

```clj
repl:13:> (bagatto/eval-data data)
@{:config {:title "A Demo Bagatto Config"}}
```

### File References

The next type of data specification is a reference to a single file in
the project. These will consist of two attributes, `:src`, which
specifies the location of the file with respect to the current working
directory, and `:attrs`, which contains a function that will be called
with the file contents, like this:

```clj
(def data {:config-json {:src "config.json"
                         :attrs bagatto/parse-json}}
```

(Theoretically, you could pass in a data literal as above in this case
too, but in that case the file would be ignored and there wouldn't be
much point.)

In this case, Bagatto will look for a file called `config.json` in the
current directory, load its contents, and then call
`bagatto/parse-json` on them. The resulting attributes will then be
the content of the site data associated with `:config-json`.

```clj
repl:17:> (bagatto/eval-data data)
@{:config-json @{"subtitle" "A Very Good Blog." 
                 :path "config.json" 
                 :src @"{\"subtitle\":\"A Very Good Blog.\"}\n"}}
```

We see that the resulting site data has a single entry,
`:config-json`. The table associated with this entry has the two
attributes we get for free---`:path` and `:src`, which are the file
path and contents, respectively---but that the call to `parse-json`
has resulted in the key/value pairs inside the JSON file have been
parsed and put in the site data too.

### File Wildcards

The last way to specify data inputs is with wildcard references to
multiple (potential) files. Under the hood, this relies on the `glob`
function of [janet-sh][sh]. There are two wildcard methods:
`bagatto/*` and `bagatto/slurp-*`.

[sh]: https://github.com/andrewchambers/janet-sh

#### List matching files

Use `bagatto/*` to provide a all the filenames that match the wildcard.

```clj
repl:24:> (bagatto/eval-loader (bagatto/* "demo/static/*"))
@["demo/static/hello.png"]
```

Thus, we can use it as a data specification:

```clj
(def data {:static {:src (bagatto/* "demo/static/*")
                    :attrs bagatto/parse-base}})
```
```clj
repl:27:> (bagatto/eval-data data)
@{:static @[@{:path "demo/static/hello.png"}]}
```

Since we specified the `parse-base` parser, and used the basic form
`bagatto/*` (which only lists files), we get an array of tables with
the `:path` attribute only.

This is the minimal case for listing files, but for files like the
above, that only need to be copied into place, it's all we need.

#### Slurp matching files

`bagatto/slurp-*` has the same wildcard functionality, but it also
includes the contents of the matching files. We can use this to
process files in more interesting ways.

```clj
repl:28:> (bagatto/eval-loader (bagatto/slurp-* "demo/posts/*.md"))
@[("demo/posts/post.md" @"## A Post That You Might Be Interested In.\n\nI'm writing this in *Markdown*. Therefore, in my page template, I'll\ncall `bagatto/markdown->html` in order to render this markdown into\nHTML.\n") ("demo/posts/post2.md" @"title: A Really Good Title\nstatus: post\n---\n\n## The Other Post on My Blog.\n\nThis post is very similar to the other post. \n\nHowever, it contains footnotes[^1].\n\n[^1]: Bagatto has built-in support for Markdown via the\n    [moondown](https://github.com/joy-framework/moondown)\n    library. However, moondown doesn't have support for footnotes, so\n    we should use multimarkdown to render this instead. Bagatto has\n    the `mmarkdown->html` (notice the extra `m`!) function which\n    shells out to the multimarkdown application. So if you install\n    that, you can take advantage of more features.\n\n    Of course, if you want to use some other markup or text processing\n    application, you can write your own function, either in your index\n    or a helper module, and use that instead.\n")]
```

The return value of the loader for each matching file is a two-tuple
of the file's path and contents. Notice that the contents of the
markdown files include their own metadata in the form of YAML
frontmatter.

We can define a data specification based off of this loader. In this
case we'll specify the multimarkdown parser as the `attrs`
callback. That will be able to extract the YAML frontmatter as
additional metadata.

```clj
(def data {:posts {:src (bagatto/slurp-* "demo/posts/*.md")
                   :attrs parse-mmarkdown}})
```

```clj
repl:33:> (bagatto/eval-data data)
@{:posts @[@{:path "demo/posts/post.md" :src @"..."} 
           @{"status" "post" 
             :path "demo/posts/post2.md" 
             :src @"..." 
             "title" "A Really Good Title"}]}
```

Having evaluated the data specification, we can see that `:posts` is
an array with one element for each file that matched the
wildcard. Unlike with the single-file example above, `parse-mmarkdown`
was then called for *each* post. I've collapsed the full contents so
we can see that, since `post2.md` included a `title:` and `status:`
attribute in its metadata, the `parse-mmarkdown` function has pulled
that out and put it in the attributes for that post. `post.md` didn't
have any frontmatter, so it has no additional attributes.

#### Transforms

Since the wildcard loaders offer the ability to load multiple files,
and the `attrs` callback operates on each file individually, Bagatto
exposes one more element of the data specification: the `transform`
callback. A transform, if specified, is called on the whole set of
elements after each one has been parsed. This allows us to, for
instance, sort a list of blog posts after they've been loaded and
parsed.

```clj
(def data {:notes {:src (bagatto/slurp-* "notes/*.md")
                   :attrs parse-note
                   :transform (bagatto/attr-sorter "topic")}})
```

[`bagatto/attr-sorter`](#bagattoattr-sorter) is exposed as a part of
the Bagatto library and allows us to specify a key present in all the
items, and sort the collection by it.

## Site

## Templates

# API

[bagatto/%p](#bagatto%p)
, [bagatto/attr-sorter](#bagattoattr-sorter)
, [bagatto/epp](#bagattoepp)
, [bagatto/eval-data](#bagattoeval-data)
, [bagatto/eval-site](#bagattoeval-site)
, [bagatto/format](#bagattoformat)
, [bagatto/get-default](#bagattoget-default)
, [bagatto/item-getter](#bagattoitem-getter)
, [bagatto/jdn-data](#bagattojdn-data)
, [bagatto/parse-base](#bagattoparse-base)
, [bagatto/parse-jdn](#bagattoparse-jdn)
, [bagatto/path-copier](#bagattopath-copier)
, [bagatto/render](#bagattorender)
, [bagatto/renderer](#bagattorenderer)
, [bagatto/set-defaults!](#bagattoset-defaults)
, [bagatto/site-getter](#bagattosite-getter)
, [bagatto/slug-from-attr](#bagattoslug-from-attr)
, [bagatto/slugify](#bagattoslugify)
, [bagatto/write-site](#bagattowrite-site)

## bagatto/%p

**function**  | [source][1]

```janet
(%p & elements)
```

Simple DSL for generating paths. 

Given any number of string or keyword arguments, will return a
function that takes an item and returns a file path.

The arguments make up the components of the path. To include an
element directly in the path, put it as an argument. To indicate
that an element should be treated as a key in `site`, precede it by
a `'%s`. To indicate that an element should be treated as a key in
`item`, precede it by a `'%i`. To join two elements directly,
without a path separator, put a `'%` between them. For instance,

```clj
repl:9:> (def f (bagatto/%p "site" '%s "author" '%i :title '% ".html"))
<function 0x55F38B3B3880>
repl:10:> (f {"author" "Z. D. Smith"} {:title "My first post"})
"site/z-d-smith/my-first-post.html"
repl:11:> (f {"author" "Z. D. Smith"} {:title "My second post"})
"site/z-d-smith/my-second-post.html"
```

[1]: bagatto.janet#L244

## bagatto/attr-sorter

**function**  | [source][2]

```janet
(attr-sorter key &opt descending?)
```

Return a sorter function that, given a list of items, sorts
according to the items' values at the specified key.

[2]: bagatto.janet#L327

## bagatto/epp

**function**  | [source][3]

```janet
(epp x)
```

Pretty-print to stderr. Since Temple templates operate over stdout,
we should use stderr instead if we need to print something to the
console for debugging purposes.

[3]: bagatto.janet#L402

## bagatto/eval-data

**function**  | [source][4]

```janet
(eval-data data)
```

Evaluate an object according to the Bagatto *site data specification*.

Not necessary to define a module, but can be useful to debug your
configuration from within the REPL.

[4]: bagatto.janet#L340

## bagatto/eval-site

**function**  | [source][5]

```janet
(eval-site site data)
```

Evaluate an object according to the Bagatto *site generation specification*,
given a site data object as context.

Not necessary to define a module, but can be useful to debug your
configuration from within the REPL.

[5]: bagatto.janet#L350

## bagatto/format

**function**  | [source][6]

```janet
(format templ & xs)
```

A simple wrapper around `string/format` to ease development. If one
of `xs` is nil, it will output an empty string rather than crashing
the template.

[6]: bagatto.janet#L392

## bagatto/get-default

**function**  | [source][7]

```janet
(get-default attribute)
```

Get the value set in the bagatto defaults for the specified key. Can
be used for a simple form of inheritence; if you have a default
callback or value set, and you have some specific handler that needs
to build on top of that functionality, you can call
`bagatto/get-default` to get or evaluate it inside of the handler
and then implement your more specific logic.

(see `bagatto/set-defaults!` for more information.)

[7]: bagatto.janet#L26

## bagatto/item-getter

**function**  | [source][8]

```janet
(item-getter path)
```

Return a function that, given site and item data, gets the value at
the given path in the item.

The path should be a tuple of keys, eg.: `[:user :full-name]`. 

[8]: bagatto.janet#L234

## bagatto/jdn-data

**function**  | [source][9]

```janet
(jdn-data src)
```

Get metadata from a JDN-formatted string.

[9]: bagatto.janet#L49

## bagatto/parse-base

**function**  | [source][10]

```janet
(parse-base _src attrs)
```

Base attribute parser: bypasses the input source and returns the
default attributes that are provided for every file. These are:
- `:path`: the file path
- `:src`: (if the file was read) the contents of the file 

[10]: bagatto.janet#L122

## bagatto/parse-jdn

**function**  | [source][11]

```janet
(parse-jdn src attrs)
```

Attribute parser for JDN.

[11]: bagatto.janet#L141

## bagatto/path-copier

**function**  | [source][12]

```janet
(path-copier base &opt key)
```

Return a function that will generate a new path with the same
base, from the same key, for any item.

eg.:

```clj
repl:2:> (def f (bagatto/path-copier "destination"))
<function 0x55F38B3AE640>
repl:3:> (f {} {:path "original/source/directory/filename.md"})
"destination/filename.md"
repl:4:> (f {} {:path "original/source/directory/newfile.png"})
"destination/newfile.png"
```

[12]: bagatto.janet#L306

## bagatto/render

**function**  | [source][13]

```janet
(render template site &opt item)
```

 
Given site data and an optional item, render the specified Temple template.

All the attributes will be available in the template under `args`.

If a single item was passed in in addition to the site, that item
will be available at `(args :_item)`.

In addition to the standard Janet library, the same additional
libraries that Bagatto makes available when writing an index module
are also available (without explicitly importing them) from within a
template.

See the [Temple](https://git.sr.ht/~bakpakin/temple) site for more details.

[13]: bagatto.janet#L155

## bagatto/renderer

**function**  | [source][14]

```janet
(renderer template-path &opt extra-attrs)
```

Return a function, given a template and optional extra attributes,
that will call `render` with that template on any input.

(see `bagatto/render` for more details.)

[14]: bagatto.janet#L210

## bagatto/set-defaults!

**function**  | [source][15]

```janet
(set-defaults! defaults)
```

Set the defaults for the values specified in data and site
specifications. For instance, setting the default of `:attrs` to
`bagatto/parse-json` will attempt to set the attributes for any file
by parsing it as JSON. `:attrs` could still be set on any entry in
the data spec in order to override the default.

[15]: bagatto.janet#L15

## bagatto/site-getter

**function**  | [source][16]

```janet
(site-getter path)
```

Return a function that, given site data or site and item data, gets
the value at the given path in the site.

The path should be a tuple of keys, eg.: `[:blog :description]`. 

[16]: bagatto.janet#L224

## bagatto/slug-from-attr

**function**  | [source][17]

```janet
(slug-from-attr item key)
```

Given an object and a key, get the value of that key in the object
it make the value into a slug.

(see `bagatto/slugify` for more details.)

[17]: bagatto.janet#L196

## bagatto/slugify

**function**  | [source][18]

```janet
(slugify s)
```

Normalize a string for use as a slug, eg., in a file path.

[18]: bagatto.janet#L182

## bagatto/write-site

**function**  | [source][19]

```janet
(write-site writer-specs)
```

Given the output of a site generation specification, trigger the
actual file generation.

Not necessary to define a module, but can be useful to debug your
configuration from within the REPL.

[19]: bagatto.janet#L361
## Manual

See the [Manual](./MANUAL.md) for a deep dive.

M bagatto.janet => bagatto.janet +1 -1
@@ 98,7 98,7 @@
  ~(defn ,name
     ,docstring
     [pattern]
     (let [filenames (sh/glob pattern)]
     (let [filenames (sh/glob pattern :x)]
       (fiber/new (fn [] (loop [it :in filenames] ,body))))))

(defeach *

M src/core.janet => src/core.janet +1 -1
@@ 118,7 118,7 @@
        (if-let [path (maybe-apply path-generator [data] :d :p)
                 contents (maybe-apply renderer [data] :d :c)]
          (push-writer :write path contents))
        
        # TODO: When is this necessary? 
        {:some site-selector
         :path path-generator}
        (if-let [item (data site-selector)