mdl 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.travis.yml +7 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +22 -0
- data/README.md +79 -0
- data/Rakefile +8 -0
- data/bin/mdl +10 -0
- data/docs/RULES.md +609 -0
- data/docs/creating_rules.md +83 -0
- data/docs/creating_styles.md +47 -0
- data/example/markdown_spec.md +897 -0
- data/lib/mdl.rb +72 -0
- data/lib/mdl/cli.rb +89 -0
- data/lib/mdl/config.rb +9 -0
- data/lib/mdl/doc.rb +252 -0
- data/lib/mdl/kramdown_parser.rb +29 -0
- data/lib/mdl/rules.rb +393 -0
- data/lib/mdl/ruleset.rb +47 -0
- data/lib/mdl/style.rb +50 -0
- data/lib/mdl/styles/all.rb +1 -0
- data/lib/mdl/styles/cirosantilli.rb +6 -0
- data/lib/mdl/styles/default.rb +1 -0
- data/lib/mdl/styles/relaxed.rb +6 -0
- data/lib/mdl/version.rb +3 -0
- data/mdl.gemspec +31 -0
- data/test/rule_tests/atx_closed_header_spacing.md +17 -0
- data/test/rule_tests/atx_header_spacing.md +5 -0
- data/test/rule_tests/blockquote_blank_lines.md +31 -0
- data/test/rule_tests/blockquote_spaces.md +21 -0
- data/test/rule_tests/bulleted_list_2_space_indent.md +6 -0
- data/test/rule_tests/bulleted_list_2_space_indent_style.rb +2 -0
- data/test/rule_tests/bulleted_list_4_space_indent.md +3 -0
- data/test/rule_tests/bulleted_list_not_at_beginning_of_line.md +14 -0
- data/test/rule_tests/code_block_dollar.md +22 -0
- data/test/rule_tests/consecutive_blank_lines.md +11 -0
- data/test/rule_tests/consistent_bullet_styles_asterisk.md +3 -0
- data/test/rule_tests/consistent_bullet_styles_dash.md +3 -0
- data/test/rule_tests/consistent_bullet_styles_plus.md +3 -0
- data/test/rule_tests/empty_doc.md +0 -0
- data/test/rule_tests/fenced_code_blocks.md +21 -0
- data/test/rule_tests/first_header_bad_atx.md +1 -0
- data/test/rule_tests/first_header_bad_setext.md +2 -0
- data/test/rule_tests/first_header_good_atx.md +1 -0
- data/test/rule_tests/first_header_good_setext.md +2 -0
- data/test/rule_tests/header_duplicate_content.md +11 -0
- data/test/rule_tests/header_multiple_toplevel.md +3 -0
- data/test/rule_tests/header_mutliple_h1_no_toplevel.md +5 -0
- data/test/rule_tests/header_trailing_punctuation.md +11 -0
- data/test/rule_tests/header_trailing_punctuation_customized.md +14 -0
- data/test/rule_tests/header_trailing_punctuation_customized_style.rb +2 -0
- data/test/rule_tests/headers_bad.md +7 -0
- data/test/rule_tests/headers_good.md +5 -0
- data/test/rule_tests/headers_surrounding_space_atx.md +9 -0
- data/test/rule_tests/headers_surrounding_space_setext.md +15 -0
- data/test/rule_tests/headers_with_spaces_at_the_beginning.md +9 -0
- data/test/rule_tests/inconsistent_bullet_indent_same_level.md +4 -0
- data/test/rule_tests/inconsistent_bullet_styles_asterisk.md +3 -0
- data/test/rule_tests/inconsistent_bullet_styles_dash.md +3 -0
- data/test/rule_tests/inconsistent_bullet_styles_plus.md +3 -0
- data/test/rule_tests/incorrect_bullet_style_asterisk.md +3 -0
- data/test/rule_tests/incorrect_bullet_style_asterisk_style.rb +2 -0
- data/test/rule_tests/incorrect_bullet_style_dash.md +3 -0
- data/test/rule_tests/incorrect_bullet_style_dash_style.rb +2 -0
- data/test/rule_tests/incorrect_bullet_style_plus.md +3 -0
- data/test/rule_tests/incorrect_bullet_style_plus_style.rb +2 -0
- data/test/rule_tests/incorrect_header_atx.md +6 -0
- data/test/rule_tests/incorrect_header_atx_closed.md +6 -0
- data/test/rule_tests/incorrect_header_atx_closed_style.rb +2 -0
- data/test/rule_tests/incorrect_header_atx_style.rb +2 -0
- data/test/rule_tests/incorrect_header_setext.md +6 -0
- data/test/rule_tests/incorrect_header_setext_style.rb +2 -0
- data/test/rule_tests/long_lines.md +3 -0
- data/test/rule_tests/long_lines_100.md +7 -0
- data/test/rule_tests/long_lines_100_style.rb +2 -0
- data/test/rule_tests/mixed_header_types_atx.md +6 -0
- data/test/rule_tests/mixed_header_types_atx_closed.md +6 -0
- data/test/rule_tests/mixed_header_types_setext.md +6 -0
- data/test/rule_tests/ordered_list_item_prefix.md +13 -0
- data/test/rule_tests/ordered_list_item_prefix_ordered.md +13 -0
- data/test/rule_tests/ordered_list_item_prefix_ordered_style.rb +2 -0
- data/test/rule_tests/reversed_link.md +7 -0
- data/test/rule_tests/spaces_after_list_marker.md +74 -0
- data/test/rule_tests/spaces_after_list_marker_style.rb +3 -0
- data/test/rule_tests/whitespace issues.md +3 -0
- data/test/setup_tests.rb +5 -0
- data/test/test_ruledocs.rb +45 -0
- data/test/test_rules.rb +56 -0
- data/tools/README.md +3 -0
- data/tools/test_location.rb +20 -0
- data/tools/view_markdown.rb +11 -0
- metadata +314 -0
data/lib/mdl.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'mdl/cli'
|
2
|
+
require 'mdl/config'
|
3
|
+
require 'mdl/doc'
|
4
|
+
require 'mdl/kramdown_parser'
|
5
|
+
require 'mdl/ruleset'
|
6
|
+
require 'mdl/style'
|
7
|
+
require 'mdl/version'
|
8
|
+
|
9
|
+
require 'kramdown'
|
10
|
+
|
11
|
+
module MarkdownLint
|
12
|
+
def self.run
|
13
|
+
cli = MarkdownLint::CLI.new
|
14
|
+
cli.run
|
15
|
+
rules = RuleSet.load_default
|
16
|
+
style = Style.load(Config[:style], rules)
|
17
|
+
# Rule option filter
|
18
|
+
rules.select! {|r| Config[:rules].include?(r) } if Config[:rules]
|
19
|
+
# Tag option filter
|
20
|
+
rules.select! {|r, v| not (v.tags & Config[:tags]).empty? } if Config[:tags]
|
21
|
+
|
22
|
+
if Config[:list_rules]
|
23
|
+
puts "Enabled rules:"
|
24
|
+
rules.each do |id, rule|
|
25
|
+
if Config[:verbose]
|
26
|
+
puts "#{id} (#{rule.tags.join(', ')}) - #{rule.description}"
|
27
|
+
else
|
28
|
+
puts "#{id} - #{rule.description}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
exit 0
|
32
|
+
end
|
33
|
+
|
34
|
+
# Recurse into directories
|
35
|
+
cli.cli_arguments.each_with_index do |filename, i|
|
36
|
+
if Dir.exist?(filename)
|
37
|
+
pattern = "#{filename}/**/*.md" # This works for both Dir and ls-files
|
38
|
+
if Config[:git_recurse]
|
39
|
+
Dir.chdir(filename) do
|
40
|
+
cli.cli_arguments[i] = %x(git ls-files '*.md').split("\n")
|
41
|
+
end
|
42
|
+
else
|
43
|
+
cli.cli_arguments[i] = Dir["#{filename}/**/*.md"]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
cli.cli_arguments.flatten!
|
48
|
+
|
49
|
+
status = 0
|
50
|
+
cli.cli_arguments.each do |filename|
|
51
|
+
puts "Checking #{filename}..." if Config[:verbose]
|
52
|
+
doc = Doc.new_from_file(filename)
|
53
|
+
filename = '(stdin)' if filename == "-"
|
54
|
+
if Config[:show_kramdown_warnings]
|
55
|
+
status = 2 if not doc.parsed.warnings.empty?
|
56
|
+
doc.parsed.warnings.each do |w|
|
57
|
+
puts "#{filename}: Kramdown Warning: #{w}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
rules.sort.each do |id, rule|
|
61
|
+
puts "Processing rule #{id}" if Config[:verbose]
|
62
|
+
error_lines = rule.check.call(doc)
|
63
|
+
next if error_lines.nil? or error_lines.empty?
|
64
|
+
status = 1
|
65
|
+
error_lines.each do |line|
|
66
|
+
puts "#{filename}:#{line}: #{id} #{rule.description}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
exit status
|
71
|
+
end
|
72
|
+
end
|
data/lib/mdl/cli.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'mixlib/cli'
|
2
|
+
|
3
|
+
module MarkdownLint
|
4
|
+
class CLI
|
5
|
+
include Mixlib::CLI
|
6
|
+
|
7
|
+
banner "Usage: #{File.basename($0)} [options] [FILE.md|DIR ...]"
|
8
|
+
|
9
|
+
option :config_file,
|
10
|
+
:short => '-c',
|
11
|
+
:long => '--config FILE',
|
12
|
+
:description => 'The configuration file to use',
|
13
|
+
:default => '~/.mdlrc'
|
14
|
+
|
15
|
+
option :verbose,
|
16
|
+
:short => '-v',
|
17
|
+
:long => '--[no-]verbose',
|
18
|
+
:description => 'Increase verbosity',
|
19
|
+
:boolean => true
|
20
|
+
|
21
|
+
option :show_kramdown_warnings,
|
22
|
+
:short => '-w',
|
23
|
+
:long => '--[no-]warnings',
|
24
|
+
:description => 'Show kramdown warnings',
|
25
|
+
:boolean => true
|
26
|
+
|
27
|
+
option :tags,
|
28
|
+
:short => '-t',
|
29
|
+
:long => '--tags TAG1,TAG2',
|
30
|
+
:description => 'Only process rules with these tags',
|
31
|
+
:proc => Proc.new { |v| v.split(',').map { |t| t.to_sym } }
|
32
|
+
|
33
|
+
option :rules,
|
34
|
+
:short => '-r',
|
35
|
+
:long => '--rules RULE1,RULE2',
|
36
|
+
:description => 'Only process these rules',
|
37
|
+
:proc => Proc.new { |v| v.split(',') }
|
38
|
+
|
39
|
+
option :style,
|
40
|
+
:short => '-s',
|
41
|
+
:long => '--style STYLE',
|
42
|
+
:description => "Load the given style"
|
43
|
+
|
44
|
+
option :list_rules,
|
45
|
+
:short => '-l',
|
46
|
+
:long => '--list-rules',
|
47
|
+
:boolean => true,
|
48
|
+
:description => "Don't process any files, just list enabled rules"
|
49
|
+
|
50
|
+
option :git_recurse,
|
51
|
+
:short => '-g',
|
52
|
+
:long => '--git-recurse',
|
53
|
+
:boolean => true,
|
54
|
+
:description => "Only process files known to git when given a directory"
|
55
|
+
|
56
|
+
option :help,
|
57
|
+
:on => :tail,
|
58
|
+
:short => '-h',
|
59
|
+
:long => '--help',
|
60
|
+
:description => 'Show this message',
|
61
|
+
:boolean => true,
|
62
|
+
:show_options => true,
|
63
|
+
:exit => 0
|
64
|
+
|
65
|
+
option :version,
|
66
|
+
:on => :tail,
|
67
|
+
:short => "-V",
|
68
|
+
:long => "--version",
|
69
|
+
:description => "Show version",
|
70
|
+
:boolean => true,
|
71
|
+
:proc => Proc.new { puts MarkdownLint::VERSION },
|
72
|
+
:exit => 0
|
73
|
+
|
74
|
+
def run(argv=ARGV)
|
75
|
+
parse_options(argv)
|
76
|
+
# Load the config file if it's present
|
77
|
+
filename = File.expand_path(config[:config_file])
|
78
|
+
MarkdownLint::Config.from_file(filename) if File.exists?(filename)
|
79
|
+
|
80
|
+
# Put values in the config file
|
81
|
+
MarkdownLint::Config.merge!(config)
|
82
|
+
|
83
|
+
# Read from stdin if we didn't provide a filename
|
84
|
+
if cli_arguments.empty? and not config[:list_rules]
|
85
|
+
cli_arguments << "-"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/mdl/config.rb
ADDED
data/lib/mdl/doc.rb
ADDED
@@ -0,0 +1,252 @@
|
|
1
|
+
require 'kramdown'
|
2
|
+
require 'mdl/kramdown_parser'
|
3
|
+
|
4
|
+
module MarkdownLint
|
5
|
+
##
|
6
|
+
# Representation of the markdown document passed to rule checks
|
7
|
+
|
8
|
+
class Doc
|
9
|
+
##
|
10
|
+
# A list of raw markdown source lines. Note that the list is 0-indexed,
|
11
|
+
# while line numbers in the parsed source are 1-indexed, so you need to
|
12
|
+
# subtract 1 from a line number to get the correct line. The element_line*
|
13
|
+
# methods take care of this for you.
|
14
|
+
|
15
|
+
attr_reader :lines
|
16
|
+
|
17
|
+
##
|
18
|
+
# A Kramdown::Document object containing the parsed markdown document.
|
19
|
+
|
20
|
+
attr_reader :parsed
|
21
|
+
|
22
|
+
##
|
23
|
+
# A list of top level Kramdown::Element objects from the parsed document.
|
24
|
+
|
25
|
+
attr_reader :elements
|
26
|
+
|
27
|
+
##
|
28
|
+
# Create a new document given a string containing the markdown source
|
29
|
+
|
30
|
+
def initialize(text)
|
31
|
+
# Workaround for the following two issues:
|
32
|
+
# https://github.com/mivok/markdownlint/issues/52
|
33
|
+
# https://github.com/gettalong/kramdown/issues/158
|
34
|
+
# Unfortunately this forces all input text back into ascii, which may
|
35
|
+
# be problematic for any rules that make use of non-ascii characters, so
|
36
|
+
# we should remove this if it no longer becomes necessary to do so.
|
37
|
+
text.encode!("ASCII", invalid: :replace, undef: :replace, replace: '')
|
38
|
+
|
39
|
+
@lines = text.split("\n")
|
40
|
+
@parsed = Kramdown::Document.new(text, :input => 'MarkdownLint')
|
41
|
+
@elements = @parsed.root.children
|
42
|
+
add_levels(@elements)
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Alternate 'constructor' passing in a filename
|
47
|
+
|
48
|
+
def self.new_from_file(filename)
|
49
|
+
if filename == "-"
|
50
|
+
self.new(STDIN.read)
|
51
|
+
else
|
52
|
+
self.new(File.read(filename))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Find all elements of a given type, returning their options hash. The
|
58
|
+
# options hash has most of the useful data about an element and often you
|
59
|
+
# can just use this in your rules.
|
60
|
+
#
|
61
|
+
# # Returns [ { :location => 1, :element_level => 2 }, ... ]
|
62
|
+
# elements = find_type(:li)
|
63
|
+
#
|
64
|
+
# If +nested+ is set to false, this returns only top level elements of a
|
65
|
+
# given type.
|
66
|
+
|
67
|
+
def find_type(type, nested=true)
|
68
|
+
find_type_elements(type, nested).map { |e| e.options }
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Find all elements of a given type, returning a list of the element
|
73
|
+
# objects themselves.
|
74
|
+
#
|
75
|
+
# Instead of a single type, a list of types can be provided instead to
|
76
|
+
# find all types.
|
77
|
+
#
|
78
|
+
# If +nested+ is set to false, this returns only top level elements of a
|
79
|
+
# given type.
|
80
|
+
|
81
|
+
def find_type_elements(type, nested=true, elements=@elements)
|
82
|
+
results = []
|
83
|
+
if type.class == Symbol
|
84
|
+
type = [type]
|
85
|
+
end
|
86
|
+
elements.each do |e|
|
87
|
+
results.push(e) if type.include?(e.type)
|
88
|
+
if nested and not e.children.empty?
|
89
|
+
results.concat(find_type_elements(type, nested, e.children))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
results
|
93
|
+
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# Returns the line number a given element is located on in the source
|
97
|
+
# file. You can pass in either an element object or an options hash here.
|
98
|
+
|
99
|
+
def element_linenumber(element)
|
100
|
+
element = element.options if element.is_a?(Kramdown::Element)
|
101
|
+
element[:location]
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Returns the actual source line for a given element. You can pass in an
|
106
|
+
# element object or an options hash here. This is useful if you need to
|
107
|
+
# examine the source line directly for your rule to make use of
|
108
|
+
# information that isn't present in the parsed document.
|
109
|
+
|
110
|
+
def element_line(element)
|
111
|
+
@lines[element_linenumber(element) - 1]
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Returns a list of line numbers for all elements passed in. You can pass
|
116
|
+
# in a list of element objects or a list of options hashes here.
|
117
|
+
|
118
|
+
def element_linenumbers(elements)
|
119
|
+
elements.map { |e| element_linenumber(e) }
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# Returns the actual source lines for a list of elements. You can pass in
|
124
|
+
# a list of elements objects or a list of options hashes here.
|
125
|
+
|
126
|
+
def element_lines(elements)
|
127
|
+
elements.map { |e| element_line(e) }
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# Returns the header 'style' - :atx (hashes at the beginning), :atx_closed
|
132
|
+
# (atx header style, but with hashes at the end of the line also), :setext
|
133
|
+
# (underlined). You can pass in the element object or an options hash
|
134
|
+
# here.
|
135
|
+
|
136
|
+
def header_style(header)
|
137
|
+
if header.type != :header
|
138
|
+
raise "header_style called with non-header element"
|
139
|
+
end
|
140
|
+
line = element_line(header)
|
141
|
+
if line.start_with?("#")
|
142
|
+
if line.strip.end_with?("#")
|
143
|
+
:atx_closed
|
144
|
+
else
|
145
|
+
:atx
|
146
|
+
end
|
147
|
+
else
|
148
|
+
:setext
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
##
|
153
|
+
# Returns the list style for a list: :asterisk, :plus, :dash, :ordered or
|
154
|
+
# :ordered_paren depending on which symbol is used to denote the list
|
155
|
+
# item. You can pass in either the element itself or an options hash here.
|
156
|
+
|
157
|
+
def list_style(item)
|
158
|
+
if item.type != :li
|
159
|
+
raise "list_style called with non-list element"
|
160
|
+
end
|
161
|
+
line = element_line(item).strip
|
162
|
+
if line.start_with?("*")
|
163
|
+
:asterisk
|
164
|
+
elsif line.start_with?("+")
|
165
|
+
:plus
|
166
|
+
elsif line.start_with?("-")
|
167
|
+
:dash
|
168
|
+
elsif line.match("[0-9]+\.")
|
169
|
+
:ordered
|
170
|
+
elsif line.match("[0-9]+\)")
|
171
|
+
:ordered_paren
|
172
|
+
else
|
173
|
+
:unknown
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Returns how much a given line is indented. Hard tabs are treated as an
|
179
|
+
# indent of 8 spaces. You need to pass in the raw string here.
|
180
|
+
|
181
|
+
def indent_for(line)
|
182
|
+
return line.match(/^\s*/)[0].gsub("\t", " " * 8).length
|
183
|
+
end
|
184
|
+
|
185
|
+
##
|
186
|
+
# Returns line numbers for lines that match the given regular expression
|
187
|
+
|
188
|
+
def matching_lines(re)
|
189
|
+
@lines.each_with_index.select{|text, linenum| re.match(text)}.map{
|
190
|
+
|i| i[1]+1}
|
191
|
+
end
|
192
|
+
|
193
|
+
##
|
194
|
+
# Returns line numbers for lines that match the given regular expression.
|
195
|
+
# Only considers text inside of 'text' elements (i.e. regular markdown
|
196
|
+
# text and not code/links or other elements).
|
197
|
+
def matching_text_element_lines(re)
|
198
|
+
matches = []
|
199
|
+
find_type_elements(:text).each do |e|
|
200
|
+
first_line = e.options[:location]
|
201
|
+
lines = e.value.split("\n")
|
202
|
+
lines.each_with_index do |l, i|
|
203
|
+
matches << first_line + i if re.match(l)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
matches
|
207
|
+
end
|
208
|
+
|
209
|
+
##
|
210
|
+
# Extracts the text from an element whose children consist of text
|
211
|
+
# elements and other things
|
212
|
+
|
213
|
+
def extract_text(element, prefix="")
|
214
|
+
quotes = {
|
215
|
+
:rdquo => '"',
|
216
|
+
:ldquo => '"',
|
217
|
+
:lsquo => "'",
|
218
|
+
:rsquo => "'"
|
219
|
+
}
|
220
|
+
# If anything goes amiss here, e.g. unknown type, then nil will be
|
221
|
+
# returned and we'll just not catch that part of the text, which seems
|
222
|
+
# like a sensible failure mode.
|
223
|
+
lines = element.children.map { |e|
|
224
|
+
if e.type == :text
|
225
|
+
e.value
|
226
|
+
elsif [:strong, :em, :p].include?(e.type)
|
227
|
+
extract_text(e, prefix).join("\n")
|
228
|
+
elsif e.type == :smart_quote
|
229
|
+
quotes[e.value]
|
230
|
+
end
|
231
|
+
}.join.split("\n")
|
232
|
+
# Text blocks have whitespace stripped, so we need to add it back in at
|
233
|
+
# the beginning. Because this might be in something like a blockquote,
|
234
|
+
# we optionally strip off a prefix given to the function.
|
235
|
+
lines[0] = element_line(element).sub(prefix, "")
|
236
|
+
lines
|
237
|
+
end
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
##
|
242
|
+
# Adds a 'level' option to all elements to show how nested they are
|
243
|
+
|
244
|
+
def add_levels(elements, level=1)
|
245
|
+
elements.each do |e|
|
246
|
+
e.options[:element_level] = level
|
247
|
+
add_levels(e.children, level+1)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Modified version of the kramdown parser to add in features/changes
|
2
|
+
# appropriate for markdownlint, but which don't make sense to try to put
|
3
|
+
# upstream.
|
4
|
+
require 'kramdown/parser/gfm'
|
5
|
+
|
6
|
+
module Kramdown
|
7
|
+
module Parser
|
8
|
+
class MarkdownLint < Kramdown::Parser::Kramdown
|
9
|
+
|
10
|
+
def initialize(source, options)
|
11
|
+
super
|
12
|
+
i = @block_parsers.index(:codeblock_fenced)
|
13
|
+
@block_parsers.delete(:codeblock_fenced)
|
14
|
+
@block_parsers.insert(i, :codeblock_fenced_gfm)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Add location information to text elements
|
18
|
+
def add_text(text, tree = @tree, type = @text_type)
|
19
|
+
super
|
20
|
+
if tree.children.last
|
21
|
+
tree.children.last.options[:location] = @src.current_line_number
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Regular kramdown parser, but with GFM style fenced code blocks
|
26
|
+
FENCED_CODEBLOCK_MATCH = Kramdown::Parser::GFM::FENCED_CODEBLOCK_MATCH
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|