Sourceryでのボイラープレートコードの自動生成入門
上のブログの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テンプレートを使用することでコードを自動生成できることを理解できました。
繰り返しになりますが、上記のコードは以下のリポジトリに記載されています。
興味があれば、下のブログも併せて読んでいただけますと幸いです。