Working With Markdown in Ruby
Published: 2023-08-25 16:17:41

*This article was originally written by [Aestimo Kirina](https://www.honeybadger.io/blog/authors/aestimonjuguna/) on the [Honeybadger Developer Blog](https://www.honeybadger.io/blog/ruby-markdown/).*

Imagine that you and your colleagues are working on a cool new project at work. Everyone's churning out code and firing on all cylinders, and everything seems to be going well, but then you remember that documentation also needs to get done for the project.

What do you choose? HTML could work, but it feels a bit clunky having to write all those tags. What about word processors or something like Google Docs? Well, these are fine, but for this project, you'd prefer something that's as close to your code as possible.

Thus, what should you use? A perfect (or near-perfect) documentation tool meets the following criteria:

- Has simple syntax that most of your team can learn to use and thereby more easily contribute to the project's documentation.
- Is as close to your project's code base as possible.
- Is readable in raw or rendered format.
- Has a small file footprint.
- Is easily managed using git tooling.
- Can be rendered into popular publishing formats, such as HTML and PDF.

Markdown meets these criteria.

## What Is Markdown?

[Markdown](https://en.wikipedia.org/wiki/Markdown) is a simple markup language with the file extension ```.md``. It was created by John Gruber and Aaron Swartz in 2004 with the goal of making a format that was readable in its source-code form.

## TL;DR

For the purposes of getting our project's documentation done, Markdown is perfect. In this article, you'll learn how to use two Ruby libraries to parse Markdown, and in a later section, we'll integrate one of the libraries with [Sinatra](https://sinatrarb.com/) to create a simple documentation app. You can clone the example app's source code from [here](https://github.com/iamaestimo/sinatra_redcarpet_app).

Now let's get to it.

## Parsing Markdown Using the Redcarpet Gem

[Redcarpet](https://github.com/vmg/redcarpet) is a Ruby library for processing Markdown. It's inbuilt renderers, one for outputting HTML and another for XHTML, are written in C, which makes it a very fast Markdown parser compared to other Ruby libraries.

### Installing Redcarpet

If you have Ruby 1.9.2 or later, you can install the latest version (as of writing this article) of Redcarpet by running  ```gem install redcarpet -v 3.3.4```.

The heart of the library is its ```Redcarpet::Markdown``` class, which is used to parse the Markdown document you feed it and then use its attached renderer to do the HTML output.

It's recommended that you instantiate the ```Redcarpet::Markdown``` class once and then reuse it as needed.

### Using Redcarpet

Initialize it as follows:

```ruby
parser = Redcarpet::Markdown.new(renderer, extensions = {})
```

As you'll notice, the ```Redcarpet::Markdown``` class accepts two arguments; the first is the renderer you prefer (for the purposes of this tutorial, we'll default to the HTML renderer ```Redcarpet::Render::HTML```), and the second argument is a hash of options.

Let's start with a simple example using the default renderer without specifying any options:

```ruby
markdown_text = <<-'TEXT'
Why is Ruby *awesome*?
- It's fun to use
- Easy to learn
- And so much more... 
TEXT

parser.render(markdown_text)
```

The following will be rendered:

```html
<p>Why is Ruby <em>awesome</em>?</p>
<ul>
    <li>It's fun to use</li>
    <li>Easy to learn</li>
    <li>And so much more...</li>
</ul>
```

The second argument in Redcarpet's class is a hash that accepts several options. Let's go over a few:

- ```:tables``` - Enables you to parse tables.
- ```:autolink``` - Generates autolinks for HTTP, HTTPS, FTP, and even email.
- ```:strikethrough``` - Lets you parse strikethrough; just use two ```~~``` to mark where the strikethrough starts.
- ```:footnotes``` - If you want to make a reference in your documentation, a footnote will do. Use a marker next to the text you'd like to reference in the footnote, ```Footnote worthy text[^1]```, and then the actual footnote text anywhere in your document using ```[^1]: Actual footnote```.

Using our previous example, here's how to include extensions:

```ruby
parser.render(markdown_text, tables: true, :footnotes: true)
```

It’s looking good so far, but Redcarpet packs much more punch under the hood. Later in the tutorial, we'll use the library to build a custom Markdown parser and use it in a Sinatra app.

In the meantime, let's turn our attention to another library, Kramdown.

## Kramdown

[Kramdown](https://github.com/gettalong/kramdown) is a pure Ruby parser that can parse several formats, including Markdown, HTML, and GitHub-flavored Markdown (GFM), and convert them into HTML, Kramdown, LaTex, and even PDF.

Installation is as easy as adding the latest version of the gem to your Gemfile, ```gem 'kramdown', '~> 1.11', '>= 1.11.1'```, or doing a direct install with ```gem install kramdown -v 1.11.1``` and then using its simple API:

```ruby
require 'kramdown'

Kramdown::Document.new(text_to_be_converted).to_html
```

Just like Redcarpet, Kramdown's new call can take two parameters. The first is the text to be converted, and the second is a hash of options, which will affect the output you'll get.

You can specify options like so:

```ruby
Kramdown::Document.new(source_text, {toc_levels: 1..3})
```

Check out the project's [documenation](https://kramdown.gettalong.org/documentation.html) for more information on Kramdown's advanced features. However, there's something almost every blog post and documentation page requires: a table of contents (ToC).

With Kramdown, you can easily generate one using your document's headers.

The table of contents can be generated as an ordered or unordered list. The example below generates a table of contents with items as an unordered list:

```markdown
# This header would be ignored by the ToC
{:.no_toc}

* This line is necessary but won't appear in the generated ToC
{:toc}

# H1 header

## H2 header
```

The one below gives us an ordered list table of contents:

```markdown
# This header would be ignored by the ToC
{:.no_toc}

1 This line is necessary but won't appear in the generated ToC
{:toc}

# H1 header

## H2 header
```

Great! We've highlighted two of the most popular Ruby Markdown parsing libraries: Redcarpet and Kramdown.

We'll now use one of these libraries to create something more functional - a simple Ruby and Markdown documentation app for our imaginary team.

## Ruby and Markdown Documentation App

To build our simple and fast Markdown documentation app, we'll use the Redcarpet gem within a Sinatra app.

### Install Sinatra

Sinatra is a stripped down Ruby framework that makes for a super-fast and flexible scripting tool for all sorts of interesting uses, such as making APIs and scraping spiders.

To get started, make sure to have Sinatra installed:

```ruby
gem install sinatra
gem install puma # optional
```

You can also clone the source code for this example from [here](https://github.com/iamaestimo/sinatra_redcarpet_app).

Switch to the project folder and run ```bundle install``` to get a fresh Gemfile.lock file on your development environment.

### The Main Class

```ruby
require 'sinatra'
require 'sinatra/reloader' if development?
require_relative './lib/helpers/custom_parser'

class Main < Sinatra::Application

# include custom MD parsing helper
helpers Sinatra::CustomParser

  get '/' do
    erb :index, layout: :layout
  end

  post '/parse_md' do
    input = params[:md_input]
    @input = md_parse(input)
    erb :parse_md, layout: :layout
  end

end
```

Our main class is the "brains" of the app. It includes a root route, a ```/markdown_output``` route where the parsed Markdown can be previewed, and, very importantly, a helper that will do the heavy lifting in terms of parsing Markdown.

### Inputting Markdown

We’ll include a simple form on the home page where a user can input some Markdown:

```html
<div class="container">
    <div>
        <h3>Input</h3>
        <form method='POST' action='/markdown_output'>
            <div class="mb-3">
                <textarea rows="10" class="form-control" name="md_input" placeholder="Enter your Markdown here"></textarea>
            </div>
            <input type="submit" class="btn btn-primary" value="Parse Markdown" />
        </form>
    </div>
</div>
```

### Markdown Parsing

Markdown parsing is handled by our special helper, ```lib/helpers/custom_parser.rb```.

It's good to highlight that although it's possible to define everything we need within the main class in Sinatra, separating our helper in this way ensures we have a well-organized app and creates a clean way for us to extend functionality when needed.

```ruby
require 'sinatra/base'
require 'redcarpet'


module Sinatra
  module CustomParser

    def convert_markdown(input)
      # define basic MD renderer
      renderer = Redcarpet::Render::HTML.new(hard_wrap: true)
      markdown = Redcarpet::Markdown.new(renderer, extensions = {})
      output = markdown.render(input)

      # return parsed output
      output
    end

  end

  helpers CustomParser

end
```

The custom helper includes a ```convert_markdown``` method that takes one argument: the input from the form in the homepage. Within this method, we define a new renderer, which is a basic Redcarpet HTML renderer with just one option for now, ```hard_wrap: true```.

With that, we have everything for our basic Markdown parser.

Now, to wrap up our tutorial, let's say we'd like to be able to include code syntax highlighting as part of our output. How can we do that?

### Adding Code Highlighting

For this, let's add the [Coderay gem](https://github.com/rubychan/coderay) into the mix. The [Rouge](https://github.com/rouge-ruby/rouge) gem could also be used to get the same effects.

Go ahead and add it to the app's Gemfile and run ```bundle```.

```ruby
# Gemfile
source "https://rubygems.org"

...
gem 'coderay'
```

We've modified our custom parser helper to include Coderay as follows:

```ruby
require 'sinatra/base'
require 'redcarpet'
require 'coderay'

module Sinatra
  module CustomParser

    class Markdownray < Redcarpet::Render::HTML
     def block_code(code, language)
       CodeRay.scan(code, language).div
     end
    end
   
     def convert_markdown(text)
       rndr = Markdownray.new(filter_html: true, hard_wrap: true)
       options = {
         fenced_code_blocks: true,
         no_intra_emphasis: true,
         autolink: true,
         lax_html_blocks: true
       }
       markdown_to_html = Redcarpet::Markdown.new(rndr, options)
       markdown_to_html.render(text)
     end
  end

  helpers CustomParser

end
```

Then, we use the ```convert_markdown``` method to convert the Markdown our users enter on the frontend form:

```ruby
post '/output' do
    input = params[:md_input]

    # process the input Markdown using Redcarpet and Coderay
    @output = convert_markdown(input)

    erb :markdown_output, layout: :layout
  end
```

With that, we now have a simple Ruby app capable of taking in Markdown and parsing it correctly with code highlighting included.

### A Quick Note

If you're keen enough, you'll notice we haven't implemented any HTML escaping, which might open you up to malicious code injection attacks. Unlike Rails, which comes with the handy ```html_escape```, Sinatra is a bare-bones framework. However, if you are considering expanding our example tutorial into something more production ready, consider using the excellent ```Rack::Utils``` module with its ```ESCAPE_HTML``` method and build out a helper to fix that problem.

## Conclusion

In this tutorial, we've learned how to process Markdown using two different Ruby libraries and built a small app to demonstrate the possibilities available to you.

There's so much more you could do with the combination of Ruby and Markdown. This is just a start; have fun!