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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f3b2d8b702d0c1f3e4401b5c0a997d76f13368f57fc1be5d52acd2ca9ea35c2
4
- data.tar.gz: f65a08dd97130930b44e93cb65cc318f218210b99d74ff915cd5842e17dca188
3
+ metadata.gz: fec16e614604e88653070c37b0d3a6eed2c3a3bd5eafc4c632c4183380aea42e
4
+ data.tar.gz: 296754842f0a085ce6973bdbf95fc77bfd5f0119347b07eb140b1ada10dd9afe
5
5
  SHA512:
6
- metadata.gz: 0a63864cdb9583963ff12d78ccfd5d304b8162c464842140ceae23d5b14afab9d8f4fd60e473634bb0fdf3f2580a68ca8aca451559df058276fc3cb235dc8290
7
- data.tar.gz: d381798b1c293e45a237368232429e0ab1e9812c1963b674383c61b0301fdcd72f3f34c285bcd58ea5ed1695fa5fd0fbf01514929aa49ded6a4df74472ce1822
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', '~> 5.26', '>= 5.26.2'
8
- gem 'pry', '~> 0.15.2'
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
@@ -14,8 +14,14 @@ module Kramdown
14
14
  @block_parsers.insert(i, :codeblock_fenced_gfm)
15
15
  end
16
16
 
17
- # Regular kramdown parser, but with GFM style fenced code blocks
18
- FENCED_CODEBLOCK_MATCH = Kramdown::Parser::GFM::FENCED_CODEBLOCK_MATCH
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
- old_level = nil
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.last.include?(text)
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
- if !headers.empty? && (doc.element_linenumber(headers[0]) == 1)
512
- headers[1..].map { |h| doc.element_linenumber(h) }
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
- !in_code || (Regexp.last_match(1).slice(0, fence.length) == fence)
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?://[^)]*\)}, '').match?(%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
- [1] if first_header.nil? || (first_header[:location] != 1) \
890
- || (first_header[:level] != params[:level])
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
@@ -1,3 +1,3 @@
1
1
  module MarkdownLint
2
- VERSION = '0.17.0'.freeze
2
+ VERSION = '0.18.1'.freeze
3
3
  end
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.lines
72
- .map { |m| File.join(filename, m.strip) }
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
- doc = Doc.new_from_file(filename, Config[:ignore_front_matter])
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
@@ -26,4 +26,5 @@ Gem::Specification.new do |spec|
26
26
  spec.add_dependency 'mixlib-cli'
27
27
  spec.add_dependency 'mixlib-config'
28
28
  spec.add_dependency 'mixlib-shellout'
29
+ spec.add_dependency 'uri'
29
30
  end
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.17.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-06-03 00:00:00.000000000 Z
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