mdl 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/rules.rb
ADDED
@@ -0,0 +1,393 @@
|
|
1
|
+
rule "MD001", "Header levels should only increment by one level at a time" do
|
2
|
+
tags :headers
|
3
|
+
check do |doc|
|
4
|
+
headers = doc.find_type(:header)
|
5
|
+
old_level = nil
|
6
|
+
errors = []
|
7
|
+
headers.each do |h|
|
8
|
+
if old_level and h[:level] > old_level + 1
|
9
|
+
errors << h[:location]
|
10
|
+
end
|
11
|
+
old_level = h[:level]
|
12
|
+
end
|
13
|
+
errors
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
rule "MD002", "First header should be a h1 header" do
|
18
|
+
tags :headers
|
19
|
+
check do |doc|
|
20
|
+
first_header = doc.find_type(:header).first
|
21
|
+
[first_header[:location]] if first_header and first_header[:level] != 1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
rule "MD003", "Header style" do
|
26
|
+
# Header styles are things like ### and adding underscores
|
27
|
+
# See http://daringfireball.net/projects/markdown/syntax#header
|
28
|
+
tags :headers
|
29
|
+
# :style can be one of :consistent, :atx, :atx_closed, :setext
|
30
|
+
params :style => :consistent
|
31
|
+
check do |doc|
|
32
|
+
headers = doc.find_type_elements(:header)
|
33
|
+
if headers.empty?
|
34
|
+
nil
|
35
|
+
else
|
36
|
+
if @params[:style] == :consistent
|
37
|
+
doc_style = doc.header_style(headers.first)
|
38
|
+
else
|
39
|
+
doc_style = @params[:style]
|
40
|
+
end
|
41
|
+
headers.map { |h| doc.element_linenumber(h) \
|
42
|
+
if doc.header_style(h) != doc_style }.compact
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
rule "MD004", "Unordered list style" do
|
48
|
+
tags :bullet, :ul
|
49
|
+
# :style can be one of :consistent, :asterisk, :plus, :dash
|
50
|
+
params :style => :consistent
|
51
|
+
check do |doc|
|
52
|
+
bullets = doc.find_type_elements(:ul).map {|l|
|
53
|
+
doc.find_type_elements(:li, false, l.children)}.flatten
|
54
|
+
if bullets.empty?
|
55
|
+
nil
|
56
|
+
else
|
57
|
+
if @params[:style] == :consistent
|
58
|
+
doc_style = doc.list_style(bullets.first)
|
59
|
+
else
|
60
|
+
doc_style = @params[:style]
|
61
|
+
end
|
62
|
+
bullets.map { |b| doc.element_linenumber(b) \
|
63
|
+
if doc.list_style(b) != doc_style }.compact
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
rule "MD005", "Inconsistent indentation for list items at the same level" do
|
69
|
+
tags :bullet, :ul, :indentation
|
70
|
+
check do |doc|
|
71
|
+
bullets = doc.find_type(:li)
|
72
|
+
errors = []
|
73
|
+
indent_levels = []
|
74
|
+
bullets.each do |b|
|
75
|
+
indent_level = doc.indent_for(doc.element_line(b))
|
76
|
+
if indent_levels[b[:element_level]].nil?
|
77
|
+
indent_levels[b[:element_level]] = indent_level
|
78
|
+
end
|
79
|
+
if indent_level != indent_levels[b[:element_level]]
|
80
|
+
errors << doc.element_linenumber(b)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
errors
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
rule "MD006", "Consider starting bulleted lists at the beginning of the line" do
|
88
|
+
# Starting at the beginning of the line means that indendation for each
|
89
|
+
# bullet level can be identical.
|
90
|
+
tags :bullet, :ul, :indentation
|
91
|
+
check do |doc|
|
92
|
+
doc.find_type(:ul, false).select{
|
93
|
+
|e| doc.indent_for(doc.element_line(e)) != 0 }.map{ |e| e[:location] }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
rule "MD007", "Unordered list indentation" do
|
98
|
+
tags :bullet, :ul, :indentation
|
99
|
+
params :indent => 2
|
100
|
+
check do |doc|
|
101
|
+
indents = []
|
102
|
+
errors = []
|
103
|
+
indents = doc.find_type(:ul).map {
|
104
|
+
|e| [doc.indent_for(doc.element_line(e)), doc.element_linenumber(e)] }
|
105
|
+
curr_indent = indents[0][0] unless indents.empty?
|
106
|
+
indents.each do |indent, linenum|
|
107
|
+
if indent > curr_indent and indent - curr_indent != @params[:indent]
|
108
|
+
errors << linenum
|
109
|
+
end
|
110
|
+
curr_indent = indent
|
111
|
+
end
|
112
|
+
errors
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
rule "MD009", "Trailing spaces" do
|
117
|
+
tags :whitespace
|
118
|
+
check do |doc|
|
119
|
+
doc.matching_lines(/\s$/)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
rule "MD010", "Hard tabs" do
|
124
|
+
tags :whitespace, :hard_tab
|
125
|
+
check do |doc|
|
126
|
+
doc.matching_lines(/\t/)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
rule "MD011", "Reversed link syntax" do
|
131
|
+
tags :links
|
132
|
+
check do |doc|
|
133
|
+
doc.matching_text_element_lines(/\([^)]+\)\[[^\]]+\]/)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
rule "MD012", "Multiple consecutive blank lines" do
|
138
|
+
tags :whitespace, :blank_lines
|
139
|
+
check do |doc|
|
140
|
+
# Every line in the document that is part of a code block. Blank lines
|
141
|
+
# inside of a code block are acceptable.
|
142
|
+
codeblock_lines = doc.find_type_elements(:codeblock).map{
|
143
|
+
|e| (doc.element_linenumber(e)..
|
144
|
+
doc.element_linenumber(e) + e.value.count('\n') - 1).to_a }.flatten
|
145
|
+
blank_lines = doc.matching_lines(/^\s*$/)
|
146
|
+
cons_blank_lines = blank_lines.each_cons(2).select{
|
147
|
+
|p, n| n - p == 1}.map{|p, n| n}
|
148
|
+
cons_blank_lines - codeblock_lines
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
rule "MD013", "Line length" do
|
153
|
+
tags :line_length
|
154
|
+
params :line_length => 80
|
155
|
+
check do |doc|
|
156
|
+
doc.matching_lines(/^.{#{@params[:line_length]}}.*\s/)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
rule "MD014", "Dollar signs used before commands without showing output" do
|
161
|
+
tags :code
|
162
|
+
check do |doc|
|
163
|
+
doc.find_type_elements(:codeblock).select{
|
164
|
+
|e| not e.value.split(/\n+/).map{|l| l.match(/^\$\s/)}.include?(nil)
|
165
|
+
}.map{|e| doc.element_linenumber(e)}
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
rule "MD018", "No space after hash on atx style header" do
|
170
|
+
tags :headers, :atx, :spaces
|
171
|
+
check do |doc|
|
172
|
+
doc.find_type_elements(:header).select do |h|
|
173
|
+
doc.header_style(h) == :atx and doc.element_line(h).match(/^#+[^#\s]/)
|
174
|
+
end.map { |h| doc.element_linenumber(h) }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
rule "MD019", "Multiple spaces after hash on atx style header" do
|
179
|
+
tags :headers, :atx, :spaces
|
180
|
+
check do |doc|
|
181
|
+
doc.find_type_elements(:header).select do |h|
|
182
|
+
doc.header_style(h) == :atx and doc.element_line(h).match(/^#+\s\s/)
|
183
|
+
end.map { |h| doc.element_linenumber(h) }
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
rule "MD020", "No space inside hashes on closed atx style header" do
|
188
|
+
tags :headers, :atx_closed, :spaces
|
189
|
+
check do |doc|
|
190
|
+
doc.find_type_elements(:header).select do |h|
|
191
|
+
doc.header_style(h) == :atx_closed \
|
192
|
+
and (doc.element_line(h).match(/^#+[^#\s]/) \
|
193
|
+
or doc.element_line(h).match(/[^#\s\\]#+$/))
|
194
|
+
end.map { |h| doc.element_linenumber(h) }
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
rule "MD021", "Multiple spaces inside hashes on closed atx style header" do
|
199
|
+
tags :headers, :atx_closed, :spaces
|
200
|
+
check do |doc|
|
201
|
+
doc.find_type_elements(:header).select do |h|
|
202
|
+
doc.header_style(h) == :atx_closed \
|
203
|
+
and (doc.element_line(h).match(/^#+\s\s/) \
|
204
|
+
or doc.element_line(h).match(/\s\s#+$/))
|
205
|
+
end.map { |h| doc.element_linenumber(h) }
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
rule "MD022", "Headers should be surrounded by blank lines" do
|
210
|
+
tags :headers, :blank_lines
|
211
|
+
check do |doc|
|
212
|
+
errors = []
|
213
|
+
doc.find_type_elements(:header).each do |h|
|
214
|
+
header_bad = false
|
215
|
+
linenum = doc.element_linenumber(h)
|
216
|
+
# Check previous line
|
217
|
+
if linenum > 1 and not doc.lines[linenum - 2].empty?
|
218
|
+
header_bad = true
|
219
|
+
end
|
220
|
+
# Check next line
|
221
|
+
next_line_idx = doc.header_style(h) == :setext ? linenum + 1 : linenum
|
222
|
+
next_line = doc.lines[next_line_idx]
|
223
|
+
header_bad = true if not next_line.nil? and not next_line.empty?
|
224
|
+
errors << linenum if header_bad
|
225
|
+
end
|
226
|
+
# Kramdown requires that headers start on a block boundary, so in most
|
227
|
+
# cases it won't pick up a header without a blank line before it. We need
|
228
|
+
# to check regular text and pick out headers ourselves too
|
229
|
+
doc.find_type_elements(:p).each do |p|
|
230
|
+
linenum = doc.element_linenumber(p)
|
231
|
+
text = p.children.select { |e| e.type == :text }.map {|e| e.value }.join
|
232
|
+
lines = text.split("\n")
|
233
|
+
prev_lines = ["", ""]
|
234
|
+
lines.each do |line|
|
235
|
+
# First look for ATX style headers without blank lines before
|
236
|
+
if line.match(/^\#{1,6}/) and not prev_lines[1].empty?
|
237
|
+
errors << linenum
|
238
|
+
end
|
239
|
+
# Next, look for setext style
|
240
|
+
if line.match(/^(-+|=+)\s*$/) and not prev_lines[0].empty?
|
241
|
+
errors << linenum - 1
|
242
|
+
end
|
243
|
+
linenum += 1
|
244
|
+
prev_lines << line
|
245
|
+
prev_lines.shift
|
246
|
+
end
|
247
|
+
end
|
248
|
+
errors.sort
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
rule "MD023", "Headers must start at the beginning of the line" do
|
253
|
+
tags :headers, :spaces
|
254
|
+
check do |doc|
|
255
|
+
errors = []
|
256
|
+
# The only type of header with spaces actually parsed as such is setext
|
257
|
+
# style where only the text is indented. We check for that first.
|
258
|
+
doc.find_type_elements(:header).each do |h|
|
259
|
+
errors << doc.element_linenumber(h) if doc.element_line(h).match(/^\s/)
|
260
|
+
end
|
261
|
+
# Next we have to look for things that aren't parsed as headers because
|
262
|
+
# they start with spaces.
|
263
|
+
doc.find_type_elements(:p).each do |p|
|
264
|
+
linenum = doc.element_linenumber(p)
|
265
|
+
lines = doc.extract_text(p)
|
266
|
+
prev_line = ""
|
267
|
+
lines.each do |line|
|
268
|
+
# First look for ATX style headers
|
269
|
+
if line.match(/^\s+\#{1,6}/)
|
270
|
+
errors << linenum
|
271
|
+
end
|
272
|
+
# Next, look for setext style
|
273
|
+
if line.match(/^\s+(-+|=+)\s*$/) and not prev_line.empty?
|
274
|
+
errors << linenum - 1
|
275
|
+
end
|
276
|
+
linenum += 1
|
277
|
+
prev_line = line
|
278
|
+
end
|
279
|
+
end
|
280
|
+
errors.sort
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
rule "MD024", "Multiple headers with the same content" do
|
285
|
+
tags :headers
|
286
|
+
check do |doc|
|
287
|
+
header_content = Set.new
|
288
|
+
doc.find_type(:header).select do |h|
|
289
|
+
not header_content.add?(h[:raw_text])
|
290
|
+
end.map { |h| doc.element_linenumber(h) }
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
rule "MD025", "Multiple top level headers in the same document" do
|
295
|
+
tags :headers
|
296
|
+
check do |doc|
|
297
|
+
headers = doc.find_type(:header).select { |h| h[:level] == 1 }
|
298
|
+
if not headers.empty? and doc.element_linenumber(headers[0]) == 1
|
299
|
+
headers[1..-1].map { |h| doc.element_linenumber(h) }
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
rule "MD026", "Trailing punctuation in header" do
|
305
|
+
tags :headers
|
306
|
+
params :punctuation => '.,;:!?'
|
307
|
+
check do |doc|
|
308
|
+
doc.find_type(:header).select {
|
309
|
+
|h| h[:raw_text].match(/[#{params[:punctuation]}]$/) }.map {
|
310
|
+
|h| doc.element_linenumber(h) }
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
rule "MD027", "Multiple spaces after blockquote symbol" do
|
315
|
+
tags :blockquote, :whitespace, :indentation
|
316
|
+
check do |doc|
|
317
|
+
errors = []
|
318
|
+
doc.find_type_elements(:blockquote).each do |e|
|
319
|
+
linenum = doc.element_linenumber(e)
|
320
|
+
lines = doc.extract_text(e, /^\s*> /)
|
321
|
+
lines.each do |line|
|
322
|
+
errors << linenum if line.start_with?(" ")
|
323
|
+
linenum += 1
|
324
|
+
end
|
325
|
+
end
|
326
|
+
errors
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
rule "MD028", "Blank line inside blockquote" do
|
331
|
+
tags :blockquote, :whitespace
|
332
|
+
check do |doc|
|
333
|
+
def check_blockquote(errors, elements)
|
334
|
+
prev = [nil, nil, nil]
|
335
|
+
elements.each do |e|
|
336
|
+
prev.shift
|
337
|
+
prev << e.type
|
338
|
+
if prev == [:blockquote, :blank, :blockquote]
|
339
|
+
# The current location is the start of the second blockquote, so the
|
340
|
+
# line before will be a blank line in between the two, or at least the
|
341
|
+
# lowest blank line if there are more than one.
|
342
|
+
errors << e.options[:location] - 1
|
343
|
+
end
|
344
|
+
check_blockquote(errors, e.children)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
errors = []
|
348
|
+
check_blockquote(errors, doc.elements)
|
349
|
+
errors
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
rule "MD029", "Ordered list item prefix" do
|
354
|
+
tags :ol
|
355
|
+
# Style can be :one or :ordered
|
356
|
+
params :style => :one
|
357
|
+
check do |doc|
|
358
|
+
if params[:style] == :ordered
|
359
|
+
doc.find_type_elements(:ol).map { |l|
|
360
|
+
doc.find_type_elements(:li, false, l.children).map.with_index { |i, idx|
|
361
|
+
doc.element_linenumber(i) \
|
362
|
+
unless doc.element_line(i).strip.start_with?("#{idx+1}. ")
|
363
|
+
}
|
364
|
+
}.flatten.compact
|
365
|
+
elsif params[:style] == :one
|
366
|
+
doc.find_type_elements(:ol).map { |l|
|
367
|
+
doc.find_type_elements(:li, false, l.children) }.flatten.map { |i|
|
368
|
+
doc.element_linenumber(i) \
|
369
|
+
unless doc.element_line(i).strip.start_with?('1. ') }.compact
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
rule "MD030", "Spaces after list markers" do
|
375
|
+
tags :ol, :ul, :whitespace
|
376
|
+
params :ul_single => 1, :ol_single => 1, :ul_multi => 1, :ol_multi => 1
|
377
|
+
check do |doc|
|
378
|
+
errors = []
|
379
|
+
doc.find_type_elements([:ul, :ol]).each do |l|
|
380
|
+
list_type = l.type.to_s
|
381
|
+
items = doc.find_type_elements(:li, false, l.children)
|
382
|
+
# The entire list is to use the multi-paragraph spacing rule if any of
|
383
|
+
# the items in it have multiple paragraphs/other block items.
|
384
|
+
srule = items.map { |i| i.children.length }.max > 1 ? "multi" : "single"
|
385
|
+
items.each do |i|
|
386
|
+
actual_spaces = doc.element_line(i).match(/^\s*\S+(\s+)/)[1].length
|
387
|
+
required_spaces = params["#{list_type}_#{srule}".to_sym]
|
388
|
+
errors << doc.element_linenumber(i) if required_spaces != actual_spaces
|
389
|
+
end
|
390
|
+
end
|
391
|
+
errors
|
392
|
+
end
|
393
|
+
end
|
data/lib/mdl/ruleset.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module MarkdownLint
|
2
|
+
class Rule
|
3
|
+
attr_accessor :id, :description
|
4
|
+
|
5
|
+
def initialize(id, description, block)
|
6
|
+
@id, @description = id, description
|
7
|
+
@tags = []
|
8
|
+
@params = {}
|
9
|
+
instance_eval &block
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def check(&block)
|
14
|
+
@check = block unless block.nil?
|
15
|
+
@check
|
16
|
+
end
|
17
|
+
|
18
|
+
def tags(*t)
|
19
|
+
@tags = t.flatten.map {|i| i.to_sym} unless t.empty?
|
20
|
+
@tags
|
21
|
+
end
|
22
|
+
|
23
|
+
def params(p = nil)
|
24
|
+
@params.update(p) unless p.nil?
|
25
|
+
@params
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class RuleSet
|
30
|
+
attr_reader :rules
|
31
|
+
|
32
|
+
def rule(id, description, &block)
|
33
|
+
@rules = {} if @rules.nil?
|
34
|
+
@rules[id] = Rule.new(id, description, block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.load_default
|
38
|
+
self.load(File.expand_path("../rules.rb", __FILE__))
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.load(rules_file)
|
42
|
+
ruleset = new
|
43
|
+
ruleset.instance_eval(File.read(rules_file), rules_file)
|
44
|
+
ruleset.rules
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/mdl/style.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module MarkdownLint
|
4
|
+
class Style
|
5
|
+
attr_reader :rules
|
6
|
+
|
7
|
+
def initialize(all_rules)
|
8
|
+
@tagged_rules = {}
|
9
|
+
all_rules.each do |id, r|
|
10
|
+
r.tags.each do |t|
|
11
|
+
@tagged_rules[t] ||= Set.new
|
12
|
+
@tagged_rules[t] << id
|
13
|
+
end
|
14
|
+
end
|
15
|
+
@all_rules = all_rules
|
16
|
+
@rules = Set.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def all
|
20
|
+
@rules.merge(@all_rules.keys)
|
21
|
+
end
|
22
|
+
|
23
|
+
def rule(id, params={})
|
24
|
+
@rules << id
|
25
|
+
@all_rules[id].params(params)
|
26
|
+
end
|
27
|
+
|
28
|
+
def exclude_rule(id)
|
29
|
+
@rules.delete(id)
|
30
|
+
end
|
31
|
+
|
32
|
+
def tag(t)
|
33
|
+
@rules.merge(@tagged_rules[t])
|
34
|
+
end
|
35
|
+
|
36
|
+
def exclude_tag(t)
|
37
|
+
@rules.subtract(@tagged_rules[t])
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.load(style_file, rules)
|
41
|
+
unless style_file.include?("/") or style_file.end_with?(".rb")
|
42
|
+
style_file = File.expand_path("../styles/#{style_file}.rb", __FILE__)
|
43
|
+
end
|
44
|
+
style = new(rules)
|
45
|
+
style.instance_eval(File.read(style_file), style_file)
|
46
|
+
rules.select! {|r| style.rules.include?(r)}
|
47
|
+
style
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|