Ivy

Ivy is a static website generator built in Python. It transforms a directory of text files into a self-contained website.

Extensions


Installing Plugins

The site extensions directory ext is intended for plugins that extend Ivy's functionality. Python modules placed in this directory will be loaded automatically and can take advantage of Ivy's extension hooks to inject their own code into the build process.

Alternatively, extension modules installed on Python's standard import path can be activated by adding their names to an extensions list in the site's configuration file:

extensions = [
    'extension_one',
    'extension_two',
]

This method can be used to load extensions installed from the package index using pip.

Event & Filter Hooks

Ivy exports a flexible framework of event and filter hooks. Plugins can extend Ivy by registering callback functions on these hooks.

Events

Event callbacks accept zero or more arguments depending on the specific hook. They may modify their arguments in place but have no return value.

Here's a simple event callback that prints a count of the number of pages that have been written to disk:

import ivy

@ivy.hooks.register('exit')
def print_page_count():
    print(ivy.site.written())

This callback is registered on the exit event hook which fires just before the application exits. (The exit hook can be found in the ivy/__init__.py file.)

Filters

Filter callbacks accept at least one argument — the value to be filtered. They may accept additional arguments depending on the specific hook. Filter callbacks modify and return the value of their first argument.

Here's a simple filter callback that changes every instance of the word foo in node content to bar:

import ivy

@ivy.hooks.register('node_text')
def foo_to_bar(text, node):
    return text.replace('foo', 'bar')

This callback is registered on the node_text filter hook which fires just before a node's text is rendered into HTML. (The node_text hook can be found in the ivy/nodes.py file).

Note that this hook supplies us with the Node instance itself as an additional argument which we here ignore.

Rendering & Parsing Engines

Ivy relies for most of its functionality on a suite of pluggable rendering and parsing engines, e.g. the Jinja template-engine for handling .jinja template files. Plugins can register support for additional rendering and parsing engines using a system of @register decorators.

Templates

Ivy has builtin support for Jinja and Ibis templates. Plugins can register support for additional formats using the @templates.register() decorator. Template-engine callbacks are registered per file-extension, e.g.

import ivy

@ivy.templates.register('jinja')
def jinja_callback(page, filename):
    ...
    return html

A template-engine callback should accept a Page object and a template filename and return a string of HTML.

Node Content

Ivy has builtin support for node files written in Markdown and Syntex. Plugins can register support for additional formats using the @renderers.register() decorator. Rendering-engine callbacks are registered per file-extension, e.g.

import ivy

@ivy.renderers.register('md')
def markdown_callback(text):
    ...
    return html

A rendering-engine callback should accept a single string argument containing plain text and return a string of HTML.

Node Meta

Ivy has builtin support for YAML file headers. Plugins can add support for additional formats by preprocessing file content on the file_text filter hook.

import ivy

@ivy.hooks.register('file_text')
def parse_toml(text, meta):
    ...
    return text

This filter fires each time a node file is loaded; it passes the raw file text along with a metadata dictionary. Callbacks can check the text for an appropriate header marker, process the header if found, and update the dictionary. They should return the text with the header stripped.

The file_text hook can be found in the ivy/loader.py file.

Shortcodes

Ivy has builtin support for WordPress-style shortcodes with space-separated positional and keyword arguments:

[% tag arg1 key=arg2 %] ... [% endtag %]

Plugins can register shortcode tags using the @shortcodes.register() decorator:

import shortcodes

@shortcodes.register('tag')
def handler(node, content, pargs, kwargs):
    ...
    return replacement_text

Handler functions should accept four arguments:

  1. The Node instance containing the shortcode.
  2. A string containing the shortcode's content, if the shortcode has block scope.
  3. A list of positional arguments.
  4. A dictionary of keyword arguments.

Positional and keyword arguments are passed as strings. The handler function itself should return a string.

Note that shortcodes are processed before node text is rendered into HTML.