Mustacheテンプレートを使用したソース自動生成

# はじめに
何十年も前からプログラムソース自動生成は行われてきました。今でこそ、生成AIが自動生成してくれますが、昔はルールベースで、テンプレートエンジンと呼ばれるライブラリがその役割を担っていました。**Apache Velocity**等はHTMLを生成するJSP等の代わりに使用するエンジンとして使用されるケースも多かったですが、私が携わっていたプロジェクトではソース自動生成ツールのテンプレートとして使用していました。

また、多くのOSSでもテンプレートエンジンは使用されており、例えば**OpenAPI Generator**は内部に**Mustache**を使用しています。
今回、私もMustacheを使用してソース自動生成を作ってみたのですが、多少ハマったため、Mustacheについてご紹介いたします。

尚、今回はソース自動生成を目的としたMustacheの利用になりますが、Mustacheは**Velocity**や**Thymeleaf**等と同様に動的HTMLを生成することもでき、**SpringBoot**でもサポートしてます。ただ、今回はあくまでプログラムソースのようなプレーンテキストファイルの出力について述べておりますので、あらかじめご了承ください。

# 1. Mustacheのサポート機能
多数のテンプレートエンジン機能を具備したOSS製品がありますが、Mustacheは**多くの言語に対応**していることが強みです。

以下、Mustacheのホームページを見ていただけると分かりますが、なんと52言語に対応しています。この52言語において基本的な記法が同じであるため、言語間の移植が容易です。

:::note info
https://mustache.github.io/
:::

トップページには大きく「**Logic-less templates**」と書かれています。

そしてマニュアルはUnix系のmanコマンドのフォーマットで端的に書かれています。

:::note info
https://mustache.github.io/mustache.5.html
:::

トップページに記載されている言語をクリックすると、各言語のGitHubのレポジトリに飛ぶようになっています。

筆者はJava使いですので、主にJavaで使用できる機能についてご説明致します。

:::note warn
以下の例で入力にJSONを使っていますが、(5)を見ていただくと分かるかと思いますが、Javaのオブジェクトを使って渡すことでデータの引き渡しことが可能です。
:::

## (1) 変数の出力
### 入力
``` JSON
{
    "氏名" : "松田"
    "職業レベル" : {
        "魔法使い" : "LV30",
        "僧侶" : "LV50"
    }
}
``` 
### テンプレートファイル
``` 
{{氏名}}
僧侶：{{職業レベル.僧侶}}
```
### 出力
> 松田
> 僧侶：LV50

## (2) 配列の出力
### 入力
``` JSON
{
    メンバー : [
        {
            "氏名" : "松田"
            "HP" : 100
        },
        {
            "氏名" : "鈴木"
            "HP" : 200
        }
    ]
}
```

### テンプレートファイル
``` 
{{#メンバー}}
{{氏名}} : {{HP}}
{{/メンバー}}
```

### 出力
> 松田 : 100
> 鈴木 : 200

尚、`List`や配列変数で、格納されているそのものが`String`型等で定義されていて、値そのものを出力したい場合は、`{{.}}`を使用します。

### 入力
``` JSON
{
    メンバー : [
        "松田",
        "鈴木"
    ]
}
```

### テンプレートファイル
```
{{#メンバー}}
{{.}}
{{/メンバー}}
```

### 出力
> 松田
> 鈴木

## (4) 条件に応じて出力（trueの場合、出力される）
### 入力
``` JSON
{
    "氏名" : "松田",
    "表示" : true
}
```
### テンプレートファイル
```
{{#表示}}
氏名：　{{氏名}}
{{/表示}}
```
### 出力
> 氏名 : 松田

尚、テンプレートファイルでfalseのときに表示する場合は、`{{^【変数名】}}`を使用します。

## (5) HTMLエスケープしないでそのまま出力
### 入力
``` JSON
{
    "氏名" : "<h1>松田</h1>"
}
```
### テンプレートファイル
``` 
{{{氏名}}}
```
### 出力
> \<h1>松田\</h1>

## (6) メソッド
渡すオブジェクトのクラスに**引数無しの**メソッドを用意しておくと、そのメソッドの結果を返してくれます。
加えて、Stringクラスのメソッド（`toUpperCase()`等）も呼び出すことができます。
引数無しで呼び出すときに、括弧`(`、`)`は不要です

### 入力データ
``` java
public class DataObject {
    private String postNumber = "104-0061";
    public String getAddress() {
        if("104-0061".equals(postNumber)) {
            return "東京都中央区銀座";
        }
        return "その他日本のどこか";
    }
    public String name = "Matsuda";
}
```

### テンプレートファイル(template.mustache)
```
{{name.toUpperCase}} : {{getAddress}} ({{postNumber}})
```

### 出力
> MATSUDA : 東京都中央区銀座 (104-0061)

