Rubyのパターンマッチを使ってMarkdownからコードブロックを抜き出す
Rubyのパターンマッチを使って、(GitHub Flavored) Markdownのテキストからコードブロックを抜き出す。次のようなテキストを考える。
# Ruby
## hello world
Rubyでは次のようにhello worldします。
```ruby
puts 'hello world'
```
```
これはRubyではありません。
```
## error
Rubyでは次のようにエラーを起こします。
```ruby
raise 'error'
```
Markdownのパース
まず、このMarkdownをパースするために、kramdown (2.4.0)とkramdown-parser-gfm (1.1.0)を使う。Markdownをパースして、ASTのハッシュを変換する。
require 'kramdown'
require 'kramdown-parser-gfm'
doc = <<~EOS
# Ruby
## hello world
Rubyでは次のようにhello worldします。
```ruby
puts 'hello world'
```
```
これはRubyではありません。
```
## error
Rubyでは次のようにエラーを起こします。
```ruby
raise 'error'
```
EOS
ast = Kramdown::Document.new(doc, input: 'GFM').to_hash_ast
これで次のようなASTが取得できる。children
にMarkdownテキスト上の各要素を持ち、それぞれ同じレベルに並んでいる。
{:type=>:root,
:options=>
{:encoding=>#<Encoding:UTF-8>,
:location=>1,
:options=>{},
:abbrev_defs=>{},
:abbrev_attr=>{},
:footnote_count=>0},
:children=>
[{:type=>:header,
:attr=>{"id"=>"ruby"},
:options=>
{:level=>1, :raw_text=>"Ruby", :location=>1},
:children=>
[{:type=>:text,
:value=>"Ruby",
:options=>{:location=>1}}]},
{:type=>:header,
:attr=>{"id"=>"hello-world"},
:options=>
{:level=>2,
:raw_text=>"hello world",
:location=>2},
:children=>
[{:type=>:text,
:value=>"hello world",
:options=>{:location=>2}}]},
{:type=>:p,
:options=>{:location=>3},
:children=>
[{:type=>:text,
:value=>"Rubyでは次のようにhello worldします。",
:options=>{:location=>3}}]},
{:type=>:codeblock,
:attr=>{"class"=>"language-ruby"},
:value=>"puts 'hello world'\n",
:options=>
{:location=>4, :fenced=>true, :lang=>"ruby"}},
{:type=>:codeblock,
:value=>"これはRubyではありません。\n",
:options=>{:location=>7, :fenced=>true}},
{:type=>:header,
:attr=>{"id"=>"error"},
:options=>
{:level=>2, :raw_text=>"error", :location=>10},
:children=>
[{:type=>:text,
:value=>"error",
:options=>{:location=>10}}]},
{:type=>:p,
:options=>{:location=>11},
:children=>
[{:type=>:text,
:value=>"Rubyでは次のようにエラーを起こします。",
:options=>{:location=>11}}]},
{:type=>:codeblock,
:attr=>{"class"=>"language-ruby"},
:value=>"raise 'error'\n",
:options=>
{:location=>12, :fenced=>true, :lang=>"ruby"}}]}
配列とハッシュの構造に対するパターンマッチでコードブロックを抽出
このASTに対して、パターンマッチで特定の構造にマッチさせて値を取得できる。ruby
のinfo stringが付与されたコードブロックを抜き出すときは、次のようにchildren
の各ノードに対してパターンマッチを適用する。
# rubyのinfo stringが付与されたコードブロックを抜き出す
def extract_ruby_codeblocks(nodes, code_blocks)
return if nodes.empty?
# Markdownテキストの各要素のハッシュの配列に対してパターンマッチを実行する
case nodes
# この構造のハッシュにマッチするとき、キー:valueの値を変数valueに入れる。
# マッチするハッシュの前後についてもパターンの適用が必要。
# 最初にマッチするハッシュが出てくるまでの分は*でマッチさせて捨てる。
# マッチしたハッシュより後ろの配列は*restという記法で変数restに入れる
in *, { type: :codeblock, options: { lang: 'ruby' }, value: value }, *rest
code_blocks << value
# restに対して繰り返しパターンマッチを実行する
extract_ruby_codeblocks(rest, code_blocks)
else
# pass
end
end
code_blocks = []
extract_ruby_codeblocks(ast[:children], code_blocks)
結果のcode_blocks
の中身は次のとおり。ruby
というinfo stringが付与されたコードブロックだけ抜き出せている。
["puts 'hello world'\n", "raise 'error'\n"]