Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Create a GFM (Github Flavored Markdown) parser #68

Closed
wants to merge 4 commits into from

9 participants

@plexus

As a first step, it supports "fenced code blocks" delimited by three or more
backticks.

I am aware that this has been discussed in #15, #31, #53, and #64, and that the general concensus is that using tildes is a more standard and portable approach.

However in #53 it is also hinted that a pull request for a separate parser for this specific variety of Markdown might be accepted. And since the popularity of GFM is not decreasing, I would also argue it's a very useful feature to have.

This PR adds a GFM parser, including tests and docs.

Kramdown::Document.new(text, :input => 'GFM')

I am testing the water with this PR, to see what the current sentiments are regarding GFM. The parser currently only implements a single extra feature, but if this is deemed acceptable then at least it creates a place to expand GFM conformance. I'll be happy to respond to remarks as to make this worthy of merging.

@plexus plexus Create a GFM (Github Flavored Markdown) parser
As a first step, it supports "fenced code blocks" delimited by three or more
backticks.
5a02866
@plexus

Wow... that's a bit strong. In #31 @gettalong wrote

Fork kramdown on github, implement the github-markdown parser based on the kramdown parser (you may not know, but kramdown supports multiple parsers, not just the default kramdown parser), send me a pull request and I will have a look at it.

So that's all I'm doing.

@yaauie yaauie commented on the diff
lib/kramdown/parser/gfm.rb
((5 lines not shown))
+ def initialize(source, options)
+ super
+ @block_parsers.unshift(:gfm_codeblock_fenced)
+ end
+
+ GFM_FENCED_CODEBLOCK_START = /^`{3,}/
+ #GFM_FENCED_CODEBLOCK_MATCH = /^(`{3,})\s*?(\w+)?\s*?\n(.*?)^\1`*\s*?\n/m
+ GFM_FENCED_CODEBLOCK_MATCH = /^`{3,} *(\w+)?\s*?\n(.*?)^`{3,}\s*?\n/m
+
+ # Parse the fenced codeblock at the current location.
+ def parse_gfm_codeblock_fenced
+ if @src.check(GFM_FENCED_CODEBLOCK_MATCH)
+ @src.pos += @src.matched_size
+ el = new_block_el(:codeblock, @src[2])
+ lang = @src[1].to_s.strip
+ el.attr['class'] = "language-#{lang}" unless lang.empty?
@yaauie
yaauie added a note

The "language-#{lang}" doesn't seem to be covered in your tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@gettalong gettalong was assigned
@gettalong
Owner

I will have a look at the implementation.

@akshatpradhan

+1 for this feature

@yaauie

:-1:

@svnpenn would you mind being constructive with your feedback? Why don't you think this contribution should be accepted?

@svnpenn

@yaauie,

@gettalong said it best, maybe people could stop opening duplicate issues.

As for the inclusion of another code block fence style: this may not really be
necessary since the only other player using this is Github itself and nobody
else...

and

The problem is that Github just invented the ``` syntax out of no apparent reason, they could have just as easily gone with the ~~~ syntax that is used by PHP Markdown Extra, python-markdown, pandoc, ... Some of this implementations just bowed to Github and implemented this other syntax but I don't see any reason to add another syntax variation.

and

If you want to create compatible docs just use ~~~ because this works in
nearly ALL markdown parsers that allow fenced code blocks.

and

If you desire portability, use the tilde version which is implemented in most
versions.

@yaauie

@svnpenn I appreciate you taking your time to dig those back up, but the context here is different – those were in response to trying to cram GFM synatx into the Kramdown parser; @gettalong specifically suggested adding GFM as a new parser based on the kramdown parser, which is what this pull-request does. It provides an additional option without cluttering up the prestine kramdown parser.

@yaauie

@gettalong wrote

One more thing: If you really want to use the Github syntax, the best option would be to create a "github-markdown" parser based on the kramdown parser (similar to the Markdown parser). This would allow for any options you may need to ensure compatibility with github flavored markdown (like github style fenced code blocks and hard
line breaks).

And later clarified:

You misunderstood me... Fork kramdown on github, implement the github-markdown parser based on the kramdown parser (you may not know, but kramdown supports multiple parsers, not just the default kramdown parser), send me a pull request and I will have a look at it.

