Module: Vivlio::Starter::CLI::CleanCommands

Defined in:
lib/vivlio/starter/cli/clean.rb

Overview

ビルド生成物のクリーンアップコマンド

オプション:

- (なし): 中間生成物を削除、最終 PDF は保持
- --purge: 最終 PDF も含めてすべて削除
- --cache: キャッシュディレクトリのみ削除
- --cover: 生成されたカバー画像のみ削除(マスターは保持)
- --all: 上記すべてを実行(開発者向け)

Constant Summary collapse

CLEAN_DESC =
{
  short: '不要ファイルやキャッシュを削除します',
  long: <<~DESC
    生成物(HTML/中間PDF など)を削除する標準クリーンに加えて、
    各オプションで特定のファイルのみを削除できます。
    - `vs clean`            : 生成物(HTML / 中間PDF 等)を削除(最終PDFは保持)
    - `vs clean --purge`    : 最終PDFも含めてすべて削除
    - `vs clean --cache`    : キャッシュディレクトリのみ削除(生成物は保持)
    - `vs clean --cover`    : 生成されたカバー画像のみを削除(マスターは保持)
  DESC
}.freeze

Class Method Summary collapse

Class Method Details

.add_dynamic_filename_patterns(patterns) ⇒ void

This method returns an undefined value.

config/book.yml の project.name から動的ファイル名パターンを生成し追加する

生成されるパターン例(project.name が “vivlio_starter” の場合):

- vivlio_starter*.pdf
- vivlio_starter_v*.pdf(バージョン付き)
- vivlio_starter_print*.pdf(印刷用)

Parameters:

  • patterns (Array<String>)

    削除対象パターンリスト(破壊的に追加)



219
220
221
222
223
224
225
226
227
228
229
# File 'lib/vivlio/starter/cli/clean.rb', line 219

def add_dynamic_filename_patterns(patterns)
  config = Common::CONFIG
  project_name = config.dig('project', 'name')
  return unless project_name

  patterns << "#{project_name}*.pdf"
  patterns << "#{project_name}_v*.pdf"
  patterns << "#{project_name}_print*.pdf"
  patterns << "#{project_name}*.epub"
  patterns << "#{project_name}_v*.epub"
end

.clean_bundled_variant_imagesvoid

This method returns an undefined value.

bundled テーマ用に生成されたバリアント画像を削除する

削除対象: stylesheets/images/bundled/ 内の *_portrait.webp, *_landscape.webp これらはビルド時に自動生成される派生画像であり、再生成可能



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/vivlio/starter/cli/clean.rb', line 237

def clean_bundled_variant_images
  images_dir = File.join(Common::STYLESHEETS_DIR, 'images', 'bundled')
  unless Dir.exist?(images_dir)
    Common.log_info("bundled テーマ画像ディレクトリが存在しません: #{images_dir}")
    return
  end

  Common.log_action('bundled テーマバリアント画像を削除中...')
  patterns = ['*_portrait.webp', '*_landscape.webp']
  deleted = 0

  patterns.each do |pattern|
    Dir.glob(File.join(images_dir, pattern)).each do |file|
      next unless File.file?(file)

      FileUtils.rm_f(file)
      Common.log_info("#{file} を削除しました")
      deleted += 1
    end
  end

  if deleted.zero?
    Common.log_info('削除対象の bundled バリアント画像はありませんでした')
  else
    Common.log_success("bundled テーマバリアントを削除しました(#{deleted}ファイル)")
  end
rescue StandardError => e
  Common.log_warn("bundled テーマバリアント削除中にエラー: #{e.message}")
end

.clean_cover_filesvoid

This method returns an undefined value.

生成されたカバー画像を削除する(マスター画像・ユーザーSVGは保持)

削除対象:

- covers/ 内の *.pdf, *.jpg(coverコマンドで生成されたファイル)
- covers/ 内の *_light.svg, *_dark.svg(bundledテンプレートから生成されたSVG)
- covers/ 内の *_rendered.svg(ユーザーSVGにプレースホルダー適用した中間ファイル)

保持対象:

- *.png(frontcover_master.png 等、利用者が用意した画像)
- *.key(Keynote ソースファイル)
- covers/bundled/ 内のファイル(テンプレート本体)
- light/dark 以外の *.svg(frontcover_floral.svg 等、利用者が用意したSVG)


315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/vivlio/starter/cli/clean.rb', line 315

