JekyllにKrokiを組み込んでみた

こういうことです。

テキストを書くだけで図を見せることができると嬉しい

最近は

最近のライティング環境2023夏(1) (2023-07-09) | あーありがち

で書いたように text diagramming を多用している。1

GitHub や esa.io を始め、オンラインで Markdown を書ける環境の場合、だいたい公式が text diagramming をサポートしてきており、「思案中」のようなもの、複雑すぎるもの、抽象度が高すぎるもの以外は割とテキストでそれっぽい図を描いておくというアプローチでなんとかなるようになってきている。

しかし Google Documents の場合はそのような支援がなく、ラスタ画像ファイルを要求するので、どうにか手間を減らす工夫をしましたよ、というのが上の「最近のライティング環境」の話。まぁ、Google Documents でもブラウザ拡張でテキストの図を起こすのは可能は可能なのだが、すべての関係者に拡張を強要するのは難しいので、どうにか手間を減らしてラスタ画像を作ろうとしている。

で、もう一つ環境があるのを思い出した。この自分のサイトである。ここは Google Documents のように text-to-diagram のレンダリングを諦める必要はない。なんかいい具合のものを作れば楽に書きつつ、見る人は誰でも問題なく図を閲覧できる。

そこで思い出したのが上の試行錯誤をくり返していた時に調べた

Kroki!

である。これをうまいこと組み込めれば自分はテキストを書くだけで読み手の環境でいい具合に画像が表示されるはずである。

jekyll-kroki-tagを作った

最初は ``` で囲む方式をぼんやり考えていたんだけど、そもそもこの機能はコードをそのまま表示しつつシンタックスハイライトする機能なので、なんだか遠回りだなと思っていた。コードとして表示したのちに JavaScript でその内容を読み取って kroki.io に送るのか? Jekyll の環境で JS を開発するのはちょっとイヤだなぁ、みたいな。

ちょっと寝かして kroki.io のドキュメントを読んでいたら img の src を kiroki.io の URL にするだけでよくない? と気づいたので、あとは Liquid のタグに仕立てていくだけとなった。

Liquid タグを作るのは何回かやっていたんだけど、毎回面倒だなと思っていたのでちょっと億劫になっていた。

何が面倒って

  1. 結局 Jekyll や地のドキュメント(タグの周辺の context)がないとテストが難しい
  2. tag にはただのテキスト情報しか渡ってこないので複雑な構造を持つデータをどうやって渡すか

この辺り。

従来は 1 をモソモソやってて、今回は Liquid タグとしての動作に依存しない module を作ってテストコードからはその module そのものをテストする方法にした。これは以前 Liquid::Template や Liquid::Context を頑張って組み立ててテストを書いた際にだいたいの構造が把握できていたので、不要な部分を端折る工夫。

今回もう一つぶつかった問題は 2 で、結論から言うと tag に渡ってきた文字列を Ruby の文法として parser gem で parse してゴニョゴニョすることにした。まず Ruby の文法として parse できたら合格、そのうえで AST の所定の値だけを抜き取れればやりたいことを始められる。いったん tag として動くようにしたうえでパラメータの解釈だけ後で追加、手直しをしていった。

今回初めて受け取った文字列を Ruby として解釈して AST から必要な値を抜き出すのをやってみたが、parser gem の sexp は割と扱いやすいなと思った。世はすでに YARP ( Yet Another Ruby Parser ) の時代ではあるけれど、いったん現時点ではドメジャーな parser gem で処理することにした。Jekyll の文脈だと Ruby の文法としての解釈は割と自然なコードとして目に馴染む。下手に JSON みたいな、機械は解釈しやすいけど人間の書きにくい文法にしなくてよかったと思う。

ソース

{% kroki type: 'plantuml' %}
skin rose
skinparam shadowing false
title Hello, PlantUML from Jekyll !

actor User
usecase "Write great code" as case
User -> case
{% endkroki %}

生成された画像

  1. (2) がどういうネタだったのかもう忘れてしまった… 

More