Module: VivlioStarter::CLI::PreProcessCommands::DataImageResolver

Defined in:
lib/vivlio_starter/cli/pre_process/data_image_resolver.rb

Overview

QueryStream 展開結果内の素ファイル名画像を data/ 配下から解決するモジュール

Constant Summary collapse

VARIANT_EXTS =

変種解決の優先順(ImagePathNormalizer.image_exists_for? と同ポリシー)。

%w[.webp .png .jpg .jpeg].freeze
DATA_REL_ROOT =

ビルド生成物参照の共通プレフィックス(ImagePathNormalizer の carve-out と対応)。

'images/data'

Class Method Summary collapse

Class Method Details

.plain_filename?(src) ⇒ Boolean

素のファイル名か("/" を含まず・スキーム付きでも絶対パスでもなく・images/ 始まりでもない)。

Returns:

  • (Boolean)


94
95
96
97
98
99
100
# File 'lib/vivlio_starter/cli/pre_process/data_image_resolver.rb', line 94

def plain_filename?(src)
  return false if src.nil? || src.empty?
  return false if src.include?('/')                       # パス・URL・images/… を一括除外
  return false if src.match?(%r{\A[a-zA-Z][a-zA-Z0-9+.-]*:}) # スキーム付き(data: 等)

  true
end

.resolve_src(src, data_base, chapter_slug, context) ⇒ String?

単一の src を解決する。書き換えるべき新 src を返し、対象外・章ローカル該当・ ミス時は nil を返す(呼び出し元は nil のとき元の記法を保つ)。

Returns:

  • (String, nil)


73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/vivlio_starter/cli/pre_process/data_image_resolver.rb', line 73

def resolve_src(src, data_base, chapter_slug, context)
  return nil unless plain_filename?(src)

  # 章ローカルが最優先。実在すれば従来経路に委ねるため書き換えない。
  return nil if variant_exists?(File.join(Common.images_dir, chapter_slug, src))

  data_dir = Common.data_dir
  hit = variant_path(File.join(data_dir, data_base, src)) ||
        variant_path(File.join(data_dir, 'images', src))

  unless hit
    warn_missing(src, data_base, chapter_slug, context)
    return nil
  end

  rel = hit.delete_prefix("#{data_dir}/")     # 例: "physics_books/relativity.webp"
  stage_into_workspace!(hit, rel)
  "#{DATA_REL_ROOT}/#{rel}"
end

.rewrite(text, context, chapter_slug:) ⇒ String

QueryStream 1 記法の展開結果内の画像参照を data/ 配下から解決して書き換える。 Markdown 画像(altattrs)と HTML の両方を対象とする。

Parameters:

  • text (String)

    QueryStream 1 記法の展開結果

  • context (Hash)

    QueryStream post_render コンテキスト(:data_file :query :location を使う)

  • chapter_slug (String)

    章スラッグ(例: "22-extentions")

Returns:

  • (String)

    画像参照を書き換えた展開結果



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/vivlio_starter/cli/pre_process/data_image_resolver.rb', line 51

def rewrite(text, context, chapter_slug:)
  data_base = File.basename(context[:data_file].to_s, '.*')

  # --- Phase: Markdown 画像 ![alt](src){attrs} ---
  result = text.gsub(/!\[(?<alt>[^\]]*)\]\((?<src>[^)\s]+)\)(?<attr>\{[^}]*\})?/) do
    md = ::Regexp.last_match
    resolved = resolve_src(md[:src], data_base, chapter_slug, context)
    resolved ? "![#{md[:alt]}](#{resolved})#{md[:attr]}" : md[0]
  end

  # --- Phase: HTML <img src="src"> ---
  result.gsub(/<img\b[^>]*?\ssrc="(?<src>[^"]+)"[^>]*>/i) do
    tag = ::Regexp.last_match[0]
    src = ::Regexp.last_match[:src]
    resolved = resolve_src(src, data_base, chapter_slug, context)
    resolved ? tag.sub(/(\ssrc=")[^"]*(")/i, "\\1#{resolved}\\2") : tag
  end
end

.stage_into_workspace!(source, rel) ⇒ Object

data/ 配下の実体をワークスペース html/images/data/ へコピーする。 既存の宛先が同一 size・同 mtime 以降なら再コピーしない(冪等・再ビルド高速化)。



116
117
118
119
120
121
122
123
# File 'lib/vivlio_starter/cli/pre_process/data_image_resolver.rb', line 116

def stage_into_workspace!(source, rel)
  dest = File.join(Common::BUILD_HTML_DIR, 'images', 'data', rel)
  return if File.exist?(dest) && File.size(dest) == File.size(source) &&
            File.mtime(dest) >= File.mtime(source)

  FileUtils.mkdir_p(File.dirname(dest))
  FileUtils.cp(source, dest)
end

.variant_exists?(base_path) ⇒ Boolean

変種込みで実在するか(真偽のみ)。

Returns:

  • (Boolean)


112
# File 'lib/vivlio_starter/cli/pre_process/data_image_resolver.rb', line 112

def variant_exists?(base_path) = !variant_path(base_path).nil?

.variant_path(base_path) ⇒ String?

変種込みで実在パスを返す(無ければ nil)。.svg は完全一致、他は VARIANT_EXTS 優先順。

Returns:

  • (String, nil)


104
105
106
107
108
109
# File 'lib/vivlio_starter/cli/pre_process/data_image_resolver.rb', line 104

def variant_path(base_path)
  return (File.exist?(base_path) ? base_path : nil) if base_path.end_with?('.svg')

  base_without_ext = base_path.sub(/\.(webp|png|jpe?g)\z/i, '')
  VARIANT_EXTS.map { "#{base_without_ext}#{it}" }.find { File.exist?(it) }
end

.warn_missing(src, data_base, chapter_slug, context) ⇒ Object

探索 3 箇所を列挙した親切な警告を出す(最終的な 🔴+プレースホルダーは normalizer が担う)。



126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/vivlio_starter/cli/pre_process/data_image_resolver.rb', line 126

def warn_missing(src, data_base, chapter_slug, context)
  searched = [
    "#{File.join(Common.images_dir, chapter_slug, src)}(章ローカル)",
    "#{File.join(Common.data_dir, data_base, src)}(データ単位フォルダ)",
    "#{File.join(Common.data_dir, 'images', src)}(共有プール)"
  ]
  Common.log_warn(
    "#{context[:location]} - データ画像 '#{src}' が見つかりません(記法: #{context[:query]}",
    detail: "探索した場所:\n  - #{searched.join("\n  - ")}\n" \
            "ヒント: #{File.join(Common.data_dir, data_base, src)} に置くとデータ一式が自己完結します"
  )
end