CI回してMarkdownにバッジを置く、ただそれだけのこと(後編)

2021/12/24に公開約7,200字

この記事は Qiita Advent Calendar 2021 の D言語カレンダー 25日目 の記事です。

はじめに

本日は最終日ということで、ちょっと真面目に思うところを色々書き残していきます。
主に、昨日の記事に続きということで、Markdownのコードブロック実行ツールについて、詳細や今後の展開などです。

というわけで、前編を呼んでいただくと何を言っているのか少し伝わりやすくなると思います。ご検討をば。

https://zenn.dev/lempiji/articles/qiita-adc-d-20211224

ツール開発の動機と発展性

今回は主にMarkdownのコードブロックを実行するために作った md というツールについて書いていきます。

リポジトリはこちらです。

https://github.com/lempiji/md

どういった動機で作ったのか、今後の展望としてどんなことを考えているか、を最初に少し整理します。

ざっくりキーワードとしては以下の3つです。

  • ドキュメントテスト
  • 文芸的プログラミング
  • 静的サイトジェネレーター

ドキュメントテスト

ドキュメントテストとは、日本では「ドキュメント検証」と呼ばれる方が多いですが、いわゆるビジネス的な「文書」を対象とする検査の枠組みを指す言葉です。これは文書の「見た目」「一貫性」「正確性」「完全性」といった要素を体系的に確認する手法です。

一方で、私は日頃趣味の開発で思いついたアイデアを試すため、色々な実証ライブラリを作っています。その中の取り組みとして、READMEにサンプルコードを多く載せることで「ライブラリの雰囲気や目指すところを掴みやすくしよう」と試みたことがありました。実際のREADMEがこちらで、そこには14個のサンプルを記載したコードブロックがあり、それぞれ伝えたい色々をコードに込めて書き残してあります。

ここで当たり前ですが、読者として考えればこれらのコードが間違っているというのは考えたくありません。また、何かの拍子で壊れてしまうとそもそも何のサンプルコードで実証なのか、と言われてしまいそうです。READMEのサンプルはとっつきやすいので作者としては書きたいところですが、あってるか分からないコードをベタ書きするというのはやりたくありません。色々な葛藤がありました。

そのような状況で、検証コストを抑えるためにツールを作った、というのがそもそもの開発動機になります。言語的にも相性がよく、作れそうじゃん?とすぐ思えたのは大変大きかったのですが。

いざツールを作ってみると、これはドキュメントテストにおける「正確性」「完全性」といった要素を確認するツールなのだと再認識しています。OSSなドキュメント検証ツール、他にもあるのかもしれませんが、Markdownをターゲットとするのは割と面白い気がしています。

Markdownが容易に様々な方法で検証できる未来、ぜひ来てほしいですね。

文芸的プログラミング

ドキュメントテストと比較して、こちらは実際コーディングする側で見たときの展望です。

文芸的プログラミングというのは、英語で Literate Programming と言います。論文などで使われる組版処理システム「TeX」の開発者としても有名なドナルド・クヌース氏によって提唱された一種のプログラミングスタイルで、あたかも動作を説明しているかのような方法でコードをドキュメントに埋め込むのが特徴です。

で、まさにそれって現代のMarkdownなんですよね。

ドキュメントとソースコードの融合というのは実際いくつか例があり、最近ならJupyter notebook、少し前にはXamarin Workbooksというのがあります(世に出た順序は逆ですが)。特にXamarin Workbooksの見た目はまさにMarkdownと大差ありません。これ、Markdownで良いのでは?と今なら思えます。

詰まるところ、Markdownなら言語を問わずコードブロックが埋め込めるため、未だ実現していない統一的なソースコードのアーカイブ形式になり得るのでは?そこで真に文芸的プログラミングが可能になるのでは?という期待を持っています。

仮に .md のまま扱うのが難しくとも、コードブロックから使えるソースコードを出力してツールに流す、という流れが確立できれば十分に実用足り得るでしょう。コードブロック中でも動作する補完機能や定義ジャンプは必要そうで、もちろん課題は山積みですが、これはこれで面白い未来ではないかなと思います。

ソースコードの拡張子は .md しかないかもしれない未来、良いのか悪いのかわかりませんけど面白そうだと思います。

静的サイトジェネレーターと組み合わせたドキュメントサイト

