Skip to content

🔰 AsciiDoc support for Middleman 4. (In Middleman 3, AsciiDoc support is provided by a core extension).

License

Notifications You must be signed in to change notification settings

middleman/middleman-asciidoc

Repository files navigation

AsciiDoc Extension for Middleman (powered by Asciidoctor)

Gem Version Build Status (GitHub Actions)

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.

This extension is designed for Middleman >= 4.0.0. It’s tested using the lastest Middleman 4 release running on C Ruby >= 2.3 and JRuby >= 9.1. Prior to Middleman 4, AsciiDoc support was bundled with Middleman core.

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 String Array, default: {})

Custom AsciiDoc attributes to pass to the processor. The following built-in attributes are automatically appended to this collection. (You can remove one of these attributes by passing a custom attribute with the corresponding name prefixed with -).

  • env=site

  • env-site

  • site-gen=middleman

  • site-gen-middleman

  • builder=middleman

  • builder-middleman

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

  • site-root=(app.root.to_s)

  • site-source=(app.source_dir.to_s)

  • site-destination=(app.root.to_s + / + app.config[:build_dir])

  • site-environment=(app.environment.to_s)

  • relfilesuffix=(path to index file, as configured, if directory indexes are enabled)

  • relfilesuffix=../ if directory indexes are enabled

  • imagesdir=(http_prefix + app.config[:images_dir])@

    • skipped if -imagesdir (soft unset) or !imagesdir (hard unset) is defined as a custom attribute

    • can be overridden by page, hence the trailing @

  • imagesoutdir=(imagesdir relative to site destination)

promoted_attributes (type: String Array, default: [])

List of attribute names in the document header to convert to page data (aka front matter). Refer to the Adding Custom Page Data section for details.

promoted_attributes_convert_dashes (type: Boolean, default: false)

Whether to replace dashes in attribute names to underscores when promoting them to page data. Refer to the Adding Custom Page Data section for details.

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.

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).

safe (type: Symbol, default: :safe)

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

template_dirs (type: String or String Array, default: nil)

One or more directory paths (relative to the site root) where Asciidoctor converter templates should be discovered. Asciidoctor templates can be used to customize the HTML generated for each node in the Asciidoctor document tree.

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

The template engine filter to apply when scanning for Asciidoctor converter templates. For example, to only discover Slim templates, set this to :slim.

template_engine_options (type: Hash, default: nil)

Additional options to pass to the template engine when invoking the Asciidoctor converter templates. Options must be indexed by the template engine name (e.g., :slim).

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

The following implicit attributes are passed to the AsciiDoc processor prior to the page being converted, making them available to both the AsciiDoc source and extensions:

  • outfile - the absolute path of the output (HTML) file

  • outdir - the absolute path of the directory containing the output file

In addition to the outdir attribute, the :to_dir option is also passed to the AsciiDoc processor, and subsequently available on the parsed document object. This option may be useful for certain integrations, such as Asciidoctor Diagram.

Configuring Specific Pages

You can pass extra attributes and other options to the AsciiDoc processor for a given page (or set of pages) using the :renderer_options option of the page directive (where the “renderer” is the AsciiDoc processor):

page 'manual', renderer_options: {
  attributes: { 'sectanchors' => '' }
}
📎
The first argument to the page directive is the page ID. The page ID is computed by starting with 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.
⚠️
Attributes passed to the page directive must be specified as a Hash and receive no additional processing.

You can add extra attributes to a page more concisely using the :attributes option on the page directive:

page 'manual', attributes: { 'sectanchors' => '' }

The :attributes option on the page directive takes precedence over the :attributes option in :renderer_options.

Defining Additional Site Attributes

Middleman doesn’t have a predefined schema for site-related data, such as the title, url, and author. However, since this information is needed for most sites, authors end up defining one themselves. This section describes how to make this site-related information available to your AsciiDoc documents as attributes.

To maintain consistently with integrations for other site generators, we’ll use site- as the prefix for site-related attributes. For example, the site title will be named site-title.

First, we assume you’ve created a data file named site.yml in the data directory. For example:

data/site.yml
title: Site Title
url: http://example.com
description: An awesome site.
author: Joe Cool

The information in this file is available via the variable path app.data.site. The next step is to convert this information into AsciiDoc attributes.

Inside the activate block for the AsciiDoc extension, convert this data into attributes to pass to AsciiDoc, then assign the result to the attributes property of the extension.

