0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-10-29

はじめに

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

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

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

1. Mustacheのサポート機能

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

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

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

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

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

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

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

(1) 変数の出力

入力

{
    "氏名" : "松田"
    "職業レベル" : {
        "魔法使い" : "LV30",
        "僧侶" : "LV50"
    }
}

テンプレートファイル

{{氏名}}
僧侶:{{職業レベル.僧侶}}

出力

松田
僧侶:LV50

(2) 配列の出力

入力

{
    メンバー : [
        {
            "氏名" : "松田"
            "HP" : 100
        },
        {
            "氏名" : "鈴木"
            "HP" : 200
        }
    ]
}

テンプレートファイル

{{#メンバー}}
{{氏名}} : {{HP}}
{{/メンバー}}

出力

松田 : 100
鈴木 : 200

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

入力

{
    メンバー : [
        "松田",
        "鈴木"
    ]
}

テンプレートファイル

{{#メンバー}}
{{.}}
{{/メンバー}}

出力

松田
鈴木

(4) 条件に応じて出力(trueの場合、出力される)

入力

{
    "氏名" : "松田",
    "表示" : true
}

テンプレートファイル

{{#表示}}
氏名: {{氏名}}
{{/表示}}

出力

氏名 : 松田

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

(5) HTMLエスケープしないでそのまま出力

入力

{
    "氏名" : "<h1>松田</h1>"
}

テンプレートファイル

{{{氏名}}}

出力

<h1>松田</h1>

(6) メソッド

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

入力データ

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には、JMustacheMustache.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

(1)Mustache.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) {
          // ファイル読み込み時のエラー
        }
    }
}

(2)JMustacheを利用する場合

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

JMustache GitHub

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?