the little static site engine that could
Plugins provide the functionality behind function calls like \foo{bar}
.
Out of the box, Booklit comes with a plugin called baselit
which provides basic functions like \title
, \section
, \italic
, and \bold
.
More functions can be added by writing plugins and using them in your documents.
If you've skipped ahead, you may want to check out Getting Started to see how to set up your Go module.
To use a plugin, pass its Go package's import path as --plugin
to the booklit
command when building your docs.
For example, Booklit comes with a chroma
plugin for syntax highlighting. To use it, run:
booklit -i index.lit -o out \
--plugin github.com/vito/booklit/chroma/plugin
The --plugin
flag must be passed every time you build your docs, so you may want to put it in a script:
#!/bin/bash
booklit -i lit/index.lit -o public \
--plugin github.com/vito/booklit/chroma/plugin \
"$@" # forward args from script to booklit
Booklit imports all specified plugins at build time, automatically adding them to go.mod
. When imported, plugins register themselves under a certain name - typically guessable from the import path.
To use the plugin in your documents, call \use-plugin
with its registered name:
\title{My Section}
\use-plugin{chroma}
\syntax{ruby}{{{
def fib(n)
fib(n - 2) + fib(n - 1)
end
}}}
The --plugin
flag can be specified multiple times, and \use-plugin
can be invoked multiple times.
Note: inline sections inherit plugins from their parent sections, but included sections do not.
Plugins are just Go packages that register a plugin factory with Booklit when they're imported with the --plugin
flag.
It's possible to use Booklit without writing any plugins of your own, but being able to write a plugin help you get the most out of Booklit.
To create a new plugin, create a directory within your Go module (where go.mod
lives) - let's call it example
for this example:
mkdir example
Then, we'll create the initial skeleton for our plugin at example/plugin.go
:
package example
import (
"github.com/vito/booklit"
)
func init() {
booklit.RegisterPlugin("example", NewPlugin)
}
func NewPlugin(sec *booklit.Section) booklit.Plugin {
return Plugin{
section: sec,
}
}
type Plugin struct {
section *booklit.Section
}
This registers a plugin that does nothing. Let's define some document functions!
Functions work by simply defining methods on the plugin struct. Let's define a basic one with no arguments:
func (plugin Plugin) HelloWorld() booklit.Content {
return booklit.String("Hello, world!")
}
Now let's create a Booklit document that uses it as hello-plugins.lit
:
\title{Hello Plugins}
\use-plugin{example}
Zero args: \hello-world
To build this document, pass the package import path (including your module name) as the --plugin
flag. For example, if your go.mod
says module foo
, the flag would be:
booklit -i hello-plugins.lit -o out \
--plugin foo/example
This should result in a page showing:
Zero args: Hello, world!
Functions can be invoked with any number of arguments, like so:
\hello-world{arg1}{arg2}
See Function Syntax for more information.
Each argument to the function corresponds to an argument for the plugin's method, which may be variadic.
The plugin's arguments must each be one of the following types:
booklit.Content
The evaluated content. This can be just about anything from a word to a sentence to a series of paragraphs, depending on how the function is invoked. It is typically used unmodified.
string
The evaluated content, converted into a string. This is useful when the content is expected to be something simple, like a word or line of text. The \title
function, for example, uses this type for its variadic tags argument.
booklit/ast.Node
The unevaluated syntax tree for the content. This is useful when doing meta-level things like \section
which need to control the evaluation context of the content.
Plugin methods can then return one of the following:
nothing
error
(
booklit.Content
, error)
If a method returns a non-nil error
value, it will bubble up and the building will fail.
Putting the pieces together, let's extend our pluglit
plugin from earlier write a real function that does something useful:
func (plugin Plugin) DescribeFruit(
name string,
definition booklit.Content,
tags ...string,
) (booklit.Content, error) {
if name == "" {
return nil, errors.New("name cannot be blank")
}
content := booklit.Sequence{}
if len(tags) == 0 {
tags = []string{name}
}
for _, tag := range tags {
content = append(content, booklit.Target{
TagName: tag,
Display: booklit.String(name),
})
}
content = append(content, booklit.Paragraph{
booklit.Styled{
Style: booklit.StyleBold,
Content: booklit.String(name),
},
})
content = append(content, definition)
return content, nil
}
There are many things to note here:
there are two required arguments; name is a string
and value is a booklit.Content
there's a variadic argument, tags, which is of type []string
this function generates content, and can raise an error when building
the booklit.Target
elements will result in tags being registered in the section the function is called from
the function name, describe-fruit
, corresponds to the method name DescribeFruit
This function would be called like so:
\describe-fruit{banana}{
A banana is a yellow fruit that only really tastes
good in its original form. Banana flavored
anything is a pit of dispair.
}{banana-opinion}
...and will result in something like the following:
banana
A banana is a yellow fruit that only really tastes good in its original form. Banana flavored anything is a pit of dispair.
...which can be referenced as \reference{banana-opinion}
, which results in a link like this: banana.