Module: VivlioStarter::CLI::Build::SectionBuilder

Defined in:
lib/vivlio_starter/cli/build/section_builder.rb

Overview

セクション HTML 生成モジュール

Constant Summary collapse

PREFACE_RANGE =

章レンジ(定数)- 新仕様に合わせて更新

(0..0)
MAIN_RANGE =
(1..89)
APPX_RANGE =
(90..98)
POSTFACE_RANGE =
(99..99)

Class Method Summary collapse

Class Method Details

.chapter_order_from(basenames, base_dir = Common::BUILD_HTML_DIR) ⇒ Object

章順序を取得(ベース名配列から) 中間 HTML はワークスペースの html/ に置かれる(P4 §3.4-1)



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/vivlio_starter/cli/build/section_builder.rb', line 44

def chapter_order_from(basenames, base_dir = Common::BUILD_HTML_DIR)
  basenames = Array(basenames).map { |bn| bn.to_s.strip }.reject(&:empty?).uniq
  return [] if basenames.empty?

  resolver = TokenResolver::Resolver.new
  sort_key = lambda do |bn|
    entry = resolver.resolve_file(bn)
    entry.number ? [entry.number.to_i, bn] : [Float::INFINITY, bn]
  end

  html_basenames = Dir.glob(File.join(base_dir, '*.html'))
                      .map { |path| File.basename(path, '.html') }
                      .uniq
                      .sort_by { |bn| sort_key.call(bn) }

  ordered = html_basenames.select { |bn| basenames.include?(bn) }

  remaining = basenames - ordered
  remaining_sorted = remaining.sort_by { |bn| sort_key.call(bn) }

  ordered + remaining_sorted
end

.convert_sections_html!(entries_or_keep = nil) ⇒ Object

セクション(前書き/本文/付録/後書き)の変換を一括実行

Parameters:

  • entries_or_keep (Array<TokenResolver::Entry>, Array<String>, nil) (defaults to: nil)

    Entry 配列または basename 配列



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/vivlio_starter/cli/build/section_builder.rb', line 124

def convert_sections_html!(entries_or_keep = nil)
  Common.log_action('[Step 4b] セクションの変換(HTML 生成)を実行します…')
  targets = resolve_targets(entries_or_keep)
  return if targets.empty?

  # 並列処理前に章の表示順を確定させる。
  # HeadingProcessor の @main_chapter_order はモジュールレベルのキャッシュなので、
  # 並列スレッドが不完全な HTML リストでキャッシュを作る前に正しい順序を注入する。
  main_tokens = targets.select { |t| t.match?(/\A\d{2}-/) && t[/\A(\d{2})/, 1].to_i.between?(1, 89) }
  PostProcessCommands::HeadingProcessor.chapter_tokens_override = main_tokens unless main_tokens.empty?

  concurrency = determine_concurrency
  if concurrency == 1
    targets.each { |target| convert_single_chapter!(target) }
  else
    parallel_each(targets, concurrency: concurrency) { |target| convert_single_chapter!(target) }
  end
end

.convert_single_chapter!(basename) ⇒ Object

単一章の変換(HTML生成)



99
100
101
102
# File 'lib/vivlio_starter/cli/build/section_builder.rb', line 99

def convert_single_chapter!(basename)
  ConvertCommands.execute_convert({}, [basename])
  PostProcessCommands.execute_post_process({}, [basename])
end

.determine_concurrencyObject

並列度を決定



164
165
166
167
168
169
170
171
172
# File 'lib/vivlio_starter/cli/build/section_builder.rb', line 164

def determine_concurrency
  concurrency = (ENV['VIVLIO_BUILD_CONCURRENCY'] || '').to_i
  if concurrency <= 0
    n_cores = Etc.respond_to?(:nprocessors) ? Etc.nprocessors : 2
    concurrency = [n_cores, 4].min
    concurrency = 1 if concurrency <= 0
  end
  concurrency
end

.parallel_each(items, concurrency: 1) ⇒ Object

簡易スレッドプールで並列実行



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/vivlio_starter/cli/build/section_builder.rb', line 69

def parallel_each(items, concurrency: 1, &)
  list = Array(items)
  effective_concurrency = concurrency.to_i
  effective_concurrency = 1 if effective_concurrency <= 0
  Common.log_info("[parallel_each] concurrency=#{effective_concurrency}")
  return list.each(&) if effective_concurrency <= 1

  q = Queue.new
  list.each { |it| q << it }
  sentinel = Object.new
  effective_concurrency.times { q << sentinel }
  workers = Array.new(effective_concurrency) do
    Thread.new do
      loop do
        it = q.pop
        break if it.equal?(sentinel)

        yield(it)
      end
    end
  end
  workers.each(&:join)
end

.preprocess_sections!(entries_or_keep = nil) ⇒ Object

セクション(前書き/本文/付録/後書き)の前処理を一括実行

Parameters:

  • entries_or_keep (Array<TokenResolver::Entry>, Array<String>, nil) (defaults to: nil)

    Entry 配列または basename 配列



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/vivlio_starter/cli/build/section_builder.rb', line 106

def preprocess_sections!(entries_or_keep = nil)
  Common.log_action('[Step 3] セクションの前処理(Markdown 修正)を実行します…')
  targets = resolve_targets(entries_or_keep)
  return if targets.empty?

  concurrency = determine_concurrency
  if concurrency == 1
    targets.each { |target| preprocess_single_chapter!(target) }
  else
    parallel_each(targets, concurrency: concurrency) { |target| preprocess_single_chapter!(target) }
  end

  # 全章の前処理完了後に1回だけクロスリファレンス処理を実行する
  PreProcessCommands.execute_cross_references(targets)
end

.preprocess_single_chapter!(basename) ⇒ Object

単一章の前処理



94
95
96
# File 'lib/vivlio_starter/cli/build/section_builder.rb', line 94

def preprocess_single_chapter!(basename)
  PreProcessCommands.execute_pre_process({}, [basename])
end

.resolve_targets(entries_or_keep = nil) ⇒ Array<String>

対象章を解決(Entry 配列または basename 配列から basename 配列を返す)

Parameters:

Returns:

  • (Array<String>)

    basename 配列



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/vivlio_starter/cli/build/section_builder.rb', line 146

def resolve_targets(entries_or_keep = nil)
  raw = Array(entries_or_keep).compact
  if raw.any?
    # Entry オブジェクトかどうかを判定
    if raw.first.respond_to?(:basename)
      raw.map(&:basename).sort
    else
      raw.map { |s| File.basename(s.to_s, '.md') }.sort
    end
  else
    Dir[File.join(Common::CONTENTS_DIR, '*.md')]
      .map { |p| File.basename(p, '.md') }
      .reject { |bn| bn.start_with?('_') }
      .sort
  end
end