4

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?

Nathan Long
  • 122,748
  • 97
  • 336
  • 451

3 Answers3

3

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

matt
  • 78,533
  • 8
  • 163
  • 197
  • Perfect! Not sure if you're getting the same crash I describe in my answer, but I found that it happens if I'm using `rackup config.ru`, but not if I'm running on Passenger. – Nathan Long Aug 03 '12 at 14:20
  • @NathanLong I’ve solved my crashing issue – a bit of googling brought up the suggestion that it’s something to do with the initialization of Python in a background thread that causes it, and a call to `Pygments.start` at start up should solve it. Sure enough, after adding that line Pygments is working ok. – matt Aug 03 '12 at 20:03
0

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.

Nathan Long
  • 122,748
  • 97
  • 336
  • 451
0

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

three
  • 8,262
  • 3
  • 35
  • 39
  • You can use it if you want syntax highlighted code blocks and nothing else. But despite what it says, it's not a Markdown renderer, but an RDoc renderer, which isn't the same thing at all (it's useless for anything other than documenting Ruby for instance) – user2261892 Jun 26 '13 at 12:14
  • rdoc does mardown too these days but I haven't checked if the gem is capable of that too. – three Jun 26 '13 at 12:49
  • I recently found this discussion and have to say I am pretty disappointed with glorify at this point. Having troubles setting up github flavored markdown. not sure who is still using RDoc but if thats what you need then use glorify – Dan Bradbury Aug 06 '14 at 18:32