各種ツールの公式サイトなど、様々な場面でいわゆるチュートリアルや Getting Started といったコードを含むサンプルが出てきます。また、世には静的サイトジェネレーターというものが出てきており、多くの公式サイトは何らかのジェネレーターを用いて書かれています。見た目も綺麗でパフォーマンスも良く、開発効率的にも非常に優秀なので当然の流れでしょう。

さて、静的サイトジェネレーターにはいくつか種類があり、主にブログなどを目的としたものは Markdown がソースとして用いられます。

そうです。Markdownが入力なのであれば、公式サイトなどのチュートリアルが正しく動くことを常に検証する、というのがこのコードブロック実行ツールによって可能になるかもしれません。公式サイトやチュートリアルといった「動かないと意味がないコード」だからこそ、多少コストをかけても確実に検証しておくことに価値があるでしょう。そして幅広く使われているMarkdownだからこそ、こういったツールなら汎用性もあり、フィットすると強いのではないかと感じます。

そんなことを考えながら、D言語で実際構成組んでみました。小綺麗なコード付きサイトを書いてみたい、検証されていたら嬉しい、と思った方がいれば、以下のリポジトリと結果サイトから見てみてください。使っているのは Vue Press という静的サイトジェネレーターです。簡単さで選びましたが、今思えばNext.js/Nuxt.js版もあると良かったかな?と思います。ひとまずソースから生成されるAPIリファレンスと比べれば、そのモダンさや小綺麗さという点でとても満足できる品質だと思います。

ドキュメントサイトの構成例

https://lempiji.github.io/sandbox-vuepress/

リポジトリ

https://github.com/lempiji/sandbox-vuepress

展望のまとめ

ドキュメントテスト、文芸的プログラミング、静的サイトジェネレーターとの組み合わせ、というキーワードで展望を挙げてみました。

プログラムを書く人がMarkdownでソースコードを書き、それをそのままドキュメントテストとしても流し、何なら静的サイトジェネレーターでWebサイトにしてしまう。そういった流れが上手くハマると開発プロセスとして非常に満足度の高いものになりそうな期待があります。

開発者にとってより素早く気持ちよく製品が作れるようになるために、今後さらにMarkdownや周辺ツールが発展していく未来を願うばかりです。

ツールの実装

さて、ここではツールの実装に際して細かい機能の紹介や使ったライブラリに少し触れていきます。

DUB連携

先の動機説明の段階で、「作ったライブラリのREADMEにあるサンプルコードを動かすため」と書きました。つまり、依存関係どうするか、という課題がここで1つ出てくるわけです。

今回作った md というツールは、実行するカレントディレクトリが DUB のパッケージであれば(dub.sdlやdub.jsonがあれば)、自動的に依存関係を追加して import できるようにする機能を持っています。

やっていることは特段難しくなく、これは内部的にソースコードを DUB の「シングルファイル形式」で出力することで簡単に実現できています。コードを書いてしまうと、 main の前に以下の要領でコメントを生成してくっつければ依存関係が表せるということですね。パス指定がポイントです。

/+dub.sdl:
    dependencies "golem" version="*" path="C:\work\..."
+/
void main() {
    // 動かしたいコード
}

ツールの作成は3日もかかりませんでしたが、本当にすべての仕様が都合良くできているなと感じます。こういった面でも一切思考を妨げることなく、こうやればできるよ、というのが向こうから歩いてやってくる感じですね。素晴らしい限りです。

commonmark-d

commonmark-d は今回使ったMarkdownのパーサーです。CommonMarkという仕様に従っており、非常に軽くて高速です。

1つ難点を挙げるとすると、C言語的な void* とコールバックを多用するスタイルで、コールバックが @nogc というのがきつかったです。

コードブロックをかき集めて配列に追加しようと思っても、追加の arr ~= block; といったコードがもうGC対象なんですよね。

そこで活躍したのが std.containerArray でした。

値型の配列でちょっと注意が必要ですが、既定の動作としてGCではなく malloc/free を使うので @nogc でも使うことができます。変にライブラリ参照とか追加しなくてもなんとかなりました。やはり標準ライブラリで大体解決できるのはとても良いですね。

jcli

jcli は今回使ったコマンドラインツールを作るフレームワークです。記述が UDA (User Defined Attribute、ユーザー定義属性)を使い、引数の定義などが非常に簡単なので採用しました。

今回のツールは dub run md -- README.md と実行しますが、dub run md -- --file README.md -v といった感じでいくつかオプションが指定できます。

イメージとしては以下の感じです。
未指定にできるなら Nullable なフィールドを宣言、通常は名前付き引数なので @ArgNamed("名前", "説明") をつけるだけで勝手にバインドされます。楽すぎ。