def clean_cover_files
  config = Common.load_config
  covers_dir = config.dig(:directories, :covers) || Common::COVERS_DIR

  unless File.directory?(covers_dir)
    Common.log_info("カバーディレクトリが存在しません: #{covers_dir}")
    return
  end

  Common.log_action('生成されたカバー画像を削除中...')

  deleted_count = 0

  # PDF / JPG はすべて生成物として削除
  %w[*.pdf *.jpg].each do |pattern|
    Dir.glob(File.join(covers_dir, pattern)).each do |file_path|
      next unless File.file?(file_path)

      FileUtils.rm_f(file_path)
      Common.log_info("  削除: #{File.basename(file_path)}")
      deleted_count += 1
    end
  end

  # SVG は bundled テンプレートから生成されたもの(light/dark)と
  # プレースホルダー適用済み中間ファイル(*_rendered.svg)のみ削除
  # 利用者が用意した SVG(floral.svg 等)は保持する
  bundled_themes = %w[light dark]
  Dir.glob(File.join(covers_dir, '*.svg')).each do |file_path|
    next unless File.file?(file_path)

    basename = File.basename(file_path, '.svg') # 例: frontcover_dark
    # *_light.svg / *_dark.svg → bundled テンプレートからの生成物
    is_bundled_generated = bundled_themes.any? { |t| basename.end_with?("_#{t}") }
    # *_rendered.svg → apply_text_placeholders_to_svg の中間ファイル
    is_rendered = basename.end_with?('_rendered')

    next unless is_bundled_generated || is_rendered

    FileUtils.rm_f(file_path)
    Common.log_info("  削除: #{File.basename(file_path)}")
    deleted_count += 1
  end

  if deleted_count.zero?
    Common.log_info('削除対象のカバー画像はありませんでした')
  else
    Common.log_success("カバー画像を削除しました(#{deleted_count}ファイル)")
  end
end

.clean_index_dictionariesvoid

This method returns an undefined value.

索引・用語集辞書データを削除する(確認プロンプトあり)

削除対象:

- config/index_glossary_terms.yml(登録済み用語)
- config/index_glossary_rejected.yml(除外用語)


274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/vivlio/starter/cli/clean.rb', line 274

def clean_index_dictionaries
  targets = [
    File.join('config', 'index_glossary_terms.yml'),
    File.join('config', 'index_glossary_rejected.yml')
  ].select { |f| File.exist?(f) }

  if targets.empty?
    Common.log_info('削除対象の索引辞書ファイルはありませんでした')
    return
  end

  Common.log_always('⚠️  以下の索引・用語集辞書データを削除しようとしています:')
  targets.each { |f| Common.log_always("  - #{f}") }
  Common.log_always('これらのファイルには著者が登録した用語データが含まれています。')
  $stdout.print('本当に削除しますか? [y/N]: ')
  ans = $stdin.gets
  unless ans && ans.strip.downcase == 'y'
    Common.log_info('索引辞書データの削除をキャンセルしました')
    return
  end

  targets.each do |f|
    FileUtils.rm_f(f)
    Common.log_success("削除しました: #{f}")
  end
end

.execute_clean(option_hash) ⇒ void

This method returns an undefined value.

クリーンアップ処理のエントリーポイント

Parameters:

  • option_hash (Hash)

    オプション設定

    • :all [Boolean] すべてのクリーンオプションを有効化

    • :cover [Boolean] カバー画像のみ削除

    • :cache [Boolean] キャッシュのみ削除

    • :purge [Boolean] 最終 PDF も含めて削除

    • :generated_images [Boolean] テーマバリアント画像を削除



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/vivlio/starter/cli/clean.rb', line 66

def execute_clean(option_hash)
  opts = option_hash || {}

  # --all は他のすべてのオプションを暗黙的に有効化する(--index-dictionaries は除く)
  all_mode = opts[:all]
  cover_requested = opts[:cover] || all_mode
  cache_requested = opts[:cache] || all_mode
  purge_requested = opts[:purge] || all_mode
  variant_cleanup_requested = opts[:generated_images] || all_mode
  index_dictionaries_requested = opts[:index_dictionaries] # --all には含めない

  # カバー画像の削除(マスター画像は保持)
  clean_cover_files if cover_requested
  # テーマ用の生成済みバリアント画像を削除
  clean_bundled_variant_images if variant_cleanup_requested
  # 索引・用語集辞書データの削除(確認あり)
  clean_index_dictionaries if index_dictionaries_requested

  if cache_requested
    begin
      dir = begin
        Common.cache_dir
      rescue StandardError
        '.cache/vs'
      end
      if dir.nil? || dir.to_s.strip.empty?
        Common.log_warn('キャッシュディレクトリが不明のため中止します')
        return
      end
      if File.directory?(dir)
        Common.log_action("キャッシュディレクトリを削除中: #{dir}")
        FileUtils.rm_rf(dir)
        Common.log_success('キャッシュ削除が完了しました')
      else
        Common.log_info("キャッシュディレクトリは存在しません: #{dir}")
      end

      # metrics キャッシュも削除
      metrics_cache = File.join('.cache', 'metrics')
      if File.directory?(metrics_cache)
        Common.log_action("metrics キャッシュを削除中: #{metrics_cache}")
        FileUtils.rm_rf(metrics_cache)
        Common.log_info("#{metrics_cache} を削除しました")
      end

      # 索引のキャッシュも削除
      index_cache = '_index_matches.yml'
      if File.exist?(index_cache)
        FileUtils.rm_f(index_cache)
        Common.log_info("#{index_cache} を削除しました")
      end

      # 索引ページもキャッシュ削除時に削除対象とする
      index_page = '_indexpage.html'
      if File.exist?(index_page)
        FileUtils.rm_f(index_page)
        Common.log_info("#{index_page} を削除しました")
      end

      if File.directory?('.vivliostyle')
        Common.log_action('.vivliostyle ディレクトリを削除中...')
        FileUtils.rm_rf('.vivliostyle')
        Common.log_info('.vivliostyle ディレクトリを削除しました')
      else
        Common.log_info('.vivliostyle ディレクトリは存在しません')
      end
    rescue StandardError => e
      Common.log_warn("clean --cache 実行中にエラー: #{e}")
    end
  end

  # --cache または --cover のみが指定された場合は通常のクリーン処理をスキップ
  # --purge が指定されている、またはオプションなしの場合は通常のクリーン処理を実行
  if (cache_requested || cover_requested) && !purge_requested
    # --cache または --cover のみの場合はここで終了
    return
  end

  # BuildHelpers.clean_generated_files! と等価の処理をここに実装
  Common.log_action('.vivliostyle ディレクトリを削除中...')
  FileUtils.rm_rf('.vivliostyle')

  Common.log_action('生成ファイルを削除中...')
  cleanup_patterns = [
    # HTML/JS 中間生成物
    '*.html',
    'entries.js',
    # 生成される一時/補助的な Markdown(任意)
    '_toc.md',
    # pre_process によりプロジェクトルートへ展開される章系の Markdown のみ削除対象に限定
    # 例: 11-install.md など(任意の *.md やドキュメントは削除しない)
    '[0-9][0-9]-*.md',
    # 内部 basename 方式の特殊ページ
    '_titlepage.md', '_legalpage.md', '_colophon.md', '_indexpage.html',
    '_index_matches.yml', '_index_review.md', '_index_glossary_review.md',
    # 中扉(Part Title Page)
    '_part*.md',
    # EPUB 中間ファイル
    'vivliostyle.config.epub.js',
    'entries.epub.js'
  ]

  intermediate_pdfs = [
    # 内部名ベースの中間PDF
    '_titlepage.pdf', '_legalpage.pdf', '_colophon.pdf',
    '_titlepage_legalpage.pdf', '_sections.pdf',
    '00-preface.pdf', '_toc.pdf',
    'blank_page.pdf', 'blank_frontmatter_insert.pdf',
    'output_tmp*.pdf',
    # 入稿用 PDF の中間ファイル(Step 13)
    '_titlepage_legalpage_print.pdf', '_sections_print.pdf',
    '_colophon_print.pdf', '_blank_before_colophon.pdf',
    'output_print.pdf'
  ]
  cleanup_patterns.concat(intermediate_pdfs)

  final_pdfs = [
    Common::CONFIG.dig('pdf', 'output_file') || 'output.pdf',
    Common::CONFIG.dig('pdf', 'output_file_compressed') || 'output_compressed.pdf'
  ].uniq

  # --purge 指定時は最終PDFも削除対象に含める
  if purge_requested
    cleanup_patterns.concat(final_pdfs)
    # 単章PDF(例: 11-install.pdf, 81-install.pdf など)も削除
    # 既に個別に列挙している中間PDFと重複しても問題ない
    cleanup_patterns << '[0-9][0-9]-*.pdf'
    # 単章EPUB(例: 01-life.epub, 02-history.epub など)も削除
    cleanup_patterns << '[0-9][0-9]-*.epub'
    # 動的ファイル名のPDFおよびEPUBも削除対象に追加
    add_dynamic_filename_patterns(cleanup_patterns)
  end

  cleanup_patterns.each do |pattern|
    Dir.glob(pattern).each do |file|
      next if File.directory?(file)

      FileUtils.rm_f(file)
      Common.log_info("#{file} を削除しました")
    end
  end
  Common.log_success('不要ファイルの削除が完了しました')
end