Dhall support in depth
By @lucasdicioccio, 811 words, 8 code snippets, 5 links, 0images.
this page uses a default layout
The Dhall support in KitchenSink provides a template engine unlike many static-site generators. In typical static-site generators, the template language serves two purposes: dictating the structure of the HTML files, and generating repetitive content of HTML files like for data tables. In KitchenSink, Layouts dictate the HTML structure and are hard to change. Dhall provides a pre-processor to apply some template on repetititve contents to render Sections.
Dhall as a pre-processor
Why Dhall rather than other languages? The short answer is that Dhall hits a sweet spot for pre-processing sections in static-blog generators. Dhall is at a same time: deterministic, easy to embed in Kitchen-Sink, and expressive. Let’s develop these points.
First, Dhall is a deterministic language. A major benefit here is that Dhall is
simple on a conceptual level: you can substitute a Dhall expression with its
result and it will work as expected. Further, being deterministic, Dhall
disallows user-defined side-effects. Albeit a constraint for people writing
Dhall code, such determinism comes handy to write a pre-processor: a
pre-processor turns Sections
into Sections
, since the pre-processor does
not do more, for instance, by having no extra side-effects to track. We do not
freeze KitchenSink development into a bad-architecture as the Section -> Section
function can be moved without surprises.
Second, Dhall is embeddable in Haskell, the programming language in which KitchenSink is written. The immediate benefit is that no external setup is required: KitchenSink embeds the same Dhall interpreter as the official Dhall binary and that’s it. Compare this simplicity (no setup required) to generator sections, whose result vary depending on which tools are available on each setups.
Third, Dhall is expressive enough to have libraries of functions that you
import over the network or on the file-system. Such imports open the door to
avoiding redundant code (e.g., to re-use the same base:social
content in
every article). Networked imports open the door to community-based sharing.
Summarizing, the choice of Dhall is not that surprising after analyzis. Dhall fits the problem of pre-processing article sections pretty well, without costing much to implement and without freezing the design of KitchenSink engine.
Examples
Let’s now give some examples.
Rendering a section in CommonMark
example 1
=base:main-content.dhalL
{ contents = ["__generated from dhall__", "\n", kitchensink.file ], format = "cmark" }
example 2
=base:main-content.dhall
{ contents = [
"::: output"
, "\n"
, "__this code block is defined in some Dhall__"
, "\n"
, "file=", kitchensink.file
, "\n", "section="
, Integer/show kitchensink.sectionNum
, "\n"
, ":::"
]
, format = "cmark"
}
rendered in HTML (via CommonMark)
this code block is defined in some Dhall
file= website-src/sections-dhall.cmark
section= +9
some number from a dataset: 42
some list from a dataset:
- foo
- bar
- baz
Rendering a section in HTML
You can return HTML directly-formatted.
example
=base:main-content.dhall
{ contents = ["__generated from dhall__", "\n", kitchensink.file ], format = "html" }
Rendering JSON-sections
Dhall can substitute itself to JSON objects as well.
Jut replace a .json
with .dhall
and KitchenSink will interpret a Dhall expression to fill-in the JSON.
A few use cases are envisioned:
- generating a commands from Dhall expressions
- generating a command requiring the filename
- avoiding redundant information like social infos using Dhall’s support for remote includes
example 1
=generator:cmd.dhall
let
ping = { cmd = "ping"
, args = ["-c", "3", "8.8.4.4"]
, target = "latency"
}
in
{ contents = ping
, format = "json"
}
example 2
This section is handy to show the content in this file. Note that I use
kitchensink.file
to get the path name.
=generator:cmd.dhall
let
cmd = { cmd = "cat"
, args = [kitchensink.file]
, target = "cat-this-file"
}
in
{ contents = cmd
, format = "json"
}
example 3
You could write the content in ./social.dhall
once and import the content.
=base:social.dhall
{ contents = ./social.dhall
, format = "json"
}
example 4
You can also generate JSON datasets if for some reason writing them in JSON is not immediate enough .
=base:dataset.dhall alice-bob
{ contents =
[ {name = "Alice", posts = 22.0}
, {name = "Bob", posts = 7.0}
]
, format = "json"
}
Implementation notes
The following notes are more useful for myself and for people curious about modifying or contributing to KitchenSink.
present
The evaluation chain
Today SiteLoader traverses all sections (i.e., after mapping Article Sections
to Article Sections). Upon encountering a .dhall
section, SiteLoader
evaluates the content as a Dhall expression returning some well-known format.
Further, datasets encountered during this phase are collected in a datasets
object. Previous datasets are passed to Dhall sections. Thus Dhall can
templatize an in-line dataset.
Thus, unlike generators
, Dhall sections are pre-processors. They are
evaluated once. When operating Kitchen-Sink as a static-site generator there
is no much difference, however if you operate Kitchen-Sink as a live-website,
then you cannot use Dhall as a generator-on-steroids.
The kitchensink
object
Evaluated Dhall expressions carry information provided by
KitchenSink. This information is bound to the kitchensink
Dhall value and has the following structure:
{ file : Text -- contains the source file path for the cmark
, sectionNum : Integer -- contains the section number of this file (starting from zero)
, datasets : <data-dependant> -- contains a record<dataset-name, json-value-to-dhall> or an error message if it failed to load
}
future work
- carry-over more context from previous sections
- allow appending new sections (i.e., preprocess articles over sections)?