Ogihara Ryo

Rails でシンタックスハイライトに対応した Markdown を書く

Tuesday, 09 27, 2016 21:00:27

概要

この技術ブログを Ruby on Rails で開発しようと思った時、まず悩んだのが Markdown で記事を書く方法と、コードをシンタックスハイライトする方法だ。結論から言うと、Markdown は Redcarpet、シンタックスハイライトは CodeRay という Gem を利用することで一瞬で解決することができた。本記事では、この2つの Gem について軽く解説する。

使い方

インストール

Gemfile に redcarpetcoderay を追加し、 bundle install する。

gem 'redcarpet'
gem 'coderay'
$ bundle

Helper の作成

app/helpers/markdown_helper.rb に下記のように記述する。コードの解説は後述。

module MarkdownHelper
  REDCARPET_OPTIONS = {
    autolink: true,
    space_after_headers: true,
    no_intra_emphasis: true,
    fenced_code_blocks: true,
    tables: true,
    hard_wrap: true,
    xhtml: true,
    lax_html_blocks: true,
    strikethrough: true
  }.freeze

  def markdown_to_html(md_code)
    html_render = HTMLwithCoderay.new(filter_html: true, hard_wrap: true)
    markdown = Redcarpet::Markdown.new(html_render, REDCARPET_OPTIONS)
    markdown.render(md_code)
  end

  class HTMLwithCoderay < Redcarpet::Render::HTML
    def block_code(code, language)
      CodeRay.scan(code, language || 'md').div
    end
  end
end

View からの呼び出し

上記の markdown_to_html メソッドの引数に Markdown の文字列を渡す。

markdown_to_html(@blog.body)

解説

markdown_to_html

このメソッドで、渡した Markdown 文字列をシンタックスハイライト用のマークアップを行った HTML に変換する。 Redcarpet::MarkdownHTMLwithCoderay のインスタンスとオプションを渡してインスタンス化し、 render メソッドに Markdown 文字列を渡すだけだ。

def markdown_to_html(md_code)
  html_render = HTMLwithCoderay.new(filter_html: true, hard_wrap: true)
  markdown = Redcarpet::Markdown.new(html_render, REDCARPET_OPTIONS)
  markdown.render(md_code)
end

Redcarpet のオプション

Redcarpet はオプションを取る。上記のコード例では下記のように freeze した Hash で定義している。

REDCARPET_OPTIONS = {
  autolink: true,
  space_after_headers: true,
  no_intra_emphasis: true,
  fenced_code_blocks: true,
  tables: true,
  hard_wrap: true,
  xhtml: true,
  lax_html_blocks: true,
  strikethrough: true
}.freeze

Redcarpet::Markdown.new(html_render, REDCARPET_OPTIONS)

オプションの種類は以下の通り。適当な和訳なので間違っているかも。

キー 機能
autolink <> で囲まれていない場合でもリンクを解析
space_after_headers ヘッダーの先頭のハッシュとハッシュ名の間にスペースを要求
no_intra_emphasis "foo_bar_baz" のような文字列は em 要素を生成しない
fenced_code_blocks コードブロック(PHP-Markdownスタイル)を解析する
tables テーブル、PHP-Markdown のスタイルを解析する
hard_wrap パラグラフ内に改行があれば br 要素を出力する
xhtml xhtml のタグを出力する(Render::XHTMLでは常に有効)
lax_html_blocks HTMLブロックの上下に改行を必要としないようにする
strikethrough 取り消し線(~)を解析する
filter_html ユーザーが入力した HTML を出力しないようにする
no_images img 要素を出力しない
no_links a 要素を出力しない
no_styles style 要素を出力しない
safe_links_only 安全と思われるプロトコルのリンクだけを出力

言語の指定方法をカスタマイズ

HTMLwithCoderay#block_code でコードの言語の指定方法をカスタマイズすることができる。例えば、下記のように rbruby と判定させたり、 ymlyaml と判定させたり、指定がなければ Markdown と判定させたりすることができる。

def block_code(code, language)
  case language.to_s
  when 'rb'
    lang = 'ruby'
  when 'yml'
    lang = 'yaml'
  when ''
    lang = 'md'
  else
    lang = language
  end

  CodeRay.scan(code, lang).div
end

つまり、

```ruby

と書かなくても

```rb

と書けば良い、といったカスタマイズができる。

特にそのような指定が不要であっても、 CodeRay.scanlanguagenil を渡す(言語を指定しない)とエラーになるので、上記のサンプルコードでは、下記のように languagenil の場合は Markdown とみなすような対策をしている。

class HTMLwithCoderay < Redcarpet::Render::HTML
  def block_code(code, language)
    CodeRay.scan(code, language || 'md').div
  end
end

あとはお好みでスタイルシートを書く。これだけで、Qiita のような感覚で自作のブログを作ることができる。

Back