Module: Vivlio::Starter::CLI::PostProcessCommands::HeadingProcessor

Defined in:
lib/vivlio/starter/cli/post_process/heading_processor.rb

Overview

Module: HeadingProcessor


【役割】

  • 見出し(h1..h6)にマーカーとメタデータを付与

  • 章番号・節番号のスパンを構築

【処理内容】

  1. 見出しマーカー付与

    • class: vs-h-marker

    • data-heading: 見出しテキスト

    • data-hN: レベル別見出しテキスト

    • data-chapter: 章トークン

  2. 見出し番号スパン構築

    • h1: <span class=“chapter-number”>第N章</span><span class=“chapter-title”>タイトル</span>

    • h2: <span class=“section-number”>N-M</span><span class=“section-title”>タイトル</span>

    • h3: <span class=“subsection-marker”>♣</span><span class=“subsection-title”>タイトル</span>

Constant Summary collapse

MAIN_CHAPTER_RANGE =
(1..89)

Class Method Summary collapse

Class Method Details

.add_chapter_token(heading, chapter_token) ⇒ Object

章トークンを追加



129
130
131
132
133
134
135
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 129

def add_chapter_token(heading, chapter_token)
  return false unless chapter_token
  return false if heading['data-chapter'] == chapter_token

  heading['data-chapter'] = chapter_token
  true
end

.add_heading_data_attributes(heading, level) ⇒ Object

見出しテキストの data 属性を追加



101
102
103
104
105
106
107
108
109
110
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 101

def add_heading_data_attributes(heading, level)
  text = extract_heading_core_text(heading)
  return false if text.nil? || text.empty?

  modified = false
  modified |= set_attr_if_changed(heading, 'data-heading', text)
  modified |= set_attr_if_changed(heading, "data-h#{level}", text)
  modified |= set_h1_id(heading, text) if level == 1
  modified
end

.add_marker_class(heading) ⇒ Object

vs-h-marker クラスを追加



91
92
93
94
95
96
97
98
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 91

def add_marker_class(heading)
  classes = (heading['class'] || '').split
  return false if classes.include?('vs-h-marker')

  classes << 'vs-h-marker'
  heading['class'] = classes.join(' ').strip
  true
end

.add_number_span(node, number_class, number_text, doc) ⇒ Object

番号スパンを追加



303
304
305
306
307
308
309
310
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 303

def add_number_span(node, number_class, number_text, doc)
  return unless number_class && !number_text.empty?

  span = Nokogiri::XML::Node.new('span', doc)
  span['class'] = number_class
  span.content = number_text
  node.add_child(span)
end

.add_title_span(node, title_class, title_text, original_nodes, doc) ⇒ Object

タイトルスパンを追加



313
314
315
316
317
318
319
320
321
322
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 313

def add_title_span(node, title_class, title_text, original_nodes, doc)
  if title_class
    span = Nokogiri::XML::Node.new('span', doc)
    span['class'] = title_class
    original_nodes.empty? ? span.content = title_text : original_nodes.each { |c| span.add_child(c) }
    node.add_child(span)
  elsif !title_text.empty?
    node.add_child(Nokogiri::XML::Text.new(title_text, doc))
  end
end

.all_integer_strings?(arr) ⇒ Boolean

配列の全要素が整数文字列かどうか

Returns:

  • (Boolean)


450
451
452
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 450

def all_integer_strings?(arr)
  Array(arr).all? { |s| s.to_s.strip.match?(/\A\d+\z/) }
end

.apply_marker_to_heading(heading, level, chapter_token) ⇒ Object

見出し要素にマーカーを適用



83
84
85
86
87
88
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 83

def apply_marker_to_heading(heading, level, chapter_token)
  modified = add_marker_class(heading)
  modified |= add_heading_data_attributes(heading, level)
  modified |= add_chapter_token(heading, chapter_token)
  modified
end

.build_h1_number_text(context) ⇒ Object

h1 の番号テキストを構築



194
195
196
197
198
199
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 194

def build_h1_number_text(context)
  return "付録 #{context[:appendix_letter]}" if context[:file_type] == 'appendix' && context[:appendix_letter]
  return "#{context[:chapter_display_number]}" if context[:chapter_display_number]

  nil
end

.build_h2_number_text(context, section_index) ⇒ Object

h2 の番号テキストを構築



222
223
224
225
226
227
228
229
230
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 222

def build_h2_number_text(context, section_index)
  if context[:file_type] == 'appendix'
    context[:appendix_letter] ? "#{context[:appendix_letter]}-#{section_index}" : section_index.to_s
  elsif context[:chapter_display_number]
    "#{context[:chapter_display_number]}-#{section_index}"
  else
    section_index.to_s
  end