## (7) 関数
ラムダ関数も使用できます。Javaの場合は、Lambdaインタフェースのオブジェクトを作成して、受け渡すことで呼び出し可能です

## (8) コメント
何も出力されません
```
{{! コメント }}
```


# 2. Mustacheで実現できない要件

ここまでがMustacheの基本形です。

基本的には関数やメソッドを書けば、実はある程度なんでも出力できるのですが、
ソース自動生成を考えると、意外とかゆいところに手が届いていなかったりします。。

それは、Velocity等と違って`{{ }}`の中に真偽式を書けないためです。例えば、配列やリストを表示させる場合、例えば、最初のレコードだけ特定の文言を出すとか、最後のレコードは特定の文字を出さない、みたいな制御がしづらいです。

これは、JSONやDDL等のソースコードや定義体の自動生成を考えると仕様局面をイメージできるかと思います。
JSONやDDLで項目を","で区切って羅列すると思うのですが、
最後のレコードの後には","が不要です。

これをいちいち消さないといけないわけです。


# 3. Mustacheには派生形が様々ある

Qiita等でもMustacheについて紹介している記事が多くありますが、Java系のMustacheには、**JMustache**と**Mustache.java**が存在しています。

OpenAPI Generatorで内包されているのは**JMustache**の方で、こちらは、以下の機能をサポートしています
|機能|記法|
|----|----|
|配列の最初の場合、trueを返す|`{{#-first}}`|
|配列の最後の場合、trueを返す|`{{#-last}}`|
|配列の順番を保持する|`{{#-index}}`|

falseの場合、`{{^-first}}`、`{{^-last}}`、`{{^-index}}`と書けますので、
```
{{^-last}},{{/-last}}
```
と書くことで、最後のみ","を消すことが可能です。


**Mustache.java**では難しく、`List`や配列に格納するオブジェクトに`isLast`のようなboolean変数を用意し、あらかじめデータを入れておくといった対応が必要になります。

その代わりに、細かなカスタマイズが可能ですが、Mustache.javaについて深く知る必要があります。


それぞれのライブラリの使い方が若干異なるので、サンプルを載せておきます。尚、テンプレートファイル（template.mustache）は`src/resources/`の下に配置しています。また、ライブラリは、MavenやGradle等でライブラリを読み込めば実行可能です。
MavenRepository

### （１）Mustache.javaを利用する場合
:::note info
- Java SEバージョン: 17
- Mustache.javaバージョン: 0.9.14
- MVN Repository: 
https://mvnrepository.com/artifact/com.github.spullara.mustache.java/compiler
:::
``` java
import com.github.mustachejava.*;

public class MustacheGenerator {
    String generate() {
        try {
          // 中略
          DataObject obj = new DataObject();
          // ClasspathResolverが実装されており、ファイル名だけで読込可能
          String filePath = "template.mustache";
          StringWriter writer = new StringWriter();
          Mustache mustache = (new DefaultMustacheFactory()).compile(filePath);

          // WriterとINPUTデータを渡すと、Writerに生成結果が出力される
          mustache.exceute(writer, obj).flush();
          return writer.toString();
        } catch (IOException ioe) {
          // ファイル読み込み時のエラー
        }
    }
}
```

### （２）JMustacheを利用する場合
:::note info
- Java SEバージョン: 17
- JMustacheバージョン: 1.15
- MVN Repository: 
https://mvnrepository.com/artifact/com.samskivert
:::

``` java
import com.samskivert.mustache.*;

public class MustacheGenerator {
    String generate() {
      // 中略
      DataObject obj = new DataObject();

      String filePath = "template.mustache";
      // クラスパスを通すため、"/"が必要
      try (InputStream is = this.getClass().getResourceAsStream("/" + filePath)) {
          // テンプレートファイルの中身をStringに変換
          String content = new String(is.readAllBytes(), StandardCharsets.UTF_8);
          Template template = Mustache.compiler.compile(content);
          // INPUTデータを渡すと、Stringを生成可能
          return template.execute(obj);
      } catch (IOException ioe) {
          // ファイル読み込み時のエラー
      } catch (MustacheException me) {
          // Mustache実行時のエラー
      }
}
```


# 4．まとめ

Mustacheの記事は意外と少なく、また公式ページが`README.md`しかないため、今回投稿させていただきました。
生成AIを使ったソースコード・設計書自動生成が全盛時代ですが、AIですので、現時点では精度の問題等はあるかなと思っています。ルールベースでソース自動生成をしたい場合等の参考になれば幸いです。


# 参考文献
## SpringBoot 3 プログラミング入門（著：掌田 津耶乃)
https://www.shuwasystem.co.jp/book/9784798069166.html
Mustacheのことが載っている数少ない日本語の書籍ですが、SpringBoot利用時のUIテンプレートツールとして紹介されています。

## Mustache.java GitHub
https://github.com/spullara/mustache.java/tree/main/compiler
## JMustache GitHub
https://github.com/samskivert/jmustache