倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

はじめての幻獣 Griffon 研 (22) : ビルド時にリソース・バンドルを生成するようにする

前回、Groovy の Config ファイルからリソース・バンドルを生成するスクリプトを書きました。 でも、アプリケーションのビルドとは別にスクリプトを実行しないといけないので、このままではちょっと面倒。 ってことで、今回はアプリケーションのビルド時にリソース・バンドルの生成も行われるようにしてみます。 もちろん、リソース・バンドルの生成だけを行うこともできるようにしておきましょう(一覧)。

まずはリファクタリング


ビルド・プロセスにリソース・バンドルの生成を織り込む前に、まずは前回作成したスクリプトをリファクタリングしておきます。 このリファクタリングによって、「ビルド・プロセス中で実行」する場合と「スクリプト単独で実行」する場合に共通する処理を抜き出して再利用します(参考 Griffon Guide 「4.2 Re-using Griffon scripts」)。

  • _Internationalize.groovy ・・・共通する処理
  • GenerateResourceBundles.groovy ・・・単独で実行する場合の処理(引数の処理)

_Internationalize.groovy

まずは共通する処理。 Griffon では使い回すスクリプトは '_' で始める習わしだそうです。

  • FunctionPlotter/scripts/_Internationalize.groovy

コードはこんな感じ:

includeTargets << griffonScript("Init")

encoding = System.getProperty('file.encoding')

target(generateResourceBundles: "Generate Resource Bundles") {

    def slurper = new ConfigSlurper()

    fileset(dir:'griffon-app/i18n', includes:'*.groovy').each{ antFile ->
        def file = antFile.file
        def props = slurper.parse(file.getText(encoding)).toProperties()

        def filename = file.name.replace('.groovy', '.properties')
        new File("griffon-app/i18n/$filename").withOutputStream{ out ->
            props.store(out, null)
        }
    }
}

変更点は

  • 引数の処理(argsMap など)は除去。
  • ターゲット名を 'main' ではなく 'generateResourceBundles' に変更。 たのスクリプトからこのターゲットの処理を呼び出したい場合は、このターゲット名を参照する。
  • defaultTarget の指定を除去。

GenerateResourceBundles.groovy

このスクリプトは単独で実行する場合に引数を処理するスクリプト。 引数を処理したら上記の _Internationalize.groovy に定義してあるターゲットを呼び出します。

  • FunctionPlotter/scripts/GenerateResourceBundles.groovy

内容は下記の通り:

includeTargets << new File("$basedir/scripts/_Internationalize.groovy")    // 上記の _Internationalize.groovy を読み込む

target(main: "Generate Resource Bundles") {

    def params = argsMap['params']
    if(!params.isEmpty()) encoding = params[0]
    
    generateResourceBundles()    // _Internationalize.groovy の generateResourceBundles ターゲットを呼び出す。
}

setDefaultTarget(main)

Gant スクリプトに特有っぽいコードがいくつかあるので解説:

  • includeTargets << ・・・_Internationalize.groovy スクリプトを読み込む。
  • encoding = params[0]・・・encoding プロパティ(これは _Internationalize.groovy で使う)に値をセットする。
  • generateResourceBundles()・・・_Internationalize.groovy に定義したターゲットを呼び出す。

さて、これでリファクタリング完了。 リファクタリングなので、スクリプトの実行の仕方は前回と同じです:

griffon gRB

コンパイル時にリソース・バンドルを生成するようにする


ではビルド・プロセス中にリソース・バンドルを生成する処理を織り込みましょう(参考 Griffon Guide 「4.3 Hooking into Events」)。 ビルド・プロセス中で発生するイベントは上記参考先にまとめられてますが、ここでは CompileStart イベントがいいでしょう。 このイベントに関するイベント・ハンドラ

  • FunctionPlotter/scripts/_Events.groovy

に記述します。 記述の仕方は eventCompileStart プロパティに処理を実行したいクロージャをセットします:

includeTargets << new File("$basedir/scripts/_Internationalize.groovy")

def alreadyGenerated = false

eventCompileStart = { kind ->
    if(!alreadyGenerated){
        generateResourceBundles()
        alreadyGenerated = true
    }
}

alreadyGenerated フィールド以外は GenerateResourceBundles.groovy の内容と同様(引数の処理がない分、むしろ簡単)です。 alreadyGenerated フィールドは、リソース・バンドルの生成を一度だけ行うために使用しています。 CompileStart イベントは「ソース・コードのコンパイル」と「テスト・コードのコンパイル」の2度呼ばれるのでこんな実装にしてます*1

これらのスクリプトができたら

griffon compile

などとすればコンパイル開始時にリソース・バンドルの生成も行ってくれます。 エンコーディングの指定はできませんが。

これで国際化も結構簡単にできるようになったかと。

追記


上記のスクリプトでも動きますが、もう少し改善を。 改善点は

  • Config オブジェクトの設定ファイルが変更されていない場合は、リソース・バンドルの生成を行わない
  • AntBuilder によるファイルのイテレーションは fileScanner で行う

です。 1つ目は File#lastModified() メソッドを使えば問題なし。 2つ目は Ant API のファイルオブジェクトから java.io.File へ変更する手間が省けます。

includeTargets << griffonScript("Init")

encoding = System.getProperty('file.encoding')

target(generateResourceBundles: "Generate Resource Bundles") {

    def slurper = new ConfigSlurper()

    fileScanner{ fileset(dir:'griffon-app/i18n', includes:'*.groovy') }.each{ configFile ->
        def filename = configFile.name.replace('.groovy', '.properties')
        def propFile = new File("griffon-app/i18n/$filename")

        if(configFile.lastModified() > propFile.lastModified()){
            def props = slurper.parse(configFile.getText(encoding)).toProperties()

            propFile.withOutputStream{ out ->
                props.store(out, null)
                echo("Genereate resource bundle : $filename")
            }
        }
    }
}

Ant 第2版

Ant 第2版

*1:本当は eventCompile クロージャに渡される引数「kind」によって「ソース・コードのコンパイル」か「テスト・コードのコンパイル」かを判定できるんだと思いますが、ちょっとどうやったらいいのか分かりませんでした。