Middleman AsciiDoc (gem: middleman-asciidoc) is an extension for the Middleman static site generator that adds support for the AsciiDoc lightweight markup language. Specifically, this extension converts AsciiDoc files in your site source to HTML pages. This conversion is performed using Asciidoctor by default.

Important
This extension is designed for Middleman >= 4.0.0. Prior to Middleman 4, AsciiDoc support was bundled with Middleman.

Installation

If you’re just getting started, first install the middleman gem.

$ gem install middleman

Then, generate a new project:

$ middleman init MY_PROJECT

The project will be created in the MY_PROJECT directory.

If you’re working with an existing project, you can skip the previous steps.

Next, add the gem for this extension to your project’s Gemfile.

gem 'middleman-asciidoc'

Finally, run Bundler to install the dependencies using the bundle command:

$ bundle

You’re now ready to activate this extension and begin putting it to work.

Activation and Configuration

To activate the AsciiDoc extension, add the following line to the config.rb file for your site:

activate :asciidoc

You can also pass options to the extension. One way is to pass a Hash of options as the second argument of activate. In this case, the option keys must be expressed as symbols.

Here’s an example of how to set the attributes option for this extension, which assigns attributes to the underlying Asciidoctor processor:

activate :asciidoc, attributes: ['source-highlighter=coderay']

Alternately, you can assign options directly to the extension object when using the block form of activate. In this case, the options are properties of the extension object.

Here’s how to assign the attributes option for this extension using the block form of activate:

activate :asciidoc do |asciidoc|
  asciidoc.attributes = ['source-highlighter=coderay']
end

The following option keys can be used to configure this extension. With the exception of layout, these option keys map directly to options in the Asciidoctor API.

attributes (type: Hash or Array, default: [])

Custom AsciiDoc attributes to pass to the processor. The following implicit attributes are automatically appended to this collection.

  • env=site

  • env-site

  • site-gen=middleman

  • site-gen-middleman

  • builder=middleman

  • builder-middleman

  • middleman-version=(value of Middleman::VERSION constant)

  • imagesdir=(value of http_prefix + images_dir in app config)@

    • skipped if !imagesdir is defined as a custom attribute

    • can be overridden in page (hence the trailing @)

backend (type: Symbol, default: :html5)

The moniker for a converter, which determines the output format to generate.

base_dir (type: String, default: :docdir)

The base directory to use for the current AsciiDoc document. If not specified, defaults to the document directory (i.e., :docdir). To set the base directory to the source directory, use the keyword :source or the expression app.source_dir. To leave the base directory undefined, use the value nil.

safe (type: Symbol, default: :safe)

The safe mode level under which to run the AsciiDoc processor.

layout (type: String or Symbol, default: nil)

The name of the default layout for AsciiDoc-based pages (pages processed by this extension, not including blog articles if a blog layout is specified).

Tip
The full set of options can be seen on your preview server’s config page at the path /__middleman/config/.

Creating Pages

Each AsciiDoc file in the source directory (except for files that begin with +_+ or which are located in a directory that begins with +_+) becomes a page in the site. AsciiDoc files can have the file extension .adoc or .html.adoc. These extensions are stripped and replaced with the value of the outfilesuffix attribute, which defaults to .html.

Note
For details about how the file extension is substituted, see the discussion in issue #7.

To add a page composed in AsciiDoc, simply add an AsciiDoc file that has one of the aforementioned AsciiDoc file extensions to the project source directory.

sample.adoc
= Sample Page
:page-layout: page
:uri-asciidoctor: http://asciidoctor.org

This is a sample page composed in AsciiDoc.
The Middleman AsciiDoc extension converts it to HTML using {uri-asciidoctor}[Asciidoctor].

[source,ruby]
----
puts "Hello, World!"
----

Adding Custom Page Data

AsciiDoc attributes defined in the document header whose names begin with page- are promoted to page data (aka front matter). The part of the name after the page- prefix is used as the entry’s key (e.g., page-layout becomes layout). The value is parsed as YAML data (that which can be expressed in a single line).

In addition to these explicit page attributes, the following AsciiDoc attributes are also promoted to page data:

  • doctitle (i.e., the document title) (becomes title)

  • author (becomes author.name)

  • email (becomes author.email or author.url)

    • if value matches the pattern url[@username], author.username is also set

  • authors (converted to an Array of Author objects)

  • revdate (becomes date; value is converted to a Time object)

  • keywords (value is kept as a String)

  • description

Tip
You can continue to specify page data using the front matter header. The AsciiDoc page- attributes override matching entries in the front matter header.
Note
If you specify a time zone in the value of the revdate attribute, that time zone is honored. Otherwise, the date specified is assumed to have the time zone set for the application. You can define the application time zone in config.rb using set :time_zone (a setting shared with the blog extension). If you don’t specify a time zone in the page’s date or for the application, dates are assumed to be UTC.

Specifying a Layout

