16
votes

I'm using markdown for comments on my site and I want users to be able to create line breaks by pressing enter instead of space space enter (see this meta question for more details on this idea)

How can I do this in Ruby? You'd think Github Flavored Markdown would be exactly what I need, but (surprisingly), it's quite buggy.

Here's their implementation:

# in very clear cases, let newlines become <br /> tags
text.gsub!(/^[\w\<][^\n]*\n+/) do |x|
  x =~ /\n{2}/ ? x : (x.strip!; x << "  \n")
end

This logic requires that the line start with a \w for a linebreak at the end to create a <br>. The reason for this requirement is that you don't to mess with lists: (But see the edit below; I'm not even sure this makes sense)

* we don't want a <br>
* between these two list items

However, the logic breaks in these cases:

[some](http://google.com)
[links](http://google.com)
*this line is in italics*
another line
> the start of a blockquote!
another line

I.e., in all of these cases there should be a <br> at the end of the first line, and yet GFM doesn't add one

Oddly, this works correctly in the javascript version of GFM.

Does anyone have a working implementation of "new lines to <br>s" in Ruby?

Edit: It gets even more confusing!

If you check out Github's official Github Flavored Markdown repository, you'll find yet another newline to <br> regex!:

# in very clear cases, let newlines become <br /> tags
text.gsub!(/(\A|^$\n)(^\w[^\n]*\n)(^\w[^\n]*$)+/m) do |x|
  x.gsub(/^(.+)$/, "\\1  ")
end

I have no clue what this regex means, but it doesn't do any better on the above test cases.

Also, it doesn't look like the "don't mess with lists" justification for requiring that lines start with word characters is valid to begin with. I.e., standard markdown list semantics don't change regardless of whether you add 2 trailing spaces. Here:

  • item 1
  • item 2
  • item 3

In the source of this question there are 2 trailing spaces after "item 1", and yet if you look at the HTML, there is no superfluous <br>

This leads me to think the best regex for converting newlines to <br>s is just:

text.gsub!(/^[^\n]+\n+/) do |x|
  x =~ /\n{2}/ ? x : (x.strip!; x << "  \n")
end

Thoughts?

2
"Thoughts?" I think you forgot to include a question that clarified what cases you want to allow and what cases you want to avoid.Phrogz
Are the requirements here really unclear? Interpreting <br>s as newlines in markdown is a huge meme (it's been discussed extensively on meta, on github, on Coding Horror, etc). I don't know all the corner cases; I was more wondering whether anyone had a successful implementation of this ideaTom Lehman
Why can't you use markdown or textile directly?kikito
@egarcia Because markdown doesn't interpret newlines as <br>s (you need to do SPACE SPACE ENTER to create a <br> in standard Markdown)Tom Lehman
Have you examined the scripts of the demonstration page? That JavaScript version uses Showdown (from attacklab.net/showdown ), which is a port of the Perl version of Markdown. It's substantially different from the Ruby code that GitHub provided. I notice that WMD, the WYSIWYM Markdown Editor (also from Attacklab.net ), has the logic problems you describe.IIsi 50MHz

2 Answers

2
votes

I'm not sure if this will help, but I just use simple_format() from ActionView::Helpers::TextHelper

ActionView simple_format

my_text = "Here is some basic text...\n...with a line break."

simple_format(my_text)

output => "<p>Here is some basic text...\n<br />...with a line break.</p>"

Even if it doesn't meet your specs, looking at the simple_format() source code .gsub! methods might help you out writing your own version of required markdown.

2
votes

A little too late, but perhaps useful for other people. I've gotten it to work (but not thoroughly tested) by preprocessing the text using regular expressions, like so. It's hideous as a result of the lack of zero-width lookbehinds, but oh well.

# Append two spaces to a simple line, if it ends in newline, to render the
# markdown properly. Note: do not do this for lists, instead insert two newlines. Also, leave double newlines
# alone.
text.gsub! /^ ([\*\+\-]\s+|\d+\s+)? (.+?) (\ \ )? \r?\n (\r?\n|[\*\+\-]\s+|\d+\s+)? /xi do
  full, pre, line, spaces, post = $~.to_a
  if post != "\n" && pre.blank? && post.blank? && spaces.blank?
    "#{pre}#{line}  \n#{post}"
  elsif pre.present? || post.present?
    "#{pre}#{line}\n\n#{post}"
  else
    full
  end
end