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"]