Content Transformations

Converting pages to their output is done by filtering the content through the appropriate Glim::Filter subclasses (provided by a plugin written in ruby).

A subclass needs to specify transforms and implement the transform method, for example to transform HAML to HTML we can write this subclass:

class HTMLAbstractionMarkupLanguage < Glim::Filter
  transforms 'haml' => 'html'
  
  def transform(content, page, options)
    engine = Haml::Engine.new(content)
    engine.render
  end
end

In the above filter we change HAML into HTML but we can also write a filter that doesn’t change the format, for example we may allow our Markdown files to set references in their front matter to a file that should be appended to the content before HTML conversion, this can be done as follows:

class AddReferencesToMarkdown < Glim::Filter
  transforms 'markdown' => 'markdown'

  def transform(content, page, options)
    if path = page.data['references']
      content + "\n\n" + File.read(path)
    else
      content
    end
  end
end

Glim will apply this filter before it applies the 'markdown' => 'html' filter, as it wants to construct the longest chain of filters.

There are 3 default filters which transform:

  1. 'liquid' => '*': Expand liquid tags in the content.
  2. 'markdown' => 'html': Convert to HTML using Kramdown.
  3. '*' => 'output': Wrap the content in the specified layout (read from _layouts/).

It is possible to override these by creating a filter that declares the same transformation. In such override, it is possible to call super in the transform method to call on the next-best filter to handle the transformation. Filters each have a priority, the default is :normal and all the default filters have :lower, therefor a new filter will by default have higher priority than the default filters.

From the above we have 4 formats (liquid, markdown, html, and output) but formats can also have a subtype: When Glim loads a file it sets the format of that file to liquid.«extension», meaning that a file named main.css gets assigned an initial format of liquid.css.

The initial format can be explicitly set in the page’s front matter, e.g. setting format: css will bypass expansion of liquid tags.

When a filter transforms something to * then the output format will be the input format’s subtype. So in the case of main.css where the input format is liquid.css, Glim will apply the 'liquid' => '*' filter and after that, the format will be css and it will then look for a filter that can transform css.

By default we have no filters for css but we do have '*' => 'output' so that filter will be used. When the input of a filter is * then the output format’s subtype will be the input format’s main type.

That means in the case of our css format, after filtering it through '*' => 'output' the new format will be output.css.

Giving an example of a markdown file the process looks as follows:

  1. Read index.markdown: Set format to liquid.markdown.
  2. Apply liquid => *: Format changed from liquid.markdown to markdown.
  3. Apply markdown => html: Format changed from markdown to html.
  4. Apply: * => output: Format changed from html to output.html.
  5. No suitable filters for output.html, filtering complete.

It was previously said that the initial format is liquid.«extension» but if there is a mapping of the extension to a format then we will use that first, which is how both index.md and index.mdown will get the same initial format of liquid.markdown.

This mapping is provided by filters who use the extensions class method, for example in the case of Markdown our subclass could look like this:

class MarkdownFilter < Glim::Filter
  transforms 'markdown' => 'html'
  extensions 'markdown' => %w( mkdown mkdn mkd md )

  def transform(content, page, options)
    …
  end
end

What the above means is that the markdown format should be used for files with any of the specified extensions. We leave out markdown since files with markdown extension already have the correct value for their format.