swift
の内部で使われているgyb
を使うことで無駄なコピペコードを減らそうという話。
gyb
の取得
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を用いて、手持ちのモデルにLens
をextension
するコードを生成することにします。
Extension.swift.gyb
とLensExtensions.gyb.py
の2つのファイルを準備します。Extension.swift.gyb
の方はどういうテンプレートなのかを具体的に示すファイルで、LensExtensions.gyb.py
の方は手持ちのコードで定義してる型情報からどれをテンプレートに使うのかを表したものです。LensExtensions.gyb.py
から情報を引っ張ってきてExtension.swift.gyb
でテンプレートを作るイメージ。
まず手持ちのモデルとして、以下のようなものがあるとします。
public struct Street {
var name: String
}
この型にLens
を追加するとして、LensExtensions.gyb.py
を以下のように作ります。
lenses_info = {
"Street": {
"name": {"type": "String"}
}
}
型名Street
とプロパティ名name
とその型String
です。対象のモデルが増えてきたらlenses_info
に追加してやればOK。
次に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
内のconfig
にVALUE
が代入されます。
実際に生成された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
}
)
}
}
実際にはコメントも出力されるのですが、ここでは省略してます。