Module: Vivlio::Starter::CLI::PreProcessCommands::LinkImageValidator

Defined in:
lib/vivlio/starter/cli/pre_process/link_image_validator.rb

Defined Under Namespace

Classes: ImageIssue, LinkIssue, ValidationReport

Class Method Summary collapse

Class Method Details

.any_issues?Boolean

蓄積されたレポートにエラー(issue)が1件以上あるか判定する

Returns:

  • (Boolean)


192
193
194
195
196
# File 'lib/vivlio/starter/cli/pre_process/link_image_validator.rb', line 192

def any_issues?
  @monitor.synchronize do
    @reports.any? { |r| r.image_issues.any? || r.link_issues.any? }
  end
end

.any_verification_enabled?(config: resolve_config) ⇒ Boolean

検証が有効か判定する

Returns:

  • (Boolean)


187
188
189
# File 'lib/vivlio/starter/cli/pre_process/link_image_validator.rb', line 187

def any_verification_enabled?(config: resolve_config)
  config[:verify_images] || config[:verify_bare_urls] || config[:verify_external_links]
end

.check_external_urls!Object

蓄積された外部 URL に対して HTTP 到達性チェックを実行する



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
# File 'lib/vivlio/starter/cli/pre_process/link_image_validator.rb', line 101

def check_external_urls!
  config = resolve_config
  return unless config[:verify_external_links]

  urls = @monitor.synchronize { @external_urls.dup }
  return if urls.empty?

  # URL を重複排除(同じ URL は 1 回だけチェック)
  unique_urls = urls.uniq { it[:url] }
  Common.log_action("[検証] 外部 URL の到達性を確認しています(#{unique_urls.size} 件)…")

  results = check_urls_batch(unique_urls, config)

  # 結果をレポートに反映
  results.each do |result|
    next if result[:ok]

    issue = LinkIssue.new(
      filename: result[:filename],
      line_number: result[:line_number],
      url: result[:url],
      issue_type: :unreachable,
      status_code: result[:status_code],
      message: result[:message]
    )
    add_link_issue_to_report(result[:filename], issue)
  end
end

検証結果のサマリーを表示する



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
# File 'lib/vivlio/starter/cli/pre_process/link_image_validator.rb', line 131

def print_summary
  reports = @monitor.synchronize { @reports.dup }
  return if reports.empty?

  total_missing_image = reports.sum { it.image_issues.count { |i| i.issue_type == :missing } }
  total_missing_code  = reports.sum { it.image_issues.count { |i| i.issue_type == :missing_code } }
  total_link = reports.sum { it.link_issues.size }

  if total_missing_image.zero? && total_missing_code.zero? && total_link.zero?
    Common.log_info('リンク・画像の検証が完了しました(良好な状態です)')
    return
  end

  # --- サマリー集計 ---
  detail_lines = []
  detail_lines << "画像: #{total_missing_image} 件の課題(存在しない画像: #{total_missing_image}" if total_missing_image.positive?
  detail_lines << "ソースコード: #{total_missing_code} 件の課題(存在しないファイル: #{total_missing_code}" if total_missing_code.positive?

  if total_link.positive?
    bare = reports.sum { it.link_issues.count { |i| i.issue_type == :bare_url } }
    unreachable = reports.sum { it.link_issues.count { |i| i.issue_type == :unreachable } }
    dangerous = reports.sum { it.link_issues.count { |i| i.issue_type == :dangerous_scheme } }
    parts = []
    parts << "危険スキーム: #{dangerous}" if dangerous.positive?
    parts << "リンク切れ: #{unreachable}" if unreachable.positive?
    parts << "裸 URL: #{bare}" if bare.positive?
    detail_lines << "リンク: #{total_link} 件の問題(#{parts.join(', ')}"
  end

  config = resolve_config
  detail_lines << '外部URL到達性チェック: スキップ(--verify-links で有効化)' unless config[:verify_external_links]

  Common.log_summary('リンク・画像検証の結果:', detail: detail_lines.join("\n"))

  # --- 危険スキームの詳細(セキュリティ上の重要度が高いため先頭)---
  reports.each do |report|
    report.link_issues.select { it.issue_type == :dangerous_scheme }.each do |issue|
      Common.log_warn(
        "#{issue.filename}:#{issue.line_number} - 危険なスキームを検出しました",
        detail: "URL: #{issue.url}\n#{issue.message}"
      )
    end
  end

  # --- リンク切れの詳細 ---
  reports.each do |report|
    report.link_issues.select { it.issue_type == :unreachable }.each do |issue|
      Common.log_error(
        "#{issue.filename}:#{issue.line_number} - リンク切れを検出しました",
        detail: "URL: #{issue.url}#{issue.message}"
      )
    end
  end
