Module: VivlioStarter::CLI::Import::YamlProcessor
- Defined in:
- lib/vivlio_starter/cli/import/yaml_processor.rb
Overview
YAML 設定ファイル変換モジュール
Constant Summary collapse
- CATALOG_KEY_MAP =
Re:VIEW のセクション名と vivlio-starter のセクション名の対応
{ 'PREDEF' => 'PREFACE', 'CHAPS' => 'CHAPTERS', 'APPENDIX' => 'APPENDICES', 'POSTDEF' => 'POSTFACE' }.freeze
- CATALOG_KEY_PATTERN =
行頭のセクション名。コメントアウトされた例(
## PREDEF:)も同時に直す /\A(\s*(?:#+\s*)?)(#{Regexp.union(CATALOG_KEY_MAP.keys)}):/- RE_EXTENSION_PATTERN =
Re:VIEW の原稿拡張子。後ろに英数字が続かないときだけ落とす (
.reviewのような語を巻き込まないため) /\.re(?![A-Za-z0-9_])/- PAGE_PRESETS =
Re:VIEW Starter の判型と vivlio-starter の判型プリセットの対応
{ 'A5' => 'a5_standard', 'B5' => 'b5_standard' }.freeze
Class Method Summary collapse
-
.build_config_updates(config, config_starter) ⇒ Array<Array>
config.yml / config-starter.yml から更新リストを構築する.
-
.convert_catalog!(starter_dir) ⇒ void
catalog.yml を変換する.
-
.convert_catalog_line(line) ⇒ String
catalog.yml の 1 行を変換する。.
-
.convert_config!(starter_dir) ⇒ void
config.yml / config-starter.yml を book.yml に変換する.
-
.extract_additional_fields(additional) ⇒ Object
additional フィールドから発行者・連絡先を抽出.
-
.extract_text(value) ⇒ Object
複数行テキストやハッシュから文字列を抽出.
-
.format_yaml_scalar(value) ⇒ Object
YAML スカラー値をフォーマットする.
-
.page_preset_for(pagesize) ⇒ String?
Re:VIEW Starter の判型に対応する判型プリセットを返す。.
-
.replace_yaml_value_in_lines!(lines, path, value) ⇒ Object
YAML 行内の値を置換する.
-
.update_book_yaml_with_values(updates) ⇒ Boolean
book.yml を更新する.
-
.use_master_cover! ⇒ Boolean
取り込んだ表紙を book.yml に反映する。.
Class Method Details
.build_config_updates(config, config_starter) ⇒ Array<Array>
config.yml / config-starter.yml から更新リストを構築する
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 |
# File 'lib/vivlio_starter/cli/import/yaml_processor.rb', line 134 def build_config_updates(config, config_starter) updates = [] # 書籍タイトル main_title = extract_text(config['booktitle']) if config['booktitle'] updates << [%w[book main_title], main_title] if main_title && !main_title.empty? # サブタイトル subtitle = extract_text(config['subtitle']) if config['subtitle'] updates << [%w[book subtitle], subtitle] if subtitle && !subtitle.empty? # 言語・ISBN updates << [%w[book language], config['language']] if config['language'] updates << [%w[book isbn], config['isbn']] if config['isbn'] # プロジェクト名・バージョン if config['bookname'] updates << [%w[project name], config['bookname']] updates << [%w[project version], '0.1.0'] end # 著者 if config['aut'] = Array(config['aut']) = .map { |a| a.is_a?(Hash) ? a['name'] : a.to_s } .reject { |name| name.to_s.strip.empty? } updates << [%w[book author], .first] if .any? end # additional フィールドから発行者・連絡先を抽出 updates.concat(extract_additional_fields(config['additional'])) if config['additional'] # 発行履歴 if config['history'] history = Array(config['history']).flatten release = history.find { |entry| !extract_text(entry).to_s.empty? } release_text = extract_text(release) if release updates << [%w[book release], release_text] if release_text && !release_text.empty? end # イベント名 updates << [%w[book series], config['pubevent_name']] if config['pubevent_name'] # 判型 preset = page_preset_for(config_starter.dig('starter', 'pagesize')) updates << [%w[page use], preset] if preset updates end |
.convert_catalog!(starter_dir) ⇒ void
This method returns an undefined value.
catalog.yml を変換する
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/vivlio_starter/cli/import/yaml_processor.rb', line 54 def convert_catalog!(starter_dir) Common.log_action('[Step 5] catalog.yml を変換します') starter_catalog = File.join(starter_dir, 'catalog.yml') unless File.exist?(starter_catalog) Common.log_warn(" catalog.yml が見つかりません: #{starter_catalog}") return end catalog_file = Build::CatalogLoader::CATALOG_FILE converted = File.readlines(starter_catalog, encoding: 'utf-8').map { convert_catalog_line(it) } FileUtils.mkdir_p(File.dirname(catalog_file)) File.write(catalog_file, converted.join, encoding: 'utf-8') Common.log_info(" #{catalog_file} を更新しました(部・コメントは原文のまま)") end |
.convert_catalog_line(line) ⇒ String
catalog.yml の 1 行を変換する。
YAML として読み書きすると「まだ有効にしていない章」を書き残したコメント行
(# - lectures.re)が消える。著者にとっては原稿の予定表そのものなので、
行単位で置き換えて並び・部(部タイトル)・コメントを原文のまま残す。
79 80 81 82 83 84 |
# File 'lib/vivlio_starter/cli/import/yaml_processor.rb', line 79 def convert_catalog_line(line) renamed = line.sub(CATALOG_KEY_PATTERN) do "#{Regexp.last_match(1)}#{CATALOG_KEY_MAP.fetch(Regexp.last_match(2))}:" end renamed.gsub(RE_EXTENSION_PATTERN, '') end |
.convert_config!(starter_dir) ⇒ void
This method returns an undefined value.
config.yml / config-starter.yml を book.yml に変換する
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/vivlio_starter/cli/import/yaml_processor.rb', line 90 def convert_config!(starter_dir) Common.log_action('[Step 6] config.yml を変換します') starter_config = File.join(starter_dir, 'config.yml') starter_config_starter = File.join(starter_dir, 'config-starter.yml') unless File.exist?(starter_config) Common.log_warn(" config.yml が見つかりません: #{starter_config}") return end config = YAML.safe_load_file(starter_config, permitted_classes: [Symbol]) config_starter = if File.exist?(starter_config_starter) YAML.safe_load_file(starter_config_starter, permitted_classes: [Symbol]) else {} end updates = build_config_updates(config, config_starter) if update_book_yaml_with_values(updates) Common.log_info(' config/book.yml を更新しました(コメント保持)') else Common.log_info(' config/book.yml に反映すべき値がありませんでした') end end |
.extract_additional_fields(additional) ⇒ Object
additional フィールドから発行者・連絡先を抽出
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/vivlio_starter/cli/import/yaml_processor.rb', line 206 def extract_additional_fields(additional) updates = [] additional.each do |item| next unless item.is_a?(Hash) case item['key'] when '発行者' value = extract_text(item['value']) updates << [%w[book publisher], value] if value && !value.empty? when '連絡先' contacts = Array(item['value']) email = contacts.find { |c| c.to_s.include?('@') } updates << [%w[book contact], email] if email end end updates end |
.extract_text(value) ⇒ Object
複数行テキストやハッシュから文字列を抽出
225 226 227 228 229 230 231 232 233 234 |
# File 'lib/vivlio_starter/cli/import/yaml_processor.rb', line 225 def extract_text(value) case value when Hash value['name'] || value.values.first when String value.gsub("\n", ' ').strip else value.to_s end end |
.format_yaml_scalar(value) ⇒ Object
YAML スカラー値をフォーマットする
293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/vivlio_starter/cli/import/yaml_processor.rb', line 293 def format_yaml_scalar(value) case value when Numeric value.to_s when TrueClass, FalseClass value.to_s else str = value.to_s return "''" if str.empty? escaped = str.gsub(/["\\]/) { |m| "\\#{m}" } "\"#{escaped}\"" end end |
.page_preset_for(pagesize) ⇒ String?
Re:VIEW Starter の判型に対応する判型プリセットを返す。
同じ判型の standard へ寄せる。行間や余白の好み(airy / compact)は 取り込んだあとに著者が選び直すもので、Re:VIEW 側の値からは決められない。
191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/vivlio_starter/cli/import/yaml_processor.rb', line 191 def page_preset_for(pagesize) key = pagesize.to_s.strip return nil if key.empty? preset = PAGE_PRESETS[key.upcase] return preset if preset Common.log_warn(" 判型 #{key} に対応する判型プリセットがありません。", detail: '対処: config/book.yml の page.use に ' \ "#{PAGE_PRESETS.values.join(' / ')} などから選んで書いてください" \ '(今回は雛形の判型のままにしました)。') nil end |
.replace_yaml_value_in_lines!(lines, path, value) ⇒ Object
YAML 行内の値を置換する
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/vivlio_starter/cli/import/yaml_processor.rb', line 263 def replace_yaml_value_in_lines!(lines, path, value) stack = [] lines.each_with_index do |line, idx| next if line.lstrip.start_with?('#') match = line.match(/^(\s*)([A-Za-z0-9_]+):(.*)$/) next unless match indent = match[1].length key = match[2] stack.pop while stack.any? && stack.last[:indent] >= indent stack << { key: key, indent: indent } next unless stack.map { |item| item[:key] } == path comment = match[3]&.match(/(\s+#.*)$/)&.[](1) scalar = format_yaml_scalar(value) new_line = "#{match[1]}#{key}: #{scalar}" new_line += comment.to_s new_line << "\n" lines[idx] = new_line return true end false end |
.update_book_yaml_with_values(updates) ⇒ Boolean
book.yml を更新する
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/vivlio_starter/cli/import/yaml_processor.rb', line 240 def update_book_yaml_with_values(updates) book_yml_path = 'config/book.yml' unless File.exist?(book_yml_path) Common.log_warn(" #{book_yml_path} が見つからなかったため、更新をスキップします") return false end lines = File.readlines(book_yml_path, encoding: 'utf-8') updated = false updates.each do |path, value| next if value.nil? replaced = replace_yaml_value_in_lines!(lines, path, value) Common.log_warn(" #{book_yml_path} 内で #{path.join('.')} を更新できませんでした") unless replaced updated ||= replaced end File.write(book_yml_path, lines.join, encoding: 'utf-8') if updated updated end |
.use_master_cover! ⇒ Boolean
取り込んだ表紙を book.yml に反映する。
表紙は covers/frontcover_master.png・backcover_master.png として取り込むので、 book.yml 側も master テーマを指している必要がある。master は既定値でもあるが、 著者が light/dark を選んだプロジェクトへ取り込むと持ち込んだ表紙が 黙って無視されるため、ここで明示的に書く。
125 126 127 |
# File 'lib/vivlio_starter/cli/import/yaml_processor.rb', line 125 def use_master_cover! update_book_yaml_with_values([[%w[output cover], 'master']]) end |