Nathan Long
Nathan Long

Reputation: 126072

How can I do Github-style Markdown rendering from Sinatra?

TL;DR - how can I use something like improved_markdown :some_file to do custom rendering, but still render the layout as usual?


Normally, to render Markdown in Sinatra, you'd just do:

markdown :some_file

But I'd like to add the ability to do "fenced" syntax highlighting, like you can do in Github README files.

```ruby
class Foo
  # etc
end
```

I've got this partially working.

First, I installed Redcarpet and added a custom rendering class that uses Pygments.rb for syntax highlighting:

# create a custom renderer that allows highlighting of code blocks
class HTMLwithPygments < Redcarpet::Render::HTML
  def block_code(code, language)
    Pygments.highlight(code, lexer: language)
  end
end

Then I used it in a route, like this:

# Try to load any Markdown file specified in the URL
get '/*' do
  viewname = params[:splat].first

  if File.exist?("views/#{viewname}.md")

    # Uses my custom rendering class
    # The :fenced_code_blocks option means it will take, for example,
    # the word 'ruby' from ```ruby and pass that as the language
    # argument to my block_code method above  
    markdown_renderer = Redcarpet::Markdown.new(HTMLwithPygments, :fenced_code_blocks => true)

    file_contents = File.read("views/#{viewname}.md")
    markdown_renderer.render(file_contents)

  else
    "Nopers, I can't find it."
  end
end

This almost works. The Markdown is rendered as HTML with additional markup for highlighting purposes.

The only problem is that it does not use my layout; after all, I'm just reading a file and returning the rendered string. The normal markdown :foo call would involve Tilt in the process.

Do I have to create a custom Tilt template engine to get that to work, or is there an easier way?

Upvotes: 4

Views: 3350

Answers (3)

three
three

Reputation: 8478

You can use zzak's Glorify gem for that kind of job.

Upvotes: 0

Nathan Long
Nathan Long

Reputation: 126072

A workaround

I'm currently doing this:

if File.exist?("views/#{viewname}.md") 
  CustomMarkdown.render(File.read("views/#{viewname}.md"))

Which uses:

module CustomMarkdown
  def self.render(markdown_string)
    content = renderer.render(markdown_string)
    layout.render { content }
  end
  def self.renderer
    @markdown_renderer ||= Redcarpet::Markdown.new(HTMLwithPygments, :fenced_code_blocks => true)
  end
  def self.layout
    # Yes, this is hardcoded; in my simple app, I always use this layout.
    Tilt['haml'].new do
      File.read("views/layout.haml")
    end
  end
end

This works decently. I have noticed that a fenced block without a keyword, like this:

```
  code of unspecified type
```
# vs
```ruby
  explicitly ruby code
```

... causes my Sinatra app to crash. I assume this means there's an error in the Python layer, because I can't catch any raised error myself.

Update: the crash happens with rackup config.ru, but not when I'm using Passenger.

Upvotes: 0

matt
matt

Reputation: 79783

You can pass arbitrary options to the markdown method (or any of the other rendering methods) and they will be passed on to the Tilt template in question. Tilt’s Redcarpet template looks for any provided :renderer option when creating it’s renderer, allowing you to specify a custom one.

You can also specify options that should be applied to all markdown calls by passing them as the second argument to set :markdown, :option => :value.

It’s not quite as simply as that though, since the current (released) version of Tilt doesn’t correctly detect if you have Redcarpet 2 installed. You can tell it explicitly though:

# first ensure we're using the right Redcarpet version
Tilt.register Tilt::RedcarpetTemplate::Redcarpet2, 'markdown', 'mkd', 'md'

# set the appropriate options for markdown
set :markdown, :renderer => HTMLwithPygments,
  :fenced_code_blocks => true, :layout_engine => :haml

Now any call to markdown should use your custom code for code blocks, and will use layout.haml as the layout.

(Disclaimer: I couldn’t get Pygments working (it causes Sinatra to crash every time), but everything else here works (I used a simple custom block_code method that just added a message so I could tell it was working).

Upvotes: 3

Related Questions