I never said that you should fork kramdown and release a separate library. The only thing I said is that I don't like to implement this in the default kramdown parser.

@plexus

I would appreciate if someone with merge access to this repo could say if a GFM based parser could be considered in principle. If not just say "thanks, but no thanks" and close this PR. I'm fine with that. I just ended up needing this to work on existing GFM documents, and decided to contribute it back so people don't have to reinvent the wheel.

To clarify : Kramdown already has parsers for two different dialects of Markdown, the Markdown parser and the Kramdown parser. This PR adds a new one, it does not impact any existing uses. It is a separate, opt-in alternative.

As for creating another "issue", this is a Pull Request, a contribution of working code. It shows up as a new issue because that's how github works. It is an invitation to review and discuss the code so that it can be made better.

@yaauie thank you for your constructive criticism. I'll look into it as soon as the author lets us know if this PR stands a chance.

@svnpenn how come you deleted the remarks you made earlier?

@gettalong
Owner

Below are my 2c...

In general adding new syntax to kramdown or changing existing syntax at this stage is not so easy anymore because people tend to rely on "a minor update won't break my texts".

And as stated multiple times I don't want to include the the "fenced code block with backtick" syntax in the main kramdown parser. Creating new issues when it has been clearly said that this won't be implemented is just a waste of all of our time.

However, if someone wants to implement a parser based on the kramdown parser with different functionality, that is fine by me. So @plexus: This pull request is fine by me.

@plexus

I completely agree that changing the original Markdown/Kramdown parsers at this point is a bad idea. It's unfortunate that these two things (adding backtick syntax to the old parser vs creating a new parser) have gotten mixed up in a single discussion.

I added a test case for class="language=#{lang}", and now also added the GFM specific behavior where line breaks in paragraphs get turned in to actual <br> tags.

Because of this a lot of the test cases are no longer valid for the GFM parser since the output now differs. I solved this by adding a ".gfm" extension, so that file will be tested against if it exists. Although I've only done this for one test case at the moment, and added another one to explicitly test the line breaks. The other ones are currently excluded from the gfm-to-html tests.

I ran the tests on MRI 1.8.7, 1.9.3, 2.0.0 and jruby-1.7.3 and they all run green.

@yaauie yaauie commented on the diff
test/testcases/block/06_codeblock/backticks_syntax.html
((4 lines not shown))
+<pre><code>Four backticks
+</code></pre>
+
+<pre><code>Unbalanced bottom heavy
+</code></pre>
+
+<pre><code>Unbalanced top heavy
+</code></pre>
+
+<div><div class="CodeRay">
+ <div class="code"><pre><span class="line-numbers"><a href="#n1" name="n1">1</a></span>language no space
+</pre></div>
+</div>
+</div>
+
+<div><div class="CodeRay">
@yaauie
yaauie added a note

@plexus I'm not seeing the ruby lang reference reflected in the output.

@gettalong Owner

This is because Coderay is enabled and at work. Using kramdown --no-enable-coderay you will get the following:

<pre><code class="language-ruby">language no space
</code></pre>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
test/test_files.rb
((43 lines not shown))
+ '/home/arne/github/kramdown/test/testcases/span/01_link/reference.text',
+ '/home/arne/github/kramdown/test/testcases/span/02_emphasis/normal.text',
+ '/home/arne/github/kramdown/test/testcases/span/03_codespan/normal.text',
+ '/home/arne/github/kramdown/test/testcases/span/04_footnote/definitions.text',
+ '/home/arne/github/kramdown/test/testcases/span/04_footnote/markers.text',
+ '/home/arne/github/kramdown/test/testcases/span/05_html/across_lines.text',
+ '/home/arne/github/kramdown/test/testcases/span/05_html/markdown_attr.text',
+ '/home/arne/github/kramdown/test/testcases/span/05_html/normal.text',
+ '/home/arne/github/kramdown/test/testcases/span/autolinks/url_links.text',
+ '/home/arne/github/kramdown/test/testcases/span/extension/comment.text',
+ '/home/arne/github/kramdown/test/testcases/span/ial/simple.text',
+ '/home/arne/github/kramdown/test/testcases/span/line_breaks/normal.text',
+ '/home/arne/github/kramdown/test/testcases/span/text_substitutions/entities_as_char.text',
+ '/home/arne/github/kramdown/test/testcases/span/text_substitutions/entities.text',
+ '/home/arne/github/kramdown/test/testcases/span/text_substitutions/typography.text'
+ ]
@gettalong Owner

