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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f3b2d8b702d0c1f3e4401b5c0a997d76f13368f57fc1be5d52acd2ca9ea35c2
4
- data.tar.gz: f65a08dd97130930b44e93cb65cc318f218210b99d74ff915cd5842e17dca188
3
+ metadata.gz: 99253a6b08c6d6b023f76b93c7a05e5052194a6c379bf0ef519415715fb5a015
4
+ data.tar.gz: 61658817f7ce0c64b809c2ef86f4aa3fb31071038eefe1a4382f9a3386b66bd4
5
5
  SHA512:
6
- metadata.gz: 0a63864cdb9583963ff12d78ccfd5d304b8162c464842140ceae23d5b14afab9d8f4fd60e473634bb0fdf3f2580a68ca8aca451559df058276fc3cb235dc8290
7
- data.tar.gz: d381798b1c293e45a237368232429e0ab1e9812c1963b674383c61b0301fdcd72f3f34c285bcd58ea5ed1695fa5fd0fbf01514929aa49ded6a4df74472ce1822
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', '~> 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,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
- old_level = nil
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.last.include?(text)
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
- if !headers.empty? && (doc.element_linenumber(headers[0]) == 1)
512
- headers[1..].map { |h| doc.element_linenumber(h) }
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
- !in_code || (Regexp.last_match(1).slice(0, fence.length) == fence)
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?://[^)]*\)}, '').match?(%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
- [1] if first_header.nil? || (first_header[:location] != 1) \
890
- || (first_header[:level] != params[:level])
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
@@ -1,3 +1,3 @@
1
1
  module MarkdownLint
2
- VERSION = '0.17.0'.freeze
2
+ VERSION = '0.18.0'.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.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-06-03 00:00:00.000000000 Z
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