Module: Vivlio::Starter::CLI::PreProcessCommands::MarkdownTransformer
- Defined in:
- lib/vivlio/starter/cli/pre_process/markdown_transformer.rb
Overview
Markdown 特殊記法変換モジュール
Class Method Summary collapse
-
.convert_book_card_inner_markdown(content) ⇒ Object
<div class=“book-card”> …
-
.convert_container_blocks(content, class_name:) ⇒ Object
- :
- … 記法で囲まれたコンテナを div に変換。 コードブロック内の :
-
記法は変換対象外とする。.
-
.convert_table_container_inner_markdown(content, class_name) ⇒ Object
<div …
-
.convert_table_rotate_inner_markdown(content) ⇒ Object
<div …
-
.escape_inline_code_html(line) ⇒ Object
インラインコード内の HTML 予約文字をエスケープする.
-
.format_book_card_inner_html(inner_html) ⇒ Object
book-card の内側を整形.
-
.normalize_book_card_md(md_text) ⇒ Object
book-card 内のMarkdownを事前整形.
-
.process_code_include(content, source_filename: nil, source_path: nil) ⇒ Object
“‘include:path“` を検出し、codes/ または絶対パスから読込。 マークダウンのコードブロックおよびインラインコード内に記述された include 記法は記法の説明例であるためスキップする。.
-
.transform_links_to_footnotes(md_text) ⇒ Object
Markdown内のリンク記法を脚注化.
Class Method Details
.convert_book_card_inner_markdown(content) ⇒ Object
<div class=“book-card”> … </div> の内側MarkdownをHTMLへ
102 103 104 105 106 107 108 109 110 |
# File 'lib/vivlio/starter/cli/pre_process/markdown_transformer.rb', line 102 def convert_book_card_inner_markdown(content) content.gsub(%r{<div class="book-card">\n(.*?)\n</div>}m) do inner = ::Regexp.last_match(1) normalized = normalize_book_card_md(inner) html = MarkdownUtils.render_markdown_to_html(normalized) formatted = format_book_card_inner_html(html) "<div class=\"book-card\">\n#{formatted}\n</div>" end end |
.convert_container_blocks(content, class_name:) ⇒ Object
- :
-
… 記法で囲まれたコンテナを div に変換。
- コードブロック内の :
-
記法は変換対象外とする。
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 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/markdown_transformer.rb', line 171 def convert_container_blocks(content, class_name:) opened_count = 0 closed_count = 0 # --- Phase: コードブロック退避 --- protected_text, spans = MarkdownUtils.extract_code_spans(content) pattern = /:::\s*\{\.([^}]+)\}\s*\n(.*?)\n:::\s*(?:\n|$)/m converted = protected_text.gsub(pattern) do raw_token_str = ::Regexp.last_match(1) inner = ::Regexp.last_match(2) raw_tokens = raw_token_str.split first_class = raw_tokens.first additional_tokens = raw_tokens.drop(1) additional_classes = additional_tokens.select { |t| t.start_with?('.') }.map { |c| c.delete_prefix('.') } param_tokens = additional_tokens.reject { |t| t.start_with?('.') } next ::Regexp.last_match(0) unless first_class == class_name || additional_classes.include?(class_name) opened_count += 1 closed_count += 1 all_classes = [first_class] + additional_classes class_attr = all_classes.join(' ') style_parts = [] param_tokens.each do |token| if (m_scale = token.match(/^scale=(.+)$/)) raw = m_scale[1].strip scale_percent = raw.end_with?('%') ? raw.to_f : raw.to_f * 100.0 scale_int = scale_percent.round style_parts << "--table-rotate-scale:#{scale_int}%;" end next unless (m_shift = token.match(/^shift-y=(.+)$/)) raw = m_shift[1].strip shift_percent = raw.end_with?('%') ? raw.to_f : raw.to_f * 100.0 shift_int = shift_percent.round sign = shift_int.negative? ? '' : '+' style_parts << "--table-rotate-shift-y:#{sign}#{shift_int}%;" end style_attr = style_parts.empty? ? '' : " style=\"#{style_parts.join(' ')}\"" "<div class=\"#{class_attr}\"#{style_attr}>\n#{inner}\n</div>\n\n" end # --- Phase: コードブロック復元 --- converted = MarkdownUtils.restore_code_spans(converted, spans) [converted, opened_count, closed_count] end |
.convert_table_container_inner_markdown(content, class_name) ⇒ Object
<div … class=“… CLASS …” …> … </div> の内側パイプテーブルをHTMLへ変換する汎用メソッド
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/vivlio/starter/cli/pre_process/markdown_transformer.rb', line 118 def convert_table_container_inner_markdown(content, class_name) content.gsub(%r{<div\s+([^>]*\bclass="[^"]*\b#{Regexp.escape(class_name)}\b[^"]*"[^>]*)>\s*(.*?)\s*</div>}m) do attrs = ::Regexp.last_match(1) inner = ::Regexp.last_match(2) normalized = "\n\n#{inner.to_s.strip}\n\n" html = MarkdownUtils.render_markdown_to_html(normalized).to_s.strip if !html.include?('<table') && inner.include?('|') table_html = MarkdownUtils.pipe_table_to_html(inner) html = table_html if table_html end "<div #{attrs}>\n#{html}\n</div>" end end |
.convert_table_rotate_inner_markdown(content) ⇒ Object
<div … class=“… table-rotate …” …> … </div> の内側MarkdownをHTMLへ
113 114 115 |
# File 'lib/vivlio/starter/cli/pre_process/markdown_transformer.rb', line 113 def convert_table_rotate_inner_markdown(content) convert_table_container_inner_markdown(content, 'table-rotate') end |
.escape_inline_code_html(line) ⇒ Object
インラインコード内の HTML 予約文字をエスケープする
228 229 230 |
# File 'lib/vivlio/starter/cli/pre_process/markdown_transformer.rb', line 228 def escape_inline_code_html(line) MarkdownUtils.escape_inline_code_html(line) end |
.format_book_card_inner_html(inner_html) ⇒ Object
book-card の内側を整形
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 |
# File 'lib/vivlio/starter/cli/pre_process/markdown_transformer.rb', line 136 def format_book_card_inner_html(inner_html) html = inner_html.to_s.strip img_match = html.match(/<img[^>]*>/i) return inner_html unless img_match img_tag = img_match[0].gsub(%r{\s*/?>}i) { '>' } if html.sub!(%r{<p>\s*#{Regexp.escape(img_match[0])}\s*</p>}i, '') # removed wrapped <p> with img else html.sub!(img_match[0], '') end title_match = html.match(%r{<p>\s*<strong>(.*?)</strong>\s*</p>}im) return inner_html unless title_match title_text = title_match[1].strip html.sub!(title_match[0], '') description_html = html.strip parts = [] parts << " #{img_tag}" parts << ' <div class="book-info">' parts << " <p class=\"book-title\">#{title_text}</p>" parts << ' <div class="book-description">' parts << " #{description_html}" parts << ' </div>' parts << ' </div>' parts.join("\n") end |
.normalize_book_card_md(md_text) ⇒ Object
book-card 内のMarkdownを事前整形
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/vivlio/starter/cli/pre_process/markdown_transformer.rb', line 85 def normalize_book_card_md(md_text) lines = md_text.to_s.split(/\r?\n/, -1) out = [] lines.each_with_index do |line, i| out << line next_line = lines[i + 1] if line.match(/^\s*!\[[^\]]*\]\([^)]+\)\s*$/) out << '' if next_line && next_line.strip != '' elsif line.match(/^\s*\*\*[^*].*\*\*\s*$/) out << '' if next_line && next_line.strip != '' end end out.join("\n") end |
.process_code_include(content, source_filename: nil, source_path: nil) ⇒ Object
“‘include:path“` を検出し、codes/ または絶対パスから読込。マークダウンのコードブロックおよびインラインコード内に記述されたinclude 記法は記法の説明例であるためスキップする。
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 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 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 |
# File 'lib/vivlio/starter/cli/pre_process/markdown_transformer.rb', line 238 def process_code_include(content, source_filename: nil, source_path: nil) matches_found = 0 line_number_map = build_line_number_map(content) skippable_lines = lines_inside_code_blocks(content) inline_code_lines = lines_with_inline_code_include(content) source_line_map = build_source_include_line_map(source_path) content.gsub!(/```include:([^:`\s]+)(?::(\d+)-(\d+))?\s*```/) do |match| # コードブロック内の include 記法はスキップ(記法説明用の例文) if (ln = line_number_map[match]) && skippable_lines.include?(ln) next match end # インラインコード内の include 記法はスキップ # `` ```include:file.rb``` `` のようにバッククォートで囲まれた場合 if (ln = line_number_map[match]) && inline_code_lines.include?(ln) next match end matches_found += 1 original_path = ::Regexp.last_match(1) start_line = ::Regexp.last_match(2)&.to_i end_line = ::Regexp.last_match(3)&.to_i Common.log_action("マッチ発見: #{match.strip}") Common.log_info("元のパス: #{original_path}") file_path = if original_path.start_with?('/') original_path else File.join(Common::CODES_DIR, original_path) end Common.log_info("解決されたパス: #{file_path}") if File.exist?(file_path) source_content = File.read(file_path) lines = source_content.lines code_content = if start_line && end_line selected_lines = lines[(start_line - 1)..(end_line - 1)] selected_lines.join else "#{source_content}\n" end language = MarkdownUtils.detect_language(file_path) replacement = "```#{language}:#{original_path}\n#{code_content}```" Common.log_success("置換完了: #{original_path} (#{language})") replacement else code_name = File.basename(original_path) # 元ファイルの行番号があればそちらを使う source_ln = source_line_map[original_path] || line_number_map[match] if source_filename && source_ln Common.log_error( "#{source_filename}:#{source_ln} - ソースコード '#{code_name}' が見つかりません", detail: "コードの場所: #{file_path}" ) LinkImageValidator.record_code_include_error(source_filename, source_ln, code_name) else Common.log_error( "ソースコード '#{code_name}' が見つかりません", detail: "コードの場所: #{file_path}" ) LinkImageValidator.record_code_include_error(source_filename || '(不明)', 0, code_name) end match end end Common.log_info("#{matches_found}個のinclude記法を処理") if matches_found.positive? content end |
.transform_links_to_footnotes(md_text) ⇒ Object
Markdown内のリンク記法を脚注化
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/vivlio/starter/cli/pre_process/markdown_transformer.rb', line 40 def transform_links_to_footnotes(md_text) original = md_text.to_s text, code_spans = MarkdownUtils.extract_code_spans(original) max_n = 0 text.scan(/\[\^url(\d+)\]:/).each do |m| n = m[0].to_i max_n = n if n > max_n end url_id = {} replacements = [] replaced = text.gsub(/(?<!!)\[(.+?)\]\((https?:[^\s)]+)\)(?!\s*\[^url\d+\])/) do |_match| label = ::Regexp.last_match(1) url = ::Regexp.last_match(2) id = (url_id[url] ||= begin max_n += 1 "url#{max_n}" end) replacements << [id, url] "[#{label}](#{url}) [^#{id}]" end existing_defs = {} text.scan(/\[\^(url\d+)\]:\s*(\S+)/) { |id, u| existing_defs[id] = u } new_defs = url_id.filter_map do |u, id| next nil if existing_defs.key?(id) "[^#{id}]: #{u}" end result = if new_defs.empty? replaced elsif replaced.strip.end_with?("\n") "#{replaced}\n#{new_defs.join("\n")}\n" else "#{replaced}\n\n#{new_defs.join("\n")}\n" end MarkdownUtils.restore_code_spans(result, code_spans) end |