上のブログのSourceryについての詳細を記述した記事です。
今回試したコードは以下のリポジトリに記載されていますので、興味があれば見て頂けますと幸いです。
やりたいこと
public class SampleView: UIView {
}
上記のようなクラスがあります。このクラスの内部に下記のようなコードを書くことを想定します。
public class SampleView: UIView {
@IBOutlet private var titleLabel: UILabel!
@IBOutlet private var subTitleLabel: UILabel!
public var title: String? {
get {
titleLabel.text
}
set {
titleLabel.text = newValue
titleLabel.isHidden = newValue == nil
}
}
public var subTitle: String? {
get {
subTitleLabel.text
}
set {
subTitleLabel.text = newValue
subTitleLabel.isHidden = newValue == nil
}
}
}
このときに、titleとsubTitleについて同じようなロジックのコードを繰り返し書いています。
このような繰り返しのコード(ボイラープレートコード)を毎回手で書くのは面倒なため、自動生成の手段を調べました。
以前から機会があればSourceryを使ってみようと思っていたため、自分でStencilのテンプレートを書いて実装してみました。
今回初めてSourceryとStencilを使用する際に、分からないことが多く時間がかかったため、SourceryとStencilについてメモを書いておきます。
Sourcery
Sourceryは、Swiftでメタプログラミングを行うためのツールです。
詳細はリポジトリを見ていただければと思います。今回はHomebrewでinstallして、CommandLine上で実行しました。
$brew install sourcery
Command Line Option
Sourceryには、多くのOptionがあります。これらのOptionファイルは .sourcery.yml に書くこともできますが、今回は手作業で実行したかったため、Command Line上でOptionを入力しています。
必要になったOptionについて説明を記載しておきます。
- --sources: SourceファイルのPathを記載します。複数指定可能。
- --templates - TemplateファイルのPathを記載します。複数指定可能。
- --output: OutputファイルのPathを記載します。
- --args: その他の入力となるパラメータを入れることができます。(--args arg1=value,arg2 のような形)。 Argsはテンプレート内でargument.nameのような形でアクセスできます。
これらのOptionを組み合わせて、下記のように書くことで、現在使用されているファイルに対してOutputすることも可能です。
$sourcery --sources SampleView.swift --templates Sample.stencil --output SampleView.swift --args title=String,subTitle=String
Inline code generation
上のbashコマンドを入力すると、コードをファイル内に挿入するのではなく、ファイル全体が書き換わってしまいます。
コードは基本的にファイル単位で生成されますが、下のようなコメントをStencilファイル内に入れることで、コードを行間に入れることができます。
// sourcery:inline:{{ type.name }}.TemplateName
// sourcery:end
SwiftファイルにもStencilに対応して下のようなコメントを書く必要があります。このコメントの間にCodeを入れることができます。
// sourcery:inline:SampleView.TemplateName // sourcery:end
参考:github.com
テンプレート作成
上で少し言及していましたが、SourceryではTemplateファイルを利用してコードを生成します。
独自のテンプレートを作成するには、Stencilというテンプレート言語でコードを書く必要があります。
StencilのGithubはコチラです。内容は上のサイトとほぼ同じです。
TemplateファイルのHello World(とりあえず実行してみる)
Stencilファイルを作り、下のようなコードを書いて、Sourceryのコマンドで引数を渡してコードを実行してみます。
StencilでSourceryから受け取るパタメータの形式については、Sourecry Reference内のWriting TemplateおよびTypesを参考にしました。
{% for type in types.classes %}
// sourcery:inline:{{ type.name }}.TemplateName
{{ type }}
// sourcery:end
{% endfor %}
実行するコマンドは以下。
$sourcery --sources Samples/Sample1.swift --templates Stencils/Sample1.stencil --output Samples/Sample1.swift
下のようなClassに関するtypeの情報が出力されることが確認できます。
class Sample1View: UIView {
// sourcery:inline:Sample1View.TemplateName
Class: module = nil, typealiases = [:], isExtension = false, kind = class, accessLevel = internal, name = Sample1View, isGeneric = false, localName = Sample1View, variables = [], methods = [], subscripts = [], initializers = [], annotations = [:], staticVariables = [], staticMethods = [], classMethods = [], instanceVariables = [], instanceMethods = [], computedVariables = [], storedVariables = [], inheritedTypes = ["UIView"], containedTypes = [], parentName = nil, parentTypes = AnyIterator<Type>(_box: Swift._IteratorBox<Swift._ClosureBasedIterator<SourceryRuntime.Type>>), attributes = [:], kind = class, isFinal = false
// sourcery:end
}
引数を渡す
今回行いたいことは、title, subTitleのような文字列に対してコードを自動生成することです。
そのためSourceryのoptionの1つであるargsを利用します。argsはStencil内でargumentとして使用することができます。
Stencilファイルは、勉強のためにあえて改行や空白を入れて書いてみます。
{% for type in types.classes %}
// sourcery:inline:{{ type.name }}.TemplateName
{{ argument }}
{% for key, value in argument %}
{{ key }}: {{value}}
{% endfor %}
// sourcery:end
{% endfor %}
実行するbashファイルは以下。
$sourcery --sources Samples/Sample2.swift --templates Stencils/Sample2.stencil --output Samples/Sample2.swift --args title=String,subTitle=String
出力結果は以下のようになります。
class Sample2: UIView {
// sourcery:inline:Sample2.TemplateName
["subTitle": String, "title": String]
subTitle: String
title: String
// sourcery:end
}
argumentを受け取って表示できていることを確認できたと思います。
また、stencilファイルで改行や空白を入れたところにも、結果が反映されていることが分かったと思います。
if, for文などの構文を書く
Stencilではif, for文などの構文はTagsという概念で扱うことができるようです。
コチラに、全ての対応しているTagsが記載されています。
上記の構文を利用して、下記のようなStencilファイルを作成します。
{% for type in types.classes %}
// sourcery:inline:{{ type.name }}.TemplateName
{% for key, value in argument %}
{% if value == "String" %}
@IBOutlet private var {{ key }}Label: UILabel!
{% endif %}
{% endfor %}
{% for key, value in argument %}
{% if value == "String" %}
public var {{ key }}: String? {
get {
{{ key }}Label.text
}
set {
{{ key }}Label.text = newValue
{{ key }}Label.isHidden = newValue == nil
}
}
{% endif %}
{% endfor %}
// sourcery:end
{% endfor %}
bashファイルは下記。
$sourcery --sources Samples/Sample3.swift --templates Stencils/Sample3.stencil --output Samples/Sample3.swift --args title=String,subTitle=String
出力されるSwiftファイルは下記です。今回やりたかったことが実現できました。
class Sample3: UIView {
// sourcery:inline:Sample3.TemplateName
@IBOutlet private var subTitleLabel: UILabel!
@IBOutlet private var titleLabel: UILabel!
public var subTitle: String? {
get {
subTitleLabel.text
}
set {
subTitleLabel.text = newValue
subTitleLabel.isHidden = newValue == nil
}
}
public var title: String? {
get {
titleLabel.text
}
set {
titleLabel.text = newValue
titleLabel.isHidden = newValue == nil
}
}
// sourcery:end
}
最後に
Sourceryはコードを生成する強力なツールであり、Stencilテンプレートを使用することでコードを自動生成できることを理解できました。
繰り返しになりますが、上記のコードは以下のリポジトリに記載されています。
興味があれば、下のブログも併せて読んでいただけますと幸いです。