Reputation: 126072
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
Reputation: 126072
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
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