Jekyllのいいところは、Front-matter(テンプレートの先頭から---で区切られたヘッダ部)にListとHashが使えるので、かなりプログラマ好みの書き方ができるところです。
テンプレートエンジンのLiquidの記述方法と合わせて、試行錯誤した結果をメモしておきます。
※ Front-matterはYAMLフォーマットです。YAMLについてはプログラマーのためのYAML入門(初級編)が詳しいです。
JekyllはVersion1.3.0を前提としています。
##Front-matterにListとHash構造を定義する
まず、Front-matter及びbody部をこのように定義してみます。
---
title: template1
layout: default
hash1:
name: Hash構造
value: 値が入る
list2:
- 一番目
- 二番目
---
{{ page.hash1.name }}<br />
{{ page.hash1.value }}<br />
<br />
{% for data in page.list2 }}
{{ data }}<br />
{% endfor %}
結果はこうなります。
Hash構造
値が入る一番目
二番目
ポイントは、記述した定義は全て"page"というhashに格納されるということです。
記述の注意として、hash1,list2の次の行はインデント(スペース1つ以上)されていないと、ListやHashと見なされません。
ここまでは基本ですが、ListのHashとかHashのListになると表現力が増します。
---
title: template2
layout: default
hash3:
name:
- 三番目
- 四番目
value: 値が入る
list4:
- list: [11,12, 13 ,"文字も入るよ"]
- list: [22, 23 ,24,スペース も入るよ]
---
{{ page.hash3.name }}<br />
{{ page.hash3.value }}<br />
<br />
{% for data in page.list4 }}
{% for item in data.list }}
{{ data }}<br />
{% endfor %}
{% endfor %}
結果はこうなります。
三番目四番目
値が入る11
12
13
文字も入るよ
21
22
23
スペース も入るよ
Listの記述方法として、インデントして頭に"-"をつける方法と、
hash3.nameはList構造ですが、List全体を指定するとList内を連結して文字列として扱います。
記述の注意として、hash3の後のリストはインデントが一つ前の行より手前の位置にあるとエラーになります。
文字列は""で括ったり括らなかったりでしたが、文字列の並びによってはうまく処理されないことがあります。その場合、カンマの前後にスペースを空けるとうまくいくようです。
##include先にListとHashを渡す
Jekyllのincludeタグは、include先にパラメータを渡すことができます。include先に渡したタグは、全て"include"という名前のhashに格納されます。
---
title: template3
layout: default
hash1:
name: Hash構造
value: 値が入る
list2:
- 一番目
- 二番目
---
{% include include1.liquid param1=page.hash1 param2=page.list2 %}
{{ include.param1.name }}<br />
{{ include.param1.value }}<br />
<br />
{% for data in include.param2 %}
{{ data }}<br />
{% endfor %}
結果はtemplate1.htmlと同じになります。
##include先に動的に作ったListを渡す
include先にFront-matterで作成したListやHashを渡せることはわかりました。ただ、残念な事に動的にテンプレート内でListを作って渡すということができません。
---
title: template4
layout: default
hash1:
name: Hash構造
value: 値が入る
hash2:
name: Hash構造2
value: 値が入る2
---
{% include include2.liquid param=[page.hash1, page.hash2] %}
{% for data in include.param %}
{{ data.name }}<br />
{{ data.value }}<br />
{% endfor %}
これはjekyllのレンダリング処理でエラーとなります。
jekyllにデフォルトで組み込まれている、include.rbは自前でincludeタグのパラメータを分解しているのですが、Listを受け入れることを想定していないのでした。
あちこち探したところ、splitを使ってListを作るという提案がありました。
---
title: template5
layout: default
hash1:
name: Hash構造
value: 値が入る
hash2:
name: Hash構造2
value: 値が入る2
---
{% assign list = "page.hash1|page.hash2" | split: "|" %}
{% include include2.liquid param=list %}
結果は、何も表示されません。
Listに入っているのは文字列"page.hash1"と"page.hash2"でした。文字列をデータ構造に置き換えるには、splitだけでは無理なようです。
##include先に動的に作ったListを渡す(その2)
splitでListにするところまではよかったのですが、もう一工夫必要なようです。
List内の文字列をキーとして、Jekyll内部からデータを拾ってくればできそうですので、プラグインを作りました。
module Jekyll
module ContextArray
def contextarray(input)
if !input.kind_of?(Array)
src = input
input = [src]
end
data = []
input.each {|a|
data.push(@context[a])
}
data
end
end
end
Liquid::Template.register_filter(Jekyll::ContextArray)
このフィルターをこのように使います。
---
title: template6
layout: default
hash1:
name: Hash構造
value: 値が入る
hash2:
name: Hash構造2
value: 値が入る2
---
{% assign list = "page.hash1|page.hash2" | split: "|" | contextarray %}
{% include include2.liquid param=list %}
ようやく結果が出力されました。
Hash構造
値が入る
Hash構造2
値が入る2
##include先に動的に作ったListを渡す(その3)
その2までで、目的は果たした気もするのですが、なんとなく直感的ではありません。気持ちよくコーディングをするためには不便を減らしていくのがプログラマの矜持ではないでしょうか。
ということで、なんとなく難しそうな本丸、include.rbを攻略することにしました。
※ Jekyllのバージョンによってinclude.rbの内容が異なりました。ここでは1.3.0版を修正しています。(1.1.2版と1.3.0版で確認)
実際に試しただけなので正確な挙動は確かめて頂きたいところですが、gems上のプラグインは、_pluginsに置いてあるプラグインで上書きされるようです。ここでは、_pluginsディレクトリに、修正したinclude.rbを配置します。
全部書くと長くなるので修正点だけ抜粋しています。
VALID_SYNTAX = /([\w-]+)\s*=\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+)|\[([^\[\]]+)\])/
def parse_params(context)
params = {}
markup = @params
while match = VALID_SYNTAX.match(markup) do
markup = markup[match.end(0)..-1]
value = if match[2]
match[2].gsub(/\\"/, '"')
elsif match[3]
match[3].gsub(/\\'/, "'")
elsif match[4]
context[match[4]]
elsif match[5]
array = []
match[5].split(/\s*,\s*/).each {|i|
avalue = if md = i.match(/^(?:"([^"\\]*(?:\\.[^"\\]*)*)")$/)
md[1].gsub(/\\"/, '"')
elsif md = i.match(/^(?:'([^'\\]*(?:\\.[^'\\]*)*)')$/)
md[1].gsub(/\\'/, "'")
elsif context.has_key?(i)
context[i]
else
i
end
array.push(avalue)
}
array
end
params[match[1]] = value
end
params
end
修正されたinclude.rbを使うと、template4.htmlがきちんと動くようになります。
##include先に動的に作ったListを渡す(おまけ)
構造をそのままinclude先に渡せるようになったので、こんなことができます。
---
title: template7
layout: default
item1:
name: 日付
value: [11/11, 11/11, 11/13, 11/14]
item2:
name: 用途
value: [弁当, ガンプラ, 映画, 漫画]
item3:
name: 金額
value: [300, 4300, 1800, 680]
numfilter: 1
---
{% include table.liquid item=[page.item1, page.item2, page.item3] %}
{% capture counti %}{{ include.item[0].value | size | minus:'1' }}{% endcapture %}
{% capture countj %}{{ include.item | size | minus:'1' }}{% endcapture %}
<table>
<tr>
{% for itemdata in include.item %}
<th>{{ itemdata.name }}</th>
{% endfor %}
</tr>
{% for i in (0..counti) %}
<tr>
{% for j in (0..countj) %}
{% if include.item[j].numfilter %}
<td>{{ include.item[j].value[i] | numberformat }}</td>
{% else %}
<td>{{ include.item[j].value[i] }}</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</table>
module Jekyll
module NumberFormat
def numberformat(number)
number.to_s.reverse.gsub( /(\d{3})(?=\d)/, '\1,' ).reverse
end
end
end
Liquid::Template.register_filter(Jekyll::NumberFormat)
結果はこうなります。テンプレートエンジンだけで、シンプルなデータ構造から複雑な表現をできることがわかります。
日付 | 用途 | 金額 |
---|---|---|
11/11 | 弁当 | 300 |
11/11 | ガンプラ | 4,300 |
11/13 | 映画 | 1,800 |
11/14 | 漫画 | 680 |
##最後に
Jekyllでちょっと凝った事をしようとしてはまった人の参考になれば幸いです。