gyb (Generate Your Boilerplate) によるテンプレート作成

  • 9
    Like
  • 0
    Comment
More than 1 year has passed since last update.

swiftの内部で使われているgybを使うことで無駄なコピペコードを減らそうという話。

  1. gybの取得
  2. configファイルの作成
  3. コード生成

gybの取得

pull_gyb.sh
mkdir gyb
curl "https://raw.githubusercontent.com/apple/swift/master/utils/gyb.py" -o "gyb/gyb.py"
curl "https://raw.githubusercontent.com/apple/swift/master/utils/gyb" -o "gyb/gyb"
chmod +x gyb/gyb

取得し実行権限を与えます。

configファイルの作成

今回はLensのSwift実装Lを用いて、手持ちのモデルにLensextensionするコードを生成することにします。

Extension.swift.gybLensExtensions.gyb.pyの2つのファイルを準備します。Extension.swift.gybの方はどういうテンプレートなのかを具体的に示すファイルで、LensExtensions.gyb.pyの方は手持ちのコードで定義してる型情報からどれをテンプレートに使うのかを表したものです。LensExtensions.gyb.pyから情報を引っ張ってきてExtension.swift.gybでテンプレートを作るイメージ。

まず手持ちのモデルとして、以下のようなものがあるとします。

model.swift
public struct Street {
    var name: String
}

この型にLensを追加するとして、LensExtensions.gyb.pyを以下のように作ります。

LensExtensions.gyb.py
lenses_info = {
    "Street": {
        "name": {"type": "String"}
    }
}

型名Streetとプロパティ名nameとその型Stringです。対象のモデルが増えてきたらlenses_infoに追加してやればOK。

次にExtensions.swift.gybを準備します。

Extensions.swift.gyb
%{
import os
import sys

try:
    config
except NameError:
    sys.exit("`-Dconfig` is not specified.")

config_path = os.path.abspath(config)
if not os.path.exists(config_path):
    sys.exit(config_path + " was not found.")
execfile(config_path)

try:
    lenses_info
except NameError:
    sys.exit("no info of lens.")

}%

import L

% for extending_type_name, properties in lenses_info.items():
extension ${extending_type_name} {

% for (key, attributes) in properties.items():
    public var ${key}_lens: Lens<${extending_type_name}, ${attributes["type"]}> {
        return Lens<${extending_type_name}, ${attributes["type"]}>(
            get: { $0.${key} },
            set: { new_${key}, obj in 
                var newObj = obj
                newObj.${key} = new_${key}
                return obj
            }
        )
    }
% end
}

% end

%{から}%で囲われた部分や%で始まる部分、${PARAM}の部分はコード部分でありテンプレートとして出力されません。テンプレートとして出力されるのはそれ以外の部分です。

%{から}%で囲われた部分でパラメータlenses_infoの準備をしています。引数からLensExtensions.gyb.pyへのパスを与えるようにしているため、まずはファイル取得ができているかチェックしています。また、ファイル中にlenses_infoがあるかをチェックしています。

%で始まる部分や${PARAM}の部分はコード部分です。lenses_infoパラメータを使ってforループ回しているだけですので、最後に記す出力ファイルと比較して見てもらえればイメージできるかと思います。

コード生成

./gyb/gyb Extension.swift.gyb -o output.swift -Dconfig=path/to/LensExtensions.gyb.py

output.swiftはテンプレートとして出力されるファイル。-D[NAME=VALUE]NAMEの部分にはカスタムパラメータを設定できます。ここではconfigを与えているのですが、Extension.swift.gyb内のconfigVALUEが代入されます。

実際に生成されたoutput.swiftはこんな感じ。

output.swift
import L

extension Street {
    public var name_lens: Lens<Street, String> {
        return Lens<Street, String>(
            get: { $0.name },
            set: { new_name, obj in 
                var newObj = obj
                newObj.name = new_name
                return obj
            }
        )
    }
}

実際にはコメントも出力されるのですが、ここでは省略してます。