Module: VivlioStarter::CLI::ConvertCommands
- Defined in:
- lib/vivlio_starter/cli/convert.rb
Overview
================================================================
Module: convert(Markdown → HTML 変換)
- 目的: Markdown を VFM で HTML に変換するコマンド群
- 関連: 共通処理は
lib/vivlio_starter/cli/common.rbを参照
================================================================
Class Method Summary collapse
-
.convert_markdown!(md) ⇒ Object
1 章を VFM で HTML へ変換する。成功したら true。.
- .enable_verbose(command_or_ctx) ⇒ Object
- .execute_convert(command_or_context, tokens_or_entries) ⇒ Object
-
.html_document?(path) ⇒ Object
VFM の出力が HTML 文書として始まっているか(先頭だけ読めば足りる).
- .normalized_context(command_or_ctx) ⇒ Object
- .options_of(command_or_ctx) ⇒ Object
-
.report_conversion_failures(failed) ⇒ Object
変換できなかった章を 🔴 で報告する。 成功メッセージは出さない——1 章でも欠ければ、そのまま組んだ本は不完全だからである。.
-
.resolve_md_files_for_convert(entries_or_basenames, base_dir) ⇒ Array<String>
Entry 配列または basename 配列から Markdown ファイルパス配列を解決する.
Class Method Details
.convert_markdown!(md) ⇒ Object
1 章を VFM で HTML へ変換する。成功したら true。
終了コードを信用しないのが要点(実測 2026-08-16)。VFM は読み込みに失敗しても
exit 0 を返し、Node のスタックトレースを標準出力へ吐く——変換はシェルの
リダイレクトで行うため、それがそのまま .html として保存される。コマンド自体が
無い場合は 0 バイトの .html が残る。どちらも「中身のある章」として後続を素通りし、
本文の代わりにエラーダンプや白紙が組み上がる。
正常な出力は必ず <!doctype html> で始まるので、中身の頭まで見て成否を決める。
39 40 41 42 43 44 45 46 47 48 |
# File 'lib/vivlio_starter/cli/convert.rb', line 39 def convert_markdown!(md) html = md.sub(/\.md\z/, '.html') ok = system(%(#{Common::VFM_COMMAND} "#{md}" > "#{html}")) return true if ok && html_document?(html) # 失敗の痕跡は残さない。後続に「欠落」として気づかせるほうが、 # 壊れた HTML を本文と信じて組むより安い FileUtils.rm_f(html) false end |
.enable_verbose(command_or_ctx) ⇒ Object
78 79 80 |
# File 'lib/vivlio_starter/cli/convert.rb', line 78 def enable_verbose(command_or_ctx) ENV['VERBOSE'] = '1' if (command_or_ctx)[:verbose] end |
.execute_convert(command_or_context, tokens_or_entries) ⇒ Object
16 17 18 19 20 21 22 23 24 25 26 27 28 |
# File 'lib/vivlio_starter/cli/convert.rb', line 16 def execute_convert(command_or_context, tokens_or_entries) ctx = normalized_context(command_or_context) enable_verbose(ctx) # 中間 .md / .html はワークスペースの html/ に置かれる(P4 §3.4-1) base_dir = Common::BUILD_HTML_DIR md_files = resolve_md_files_for_convert(tokens_or_entries, base_dir) failed = md_files.reject { convert_markdown!(it) } return report_conversion_failures(failed) unless failed.empty? Common.log_success('Markdown→HTML 変換が完了しました') end |
.html_document?(path) ⇒ Object
VFM の出力が HTML 文書として始まっているか(先頭だけ読めば足りる)
52 53 54 |
# File 'lib/vivlio_starter/cli/convert.rb', line 52 def html_document?(path) File.file?(path) && File.read(path, 16).to_s.lstrip.start_with?('<') end |
.normalized_context(command_or_ctx) ⇒ Object
71 72 73 74 75 |
# File 'lib/vivlio_starter/cli/convert.rb', line 71 def normalized_context(command_or_ctx) return command_or_ctx if command_or_ctx.is_a?(Hash) { options: (command_or_ctx) } end |
.options_of(command_or_ctx) ⇒ Object
83 84 85 86 87 88 89 90 91 |
# File 'lib/vivlio_starter/cli/convert.rb', line 83 def (command_or_ctx) if command_or_ctx.is_a?(Hash) command_or_ctx[:options] || {} elsif command_or_ctx.respond_to?(:options) command_or_ctx. || {} else {} end end |
.report_conversion_failures(failed) ⇒ Object
変換できなかった章を 🔴 で報告する。 成功メッセージは出さない——1 章でも欠ければ、そのまま組んだ本は不完全だからである。
59 60 61 62 63 64 65 66 67 68 |
# File 'lib/vivlio_starter/cli/convert.rb', line 59 def report_conversion_failures(failed) Common.log_error( "VFM の Markdown → HTML 変換に失敗しました(#{failed.size} 件)", detail: <<~DETAIL #{failed.map { "- #{File.basename(it)}" }.join("\n")} 対処: `#{Common::VFM_COMMAND} #{File.basename(failed.first)}` を手で実行するとエラーの内容が読めます (壊れた HTML は残していません。そのまま進むと当該章が白紙で組み上がるためです) DETAIL ) end |
.resolve_md_files_for_convert(entries_or_basenames, base_dir) ⇒ Array<String>
Entry 配列または basename 配列から Markdown ファイルパス配列を解決する
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/vivlio_starter/cli/convert.rb', line 98 def resolve_md_files_for_convert(entries_or_basenames, base_dir) raw = Array(entries_or_basenames).compact if raw.empty? return Dir.glob(File.join(base_dir, '*.md')).reject { |f| File.basename(f) =~ /\A(README|ROADMAP)\.md\z/ } end # Entry オブジェクトかどうかを判定 if raw.first.respond_to?(:basename) raw.map { |entry| File.join(base_dir, "#{entry.basename}.md") }.uniq else # basename 配列: パスに変換 raw.map do |bn| name = bn.to_s.sub(/\.md\z/, '') File.join(base_dir, "#{name}.md") end.uniq end end |