mdl 0.16.0 → 0.18.0
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 +4 -4
- data/Gemfile +2 -2
- data/lib/mdl/cli.rb +19 -0
- data/lib/mdl/doc.rb +5 -4
- data/lib/mdl/kramdown_parser.rb +8 -2
- data/lib/mdl/rules.rb +61 -14
- data/lib/mdl/version.rb +1 -1
- data/lib/mdl.rb +10 -5
- data/mdl.gemspec +1 -0
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 99253a6b08c6d6b023f76b93c7a05e5052194a6c379bf0ef519415715fb5a015
|
|
4
|
+
data.tar.gz: 61658817f7ce0c64b809c2ef86f4aa3fb31071038eefe1a4382f9a3386b66bd4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 85dfb4fece1edff5969af1cad21c30a506ff54f7f0ee1fd770e0d59f29c01a6ef14f52e34d99ceade84a718cdaeb97fd5708601c7c0c99cb28feaeda25769bf3
|
|
7
|
+
data.tar.gz: c4ce1bfb14b52592e23107631f76e537fbe857122abb2715b16c1fee9543fddba0f8bd6781f48d20d87b40616bc9510c2d114b4065ffe58e1da7f88c4eff5b03
|
data/Gemfile
CHANGED
|
@@ -4,8 +4,8 @@ gemspec
|
|
|
4
4
|
group :development, :test do
|
|
5
5
|
gem 'base64'
|
|
6
6
|
gem 'bundler', '>= 1.12', '< 5'
|
|
7
|
-
gem 'minitest', '~>
|
|
8
|
-
gem 'pry', '~> 0.
|
|
7
|
+
gem 'minitest', '~> 6.0'
|
|
8
|
+
gem 'pry', '~> 0.16.0'
|
|
9
9
|
gem 'rake', '~> 13.3', '>= 13.3.1'
|
|
10
10
|
gem 'rubocop', '~> 1.81'
|
|
11
11
|
end
|
data/lib/mdl/cli.rb
CHANGED
|
@@ -29,12 +29,31 @@ module MarkdownLint
|
|
|
29
29
|
:description => 'Increase verbosity',
|
|
30
30
|
:boolean => true
|
|
31
31
|
|
|
32
|
+
option :html_to_native,
|
|
33
|
+
:short => '-n',
|
|
34
|
+
:long => '--[no-]html-to-native',
|
|
35
|
+
:boolean => true,
|
|
36
|
+
:description => 'Convert HTML elements to native elements'
|
|
37
|
+
|
|
32
38
|
option :ignore_front_matter,
|
|
33
39
|
:short => '-i',
|
|
34
40
|
:long => '--[no-]ignore-front-matter',
|
|
35
41
|
:boolean => true,
|
|
36
42
|
:description => 'Ignore YAML front matter'
|
|
37
43
|
|
|
44
|
+
option :parse_block_html,
|
|
45
|
+
:short => '-b',
|
|
46
|
+
:long => '--[no-]parse-block-html',
|
|
47
|
+
:boolean => true,
|
|
48
|
+
:description => 'Process kramdown syntax in block HTML tags'
|
|
49
|
+
|
|
50
|
+
option :parse_span_html,
|
|
51
|
+
:short => '-p',
|
|
52
|
+
:long => '--[no-]parse-span-html',
|
|
53
|
+
:boolean => true,
|
|
54
|
+
:default => true,
|
|
55
|
+
:description => 'Process kramdown syntax in span HTML tags'
|
|
56
|
+
|
|
38
57
|
option :show_kramdown_warnings,
|
|
39
58
|
:short => '-w',
|
|
40
59
|
:long => '--[no-]warnings',
|
data/lib/mdl/doc.rb
CHANGED
|
@@ -26,7 +26,7 @@ module MarkdownLint
|
|
|
26
26
|
##
|
|
27
27
|
# Create a new document given a string containing the markdown source
|
|
28
28
|
|
|
29
|
-
def initialize(text, ignore_front_matter = false)
|
|
29
|
+
def initialize(text, ignore_front_matter = false, **)
|
|
30
30
|
regex = /\A---\n(.*?)---\n\n?/m
|
|
31
31
|
if ignore_front_matter && regex.match(text)
|
|
32
32
|
@front_matter = regex.match(text).to_s
|
|
@@ -39,7 +39,7 @@ module MarkdownLint
|
|
|
39
39
|
# The -1 is to cause split to preserve an extra entry in the array so we
|
|
40
40
|
# can tell if there's a final newline in the file or not.
|
|
41
41
|
@lines = text.split(/\R/, -1)
|
|
42
|
-
@parsed = Kramdown::Document.new(text, :input => 'MarkdownLint')
|
|
42
|
+
@parsed = Kramdown::Document.new(text, :input => 'MarkdownLint', **)
|
|
43
43
|
@elements = @parsed.root.children
|
|
44
44
|
add_annotations(@elements)
|
|
45
45
|
end
|
|
@@ -47,13 +47,14 @@ module MarkdownLint
|
|
|
47
47
|
##
|
|
48
48
|
# Alternate 'constructor' passing in a filename
|
|
49
49
|
|
|
50
|
-
def self.new_from_file(filename, ignore_front_matter = false)
|
|
50
|
+
def self.new_from_file(filename, ignore_front_matter = false, **)
|
|
51
51
|
if filename == '-'
|
|
52
|
-
new($stdin.read.scrub, ignore_front_matter)
|
|
52
|
+
new($stdin.read.scrub, ignore_front_matter, **)
|
|
53
53
|
else
|
|
54
54
|
new(
|
|
55
55
|
File.read(filename, :encoding => 'UTF-8').scrub,
|
|
56
56
|
ignore_front_matter,
|
|
57
|
+
**,
|
|
57
58
|
)
|
|
58
59
|
end
|
|
59
60
|
end
|
data/lib/mdl/kramdown_parser.rb
CHANGED
|
@@ -14,8 +14,14 @@ module Kramdown
|
|
|
14
14
|
@block_parsers.insert(i, :codeblock_fenced_gfm)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
#
|
|
18
|
-
|
|
17
|
+
# GFM fenced code blocks, extended to allow spaces in the info string
|
|
18
|
+
# (e.g. ```c hlines=2). The GFM regex restricts the info string to a
|
|
19
|
+
# single non-whitespace token, which causes kramdown to miss blocks whose
|
|
20
|
+
# info string contains a space, leading to false positives inside them.
|
|
21
|
+
# Capture groups match GFM's: 1=fence, 2=fence-char, 3=full-info,
|
|
22
|
+
# 4=first-word, 5=content.
|
|
23
|
+
FENCED_CODEBLOCK_MATCH =
|
|
24
|
+
/^ {0,3}(([~`]){3,})\s*?((\S+?)[^\n]*)?\n(.*?)^ {0,3}\1\2*\s*?\n/m
|
|
19
25
|
|
|
20
26
|
# End paragraphs when a fenced code block starts, matching GFM
|
|
21
27
|
# behavior. Without this, fenced code blocks without a preceding
|
data/lib/mdl/rules.rb
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
|
|
3
|
+
URI_REGEXP = URI::RFC2396_PARSER.make_regexp
|
|
4
|
+
|
|
1
5
|
docs do |id, description|
|
|
2
6
|
url_hash = [id.downcase,
|
|
3
7
|
description.downcase.gsub(/[^a-z]+/, '-')].join('---')
|
|
@@ -7,9 +11,12 @@ end
|
|
|
7
11
|
rule 'MD001', 'Header levels should only increment by one level at a time' do
|
|
8
12
|
tags :headers
|
|
9
13
|
aliases 'header-increment'
|
|
14
|
+
params :front_matter_title => /^\s*title\s*[:=]/
|
|
10
15
|
check do |doc|
|
|
11
16
|
headers = doc.find_type(:header)
|
|
12
|
-
|
|
17
|
+
has = params[:front_matter_title]
|
|
18
|
+
has &&= doc.front_matter.match(params[:front_matter_title])
|
|
19
|
+
old_level = 1 if has
|
|
13
20
|
errors = []
|
|
14
21
|
headers.each do |h|
|
|
15
22
|
errors << h[:location] if old_level && (h[:level] > old_level + 1)
|
|
@@ -22,8 +29,12 @@ end
|
|
|
22
29
|
rule 'MD002', 'First header should be a top level header' do
|
|
23
30
|
tags :headers
|
|
24
31
|
aliases 'first-header-h1'
|
|
25
|
-
params :level => 1
|
|
32
|
+
params :level => 1, :front_matter_title => /^\s*title\s*[:=]/
|
|
26
33
|
check do |doc|
|
|
34
|
+
has = params[:front_matter_title]
|
|
35
|
+
has &&= doc.front_matter.match(params[:front_matter_title])
|
|
36
|
+
next [] if has
|
|
37
|
+
|
|
27
38
|
first_header = doc.find_type(:header).first
|
|
28
39
|
if first_header && (first_header[:level] != @params[:level])
|
|
29
40
|
[first_header[:location]]
|
|
@@ -242,7 +253,8 @@ rule 'MD013', 'Line length' do
|
|
|
242
253
|
tags :line_length
|
|
243
254
|
aliases 'line-length'
|
|
244
255
|
params :line_length => 80, :ignore_code_blocks => false, :code_blocks => true,
|
|
245
|
-
:tables => true, :headings => true
|
|
256
|
+
:tables => true, :headings => true,
|
|
257
|
+
:treat_links_as_single_words => false
|
|
246
258
|
|
|
247
259
|
check do |doc|
|
|
248
260
|
# Every line in the document that is part of a code block.
|
|
@@ -274,12 +286,30 @@ rule 'MD013', 'Line length' do
|
|
|
274
286
|
table_lines << linenum if line.match?(/^\s*\|.*\|/)
|
|
275
287
|
end
|
|
276
288
|
end
|
|
277
|
-
single_word_lines = doc.matching_lines(/^\S+$/)
|
|
289
|
+
single_word_lines = doc.matching_lines(/^\s*\S+$/) +
|
|
290
|
+
doc.matching_lines(/^\s*(?:[-*+]|\d+[.)])\s+\S+$/)
|
|
291
|
+
if params[:treat_links_as_single_words]
|
|
292
|
+
link_re = /\[.*\]\([^)]*\)/
|
|
293
|
+
list_re = /^\s*(?:[-*+]|\d+[.)])\s+#{link_re.source}$/
|
|
294
|
+
single_word_lines +=
|
|
295
|
+
doc.matching_lines(/^\s*#{link_re.source}$/) +
|
|
296
|
+
doc.matching_lines(list_re)
|
|
297
|
+
end
|
|
278
298
|
# Every line in the document that is a header.
|
|
279
299
|
header_lines = doc.find_type_elements(:header).map do |e|
|
|
280
300
|
doc.element_linenumber(e)
|
|
281
301
|
end
|
|
302
|
+
# Every line that is a link reference:
|
|
303
|
+
#
|
|
304
|
+
# This link is referenced[1] at the bottom
|
|
305
|
+
#
|
|
306
|
+
# [1]: https://example.com
|
|
307
|
+
link_reference_lines = doc.matching_lines(/^\[.*\]: #{URI_REGEXP}$/)
|
|
308
|
+
|
|
282
309
|
overlines = doc.matching_lines(/^.{#{@params[:line_length]}}.+/)
|
|
310
|
+
|
|
311
|
+
overlines -= link_reference_lines if params[:treat_links_as_single_words]
|
|
312
|
+
|
|
283
313
|
overlines -= single_word_lines
|
|
284
314
|
if !params[:code_blocks] || params[:ignore_code_blocks]
|
|
285
315
|
overlines -= codeblock_lines
|
|
@@ -477,7 +507,7 @@ rule 'MD024', 'Multiple headers with the same content' do
|
|
|
477
507
|
stack.pop
|
|
478
508
|
elsif current_level < level
|
|
479
509
|
stack.push([text])
|
|
480
|
-
elsif stack
|
|
510
|
+
elsif stack&.last&.include?(text)
|
|
481
511
|
same_nesting_duplicates.add(header)
|
|
482
512
|
end
|
|
483
513
|
|
|
@@ -494,13 +524,16 @@ end
|
|
|
494
524
|
rule 'MD025', 'Multiple top level headers in the same document' do
|
|
495
525
|
tags :headers
|
|
496
526
|
aliases 'single-h1'
|
|
497
|
-
params :level => 1
|
|
527
|
+
params :level => 1, :front_matter_title => /^\s*title\s*[:=]/
|
|
498
528
|
check do |doc|
|
|
499
529
|
headers = doc.find_type(:header, false).select do |h|
|
|
500
530
|
h[:level] == params[:level]
|
|
501
531
|
end
|
|
502
|
-
|
|
503
|
-
|
|
532
|
+
has = params[:front_matter_title]
|
|
533
|
+
has &&= doc.front_matter.match(params[:front_matter_title])
|
|
534
|
+
if !headers.empty? && (doc.element_linenumber(headers[0]) == 1 || has)
|
|
535
|
+
headers = headers[1..] unless has
|
|
536
|
+
headers.map { |h| doc.element_linenumber(h) }
|
|
504
537
|
end
|
|
505
538
|
end
|
|
506
539
|
end
|
|
@@ -703,8 +736,8 @@ rule 'MD032', 'Lists should be surrounded by blank lines' do
|
|
|
703
736
|
end
|
|
704
737
|
line.strip.match(/^(`{3,}|~{3,})/)
|
|
705
738
|
if Regexp.last_match(1) && (
|
|
706
|
-
|
|
707
|
-
|
|
739
|
+
!in_code || (Regexp.last_match(1).slice(0, fence.length) == fence)
|
|
740
|
+
)
|
|
708
741
|
fence = in_code ? nil : Regexp.last_match(1)
|
|
709
742
|
in_code = !in_code
|
|
710
743
|
in_list = false
|
|
@@ -732,6 +765,7 @@ end
|
|
|
732
765
|
rule 'MD034', 'Bare URL used' do
|
|
733
766
|
tags :links, :url
|
|
734
767
|
aliases 'no-bare-urls'
|
|
768
|
+
params :allow_quoted => false
|
|
735
769
|
check do |doc|
|
|
736
770
|
errors = doc.matching_text_element_lines(
|
|
737
771
|
%r{https?://}, %i{a html_element}
|
|
@@ -765,7 +799,17 @@ rule 'MD034', 'Bare URL used' do
|
|
|
765
799
|
next false if line.nil?
|
|
766
800
|
|
|
767
801
|
# Strip URLs inside markdown links, then check if a bare URL remains
|
|
768
|
-
line.gsub(%r{\]\(https?://[^)]*\)}, '')
|
|
802
|
+
line = line.gsub(%r{\]\(https?://[^)]*\)}, '')
|
|
803
|
+
|
|
804
|
+
if params[:allow_quoted]
|
|
805
|
+
# If allow_quoted is set, also strip any URL directly en-quoted,
|
|
806
|
+
# check if a bare url remains
|
|
807
|
+
line = line
|
|
808
|
+
.gsub(%r{"https?://\S*?"}, '')
|
|
809
|
+
.gsub(%r{'https?://\S*?'}, '')
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
line.match?(%r{https?://})
|
|
769
813
|
end
|
|
770
814
|
end
|
|
771
815
|
end
|
|
@@ -874,11 +918,14 @@ end
|
|
|
874
918
|
rule 'MD041', 'First line in file should be a top level header' do
|
|
875
919
|
tags :headers
|
|
876
920
|
aliases 'first-line-h1'
|
|
877
|
-
params :level => 1
|
|
921
|
+
params :level => 1, :front_matter_title => /^\s*title\s*[:=]/
|
|
878
922
|
check do |doc|
|
|
879
923
|
first_header = doc.find_type(:header).first
|
|
880
|
-
|
|
881
|
-
|
|
924
|
+
first = first_header.nil? || (first_header[:location] != 1)
|
|
925
|
+
first ||= first_header[:level] != params[:level]
|
|
926
|
+
has = params[:front_matter_title]
|
|
927
|
+
has &&= doc.front_matter.match(params[:front_matter_title])
|
|
928
|
+
[1] if first && !has
|
|
882
929
|
end
|
|
883
930
|
end
|
|
884
931
|
|
data/lib/mdl/version.rb
CHANGED
data/lib/mdl.rb
CHANGED
|
@@ -67,9 +67,9 @@ module MarkdownLint
|
|
|
67
67
|
if Config[:git_recurse]
|
|
68
68
|
Dir.chdir(filename) do
|
|
69
69
|
cli.cli_arguments[i] =
|
|
70
|
-
Mixlib::ShellOut.new("git ls-files '*.md' '*.markdown'")
|
|
71
|
-
.run_command.stdout.
|
|
72
|
-
.map { |m| File.join(filename, m
|
|
70
|
+
Mixlib::ShellOut.new("git ls-files -z '*.md' '*.markdown'")
|
|
71
|
+
.run_command.stdout.split("\x00")
|
|
72
|
+
.map { |m| File.join(filename, m) }
|
|
73
73
|
end
|
|
74
74
|
else
|
|
75
75
|
cli.cli_arguments[i] = Dir["#{filename}/**/*.{md,markdown}"]
|
|
@@ -95,7 +95,12 @@ module MarkdownLint
|
|
|
95
95
|
text = original_text.dup
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
options = {
|
|
99
|
+
:parse_block_html => Config[:parse_block_html],
|
|
100
|
+
:parse_span_html => Config[:parse_span_html],
|
|
101
|
+
:html_to_native => Config[:html_to_native],
|
|
102
|
+
}
|
|
103
|
+
doc = Doc.new_from_file(filename, Config[:ignore_front_matter], **options)
|
|
99
104
|
filename = '(stdin)' if filename == '-'
|
|
100
105
|
if Config[:show_kramdown_warnings]
|
|
101
106
|
status = 2 unless doc.parsed.warnings.empty?
|
|
@@ -113,7 +118,7 @@ module MarkdownLint
|
|
|
113
118
|
if Config[:fix] && filename != '(stdin)' && rule.fix
|
|
114
119
|
rule.fix.call(doc, error_lines)
|
|
115
120
|
text = doc.to_s
|
|
116
|
-
doc = Doc.new(text.dup, Config[:ignore_front_matter])
|
|
121
|
+
doc = Doc.new(text.dup, Config[:ignore_front_matter], **options)
|
|
117
122
|
corrected = true
|
|
118
123
|
end
|
|
119
124
|
|
data/mdl.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mdl
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.18.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Phil Dibowitz
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05
|
|
11
|
+
date: 2026-08-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: kramdown
|
|
@@ -80,6 +80,20 @@ dependencies:
|
|
|
80
80
|
- - ">="
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: uri
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
83
97
|
description: Style checker/lint tool for markdown files
|
|
84
98
|
email:
|
|
85
99
|
- phil@ipom.com
|