@CommandDefault("Execute code block in markdown.")
struct DefaultCommand
{
    @ArgPositional("file", "Markdown file (.md)")
    Nullable!string file;

    @ArgGroup("Options")
    {
        @ArgNamed("quiet|q", "Only print warnings and errors")
        Nullable!bool quiet;

        @ArgNamed("verbose|v", "Print diagnostic output")
        Nullable!bool verbose;
    }

    int onExecute()
    {
      return 0;
    }
}

途中で結構作り変えがあったりして使う側としては厳しい時期もあったのですが、今はモジュール構成もスッキリして落ち着いたっぽいです。

コンパイル時に引数の情報を解析しておけるなどD言語の魅力が詰まっており、CLIツール作る上ではとても便利なライブラリだと思うのでぜひ使ってみてください。

今後の強化

ツールにあったらいいな~と思っている機能を書き残すコーナーです。こういうのは本来GitHubのプロジェクトでやるんでしょうね…

コードブロックの使いまわし

これが文芸的プログラミングには必須の機能でした。あとは順序入れ替えとかも例としてあるのですが、それはやりすぎかなぁという気がしてます。

ただやり方どうするかというのが課題で、どうやって指定すれば人間の思考通りなのか、記法がまったく定まっていません。refer=<name> とか、prev=<name> とかあるにはあるのですが、どれも何か違うなぁという感じ。

でも次に1つ機能強化するならこれかなと思っていおり、アイデアある方はぜひともコメントやTwitterでご意見いただければと思います。あ、フォローもお待ちしています!

https://twitter.com/lempiji

名前指定による部分実行

名前付きコードブロックというものが書けることがわかり、あとは名前でフィルタリングして実行できるようになると、これこそ文芸的プログラミングの極地では?と勝手に思っていたりします。

様々なコードをブロック単位で共有してソースをインライン化しつつ、うまく処理を切り替える。正規表現でフィルタするだけで良さそうだし、割と簡単そうなので今後強化していこうと思っています。

コンパイラおよびオプション指定

これも .md をソースとして考えてツールとして実用するには必須な機能です。

あとはライブラリとして出力できるようにするという方向で、「main を補わない使い方」も1つ考えたいところです。Markdownから .lib.a が出てくると面白いかな、と思っています。

様々なオプションがあるのでどうしようなんですが、シングルファイル形式なら適当に dflags とかの行を埋めればいけたりする…?もうちょっと考えどころですかね。

その他あれこれ

ソース出力

コードブロックをつなげてファイルに吐き出すコマンドを作ろうかなと考えてました。ちょっとリファクタリングしないとなんですが。

これも実用のために必要な気がしています。Markdownからソースコードを生成できれば後工程を上手くやる方法は如何様にもありそうなんですよね。
特にD言語以外に発展させることを考えると…

ファイル名対応?

Markdownの言語名のあたり、以下のようにファイル名をつけるの誰がやり始めたんでしょうね。(大体見当はついている)

CommonMarkの仕様にも見当たらず、GitHubでもそんな表示は無く、完全にガラパゴスジャパンを発揮しているような気がしています。

直前に太字の文字列置いておけば良いと思ったりするんですが、まぁわかりやすいから良しなんですかね…

コード例

```d:app.d
writeln("Hello");
```

app.d
writeln("Hello");

と出る機能

さいごに

1日目の記事ですが、リポジトリを直前公開しようと思っていたところ、いざGitHubに置いたらActionsが上手く動かないことがわかりました。

md が依存している jcli が古くて、最近公開された dmd 2.098.1 でエラーになるのです。2.098.0 はセーフなんですけど。

速攻で直してリリースして記事はなんとか夜のうちに出しました。若干試行錯誤のログが残ってしまったのが悔やまれますね。前もってちゃんと準備しておきましょう、という反省が残りました。

今回は MarkdownをCIでドキュメントテストにかけるというアイデア、ZennのGitHub連携が便利、の2点だけでも伝わればと思います。

そして何よりD言語ならこんなツールが数日で作れること、他の言語では一見難しいこともあっさりできるなという感覚があり、推し言語が強いという感動でまた1年過ごせそうです。

ここまで、何か思うところがあればいいねやTwitterのフォローをお願いいたします!!

来年も楽しく面白くプログラミングしていきましょう!!!

GitHubで編集を提案

Discussion

ログインするとコメントできます