Module: Vivlio::Starter::CLI::PreProcessCommands::MarkdownUtils
- Defined in:
- lib/vivlio/starter/cli/pre_process/markdown_utils.rb
Overview
Markdown 処理の共通ユーティリティ
Constant Summary collapse
- EXT_TO_LANG =
拡張子→言語の対応表
{ 'c' => 'c', 'cc' => 'cpp', 'cpp' => 'cpp', 'cs' => 'csharp', 'css' => 'css', 'cxx' => 'cpp', 'go' => 'go', 'html' => 'html', 'java' => 'java', 'js' => 'javascript', 'json' => 'json', 'kt' => 'kotlin', 'md' => 'markdown', 'php' => 'php', 'py' => 'python', 'rb' => 'ruby', 'rs' => 'rust', 'scala' => 'scala', 'scss' => 'scss', 'sh' => 'bash', 'sql' => 'sql', 'swift' => 'swift', 'ts' => 'typescript', 'xml' => 'xml', 'yaml' => 'yaml', 'yml' => 'yaml' }.freeze
- CODE_SPAN_PLACEHOLDER_PREFIX =
'__VS_CODE_SPAN__'
Class Method Summary collapse
-
.detect_language(file_path) ⇒ Object
拡張子から言語名を推定.
-
.escape_inline_code_html(md_text) ⇒ Object
インラインコード(‘…`)内は、そのままの文字列を維持する.
-
.extract_code_spans(text) ⇒ Object
コードスパン(バッククォートで囲まれた部分)を一時的に退避し、 その中身を後続のテキスト変形処理から除外するためのユーティリティ。 コードフェンス(“‘…“`)も退避対象とする。.
-
.pipe_table_to_html(md_text) ⇒ Object
パイプテーブルを簡易HTML化.
-
.render_markdown_fallback(md_text) ⇒ Object
Kramdown が使えない場合のフォールバック実装.
-
.render_markdown_to_html(md_text) ⇒ Object
簡易Markdown→HTML 変換.
-
.restore_code_spans(text, spans) ⇒ Object
extract_code_spans で退避したコードスパンを元に戻す.
Class Method Details
.detect_language(file_path) ⇒ Object
拡張子から言語名を推定
113 114 115 116 |
# File 'lib/vivlio/starter/cli/pre_process/markdown_utils.rb', line 113 def detect_language(file_path) ext = File.extname(file_path).downcase.delete_prefix('.') EXT_TO_LANG.fetch(ext, 'text') end |
.escape_inline_code_html(md_text) ⇒ Object
インラインコード(‘…`)内は、そのままの文字列を維持する
108 109 110 |
# File 'lib/vivlio/starter/cli/pre_process/markdown_utils.rb', line 108 def escape_inline_code_html(md_text) md_text.to_s end |
.extract_code_spans(text) ⇒ Object
コードスパン(バッククォートで囲まれた部分)を一時的に退避し、その中身を後続のテキスト変形処理から除外するためのユーティリティ。コードフェンス(“‘…“`)も退避対象とする。
対応するケース:
- 3 個以上のバッククォートまたはチルダによるフェンス(先頭 0-3 スペースまで許容)
- 単一バッククォートのインラインコード `foo`
- マルチバッククォートのインラインコード ``foo`bar`` (N 個の開き=N 個の閉じ)
64 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 |
# File 'lib/vivlio/starter/cli/pre_process/markdown_utils.rb', line 64 def extract_code_spans(text) spans = {} counter = 0 alloc = lambda do |match| key = "#{CODE_SPAN_PLACEHOLDER_PREFIX}#{counter}__" spans[key] = match counter += 1 key end # まずコードフェンスブロック全体を退避(インラインコードより先に処理) # CommonMark に合わせて先頭 0-3 スペースのインデントも許容する。 # ```include: で始まる行はインクルード記法であり、フェンスブロックの # 開始ではないため除外する。 protected_text = text.to_s.gsub(/^ {0,3}(`{3,}|~{3,}).*?^ {0,3}\1\s*$/m) do |block| # ```include: で始まる行はフェンスブロックではなくインクルード記法 if block.match?(/\A\s*`{3,}include:/) block else alloc.call(block) end end # 次にインラインコードスパンを退避 # N 個の連続バッククォート同士の対を 1 組として扱い、 # ``foo`bar`` のように内部にバッククォートを含むケースも保護する。 # (?<!`) / (?!`) で開き・閉じの両端が「ちょうど N 個のラン」であることを担保する。 protected_text = protected_text.gsub(/(?<!`)(`+)(?!`).+?(?<!`)\1(?!`)/m, &alloc) [protected_text, spans] end |
.pipe_table_to_html(md_text) ⇒ Object
パイプテーブルを簡易HTML化
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 226 227 228 |
# File 'lib/vivlio/starter/cli/pre_process/markdown_utils.rb', line 185 def pipe_table_to_html(md_text) text = md_text.to_s.strip lines = text.split(/\r?\n/).map(&:rstrip) return nil if lines.size < 2 header = lines[0] sep = lines[1] return nil unless header.include?('|') return nil unless sep && sep =~ /^\s*\|?[\s:\-|]+\|?\s*$/ rows = lines[2..] || [] to_cells = lambda do |line| parts = line.split('|') parts.shift if parts.first&.strip == '' parts.pop if parts.last&.strip == '' parts.map(&:strip) end esc_code = lambda do |s| s.gsub(/`([^`]+)`/) { "<code>#{::Regexp.last_match(1)}</code>" } .gsub('&', '&') .gsub('<', '<') .gsub('>', '>') end thead_cells = to_cells.call(header) tbody_rows = rows.map { |r| to_cells.call(r) } html = [] html << '<table>' html << ' <thead>' html << " <tr>#{thead_cells.map { |c| "<th>#{esc_code.call(c)}</th>" }.join}</tr>" html << ' </thead>' if tbody_rows.any? html << ' <tbody>' tbody_rows.each do |cells| html << " <tr>#{cells.map { |c| "<td>#{esc_code.call(c)}</td>" }.join}</tr>" end html << ' </tbody>' end html << '</table>' html.join("\n") end |
.render_markdown_fallback(md_text) ⇒ Object
Kramdown が使えない場合のフォールバック実装
129 130 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 |
# File 'lib/vivlio/starter/cli/pre_process/markdown_utils.rb', line 129 def render_markdown_fallback(md_text) lines = md_text.to_s.split(/\r?\n/) html_parts = [] in_ol = false buffer_p = [] flush_p = lambda do unless buffer_p.empty? paragraph = buffer_p.join(' ').strip html_parts << "<p>#{paragraph}</p>" unless paragraph.empty? buffer_p.clear end end lines.each do |line| if line.strip.empty? flush_p.call next end # 画像 if (m = line.match(/^\s*!\[[^\]]*\]\(([^)]+)\)\s*$/)) flush_p.call src = m[1] html_parts << "<img src=\"#{src}\">" next end # 見出し相当の太字行 if (m = line.match(/^\s*\*\*(.+?)\*\*\s*$/)) flush_p.call html_parts << "<p><strong>#{m[1]}</strong></p>" next end # 番号リスト if (m = line.match(/^\s*(\d+)\.\s+(.*)$/)) flush_p.call html_parts << '<ol>' unless in_ol in_ol = true html_parts << "<li>#{m[2]}</li>" next elsif in_ol html_parts << '</ol>' in_ol = false end buffer_p << line end flush_p.call html_parts << '</ol>' if in_ol html_parts.join("\n") end |
.render_markdown_to_html(md_text) ⇒ Object
簡易Markdown→HTML 変換
119 120 121 122 123 124 125 126 |
# File 'lib/vivlio/starter/cli/pre_process/markdown_utils.rb', line 119 def render_markdown_to_html(md_text) # まずはKramdownを試す require 'kramdown' Kramdown::Document.new(md_text, syntax_highlighter: nil).to_html rescue LoadError # フォールバック: 最小限のMarkdownをHTMLへ render_markdown_fallback(md_text) end |
.restore_code_spans(text, spans) ⇒ Object
extract_code_spans で退避したコードスパンを元に戻す
98 99 100 101 102 103 104 105 |
# File 'lib/vivlio/starter/cli/pre_process/markdown_utils.rb', line 98 def restore_code_spans(text, spans) restored = text.to_s # gsub の置換文字列として解釈されないよう Regexp.last_match を使う spans.each do |placeholder, original| restored = restored.gsub(placeholder) { original } end restored end |