Working With Markdown in Ruby
Honeybadger Staff
Posted on August 25, 2023
This article was originally written by Aestimo Kirina on the Honeybadger Developer Blog.
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 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 to create a simple documentation app. You can clone the example app's source code from here.
Now let's get to it.
Parsing Markdown Using the Redcarpet Gem
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
Why is Ruby awesome?
- It's fun to use
- Easy to learn
- And so much more...
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 ```
```.
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
Input
### 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!
Posted on August 25, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024