突然だがShields.ioについてご存じだろうか。
a “service for concise, consistent, and legible badges in SVG and raster format, which can easily be included in GitHub readmes or any other web page.”
Shields.io: Quality metadata badges for open source projects
このサービスは、ビルドステータスのバッジと同じような画像をURLだけで引っ張ってこれるサービスである。 GETパラメータを指定するとこんな感じで生成できる:
https://img.shields.io/static/v1?label=Find%20me%20on&message=GitHub&color=181717&style=flat&logo=github
…と書くと:
となる。
既にいくつかこのような画像がこのブログで登場しているのにはお気づきかもしれないが、 たくさん設置しようとしたとたんに問題に気づいた - 画像用のURLが長すぎて、どんな設定をしているのか一目で確認できなくなってきたのだ。 もちろんこのバッジの中身を変えることはさほどないにしても、もう少しメンテしやすい方法で書ければよさそうだ。
このブログはJekyllで書いているので、自作の”Liquid Tag”を作ることができる。例えば私が英語版の記事を同時公開しているDEV.toでは、
{% github repo %}
とか {% embed website %}
というタグが実装されているが、同じように {% shields_io payload %}
,
と書くことでこれらの画像を出すようなタグも作れる。
JekyllのLiquid Tagの仕組み
Jekyllに限らず、Liquid Tagはこのようなフォーマットである。
{% tag_name [tag_argument] %}
「タグの名前」と「引数」で一セットだ。
ここで問題になるのだが、この引数、1個しか渡せないのである。Shields.ioで画像を生成するには引数1個では収まらない。 また、ここにGETパラメータを書いたのでは本末転倒だ。
だからと言ってこの企画が終了したわけではない。どんなコードを書くのか見てみよう:
module Jekyll
class CustomTag < Liquid::Tag
def initialize(tag_name, arg, parse_context)
super
# @type [String]
@arg = arg
end
def render(_context)
"The argument is #{@arg}" # return the render result here
end
end
end
arg
がタグから飛んでくる引数である。これは文字列になっている。
しかも、今回Shields.ioに渡そうとしているのはキーと値のペアだ。
つまり引数にJSONを渡せば解決する。Rubyは最初からJSONの解釈ができるようになっているので、そんなに負担もない。
ペイロードをURLにする
RubyはJSONを解釈するとHashなるものに変換する。これに対して .map
を実行してやれば、GETパラメータを生成できる。
引数をJSONとして解釈して:
def initialize(tag_name, input, parse_context)
super
# @type [Hash]
@config = JSON.load(input.strip)
end
GETパラメータに変換する。ついでに、パラメータ href
と alt
を引数に渡せるキーに追加して、リンク化・代替テキストの指定も可能にした。
def render(_context)
href = @config[:href]
alt = @config[:alt]
@config.delete(:href)
@config.delete(:alt)
shield_tag = <<HTML
<img src="https://img.shields.io/static/v1?#{hash_to_query}"
HTML
if alt != nil
shield_tag += " alt=\"#{alt}\" />"
else
shield_tag += " />"
end
if href != nil
<<HTML
<a href="#{href}">
#{shield_tag}
</a>
HTML
else
shield_tag
end
end
private
def hash_to_query
@config.to_a.map { |k, v|
"#{k}=#{v}"
}.join '&'
end
できた
こんな感じで作ることができる。今回ここで作ったコードはGistとして公開しているので、 使いたい人は最後の2つのファイルをプロジェクトに追加してみよう: