Module: Vivlio::Starter::CLI::PreProcessCommands::CrossReferenceProcessor

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

Overview

クロスリファレンス処理モジュールrubocop:disable Metrics/ModuleLength

Defined Under Namespace

Classes: CaptionedBlockTransformer, Label, LabelCollectorContext, ReferenceReplacer

Constant Summary collapse

LABEL_TYPE_NAMES =

ラベル種別の日本語名

{ list: 'リスト', table: '', fig: '' }.freeze
CAPTION_PATTERN =
/^\*\*\s*(.+?)\s+@([-\w]+)\s*\*\*\s*$/
RESERVED_IDS =

自動採番用の予約ID(キャプションで @auto / @omakase / @id と書くと type-chapter-N 形式に採番される)

%w[auto omakase id].freeze
RESERVED_MACRO_IDS =

config/post_replace_list.yml のマクロ名(完全一致で予約)。これらは @ID 参照ではなくシステム予約のマクロなので、未定義のラベルIDとして警告しない。

%w[
  div divend
  nega posi clear
  comment commend
].freeze
RESERVED_MACRO_POSITION_PREFIXES =

config/post_replace_list.yml の絶対配置+SVG ガイド線マクロ接頭辞。例: @lu25,15@20,30 の ‘lu25` や、@ls40@20,20 の `ls40` など、接頭辞に続く数字列(幅/高さ指定)を丸ごと予約する。Planned 扱いのため現状はコメントアウトされているが、資料やコードサンプルで登場しても警告しないようにしておく。

%w[
  lu ld ru rd ur
  ls rs us ds
].freeze
IMAGE_PATTERN =
/^!\[[^\]]*\]\([^)]+\)(?:\{[^}]+\})?$/
MAIN_CHAPTER_RANGE =
PostProcessCommands::HeadingProcessor::MAIN_CHAPTER_RANGE

Class Method Summary collapse

Class Method Details

.build_labels_map_with_duplicates_check(all_labels) ⇒ Hash

レポート生成

Returns:

  • (Hash)

    labels_map と duplicates_by_id を含むduplicates_by_id: { id => { first_label: Label, all_labels: [Label, …] } }



214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/vivlio/starter/cli/pre_process/cross_reference_processor.rb', line 214

def build_labels_map_with_duplicates_check(all_labels)
  map = {}
  # IDごとに全ラベルを蓄積する(先勝ちで map に登録)
  all_occurrences = Hash.new { |h, k| h[k] = [] }

  all_labels.each do |label|
    all_occurrences[label.id] << label
    map[label.id] ||= label
  end

  duplicates_by_id = all_occurrences.select { |_, labels| labels.size > 1 }
  { labels_map: map, duplicates_by_id: }
end

.collect_labels(content, source_file, chapter_number) ⇒ Object

ラベル収集



136
137
138
139
# File 'lib/vivlio/starter/cli/pre_process/cross_reference_processor.rb', line 136

def collect_labels(content, source_file, chapter_number)
  collector = LabelCollectorContext.new(source_file, chapter_number)
  collector.collect(content)
end

.detect_block_type(lines, idx) ⇒ Object



101
102
103
104
105
106
107
108
109
# File 'lib/vivlio/starter/cli/pre_process/cross_reference_processor.rb', line 101

def detect_block_type(lines, idx)
  ((idx + 1)...lines.size).each do |index|
    line = lines[index].strip
    next if line.empty? || line.start_with?(':::{')

    return detect_type_from_line(line)
  end
  nil
end

.display_chapter_number_for_filename(filename) ⇒ Object



126
127
128
129
130
131
132
133
# File 'lib/vivlio/starter/cli/pre_process/cross_reference_processor.rb', line 126

def display_chapter_number_for_filename(filename)
  num = extract_chapter_number(filename).to_i
  return num.to_s unless MAIN_CHAPTER_RANGE.include?(num)

  token = File.basename(filename, File.extname(filename))
  idx = main_chapter_order.index(token)
  idx ? (idx + 1).to_s : (num - 10).to_s
end

.extract_caption_label(line) ⇒ Object



93
94
95
96
97
98
99
# File 'lib/vivlio/starter/cli/pre_process/cross_reference_processor.rb', line 93

def extract_caption_label(line)
  match = line.match(CAPTION_PATTERN)
  return nil unless match

  { title: match[1].strip, id: match[2].strip,
    auto: RESERVED_IDS.include?(match[2].strip) }
end

.extract_chapter_number(filename) ⇒ Object

章番号関連



121
122
123
124
# File 'lib/vivlio/starter/cli/pre_process/cross_reference_processor.rb', line 121

def extract_chapter_number(filename)
  match = File.basename(filename, '.*').match(/^(\d+)/)
  match ? match[1] : '0'
end

.process_cross_references(chapters) ⇒ Object

Public API ===



80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/vivlio/starter/cli/pre_process/cross_reference_processor.rb', line 80

def process_cross_references(chapters)
  all_labels, all_errors = collect_all_labels(chapters)
  labels_map, duplicates = build_labels_map(all_labels)
  log_duplicates(duplicates, all_errors)

  processed = transform_all_chapters(chapters, labels_map)
  processed, ref_errors = replace_all_references(processed, labels_map)
  all_errors.concat(ref_errors)

  { chapters: processed, report: generate_report(all_labels),
    errors: all_errors, labels_count: all_labels.size }
end

.replace_references(content, labels_map, filename = nil) ⇒ Object

参照置換



207
208
209
# File 'lib/vivlio/starter/cli/pre_process/cross_reference_processor.rb', line 207

def replace_references(content, labels_map, filename = nil)
  ReferenceReplacer.new(content, labels_map, filename).replace
end

.reserved_id?(label_id) ⇒ Boolean

予約IDの判定を一元化する。RESERVED_IDS: auto / omakase / id RESERVED_MACRO_IDS: div / nega / comment など完全一致RESERVED_MACRO_POSITION_PREFIXES: lu25 / ls40 など接頭辞+数字

Returns:

  • (Boolean)


56
57
58
59
60
61
# File 'lib/vivlio/starter/cli/pre_process/cross_reference_processor.rb', line 56

def self.reserved_id?(label_id)
  return true if RESERVED_IDS.include?(label_id)
  return true if RESERVED_MACRO_IDS.include?(label_id)

  RESERVED_MACRO_POSITION_PREFIXES.any? { |prefix| label_id.match?(/\A#{prefix}\d*\z/) }
end

.transform_captioned_blocks(content, filename, labels_map) ⇒ Object

キャプション付きブロック変換



202
203
204
# File 'lib/vivlio/starter/cli/pre_process/cross_reference_processor.rb', line 202

def transform_captioned_blocks(content, filename, labels_map)
  CaptionedBlockTransformer.new(content, filename, labels_map).transform
end