Module: VivlioStarter::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 ].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
- DIRECT_PAGE_PRESET =
直接ビルド(book.yml を持たない単一 Markdown)の版面。日本の技術書の主流サイズであり、 resolve_page_size の既定フォールバック(B5)とも一致する。
'b5_standard'- LEVELS =
{ 'error' => 0, 'warn' => 1, 'info' => 2, 'success' => 2, 'action' => 2, 'debug' => 3 }.freeze
- DEFAULT_LOG_LEVEL =
--log 未指定時のレベル(🟡 警告と 🔴 エラーだけを出す)
LEVELS['warn']
- LOG_LEVEL_NAMES =
利用者に案内するログレベル名(success / action は内部の分類なので出さない)
%w[error warn info debug].freeze
- CONFIG_DIR =
'config'- CONTENTS_DIR =
'contents'- STYLESHEETS_DIR =
'stylesheets'- IMAGES_DIR =
'images'- DATA_DIR =
'data'- CODES_DIR =
'codes'- TEMPLATES_DIR =
'templates'- COVERS_DIR =
'covers'- VFM_COMMAND =
'vfm'- CACHE_DIR =
'.cache/vs'- VIVLIOSTYLE_CONFIG_FILE =
旧バージョン(撤去済み手動フロー)のルート config 名。 doctor の旧プロジェクト検出マーカーとしてのみ参照する。
'vivliostyle.config.js'- BUILD_DIR =
ビルドワークスペース(P4: 中間生成物の分離場所)
中間 md/HTML/中間 PDF/EPUB 作業物はルートではなくこの配下に閉じる。 4 消費者 dir は同一深度(ルートから 4 階層)にすることが仕様の要: 資産への相対プレフィックスが全消費者で共通になり、html/ から 消費者 dir へのコピーが無加工(バイト同一)で成立する(P4 §3.1)。
"#{CACHE_DIR}/build"- BUILD_HTML_DIR =
"#{BUILD_DIR}/html"- BUILD_PDF_DIR =
"#{BUILD_DIR}/pdf"- BUILD_EPUB_DIR =
"#{BUILD_DIR}/epub"- BUILD_KINDLE_DIR =
"#{BUILD_DIR}/kindle"- BUILD_VIVLIOSTYLE_PDF_DIR =
vivliostyle の作業ディレクトリ(生成 config の workspaceDir)。 中身はプロジェクトの木の写し(実測 15,263 ファイル)で、直下の publication.json は各ビルドが自分のエントリ一覧で上書きする。1 つを共有した まま 2 つの vivliostyle が同時に走れば確実に踏み合うため、PDF 枝と EPUB 枝で 分ける(build-target-parallelization-spec.md §3.1)。 消費者 dir と同じ 4 階層に置くこと——
.vivliostyle/pdfのように 1 段 深くすると資産への相対プレフィックス(ASSET_PREFIX)が合わなくなる。 "#{BUILD_DIR}/.vivliostyle-pdf"- BUILD_VIVLIOSTYLE_EPUB_DIR =
"#{BUILD_DIR}/.vivliostyle-epub"- INDEX_MATCHES_FILE =
索引スキャン結果の中間 YAML(書き手 IndexMatchScanner・読み手 UnifiedPageBuilder)。 ルートではなくワークスペース直下へ置き、ルート無汚染を保つ(P4b §2.5)。
"#{BUILD_DIR}/_index_matches.yml"- COVER_CACHE_DIR =
再生成コストの高い生成資産のキャッシュ(generated-assets 移設仕様 §2)
BUILD_DIR の外に置くのが要点: final clean(rm_rf BUILD_DIR)を生き延び、 waifu2x を伴う高コストなバリアント生成や covers の毎ビルド再生成を避ける。 前処理の生成資産(mermaid / showcase / math)も同方針で CACHE_DIR 配下に置く (PreProcessCommands::GeneratedAssetCache が .cache/vs/<種別>/ を管理する)。
"#{CACHE_DIR}/covers"- THEME_IMAGES_CACHE_DIR =
"#{CACHE_DIR}/theme-images"- ASSET_PREFIX =
中間 HTML/md から著者資産(stylesheets/ images/ 等)への相対プレフィックス。 生成時に正しいプレフィックスで書く(コピー時 gsub はしない)が P4 §3.3 の方針。 資産参照を生成する choke point(FrontmatterGenerator / ImagePathNormalizer / MathTransformer / Techbook::Processor / TocGenerator / UnifiedPageBuilder)は 必ずこの値を参照すること。 ワークスペース(ルートから 4 階層)からルート資産への上方参照。
'../../../../'- RESERVED_CONFIG_KEYS =
Data の既存メソッドと衝突すると [] やドット記法が member を返せなくなるため、 ロード時に警告する(dig は wrap_config が後付けするメソッド)
(Data.instance_methods | %i[dig]).freeze
- RETIRED_CONFIG_KEYS =
================================================================
廃止した設定キー
キーを廃止したらここへ 1 行足すだけでよい。検出も案内もこの表が担い、 各コマンドは何も書かない。
なぜ CONFIG では判定できないか:
CONFIG は既定値スキーマとマージした「実効値」の view であって、著者が 何を書いたかを答えるものではない。スキーマ外のキーは自由拡張のために 素通しするので、今は CONFIG の形からでも記述の有無を言い当てられる。 だがそれは「そのキーがスキーマに無い」ことに寄りかかった判定で、同名の キーが既定値付きで復活した途端、誰も書いていないのに警告が出るようになる。 ここで問うているのは記述の有無なので、マージ前の生の記述を見る。値は「代わりにどうするか」。著者が読んで行動できる文言にすること (警告は具体的な修正案とセットにする、が本プロジェクトの流儀)。
ConfigKeys.retirement_notices.freeze
- DETAIL_INDENT =
detail 行のインデント幅(半角スペース 8 文字)
' '- EMIT_SINK_KEY =
出力先を差し替えるためのスレッドローカル鍵(with_emit_sink が管理する)
:vivlio_starter_emit_sink- PAGE_SIZES =
================================================================
Page Size Utilities
{ 'A4' => { width: '210mm', height: '297mm' }, 'A5' => { width: '148mm', height: '210mm' }, # 'B5' は技術書慣習により JIS 寸法(182×257)の別名。ISO B5(176×250)は非サポート。 'B5' => { width: '182mm', height: '257mm' }, 'JIS-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- REQUIRED_BOOK_KEYS =
未設定だと成果物が目に見えて欠ける主要キーと、その記入例。 記入例は警告の「直し方」としてそのまま見せるので、著者が貼って直せる形で書く。
ConfigKeys..freeze
Class Attribute Summary collapse
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_log_level!(command) ⇒ Object
解析済みコマンドの --log 指定からログレベルを確定する。 呼ぶのは CLI.start の 1 箇所だけ(各所で ARGV を読み直さない)。.
-
.apply_page_preset(cfg) ⇒ Object
--- Hardcoded Defaults (Data objects for immutability) ---.
-
.asset_prefix ⇒ Object
ワークスペース関連(P4).
- .blank?(v) ⇒ Object
- .build_dir ⇒ Object
-
.build_direct_configuration(overrides = {}) ⇒ Data
直接ビルド(vs build myawesome.md)用の CONFIG を YAML を読まずに組み立てる。 「設定ファイルを介さない」が本機能の定義のため book.yml は一切参照せず、 既定値スキーマ+版面プリセットに呼び出し側の上書きを重ねるだけで完結させる。 仕様: direct-build-spec.md §2.3.
- .build_html_dir ⇒ Object
- .build_pdf_dir ⇒ Object
-
.cache_dir ⇒ Object
キャッシュ関連(directories 同様、2026-08-08 にシステム定数へ戻した).
- .cache_enabled? ⇒ Object
- .chapter_template_path ⇒ Object
-
.collect_key_paths(node, prefix = [], into = ) ⇒ Object
ネストしたハッシュのキーパスを集合にする(authored_keys の作成用).
-
.config_dir ⇒ Object
ディレクトリ関連.
- .config_dir_path ⇒ Object
-
.configured? ⇒ Object
CONFIG が未ロード(プロジェクト外)の場合に呼び出し元で検査するためのヘルパー.
-
.confirm?(message, default: false, input: $stdin) ⇒ Boolean
取り返しのつかない操作の前に確認を取る(❓)。ログレベルに関わらず常に表示する。 各コマンドが独自に print していると絵文字も表記も揃わないため、ここに集約する。.
- .consume_vivliostyle_build_timings ⇒ Object
- .contents_dir ⇒ Object
-
.cover_cache_dir ⇒ Object
生成資産キャッシュ(covers 生成物・テーマ画像バリアント)。 cache.dir 設定で cache_dir が変わっても追従するようヘルパ経由で参照する。.
-
.cover_theme ⇒ Object
カバー設定関連(CONFIG&.
- .covers_dir ⇒ Object
-
.current_log_level ⇒ Object
現在のログレベル。error: 0 / warn: 1 / info,success,action: 2 / debug: 3.
- .current_step_label ⇒ Object
- .data_dir ⇒ Object
-
.deep_merge_config(default, user) ⇒ Object
既定値と book.yml の値を再帰的にマージする。 著者が「キーだけ書いて値を空欄」にした場合(nil)は既定値を採用する。 false は明示的な設定として尊重する(nil のみ既定値扱い)。.
-
.default_config_schema ⇒ Object
book.yml の全セクションの既定値スキーマ。 コードが参照する既知キーを列挙し、未設定時のドット記法アクセスを保証する。 nil は「既定値なし(未設定)」を表し、実際の既定値は従来どおり参照側が決める。 book.yml の全セクションの既定値スキーマ。 宣言は ConfigKeys::KEYS が持つ(config-defaults-design-spec.md)。 ここは葉キーの宣言から入れ子ハッシュを組み立て直すだけで、値は持たない。.
-
.direct_page_settings ⇒ Object
直接ビルドの版面。page の版面キー(size/margin_* 等)は既定値スキーマに無く page_presets.yml 由来のため、ここでプリセットを 1 つ解決して埋める。 プリセットが読めない環境でも組版自体は page-settings.css の既定で成立する。.
-
.emit(line) ⇒ Object
ログ行を出力する唯一の出口。スピナーが回っていれば行を消してから出す (消さないとスピナーの残骸とログが同じ行に重なる)。.
-
.ensure_build_workspace! ⇒ Object
4 消費者 dir を作成してワークスペースを準備する.
- .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 を走査してコマンドが実行可能か判定する。.
-
.format_converter_stderr(text) ⇒ Object
run_svg_converter! 用に stderr テキストをユーザー向けに整形する。 空のとき / 長すぎるときを吸収する。.
-
.format_detail(detail) ⇒ Object
detail 文字列を行配列に変換する。nil の場合は空配列を返す。 log_* からのみ呼ばれる内部ヘルパー。.
- .generate_compressed_pdf_filename(target = 'pdf') ⇒ Object
-
.generate_cover_output_filename(side) ⇒ String
印刷カバー PDF のルート成果品名(generated-assets 移設仕様 §3.4)。 generate_output_filename と同じ include_version 規則に従う。 例: vivlio_starter_frontcover_v1.0.0.pdf.
- .generate_epub_filename ⇒ Object
-
.generate_kindle_epub_filename ⇒ Object
Kindle 用の中間 EPUB(kindlepreviewer の入力)。KPF 生成後は削除される(§1-4)。 例: vivlio_starter_v1.0.0-kindle.epub.
-
.generate_kpf_filename ⇒ Object
Kindle の最終成果物(KPF・ルート直下)。例: vivlio_starter_v1.0.0.kpf.
-
.generate_output_filename(target = 'pdf', suffix: nil) ⇒ Object
Output Filename Generation ================================================================.
- .generate_print_pdf_filename ⇒ Object
- .images_dir ⇒ Object
- .index_matches_file ⇒ Object
-
.install_configuration!(config) ⇒ Object
CONFIG 定数を差し替える唯一の入口(既存なら削除して再定義警告を回避)。 book.yml 由来の reload_configuration! と、YAML を読まずに組み立てる 直接ビルド(build_direct_configuration)の両方がここを通る。.
-
.kindle_embed? ⇒ Object
Kindle 表紙の埋め込み。未設定時は false(二重表紙回避・§1-6)。.
-
.load_config ⇒ Object
book.yml を読み込み、ハードコーディングされた既定値をマージして返す.
-
.load_page_presets ⇒ Object
safe_load_file に統一(ensure_required_yaml_files! の検証経路と同一ポリシー)。 aliases はプリセットの差分定義(<<: *a5_std)に必須のため許可する。.
-
.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_option_value(command) ⇒ Object
ルートコマンドが受け取った実行対象(サブコマンド)の --log を読む。 --log を宣言していないコマンドでは nil になり、既定レベルのままとなる。.
-
.log_result(msg, status:) ⇒ Object
処理の最終結果を報告する(✅/❗/❌/📚)。ログレベルに関わらず常に表示。 :warning は「警告はあるが処理は成立した」中間の結末を表す (preflight の警告のみ=終了コード 0 のケース)。.
-
.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 に記述がなくても全セクション・既知キーが常に存在し、 CONFIG.lint.config のようなドット記法が安全になる(値未設定なら nil)。 仕様: config-access-unification-spec.md §2.2.
-
.merge_vivliostyle_build_timings(entries) ⇒ Object
別スレッドで集めた計時を、このスレッドの記録へ合流させる。 計時はスレッドローカルなので競合はしないが、そのままでは親が子枝の内訳を 見られない(build-target-parallelization-spec.md §3.5)。.
-
.missing_book_config_example(missing) ⇒ Object
未設定キーを book.yml の記法どおり(セクションごと)に並べた記入例を組み立てる。.
-
.missing_book_config_keys(cfg) ⇒ Array<Array<Symbol>>
主要キーのうち 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
page 設定の単位を正規化する(仕様: page-unit-conversion-spec.md §3.3)。 文字サイズを先に pt 化し、その結果を基準に行送り(倍率/em)を絶対 pt へ解決する。 行送りを倍率のまま CSS へ渡さないのは、参照箇所ごとの font-size に依存させず 版面の行グリッドを揃えるため(同 §1.3)。.
- .pdf_combined? ⇒ Object
- .pdf_compress? ⇒ Object
- .postface_template_path ⇒ Object
- .preface_template_path ⇒ Object
-
.print_pdf_full_bleed? ⇒ Object
本文にフチなし(塗り足しまで届く)要素があるか。true の本は閲覧用 PDF から 塗り足しを復元できないため、入稿用 PDF を個別レンダリングする(既定 false = 導出)。.
- .record_vivliostyle_build(duration, label = nil) ⇒ Object
- .relative_path_from_root(path) ⇒ Object
-
.reload_configuration!(silent: false) ⇒ Object
定数を安全に(警告なしで)再定義する.
-
.required_yaml_files_loadable? ⇒ Object
必須 YAML がすべて存在し、かつ解析可能かを abort せずに判定する。 モジュール初期ロードで使用(破損時に起動ごと止めてしまうと、修復手段で ある vs doctor --fix 自体が実行できなくなるため).
- .reset_vivliostyle_build_timings ⇒ Object
-
.resolve_log_level(raw) ⇒ Object
値なし
--logは OptionTokenNormalizer が info へ開いて渡すため、ここには 常に具体的な値か nil だけが来る。大文字は記法を問わず downcase して扱う (従来は--log=DEBUGだけが downcase され--log DEBUGは info に落ちていた)。. -
.resolve_page_size(pcfg) ⇒ Object
ページサイズを解決する(シンボルキーの Hash 前提) CONFIG.page(Data)を渡す場合は呼び出し側の境界で .to_h してから渡す(spec §2.4).
-
.resolve_path_from_root(path) ⇒ Object
Path Utilities ================================================================.
-
.run_svg_converter!(argv, input_path:, output_path: nil, purpose: nil, env: nil) ⇒ Boolean
外部 SVG 変換コマンド(rsvg-convert / ImageMagick 等)を実行し、 失敗した場合はユーザー向けの整形済みエラーメッセージを出力する。.
- .stylesheets_dir ⇒ Object
- .template_path(name) ⇒ Object
- .templates_dir ⇒ Object
- .theme_images_cache_dir ⇒ Object
-
.to_roman_lower(n) ⇒ Object
Chapter Utilities ================================================================.
-
.truthy?(val) ⇒ Object
Helpers ================================================================.
-
.validate_cover_settings ⇒ Object
カバー設定のバリデーション。 output.cover は既定値 master を持つため「未設定」の状態は存在しない (従来も cover.rb が
|| 'master'で補っており、実際の出力は同じだった)。 画像が足りない場合は下の存在チェックが具体的に指摘する。. - .verbose? ⇒ Object
-
.vfm_command ⇒ Object
コマンド関連.
-
.warn_missing_book_config ⇒ Object
主要キーの欠落を、廃止キー案内と同じ関門(ensure_configured!)で 1 回だけ案内する。 「タイトルが空欄の PDF ができてから気付く」のを避けるのが目的。 既存の最小構成プロジェクトを弾かないよう abort はしない。.
-
.warn_reserved_config_keys(keys) ⇒ Object
book.yml のキーが Data の予約メソッド名と衝突していないかを検査する。 衝突すると member 定義がメソッドを上書き(またはその逆)して静かに誤動作するため、 ロード時に著者へ改名を促す。.
-
.warn_retired_config_keys ⇒ Object
廃止キーが書かれていたら、まとめて 1 回だけ案内する。 黙って無視すると「設定したのに効かない」という最悪の形になる。.
-
.warn_unknown_log_level(raw) ⇒ Object
不明なログレベルは 🟡 で知らせて info へ倒す。ログ指定のタイプミスでビルドを 止めるのは過剰だが、黙って既定に落とすと間違いに気づけない。.
- .with_current_step_label(label) ⇒ Object
-
.with_emit_sink(sink) ⇒ Object
ブロックの間、このスレッドのログ行を sink(<< を受ける任意のオブジェクト)へ 溜める。溜めた行は呼び出し側が持つので、ブロックが例外で抜けても失われない。.
-
.wrap_config(input) ⇒ Object
Hashを再帰的にDataオブジェクトに変換するヘルパー ドット記法と [] アクセスの両方を提供します 正規記法は config-access-unification-spec.md §2 を参照.
Class Attribute Details
.log_level ⇒ Object
362 |
# File 'lib/vivlio_starter/cli/common.rb', line 362 def log_level = @log_level || DEFAULT_LOG_LEVEL |
Class Method Details
.abort_with_error(msg) ⇒ Object
855 856 857 858 859 |
# File 'lib/vivlio_starter/cli/common.rb', line 855 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 の付録一覧から順番を取得する。
698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 |
# File 'lib/vivlio_starter/cli/common.rb', line 698 def appendix_number_to_letter(num, entries: nil) n = num.to_i return nil unless n.between?(90, 98) # ビルド対象のエントリ(付録を含む非空配列)が渡された場合のみ、その並びを使う。 # 空配列は「指定なし」として扱い catalog 全体から付録順を取り直す。 # (フルビルドでは本文章のみの override が渡され、付録抽出後に空配列となるため、 # ここで全体 resolve に委ねないと末尾フォールバックに落ちて採番がずれる) appendix_entries = if entries && !entries.empty? 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
995 |
# File 'lib/vivlio_starter/cli/common.rb', line 995 def appendix_template_path = template_path('appendix') |
.apply_log_level!(command) ⇒ Object
解析済みコマンドの --log 指定からログレベルを確定する。 呼ぶのは CLI.start の 1 箇所だけ(各所で ARGV を読み直さない)。
370 371 372 |
# File 'lib/vivlio_starter/cli/common.rb', line 370 def apply_log_level!(command) Common.log_level = resolve_log_level(log_option_value(command)) end |
.apply_page_preset(cfg) ⇒ Object
--- Hardcoded Defaults (Data objects for immutability) ---
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
# File 'lib/vivlio_starter/cli/common.rb', line 287 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 # プリセット既定 < 著者インライン値(page_cfg)。page_cfg は use 等の選択子キーも # 含むが、選択子は既に消費済みで害はない(仕様 §3.6)。 cfg.merge(page: normalize_page_units(selected.merge(page_cfg))) else cfg end else cfg end end |
.asset_prefix ⇒ Object
ワークスペース関連(P4)
1008 |
# File 'lib/vivlio_starter/cli/common.rb', line 1008 def asset_prefix = ASSET_PREFIX |
.blank?(v) ⇒ Object
644 |
# File 'lib/vivlio_starter/cli/common.rb', line 644 def blank?(v) = v.nil? || v.to_s.strip.empty? |
.build_dir ⇒ Object
1009 |
# File 'lib/vivlio_starter/cli/common.rb', line 1009 def build_dir = BUILD_DIR |
.build_direct_configuration(overrides = {}) ⇒ Data
直接ビルド(vs build myawesome.md)用の CONFIG を YAML を読まずに組み立てる。 「設定ファイルを介さない」が本機能の定義のため book.yml は一切参照せず、 既定値スキーマ+版面プリセットに呼び出し側の上書きを重ねるだけで完結させる。 仕様: direct-build-spec.md §2.3
893 894 895 896 |
# File 'lib/vivlio_starter/cli/common.rb', line 893 def build_direct_configuration(overrides = {}) base = merge_hardcoded_defaults({ page: direct_page_settings }) wrap_config(deep_merge_config(base, overrides)).freeze end |
.build_html_dir ⇒ Object
1010 |
# File 'lib/vivlio_starter/cli/common.rb', line 1010 def build_html_dir = BUILD_HTML_DIR |
.build_pdf_dir ⇒ Object
1011 |
# File 'lib/vivlio_starter/cli/common.rb', line 1011 def build_pdf_dir = BUILD_PDF_DIR |
.cache_dir ⇒ Object
キャッシュ関連(directories 同様、2026-08-08 にシステム定数へ戻した)
999 |
# File 'lib/vivlio_starter/cli/common.rb', line 999 def cache_dir = CACHE_DIR |
.cache_enabled? ⇒ Object
1000 |
# File 'lib/vivlio_starter/cli/common.rb', line 1000 def cache_enabled? = true |
.chapter_template_path ⇒ Object
993 |
# File 'lib/vivlio_starter/cli/common.rb', line 993 def chapter_template_path = template_path('chapter') |
.collect_key_paths(node, prefix = [], into = ) ⇒ Object
ネストしたハッシュのキーパスを集合にする(authored_keys の作成用)
267 268 269 270 271 272 273 274 275 276 |
# File 'lib/vivlio_starter/cli/common.rb', line 267 def collect_key_paths(node, prefix = [], into = Set[]) return into unless node.is_a?(Hash) node.each do |key, value| path = prefix + [key] into << path collect_key_paths(value, path, into) end into end |
.config_dir ⇒ Object
ディレクトリ関連
かつては book.yml の directories.* で改名できたが、2026-08-08 にシステム定数へ戻した。
設定として提供しながら半分のコードしか見ていなかった——定数直参照が 48 箇所、
このメソッド経由が 43 箇所で、改名すると両者が別の場所を指す。加えて vs create の
案内文や原稿の解説(「contents/ に原稿を書きます」)がすべて嘘になる。
メソッドは呼び出し 43 箇所のために残す(定数を返すだけ)。
980 |
# File 'lib/vivlio_starter/cli/common.rb', line 980 def config_dir = CONFIG_DIR |
.config_dir_path ⇒ Object
981 |
# File 'lib/vivlio_starter/cli/common.rb', line 981 def config_dir_path = resolve_path_from_root(config_dir) |
.configured? ⇒ Object
CONFIG が未ロード(プロジェクト外)の場合に呼び出し元で検査するためのヘルパー
956 |
# File 'lib/vivlio_starter/cli/common.rb', line 956 def configured? = !CONFIG.nil? |
.confirm?(message, default: false, input: $stdin) ⇒ Boolean
取り返しのつかない操作の前に確認を取る(❓)。ログレベルに関わらず常に表示する。 各コマンドが独自に print していると絵文字も表記も揃わないため、ここに集約する。
497 498 499 500 501 502 503 504 505 506 |
# File 'lib/vivlio_starter/cli/common.rb', line 497 def confirm?(, default: false, input: $stdin) Spinner.clear_active_line $stdout.print("❓ #{} #{default ? '[Y/n]' : '[y/N]'}: ") $stdout.flush answer = input.gets&.strip&.downcase return default if answer.nil? || answer.empty? %w[y yes].include?(answer) end |
.consume_vivliostyle_build_timings ⇒ Object
829 830 831 832 833 |
# File 'lib/vivlio_starter/cli/common.rb', line 829 def consume_vivliostyle_build_timings timings = Thread.current[VIVLIOSTYLE_TIMINGS_KEY] || [] Thread.current[VIVLIOSTYLE_TIMINGS_KEY] = [] timings end |
.contents_dir ⇒ Object
982 |
# File 'lib/vivlio_starter/cli/common.rb', line 982 def contents_dir = CONTENTS_DIR |
.cover_cache_dir ⇒ Object
生成資産キャッシュ(covers 生成物・テーマ画像バリアント)。 cache.dir 設定で cache_dir が変わっても追従するようヘルパ経由で参照する。
1004 |
# File 'lib/vivlio_starter/cli/common.rb', line 1004 def cover_cache_dir = File.join(cache_dir, 'covers') |
.cover_theme ⇒ Object
カバー設定関連(CONFIG&. は CONFIG 未ロード(プロジェクト外)を吸収する。 各セクションは既定値スキーマで存在保証されるため、以降はドットで辿れる)
1025 |
# File 'lib/vivlio_starter/cli/common.rb', line 1025 def cover_theme = CONFIG&.output&.cover |
.covers_dir ⇒ Object
987 |
# File 'lib/vivlio_starter/cli/common.rb', line 987 def covers_dir = COVERS_DIR |
.current_log_level ⇒ Object
現在のログレベル。error: 0 / warn: 1 / info,success,action: 2 / debug: 3
366 |
# File 'lib/vivlio_starter/cli/common.rb', line 366 def current_log_level = Common.log_level |
.current_step_label ⇒ Object
851 852 853 |
# File 'lib/vivlio_starter/cli/common.rb', line 851 def current_step_label Thread.current[VIVLIOSTYLE_CURRENT_STEP_KEY] end |
.data_dir ⇒ Object
985 |
# File 'lib/vivlio_starter/cli/common.rb', line 985 def data_dir = DATA_DIR |
.deep_merge_config(default, user) ⇒ Object
既定値と book.yml の値を再帰的にマージする。 著者が「キーだけ書いて値を空欄」にした場合(nil)は既定値を採用する。 false は明示的な設定として尊重する(nil のみ既定値扱い)。
210 211 212 213 214 215 216 |
# File 'lib/vivlio_starter/cli/common.rb', line 210 def deep_merge_config(default, user) case [default, user] in [Hash => d, Hash => u] then d.merge(u) { |_k, dv, uv| deep_merge_config(dv, uv) } in [_, nil] then default else user end end |
.default_config_schema ⇒ Object
book.yml の全セクションの既定値スキーマ。 コードが参照する既知キーを列挙し、未設定時のドット記法アクセスを保証する。 nil は「既定値なし(未設定)」を表し、実際の既定値は従来どおり参照側が決める。 book.yml の全セクションの既定値スキーマ。 宣言は ConfigKeys::KEYS が持つ(config-defaults-design-spec.md)。 ここは葉キーの宣言から入れ子ハッシュを組み立て直すだけで、値は持たない。
284 |
# File 'lib/vivlio_starter/cli/common.rb', line 284 def default_config_schema = ConfigKeys.default_schema |
.direct_page_settings ⇒ Object
直接ビルドの版面。page の版面キー(size/margin_* 等)は既定値スキーマに無く page_presets.yml 由来のため、ここでプリセットを 1 つ解決して埋める。 プリセットが読めない環境でも組版自体は page-settings.css の既定で成立する。
901 902 903 904 905 906 |
# File 'lib/vivlio_starter/cli/common.rb', line 901 def direct_page_settings apply_page_preset({ page: { use: DIRECT_PAGE_PRESET } })[:page] rescue StandardError => e log_debug("[direct] 版面プリセットの解決に失敗しました(既定値で続行): #{e.}") { use: DIRECT_PAGE_PRESET } end |
.emit(line) ⇒ Object
ログ行を出力する唯一の出口。スピナーが回っていれば行を消してから出す (消さないとスピナーの残骸とログが同じ行に重なる)。
出力先はスレッドごとに差し替えられる。並列ビルドの子枝はここへ配列を挿し、 親が合流時にまとめて吐く——スピナーと 2 つの枝が同じ TTY を奪い合うと行が 混ざるため、書き手を常に 1 つに保つ(build-target-parallelization-spec.md §3.4)。
408 409 410 411 412 413 414 |
# File 'lib/vivlio_starter/cli/common.rb', line 408 def emit(line) sink = Thread.current[EMIT_SINK_KEY] return sink << line if sink Spinner.clear_active_line puts(line) end |
.ensure_build_workspace! ⇒ Object
4 消費者 dir を作成してワークスペースを準備する
1015 1016 1017 1018 |
# File 'lib/vivlio_starter/cli/common.rb', line 1015 def ensure_build_workspace! [BUILD_HTML_DIR, BUILD_PDF_DIR, BUILD_EPUB_DIR, BUILD_KINDLE_DIR].each { FileUtils.mkdir_p(it) } BUILD_DIR end |
.ensure_cache_dir! ⇒ Object
668 669 670 671 672 |
# File 'lib/vivlio_starter/cli/common.rb', line 668 def ensure_cache_dir! dir = cache_dir FileUtils.mkdir_p(dir) dir end |
.ensure_configured! ⇒ Object
958 959 960 961 962 963 964 965 966 967 |
# File 'lib/vivlio_starter/cli/common.rb', line 958 def ensure_configured! warn_retired_config_keys warn_missing_book_config return if configured? # 欠落と破損で正確な理由を出し分けるため、ファイル単位の検証に委ねて abort する # (破損時は vs doctor --fix による修復導線も案内される) ensure_required_yaml_files! abort_with_error('設定ファイルの読み込みに失敗しました: config/book.yml') end |
.ensure_external_command!(cmd, purpose: nil) ⇒ Object
コマンドが見つからない場合は vs doctor 案内付きで例外を送出する。
560 561 562 563 564 |
# File 'lib/vivlio_starter/cli/common.rb', line 560 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
163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/vivlio_starter/cli/common.rb', line 163 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}\n 修復するには vs doctor --fix を実行してください(破損ファイルはバックアップを取得します)") end rescue StandardError => e abort_with_error("必須設定ファイルの解析に失敗しました (#{path}): #{e.}\n 修復するには vs doctor --fix を実行してください(破損ファイルはバックアップを取得します)") end end |
.epub_embed? ⇒ Object
1028 1029 |
# File 'lib/vivlio_starter/cli/common.rb', line 1028 def = CONFIG&.output&.epub&. == true # Kindle 表紙の埋め込み。未設定時は false(二重表紙回避・§1-6)。 |
.external_command_available?(cmd) ⇒ Boolean
外部コマンド可用性チェック
PATH を走査してコマンドが実行可能か判定する。
523 524 525 526 527 528 529 530 531 532 533 534 535 |
# File 'lib/vivlio_starter/cli/common.rb', line 523 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 |
.format_converter_stderr(text) ⇒ Object
run_svg_converter! 用に stderr テキストをユーザー向けに整形する。 空のとき / 長すぎるときを吸収する。
613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 |
# File 'lib/vivlio_starter/cli/common.rb', line 613 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_* からのみ呼ばれる内部ヘルパー。
510 511 512 513 514 |
# File 'lib/vivlio_starter/cli/common.rb', line 510 def format_detail(detail) return [] if detail.nil? detail.lines.map(&:chomp) end |
.generate_compressed_pdf_filename(target = 'pdf') ⇒ Object
806 807 808 809 810 |
# File 'lib/vivlio_starter/cli/common.rb', line 806 def generate_compressed_pdf_filename(target = 'pdf') # 新しい設定構造ではsuffixは"_compressed"に固定 suffix = 'compressed' generate_output_filename(target, suffix: suffix) end |
.generate_cover_output_filename(side) ⇒ String
印刷カバー PDF のルート成果品名(generated-assets 移設仕様 §3.4)。 generate_output_filename と同じ include_version 規則に従う。 例: vivlio_starter_frontcover_v1.0.0.pdf
791 792 793 794 795 796 797 798 |
# File 'lib/vivlio_starter/cli/common.rb', line 791 def generate_cover_output_filename(side) project = CONFIG&.project include_version = CONFIG&.output&.include_version || false filename = "#{project&.name || 'vivlio_starter'}_#{side}cover" filename += "_v#{project&.version}" if include_version && !blank?(project&.version) "#{filename}.pdf" end |
.generate_epub_filename ⇒ Object
799 800 |
# File 'lib/vivlio_starter/cli/common.rb', line 799 def generate_epub_filename = generate_output_filename('epub') # Kindle の最終成果物(KPF・ルート直下)。例: vivlio_starter_v1.0.0.kpf |
.generate_kindle_epub_filename ⇒ Object
Kindle 用の中間 EPUB(kindlepreviewer の入力)。KPF 生成後は削除される(§1-4)。 例: vivlio_starter_v1.0.0-kindle.epub
804 |
# File 'lib/vivlio_starter/cli/common.rb', line 804 def generate_kindle_epub_filename = generate_kpf_filename.sub(/\.kpf\z/, '-kindle.epub') |
.generate_kpf_filename ⇒ Object
Kindle の最終成果物(KPF・ルート直下)。例: vivlio_starter_v1.0.0.kpf
801 802 803 |
# File 'lib/vivlio_starter/cli/common.rb', line 801 def generate_kpf_filename = generate_output_filename('kindle') # Kindle 用の中間 EPUB(kindlepreviewer の入力)。KPF 生成後は削除される(§1-4)。 # 例: vivlio_starter_v1.0.0-kindle.epub |
.generate_output_filename(target = 'pdf', suffix: nil) ⇒ Object
================================================================
Output Filename Generation
762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 |
# File 'lib/vivlio_starter/cli/common.rb', line 762 def generate_output_filename(target = 'pdf', suffix: nil) project = CONFIG&.project project_name = project&.name || 'vivlio_starter' project_version = project&.version include_version = CONFIG&.output&.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' when 'kindle' then '.kpf' else '.pdf' end filename + ext end |
.generate_print_pdf_filename ⇒ Object
784 |
# File 'lib/vivlio_starter/cli/common.rb', line 784 def generate_print_pdf_filename = generate_output_filename('print_pdf') |
.images_dir ⇒ Object
984 |
# File 'lib/vivlio_starter/cli/common.rb', line 984 def images_dir = IMAGES_DIR |
.index_matches_file ⇒ Object
1012 |
# File 'lib/vivlio_starter/cli/common.rb', line 1012 def index_matches_file = INDEX_MATCHES_FILE |
.install_configuration!(config) ⇒ Object
CONFIG 定数を差し替える唯一の入口(既存なら削除して再定義警告を回避)。 book.yml 由来の reload_configuration! と、YAML を読まずに組み立てる 直接ビルド(build_direct_configuration)の両方がここを通る。
882 883 884 885 |
# File 'lib/vivlio_starter/cli/common.rb', line 882 def install_configuration!(config) remove_const(:CONFIG) if const_defined?(:CONFIG) const_set(:CONFIG, config) end |
.kindle_embed? ⇒ Object
Kindle 表紙の埋め込み。未設定時は false(二重表紙回避・§1-6)。
1030 1031 1032 |
# File 'lib/vivlio_starter/cli/common.rb', line 1030 def = CONFIG&.output&.kindle&. == true # 本文にフチなし(塗り足しまで届く)要素があるか。true の本は閲覧用 PDF から # 塗り足しを復元できないため、入稿用 PDF を個別レンダリングする(既定 false = 導出)。 |
.load_config ⇒ Object
book.yml を読み込み、ハードコーディングされた既定値をマージして返す
191 192 193 194 195 196 197 |
# File 'lib/vivlio_starter/cli/common.rb', line 191 def load_config YAML.load_file(CONFIG_FILE, aliases: true, symbolize_names: true) => raw_config # 既定値をマージすると未知キーが落ちるので、著者の記述はここで記録する @authored_keys = collect_key_paths(raw_config) cfg = apply_page_preset(raw_config) merge_hardcoded_defaults(cfg) end |
.load_page_presets ⇒ Object
safe_load_file に統一(ensure_required_yaml_files! の検証経路と同一ポリシー)。 aliases はプリセットの差分定義(<<: *a5_std)に必須のため許可する。
309 310 311 |
# File 'lib/vivlio_starter/cli/common.rb', line 309 def load_page_presets YAML.safe_load_file(PAGE_PRESETS_FILE, aliases: true, symbolize_names: true) end |
.log_action(msg) ⇒ Object
処理ステップの開始・進行(🔧)。--log=info 以上で表示。
451 452 453 |
# File 'lib/vivlio_starter/cli/common.rb', line 451 def log_action(msg) emit("🔧 #{msg}") if current_log_level >= 2 end |
.log_always(msg) ⇒ Object
アイコンなしで常に表示する汎用出力。
487 488 489 |
# File 'lib/vivlio_starter/cli/common.rb', line 487 def log_always(msg) emit(msg) end |
.log_debug(msg) ⇒ Object
デバッグ情報(🧪)。--log=debug のみ表示。
456 457 458 |
# File 'lib/vivlio_starter/cli/common.rb', line 456 def log_debug(msg) emit("🧪 #{msg}") if current_log_level >= 3 end |
.log_error(msg, detail: nil) ⇒ Object
エラー(🔴)。ログレベルに関わらず常に表示。
445 446 447 448 |
# File 'lib/vivlio_starter/cli/common.rb', line 445 def log_error(msg, detail: nil) emit("🔴 #{msg}") format_detail(detail).each { |line| emit("#{DETAIL_INDENT}#{line}") } end |
.log_info(msg) ⇒ Object
補足情報・処理の詳細(🔵)。--log=info 以上で表示。
427 428 429 |
# File 'lib/vivlio_starter/cli/common.rb', line 427 def log_info(msg) emit("🔵 #{msg}") if current_log_level >= 2 end |
.log_option_value(command) ⇒ Object
ルートコマンドが受け取った実行対象(サブコマンド)の --log を読む。 --log を宣言していないコマンドでは nil になり、既定レベルのままとなる。
376 377 378 379 |
# File 'lib/vivlio_starter/cli/common.rb', line 376 def log_option_value(command) target = (command.command if command.respond_to?(:command)) || command target.[:log_level] if target.respond_to?(:options) end |
.log_result(msg, status:) ⇒ Object
処理の最終結果を報告する(✅/❗/❌/📚)。ログレベルに関わらず常に表示。 :warning は「警告はあるが処理は成立した」中間の結末を表す (preflight の警告のみ=終了コード 0 のケース)。
異体字セレクタ(U+FE0F)を伴う絵文字は使わないこと。 ⚠️(U+26A0 + FE0F)は
East Asian Width が N の文字記号を VS16 で絵文字化しているだけなので、
幅の扱いが端末に委ねられ半角で描かれる——桁が揃わず表が崩れる。
ここで使う 4 つはいずれも単一コードポイントで EAW=W(全角固定)。
新しいアイコンを足すときも同じ条件を満たすものを選ぶ。
476 477 478 479 480 481 482 483 484 |
# File 'lib/vivlio_starter/cli/common.rb', line 476 def log_result(msg, status:) icon = case status when :success then "✅" when :warning then "❗" when :failure then "❌" when :artifact then "📚" end emit("#{icon} #{msg}") end |
.log_success(msg) ⇒ Object
処理の成功(✅)。--log=info 以上で表示。
432 433 434 |
# File 'lib/vivlio_starter/cli/common.rb', line 432 def log_success(msg) emit("✅ #{msg}") if current_log_level >= 2 end |
.log_summary(msg, detail: nil) ⇒ Object
検証結果の集計サマリー(🔍)。ログレベルに関わらず常に表示。
461 462 463 464 |
# File 'lib/vivlio_starter/cli/common.rb', line 461 def log_summary(msg, detail: nil) emit("🔍 #{msg}") format_detail(detail).each { |line| emit("#{DETAIL_INDENT}#{line}") } end |
.log_warn(msg, detail: nil) ⇒ Object
注意・警告(🟡)。--log=warn 以上(既定)で表示。
437 438 439 440 441 442 |
# File 'lib/vivlio_starter/cli/common.rb', line 437 def log_warn(msg, detail: nil) return unless current_log_level >= 1 emit("🟡 #{msg}") format_detail(detail).each { |line| emit("#{DETAIL_INDENT}#{line}") } end |
.merge_hardcoded_defaults(cfg) ⇒ Object
ハードコーディングされた既定値スキーマをマージする book.yml に記述がなくても全セクション・既知キーが常に存在し、 CONFIG.lint.config のようなドット記法が安全になる(値未設定なら nil)。 仕様: config-access-unification-spec.md §2.2
203 204 205 |
# File 'lib/vivlio_starter/cli/common.rb', line 203 def merge_hardcoded_defaults(cfg) default_config_schema.merge(cfg) { |_key, default_val, user_val| deep_merge_config(default_val, user_val) } end |
.merge_vivliostyle_build_timings(entries) ⇒ Object
別スレッドで集めた計時を、このスレッドの記録へ合流させる。 計時はスレッドローカルなので競合はしないが、そのままでは親が子枝の内訳を 見られない(build-target-parallelization-spec.md §3.5)。
838 839 840 841 |
# File 'lib/vivlio_starter/cli/common.rb', line 838 def merge_vivliostyle_build_timings(entries) timings = Thread.current[VIVLIOSTYLE_TIMINGS_KEY] ||= [] timings.concat(Array(entries)) end |
.missing_book_config_example(missing) ⇒ Object
未設定キーを book.yml の記法どおり(セクションごと)に並べた記入例を組み立てる。
936 937 938 939 940 |
# File 'lib/vivlio_starter/cli/common.rb', line 936 def missing_book_config_example(missing) missing.group_by(&:first).flat_map do |section, paths| ["#{section}:", *paths.map { " #{it.last}: #{REQUIRED_BOOK_KEYS[it]}" }] end.join("\n") end |
.missing_book_config_keys(cfg) ⇒ Array<Array<Symbol>>
主要キーのうち book.yml に書かれていないものを返す。
915 916 917 |
# File 'lib/vivlio_starter/cli/common.rb', line 915 def missing_book_config_keys(cfg) REQUIRED_BOOK_KEYS.keys.select { blank?(cfg.dig(*it)) } end |
.missing_external_command_message(cmd, purpose: nil) ⇒ String
外部コマンドが見つからない際の案内メッセージを生成する。
vs doctor / vs doctor --fix への誘導を含む。
542 543 544 545 546 547 548 549 550 551 552 553 554 |
# File 'lib/vivlio_starter/cli/common.rb', line 542 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
326 327 328 329 330 331 |
# File 'lib/vivlio_starter/cli/common.rb', line 326 def normalize_font_sizes(pcfg) FONT_SIZE_KEYS.each_with_object({}) do |key, memo| normalized = Units.font_size_to_pt(pcfg[key]) memo[key] = normalized if normalized end end |
.normalize_line_height(pcfg) ⇒ Object
333 334 335 336 337 338 339 340 341 342 343 |
# File 'lib/vivlio_starter/cli/common.rb', line 333 def normalize_line_height(pcfg) case [pcfg[:base_line_height]&.to_s&.strip, Units.pt_value(pcfg[:base_font_size])] in [nil | '', _] then nil in [/pt\z/i => s, _] then s in [/q\z/i => s, _] then Units.format_pt(s.to_f * Units::PT_PER_Q) in [_, nil] then pcfg[:base_line_height] in [/em\z/i => s, f_pt] then Units.format_pt(f_pt * s.to_f) in [/\A[\d.]+\z/ => s, f_pt] then Units.format_pt(f_pt * s.to_f) in [other, _] then other end end |
.normalize_page_size!(page_cfg) ⇒ Object
749 750 751 752 753 754 755 756 |
# File 'lib/vivlio_starter/cli/common.rb', line 749 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
page 設定の単位を正規化する(仕様: page-unit-conversion-spec.md §3.3)。 文字サイズを先に pt 化し、その結果を基準に行送り(倍率/em)を絶対 pt へ解決する。 行送りを倍率のまま CSS へ渡さないのは、参照箇所ごとの font-size に依存させず 版面の行グリッドを揃えるため(同 §1.3)。
321 322 323 324 |
# File 'lib/vivlio_starter/cli/common.rb', line 321 def normalize_page_units(pcfg) sized = pcfg.merge(**normalize_font_sizes(pcfg)) sized.merge(base_line_height: normalize_line_height(sized)).compact end |
.pdf_combined? ⇒ Object
1026 |
# File 'lib/vivlio_starter/cli/common.rb', line 1026 def pdf_combined? = CONFIG&.output&.pdf&.combined == true |
.pdf_compress? ⇒ Object
1027 |
# File 'lib/vivlio_starter/cli/common.rb', line 1027 def pdf_compress? = CONFIG&.output&.pdf&.compress == true |
.postface_template_path ⇒ Object
996 |
# File 'lib/vivlio_starter/cli/common.rb', line 996 def postface_template_path = template_path('postface') |
.preface_template_path ⇒ Object
994 |
# File 'lib/vivlio_starter/cli/common.rb', line 994 def preface_template_path = template_path('preface') |
.print_pdf_full_bleed? ⇒ Object
本文にフチなし(塗り足しまで届く)要素があるか。true の本は閲覧用 PDF から 塗り足しを復元できないため、入稿用 PDF を個別レンダリングする(既定 false = 導出)。
1033 |
# File 'lib/vivlio_starter/cli/common.rb', line 1033 def print_pdf_full_bleed? = truthy?(CONFIG&.output&.print_pdf&.full_bleed) |
.record_vivliostyle_build(duration, label = nil) ⇒ Object
823 824 825 826 827 |
# File 'lib/vivlio_starter/cli/common.rb', line 823 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
660 661 662 663 664 665 666 |
# File 'lib/vivlio_starter/cli/common.rb', line 660 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
定数を安全に(警告なしで)再定義する
863 864 865 866 867 868 869 870 871 872 873 874 875 876 |
# File 'lib/vivlio_starter/cli/common.rb', line 863 def reload_configuration!(silent: false) ensure_required_yaml_files! # load_configの結果をDataオブジェクトにラップしてフリーズ raw_config = load_config # 検査は毎回するが、案内は ensure_configured! まで持ち越す。 # ここで出すと module load 時(= まだログ level も決まっていない時点)に流れてしまい、 # かつ CONFIG を読まない new / doctor / help にも無関係な警告が付く。 @missing_book_keys = missing_book_config_keys(raw_config) @missing_book_keys_reported = false install_configuration!(wrap_config(raw_config).freeze) puts("🧪 Configuration reloaded: #{CONFIG_FILE}") if !silent && current_log_level >= 3 end |
.required_yaml_files_loadable? ⇒ Object
必須 YAML がすべて存在し、かつ解析可能かを abort せずに判定する。 モジュール初期ロードで使用(破損時に起動ごと止めてしまうと、修復手段で ある vs doctor --fix 自体が実行できなくなるため)
181 182 183 184 185 186 187 188 |
# File 'lib/vivlio_starter/cli/common.rb', line 181 def required_yaml_files_loadable? REQUIRED_YAML_FILES.all? do |path| File.file?(path) && (YAML.safe_load(File.read(path, encoding: 'utf-8'), aliases: true, symbolize_names: true) in Hash | Array) rescue StandardError false end end |
.reset_vivliostyle_build_timings ⇒ Object
819 820 821 |
# File 'lib/vivlio_starter/cli/common.rb', line 819 def reset_vivliostyle_build_timings Thread.current[VIVLIOSTYLE_TIMINGS_KEY] = [] end |
.resolve_log_level(raw) ⇒ Object
値なし --log は OptionTokenNormalizer が info へ開いて渡すため、ここには
常に具体的な値か nil だけが来る。大文字は記法を問わず downcase して扱う
(従来は --log=DEBUG だけが downcase され --log DEBUG は info に落ちていた)。
384 385 386 387 388 |
# File 'lib/vivlio_starter/cli/common.rb', line 384 def resolve_log_level(raw) return DEFAULT_LOG_LEVEL if raw.nil? LEVELS[raw.to_s.downcase] || warn_unknown_log_level(raw) end |
.resolve_page_size(pcfg) ⇒ Object
ページサイズを解決する(シンボルキーの Hash 前提) CONFIG.page(Data)を渡す場合は呼び出し側の境界で .to_h してから渡す(spec §2.4)
736 737 738 739 740 741 742 743 744 745 746 747 |
# File 'lib/vivlio_starter/cli/common.rb', line 736 def resolve_page_size(pcfg) 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
650 651 652 653 654 655 656 657 658 |
# File 'lib/vivlio_starter/cli/common.rb', line 650 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, env: nil) ⇒ Boolean
外部 SVG 変換コマンド(rsvg-convert / ImageMagick 等)を実行し、 失敗した場合はユーザー向けの整形済みエラーメッセージを出力する。
堅牢性仕様 7-1: 不正な SVG XML 等で外部コマンドが失敗した際に、
従来はサイレントに下流で No such file となっていた問題を解消する。
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 |
# File 'lib/vivlio_starter/cli/common.rb', line 579 def run_svg_converter!(argv, input_path:, output_path: nil, purpose: nil, env: nil) require 'open3' capture_args = env&.any? ? [env, *argv] : argv _stdout, stderr, status = Open3.capture3(*capture_args) 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
983 |
# File 'lib/vivlio_starter/cli/common.rb', line 983 def stylesheets_dir = STYLESHEETS_DIR |
.template_path(name) ⇒ Object
989 990 991 |
# File 'lib/vivlio_starter/cli/common.rb', line 989 def template_path(name) File.join(templates_dir, "#{name}.md") end |
.templates_dir ⇒ Object
986 |
# File 'lib/vivlio_starter/cli/common.rb', line 986 def templates_dir = TEMPLATES_DIR |
.theme_images_cache_dir ⇒ Object
1005 |
# File 'lib/vivlio_starter/cli/common.rb', line 1005 def theme_images_cache_dir = File.join(cache_dir, 'theme-images') |
.to_roman_lower(n) ⇒ Object
================================================================
Chapter Utilities
678 679 680 681 682 683 684 685 686 687 688 689 690 691 |
# File 'lib/vivlio_starter/cli/common.rb', line 678 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
637 638 639 640 641 642 |
# File 'lib/vivlio_starter/cli/common.rb', line 637 def truthy?(val) case val&.to_s&.strip&.downcase in true | 'true' | 'yes' | 'on' | '1' then true else false end end |
.validate_cover_settings ⇒ Object
カバー設定のバリデーション。
output.cover は既定値 master を持つため「未設定」の状態は存在しない
(従来も cover.rb が || 'master' で補っており、実際の出力は同じだった)。
画像が足りない場合は下の存在チェックが具体的に指摘する。
1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 |
# File 'lib/vivlio_starter/cli/common.rb', line 1039 def validate_cover_settings theme = cover_theme # 標準テーマの場合は有効 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
629 630 631 |
# File 'lib/vivlio_starter/cli/common.rb', line 629 def verbose? current_log_level >= 2 end |
.vfm_command ⇒ Object
コマンド関連
1021 |
# File 'lib/vivlio_starter/cli/common.rb', line 1021 def vfm_command = VFM_COMMAND |
.warn_missing_book_config ⇒ Object
主要キーの欠落を、廃止キー案内と同じ関門(ensure_configured!)で 1 回だけ案内する。 「タイトルが空欄の PDF ができてから気付く」のを避けるのが目的。 既存の最小構成プロジェクトを弾かないよう abort はしない。
922 923 924 925 926 927 928 929 930 931 932 933 |
# File 'lib/vivlio_starter/cli/common.rb', line 922 def warn_missing_book_config return if @missing_book_keys_reported @missing_book_keys_reported = true missing = Array(@missing_book_keys) return if missing.empty? log_warn("config/book.yml の推奨キーが未設定です: #{missing.map { it.join('.') }.join(', ')}", detail: "→ config/book.yml に次のように書いてください。\n" \ "#{missing_book_config_example(missing)}\n" \ 'このままでも動作しますが、PDF のタイトル・著者・出力ファイル名が空欄になります。') end |
.warn_reserved_config_keys(keys) ⇒ Object
book.yml のキーが Data の予約メソッド名と衝突していないかを検査する。 衝突すると member 定義がメソッドを上書き(またはその逆)して静かに誤動作するため、 ロード時に著者へ改名を促す。
152 153 154 155 156 157 |
# File 'lib/vivlio_starter/cli/common.rb', line 152 def warn_reserved_config_keys(keys) reserved = keys.map { it.respond_to?(:to_sym) ? it.to_sym : it } & RESERVED_CONFIG_KEYS return if reserved.empty? log_warn("book.yml のキー名 #{reserved.join(', ')} は予約名のため正しく参照できません。別名への変更を推奨します(例: hash → hash_value)") end |
.warn_retired_config_keys ⇒ Object
廃止キーが書かれていたら、まとめて 1 回だけ案内する。 黙って無視すると「設定したのに効かない」という最悪の形になる。
252 253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/vivlio_starter/cli/common.rb', line 252 def warn_retired_config_keys return if @retired_keys_reported @retired_keys_reported = true found = RETIRED_CONFIG_KEYS.select { |path, _| (*path) } return if found.empty? found.each do |path, guidance| log_warn("config/book.yml の #{path.join('.')} は廃止されました", detail: guidance) end log_warn('上記のキーは読み込まれません。book.yml から削除してください', detail: '指定できるキーは vs doctor か各コマンドの --help で確認できます') end |
.warn_unknown_log_level(raw) ⇒ Object
不明なログレベルは 🟡 で知らせて info へ倒す。ログ指定のタイプミスでビルドを 止めるのは過剰だが、黙って既定に落とすと間違いに気づけない。
392 393 394 395 396 397 |
# File 'lib/vivlio_starter/cli/common.rb', line 392 def warn_unknown_log_level(raw) log_warn("--log=#{raw} は不明なログレベルです。", detail: "対処: #{LOG_LEVEL_NAMES.join(' / ')} のいずれかを指定してください" \ '(今回は info で続行します)。') LEVELS['info'] end |
.with_current_step_label(label) ⇒ Object
843 844 845 846 847 848 849 |
# File 'lib/vivlio_starter/cli/common.rb', line 843 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 |
.with_emit_sink(sink) ⇒ Object
ブロックの間、このスレッドのログ行を sink(<< を受ける任意のオブジェクト)へ 溜める。溜めた行は呼び出し側が持つので、ブロックが例外で抜けても失われない。
418 419 420 421 422 423 424 |
# File 'lib/vivlio_starter/cli/common.rb', line 418 def with_emit_sink(sink) previous = Thread.current[EMIT_SINK_KEY] Thread.current[EMIT_SINK_KEY] = sink yield ensure Thread.current[EMIT_SINK_KEY] = previous end |
.wrap_config(input) ⇒ Object
Hashを再帰的にDataオブジェクトに変換するヘルパー ドット記法と [] アクセスの両方を提供します 正規記法は config-access-unification-spec.md §2 を参照
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 |
# File 'lib/vivlio_starter/cli/common.rb', line 112 def wrap_config(input) case input in Hash # キーを動的にDataの属性として定義 keys = input.keys warn_reserved_config_keys(keys) cls = Data.define(*keys) do # 動的キー用の [] アクセス(Symbol 限定・member 限定)。 # respond_to? ベースだと to_h 等のメソッド戻り値が漏れるため member 限定とし、 # String キーは記法混在の再発を防ぐため即座にエラーにする(The One Way)。 def [](key) raise ArgumentError, "CONFIG のキーは Symbol で指定してください(String は不可): #{key.inspect}" if key.is_a?(String) members.include?(key) ? public_send(key) : nil end # パターンマッチング(deconstruct_keys)への対応。 # keys が nil のとき全体を返すのは Ruby の規約(`in { **rest }` で全キーを束縛可能にする) def deconstruct_keys(keys) = keys.nil? ? to_h : to_h.slice(*keys) # 動的な多段アクセス用の dig(Symbol キーのみ。配列添字の Integer は可) def dig(*keys) keys.reduce(self) do |obj, key| return nil unless obj.respond_to?(:[]) obj[key] end end end cls.new(**input.transform_values { wrap_config(it) }) in Array input.map { wrap_config(it) } else input end end |