On this page:

4 Extending with Plugins

Plugins are what provide the functionality behind function calls like \foo{bar}. Out of the box, Booklit comes with a plugin called baselit which provides basic primives like \title and \section in addition to support for basic markup like \italic and \bold. It also serves as a good reference when writing your own plugin.

4.1 Get Going

Plugins are implemented in the Go programming language, so if you're planning on writing or using one, make sure you've got it installed.

First you'll need to get your $GOPATH sorted out. By default, Go sets this value to ~/go, but if that's not sufficient you can set it to something else. This is the path under which Go source code and compiled binaries will live.

One suggestion: you may want to set the root of your Booklit content as your $GOPATH, so that your content can live alongside its plugins, whether they're all third-party or whether you'll be writing some yourself.

For starters, let's fetch Booklit itself into your $GOPATH:

go get github.com/vito/booklit

This fetch is necessary even if you already have the booklit binary installed, as plugins make use of Booklit's Go API.

4.2 Using Plugins

To use a plugin, you'll need to pass its Go import path to the booklit command when building your docs via the --plugin flag, like so:

booklit -i index.lit -o out \
    --plugin my/fancyplugin

The plugin will then be pulled in at runtime, and it will register itself under a certain name (conventionally, the same name as its package). Next, execute \use-plugin in your .lit document to add it to the section:

\title{My Section}

\use-plugin{fancyplugin}

Blah blah.

The --plugin flag can be specified multiple times, and \use-plugin can be invoked multiple times.

4.3 Writing Plugins

To write a new plugin, first you'll need to create a directory for your plugin's package in your $GOPATH. Let's just call it something cute like pluglit.

mkdir $GOPATH/src/pluglit

Then, we'll create the initial skeleton for our plugin at $GOPATH/src/pluglit/plugin.go:

package pluglit

import (
  "github.com/vito/booklit"
)

func init() {
  booklit.RegisterPlugin("pluglit", NewPlugin)
}

func NewPlugin(sec *booklit.Section) booklit.Plugin {
  return Plugin{
    section: sec,
  }
}

type Plugin struct {
  section *booklit.Section
}

This registers a plugin that effectively does nothing. But it does it well.

Let's define some document functions! Functions work by simply defining methods on the plugin. 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:

\use-plugin{pluglit}

Zero args: \hello-world

And build it like so:

booklit -i hello-plugins.lit -o out \
    --plugin pluglit

This should result in a page showing:

Zero args: Hello, world!

4.3.1 Argument Types

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.

4.3.2 Return Values

Plugin methods can then return one of the following:

If a method returns a non-nil error value, it will bubble up and the building will fail.

4.3.3 A Full Example

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:

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.