Gaiden Filters/Extensions活用のススメ

はじめに

久しぶりのブログ記事です。 この記事は、G* Advent Calendarの13日目として書いています。 昨日はきょんさんによるGroovyのAST変換事情で、明日もきょんさんの予定です。

Gaidenとは?

数年前にGaidenというドキュメント作成ツールひっそりとリリースして、弊社の社内ではそこそこ活用していたりします。

特徴は、↓という感じです。

  • Markdownでソースを執筆する
  • Sphinxライクな複数ページのHTMLドキュメントを生成できる
  • Gaidenラッパーという仕組みで自動インストールできるので、Javaさえあればすぐにビルドできる
  • Gaiden Extensionという仕組みで、自由にシンタックスを拡張できる *1

Gaiden自体のホームページもGaidenで生成しています。 ちなみに、拙作のGroovyServのホームページも同じくGaiden製です。

Markdownの記述力って低いんでしょ?

確かにGFM(Github Flavored Markdown)でさえも相当シンプルなので、そこそこの分量のドキュメントを執筆するにはMarkdownの記述力だと不足ガチかもしれません。 その点、Asciidocはかなり機能がリッチなので、昨今、人気が高まってきている感じがあります。

しかし、個人的にはMarkdownのプレーンテキストフレンドリなシンプルさが好きなので、なんとかMarkdownで書きたいわけです。

そこで、Gaiden Filtersの出番です。

Gaiden Filters

簡単にいうと、簡単なGroovyコードを書くだけで、自由にシンタックスを増やしたり拡張したりし放題できる仕組みです。

以下の3つのタイミングで自由に介入して、自由にテキストが置換できます。

  • before: Markdownソースのテキスト
  • after: Markdownソースから変換されたHTML断片のテキスト
  • afterTemplate: テンプレートを使ってレンダリングされた最終的なHTMLのテキスト

サンプル: バージョンを設定ファイルに外だしして、ビルド時に置換する

config.groovyに、以下のように記述するだけです。 beforeに代入したクロージャの戻り値のテキストが次のフェーズ(この場合はMarkdowのレンダリング)に渡されます。

// in config.groovy
...

version = "1.0.0"

// Filters
filters = {
    expandVersion {
        before = { text ->
            text.replaceAll(/(?m)<VERSION>/, version)
        }
    }
}

あとは、Markdownで以下のようにVERSIONという文字を使ってドキュメントを書きます。

このドキュメントのバージョンはVERSIONです。

これでgaiden buildを実行すると、生成されたHTMLドキュメントでは

このドキュメントのバージョンは1.0.0です。

と、バージョン番号が展開されます。 とてもわかりやすいですね。

Gaiden Extensions

Gaiden Filtersが相当便利なので、ドキュメントプロジェクトごとに色々フィルタ実装を追加しまくっていると、汎用性の高いフィルタができたりして、他のドキュメントプロジェクトでも同じフィルタを利用したくなってきます。

config.groovyのフィルタ実装をコピペしてもよいのですが、そこで一歩進んで、再利用可能なモジュールであるGaiden Extensions形式にしてみましょう。

と言っても大したものではなくて、config.groovyと必要なリソースを特定のディレクトリに放り込んだだけの構造です。

先ほどのサンプルのように他のリソースが不要なら、次のようにとてもシンプルな構造になります。

extensions/
└── expandVersion
    └── config.groovy
// in extensions/expandVersion/config.groovy
filters = {
    expandVersion {
        before = { text ->
            text.replaceAll(/(?m)<VERSION>/, version)
        }
    }
}

先ほどのサンプルからだと、以下の3手順で移行できます。

  1. extensions/expandVersionディレクトリを生成する。
  2. 直下のconfig.groovyextensions/expandVersion配下にコピーする。
  3. extensions/expandVersion/config.groovyを編集して、必要なfilters以外のソースをすべて削除する。

Gaidenはexntensionsディレクトリ配下の各ディレクトリの中のconfig.groovyも順番に評価してくれるので、先ほどと全く同じように動作します。

Gaiden ExntensionsでAdmonitionを実現してみる

さて、再利用性の高いオレオレシンタックスの作り方がわかったところで、Asciidocの素敵機能のひとつである Admonition (アドモニション)をGaiden Extensionsで実現してみましょう。

Admonitionの例

こういうヤツです。

Markdownの良いところはプレーンテキストとしてみてもそこそこ良い感じに読める、という点なので、そこは譲れません。 つまり、プレーンテキストでも良い感じに読めるような特殊なシンタックスを決めて、それをGaiden Filtersの仕組みでAdmonition表現に置換するようなGaiden Extensionsを作る必要があります。

ここでは、以下のようなシンタックスを導入してみます。

 > **NOTE**: This is title!
 >
 > This is an admonition with title.

いかがでしょうか。 個人的にはそんなに醜くないし、普通のMarkdownとしてレンダリングされた結果としても割と良い感じなのではないかと思いますが、最終的には個人の主観にお任せします。

あとは、これをビルドすると上の画像のようなレンダリング結果が得られるように置換ロジックを書けばOKです。

で、できあがったGaiden Extensionsが、こちらのGaiden Extension: Admonitionになります。

filterの実装はこんな感じです。

filters = {
    admonition {
        before = { text ->
            def converter = { all, type, title, lines ->
                def header = title ? """<div class="admonition-caption">$title</div>""" : ""
                return """<div class="admonition admonition-${type.toLowerCase()}">
                         |  <div class="admonition-icon">
                         |    <i class="fa"></i>
                         |  </div>
                         |  <div class="admonition-content" markdown="1">
                         |    $header
                         |    ${lines.replaceAll(/(?m)^\s*>\s*?/, '')}
                         |  </div>
                         |</div>""".stripMargin()
            }
            text.
                replaceAll(/(?ms)^> \*\*(NOTE|TIP|WARNING|IMPORTANT)\*\*(?::(.*?))?$(.*?)(?:^$|> ----)/, converter). // block
                replaceAll(/(?ms)^\*\*(NOTE|TIP|WARNING|IMPORTANT)\*\*:()(.*?)$/, converter) // inline
        }
    }
}

なお、CSSファイルもassetsとして追加してあります。

extensions/admonition
├── assets
│   └── css
│       └── admonition.css
└── config.groovy

NOTEだけではなく、

Admonitionの例

Admonitionの例

Admonitionの例

など複数種類に対応してたり、一行でシンプルに書いたり、タイトルをつけたり、などそこそこリッチな感じです。 詳しくはREADMEを読んでいただければ、使い方やサポートするシンタックスなどがわかるかと思います。

個人的には、これを大変便利に色々なドキュメントプロジェクトで使いまくってます。

おわりに

オレオレシンタックスを増やしたら、学習コストやメンテナンスコストが大変なのでは、という懸念もあるでしょうが、あーあー聞こえない聞こえない。 ではなく、まあ節度を持って活用すれば良いんじゃないでしょうか。

というわけで、巷では非常にレアなGaiden活用テクニックをお送りしました。

参考リンク

*1:なお、現在のバージョンではWindowsのGaidenラッパーによるインストール時にファイルパーミッション関連のエラーが発生してしまいますが、インストール自体は成功してるはずなのでエラーにめげずにもう一度Gaidenコマンドを実行すると動くんじゃないかと思います。たぶん。