mdl 0.17.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 +50 -12
- 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]]
|
|
@@ -288,7 +299,17 @@ rule 'MD013', 'Line length' do
|
|
|
288
299
|
header_lines = doc.find_type_elements(:header).map do |e|
|
|
289
300
|
doc.element_linenumber(e)
|
|
290
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
|
+
|
|
291
309
|
overlines = doc.matching_lines(/^.{#{@params[:line_length]}}.+/)
|
|
310
|
+
|
|
311
|
+
overlines -= link_reference_lines if params[:treat_links_as_single_words]
|
|
312
|
+
|
|
292
313
|
overlines -= single_word_lines
|
|
293
314
|
if !params[:code_blocks] || params[:ignore_code_blocks]
|
|
294
315
|
overlines -= codeblock_lines
|
|
@@ -486,7 +507,7 @@ rule 'MD024', 'Multiple headers with the same content' do
|
|
|
486
507
|
stack.pop
|
|
487
508
|
elsif current_level < level
|
|
488
509
|
stack.push([text])
|
|
489
|
-
elsif stack
|
|
510
|
+
elsif stack&.last&.include?(text)
|
|
490
511
|
same_nesting_duplicates.add(header)
|
|
491
512
|
end
|
|
492
513
|
|
|
@@ -503,13 +524,16 @@ end
|
|
|
503
524
|
rule 'MD025', 'Multiple top level headers in the same document' do
|
|
504
525
|
tags :headers
|
|
505
526
|
aliases 'single-h1'
|
|
506
|
-
params :level => 1
|
|
527
|
+
params :level => 1, :front_matter_title => /^\s*title\s*[:=]/
|
|
507
528
|
check do |doc|
|
|
508
529
|
headers = doc.find_type(:header, false).select do |h|
|
|
509
530
|
h[:level] == params[:level]
|
|
510
531
|
end
|
|
511
|
-
|
|
512
|
-
|
|
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) }
|
|
513
537
|
end
|
|
514
538
|
end
|
|
515
539
|
end
|
|
@@ -712,8 +736,8 @@ rule 'MD032', 'Lists should be surrounded by blank lines' do
|
|
|
712
736
|
end
|
|
713
737
|
line.strip.match(/^(`{3,}|~{3,})/)
|
|
714
738
|
if Regexp.last_match(1) && (
|
|
715
|
-
|
|
716
|
-
|
|
739
|
+
!in_code || (Regexp.last_match(1).slice(0, fence.length) == fence)
|
|
740
|
+
)
|
|
717
741
|
fence = in_code ? nil : Regexp.last_match(1)
|
|
718
742
|
in_code = !in_code
|
|
719
743
|
in_list = false
|
|
@@ -741,6 +765,7 @@ end
|
|
|
741
765
|
rule 'MD034', 'Bare URL used' do
|
|
742
766
|
tags :links, :url
|
|
743
767
|
aliases 'no-bare-urls'
|
|
768
|
+
params :allow_quoted => false
|
|
744
769
|
check do |doc|
|
|
745
770
|
errors = doc.matching_text_element_lines(
|
|
746
771
|
%r{https?://}, %i{a html_element}
|
|
@@ -774,7 +799,17 @@ rule 'MD034', 'Bare URL used' do
|
|
|
774
799
|
next false if line.nil?
|
|
775
800
|
|
|
776
801
|
# Strip URLs inside markdown links, then check if a bare URL remains
|
|
777
|
-
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?://})
|
|
778
813
|
end
|
|
779
814
|
end
|
|
780
815
|
end
|
|
@@ -883,11 +918,14 @@ end
|
|
|
883
918
|
rule 'MD041', 'First line in file should be a top level header' do
|
|
884
919
|
tags :headers
|
|
885
920
|
aliases 'first-line-h1'
|
|
886
|
-
params :level => 1
|
|
921
|
+
params :level => 1, :front_matter_title => /^\s*title\s*[:=]/
|
|
887
922
|
check do |doc|
|
|
888
923
|
first_header = doc.find_type(:header).first
|
|
889
|
-
|
|
890
|
-
|
|
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
|
|
891
929
|
end
|
|
892
930
|
end
|
|
893
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-
|
|
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
|