Custom markdown in Middleman

How we're using middleman, redcarpet, and custom markdown rendering for our new docs site

Background

We’ve been releasing some brand new products here at RightScale that are pretty technical in nature and really required us to take a hard look at our documentation layout and usability. The fact of the matter is, our old documentation site was from a different point in our business and didn’t provide the kinds of collaboration tools and freedom that we wanted for our new products.

After an analysis of different documentation platforms, we settled on Middleman. We were planning on engaging our engineering team much more for documentation content on our new docs site, so we needed something that fit in with our existing tools and processes and we didn’t mind trading added complexity for all of the benefits we would get from having a full open source platform at our disposal. Our UX team could handle the CSS and design aspect, while our engineering teams could use our standard engineering flow to submit pull requests which would eventually be reviewed by the product team and merged by our documentation team.

Once we had nailed down the implementation specifics, including the decision, for now, to host the site on Github pages, our design team got to work on creating a styleguide that we would use across the site. The styleguide would show conventions and approved UI elements, as well as describe how to use them all using markdown, which we decided would be our syntax of choice.

While most of the styles were pretty standard - how to do tables, syntax highlighting, etc - there were a couple that required the use of straight HTML to get the desired effect. Instead of forcing our document authors to copy and paste these large snippets of HTML, we decided to implement custom markdown syntax. Before we get into the details, let’s look at an example of one of those styles, what the HTML looked like, and what we can do with markdown now.

Custom Markdown example

Here’s the “content card” style from the styleguide, and the associated HTML needed to generate it: content card

<div class="panel panel-default">
  <div class="panel-heading">
    <div class="panel-title">
      Title
    </div>
  </div>
  <div class="panel-body">
    <p>
      Card body lorem ipsum dolor sit amet, consectetur adipisicing elit. Ratione porro itaque culpa adipisci iusto qui accusamus, dolorum cumque eligendi laudantium doloribus illo, eveniet accusantium, ducimus voluptatum dolore expedita repellendus totam.
    </p>
  </div>
  <div class="panel-footer">
    <p>
      Card footer lorem ipsum dolor sit amet, consectetur.
    </p>
  </div>
</div>

And here’s the same card, using our custom markdown syntax:

[[Title
Card body lorem ipsum dolor sit amet, consectetur adipisicing elit. Ratione porro itaque culpa adipisci iusto qui accusamus, dolorum cumque eligendi laudantium doloribus illo, eveniet accusantium, ducimus voluptatum dolore expedita repellendus totam.
Card footer lorem ipsum dolor sit amet, consectetur.]]

You can see that for the above style, we’ve chosen to use 2 brackets as the beginning and end delimiter, post and prefixed by the title and footer content. The main card content can be any number of lines and can contain other markdown elements within it.

Implementing custom Markdown

Implementing custom markdown requires a few moving pieces: building a custom markdown renderer class, defining your custom markdown syntax parsing with regular expressions, and instructing Middleman to use the new renderer.

Creating a custom renderer

First we need to create a custom renderer class that will handle rendering the custom markdown. We’re using Redcarpet as our renderer in middleman, so we create a Ruby class that inherits from the redcarpet HTML renderer. Note that you could build this class directly in your middleman config.rb file, but as it starts to get more complex, you’ll want to move it to its own file with specs/etc.

Declaring and initializing

class RightScaleCustomMarkdown < Redcarpet::Render::HTML
  def initialize(options={})
    super options.merge(
      :with_toc_data                => true,
    )
  end
end

Note above that we have to merge certain options into our custom class, the options listed for the Renderer in the redcarpet page.

Overriding renderer methods

The redcarpet page also lists the methods that you can override, but since we’re trying to implement block-level markdown elements, we’re going to have to process the raw page before handing it over to the normal renderer, so we’ll implement the preprocess method.

...

  # Pre process all the RightScale-specific markdown tags
  def preprocess(document)

    # We need this renderer to force render of content within our multi-line markdown elements
    # Note this is the same config used in config.rb for the generic middleman renderer
    r = Redcarpet::Markdown.new(self, options = {  
      :markdown => true,
      :fenced_code_blocks           => true,
      :no_intra_emphasis            => true,
      :tables                       => true,
      :autolink                     => true,
      :disable_indented_code_blocks => true,
      :quote                        => true,
      :lax_spacing                  => true
    })

    # Render all of our custom markdown elements
    rendered_doc = custom_content_card(rendered_doc, r)
  end
...

As the comment in the code notes, if you want to render any markdown inside of your methods, you have to have a renderer class available to do that. You can’t simply use super for various reasons described on this page, so you have to create a new class. And, you have to remember to use any options for the new class that you want the renderer to use (in our case, the same options as for middleman – see below).

Parsing the content for the new markdown syntax

As you can see above, our preprocess method calls a method that does the work of locating text and generating HTML content. Let’s take a look at the method for the content cards that we saw above:

...
  # Custom renderer for the "content card" style type in the styleguide
  def custom_content_card(document, renderer)
    document.gsub(/^\[\[(.*?)\n(.*?)\n([^\n]*?)\]\]/m) do
      resp = "<div class=\"panel panel-default\">
        <div class=\"panel-heading\">
          <div class=\"panel-title\">
          #{$1}
          </div>
        </div>
        <div class=\"panel-body\">#{renderer.render($2)}</div>
        <div class=\"panel-footer\">#{$3}</div>
      </div>"
      resp
    end
  end

The above is fairly straightforward - we’re searching the text for our custom markdown syntax, using regex groups to get the content we need, and generating the surrounding HTML. One point to note is the use of the renderer class that we declared above. Because we want the content of the card to be rendered like normal markdown, we explicitly use the renderer to render it so that when we return the resulting string to middleman, these code portions are all done.

Configuring middleman

Now that we have our custom renderer, we configure middleman with all of the markdown rendering options that we want to use and instruct it to use the customer renderer. Note that we also use middleman-syntax for code highlighting, which, in turn, uses Rouge. Here are the relevant parts of our middleman config.rb:

activate :syntax, :css_class => 'syntax-highlight', :line_numbers => false

# Markdown configuration
set :markdown_engine, :redcarpet
set(
  :markdown,
  :fenced_code_blocks           => true,
  :no_intra_emphasis            => true,
  :tables                       => true,
  :autolink                     => true,
  :disable_indented_code_blocks => true,
  :quote                        => true,
  :lax_spacing                  => true,
  :renderer                     => RightScaleCustomMarkdown
)

Note the last line - this is how to instruct middleman to use our custom renderer class. One point to note is that all of the options listed in that block are from the Markdown object, i.e. those listed here on the redcarpet page. The options listed farther down on the referenced redcarpet page are for the Renderer classes (which we set above when creating our custom renderer class).

Other custom markdown in use

We also implemented a couple of other markdown syntaxes for other UI elements. The first is for “alert” boxes – the style and the markdown we use is shown below.

alert style

The syntax for the above is:

!!info*Heads up!* This alert needs your attention, but it's not super important.

The final syle we implemented was to build tabsets using boot strap so that we could show examples in multiple languages. The style looks like this:

alert style

Which can be written in markdown like so:

  [[[
  ### Curl
  ``` shell
  curl -i -h -X ...
  ```
  ###

  ### Ruby
  ``` Ruby
  RestClient.get(...)
  ```
  ###
  ]]]

Conclusion

When we set out to build our new docs site, we wanted to make it easy for people to focus on the content, and not the structure. For us, using a set of open source tools and customizing them has been a successful approach. By implementing custom markdown, we’re able to generate great looking pages with modern content while keeping authors out of HTML/style land. For a real-world example of all of these styles, check out this page in our docs site.