The most important of these page attributes is page-layout, which determines the layout that is applied to the page. Middleman will look for the first file that matches this root name under the source directory and use it as the layout. For example, if page-layout has the value page, Middleman might resolve a layout named page.erb. You can set the extension of the layout file using the page-layout-engine attribute.

If a layout is not specified, or the value of the page-layout attribute is empty, the default layout for the site is used.

You can set a default layout for all pages in config.rb using:

set :layout, :name_of_layout

Alternately, you can set a default layout just for AsciiDoc-based pages (pages processed by this extension) in config.rb using:

activate :asciidoc, layout: :name_of_layout

Finally, you can set the layout for a specific page or group of pages using the page directive. This is an alternate way to define front matter for a page.

page 'home', layout: :name_of_layout
Note
The first argument to the page function is the page ID. The page ID is computed starting from the path of the source file relative to the source directory, then removing the template extension (i.e., the AsciiDoc extension), then removing the .html extension, if present. For example, the page ID for both home.adoc and home.html.adoc is home.
Tip
When you define the layout in config.rb, you can specify the value either as a String or a Symbol.

If you don’t set the layout in config.rb, the default layout is considered unset. (The one exception to this rule is the layout for blog articles, which is controlled by the configuration for the blog extension).

AsciiDoc-based pages are configured to use the automatic layout by default (i.e., the page-layout attribute is set to blank). If you unset the page-layout attribute, the AsciiDoc processor will handle generating a standalone document (header_footer: true). In this case, the page will appear like an HTML file that is generated by the AsciiDoc processor directly.

Here are the different ways to specify a layout:

  • :page-layout:, :page-layout: _auto_layout, or not specified — use the automatic layout (default: layout)

  • :page-layout: custom — use the page layout named "custom" (e.g., custom.erb)

  • :!page-layout: or :page-layout: false — generate a standalone HTML document

  • :page-layout: ~ or :page-layout: null — generate a page without a layout (don’t wrap content in a layout)

Warning
Layout for blog posts
If you’re using the Middleman Blog extension to write blog posts, the layout property on the blog configuration overrides the default layout, but you can still override that setting using the page-layout attribute in each post.

Accessing the AsciiDoc Configuration From a Layout

You can access the global configuration for the AsciiDoc extension from a layout template using the variable path app.config.asciidoc (Hash).

For example, let’s say you want to reference the location stored in the imagesoutdir attribute. You can do so in an ERB template using:

<%= app.config.asciidoc[:attributes]['imagesoutdir'] %>

Other processor options, such as :safe, are available from the app.config.asciidoc variable path.

If you want to access the options passed to the AsciiDoc processor for the current page, use the variable path current_page.options[:renderer_options] (Hash) instead.

For example, let’s say you want to access the resolved base directory for the current page. You can do so in an ERB template using:

<%= current_page.options[:renderer_options][:base_dir] %>

Other processor options, such as :attributes, are available from the current_page.options[:renderer_options] variable path.

Ignoring a Page

In addition to the normal ignore filter in Middleman, you can also control whether a page is ignored from AsciiDoc. To mark a page as ignored from AsciiDoc, set the page-ignored attribute in the AsciiDoc document header to any value other than false, as follows:

= Ignored Page
:page-ignored:

Once this page attribute is detected, no further processing is performed on the document by this extension.

Marking an Article as a Draft

If you’re using the Middleman Blog extension, you can mark an article as a draft so it does not get published. To do so, assign the value false the page attribute named page-published in the AsciiDoc document header, as follows:

= Draft Article
:page-published: false

This effectively sets the published key in the page data to false. Recall that the AsciiDoc extension converts the value of page attributes as a YAML value, which means the string literal "false" becomes the boolean value false. Middleman then knows not to publish this article.

Another option is to set the date of the article way into the future.

= Future Post
Author Name
3001-01-01

By default, the blog extension does not publish articles with a future date.

Linking Between Pages

You can link from one page to another using an inter-document xref. Let’s say you have the following two pages in the source directory:

  • about.adoc

  • team.adoc

You can link from the about page to the team page using the following:

Meet our <<team.adoc#,team>>.

The .adoc# suffix indicates the xref targets another page. The target is the path from the current page to the other page (a source-to-source reference). This reference is then converted to the following HTML:

Meet our <a href="team.html">team</a>.

Of course, we’re assuming there that the input maps 1-to-1 to the output. That assumption breaks down as soon as you enable directory indexes.

When directory indexes are enabled, each page is moved into its own folder and renamed to index.html. So how does the xref work in that case?

This extension provides built-in support for directory indexes. When the directory indexes extension is enabled, this extension automatically defines the relfileprefix and relfilesuffix attributes on the AsciiDoc document. The relfilesuffix attribute honors both the :trailing_slash and :strip_index_file options in Middleman. However, you have to make one change to your pages for these attributes to work with the xref macro.

Below the document header (but not in the document header), you must assign the outfilesuffix attribute to the value of the relfilesuffix attribute. Here’s an example:

= About Us

// ^ the previous blank line is required!
ifdef::relfilesuffix[:outfilesuffix: {relfilesuffix}]

