JekyllはLiquidというテンプレートエンジンを採用しています.Liquidは表現力をあえて制限することで危険なコードをテンプレートに埋め込めないようになっています.そのため,テンプレートで何かおもしろいことをしようとすると,新しいプラグインを書かなければならないことがよくあります.ところが,Jekyllのプラグインの書き方に関するドキュメントはそれほど多くないため,ここに書き方をメモしておきます.
Case Study 1: Echo
最初に引数をそのまま表示するタグを作ってみます.タグは Liquid::Tag
を継承して作ります.
# coding: utf-8
module Jekyll
class EchoTag < Liquid::Tag
def initialize(tag_name, markup, tokens)
super
@arg = markup
end
def render(context)
@arg
end
end
end
Liquid::Template.register_tag("echo", Jekyll::EchoTag)
これを _plugins/echotag.rb
に配置して,ページの中で {% echo hello %}
と書くと hello
が出力されます.
EchoTag#initialize
の第二引数にタグの引数が渡されます.それを EchoTag#render
メソッドで返すようにすれば echo タグを実装できます.
Case Study 2: RSS Feed
ここでは,RSSを使って外部のサイトから更新情報を取ってきてページに埋め込むタグを例として解説していきます.
先に使用例を示します. {% feed URL %}
と {% endfeed %}
で囲った間では entries
という配列を参照でき,その中にエントリのURLとタイトルが納められています.
<section>
<h1>CoffeeScript の話題</h1>
{% feed http://qiita.com/tags/CoffeeScript/feed.atom %}
<ul>
{% for entry in entries %}
<li>
<a href="{{entry.url | xml_escape}}">
{{entry.title | escape}}
</a>
</li>
{% endfor %}
</ul>
{% endfeed %}
</section>
このように閉じタグがあるようなタグは Liquid::Tag
ではなく Liquid::Block
を継承します.プラグインのコードは以下のようになります.
# coding: utf-8
require "open-uri"
require "feed-normalizer"
module Jekyll
class FeedTag < Liquid::Block
def initialize(tag_name, markup, tokens)
super
@url = markup.strip
end
def render(context)
entries = fetch_feed(@url)
context.stack do
context["entries"] = entries
return super
end
end
def fetch_feed(url)
open(url) do |f|
feed = FeedNormalizer::FeedNormalizer.parse(f)
return feed.entries.map {|entry| {
"url" => entry.url,
"title" => entry.title,
}
}
end
end
end
end
Liquid::Template.register_tag("feed", Jekyll::FeedTag)
initialize
メソッドの第二引数がタグの引数であることはecho
の場合と同じです.
fetch_feed
メソッドでは,url
を開いてRSSフィードをパースしてハッシュの配列にしています.Liquidはto_liquid
メソッドを持っているオブジェクトしかテンプレートに渡せません.ArrayやHashはLiquidがto_liquid
メソッドを定義してくれるため,これらに変換しておけば,テンプレートの中でその変数を参照することができます.
render
メソッドでは,fetch_feed
を呼び出したあと,2つのことを行っています.
まず,テンプレートの中で entries
を参照するために,context
に entries
を登録しています.ただし,何も考えずに登録すると,{% endfeed %}
の後でも entries
を参照できてしまうため,context["entries"] = entries
を context.stack do
~ end
で囲う必要があります.
次に,super
を呼び出して,親クラスにタグで囲われた部分をレンダリングをさせます.(super
はオーバーライドしているメソッドを呼び出す構文です.詳しくはリファレンスを参照.)
補足
site変数を取得する方法
site = context.registers[:site]
baseurlを取得する方法
site.config["baseurl"]
site
は上述の方法で取得する.