Module: VivlioStarter::CLI::Build::BacklinkDedupOrchestrator

Defined in:
lib/vivlio_starter/cli/build/backlink_dedup_orchestrator.rb

Overview

バックリンク重複排除の全ワークフローを管理

Class Method Summary collapse

Class Method Details

.build_sections_pdf!Object

本文用生成 config で _sections.pdf をレンダする



142
143
144
145
# File 'lib/vivlio_starter/cli/build/backlink_dedup_orchestrator.rb', line 142

def build_sections_pdf!
  config = File.join(Common::BUILD_PDF_DIR, 'vivliostyle.config.sections.js')
  PdfCommands.execute_pdf({}, config_path: config, output_path: sections_pdf)
end

.dedup_enabled?Boolean

重複排除機能が有効か 索引・用語集機能が有効で、かつ用語集または索引の重複排除が有効な場合に true 重複排除は常に行なう。索引・用語集自体が無効なときだけ走らない。

かつては index.backlink_dedup / glossary.backlink_dedup で切れたが、 切る利点が無いため 2026-08-08 に設定をやめた。オフにして浮くのは dedup 判定の 0.8 秒だけ(Step 8 の 107 秒はほぼ再レンダで、dedup の 有無に関わらず発生する)。対して失うのは可読性で、用語の出現箇所 すべてにダガー印が付き数千箇所のノイズになる (backlink-dedup-pdf-map-spec.md §4.4 の実測)。

Returns:

  • (Boolean)


88
# File 'lib/vivlio_starter/cli/build/backlink_dedup_orchestrator.rb', line 88

def dedup_enabled? = IndexCommands.index_enabled?

.dedup_target_exists?Boolean

重複排除対象の HTML が存在するか dedup はワークスペース pdf/ 配下のコピーに閉じる(P4 §3.4-4)

Returns:

  • (Boolean)


92
93
94
95
# File 'lib/vivlio_starter/cli/build/backlink_dedup_orchestrator.rb', line 92

def dedup_target_exists?
  File.exist?(File.join(Common::BUILD_PDF_DIR, '_glossarypage.html')) ||
    File.exist?(File.join(Common::BUILD_PDF_DIR, '_indexpage.html'))
end

HTML のバックリンク重複を排除

Parameters:

Returns:



123
124
125
126
# File 'lib/vivlio_starter/cli/build/backlink_dedup_orchestrator.rb', line 123

def deduplicate_backlinks(page_mapping)
  deduplicator = BacklinkDeduplicator.new(page_mapping)
  deduplicator.deduplicate!
end

.extract_page_mappingPdfPageMapExtractor::PageMapping?

ページマッピングを抽出 本文 PDF が未生成の経路(入稿用を個別レンダする print_pdf 単独ビルド)では ここで一度だけ本文をレンダする。従来この経路では preview が全ページを レンダしていたので、同等コストで実 PDF が手に入る。



108
109
110
111
112
113
114
115
116
117
118
# File 'lib/vivlio_starter/cli/build/backlink_dedup_orchestrator.rb', line 108

def extract_page_mapping
  unless File.exist?(sections_pdf)
    Common.log_action('[Step 8] ページマッピング取得のため本文 PDF を生成します…')
    build_sections_pdf!
  end

  PdfPageMapExtractor.new(sections_pdf).extract!
rescue StandardError => e
  Common.log_error("[Step 8] ページマッピング抽出に失敗: #{e.message}")
  nil
end

.log_result(result) ⇒ Object

結果をログ出力



148
149
150
151
152
153
154
155
156
157
# File 'lib/vivlio_starter/cli/build/backlink_dedup_orchestrator.rb', line 148

def log_result(result)
  Common.log_info("[Step 8] 用語集バックリンク: #{result.glossary_removed} 件の重複を削除")
  Common.log_info("[Step 8] 本文 †マーク: #{result.body_removed} 件の重複を削除")
  Common.log_info("[Step 8] 索引ページ番号: #{result.index_removed} 件の重複を削除")
  if result.files_modified.any?
    Common.log_info("[Step 8] 更新ファイル: #{result.files_modified.join(', ')}")
  else
    Common.log_info('[Step 8] 重複なし。PDF 再ビルドは不要です')
  end
end

.rebuild_pdf!(_entries = []) ⇒ Object

浄化済み HTML で本文 PDF を再ビルド 本文用生成 config(vivliostyle.config.sections.js)を再実行するだけでよい (P4 §3.2: entries は用途別ファイルのため再生成不要・出力も pdf/_sections.pdf 直行)



131
132
133
134
135
136
137
138
139
# File 'lib/vivlio_starter/cli/build/backlink_dedup_orchestrator.rb', line 131

def rebuild_pdf!(_entries = [])
  build_sections_pdf!

  if File.exist?(sections_pdf)
    Common.log_success('[Step 8] 重複排除済み _sections.pdf を再生成しました')
  else
    Common.log_warn('[Step 8] PDF 再ビルドの出力が見つかりません')
  end
end

.run!(entries = [], rebuild_pdf: true) ⇒ Boolean

重複排除を実行し、必要なら浄化済み HTML で本文 PDF を再ビルドする

Parameters:

  • entries (Array) (defaults to: [])

    ビルド対象エントリ

  • rebuild_pdf (Boolean) (defaults to: true)

    浄化後に閲覧用 _sections.pdf を再レンダするか。 閲覧用 PDF を出力する場合、または入稿用 PDF を閲覧用から導出する場合に true。 入稿用を個別レンダする経路では、print レンダ自体が浄化済み HTML を読むため不要。

Returns:

  • (Boolean)

    重複排除が実行されたか



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/vivlio_starter/cli/build/backlink_dedup_orchestrator.rb', line 39

def run!(entries = [], rebuild_pdf: true)
  unless dedup_enabled?
    Common.log_info('[Step 8] 重複排除は無効です')
    return false
  end

  unless dedup_target_exists?
    Common.log_info('[Step 8] _glossarypage.html / _indexpage.html がいずれも存在しないためスキップします')
    return false
  end

  # --- Phase 1: ページマッピング抽出(本文 PDF の /Dests から) ---
  Common.log_action('[Step 8] 重複排除を開始します…')
  page_mapping = extract_page_mapping

  return false unless page_mapping

  # --- Phase 2: HTML 浄化(用語集 + 索引を一括処理) ---
  result = deduplicate_backlinks(page_mapping)
  log_result(result)

  return false if result.files_modified.empty?

  # --- Phase 3: 浄化された HTML で本文 PDF を再ビルド ---
  if rebuild_pdf
    Common.log_action('[Step 8] 浄化済み HTML で PDF を再ビルドします…')
    rebuild_pdf!(entries)
  else
    Common.log_info('[Step 8] 閲覧用 PDF は出力しないため再ビルドを省略します')
  end
  true
rescue StandardError => e
  Common.log_warn("[Step 8] 重複排除でエラーが発生しました: #{e.message}")
  Common.log_warn('[Step 8] 重複排除をスキップし、既存の PDF で続行します')
  false
end

.sections_pdfObject

本文 PDF(マッピングの供給源であり、dedup 後の再レンダ先でもある)



98
# File 'lib/vivlio_starter/cli/build/backlink_dedup_orchestrator.rb', line 98

def sections_pdf = File.join(Common::BUILD_PDF_DIR, '_sections.pdf')