mdl 0.17.0 → 0.18.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 +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 +55 -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: fec16e614604e88653070c37b0d3a6eed2c3a3bd5eafc4c632c4183380aea42e
|
|
4
|
+
data.tar.gz: 296754842f0a085ce6973bdbf95fc77bfd5f0119347b07eb140b1ada10dd9afe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: acc0a0484292c8e67c05662b2752db9d7920c97300544e868969540ec22209071e8998b1366877c1535da8791b0a8fc7aafd4ad377febea11f6e2b4612c8b991
|
|
7
|
+
data.tar.gz: 386804f3431fad7ca7a6fc3e68a93131357175ec5298e7b2b780d768628085131b201409b08cebe6b66942d5003c387faa4333ff61eb53792c0ca18720889dc2
|
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,12 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
|
|
3
|
+
uri_parser = if URI.const_defined?(:RFC2396_PARSER, false)
|
|
4
|
+
URI::RFC2396_PARSER
|
|
5
|
+
else
|
|
6
|
+
URI::DEFAULT_PARSER
|
|
7
|
+
end
|
|
8
|
+
URI_REGEXP = uri_parser.make_regexp
|
|
9
|
+
|
|
1
10
|
docs do |id, description|
|
|
2
11
|
url_hash = [id.downcase,
|
|
3
12
|
description.downcase.gsub(/[^a-z]+/, '-')].join('---')
|
|
@@ -7,9 +16,12 @@ end
|
|
|
7
16
|
rule 'MD001', 'Header levels should only increment by one level at a time' do
|
|
8
17
|
tags :headers
|
|
9
18
|
aliases 'header-increment'
|
|
19
|
+
params :front_matter_title => /^\s*title\s*[:=]/
|
|
10
20
|
check do |doc|
|
|
11
21
|
headers = doc.find_type(:header)
|
|
12
|
-
|
|
22
|
+
has = params[:front_matter_title]
|
|
23
|
+
has &&= doc.front_matter.match(params[:front_matter_title])
|
|
24
|
+
old_level = 1 if has
|
|
13
25
|
errors = []
|
|
14
26
|
headers.each do |h|
|
|
15
27
|
errors << h[:location] if old_level && (h[:level] > old_level + 1)
|
|
@@ -22,8 +34,12 @@ end
|
|
|
22
34
|
rule 'MD002', 'First header should be a top level header' do
|
|
23
35
|
tags :headers
|
|
24
36
|
aliases 'first-header-h1'
|
|
25
|
-
params :level => 1
|
|
37
|
+
params :level => 1, :front_matter_title => /^\s*title\s*[:=]/
|
|
26
38
|
check do |doc|
|
|
39
|
+
has = params[:front_matter_title]
|
|
40
|
+
has &&= doc.front_matter.match(params[:front_matter_title])
|
|
41
|
+
next [] if has
|
|
42
|
+
|
|
27
43
|
first_header = doc.find_type(:header).first
|
|
28
44
|
if first_header && (first_header[:level] != @params[:level])
|
|
29
45
|
[first_header[:location]]
|
|
@@ -288,7 +304,17 @@ rule 'MD013', 'Line length' do
|
|
|
288
304
|
header_lines = doc.find_type_elements(:header).map do |e|
|
|
289
305
|
doc.element_linenumber(e)
|
|
290
306
|
end
|
|
307
|
+
# Every line that is a link reference:
|
|
308
|
+
#
|
|
309
|
+
# This link is referenced[1] at the bottom
|
|
310
|
+
#
|
|
311
|
+
# [1]: https://example.com
|
|
312
|
+
link_reference_lines = doc.matching_lines(/^\[.*\]: #{URI_REGEXP}$/)
|
|
313
|
+
|
|
291
314
|
overlines = doc.matching_lines(/^.{#{@params[:line_length]}}.+/)
|
|
315
|
+
|
|
316
|
+
overlines -= link_reference_lines if params[:treat_links_as_single_words]
|
|
317
|
+
|
|
292
318
|
overlines -= single_word_lines
|
|
293
319
|
if !params[:code_blocks] || params[:ignore_code_blocks]
|
|
294
320
|
overlines -= codeblock_lines
|
|
@@ -486,7 +512,7 @@ rule 'MD024', 'Multiple headers with the same content' do
|
|
|
486
512
|
stack.pop
|
|
487
513
|
elsif current_level < level
|
|
488
514
|
stack.push([text])
|
|
489
|
-
elsif stack
|
|
515
|
+
elsif stack&.last&.include?(text)
|
|
490
516
|
same_nesting_duplicates.add(header)
|
|
491
517
|
end
|
|
492
518
|
|
|
@@ -503,13 +529,16 @@ end
|
|
|
503
529
|
rule 'MD025', 'Multiple top level headers in the same document' do
|
|
504
530
|
tags :headers
|
|
505
531
|
aliases 'single-h1'
|
|
506
|
-
params :level => 1
|
|
532
|
+
params :level => 1, :front_matter_title => /^\s*title\s*[:=]/
|
|
507
533
|
check do |doc|
|
|
508
534
|
headers = doc.find_type(:header, false).select do |h|
|
|
509
535
|
h[:level] == params[:level]
|
|
510
536
|
end
|
|
511
|
-
|
|
512
|
-
|
|
537
|
+
has = params[:front_matter_title]
|
|
538
|
+
has &&= doc.front_matter.match(params[:front_matter_title])
|
|
539
|
+
if !headers.empty? && (doc.element_linenumber(headers[0]) == 1 || has)
|
|
540
|
+
headers = headers[1..] unless has
|
|
541
|
+
headers.map { |h| doc.element_linenumber(h) }
|
|
513
542
|
end
|
|
514
543
|
end
|
|
515
544
|
end
|
|
@@ -712,8 +741,8 @@ rule 'MD032', 'Lists should be surrounded by blank lines' do
|
|
|
712
741
|
end
|
|
713
742
|
line.strip.match(/^(`{3,}|~{3,})/)
|
|
714
743
|
if Regexp.last_match(1) && (
|
|
715
|
-
|
|
716
|
-
|
|
744
|
+
!in_code || (Regexp.last_match(1).slice(0, fence.length) == fence)
|
|
745
|
+
)
|
|
717
746
|
fence = in_code ? nil : Regexp.last_match(1)
|
|
718
747
|
in_code = !in_code
|
|
719
748
|
in_list = false
|
|
@@ -741,6 +770,7 @@ end
|
|
|
741
770
|
rule 'MD034', 'Bare URL used' do
|
|
742
771
|
tags :links, :url
|
|
743
772
|
aliases 'no-bare-urls'
|
|
773
|
+
params :allow_quoted => false
|
|
744
774
|
check do |doc|
|
|
745
775
|
errors = doc.matching_text_element_lines(
|
|
746
776
|
%r{https?://}, %i{a html_element}
|
|
@@ -774,7 +804,17 @@ rule 'MD034', 'Bare URL used' do
|
|
|
774
804
|
next false if line.nil?
|
|
775
805
|
|
|
776
806
|
# Strip URLs inside markdown links, then check if a bare URL remains
|
|
777
|
-
line.gsub(%r{\]\(https?://[^)]*\)}, '')
|
|
807
|
+
line = line.gsub(%r{\]\(https?://[^)]*\)}, '')
|
|
808
|
+
|
|
809
|
+
if params[:allow_quoted]
|
|
810
|
+
# If allow_quoted is set, also strip any URL directly en-quoted,
|
|
811
|
+
# check if a bare url remains
|
|
812
|
+
line = line
|
|
813
|
+
.gsub(%r{"https?://\S*?"}, '')
|
|
814
|
+
.gsub(%r{'https?://\S*?'}, '')
|
|
815
|
+
end
|
|
816
|
+
|
|
817
|
+
line.match?(%r{https?://})
|
|
778
818
|
end
|
|
779
819
|
end
|
|
780
820
|
end
|
|
@@ -883,11 +923,14 @@ end
|
|
|
883
923
|
rule 'MD041', 'First line in file should be a top level header' do
|
|
884
924
|
tags :headers
|
|
885
925
|
aliases 'first-line-h1'
|
|
886
|
-
params :level => 1
|
|
926
|
+
params :level => 1, :front_matter_title => /^\s*title\s*[:=]/
|
|
887
927
|
check do |doc|
|
|
888
928
|
first_header = doc.find_type(:header).first
|
|
889
|
-
|
|
890
|
-
|
|
929
|
+
first = first_header.nil? || (first_header[:location] != 1)
|
|
930
|
+
first ||= first_header[:level] != params[:level]
|
|
931
|
+
has = params[:front_matter_title]
|
|
932
|
+
has &&= doc.front_matter.match(params[:front_matter_title])
|
|
933
|
+
[1] if first && !has
|
|
891
934
|
end
|
|
892
935
|
end
|
|
893
936
|
|
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.1
|
|
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-09 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
|