Module: Mdlint::Toc

Defined in:
lib/mdlint/toc.rb,
sig/mdlint.rbs,
sig/internal.rbs

Constant Summary collapse

START_MARKERS =
["<!-- toc -->", "<!-- toc:start -->"].freeze
END_MARKERS =
["<!-- toc -->", "<!-- toc:end -->"].freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.marker_pairs(lines) ⇒ Array[Array[Integer]]

Parameters:

  • lines (Object)

Returns:

  • (Array[Array[Integer]])


40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/mdlint/toc.rb', line 40

def marker_pairs(lines)
  pairs = []
  index = 0
  while index < lines.length
    marker = lines[index].strip.downcase
    if marker == "<!-- toc:start -->"
      ending = lines[(index + 1)..]&.index { |line| line.strip.downcase == "<!-- toc:end -->" }
      if ending
        pairs << [index, index + ending + 1]
        index += ending + 1
      end
    elsif marker == "<!-- toc -->"
      ending = lines[(index + 1)..]&.index { |line| line.strip.downcase == "<!-- toc -->" }
      if ending
        pairs << [index, index + ending + 1]
        index += ending + 1
      end
    end
    index += 1
  end
  pairs
end

.slugify(value, counts) ⇒ String

Parameters:

  • value (Object)
  • counts (Object)

Returns:

  • (String)


63
64
65
66
67
68
69
70
71
72
# File 'lib/mdlint/toc.rb', line 63

def slugify(value, counts)
  base = value.gsub(/[`*_~\[\]()<>]/, "")
               .downcase
               .gsub(/[^\p{Alnum}\p{Han}\p{Hiragana}\p{Katakana}\p{Hangul}\s-]/, "")
               .strip
               .gsub(/\s+/, "-")
  suffix = counts[base]
  counts[base] += 1
  suffix.zero? ? base : "#{base}-#{suffix}"
end

.table_of_contents(tokens) ⇒ Array[String]

Parameters:

  • tokens (Object)

Returns:

  • (Array[String])


25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/mdlint/toc.rb', line 25

def table_of_contents(tokens)
  counts = Hash.new(0)
  tokens.each_with_index.filter_map do |token, index|
    next unless token.type == :heading_open

    inline = tokens[(index + 1)..]&.find { |candidate| candidate.type == :inline }
    title = inline&.content.to_s.strip
    slug = slugify(title, counts)
    next if title.empty? || slug.empty?

    level = token.tag.to_s.delete_prefix("h").to_i
    "#{"  " * [level - 1, 0].max}- [#{title}](##{slug})"
  end
end

.update(source, options = {}) ⇒ String

Parameters:

  • source (String)
  • options (options) (defaults to: {})

Returns:

  • (String)


10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/mdlint/toc.rb', line 10

def update(source, options = {})
  eol = source.include?("\r\n") ? "\r\n" : "\n"
  normalized_source = source.gsub("\r\n", "\n")
  lines = normalized_source.split("\n", -1)
  heading_tokens = Parser.parse(normalized_source, options)
  entries = table_of_contents(heading_tokens)
  replacement = entries.join("\n")
  marker_pairs(lines).reverse_each do |pair|
    start_index = pair.fetch(0)
    end_index = pair.fetch(1)
    lines[(start_index + 1)...end_index] = replacement.empty? ? [] : replacement.lines(chomp: true)
  end
  lines.join(eol)
end

Instance Method Details

#update(source, options = {}) ⇒ String

Parameters:

  • source (Object)
  • options (Object) (defaults to: {})

Returns:

  • (String)


10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/mdlint/toc.rb', line 10

def update(source, options = {})
  eol = source.include?("\r\n") ? "\r\n" : "\n"
  normalized_source = source.gsub("\r\n", "\n")
  lines = normalized_source.split("\n", -1)
  heading_tokens = Parser.parse(normalized_source, options)
  entries = table_of_contents(heading_tokens)
  replacement = entries.join("\n")
  marker_pairs(lines).reverse_each do |pair|
    start_index = pair.fetch(0)
    end_index = pair.fetch(1)
    lines[(start_index + 1)...end_index] = replacement.empty? ? [] : replacement.lines(chomp: true)
  end
  lines.join(eol)
end