Class: VivlioStarter::CLI::IndexCommands::HeadingOutline

Inherits:
Object
  • Object
show all
Defined in:
lib/vivlio_starter/cli/index/heading_outline.rb

Overview

章の見出し構造

Defined Under Namespace

Classes: Heading, Located

Constant Summary collapse

HEADING =

行末は $(改行の手前)で止める。\z にすると each_prose_line が渡す 改行つきの行にマッチしない。

/\A(\#{1,6})[ \t]+(\S.*?)[ \t]*$/

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(headings, total_lines) ⇒ HeadingOutline

Returns a new instance of HeadingOutline.



72
73
74
75
# File 'lib/vivlio_starter/cli/index/heading_outline.rb', line 72

def initialize(headings, total_lines)
  @headings = headings
  @total_lines = total_lines
end

Instance Attribute Details

#headingsObject (readonly)

Returns the value of attribute headings.



42
43
44
# File 'lib/vivlio_starter/cli/index/heading_outline.rb', line 42

def headings
  @headings
end

Class Method Details

.normalize(text) ⇒ Object

見出しの文言を比較用に均す。 見出しには強調・インラインコード・ルビ・索引タグが混ざるうえ、 「CSS 組版」と「CSS組版」のような空白の揺れもある。



64
65
66
67
68
69
70
# File 'lib/vivlio_starter/cli/index/heading_outline.rb', line 64

def self.normalize(text)
  text.gsub(/`([^`]*)`/, '\1')
      .gsub(/\*{1,3}([^*]*)\*{1,3}/, '\1')
      .gsub(/\{([^|}]*)\|[^}]*\}/, '\1')
      .gsub(/\[([^\]|]*)(?:\|[^\]]*)?\]/, '\1')
      .gsub(/[[:space:]]+/, '')
end

.parse(content) ⇒ Object

Parameters:

  • content (String)

    章の Markdown 全文



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/vivlio_starter/cli/index/heading_outline.rb', line 45

def self.parse(content)
  headings = []
  stack = []
  Masking.each_prose_line(content) do |line, lineno|
    m = HEADING.match(line) or next
    level = m[1].size
    stack.pop while stack.any? && stack.last.level >= level
    headings << Heading.new(
      lineno:, level:, text: m[2],
      normalized: normalize(m[2]), ancestors: stack.map(&:normalized)
    )
    stack.push(headings.last)
  end
  new(headings, content.lines.size)
end

Instance Method Details

#locate(path) ⇒ Located?

節の指定を行範囲へ解決する。

Parameters:

  • path (Array<String>)

    上位から並べた見出しの文言(中間は省略可)

Returns:

  • (Located, nil)

    見つからなければ nil



80
81
82
83
84
85
86
87
# File 'lib/vivlio_starter/cli/index/heading_outline.rb', line 80

def locate(path)
  wanted = path.map { self.class.normalize(it) }
  hits = @headings.select { matches?(it, wanted) }
  return nil if hits.empty?

  Located.new(range: range_of(hits.first), ambiguous: hits.size > 1,
              candidates: hits.map { describe(it) })
end

#nearest(path, limit: 2) ⇒ Object

「近いもの」を 1〜2 件だけ挙げる。候補を並べ立てない (著者が探しているのは正解であって、選択肢の山ではない)。



96
97
98
99
100
101
102
103
104
# File 'lib/vivlio_starter/cli/index/heading_outline.rb', line 96

def nearest(path, limit: 2)
  wanted = self.class.normalize(path.last.to_s)
  return [] if wanted.empty?

  @headings.map { [it, similarity(it.normalized, wanted)] }
           .select { |_, score| score >= 0.5 }
           .max_by(limit) { |_, score| score }
           .map { |h, _| h.text }
end

#summary(limit: 6) ⇒ Object

警告に添える見出しの一覧(先頭から数件)



90
91
92
# File 'lib/vivlio_starter/cli/index/heading_outline.rb', line 90

def summary(limit: 6)
  @headings.map(&:text).first(limit)
end