@plexus The full path should not be contained, ie. strip /home/arne/github/kramdown/ from the paths.

@plexus
plexus added a note

Sorry, I missed that. I took it out now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@trans

I am so glad to see work done on this! Please merge.

Providing this feature should allow Smeagol to run properly on Rubinus and JRuby.

@plexus

Can this go in as it is, or are there still things I should improve first? Just checking if something is still in the way of this being merged. Thanks!

@lhazlewood

Huge +1 for this feature.

While I understand that GitHub is the one who invented it, GitHub's Markdown is the predominant Markdown flavor and more people know it better than any other flavor of Markdown.

Please incorporate this feature as a pragmatic solution - it will make many people happy!

@gettalong
Owner

I have merged your changes and updated the implementation to avoid too much code duplication.

Anything else to do?

@plexus
@gettalong gettalong closed this
@konklone

I've opened jekyll/jekyll#1791 to update the Jekyll docs with the existence of this option.

Notably though, this feature doesn't do any syntax-specific highlighting (e.g. Pygments) itself, it just surrounds the <code> block with a class named after the language. I recognize that could be considered out of scope, but this is something that Maruku does, and looks to be a dealbreaker for me.

It looks like in #24 the answer was, "write a plugin". @navarroj did that over at krampygs. If there's interest in merging that into core as an option, I bet @navarroj or myself could be persuaded to prepare such a PR.

@plexus

Syntax highlighting in Kramdown is done with coderay. If you set the enable_coderay option to true you should get HTML with actual syntax highlighting.

@konklone

Yeah, you're right, I realized that and should have been more precise in my comment. But Pygments seems simpler, like it has more of a future, and would make Kramdown easier to switch to from a Redcarpet or Maruku based site (which is where I am now).

@gettalong
Owner

@konklone There will be plugable syntax highlighting in the future so that rouge can also be used (rouge can use pygments stylesheets but is comparable in speed to coderay, so much faster than any pygments solution).

@svnpenn svnpenn referenced this pull request in jneen/rouge
Closed

Kramdown support #118

@jneen

Neat! I'm happy to add a plugin for Kramdown (similar to the existing one for [redcarpet][]) if there's a stable integration point. I've seen some code floating around that subclasses Kramdown::Converters::Html and overrides #convert_codeblock - is there a different way you envision pluggable highlighting?

@gettalong
Owner

I would like to add an option that can be used to specify the syntax highlighter per converter. And another option for setting syntax highlighter specific data, like the current coderay* options. This would also allow, for example, minted to be used with the LaTeX converter.

@konklone

Both sound like great ideas to me.

@plexus plexus deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 22, 2013
  1. @plexus

    Create a GFM (Github Flavored Markdown) parser

    plexus authored
    As a first step, it supports "fenced code blocks" delimited by three or more
    backticks.
Commits on Jul 31, 2013
  1. @plexus
  2. @plexus
Commits on Aug 2, 2013
  1. @plexus
