Rails / Redcarpet / Rouge / マークダウンで記述するコードブロックの表示をいい感じにする
このブログ、マークダウンで書いているのですが、コードの記述が多いので、そこもオシャレにしたいところ。
マークダウンには Redcarpet 、シンタックスハイライトには Rouge を使って独自にカスタマイズしました。
Redcarpet
https://github.com/vmg/redcarpet
Rouge
https://github.com/jneen/rouge
Gemfile
1
2
gem 'rouge'
gem 'redcarpet'
MarkdownをHTMLで出力する
基本的な使い方はこう。
render_options
と extensions
は ドキュメント を参照してください。
application_helper.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def markdown(text)
render_options = {
filter_html: false,
hard_wrap: true
}
renderer = Redcarpet::Render::HTML.new(render_options)
extensions = {
autolink: true,
fenced_code_blocks: true,
lax_spacing: true,
no_intra_emphasis: true,
strikethrough: true,
superscript: true,
tables: true,
}
Redcarpet::Markdown.new(renderer, extensions).render(text).html_safe
end
これをビューで使用する。
@post という記事のオブジェクトがあるとします。
views/posts/show.html.erb
1
<%= markdown(@post.body) %>
Rouge
このままだとコードブロックの見た目がしょぼいので、言語によっていい感じにしたい。
そのために Recarpet の renderer
を変更します。
Redcarpet::Render::HTML
を継承してカスタムなレンダラークラスを定義する。
その際に Rouge の プラグインをインクルードすれば基本的にはOKです。
custom_markdown_renderer.rb
1
2
3
class CustomMarkdownRenderer < Redcarpet::Render::HTML
include Rouge::Plugins::Redcarpet
end
application_helper.rb
1
2
3
# renderer を変更します
# renderer = Redcarpet::Render::HTML.new(render_options)
renderer = ::CustomMarkdownRenderer.new(render_options)
さらに Rouge が用意してくれている CSS を読み込みましょう。
Themes の一覧は ココ にあるので、好きなものを選択してください。
_rouge.scss.erb
1
<%= Rouge::Themes::Github.render(:scope => '.highlight') %>
メインのCSSで読み込んで Sprockets にのせる。
application.scss
1
@import 'rouge';
さらに自分好みにカスタマイズ
これでもなかなかいい感じに出力してくれるのですが、細かいところはやっぱり自分で何とかしないといけない。欲しかったのは、
・行番号の表示
・行番号を span で囲む
・言語名もしくはファイル名を表示
という機能。
ファイル名 assets/javascripts/application.js
とかが指定されていれば、ファイル名だけ表示、言語名だけが指定されていれば js
等と表示したい。どちらも指定されていなければ何も表示しない、としたい。
カスタムレンダラーの作成方法は Redcarpet の Github に説明が ありますが Rouge との兼ね合いをどうすれば良いのか分からず、しばらく悩みました。
block_code
だけでなく、 Rouge::Plugins::Redcarpet
によって、追加されている rouge_formatter
も上書きします。
もともとの実装は こちら 。
custom_markdown_renderer.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class MarkdownCustomRenderer < Redcarpet::Render::HTML
include Rouge::Plugins::Redcarpet
# Override a method from Rouge::Plugins::Redcarpet
# Add language section above code block.
def block_code(code, language)
# --- Extract file name ---------------------- #
filename = ''
regx = Regexp.new(/(<!--\s?filename:(\s?.{1,}\s?)-->\n?)/)
if !(code =~ regx).nil?
code.match(regx)
filename = $2.try(:strip) || ''
code.gsub!(regx, '')
end
# -------------------------------------------- #
lexer = Rouge::Lexer.find_fancy(language, code) || Rouge::Lexers::PlainText
if lexer.tag == 'make'
code.gsub! /^ /, "\t"
end
formatter = rouge_formatter(lexer)
result = formatter.format(lexer.lex(code))
return result if language.blank? && filename.blank?
# --- Compose language and filename section --- #
info_inner_html = [filename, language].select(&:present?).map.with_index { |text, i|
i == 0 ? "<span class='highlight-info__inner'>#{text}</span>" : nil
}.compact.join('')
%(<div class='highlight-info'>
#{info_inner_html}
</div>
#{result}
)
# -------------------------------------------- #
end
def rouge_formatter(options = {})
options = {
line_numbers: true,
line_format: '<span>%i</span>'
}
Rouge::Formatters::HTMLLegacy.new(options)
end
end
デザイン
最後にスタイルをあてれば完成です。
なんだかんだで結構見た目の調整はしちゃいました。
先程の SCSS ファイルにスタイルを追加します。
_rouge.scss.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
<%= Rouge::Themes::Github.render(:scope => '.highlight') %>
// Custome style for rouge formatter
// formatter is defined in application_helper.rb
.highlight {
margin-top: 10px;
margin-bottom: 40px;
pre {
overflow: auto;
word-wrap: normal;
white-space: pre;
font-size: 0.9em;
line-height: 1.5;
border-radius: 2px;
padding: 0;
margin: 0;
code {
font-size: 1em;
color: #466568;
table.rouge-table {
margin: 0;
// code
td.rouge-code {
padding: 0;
vertical-align: top;
pre {
padding: 5px 10px;
line-height: 1.6em;
}
}
// line number
td.rouge-gutter {
text-align: center;
background-color: #ddd;
padding: 5px;
width: 20px;
vertical-align: top;
pre {
line-height: 1.6em;
}
}
}
}
}
}
.highlight-info {
margin-top: 10px;
margin-bottom: 2px;
.highlight-info__inner {
display: inline-block;
border-left: 1px solid #ddd;
padding: 1px 7px 2px;
font-size: 0.8em;
letter-spacing: 0.5px;
}
}
.highlight-info + .highlight {
margin-top: 0;
}
// Override rouge theme
.highlight {
.c1 {
color: #939393;
}
.s1 {
color: #bf4c00;
}
}
使い方
言語名を表示 + シンタックスハイライト
ファイル名を表示 + シンタックスハイライト
せっかくマークダウンなのに HTML のコメント方式で書いてるのはイケてない感。
が、力尽きたのでひとまずこのままで。
以上です!
ブログ書くのにもテンションが必要なので、
見た目がキレイって大事だなと思った次第です。