site.fabricate.api
Namespace

Fabricate's public API. This contains the core set of operations that Fabricate uses to produce a website from input files.

Fabricate's API is meant to be used in 3 steps, each specified by a function and a corresponding multimethod.

  1. 1.plan!collect
  2. 2.assemblebuild
  3. 3.construct!produce!

The rest of this this document explains each of these steps.

plan!

site.fabricate.api/plan!
Function

Execute all the given setup-tasks, then collect the list of entries from each source.

This list of entries will be appended to any entries passed in as a component of the site argument.

Arguments
  1. setup-tasks
  2. {:keys [site.fabricate.api/entries
    site.fabricate.api/options]

    :or {entries []}
    :as site}
Returns
site: a map with two keys: :site.fabricate.api/entries with the list of entries, and :site.fabricate.api/options, containing site-wide options.
Source
site/fabricate/api.clj

The plan step executes a list of setup functions, and then collects the initial list of entries that will be assembled during the build step. Each implementation of the collect multimethod specifies a source location that entries will be collected from - typically a string indicating a file path - and a function that uses that location to produce a list of entries.

Each source can produce one or more entries. See below for an example of how Fabricate uses a glob pattern string to generate a sequence of entries.

collect

site.fabricate.api/collect
Multimethod

Generate the input entries from a source.

Arguments
  1. Entry map
  2. Options map
Returns
A sequence of entries, specified as maps.
Source
site/fabricate/api.clj

Example

(defmethod api/collect "pages/**.fab"
[src options]
(mapv (fn path->entry [p] ...)
(fs/glob (System/getProperty "user.dir") src))
)

The entries collected from all of the sources are flattened into a single vector at the end of the plan step and returned with the site.

assemble

site.fabricate.api/assemble
Function

Prepare the entries for produce! by calling build on each entry, then running tasks on the results.

Arguments
  1. tasks
  2. {:keys [site.fabricate.api/entries
    site.fabricate.api/options]

    :as site}
Returns
A site, specified as a map.
Source
site/fabricate/api.clj

The assemble step builds structured data for each entry generated by the plan step. Each entry gets passed to the build multimethod.

After this, each function in the sequence of tasks passed as an argument gets called on the resulting site, in order. This lets you do things like assemble additional entries - like a table of contents, index, or site map - from the contents of the entries after they get built.

build

site.fabricate.api/build
Multimethod

Generate structured (EDN) document content for an entry from a source format. Takes an entry and returns a document (entry).

Arguments
  1. Entry map
  2. Options map
Returns
An entry with the :site.fabricate.document/data key added.
Source
site/fabricate/api.clj

Example

(defmethod api/build [:clojure/v0 :hiccup]
[{:as entry
source-location :site.fabricate.source/location}
opts]

(println "generating hiccup from" (str source-location))
(clj-entry->hiccup entry))

Build dispatches on two keys for each entry:

  1. :site.fabricate.source/format
  2. :site.fabricate.document/format

Because of this, each implementation of collect should define a format for each entry it produces.

When built, the entry will have a key - :site.fabricate.document/data - containing the document after its conversion into Clojure data. Fabricate's default build methods return Hiccup from one of Fabricate's 2 built-in sources: Fabricate templates, Clojure source code.

However, because it dispatches on any keyword, your implementation of build can extend Fabricate to any method of representing structured information in Clojure, or override these defaults.

construct!

site.fabricate.api/construct!
Function

Run the tasks necessary to complete the website. Execute produce on every page, then run tasks.

Arguments
  1. tasks
  2. {:keys [site.fabricate.api/entries
    site.fabricate.api/options]

    :as init-site}
Returns
Source
site/fabricate/api.clj

The construct! function uses the structured content assembled in the previous step to produce output pages for the site.

By default, they are HTML. Any other output format could be generated from the entries by specifying the appropriate produce! implementation.

produce!

site.fabricate.api/produce!
Multimethod

Produce the content of a file from the results of the build operation and write it to disk. Takes an entry and returns an entry.

Arguments
  1. Entry map
  2. Options map
Returns
An entry with the :site.fabricate.page/location key added.
Source
site/fabricate/api.clj

The produce! multimethod creates output for the entry passed as an argument. It dispatches on two keys:

  1. :site.fabricate.document/format
  2. :site.fabricate.page/format

The implementation will generate output of the given page format from data of the given document format. By default, this is a HTML file, generated from the Hiccup data built in the assemble step.

The produce! multimethod returns an entry with a key added: :site.fabricate.page/location. This key indicates the URL or file path of a generated page.

In use

The API provides an elegant combination of extensibility and ease of use. This example is simplified from Fabricate's own page generation code.

(require [site.fabricate.api :as api]
[site.fabricate.dev.styles :as styles]
[site.fabricate.prototype.time :as time]
[site.fabricate.prototype.read :as read]
[site.fabricate.prototype.read.grammar :as grammar]
[site.fabricate.prototype.hiccup :as hiccup]
[dev.onionpancakes.chassis.core :as c])

Setting up with defmethod

Before running the API's 3 main functions, the build process defines methods for each of these multimethods.

1. api/collect

This implementation generates entries from each Fabricate template.

(defmethod api/collect "docs/**.fab"
[src options]
(mapv
(fn path->entry [p]
{:site.fabricate.document/format :hiccup
:site.fabricate.source/format :site.fabricate.read/v0
:site.fabricate.source/created (time/file-created p)
:site.fabricate.page/outputs
[{:site.fabricate.page/format :html
:site.fabricate.page/location
(fs/file (:site.fabricate.page/publish-dir
options)
)
}
]

:site.fabricate.source/modified (time/file-modified p)
:site.fabricate.api/source src
:site.fabricate.source/location (fs/file p)}
)

(fs/glob (System/getProperty "user.dir") src))
)

2. api/build

This implementation of the build multimethod evaluates Fabricate's templates and produces Hiccup from the results.

(defmethod api/build [:site.fabricate.read/v0 :hiccup]
([{loc :site.fabricate.source/location :as entry} _opts]
(try (fabricate-v0->hiccup entry)
(catch Exception ex
(throw (ex-info (ex-message ex)
(assoc
(Throwable->map
ex
:site.fabricate.source/location
loc)
)
)
)
)
)
)
)

3. api/produce!

This implementation of the produce! generates HTML from Hiccup elements.

(defmethod api/produce! [:hiccup :html]
[entry opts]
(hiccup->html entry opts))

Evaluating using the main API functions

Fabricate defines a few setup functions:

(def setup-tasks
[create-publish-dirs! get-css! copy-fonts!])

And some options:

(def options
"Options for building Fabricate's own documentation."
(let [d "html"] {:site.fabricate.page/publish-dir d}))

With all of that defined, this is all that Fabricate needs to do to generate its own documentation:

(->> {:site.fabricate.api/options options}
(api/plan! setup-tasks)
(api/assemble [])
(api/construct! []))