end

.record_code_include_error(filename, line_number, code_name) ⇒ Object

コードインクルードエラーをレポートに記録するMarkdownTransformer から呼ばれ、preflight の終了コード判定に反映させる

Parameters:

  • filename (String)

    ソースファイル名

  • line_number (Integer)

    行番号

  • code_name (String)

    見つからなかったコードファイル名



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/vivlio/starter/cli/pre_process/link_image_validator.rb', line 203

def record_code_include_error(filename, line_number, code_name)
  issue = ImageIssue.new(
    filename:,
    line_number:,
    image_path: code_name,
    issue_type: :missing_code
  )
  @monitor.synchronize do
    report = @reports.find { it.filename == filename }
    if report
      idx = @reports.index(report)
      @reports[idx] = ValidationReport.new(
        filename: report.filename,
        image_issues: report.image_issues + [issue],
        link_issues: report.link_issues
      )
    else
      # process_code_includes! は validate の後に実行されるため、
      # レポートが存在しない場合は新規作成する
      @reports << ValidationReport.new(filename:, image_issues: [issue], link_issues: [])
    end
  end
end

.reset!Object

レポート蓄積をリセットする(ビルド開始時に呼ぶ)



53
54
55
56
57
58
# File 'lib/vivlio/starter/cli/pre_process/link_image_validator.rb', line 53

def reset!
  @monitor.synchronize do
    @reports = []
    @external_urls = []
  end
end

.validate(content, filename, source_path: nil, config: resolve_config) ⇒ Object

ファイル単位の検証を実行し、レポートを蓄積する

Parameters:

  • content (String)

    Markdown テキスト(画像パス正規化済み)

  • filename (String)

    対象ファイル名

  • source_path (String, nil) (defaults to: nil)

    元ファイルのパス(行番号補正用)

  • config (Hash) (defaults to: resolve_config)

    検証設定(verify_images, verify_bare_urls, verify_external_links)



65
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
# File 'lib/vivlio/starter/cli/pre_process/link_image_validator.rb', line 65

def validate(content, filename, source_path: nil, config: resolve_config)
  source_content = source_path && File.exist?(source_path) ? File.read(source_path, encoding: 'utf-8') : nil

  image_issues = config[:verify_images] ? scan_missing_images(content, filename) : []
  link_issues = config[:verify_bare_urls] ? scan_bare_urls(content, filename) : []

  # 行番号を元ファイルの行番号に補正する
  if source_content
    image_issues = image_issues.map { correct_line_number(it, source_content) }
    link_issues = link_issues.map { correct_link_line_number(it, source_content) }
  end

  # 補正後の行番号でログを出力
  link_issues.select { it.issue_type == :bare_url }.each do |issue|
    Common.log_warn(
      "#{issue.filename}:#{issue.line_number} - 裸 URL を検出しました",
      detail: "URL: #{issue.url}"
    )
  end

  # セキュリティ検証(11-1): 危険スキームの検出は常時有効
  # file:// / javascript: 等は --no-verify でも無効化しない
  link_issues += scan_dangerous_schemes(content, filename)

  # 外部 URL チェック用に URL を蓄積(後でバッチ実行)
  if config[:verify_external_links]
    urls = extract_external_urls(content, filename)
    @monitor.synchronize { @external_urls.concat(urls) }
  end

  report = ValidationReport.new(filename:, image_issues:, link_issues:)
  @monitor.synchronize { @reports << report }
  report
end