end

.build_heading_context(html_path, entry) ⇒ Object

見出し処理のコンテキストを構築

Parameters:

  • html_path (String)

    HTML ファイルパス

  • entry (TokenResolver::Entry)

    章情報を持つ Entry オブジェクト



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 164

def build_heading_context(html_path, entry)
  chapter_token = File.basename(html_path, File.extname(html_path))
  chapter_number_i = entry.number&.to_i

  {
    file_type: entry.kind.to_s,
    chapter_display_number: if chapter_number_i
                              resolve_main_chapter_display_number(chapter_token,
                                                                  chapter_number_i)
                            end,
    appendix_letter: if chapter_number_i&.between?(90, 98)
                       resolve_appendix_letter(chapter_token, chapter_number_i)
                     end,
    process_headings: %i[chapter appendix].include?(entry.kind)
  }
end

.chapter_number_string?(str) ⇒ Boolean

文字列が「章番号/範囲」のみで構成されているか判定例: “11, 12-13” → true / “11-install” → false

Returns:

  • (Boolean)


440
441
442
443
444
445
446
447
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 440

def chapter_number_string?(str)
  parts = str.to_s.split(',').map(&:strip).reject(&:empty?)
  return false if parts.empty?

  parts.all? do |part|
    part.match?(/\A\d+\z/) || part.match?(/\A\d+-\d+\z/)
  end
end

.chapter_tokens_overrideObject



47
48
49
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 47

def chapter_tokens_override
  @chapter_tokens_override || []
end

.chapter_tokens_override=(tokens) ⇒ Object

一時的な章トークンの並びを外部から指定するためのオーバーライド例: vs build 54-56 のような単章/範囲ビルド時に、

そのビルド対象だけを 1,2,3... の順番で扱いたい場合に使用する。
  • nil または空配列の場合はオーバーライドなし(従来どおり CONFIG や HTML から自動検出)

  • 設定された場合は、その並びを優先的に main_chapter_order の候補として利用する



41
42
43
44
45
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 41

def chapter_tokens_override=(tokens)
  @chapter_tokens_override = Array(tokens).compact.map(&:to_s)
  # 章並びを再計算させるためキャッシュを無効化
  @main_chapter_order = nil
end

.configured_main_chapter_tokensArray<String>?

設定ファイルから章トークンを取得対応形式(config/book.yml の chapters キー):

- nil / 'all'        → フルビルド(nil を返す)
- "54-56"            → 章番号指定(11..89 の範囲)
- "02, 11-13, 91"   → カンマ区切り + 範囲指定
- [54, 55, 56]       → 章番号配列
- "11-install\n12-tutorial" → ファイルベース名(行ごと)
- ["11-install", "12-tutorial"] → ファイルベース名配列

Returns:

  • (Array<String>, nil)

    章トークンの配列



400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 400

def configured_main_chapter_tokens
  cfg = Common::CONFIG['chapters']

  case cfg
  when nil
    nil
  when String
    str = cfg.to_s
    return nil if str.strip.casecmp('all').zero?

    # 数字/レンジ指定(例: "54-56" や "11, 12-13")として解釈できる場合
    if chapter_number_string?(str)
      numbers = parse_chapter_numbers_from_string(str)
      return tokens_from_chapter_numbers(numbers) if numbers && !numbers.empty?

      return nil
    end

    # それ以外は、行ごとのトークン(ファイルベース名)として扱う
    raw_list = str.lines.map(&:strip).reject(&:empty?)
    return nil if raw_list.empty?

    normalize_and_filter_tokens(raw_list)
  when Array
    arr = cfg.map { |s| s.to_s.strip }.reject(&:empty?)
    return nil if arr.empty?

    # 全要素が整数として解釈できる場合は章番号配列
    if all_integer_strings?(arr)
      numbers = arr.map(&:to_i).uniq.sort
      return tokens_from_chapter_numbers(numbers)
    end

    # それ以外はトークン配列として扱う
    normalize_and_filter_tokens(arr)
  end
end

.discovered_main_chapter_tokensArray<String>

発見されたHTMLファイルから章トークンを取得

Returns:

  • (Array<String>)

    章トークンの配列



499
500
501
502
503
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 499

def discovered_main_chapter_tokens
  resolver = TokenResolver::Resolver.new
  html_tokens = Dir.glob(File.join('.', '*.html')).map { |path| File.basename(path, '.html') }
  normalize_and_filter_tokens(html_tokens).sort_by { |token| resolver.resolve_file(token).number.to_i }
end

.extract_chapter_token(path) ⇒ Object

ファイルパスから章トークンを抽出



138
139
140
141
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 138

def extract_chapter_token(path)
  token = File.basename(path, File.extname(path)).to_s.strip
  token.empty? ? nil : token
end

.extract_heading_core_text(node) ⇒ String

見出しのコアテキストを抽出

Parameters:

  • node (Nokogiri::XML::Element)

    見出し要素

Returns:

  • (String)

    見出しテキスト



327
328
329
330
331
332
333
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 327

def extract_heading_core_text(node)
  %w[chapter-title section-title subsection-title].each do |cls|
    span = node.at_css("span.#{cls}")
    return span.text.to_s.strip if span
  end
  node.text.to_s.strip
end

.extract_original_title_nodes(node, number_class, title_class) ⇒ Object

元のタイトルノードを抽出



291
292
293
294
295
296
297
298
299
300
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 291

def extract_original_title_nodes(node, number_class, title_class)
  title_span = title_class ? node.at_css("span.#{title_class}") : nil
  if title_span
    title_span.children.map(&:dup)
  else
    node.children.reject do |child|
      number_class && child.element? && child['class'].to_s.split.include?(number_class)
    end.map(&:dup)
  end
end

.heading_span_classes(kind) ⇒ Object

見出し種別に応じたクラス名を取得



270
271
272
273
274
275
276
277
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 270

def heading_span_classes(kind)
  case kind
  when :chapter then %w[chapter-number chapter-title]
  when :section then %w[section-number section-title]
  when :subsection then %w[subsection-marker subsection-title]
  else [nil, nil]
  end
end

.inject_heading_markers!(html_paths, max_level: 3) ⇒ Object

見出し(h1..hN)に本文参照用のマーカー(class と data 属性)を付与

Parameters:

  • html_paths (Array<String>)

    HTMLファイルパスの配列

  • max_level (Integer) (defaults to: 3)

    処理する見出しの最大レベル(デフォルト: 3)



54
55
56
57
58
59
60
61
62
63
64
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 54

def inject_heading_markers!(html_paths, max_level: 3)
  paths = Array(html_paths).select { |p| File.exist?(p) }
  return if paths.empty?

  max_l = max_level.to_i.clamp(1, 6)
  paths.each do |path|
    process_heading_markers_for_file(path, max_l)
  rescue StandardError => e
    Common.log_warn("見出しメタ付与に失敗: #{path} (#{e})")
  end
end

.inject_heading_number_spans!(html_path, entry) ⇒ Object

見出し番号スパンを構築

Parameters:

  • html_path (String)

    HTMLファイルパス

  • entry (TokenResolver::Entry)

    章情報を持つ Entry オブジェクト



146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 146

def inject_heading_number_spans!(html_path, entry)
  return unless File.exist?(html_path)

  html = File.read(html_path, encoding: 'utf-8')
  doc = parse_html_document(html)
  context = build_heading_context(html_path, entry)
  return unless context[:process_headings]

  modified = process_h1_spans(doc, context)
  modified |= process_h2_spans(doc, context)
  modified |= process_h3_spans(doc)

  save_html_document(html_path, doc) if modified
end

.main_chapter_orderArray<String>

メイン章の順序を取得

Returns:

  • (Array<String>)

    章トークンの配列



372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 372

def main_chapter_order
  return @main_chapter_order if @main_chapter_order

  # ビルドコマンド等から一時的な章リストが与えられている場合はそれを優先
  override = chapter_tokens_override
  if override && !override.empty?
    tokens = normalize_and_filter_tokens(override)
    if tokens && !tokens.empty?
      @main_chapter_order = tokens
      return @main_chapter_order
    end
  end

  configured = configured_main_chapter_tokens
  tokens = configured&.any? ? configured : discovered_main_chapter_tokens
  @main_chapter_order = tokens
end

.main_chapter_token?(token) ⇒ Boolean

メイン章トークンかどうか判定

Parameters:

  • token (String)

    トークン

Returns:

  • (Boolean)

    メイン章トークンの場合true



541
542
543
544
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 541

def main_chapter_token?(token)
  entry = TokenResolver::Resolver.new.resolve_file(token)
  entry.number && MAIN_CHAPTER_RANGE.include?(entry.number.to_i)
end

.needs_heading_update?(node, number_text, title_text, number_class, title_class) ⇒ Boolean

見出しの更新が必要か判定

Returns:

  • (Boolean)


280
281
282
283
284
285
286
287
288
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 280

def needs_heading_update?(node, number_text, title_text, number_class, title_class)
  current_number = number_class ? node.at_css("span.#{number_class}")&.text&.strip : nil
  current_title_span = title_class ? node.at_css("span.#{title_class}") : nil
  current_title = current_title_span&.text&.strip || extract_heading_core_text(current_title_span || node)

  number_changed = number_text.empty? ? !current_number.to_s.empty? : current_number != number_text
  title_changed = current_title != title_text
  number_changed || title_changed
end

.normalize_and_filter_tokens(list) ⇒ Array<String>

トークンリストを正規化してフィルタ

Parameters:

  • list (Array)

    トークンリスト

Returns:

  • (Array<String>)

    正規化された章トークンの配列



508
509
510
511
512
513
514
515
516
517
518
519
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 508

def normalize_and_filter_tokens(list)
  seen = {}
  Array(list).each_with_object([]) do |entry, acc|
    token = normalize_chapter_token(entry)
    next unless token
    next unless main_chapter_token?(token)
    next if seen[token]

    seen[token] = true
    acc << token
  end
end

.normalize_chapter_token(entry) ⇒ String?

章トークンを正規化

Parameters:

  • entry (String)

    エントリ

Returns:

  • (String, nil)

    正規化されたトークン



524
525
526
527
528
529
530
531
532
533
534
535
536
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 524

def normalize_chapter_token(entry)
  s = entry.to_s.strip
  return nil if s.empty?

  s = s.sub(%r{\A\./}, '')
  s = s.sub(%r{\A#{Regexp.escape(Common::CONTENTS_DIR)}/}i, '')
  s = s.sub(/\.(html|md)\z/i, '')
  s = s.sub(/\.(html|md)\z/i, '')
  s = s.strip
  return nil if s.empty?

  s
end

.parse_chapter_numbers_from_string(str) ⇒ Object

カンマ区切り + 範囲指定文字列から章番号配列を抽出例: “02, 11-13, 91” → [2, 11, 12, 13, 91]



456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 456

def parse_chapter_numbers_from_string(str)
  parts = str.to_s.split(',').map(&:strip).reject(&:empty?)
  numbers = []

  parts.each do |part|
    if (m = part.match(/\A(\d+)-(\d+)\z/))
      start_num = m[1].to_i
      end_num   = m[2].to_i
      next if start_num > end_num

      numbers.concat((start_num..end_num).to_a)
    elsif part.match?(/\A\d+\z/)
      numbers << part.to_i
    else
      # 数字以外が混在している場合は番号指定としては扱わない
      return nil
    end
  end

  numbers.uniq.sort
rescue StandardError
  nil
end

.parse_html_document(html) ⇒ Object

HTMLドキュメントをパース(HtmlParser に委譲)



245
246
247
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 245

def parse_html_document(html)
  HtmlParser.parse_html_document(html)
end

.process_h1_spans(doc, context) ⇒ Object

h1 のスパン処理



182
183
184
185
186
187
188
189
190
191
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 182

def process_h1_spans(doc, context)
  h1 = doc.at_css('h1')
  return false unless h1

  title_text = extract_heading_core_text(h1)
  number_text = build_h1_number_text(context)
  modified = rebuild_heading_with_spans(h1, number_text, title_text, :chapter, doc)
  update_h1_data_attributes(h1, number_text, title_text)
  modified
end

.process_h2_spans(doc, context) ⇒ Object

h2 のスパン処理



208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 208

def process_h2_spans(doc, context)
  modified = false
  doc.css('h2').each_with_index do |h2, idx|
    section_index = idx + 1
    title_text = extract_heading_core_text(h2)
    number_text = build_h2_number_text(context, section_index)
    modified |= rebuild_heading_with_spans(h2, number_text, title_text, :section, doc)
    h2['data-section-number-display'] = number_text if number_text
    h2['data-section-title'] = title_text if title_text
  end
  modified
end

.process_h3_spans(doc) ⇒ Object

h3 のスパン処理



233
234
235
236
237
238
239
240
241
242
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 233

def process_h3_spans(doc)
  marker = Common::CONFIG.dig('theme', 'markers', 'h3') || ''
  modified = false
  doc.css('h3').each do |h3|
    title_text = extract_heading_core_text(h3)
    modified |= rebuild_heading_with_spans(h3, marker, title_text, :subsection, doc)
    h3['data-subsection-title'] = title_text if title_text
  end
  modified
end

.process_heading_markers_for_file(path, max_level) ⇒ Object

単一ファイルの見出しマーカー処理



67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 67

def process_heading_markers_for_file(path, max_level)
  html = File.read(path, encoding: 'utf-8')
  doc = parse_html_document(html)
  chapter_token = extract_chapter_token(path)

  modified = false
  (1..max_level).each do |lvl|
    doc.css("h#{lvl}").each do |h|
      modified |= apply_marker_to_heading(h, lvl, chapter_token)
    end
  end

  save_html_document(path, doc) if modified
end

.rebuild_heading_with_spans(node, number_text, title_text, kind, doc) ⇒ Object

見出しを番号スパンとタイトルスパンで再構築



255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 255

def rebuild_heading_with_spans(node, number_text, title_text, kind, doc)
  number_text = number_text.to_s.strip
  title_text = title_text.to_s.strip
  number_class, title_class = heading_span_classes(kind)

  return false unless needs_heading_update?(node, number_text, title_text, number_class, title_class)

  original_title_nodes = extract_original_title_nodes(node, number_class, title_class)
  node.children.remove
  add_number_span(node, number_class, number_text, doc)
  add_title_span(node, title_class, title_text, original_title_nodes, doc)
  true
end

.resolve_appendix_letter(chapter_token, chapter_number_i) ⇒ Object

付録のレター(A/B/C…)をビルド対象の付録の順番に基づいて解決する



354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 354

def resolve_appendix_letter(chapter_token, chapter_number_i)
  override = chapter_tokens_override
  if override && !override.empty?
    # 単章/選択ビルド: ビルド対象の付録トークンから Entry を構築
    resolver = TokenResolver::Resolver.new
    entries = override.filter_map do |token|
      entry = resolver.resolve_file(token)
      entry if entry&.kind == :appendix
    end
    Common.appendix_number_to_letter(chapter_number_i, entries: entries)&.upcase
  else
    # フルビルド: catalog.yml の全付録から順番を取得
    Common.appendix_number_to_letter(chapter_number_i)&.upcase
  end
end

.resolve_main_chapter_display_number(chapter_token, chapter_number_i = nil) ⇒ Integer?

メイン章の表示番号を解決

Parameters:

  • chapter_token (String)

    章トークン

  • chapter_number_i (Integer, nil) (defaults to: nil)

    章番号

Returns:

  • (Integer, nil)

    表示番号



339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 339

def resolve_main_chapter_display_number(chapter_token, chapter_number_i = nil)
  return nil if chapter_token.nil? || chapter_token.empty?

  chapter_number_i ||= TokenResolver::Resolver.new.resolve_file(chapter_token).number&.to_i
  return nil unless chapter_number_i && MAIN_CHAPTER_RANGE.include?(chapter_number_i)

  order = main_chapter_order
  if (idx = order.index(chapter_token))
    return idx + 1
  end

  chapter_number_i
end

.save_html_document(path, doc) ⇒ Object

HTMLドキュメントを保存(HtmlParser に委譲)



250
251
252
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 250

def save_html_document(path, doc)
  HtmlParser.save_html_document(path, doc)
end

.set_attr_if_changed(node, attr, value) ⇒ Object

属性値が変わった場合のみ設定



113
114
115
116
117
118
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 113

def set_attr_if_changed(node, attr, value)
  return false if node[attr] == value

  node[attr] = value
  true
end

.set_h1_id(heading, text) ⇒ Object

h1 に id を設定



121
122
123
124
125
126
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 121

def set_h1_id(heading, text)
  return false unless heading['id'].to_s.strip.empty?

  heading['id'] = text
  true
end

.tokens_from_chapter_numbers(numbers) ⇒ Object

章番号配列からメイン章トークンの配列を生成対象は contents/*.md のうち MAIN_CHAPTER_RANGE に入る章



482
483
484
485
486
487
488
489
490
491
492
493
494
495
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 482

def tokens_from_chapter_numbers(numbers)
  return nil unless numbers&.any?

  allowed = numbers.map(&:to_i).uniq
  resolver = TokenResolver::Resolver.new

  md_tokens = Dir.glob(File.join(Common::CONTENTS_DIR, '*.md')).map { |p| File.basename(p, '.md') }
  candidates = normalize_and_filter_tokens(md_tokens)

  candidates.select do |token|
    entry = resolver.resolve_file(token)
    entry.number && allowed.include?(entry.number.to_i)
  end
end

.update_h1_data_attributes(h1, number_text, title_text) ⇒ Object

h1 の data 属性を更新



202
203
204
205
# File 'lib/vivlio/starter/cli/post_process/heading_processor.rb', line 202

def update_h1_data_attributes(h1, number_text, title_text)
  number_text ? h1['data-chapter-number-display'] = number_text : h1.delete('data-chapter-number-display')
  title_text ? h1['data-chapter-title'] = title_text : h1.delete('data-chapter-title')
end