...

Meet our <<team.adoc#,team>>.

With the help of the outfilesuffix assignment, Asciidoctor automatically produces the correct link to the other page.

Meet our <a href="../team/">team</a>.

Optionally, you can construct the link manually using:

Meet our link:{relfileprefix}team{relfilesuffix}[team].

I think you’ll agree that using the xref macro is simpler.

Controlling the Destination Path

By default, Middleman does not support controlling the destination path from the page data, often called a permalink. However, with the addition of a simple extension, it’s possible to enable this feature.

Start by adding the following Ruby code to the file lib/permalink.rb.

lib/permalink.rb
class Permalink < Middleman::Extension
  # Run after front matter extension (priority: 20), after the AsciiDoc extension (priority: 30),
  # and before other third-party extensions (priority: 50).
  self.resource_list_manipulator_priority = 35

  def manipulate_resource_list resources
    resources.each do |resource|
      if !resource.ignored? && (resource.respond_to? :data) && (permalink = resource.data.permalink)
        permalink = permalink.slice 1, permalink.length if permalink.start_with? '/'
        resource.destination_path = %(#{permalink}#{resource.ext})
      end
    end
  end
end

Middleman::Extensions.register :permalink, Permalink

Next, require and activate this extension in the config.rb file for your site:

require_relative 'lib/permalink'
activate :permalink

You can now customize the destination path for any AsciiDoc-based page by adding the following attribute entry to the document header:

:page-permalink: custom-destination-path

Customize the destination path to your liking. The leading forward slash (/) is optional.

Building Your Site

You can now build your site using:

$ middleman build

or preview it using:

$ middleman serve

If you’re using Bundler, use the following commands instead:

$ bundle exec middleman build
$ bundle exec middleman serve

Customizing the HTML

You can use templates to customize the HTML Asciidoctor generates for the pages in your site. Each template file corresponds to a node in the AsciiDoc document tree (aka AST). Template files can be composed in any templating language supported by Tilt.

Follow the steps below to configure Asciidoctor to use custom templates when converting AsciiDoc documents to HTML.

Step 1: Add Required Gems

You’ll first need to add the thread_safe gem to your Gemfile. If you plan to use a template language other than ERB (.erb) or InterpolatedString (.str), you’ll also need to add the dependency for the template language. We’ve decided to use Slim for this example, so we need to also add the slim gem.

gem 'slim', '~> 3.0.9'
gem 'thread_safe', '~> 0.3.6'

Step 2: Install New Gems

Now run the bundle command to install the new gems.

$ bundle

Step 3: Create a Templates Folder

Next, create a new folder in your site named source/_asciidoc_templates to store your templates for AsciiDoc.

$ mkdir source/_asciidoc_templates

We prefix the folder name with an underscore so it doesn’t get included in the sitemap (i.e., Middleman won’t look for pages in this folder).

Step 4: Configure Asciidoctor to Load Templates

In your site’s config.rb file, configure Asciidoctor to load the templates by setting the :template_dirs option when activating the extension:

activate :asciidoc, template_dirs: source/_asciidoc_templates

Step 5: Compose a Template

The final step is to compose a template. We’ll be customizing the unordered list node. Add a file named ulist.html.slim to the source/_asciidoc_templates directory. Populate the file with the following contents:

source/_asciidoc_templates/ulist.html.slim
- if title?
  figure.list.unordered id=id
    figcaption=title
    ul class=[style, role]
      - items.each do |_item|
        li
          span.primary=_item.text
          - if _item.blocks?
            =_item.content
- else
  ul id=id class=[style, role]
    - items.each do |_item|
      li
        span.primary=_item.text
        - if _item.blocks?
          =_item.content

The next time you build your site, Asciidoctor will use your custom template to generate HTML for all unordered lists converted from AsciiDoc.

Community

The official community forum for Middleman can be found at https://forum.middlemanapp.com. For questions related to this extension or general questions about AsciiDoc, please post to the Asciidoctor discussion list at http://discuss.asciidoctor.org.

Bug Reports

Github Issues are used for managing bug reports and feature requests. If you run into issues, please search the issues and submit new problems in the project’s issue tracker.

The best way to get quick responses to your issues and swift fixes to your bugs is to submit detailed bug reports, include test cases and respond to developer questions in a timely manner. Even better, if you know Ruby, you can submit pull requests containing Cucumber Features which describe how your feature should work or exploit the bug you are submitting.

How to Run Tests

The tests are based on Cucumber. Here’s how to clone the project and run the tests.

  1. Clone the repository:

    $ git clone {uri-repo} &&
      cd "`basename $_`"
  2. Install Bundler (if not already installed):

    $ gem install bundler
  3. Run Bundler (from the project root) to install the gem dependencies:

    $ bundle
  4. Run test cases (based on Cucumber) using Rake:

    $ bundle exec rake cucumber

Copyright © 2014-2017 Dan Allen and the Asciidoctor Project. Free use of this software is granted under the terms of the MIT License. For the full text of the license, see the LICENSE file.