Jekyllで自作のタグを作る

突然だが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.”

https://shields.io/

Shields.io: Quality metadata badges for open source projects

Pixel-perfect   Retina-ready   Fast   Consistent   Hackable   No tracking...

このサービスは、ビルドステータスのバッジと同じような画像をURLだけで引っ張ってこれるサービスである。 GETパラメータを指定するとこんな感じで生成できる:

https://img.shields.io/static/v1?label=Find%20me%20on&message=GitHub&color=181717&style=flat&logo=github

…と書くと:

My 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パラメータに変換する。ついでに、パラメータ hrefalt を引数に渡せるキーに追加して、リンク化・代替テキストの指定も可能にした。

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つのファイルをプロジェクトに追加してみよう: