Module: Vivlio::Starter::CLI::Common
- Defined in:
- lib/vivlio/starter/cli/common.rb
Constant Summary collapse
- REQUIRED_YAML_FILES =
— 定数定義 —
%w[ config/book.yml config/catalog.yml config/page_presets.yml config/post_replace_list.yml ].freeze
- CONFIG_FILE =
'config/book.yml'- PAGE_PRESETS_FILE =
'config/page_presets.yml'- FONT_SIZE_KEYS =
%i[base_font_size column_font_size folio_font_size].freeze
- PAGE_PRESET_EXCLUDE_KEYS =
%i[preset use preset_name].freeze
- LEVELS =
{ 'error' => 0, 'warn' => 1, 'info' => 2, 'success' => 2, 'action' => 2, 'debug' => 3 }.freeze
- CONFIG_DIR =
'config'- CONTENTS_DIR =
'contents'- STYLESHEETS_DIR =
'stylesheets'- IMAGES_DIR =
'images'- CODES_DIR =
'codes'- TEMPLATES_DIR =
'templates'- COVERS_DIR =
'covers'- VFM_COMMAND =
'vfm'- POST_REPLACE_FILE =
'post_replace_list.yml'- CACHE_DIR =
'.cache/vs'- VIVLIOSTYLE_CONFIG_FILE =
'vivliostyle.config.js'- DETAIL_INDENT =
detail 行のインデント幅(半角スペース 8 文字)
' '- PAGE_SIZES =
Page Size Utilities
{ 'A4' => { width: '210mm', height: '297mm' }, 'A5' => { width: '148mm', height: '210mm' }, 'B5' => { width: '182mm', height: '257mm' } }.freeze
- VIVLIOSTYLE_TIMINGS_KEY =
Build Timing & Step Tracking
:vivlio_starter_vivliostyle_timings- VIVLIOSTYLE_CURRENT_STEP_KEY =
:vivlio_starter_current_step_label
Class Method Summary collapse
- .abort_with_error(msg) ⇒ Object
-
.appendix_number_to_letter(num, entries: nil) ⇒ Object
付録の章番号をビルド対象の付録の順番に基づいてレター(a〜i)に変換する。 entries が渡された場合はその中の付録の順番を使い、 渡されない場合は catalog.yml の付録一覧から順番を取得する。.
- .appendix_template_path ⇒ Object
- .apply_page_preset(cfg) ⇒ Object
- .blank?(v) ⇒ Object
-
.cache_cfg ⇒ Object
キャッシュ関連.
- .cache_dir ⇒ Object
- .cache_enabled? ⇒ Object
- .chapter_template_path ⇒ Object
- .codes_dir ⇒ Object
-
.config_dir ⇒ Object
ディレクトリ関連.
- .config_dir_path ⇒ Object
-
.configured? ⇒ Object
CONFIG が未ロード(プロジェクト外)の場合に呼び出し元で検査するためのヘルパー.
- .consume_vivliostyle_build_timings ⇒ Object
- .contents_dir ⇒ Object
-
.cover_theme ⇒ Object
カバー設定関連.
- .covers_dir ⇒ Object
-
.current_log_level ⇒ Object
–log オプションから現在のログレベルを解決する。 error: 0 / warn: 1 / info,success,action: 2 / debug: 3.
- .current_step_label ⇒ Object
- .default_cache ⇒ Object
- .default_commands ⇒ Object
-
.default_directories ⇒ Object
— Hardcoded Defaults (Data objects for immutability) —.
- .default_files ⇒ Object
-
.default_vfm ⇒ Object
VFM (Vivliostyle Flavored Markdown) の既定値設定 日本語文章の直感的な執筆体験を提供するため、hardLineBreaks をデフォルト有効化.
- .default_vivliostyle ⇒ Object
- .ensure_cache_dir! ⇒ Object
- .ensure_configured! ⇒ Object
-
.ensure_external_command!(cmd, purpose: nil) ⇒ Object
コマンドが見つからない場合は vs doctor 案内付きで例外を送出する。.
-
.ensure_required_yaml_files! ⇒ Object
Validation & Loading ================================================================.
- .epub_embed? ⇒ Object
-
.external_command_available?(cmd) ⇒ Boolean
———————————————————— 外部コマンド可用性チェック ———————————————————— PATH を走査してコマンドが実行可能か判定する。.
-
.fetch_bool(obj, keys, default: false) ⇒ Object
シンボルキーのみを前提としたブール値取得.
-
.format_converter_stderr(text) ⇒ Object
run_svg_converter! 用に stderr テキストをユーザー向けに整形する。 空のとき / 長すぎるときを吸収する。.
-
.format_detail(detail) ⇒ Object
detail 文字列を行配列に変換する。nil の場合は空配列を返す。 log_* からのみ呼ばれる内部ヘルパー。.
- .format_pt(value) ⇒ Object
- .generate_compressed_pdf_filename(target = 'pdf') ⇒ Object
- .generate_epub_filename ⇒ Object
-
.generate_output_filename(target = 'pdf', suffix: nil) ⇒ Object
Output Filename Generation ================================================================.
- .generate_print_pdf_filename ⇒ Object
- .images_dir ⇒ Object
-
.load_config ⇒ Object
book.yml を読み込み、ハードコーディングされた既定値をマージして返す.
- .load_page_presets ⇒ Object
-
.log_action(msg) ⇒ Object
処理ステップの開始・進行(🔧)。–log=info 以上で表示。.
-
.log_always(msg) ⇒ Object
アイコンなしで常に表示する汎用出力。.
-
.log_debug(msg) ⇒ Object
デバッグ情報(🧪)。–log=debug のみ表示。.
-
.log_error(msg, detail: nil) ⇒ Object
エラー(🔴)。ログレベルに関わらず常に表示。.
-
.log_info(msg) ⇒ Object
補足情報・処理の詳細(🔵)。–log=info 以上で表示。.
-
.log_inspection(msg) ⇒ Object
詳細診断情報(🔍)。–log=info 以上で表示。.
-
.log_result(msg, status:) ⇒ Object
処理の最終結果を報告する(✅/❌/📚)。ログレベルに関わらず常に表示。.
-
.log_success(msg) ⇒ Object
処理の成功(✅)。–log=info 以上で表示。.
-
.log_summary(msg, detail: nil) ⇒ Object
検証結果の集計サマリー(🔍)。ログレベルに関わらず常に表示。.
-
.log_warn(msg, detail: nil) ⇒ Object
注意・警告(🟡)。–log=warn 以上(既定)で表示。.
-
.merge_hardcoded_defaults(cfg) ⇒ Object
ハードコーディングされた既定値をマージする book.yml に記述がなくても、これらの値は常に利用可能.
-
.missing_external_command_message(cmd, purpose: nil) ⇒ String
外部コマンドが見つからない際の案内メッセージを生成する。 ‘vs doctor` / `vs doctor –fix` への誘導を含む。.
- .normalize_font_sizes(pcfg) ⇒ Object
- .normalize_line_height(pcfg) ⇒ Object
- .normalize_page_size!(page_cfg) ⇒ Object
-
.normalize_page_units(pcfg) ⇒ Object
Normalization (Unit conversion) ================================================================.
- .pdf_combined? ⇒ Object
- .pdf_compress? ⇒ Object
-
.post_replace_file ⇒ Object
ファイル関連.
- .post_replace_file_path ⇒ Object
- .postface_template_path ⇒ Object
- .preface_template_path ⇒ Object
- .pt_value(value) ⇒ Object
- .q_to_pt(value) ⇒ Object
- .record_vivliostyle_build(duration, label = nil) ⇒ Object
- .relative_path_from_root(path) ⇒ Object
-
.reload_configuration!(silent: false) ⇒ Object
定数を安全に(警告なしで)再定義する.
- .reset_vivliostyle_build_timings ⇒ Object
-
.resolve_page_size(page_cfg) ⇒ Object
ページサイズを解決する(シンボルキー前提).
-
.resolve_path_from_root(path) ⇒ Object
Path Utilities ================================================================.
-
.run_svg_converter!(argv, input_path:, output_path: nil, purpose: nil) ⇒ Boolean
外部 SVG 変換コマンド(rsvg-convert / ImageMagick 等)を実行し、 失敗した場合はユーザー向けの整形済みエラーメッセージを出力する。.
- .stylesheets_dir ⇒ Object
- .template_path(name) ⇒ Object
- .templates_dir ⇒ Object
-
.to_roman_lower(n) ⇒ Object
Chapter Utilities ================================================================.
-
.truthy?(val) ⇒ Object
Helpers ================================================================.
-
.validate_book_config!(cfg) ⇒ Object
book.yml の主要キー(book.main_title, book.author, project.name)が 欠落していないかを検査し、欠落があれば警告を出す。 既存の最小構成プロジェクトとの互換性を保つため abort はせず、 PDF 生成時にタイトルが空になる等の問題にユーザーが早期に気付けるようにする。.
-
.validate_cover_settings ⇒ Object
カバー設定のバリデーション.
- .verbose? ⇒ Object
-
.vfm_command ⇒ Object
コマンド関連.
- .with_current_step_label(label) ⇒ Object
-
.wrap_config(input) ⇒ Object
Hashを再帰的にDataオブジェクトに変換するヘルパー ドット記法と [] アクセスの両方を提供します.
Class Method Details
.abort_with_error(msg) ⇒ Object
630 631 632 633 634 |
# File 'lib/vivlio/starter/cli/common.rb', line 630 def abort_with_error(msg) log_error(msg) log_error('コマンドを中止します') exit 1 end |
.appendix_number_to_letter(num, entries: nil) ⇒ Object
付録の章番号をビルド対象の付録の順番に基づいてレター(a〜i)に変換する。entries が渡された場合はその中の付録の順番を使い、渡されない場合は catalog.yml の付録一覧から順番を取得する。
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 |
# File 'lib/vivlio/starter/cli/common.rb', line 487 def appendix_number_to_letter(num, entries: nil) n = num.to_i return nil unless n.between?(90, 98) # ビルド対象のエントリが渡された場合はその中の付録の順番を使う appendix_entries = if entries entries.select { it.kind == :appendix }.sort_by { it.number.to_i } else resolver = TokenResolver::Resolver.new resolver.resolve.select { it.kind == :appendix }.sort_by { it.number.to_i } end index = appendix_entries.index { it.number.to_i == n } return ('a'..'i').to_a[index] if index # 見つからない場合は章番号から直接計算(フォールバック) ('a'..'i').to_a[n - 90] rescue StandardError nil end |
.appendix_template_path ⇒ Object
709 |
# File 'lib/vivlio/starter/cli/common.rb', line 709 def appendix_template_path = template_path('appendix') |
.apply_page_preset(cfg) ⇒ Object
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/vivlio/starter/cli/common.rb', line 160 def apply_page_preset(cfg) case cfg in { page: { **page_cfg } } preset_name = page_cfg.values_at(*PAGE_PRESET_EXCLUDE_KEYS).find { _1 } return cfg if blank?(preset_name) presets = load_page_presets case presets[preset_name.to_sym] in Hash => selected overrides = page_cfg.reject { PAGE_PRESET_EXCLUDE_KEYS.include?(it) } merged = selected.merge(overrides).merge(page_cfg) cfg.merge(page: normalize_page_units(merged)) else cfg end else cfg end end |
.blank?(v) ⇒ Object
433 |
# File 'lib/vivlio/starter/cli/common.rb', line 433 def blank?(v) = v.nil? || v.to_s.strip.empty? |
.cache_cfg ⇒ Object
キャッシュ関連
713 |
# File 'lib/vivlio/starter/cli/common.rb', line 713 def cache_cfg = CONFIG&.cache |
.cache_dir ⇒ Object
714 |
# File 'lib/vivlio/starter/cli/common.rb', line 714 def cache_dir = CONFIG&.cache&.dir || CACHE_DIR |
.cache_enabled? ⇒ Object
715 |
# File 'lib/vivlio/starter/cli/common.rb', line 715 def cache_enabled? = CONFIG&.cache&.enabled != false |
.chapter_template_path ⇒ Object
707 |
# File 'lib/vivlio/starter/cli/common.rb', line 707 def chapter_template_path = template_path('chapter') |
.codes_dir ⇒ Object
699 |
# File 'lib/vivlio/starter/cli/common.rb', line 699 def codes_dir = CONFIG&.directories&.codes || CODES_DIR |
.config_dir ⇒ Object
ディレクトリ関連
694 |
# File 'lib/vivlio/starter/cli/common.rb', line 694 def config_dir = CONFIG&.directories&.config || CONFIG_DIR |
.config_dir_path ⇒ Object
695 |
# File 'lib/vivlio/starter/cli/common.rb', line 695 def config_dir_path = resolve_path_from_root(config_dir) |
.configured? ⇒ Object
CONFIG が未ロード(プロジェクト外)の場合に呼び出し元で検査するためのヘルパー
681 |
# File 'lib/vivlio/starter/cli/common.rb', line 681 def configured? = !CONFIG.nil? |
.consume_vivliostyle_build_timings ⇒ Object
593 594 595 596 597 |
# File 'lib/vivlio/starter/cli/common.rb', line 593 def consume_vivliostyle_build_timings timings = Thread.current[VIVLIOSTYLE_TIMINGS_KEY] || [] Thread.current[VIVLIOSTYLE_TIMINGS_KEY] = [] timings end |
.contents_dir ⇒ Object
696 |
# File 'lib/vivlio/starter/cli/common.rb', line 696 def contents_dir = CONFIG&.directories&.contents || CONTENTS_DIR |
.cover_theme ⇒ Object
カバー設定関連
736 |
# File 'lib/vivlio/starter/cli/common.rb', line 736 def cover_theme = CONFIG.dig('output', 'cover') |
.covers_dir ⇒ Object
701 |
# File 'lib/vivlio/starter/cli/common.rb', line 701 def covers_dir = CONFIG&.directories&.covers || COVERS_DIR |
.current_log_level ⇒ Object
–log オプションから現在のログレベルを解決する。error: 0 / warn: 1 / info,success,action: 2 / debug: 3
229 230 231 232 233 234 235 236 |
# File 'lib/vivlio/starter/cli/common.rb', line 229 def current_log_level case ARGV in [*, /^--log=(.+)$/, *] then LEVELS[::Regexp.last_match(1).downcase] || 2 in [*, '--log', level, *] if LEVELS.key?(level) then LEVELS[level] in [*, '--log', *] then 2 else 1 end end |
.current_step_label ⇒ Object
607 608 609 |
# File 'lib/vivlio/starter/cli/common.rb', line 607 def current_step_label Thread.current[VIVLIOSTYLE_CURRENT_STEP_KEY] end |
.default_cache ⇒ Object
139 |
# File 'lib/vivlio/starter/cli/common.rb', line 139 def default_cache = { dir: CACHE_DIR, enabled: true } |
.default_commands ⇒ Object
140 |
# File 'lib/vivlio/starter/cli/common.rb', line 140 def default_commands = { vfm: VFM_COMMAND } |
.default_directories ⇒ Object
— Hardcoded Defaults (Data objects for immutability) —
127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/vivlio/starter/cli/common.rb', line 127 def default_directories { config: CONFIG_DIR, contents: CONTENTS_DIR, stylesheets: STYLESHEETS_DIR, images: IMAGES_DIR, codes: CODES_DIR, templates: TEMPLATES_DIR, covers: COVERS_DIR } end |
.default_files ⇒ Object
141 |
# File 'lib/vivlio/starter/cli/common.rb', line 141 def default_files = { post_replace: POST_REPLACE_FILE } |
.default_vfm ⇒ Object
VFM (Vivliostyle Flavored Markdown) の既定値設定日本語文章の直感的な執筆体験を提供するため、hardLineBreaks をデフォルト有効化
154 155 156 157 158 |
# File 'lib/vivlio/starter/cli/common.rb', line 154 def default_vfm { hardLineBreaks: true } end |
.default_vivliostyle ⇒ Object
143 144 145 146 147 148 149 150 |
# File 'lib/vivlio/starter/cli/common.rb', line 143 def default_vivliostyle { quiet: true, reading_progression: 'ltr', entries_file: 'entries.js', config_file: VIVLIOSTYLE_CONFIG_FILE } end |
.ensure_cache_dir! ⇒ Object
457 458 459 460 461 |
# File 'lib/vivlio/starter/cli/common.rb', line 457 def ensure_cache_dir! dir = cache_dir FileUtils.mkdir_p(dir) dir end |
.ensure_configured! ⇒ Object
683 684 685 686 687 |
# File 'lib/vivlio/starter/cli/common.rb', line 683 def ensure_configured! return if configured? abort_with_error('必須設定ファイルが見つかりません: config/book.yml') end |
.ensure_external_command!(cmd, purpose: nil) ⇒ Object
コマンドが見つからない場合は vs doctor 案内付きで例外を送出する。
351 352 353 354 355 |
# File 'lib/vivlio/starter/cli/common.rb', line 351 def ensure_external_command!(cmd, purpose: nil) return if external_command_available?(cmd) raise (cmd, purpose: purpose) end |
.ensure_required_yaml_files! ⇒ Object
Validation & Loading
91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/vivlio/starter/cli/common.rb', line 91 def ensure_required_yaml_files! REQUIRED_YAML_FILES.each do |path| abort_with_error("必須設定ファイルが見つかりません: #{path}") unless File.file?(path) case YAML.safe_load(File.read(path, encoding: 'utf-8'), aliases: true, symbolize_names: true) in Hash | Array # Valid else abort_with_error("必須設定ファイルの内容が空、または形式が不正です: #{path}") end rescue StandardError => e abort_with_error("必須設定ファイルの解析に失敗しました (#{path}): #{e.}") end end |
.epub_embed? ⇒ Object
739 |
# File 'lib/vivlio/starter/cli/common.rb', line 739 def = CONFIG.dig('output', 'epub', 'embed') == true |
.external_command_available?(cmd) ⇒ Boolean
外部コマンド可用性チェック
PATH を走査してコマンドが実行可能か判定する。
314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/vivlio/starter/cli/common.rb', line 314 def external_command_available?(cmd) candidate = cmd.to_s.strip return false if candidate.empty? if candidate.include?(File::SEPARATOR) return File.executable?(candidate) && !File.directory?(candidate) end ENV.fetch('PATH', '').split(File::PATH_SEPARATOR).any? do |dir| path = File.join(dir, candidate) File.executable?(path) && !File.directory?(path) end end |
.fetch_bool(obj, keys, default: false) ⇒ Object
シンボルキーのみを前提としたブール値取得
616 617 618 619 620 621 622 623 624 625 626 627 628 |
# File 'lib/vivlio/starter/cli/common.rb', line 616 def fetch_bool(obj, keys, default: false) cur = obj Array(keys).each do |k| return default unless cur.respond_to?(:[]) cur = cur[k.to_sym] end return default if cur.nil? truthy?(cur) rescue StandardError default end |
.format_converter_stderr(text) ⇒ Object
run_svg_converter! 用に stderr テキストをユーザー向けに整形する。空のとき / 長すぎるときを吸収する。
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 |
# File 'lib/vivlio/starter/cli/common.rb', line 402 def format_converter_stderr(text) trimmed = text.to_s.strip return 'stderr: (出力なし)' if trimmed.empty? lines = trimmed.lines.map(&:chomp) shown = if lines.size > 12 head = lines.first(8) tail = lines.last(3) [*head, ' ... (中略) ...', *tail] else lines end indented = shown.map { |l| " #{l}" }.join("\n") "stderr:\n#{indented}" end |
.format_detail(detail) ⇒ Object
detail 文字列を行配列に変換する。nil の場合は空配列を返す。log_* からのみ呼ばれる内部ヘルパー。
301 302 303 304 305 |
# File 'lib/vivlio/starter/cli/common.rb', line 301 def format_detail(detail) return [] if detail.nil? detail.lines.map(&:chomp) end |
.format_pt(value) ⇒ Object
218 |
# File 'lib/vivlio/starter/cli/common.rb', line 218 def format_pt(value) = "#{value.to_f.round(3)}pt" |
.generate_compressed_pdf_filename(target = 'pdf') ⇒ Object
570 571 572 573 574 |
# File 'lib/vivlio/starter/cli/common.rb', line 570 def generate_compressed_pdf_filename(target = 'pdf') # 新しい設定構造ではsuffixは"_compressed"に固定 suffix = 'compressed' generate_output_filename(target, suffix: suffix) end |
.generate_epub_filename ⇒ Object
568 |
# File 'lib/vivlio/starter/cli/common.rb', line 568 def generate_epub_filename = generate_output_filename('epub') |
.generate_output_filename(target = 'pdf', suffix: nil) ⇒ Object
Output Filename Generation
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 |
# File 'lib/vivlio/starter/cli/common.rb', line 546 def generate_output_filename(target = 'pdf', suffix: nil) project = CONFIG[:project] project_name = project&.name || 'vivlio_starter' project_version = project&.version include_version = CONFIG.dig(:output, :filename, :include_version) || false filename = project_name.to_s.dup filename += '_print' if target == 'print_pdf' filename += "_v#{project_version}" if include_version && !blank?(project_version) if suffix && !blank?(suffix) && target == 'pdf' filename += (suffix.to_s.start_with?('_') ? suffix : "_#{suffix}") end ext = case target when 'pdf', 'print_pdf' then '.pdf' when 'epub' then '.epub' else '.pdf' end filename + ext end |
.generate_print_pdf_filename ⇒ Object
567 |
# File 'lib/vivlio/starter/cli/common.rb', line 567 def generate_print_pdf_filename = generate_output_filename('print_pdf') |
.images_dir ⇒ Object
698 |
# File 'lib/vivlio/starter/cli/common.rb', line 698 def images_dir = CONFIG&.directories&.images || IMAGES_DIR |
.load_config ⇒ Object
book.yml を読み込み、ハードコーディングされた既定値をマージして返す
107 108 109 110 111 |
# File 'lib/vivlio/starter/cli/common.rb', line 107 def load_config YAML.load_file(CONFIG_FILE, aliases: true, symbolize_names: true) => raw_config cfg = apply_page_preset(raw_config) merge_hardcoded_defaults(cfg) end |
.load_page_presets ⇒ Object
180 181 182 |
# File 'lib/vivlio/starter/cli/common.rb', line 180 def load_page_presets YAML.load_file(PAGE_PRESETS_FILE, aliases: true, symbolize_names: true) end |
.log_action(msg) ⇒ Object
処理ステップの開始・進行(🔧)。–log=info 以上で表示。
263 264 265 |
# File 'lib/vivlio/starter/cli/common.rb', line 263 def log_action(msg) puts("🔧 #{msg}") if current_log_level >= 2 end |
.log_always(msg) ⇒ Object
アイコンなしで常に表示する汎用出力。
295 296 297 |
# File 'lib/vivlio/starter/cli/common.rb', line 295 def log_always(msg) puts(msg) end |
.log_debug(msg) ⇒ Object
デバッグ情報(🧪)。–log=debug のみ表示。
268 269 270 |
# File 'lib/vivlio/starter/cli/common.rb', line 268 def log_debug(msg) puts("🧪 #{msg}") if current_log_level >= 3 end |
.log_error(msg, detail: nil) ⇒ Object
エラー(🔴)。ログレベルに関わらず常に表示。
257 258 259 260 |
# File 'lib/vivlio/starter/cli/common.rb', line 257 def log_error(msg, detail: nil) puts("🔴 #{msg}") format_detail(detail).each { |line| puts("#{DETAIL_INDENT}#{line}") } end |
.log_info(msg) ⇒ Object
補足情報・処理の詳細(🔵)。–log=info 以上で表示。
239 240 241 |
# File 'lib/vivlio/starter/cli/common.rb', line 239 def log_info(msg) puts("🔵 #{msg}") if current_log_level >= 2 end |
.log_inspection(msg) ⇒ Object
詳細診断情報(🔍)。–log=info 以上で表示。
279 280 281 |
# File 'lib/vivlio/starter/cli/common.rb', line 279 def log_inspection(msg) puts "🔍 #{msg}" if current_log_level >= 2 end |
.log_result(msg, status:) ⇒ Object
処理の最終結果を報告する(✅/❌/📚)。ログレベルに関わらず常に表示。
285 286 287 288 289 290 291 292 |
# File 'lib/vivlio/starter/cli/common.rb', line 285 def log_result(msg, status:) icon = case status when :success then "✅" when :failure then "❌" when :artifact then "📚" end puts "#{icon} #{msg}" end |
.log_success(msg) ⇒ Object
処理の成功(✅)。–log=info 以上で表示。
244 245 246 |
# File 'lib/vivlio/starter/cli/common.rb', line 244 def log_success(msg) puts("✅ #{msg}") if current_log_level >= 2 end |
.log_summary(msg, detail: nil) ⇒ Object
検証結果の集計サマリー(🔍)。ログレベルに関わらず常に表示。
273 274 275 276 |
# File 'lib/vivlio/starter/cli/common.rb', line 273 def log_summary(msg, detail: nil) puts "🔍 #{msg}" format_detail(detail).each { |line| puts("#{DETAIL_INDENT}#{line}") } end |
.log_warn(msg, detail: nil) ⇒ Object
注意・警告(🟡)。–log=warn 以上(既定)で表示。
249 250 251 252 253 254 |
# File 'lib/vivlio/starter/cli/common.rb', line 249 def log_warn(msg, detail: nil) return unless current_log_level >= 1 puts("🟡 #{msg}") format_detail(detail).each { |line| puts("#{DETAIL_INDENT}#{line}") } end |
.merge_hardcoded_defaults(cfg) ⇒ Object
ハードコーディングされた既定値をマージするbook.yml に記述がなくても、これらの値は常に利用可能
115 116 117 118 119 120 121 122 123 124 |
# File 'lib/vivlio/starter/cli/common.rb', line 115 def merge_hardcoded_defaults(cfg) cfg.merge( directories: default_directories.merge(cfg[:directories] || {}), cache: default_cache.merge(cfg[:cache] || {}), commands: default_commands.merge(cfg[:commands] || {}), files: default_files.merge(cfg[:files] || {}), vivliostyle: default_vivliostyle.merge(cfg[:vivliostyle] || {}), vfm: default_vfm.merge(cfg[:vfm] || {}) ) end |
.missing_external_command_message(cmd, purpose: nil) ⇒ String
外部コマンドが見つからない際の案内メッセージを生成する。‘vs doctor` / `vs doctor –fix` への誘導を含む。
333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/vivlio/starter/cli/common.rb', line 333 def (cmd, purpose: nil) header = if purpose && !purpose.to_s.strip.empty? "#{purpose}に必要な外部コマンドが見つかりません: #{cmd}" else "必要な外部コマンドが見つかりません: #{cmd}" end <<~MSG.strip #{header} 環境診断と自動セットアップを試すには: vs doctor # 不足しているツールの一覧を表示 vs doctor --fix # macOS なら Homebrew で自動インストールを試行 MSG end |
.normalize_font_sizes(pcfg) ⇒ Object
195 196 197 198 199 200 201 202 |
# File 'lib/vivlio/starter/cli/common.rb', line 195 def normalize_font_sizes(pcfg) FONT_SIZE_KEYS.each_with_object({}) do |key, memo| case pcfg[key]&.to_s&.strip in /q\z/i => s then memo[key] = q_to_pt(s) else # Skip end end end |
.normalize_line_height(pcfg) ⇒ Object
204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/vivlio/starter/cli/common.rb', line 204 def normalize_line_height(pcfg) case [pcfg[:base_line_height]&.to_s&.strip, pt_value(pcfg[:base_font_size])] in [nil | '', _] then nil in [_, nil] then pcfg[:base_line_height] in [/pt\z/i => s, _] then s in [/q\z/i => s, _] then q_to_pt(s) in [/em\z/i => s, f_pt] then format_pt(f_pt * s.to_f) in [/\A[\d.]+\z/ => s, f_pt] then format_pt(f_pt * s.to_f) in [other, _] then other end end |
.normalize_page_size!(page_cfg) ⇒ Object
533 534 535 536 537 538 539 540 |
# File 'lib/vivlio/starter/cli/common.rb', line 533 def normalize_page_size!(page_cfg) return page_cfg unless page_cfg.is_a?(Hash) w, h = resolve_page_size(page_cfg) page_cfg[:width] = w page_cfg[:height] = h page_cfg end |
.normalize_page_units(pcfg) ⇒ Object
Normalization (Unit conversion)
188 189 190 191 192 193 |
# File 'lib/vivlio/starter/cli/common.rb', line 188 def normalize_page_units(pcfg) pcfg.merge( **normalize_font_sizes(pcfg), base_line_height: normalize_line_height(pcfg) ).compact end |
.pdf_combined? ⇒ Object
737 |
# File 'lib/vivlio/starter/cli/common.rb', line 737 def pdf_combined? = CONFIG.dig('output', 'pdf', 'combined') == true |
.pdf_compress? ⇒ Object
738 |
# File 'lib/vivlio/starter/cli/common.rb', line 738 def pdf_compress? = CONFIG.dig('output', 'pdf', 'compress') == true |
.post_replace_file ⇒ Object
ファイル関連
721 |
# File 'lib/vivlio/starter/cli/common.rb', line 721 def post_replace_file = CONFIG&.files&.post_replace || POST_REPLACE_FILE |
.post_replace_file_path ⇒ Object
723 724 725 726 727 728 729 730 731 732 733 |
# File 'lib/vivlio/starter/cli/common.rb', line 723 def post_replace_file_path file = post_replace_file return nil if blank?(file) pn = Pathname.new(file) base = Pathname.new(config_dir) pn = base.join(pn) unless pn.absolute? pn.cleanpath.to_s rescue StandardError resolve_path_from_root(file) end |
.postface_template_path ⇒ Object
710 |
# File 'lib/vivlio/starter/cli/common.rb', line 710 def postface_template_path = template_path('postface') |
.preface_template_path ⇒ Object
708 |
# File 'lib/vivlio/starter/cli/common.rb', line 708 def preface_template_path = template_path('preface') |
.pt_value(value) ⇒ Object
217 |
# File 'lib/vivlio/starter/cli/common.rb', line 217 def pt_value(value) = value&.to_s&.match(/\A([\d.]+)pt\z/i)&.[](1)&.to_f |
.q_to_pt(value) ⇒ Object
216 |
# File 'lib/vivlio/starter/cli/common.rb', line 216 def q_to_pt(value) = format_pt(value.to_f * 0.709) |
.record_vivliostyle_build(duration, label = nil) ⇒ Object
587 588 589 590 591 |
# File 'lib/vivlio/starter/cli/common.rb', line 587 def record_vivliostyle_build(duration, label = nil) timings = Thread.current[VIVLIOSTYLE_TIMINGS_KEY] ||= [] label_text = label.to_s.empty? ? 'Vivliostyle build' : label.to_s timings << { duration: duration.to_f, label: label_text } end |
.relative_path_from_root(path) ⇒ Object
449 450 451 452 453 454 455 |
# File 'lib/vivlio/starter/cli/common.rb', line 449 def relative_path_from_root(path) return path if blank?(path) Pathname.new(path).relative_path_from(Pathname.new(Dir.pwd)).to_s rescue StandardError path.to_s end |
.reload_configuration!(silent: false) ⇒ Object
定数を安全に(警告なしで)再定義する
638 639 640 641 642 643 644 645 646 647 648 649 650 651 |
# File 'lib/vivlio/starter/cli/common.rb', line 638 def reload_configuration!(silent: false) ensure_required_yaml_files! # load_configの結果をDataオブジェクトにラップしてフリーズ raw_config = load_config validate_book_config!(raw_config) unless silent new_config = wrap_config(raw_config).freeze # 定数の再定義(既存なら削除して警告を回避) remove_const(:CONFIG) if const_defined?(:CONFIG) const_set(:CONFIG, new_config) puts("🧪 Configuration reloaded: #{CONFIG_FILE}") if !silent && current_log_level >= 3 end |
.reset_vivliostyle_build_timings ⇒ Object
583 584 585 |
# File 'lib/vivlio/starter/cli/common.rb', line 583 def reset_vivliostyle_build_timings Thread.current[VIVLIOSTYLE_TIMINGS_KEY] = [] end |
.resolve_page_size(page_cfg) ⇒ Object
ページサイズを解決する(シンボルキー前提)
519 520 521 522 523 524 525 526 527 528 529 530 531 |
# File 'lib/vivlio/starter/cli/common.rb', line 519 def resolve_page_size(page_cfg) pcfg = page_cfg.is_a?(Hash) ? page_cfg : {} size = pcfg[:size].to_s.strip.upcase defaults = PAGE_SIZES[size] || PAGE_SIZES['B5'] width = pcfg[:width]&.to_s&.strip height = pcfg[:height]&.to_s&.strip [ width.to_s.empty? ? defaults[:width] : width, height.to_s.empty? ? defaults[:height] : height ] end |
.resolve_path_from_root(path) ⇒ Object
Path Utilities
439 440 441 442 443 444 445 446 447 |
# File 'lib/vivlio/starter/cli/common.rb', line 439 def resolve_path_from_root(path) return nil if blank?(path) pn = Pathname.new(path) pn = Pathname.new(Dir.pwd).join(pn) unless pn.absolute? pn.cleanpath.to_s rescue StandardError path end |
.run_svg_converter!(argv, input_path:, output_path: nil, purpose: nil) ⇒ Boolean
外部 SVG 変換コマンド(rsvg-convert / ImageMagick 等)を実行し、失敗した場合はユーザー向けの整形済みエラーメッセージを出力する。
堅牢性仕様 7-1: 不正な SVG XML 等で外部コマンドが失敗した際に、従来はサイレントに下流で ‘No such file` となっていた問題を解消する。
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 |
# File 'lib/vivlio/starter/cli/common.rb', line 369 def run_svg_converter!(argv, input_path:, output_path: nil, purpose: nil) require 'open3' _stdout, stderr, status = Open3.capture3(*argv) exit_ok = status.success? file_ok = output_path.nil? || File.exist?(output_path) return true if exit_ok && file_ok command_name = argv.first purpose_hint = purpose && !purpose.to_s.strip.empty? ? "(#{purpose})" : '' reason = if !exit_ok "終了コード: #{status.exitstatus || 'unknown'}" else '出力ファイルが生成されませんでした' end stderr_digest = format_converter_stderr(stderr) log_error(<<~MSG.strip) SVG 変換に失敗しました#{purpose_hint}: #{input_path} 実行コマンド: #{command_name} #{reason} #{stderr_digest} MSG false rescue Errno::ENOENT => e log_error("SVG 変換コマンドが見つかりません: #{argv.first} (#{e.})") false rescue StandardError => e log_error("SVG 変換中に予期せぬ例外が発生しました: #{e.class}: #{e.} (input=#{input_path})") false end |
.stylesheets_dir ⇒ Object
697 |
# File 'lib/vivlio/starter/cli/common.rb', line 697 def stylesheets_dir = CONFIG&.directories&.stylesheets || STYLESHEETS_DIR |
.template_path(name) ⇒ Object
703 704 705 |
# File 'lib/vivlio/starter/cli/common.rb', line 703 def template_path(name) File.join(templates_dir, "#{name}.md") end |
.templates_dir ⇒ Object
700 |
# File 'lib/vivlio/starter/cli/common.rb', line 700 def templates_dir = CONFIG&.directories&.templates || TEMPLATES_DIR |
.to_roman_lower(n) ⇒ Object
Chapter Utilities
467 468 469 470 471 472 473 474 475 476 477 478 479 480 |
# File 'lib/vivlio/starter/cli/common.rb', line 467 def to_roman_lower(n) return '' if n.to_i <= 0 n = n.to_i mapping = [ [1000, 'm'], [900, 'cm'], [500, 'd'], [400, 'cd'], [100, 'c'], [90, 'xc'], [50, 'l'], [40, 'xl'], [10, 'x'], [9, 'ix'], [5, 'v'], [4, 'iv'], [1, 'i'] ] mapping.each_with_object(String.new) do |(val, sym), res| count, n = n.divmod(val) res << (sym * count) end end |
.truthy?(val) ⇒ Object
Helpers
426 427 428 429 430 431 |
# File 'lib/vivlio/starter/cli/common.rb', line 426 def truthy?(val) case val&.to_s&.strip&.downcase in true | 'true' | 'yes' | 'on' | '1' then true else false end end |
.validate_book_config!(cfg) ⇒ Object
book.yml の主要キー(book.main_title, book.author, project.name)が欠落していないかを検査し、欠落があれば警告を出す。既存の最小構成プロジェクトとの互換性を保つため abort はせず、PDF 生成時にタイトルが空になる等の問題にユーザーが早期に気付けるようにする。
658 659 660 661 662 663 664 665 666 667 668 |
# File 'lib/vivlio/starter/cli/common.rb', line 658 def validate_book_config!(cfg) missing = [] missing << 'book.main_title' if blank?(cfg.dig(:book, :main_title)) missing << 'book.author' if blank?(cfg.dig(:book, :author)) missing << 'project.name' if blank?(cfg.dig(:project, :name)) return if missing.empty? warn "[book.yml] 警告: 以下の推奨キーが未設定です: #{missing.join(', ')}" warn " config/book.yml を編集して値を設定してください。未設定のままでも動作しますが、" warn ' PDF のタイトル・著者・出力ファイル名が空欄になります。' end |
.validate_cover_settings ⇒ Object
カバー設定のバリデーション
742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 |
# File 'lib/vivlio/starter/cli/common.rb', line 742 def validate_cover_settings theme = cover_theme unless theme log_error('output.cover 設定が見つかりません') return false end # 標準テーマの場合は有効 return true if %w[light dark].include?(theme) # masterテーマは特別扱い(既存のmaster.pngファイルを使用) if theme == 'master' front_path = File.join(covers_dir, "frontcover_#{theme}.png") back_path = File.join(covers_dir, "backcover_#{theme}.png") unless File.exist?(front_path) && File.exist?(back_path) log_error("マスター画像 '#{theme}' のPNGファイルが見つかりません") return false end return true end # カスタムテーマの場合は命名規則をチェック unless theme.match?(/\A[a-z0-9_]+\z/) log_error("テーマ名 '#{theme}' は無効な形式です") return false end # カスタムテーマの場合はPNGファイルの存在を確認 front_path = File.join(covers_dir, "frontcover_#{theme}.png") back_path = File.join(covers_dir, "backcover_#{theme}.png") unless File.exist?(front_path) && File.exist?(back_path) log_error("カスタム画像 '#{theme}' のPNGファイルが見つかりません") return false end true end |
.verbose? ⇒ Object
418 419 420 |
# File 'lib/vivlio/starter/cli/common.rb', line 418 def verbose? current_log_level >= 2 end |
.vfm_command ⇒ Object
コマンド関連
718 |
# File 'lib/vivlio/starter/cli/common.rb', line 718 def vfm_command = CONFIG&.commands&.vfm || VFM_COMMAND |
.with_current_step_label(label) ⇒ Object
599 600 601 602 603 604 605 |
# File 'lib/vivlio/starter/cli/common.rb', line 599 def with_current_step_label(label) previous = Thread.current[VIVLIOSTYLE_CURRENT_STEP_KEY] Thread.current[VIVLIOSTYLE_CURRENT_STEP_KEY] = label.to_s yield ensure Thread.current[VIVLIOSTYLE_CURRENT_STEP_KEY] = previous end |
.wrap_config(input) ⇒ Object
Hashを再帰的にDataオブジェクトに変換するヘルパードット記法と [] アクセスの両方を提供します
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/vivlio/starter/cli/common.rb', line 53 def wrap_config(input) case input in Hash # キーを動的にDataの属性として定義 keys = input.keys cls = Data.define(*keys) do # 従来型の [] アクセスも提供 def [](key) = respond_to?(key) ? public_send(key) : nil # パターンマッチング(deconstruct_keys)への対応 def deconstruct_keys(keys) = to_h.slice(*keys) # dig メソッドの提供(既存コードとの互換性) def dig(*keys) keys.reduce(self) do |obj, key| return nil unless obj.respond_to?(:[]) obj[key] end end # fetch メソッドの提供 def fetch(key, default = nil) val = self[key] val.nil? ? default : val end end cls.new(**input.transform_values { wrap_config(it) }) in Array input.map { wrap_config(it) } else input end end |