Module: VivlioStarter::CLI::Build::Utilities

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

Overview

ビルド共通ユーティリティモジュール

Class Method Summary collapse

Class Method Details

.build_pdf_with_body_guard!(output_path, min_pages:, attempts: 3) { ... } ⇒ true

本文 PDF(閲覧用 _sections.pdf / 入稿用 _sections_print.pdf)の生成を、 本文欠落に備えてリトライ付きで実行する。

背景: 入稿用本文はトンボ・塗り足し付きの最重量レンダリングで、Chrome の 一過性失敗により本文欠落(数ページの degenerate)になる flaky があった。 従来は失敗が握り潰され、merge が front+colophon だけで「成功」してしまい、 約4ページの不正な PDF が出荷されていた。ここで本文相応のページ数を検証し、 失敗・degenerate ならリトライ、回復不能ならビルドを明示的に中断する (黙って本文欠落 PDF を残さない)。

Parameters:

  • output_path (String)

    検証対象の本文 PDF パス(block が生成する)

  • min_pages (Integer)

    本文欠落とみなす下限(これ未満なら degenerate)

  • attempts (Integer) (defaults to: 3)

    最大試行回数

Yields:

  • 本文 PDF を生成する処理。最後の式は生成コマンドの成否(Boolean)を返すこと。

Returns:

  • (true)

    本文相応の PDF を生成できた場合



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/vivlio_starter/cli/build/utilities.rb', line 63

def build_pdf_with_body_guard!(output_path, min_pages:, attempts: 3)
  attempts.times do |i|
    success = yield
    pages = page_count(output_path).to_i
    return true if success && File.exist?(output_path) && pages >= min_pages

    Common.log_warn(
      "[本文ガード] #{output_path} が生成失敗/本文欠落の疑い" \
      "(success=#{success}, #{pages}p < 想定 #{min_pages}p)。再ビルドします(#{i + 1}/#{attempts}"
    )
  end

  Common.log_error("[本文ガード] #{output_path} の本文生成に繰り返し失敗しました。ビルドを中止します。")
  exit 1
end

.chapter_numbers_for_book(entries_or_keep = nil) ⇒ Array<Integer>

1..89 範囲の章番号(整数)の配列を返す(新仕様)

Parameters:

  • entries_or_keep (Array<TokenResolver::Entry>, Array<String>, nil) (defaults to: nil)

    Entry 配列または basename 配列

Returns:

  • (Array<Integer>)

    1..89 範囲の章番号配列



82
83
84
85
86
87
88
89
# File 'lib/vivlio_starter/cli/build/utilities.rb', line 82

def chapter_numbers_for_book(entries_or_keep = nil)
  entries = resolve_entries(entries_or_keep)
  entries
    .filter_map { it.number&.to_i }
    .grep(1..89)
    .uniq
    .sort
end

.chapter_numbers_for_outline(entries_or_keep = nil) ⇒ Array<Integer>

PDF アウトライン生成対象の章番号リストを取得(新仕様)

Parameters:

  • entries_or_keep (Array<TokenResolver::Entry>, Array<String>, nil) (defaults to: nil)

    Entry 配列または basename 配列

Returns:

  • (Array<Integer>)

    アウトライン対象の章番号配列



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/vivlio_starter/cli/build/utilities.rb', line 94

def chapter_numbers_for_outline(entries_or_keep = nil)
  # 新仕様: 0=PREFACE, 1-89=CHAPTERS, 90-98=APPENDICES, 99=POSTFACE
  allowed_numbers = [0, 99] + (1..89).to_a + (90..98).to_a
  entries = resolve_entries(entries_or_keep)

  numbers = entries
            .filter_map { it.number&.to_i }
            .select { allowed_numbers.include?(it) }

  # TOC (_toc.html) はアウトライン生成時に別途処理されるため、ここでは追加不要

  numbers.uniq!
  numbers.sort!
  numbers
end

