How do I implement Rouge syntax highlighting in Rails? - ruby-on-rails

There are a bunch of tutorials floating around, but they seem to be incomplete or not fully current or don't fully work for me.
This is what I have done.
Gemfile:
gem 'rouge'
gem 'redcarpet'
Then I created a config/initializer/rouge.rb:
require 'rouge/plugins/redcarpet'
Then I created a file called app/assets/stylesheets/rouge.css.erb
<%= Rouge::Themes::Github.render(:scope => '.highlight') %>
Then in my app/helpers/application_helper.rb, I added this:
module ApplicationHelper
class HTML < Redcarpet::Render::HTML
include Rouge::Plugins::Redcarpet
def block_code(code, language)
Rouge.highlight(code, language || 'text', 'html')
end
end
def markdown(text)
render_options = {
filter_html: true,
hard_wrap: true,
link_attributes: { rel: 'nofollow' }
}
renderer = HTML.new(render_options)
extensions = {
autolink: true,
fenced_code_blocks: true,
lax_spacing: true,
no_intra_emphasis: true,
strikethrough: true,
superscript: true
}
Redcarpet::Markdown.new(renderer, extensions).render(text).html_safe
end
end
Then in my show.html.erb, I did this:
<%= markdown(#question.body) %>
But that literally does not work. It outputs my ruby code snippet like this:
How do I get this snippet of code to be formatted like Github? Or even just the first step being to be formatted any at all, then how do I tweak the formatting?
I don't see a stylesheet included in the source of the page, so I don't know which styles to tweak for what I want.
Edit 1
Or even when I do this:
<div class="highlight">
<%= #question.test_suite %>
</div>
It renders like this:
Edit 2
I attempted BoraMa's suggestion and I got output that looks like this:
Edit 3
I made a modification to BoraMa's answer as follows.
In my block_code method, I call highlight as follows:
Rouge.highlight(code, 'ruby', 'html')
Then in my view I do this:
<%= raw rouge_markdown(<<-'EOF'
def rouge_me
puts "this is a #{'test'} for rouge"
end
EOF
) %>
Then that produces this:
Note I am referring to the code snippet at the bottom of the screenshot.
However, the text at the top is generated with this:
<pre class="highlight ruby">
<%= rouge_markdown(#question.body) %>
</pre>
And it is rendered as is shown in the screenshot.
Edit 4
After removing the <div class="highlight">, I see this:
Aka....nothing is being rendered at all.
Once I add raw to my view...aka <%= raw rouge_markdown(#question.body) %>
The view renders this:
Edit 5
Here is the content for various #question objects:
[1] pry(#<#<Class:0x007fc041b97ce8>>)> #question.body
=> "5.times do\r\n puts \"Herro Rerl!\"\r\nend"
[1] pry(#<#<Class:0x007fc041b97ce8>>)> #question.body
=> "puts \"Hello World version 9\"\r\nputs \"This comes after version 8.\"\r\nputs \"This comes after version 7.\"\r\nputs \"This comes after version 6.\"\r\nputs \"This comes after version 5.\"\r\nputs \"This comes after version 4.\"\r\nputs \"This comes after version 3.\"\r\nputs \"This comes after version 2.\"\r\nputs \"This definitely comes after version 1.\""
[1] pry(#<#<Class:0x007fc041b97ce8>>)> #question.body
=> "def convert_relation(invited_gender, relation)\r\n case invited_gender\r\n \twhen \"male\"\r\n \tcase relation\r\n when \"daughter\", \"son\" then \"dad\"\r\n when \"mom\", \"dad\" then \"son\"\r\n when \"grandfather\", \"grandmother\" then \"grandson\"\r\n when \"sister\", \"brother\" then \"brother\"\r\n when \"wife\" then \"husband\"\r\n when \"husband\" then \"husband\"\r\n end\r\n when \"female\"\r\n \tcase relation\r\n when \"daughter\", \"son\" then \"mom\"\r\n when \"mom\", \"dad\" then \"daughter\"\r\n when \"grandfather\", \"grandmother\" then \"granddaughter\"\r\n when \"sister\", \"brother\" then \"sister\"\r\n when \"wife\" then \"wife\"\r\n when \"husband\" then \"wife\"\r\n end\r\n end\r\nend\r\n\r\nputs convert_relation(\"male\", \"wife\")"

The original question indicated (in the solution attempted) that markdown would be used in the highlighted questions but it turned out not to be the case. So this answer is split to two distinct sections, one for highlighting pure code without markdown, the other one for markdown text with code.
A) You want to highlight pure code (no Markdown involved)
In this case, and according to the README, all you need to highlight the code with Rouge is a lexer and a formatter. Since the highlighted text will be displayed on a web page, you need the HTML formatter. For the lexer, you need to know the language the code is in beforehand (or you may try guessing it from the source code itself but it does not seem to be very reliable for small code snippets).
You can create a simple helper method for the highlighting:
module RougeHelper
def rouge(text, language)
formatter = Rouge::Formatters::HTML.new(css_class: 'highlight')
lexer = Rouge::Lexer.find(language)
formatter.format(lexer.lex(text))
end
end
Then in the template, simply call this helper with a text to highlight and the language:
<%= raw rouge("def rouge_me\n puts 'hey!'\nend", "ruby") %>
Which will render:
To get a list of all languages that Rouge supports and their corresponding names that should be passed to the rouge helper, you can use the following code. The code gets all defined lexers from Rouge and shows their tags (i.e. the names Rouge recognizes them with):
Rouge::Lexer.all.map(&:tag).sort
# => ["actionscript", "apache", "apiblueprint", "applescript", ..., "xml", "yaml"]
You can (and probably should) use this list when showing users the languages to choose from in the selection box. Note that each lexer also has the title and desc methods defined that will give you a human-readable name and a short description of each of them. You might want to use this info to be shown to the user, too.
Note: you should get rid of the initializer, the custom HTML class and the div wrapped around the rouge helper call (all of these you have in your original attempt). The only thing you need besides the code above is the CSS rules, which you have already correctly included in the web page.
B) The highlighted text is a Markdown text with code blocks
A couple of changes from your attempt to make it work:
The initializer is not needed, you can remove it I think (but if you don't want to require all the files later in the helper, I guess you can leave it).
Remove the block_code method from the helper class, the same is already done by including the markdown plugin.
Remove the <div class="highlight"> wrapper div from your template and just use the helper in it. Rouge adds its own wrapper with the "highlight" class and another div seems to confuse it.
Try the following helper code. BTW, I moved the code from ApplicationHelper to a separate RougeHelper (but that is not a required change):
module RougeHelper
require 'redcarpet'
require 'rouge'
require 'rouge/plugins/redcarpet'
class HTML < Redcarpet::Render::HTML
include Rouge::Plugins::Redcarpet
end
def rouge_markdown(text)
render_options = {
filter_html: true,
hard_wrap: true,
link_attributes: { rel: 'nofollow' }
}
renderer = HTML.new(render_options)
extensions = {
autolink: true,
fenced_code_blocks: true,
lax_spacing: true,
no_intra_emphasis: true,
strikethrough: true,
superscript: true
}
markdown = Redcarpet::Markdown.new(renderer, extensions)
markdown.render(text)
end
end
Then, in the template, I tried to highlight a test ruby code:
<%= raw rouge_markdown(<<-'EOF'
```ruby
def rouge_me
puts "this is a #{'test'} for rouge"
end
```
EOF
) %>
Note that I needed to specify the language manually, which made me use the 3 backticks way to delimit the code instead of space indentation. I have no clue why the code language autodetection did not work here, perhaps it's a too short code.
In the end this rendered the colors for me nicely:

Related

Rails: Redcarpet markdown and auto_html conflict

I am working on an app that allows users to input youtube videos, images, tweets, etc. To accomplish that I used auto_html(https://github.com/dejan/auto_html) and the code works fine. I am now trying to implement redcarpet markdown but whenever I use the function "markdown" (which I defined myself), auto_html stops working.
Here is the code for redcarpet (in the helper file):
module ApplicationHelper
def markdown(text)
markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, hard_wrap: true, autolink: true, quote: true, fenced_code_blocks: true, strikethrough: true)
return markdown.render(text).html_safe
end
end
Here is the code for auto_html (in the Msg Model):
class Msg < ActiveRecord::Base
auto_html_for :content do
html_escape
image
twitter
vimeo
youtube(:width => 575, :height => 300, :autoplay => false)
soundcloud
link :target => "_blank", :rel => "nofollow"
simple_format
end
end
This is the view:
<p><%= markdown(msg.content_html) %></p>
where msg.content is users' input in text form, and msg.content_html applies auto_html filters and transforms an input URL to its format (image, video, etc).
I got auto_html and markdown working separately. If I leave the code in the view as above, my auto_html loads fine but markdown doesn't work. If I suppress the "_html" from msg.content, markdown works.
Any ideas how to go around this? Am I missing anything?

Is it a security risk to allow the following HTML elements (e.g. code, pre)?

I'm using the following plugin: https://github.com/jhollingworth/bootstrap-wysihtml5/
This is how I'm sanitizing my input/outputs in my Rails app:
post.rb:
protected
def clean_input
self.content = sanitize(self.content, :tags => %w(b i u br p span blockquote pre code), :attributes => %w(id class style))
end
posts/show.html.rb:
<p><%= sanitize #post.content, :tags => %w(b i u p span br blockquote pre code), :attributes => %w(id class style) %></p>
This parser rules for wysihtml5 (of course, the editor is allowing tags like b, i, etc. as default):
shared/editor_toolbar:
parserRules: {
classes: {
"ruby": 1,
"variable": 1,
"string": 1
},
tags: {
span: {},
code: {},
pre: {}
}
},
So, right now the user can input and the app can output something like this:
<pre class="ruby">
<code>
<span class="variable">
$(</span><span class="string">'.wysihtml5'</span>).wysihtml5({<span class=
"string">'stylesheets'</span>: false});
</code>
</pre>
(The user can switch from visual and html view)
I hope this is not a stupid question (I'm not very familiar with security), but is this relatively safe or dangerous? If so, how to prevent it?
I really don't know about Ruby, but in PHP you can allow tags like that and from what I've experimented, it's NOT secure at all... The reason why is because attributes on these authorized tags are not sanitised so any user could input a very gentle and inoffensive <span></span> tag but adding this to it :
<span onmouseover="hack_the_whole_fucking_website();">contenthere</span>
This way, the JavaScript will be executed when a user move his mouse over it ! From there I guess an hacked could steal user's cookies + steal Session cookie + hijack users Sessions + maybe hijack an admin session and then explode your website. It's an open door for hackers.
The solution I use for this is BBcode tags. They are kind of "substitutes" for existing HTML tags. Some examples :
<i> = [i]
<img src="#"> = [img=#]
text = [url=#]text[/url]
...
The output of the editor should be in this format so you can run a sanitizing script that properly delete all real HTML tags. And then when it's time to output this data to the user, you replace these replacement tags by the real HTML tag using some regular expressions. :)

Empty attribute with Ruby HAML

I'm implementing Schema microformats on a Ruby project using HAML and can't figure out how to set an empty attribute on a tag. I tried nil and false, but they simply do not shown.
Example: <div itemscope>
I'm tring to set an empty itemscope attribute.
Code added from comment by #StrangeElement:
My code:
.agency.premium{:itemscope => true, :itemtype => 'schema.org/ProfessionalService';}
:itemscope => true seems to be the recommended approach from HAML's documentation. I get the same result as I would get with :itemscope => '', a XHTML-valid attribute with an empty value (i.e. <div itemscope="">).
Probably fine, but I'd rather have it empty as is documented in the Schema doc.
Using something like
%div{:itemscope => true}
is the correct way to specify this in your Haml file.
How this is rendered depends on how you set Haml's format option. The default in Haml 3.1 is xhtml, and with that it will render as itemprop='itemprop', which is valid xhtml. To render with minimized attributes (like <div itemscope>) you need to set the format to html4 or html5. (In Rails 3 the default is html5, and in Haml 4.0 the default is html5).
How to set the Haml options depends on how you are using it, see the options section in the docs.
For example, using Haml directly in Ruby, this:
engine = Haml::Engine.new '%div{:itemscope => true}'
puts engine.render
produces the default xhtml with full attributes:
<div itemscope='itemscope'></div>
But this:
engine = Haml::Engine.new '%div{:itemscope => true}', :format => :html5
puts engine.render
produces the desired result with minimized attributes:
<div itemscope></div>
If someone is interested in how to put more words in that way, he may use "foo bar" => true:
%option{ "disabled selected value" => true } Choose an option
results is:
<option disabled="" selected="" value="">Choose an option</option>
and works as expected.
The accepted answer works, but it produces an HTML attribute with value.
If you want the attribute only to be output on HTML, without value, you can use the HTML-style attributes syntax of HAML:
%div(itemscope)

Forcing "humanized" names of fields to be lowercase in Rails 3

As far as I know, the accepted way to set the "humanized" names of fields in Rails 3 is to use locales:
# config/locales/en.yml
en:
activerecord:
attributes:
member:
username: 'username' # rather than 'Username'
However, I simply want Rails 3 to use lowercase versions of its default humanized names. Is there an easy, built-in way to do this?
An example to help clarify: When inside of a form_for, <%= f.label :username %> displays "Username" by default. I want it to display "username".
I had the same problem.
I solved it via css:
In foo.html.erb:
<%= f.label :username, :class => "my_class" %>
In bar.css:
label.my_class {
text-transform: lowercase;
}
I would prefer a different solution, too. But that's the only one I've been able to find, so far.
The label helper defaults to using human_attribute_name to turn an attribute into a human name. If you look at the source, human_attribute_name tries a few things before falling back to attributes.to_s.humanize. It tries the translation first, and then it looks for a :default option in the options hash.
So, the simplest/best way to get the functionality you want is to override human_attribute_name with your own that provides a :default option and then calls the original. Rails provides a reasonable way to do this sort of thing with alias_method_chain, so...
I've heard enough, just give me the answer!
Put the following in any file in config/initializers and restart your app:
module ActiveModel
module Translation
def human_attribute_name_with_foo attribute, options = {}
human_attribute_name_without_foo attribute, options.merge( :default => attribute.humanize.downcase )
end
alias_method_chain :human_attribute_name, :foo
end
end

How do I create a more meaningful error message in this case?

Say I have the following model:
class Information < ActiveRecord::Base
...
validates_length_of :name, :minimum=>3, :message=>"should be longer than 3 characters!"
...
What I want to have as an error is:
Information should be longer than 3 characters! (or similar)
and NOT "Information name should be longer than 3 characters!".
Two possible workarounds I've looked at:
human_attribute_name method (mentioned here): doesn't work with my Rails 2.3.2. :-(
directly do a information.errors.add "","..." if information.name.length < 3: however, this removes many useful properties triggered by the validated_length_of method like the special class-tags (for coloring the stuff red).
Any ideas? Thank you for your time.
I suppose that you display errors through full_messages method, which is meant for console, not for web application use. You should use error_message_on or error_messages_for helpers instead (see documentation for more info), which allow you to customize error messages.
Example:
<%= error_message_on "information", "name", :prepend_text => 'Information ' %>
don't use the rails helper to make the errors, normally i have inline errors so something like:
def inline_error_block(obj, meth, prepend="", append="", klass="error error-form", &block)
content = capture(&block)
obj = (obj.respond_to?(:errors) ? obj : instance_variable_get("##{obj}"))
if obj
errors = obj.errors.on(meth.to_s)
if errors
output = content_tag(:div, :class => klass) do
content_tag(:p, "#{prepend}#{errors.is_a?(Array) ? errors.first : errors}#{append}", :class => "error-msg clearfix") + content
end
return concat(output)
end
end
concat(content_tag(:div, content, :class => "no-error"))
end
tends to do the trick, but, it only shows one error per form field, am sure you could rearrange it to show them all, should you need to! (errors.first to errors.each).
To get the full name, just write the message with the field name as you want it displayed:
validates_length_of :name, :minimum=>3, :message=>"Information should be longer than 3 characters!"
You could always set your :message to an empty string in the model then set the :prepend_text in the view to whatever you like.

Resources