This page is out of date. Refresh to see the latest.
View
12 doc/parser/gfm.page
@@ -0,0 +1,12 @@
+---
+title: GFM Parser
+---
+# GFM Parser
+
+## Introduction
+
+Parse ["Github Flavored Markdown"](https://help.github.com/articles/github-flavored-markdown). This is a format of Markdown that is used on Github.com for most places where textual input is required, such as issues and comments. Some of the extensions, notably the "backtick fenced code blocks" are also used on other sites, for example StackOverflow.
+
+## Conformance
+
+At the moment this parser is identical to the Kramdown parser, except that it adds support for fenced code blocks using three or more backticks to delimit the block.
View
1  lib/kramdown/parser.rb
@@ -20,6 +20,7 @@ module Parser
autoload :Kramdown, 'kramdown/parser/kramdown'
autoload :Html, 'kramdown/parser/html'
autoload :Markdown, 'kramdown/parser/markdown'
+ autoload :GFM, 'kramdown/parser/gfm'
end
View
47 lib/kramdown/parser/gfm.rb
@@ -0,0 +1,47 @@
+module Kramdown
+ module Parser
+ class GFM < Kramdown::Parser::Kramdown
+
+ def initialize(source, options)
+ super
+ @block_parsers.unshift(:gfm_codeblock_fenced)
+ end
+
+ GFM_FENCED_CODEBLOCK_START = /^`{3,}/
+ GFM_FENCED_CODEBLOCK_MATCH = /^`{3,} *(\w+)?\s*?\n(.*?)^`{3,}\s*?\n/m
+
+ # Parse the fenced codeblock at the current location.
+ def parse_gfm_codeblock_fenced
+ if @src.check(GFM_FENCED_CODEBLOCK_MATCH)
+ @src.pos += @src.matched_size
+ el = new_block_el(:codeblock, @src[2])
+ lang = @src[1].to_s.strip
+ el.attr['class'] = "language-#{lang}" unless lang.empty?
@yaauie
yaauie added a note

The "language-#{lang}" doesn't seem to be covered in your tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ @tree.children << el
+ true
+ else
+ false
+ end
+ end
+ define_parser(:gfm_codeblock_fenced, GFM_FENCED_CODEBLOCK_START)
+
+ def parse_paragraph
+ result = @src.scan(PARAGRAPH_MATCH)
+ while !@src.match?(self.class::PARAGRAPH_END)
+ result << @src.scan(PARAGRAPH_MATCH)
+ end
+ result.chomp!
+ unless @tree.children.last && @tree.children.last.type == :p
+ @tree.children << new_block_el(:p)
+ end
+ lines = result.lstrip.each_line.map {|line| Element.new(@text_type, line) }
+ @tree.children.last.children << lines.first
+ lines.drop(1).each do |line|
+ @tree.children.last.children << Element.new(:br) << line
+ end
+ true
+ end
+
+ end
+ end
+end
View
87 test/test_files.rb
@@ -15,8 +15,14 @@
Encoding.default_external = 'utf-8' if RUBY_VERSION >= '1.9'
class TestFiles < Test::Unit::TestCase
+ GFM_TEXT_FILES = [
+ 'test/testcases/block/06_codeblock/backticks_syntax.text',
+ 'test/testcases/block/06_codeblock/backticks_disable_highlighting.text',
+ 'test/testcases/block/03_paragraph/two_para_hard_line_breaks.text'
+ ]
EXCLUDE_KD_FILES = [('test/testcases/block/04_header/with_auto_ids.text' if RUBY_VERSION <= '1.8.6'), # bc of dep stringex not working
+ *GFM_TEXT_FILES
].compact
# Generate test methods for kramdown-to-xxx conversion
@@ -48,6 +54,7 @@ class TestFiles < Test::Unit::TestCase
'test/testcases/span/03_codespan/highlighting.html', # bc of span elements inside code element
'test/testcases/block/04_header/with_auto_ids.html', # bc of auto_ids=true option
'test/testcases/block/04_header/header_type_offset.html', # bc of header_offset option
+ *GFM_TEXT_FILES.map {|f| f.sub(/text$/, 'html')}
]
Dir[File.dirname(__FILE__) + '/testcases/**/*.{html,html.19,htmlinput,htmlinput.19}'].each do |html_file|
next if EXCLUDE_HTML_FILES.any? {|f| html_file =~ /#{f}(\.19)?$/}
@@ -85,6 +92,7 @@ def tidy_output(out)
EXCLUDE_LATEX_FILES = ['test/testcases/span/01_link/image_in_a.text', # bc of image link
'test/testcases/span/01_link/imagelinks.text', # bc of image links
'test/testcases/span/04_footnote/markers.text', # bc of footnote in header
+ *GFM_TEXT_FILES
]
Dir[File.dirname(__FILE__) + '/testcases/**/*.text'].each do |text_file|
next if EXCLUDE_LATEX_FILES.any? {|f| text_file =~ /#{f}$/}
@@ -121,6 +129,7 @@ def tidy_output(out)
'test/testcases/span/extension/comment.text', # bc of comment text modifications (can this be avoided?)
'test/testcases/block/04_header/header_type_offset.text', # bc of header_offset being applied twice
('test/testcases/block/04_header/with_auto_ids.text' if RUBY_VERSION <= '1.8.6'), # bc of dep stringex not working
+ *GFM_TEXT_FILES
].compact
Dir[File.dirname(__FILE__) + '/testcases/**/*.text'].each do |text_file|
next if EXCLUDE_TEXT_FILES.any? {|f| text_file =~ /#{f}$/}
@@ -155,6 +164,7 @@ def tidy_output(out)
'test/testcases/block/04_header/header_type_offset.html', # bc of header_offset option
'test/testcases/block/16_toc/toc_exclude.html', # bc of different attribute ordering
'test/testcases/span/autolinks/url_links.html', # bc of quot entity being converted to char
+ *GFM_TEXT_FILES.map {|f| f.sub(/text$/, 'html')}
]
Dir[File.dirname(__FILE__) + '/testcases/**/*.{html,html.19}'].each do |html_file|
next if EXCLUDE_HTML_KD_FILES.any? {|f| html_file =~ /#{f}(\.19)?$/}
@@ -171,6 +181,83 @@ def tidy_output(out)
end
end
+ EXCLUDE_GFM_FILES = [
+ 'test/testcases/block/03_paragraph/no_newline_at_end.text',
+ 'test/testcases/block/03_paragraph/two_para.text',
+ 'test/testcases/block/04_header/atx_header.text',
+ 'test/testcases/block/04_header/setext_header.text',
+ 'test/testcases/block/05_blockquote/indented.text',
+ 'test/testcases/block/05_blockquote/lazy.text',
+ 'test/testcases/block/05_blockquote/nested.text',
+ 'test/testcases/block/05_blockquote/no_newline_at_end.text',
+ 'test/testcases/block/06_codeblock/error.text',
+ 'test/testcases/block/07_horizontal_rule/error.text',
+ 'test/testcases/block/08_list/escaping.text',
+ 'test/testcases/block/08_list/item_ial.text',
+ 'test/testcases/block/08_list/lazy.text',
+ 'test/testcases/block/08_list/list_and_others.text',
+ 'test/testcases/block/08_list/other_first_element.text',
+ 'test/testcases/block/08_list/simple_ul.text',
+ 'test/testcases/block/08_list/special_cases.text',
+ 'test/testcases/block/09_html/comment.text',
+ 'test/testcases/block/09_html/html_to_native/code.text',
+ 'test/testcases/block/09_html/html_to_native/emphasis.text',
+ 'test/testcases/block/09_html/html_to_native/typography.text',
+ 'test/testcases/block/09_html/parse_as_raw.text',
+ 'test/testcases/block/09_html/simple.text',
+ 'test/testcases/block/12_extension/comment.text',
+ 'test/testcases/block/12_extension/ignored.text',
+ 'test/testcases/block/12_extension/nomarkdown.text',
+ 'test/testcases/block/13_definition_list/item_ial.text',
+ 'test/testcases/block/13_definition_list/multiple_terms.text',
+ 'test/testcases/block/13_definition_list/no_def_list.text',
+ 'test/testcases/block/13_definition_list/simple.text',
+ 'test/testcases/block/13_definition_list/with_blocks.text',
+ 'test/testcases/block/14_table/errors.text',
+ 'test/testcases/block/14_table/escaping.text',
+ 'test/testcases/block/14_table/simple.text',
+ 'test/testcases/block/15_math/normal.text',
+ 'test/testcases/encoding.text',
+ 'test/testcases/span/01_link/inline.text',
+ 'test/testcases/span/01_link/link_defs.text',
+ 'test/testcases/span/01_link/reference.text',
+ 'test/testcases/span/02_emphasis/normal.text',
+ 'test/testcases/span/03_codespan/normal.text',
+ 'test/testcases/span/04_footnote/definitions.text',
+ 'test/testcases/span/04_footnote/markers.text',
+ 'test/testcases/span/05_html/across_lines.text',
+ 'test/testcases/span/05_html/markdown_attr.text',
+ 'test/testcases/span/05_html/normal.text',
+ 'test/testcases/span/autolinks/url_links.text',
+ 'test/testcases/span/extension/comment.text',
+ 'test/testcases/span/ial/simple.text',
+ 'test/testcases/span/line_breaks/normal.text',
+ 'test/testcases/span/text_substitutions/entities_as_char.text',
+ 'test/testcases/span/text_substitutions/entities.text',
+ 'test/testcases/span/text_substitutions/typography.text'
+ ]
+
+ # Generate test methods for kramdown-to-gfm conversion
+ Dir[File.dirname(__FILE__) + '/testcases/**/*.text'].each do |text_file|
+ next if EXCLUDE_GFM_FILES.any? {|f| text_file =~ /#{f}$/}
+ basename = text_file.sub(/\.text$/, '')
+
+ html_file = [
+ ".html.gfm",
+ (".html.19" if RUBY_VERSION >= '1.9'),
+ ".html"
+ ].compact.
+ map {|ext| basename + ext }.
+ detect {|file| File.exist?(file) }
+
+ define_method('test_gfm_' + text_file.tr('.', '_') + "_to_html") do
+ opts_file = basename + '.options'
+ opts_file = File.join(File.dirname(html_file), 'options') if !File.exist?(opts_file)
+ options = File.exist?(opts_file) ? YAML::load(File.read(opts_file)) : {:auto_ids => false, :footnote_nr => 1}
+ doc = Kramdown::Document.new(File.read(text_file), options.merge(:input => 'GFM'))
+ assert_equal(File.read(html_file), doc.to_html)
+ end
+ end
# Generate test methods for asserting that converters don't modify the document tree.
View
18 test/testcases/block/03_paragraph/indented.html.gfm
@@ -0,0 +1,18 @@
+<p>This is a para.</p>
+
+<p>This is a para.</p>
+
+<p>This is a para.</p>
+
+<p>This is a para.</p>
+
+<pre><code>This is a code block.
+</code></pre>
+
+<p>And this is another.</p>
+
+<p>A para
+<br /> with
+<br /> mixed
+<br />indents.
+<br /> and with much indent</p>
View
4 test/testcases/block/03_paragraph/two_para_hard_line_breaks.html
@@ -0,0 +1,4 @@
+<p>This is just a normal paragraph.
+<br />Containing a line break.</p>
+
+<p>Another paragraph.</p>
View
4 test/testcases/block/03_paragraph/two_para_hard_line_breaks.text
@@ -0,0 +1,4 @@
+This is just a normal paragraph.
+Containing a line break.
+
+Another paragraph.
View
4 test/testcases/block/03_paragraph/two_para_hard_line_breaks_gfm.html
@@ -0,0 +1,4 @@
+<p>This is just a normal paragraph.
+<br />Containing a line break.</p>
+
+<p>Another paragraph.</p>
View
2  test/testcases/block/06_codeblock/backticks_disable_highlighting.html
@@ -0,0 +1,2 @@
+<pre><code class="language-ruby">Kramdown::Document.new(text, :input =&gt; 'GFM')
+</code></pre>
View
1  test/testcases/block/06_codeblock/backticks_disable_highlighting.options
@@ -0,0 +1 @@
+:enable_coderay: false
View
3  test/testcases/block/06_codeblock/backticks_disable_highlighting.text
@@ -0,0 +1,3 @@
+```ruby
+Kramdown::Document.new(text, :input => 'GFM')
+```
View
23 test/testcases/block/06_codeblock/backticks_syntax.html
@@ -0,0 +1,23 @@
+<pre><code>Three backticks
+</code></pre>
+
+<pre><code>Four backticks
+</code></pre>
+
+<pre><code>Unbalanced bottom heavy
+</code></pre>
+
+<pre><code>Unbalanced top heavy
+</code></pre>
+
+<div><div class="CodeRay">
+ <div class="code"><pre><span class="line-numbers"><a href="#n1" name="n1">1</a></span>language no space
+</pre></div>
+</div>
+</div>
+
+<div><div class="CodeRay">
@yaauie
yaauie added a note

@plexus I'm not seeing the ruby lang reference reflected in the output.

@gettalong Owner

This is because Coderay is enabled and at work. Using kramdown --no-enable-coderay you will get the following:

<pre><code class="language-ruby">language no space
</code></pre>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ <div class="code"><pre><span class="line-numbers"><a href="#n1" name="n1">1</a></span>language with space
+</pre></div>
+</div>
+</div>
View
23 test/testcases/block/06_codeblock/backticks_syntax.text
@@ -0,0 +1,23 @@
+```
+Three backticks
+```
+
+````
+Four backticks
+````
+
+```
+Unbalanced bottom heavy
+``````
+
+``````
+Unbalanced top heavy
+````
+
+````ruby
+language no space
+````
+
+```` ruby
+language with space
+````
Something went wrong with that request. Please try again.