.ensure_blank_page_pdf(path = 'blank_page.pdf') ⇒ Object

空白1ページPDFを生成



128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/vivlio_starter/cli/build/utilities.rb', line 128

def ensure_blank_page_pdf(path = 'blank_page.pdf')
  return path if File.exist?(path)

  w_pt, h_pt = Build::Utilities.page_size_points_from_config
  require 'vivlio_starter/cli/pdf/provider'
  VivlioStarter::Pdf.provider.ensure_blank_page_pdf(path, w_pt, h_pt)

  # --- 旧実装(MIT化動作確認後に削除予定) ---
  # doc = HexaPDF::Document.new
  # w_pt, h_pt = Build::Utilities.page_size_points_from_config
  # doc.pages.add([0, 0, w_pt, h_pt])
  # doc.write(path, optimize: true)
  # path
end

.page_count(file) ⇒ Object

PDF のページ数を取得(pdfinfo → HexaPDF フォールバック → MIT版へ変更)



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/vivlio_starter/cli/build/utilities.rb', line 27

def page_count(file)
  return nil unless File.exist?(file)

  # pdfinfo を優先
  if system('which pdfinfo >/dev/null 2>&1')
    info = `pdfinfo "#{file}" 2>/dev/null`
    pages = info[/^Pages:\s+(\d+)/i, 1]
    return pages.to_i if pages
  end

  # 新実装 (MIT版 Provider への委譲)
  require 'vivlio_starter/cli/pdf/provider'
  VivlioStarter::Pdf.provider.page_count(file)

  # --- 旧実装(MIT化動作確認後に削除予定) ---
  # doc = HexaPDF::Document.open(file)
  # doc.pages.count
rescue StandardError
  nil
end

.page_size_points_from_configObject

現在の設定からページサイズ(pt)を取得



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/vivlio_starter/cli/build/utilities.rb', line 156

def page_size_points_from_config
  width_s, height_s = page_size_strings_from_config
  mm_to_pt = Units::PT_PER_MM
  parse_len = lambda { |s|
    str = s.to_s.strip.downcase
    if str.end_with?('mm')
      str.sub(/mm\z/, '').to_f * mm_to_pt
    elsif str.end_with?('pt')
      str.sub(/pt\z/, '').to_f
    else
      str.to_f
    end
  }
  w_pt = parse_len.call(width_s)
  h_pt = parse_len.call(height_s)
  if w_pt <= 0 || h_pt <= 0 || w_pt.nan? || h_pt.nan?
    w_pt = 182.0 * mm_to_pt
    h_pt = 257.0 * mm_to_pt
  end
  [w_pt, h_pt]
end

.page_size_strings_from_configObject

現在の設定からページサイズ(文字列: mm/pt)を取得



144
145
146
147
148
149
150
151
152
153
# File 'lib/vivlio_starter/cli/build/utilities.rb', line 144

def page_size_strings_from_config
  result = Common.resolve_page_size(Common::CONFIG.page.to_h)
  if result.is_a?(Array) && result.size == 2 && result.all? do |dim|
    dim.to_s.strip.match?(/\A[0-9.]+(mm|pt)?\z/)
  end
    result
  else
    %w[182mm 257mm]
  end
end

.resolve_entries(entries_or_keep) ⇒ Array<TokenResolver::Entry>

Entry 配列または basename 配列を Entry 配列に解決

Parameters:

Returns:



113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/vivlio_starter/cli/build/utilities.rb', line 113

def resolve_entries(entries_or_keep)
  raw = Array(entries_or_keep).compact
  if raw.empty?
    # 全ファイルを解決
    resolver = TokenResolver::Resolver.new
    Dir[File.join(Common::CONTENTS_DIR, '*.md')].map { resolver.resolve_file(it) }
  elsif raw.first.respond_to?(:kind)
    raw
  else
    resolver = TokenResolver::Resolver.new
    raw.map { resolver.resolve_file(it) }
  end
end