activate :asciidoc do |asciidoc|
  attributes = {}
  allowed_value_types = [String, Numeric, TrueClass, FalseClass, Date, Time]
  app.data.site.inject(attributes) do |accum, (k, v)|
    accum[%(site-#{k})] = v if allowed_value_types.detect {|type| type === v }
    accum
  end
  asciidoc.attributes = attributes
end

You can now access these attributes from any AsciiDoc document in the site. For example, to reference the site title, you’d use:

{site-title}

If you want to support nested properties (e.g., site.blog.title), you can refactor the code above into a function you can call recursively. The convention is to flatten these paths into attribute names using the hyphen character (e.g., site-blog-title).

Customizing the Output File Extension

By default, the extension of an output file created from an AsciiDoc document is .html. If you want to customize this extension, for example to change it to .jsp, there are two ways to do so.

First, you can give the AsciiDoc source document a double file extension, such as .jsp.adoc. In this case, the second file extension (.adoc) tells Middleman the file is an AsciiDoc file and the first file extension (.jsp) tells Middleman which extension to use for the output file.

If you’re working with existing AsciiDoc documents, you may not want to change the file extension from .adoc. In this case, you must set the outfilesuffix AsciiDoc attribute, either globally:

activate :asciidoc, attributes: ['outfilesuffix=.jsp']

or per page you want to customize:

page 'manual', attributes: { 'outfilesuffix' =>  '.jsp' }

Note that setting the outfilesuffix has no effect on a file with a double file extension. When the file has a double file extension, the first extension is always used as the extension for the output file.

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.

📎
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). You can also set the :promoted_attributes option to provide a list of attributes that are always promoted without the page- prefix. If the :promoted_attributes_convert_dashes option is set to true, dashes in the attribute names are replaced with underscores (e.g., page-a-very-long-name becomes a_very_long_name). The value is parsed as YAML data (that which can be expressed in a single line).

In addition to these explicit page attributes and the :promoted_attributes option, 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

💡
You can continue to specify page data using the front matter header. Keep in mind that the AsciiDoc page- attributes override matching entries in the front matter header.
📎
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
💡
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)

⚠️
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.

Accessing the AsciiDoc Document From a Layout

You can access the document model for the current AsciiDoc-based page from the page layout as follows:

<%= current_page.data.document %>

This object is an instance of Asciidoctor::Document. It can be used, for instance, to output a table of contents for the current page:

<% if (doc = current_page.data.document) %>
<%= doc.converter.convert doc, 'outline', toclevels: 3 %>
<% end %>

For more information about this API, refer to the API documentation.

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. Here’s the result of the xref macro when directory indexes are enabled.

Meet our <a href="../team/">team</a>.
Using the interdoc xref macro with directory indexes on Asciidoctor < 1.5.7

If you’re using Asciidoctor < 1.5.7, you have to make one additional change to your pages to get the relfilesuffix to influence the output of the interdoc 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 < 1.5.7 will automatically produce the correct link to the other page when using directory indexes.

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 asciidoc_templates to store your templates for AsciiDoc.

$ mkdir asciidoc_templates

We don’t put this folder under the source directory since we don’t need Middleman monitoring it.

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 (and optionally the :template_engine option) when activating the extension:

activate :asciidoc, template_dirs: 'asciidoc_templates', template_engine: :slim

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 asciidoc_templates directory. Populate the file with the following contents:

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.

Using Asciidoctor Diagram

You can use Asciidoctor Diagram (gem: asciidoctor-diagram) to generate diagrams from plain text defined in specially marked literal blocks. Follow the steps below to enable Asciidoctor Diagram in your Middleman site.

Step 1: Add the Asciidoctor Diagram Gem

You’ll first need to add the asciidoctor-diagram gem to your Gemfile.

gem 'asciidoctor-diagram'

If you plan to make diagrams that rely on external commands, such as graphviz (dot), you may need to install additional external applications. Consult the Asciidoctor Diagram docs for further instructions.

Step 2: Install New Gems

Now run the bundle command to install the gem.

$ bundle

Step 3: Switch Asciidoctor to Unsafe Mode

This step is very important. You must change Asciidoctor to unsafe mode. Don’t worry, it’s not that unsafe. It just means that Asciidoctor Diagram will be able to write the generated image to a parent directory of the source file.

activate :asciidoc, safe: :unsafe

Step 4: Skip Build Clean

To prevent Middleman from removing the generated images when you run the build command, you need to add the following function to config.rb.

set :skip_build_clean, proc {|f| f.start_with? 'build/images/' }

This step is necessary since the generated images don’t get registered with the sitemap. As a result, Middleman inconveniently wants to remove them for you.

Step 5: Start Making Diagrams

At this point, you’re all set to start making diagrams! Just create an AsciiDoc page in the source folder and populate it with the following contents:

= Diagrams FTW!

Here's a document symbol generated by ditaa.

[ditaa,document,png]
....
+----+
|{d} |
|    |
+----+
....

The next time you generate the site, Asciidoctor Diagram replaces the literal block that has the style ditaa with a generated diagram. The diagram image ends up in the images folder under the build folder (build/images/document.png). The corresponding <img> element in the HTML refers to this image using a root-relative path (/images/document.png). Asciidoctor Diagram caches information about the image under the .asciidoctor/diagram folder relative to the project root (MM_ROOT). This folder is safe to delete.

Limitations

There are limitations with using Asciidoctor Diagram with Middleman. Since Asciidoctor Diagram generates the images during AsciiDoc conversion, Middleman doesn’t know about these resources (since they don’t get registered in Middleman’s sitemap). As a result, there are two things you must be aware of:

  • Diagrams won’t be shown when using the preview server (server)

  • Middleman will try to purge the generated images when building the site (build)

To prevent Middleman from purging the diagrams, you need to add the “skip build clean” function mentioned above.

Community

The official community forum for Middleman can be found at forum.middlemanapp.com. For questions related to this extension or general questions about AsciiDoc, please post to the Asciidoctor discussion list at 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 https://github.com/middleman/middleman-asciidoc &&
      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-2018 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.

About

🔰 AsciiDoc support for Middleman 4. (In Middleman 3, AsciiDoc support is provided by a core extension).

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published