#Swift fastlaneの次に来る？ Sourcery🔮でメタプログラミングする

# Sourcery :crystal_ball:
[krzysztofzablocki/Sourcery](https://github.com/krzysztofzablocki/Sourcery)

> Sourcery scans your source code, applies your personal templates and generates Swift code for you, allowing you to use meta-programming techniques to save time and decrease potential mistakes.


テンプレートから`Swift`のコードを生成するツール

## Benefit

* ボイラープレートコードが減り、[DRY（Don't repeat yourself）原則](https://ja.wikipedia.org/wiki/Don't_repeat_yourself)を守りやすい
* リファクタリング時のヒューマンエラーを減らす
* テンプレートを書くと、リアルタイムにFeedbackが行われる

など、良さそうです

# Installing
https://github.com/krzysztofzablocki/Sourcery#installing

方法は以下の4通り :tada:

* Binary form
* Via CocoaPods
* Via Swift Package Manager
* From Source

**今回はCocoaPodsでインストールします！**

### と思ったら`pod install`でエラーが出た :scream_cat:

```
LoadError - cannot load such file -- nanaimo
/Library/Ruby/Gems/2.0.0/gems/xcodeproj-1.4.1/lib/xcodeproj/plist.rb:23:in `read_from_path'
/Library/Ruby/Gems/2.0.0/gems/xcodeproj-1.4.1/lib/xcodeproj/project.rb:200:in `initialize_from_file'
/Library/Ruby/Gems/2.0.0/gems/xcodeproj-1.4.1/lib/xcodeproj/project.rb:102:in `open'
/Library/Ruby/Gems/2.0.0/gems/cocoapods-1.2.0/lib/cocoapods/installer/analyzer.rb:851:in `block (2 levels) in inspect_targets_to_integrate'
/Library/Ruby/Gems/2.0.0/gems/cocoapods-1.2.0/lib/cocoapods/installer/analyzer.rb:850:in `each'
/Library/Ruby/Gems/2.0.0/gems/cocoapods-1.2.0/lib/cocoapods/installer/analyzer.rb:850:in `block in inspect_targets_to_integrate'
/Library/Ruby/Gems/2.0.0/gems/cocoapods-1.2.0/lib/cocoapods/user_interface.rb:64:in `section'
/Library/Ruby/Gems/2.0.0/gems/cocoapods-1.2.0/lib/cocoapods/installer/analyzer.rb:845:in `inspect_targets_to_integrate'
/Library/Ruby/Gems/2.0.0/gems/cocoapods-1.2.0/lib/cocoapods/installer/analyzer.rb:66:in `analyze'
/Library/Ruby/Gems/2.0.0/gems/cocoapods-1.2.0/lib/cocoapods/installer.rb:236:in `analyze'
/Library/Ruby/Gems/2.0.0/gems/cocoapods-1.2.0/lib/cocoapods/installer.rb:150:in `block in resolve_dependencies'
/Library/Ruby/Gems/2.0.0/gems/cocoapods-1.2.0/lib/cocoapods/user_interface.rb:64:in `section'
/Library/Ruby/Gems/2.0.0/gems/cocoapods-1.2.0/lib/cocoapods/installer.rb:149:in `resolve_dependencies'
/Library/Ruby/Gems/2.0.0/gems/cocoapods-1.2.0/lib/cocoapods/installer.rb:110:in `install!'
/Library/Ruby/Gems/2.0.0/gems/cocoapods-1.2.0/lib/cocoapods/command/install.rb:37:in `run'
/Library/Ruby/Gems/2.0.0/gems/claide-1.0.1/lib/claide/command.rb:334:in `run'
/Library/Ruby/Gems/2.0.0/gems/cocoapods-1.2.0/lib/cocoapods/command.rb:52:in `run'
/Library/Ruby/Gems/2.0.0/gems/cocoapods-1.2.0/bin/pod:55:in `<top (required)>'
/usr/local/bin/pod:23:in `load'
/usr/local/bin/pod:23:in `<main>'
```

### 解決 :smiley_cat::v:
https://github.com/CocoaPods/CocoaPods/issues/6214

```
gem list | grep nanaimo  // print nanaimo (0.2.3, 0.2.2)
gem uninstall nanaimo -v 0.2.2
```

## テンプレート用・生成コード用のフォルダを作成
なんでも良さそうですが、公式サンプルと同じ名前にします

* Templates - テンプレート用フォルダ
* CodeGenerated - 生成コード用フォルダ

<img width="350" alt="folders.png" src="https://qiita-image-store.s3.amazonaws.com/0/98613/ef52d76b-5cc2-bf6a-2a14-47c6274f1ab9.png">


## プロジェクトにScriptを追加
Build Phases > Run Scriptに以下を追加

`$PODS_ROOT/Sourcery/bin/sourcery {source} {templates} {output}`  

今回、プロジェクト名は SourcerySample にしたので、以下のScriptを追加しました :tada:

```
"${PODS_ROOT}/Sourcery/bin/sourcery" "${SRCROOT}/SourcerySample" "${SRCROOT}/Templates" "${SRCROOT}/CodeGenerated"
```

# Usage

## `.stencil`ファイルにテンプレートを定義
### `Stencil`とは
[kylef/Stencil](https://github.com/kylef/Stencil)
> Stencil is a simple and powerful template language for Swift. It provides a syntax similar to Django and Mustache. If you're familiar with these, you will feel right at home with Stencil.

syntaxがDjango、Mustacheに似た、`Swift`のためのテンプレート言語


### `Stencil`のテンプレート
今回はプロジェクト内の列挙型に`count`を返すextensionを定義します
`Stencil`ファイル名は、`Enum+Count`にします

```stensil:Enum+Count.stencil
{% for enum in types.enums %}
extension {{ enum.name| }} {
static var count: Int { return {{ enum.cases.count }} }
}
{% endfor %}
```

検証用に、以下の`Swift`の列挙型を用意しました

```swift:Planets.swift
enum Planets {
    
    case mecury
    case venus
    case earth
    case mars
    case jupiter
    case saturn
    case uranus
    case neptune
}
```


## コマンドを実行

`$ ./sourcery . templates output --watch`

* source - `Swift`ファイルのpath
* templates - テンプレートのディレクトリのpath
* output - 生成したコードのディレクトリのpath
* --watch - このflagにより、Templateファイルの変更を受けてリアルタイムにコード反映を行う

### 生成されました :tada:
#### Finder
<img width="400" alt="generated.png" src="https://qiita-image-store.s3.amazonaws.com/0/98613/1b8e9c08-6991-9def-f798-431c835d9e63.png">

この`Swift`ファイルをXcodeのプロジェクトに追加します

#### コード
検証用の列挙型の`count`を定義した`Extension`が生成されています

```swift:Enum+Count.generated.swift
extension Planets {
static var count: Int { return 8 }
}
```

## リアルタイムにコードを更新
一度[コマンドを実行する](#コマンドを実行)と、停止するまで`watch`が継続されます
その間、`Stencil`ファイルやテンプレート定義に該当するファイルを変更すると、
`⌘ + S`で生成コードがリアルタイムに更新されます

今までのテンプレートから少し変更して、`extension`にコメントを付与してみます

```stensil:Enum+Count.stencil
{% for enum in types.enums %}
// {{ enum.name }}のcount定義をするExtension　←　新たに追加したコメント
extension {{ enum.name }} {
static var count: Int { return {{ enum.cases.count }} }
}
{% endfor %}
```

`⌘ + S`で保存すると、ジェネレートされたファイルに反映されています :tada:

```swift:Enum+Count.generated.swift

// Planetsのcount定義をするExtension
extension Planets {
static var count: Int { return 8 }
}
```

列挙型の宣言を増やしてもバッチリです :tada::tada:

```swift:Enum+Count.generated.swift

// Planetsのcount定義をするExtension
extension Planets {
static var count: Int { return 8 }
}

// Zodiac.Signのcount定義をするExtension
extension Zodiac.Sign {
static var count: Int { return 12 }
}
```

# Sample Code
[mafmoff/SourcerySample](https://github.com/mafmoff/SourcerySample)

1. cloneする
2. `pod install`
3. `$ ./sourcery . templates output --watch`のpathを書き換えて実行

リアルタイムで、コードジェネレートを試すことができます

# テンプレートの書き方
その他のテンプレートの書き方は公式Readmeにまとまっています
[krzysztofzablocki/Sourcery#writing-templates](https://github.com/krzysztofzablocki/Sourcery#writing-templates)

# 参考
* [krzysztofzablocki/Sourcery](https://github.com/krzysztofzablocki/Sourcery)
* [#292: Metaprogramming with Sourcery 🔮](https://littlebitesofcocoa.com/292-metaprogramming-with-sourcery)
* [SOURCERY – Medium](https://medium.com/@martinho_t/sourcery-75a895b91936#.8akwrz3mu)

ありがとうございました！

# まとめ
* 4通りのインストール方法がある
* テンプレートとジェネレート用のディレクトリが必要
* `--watch`の間は、リアルタイム反映ができる
* `⌘ + S`でジェネレートするコードを更新

### 投稿のタイトルについて
参考にさせていただいた[#292: Metaprogramming with Sourcery 🔮](https://littlebitesofcocoa.com/292-metaprogramming-with-sourcery)に

> In modern times, things like Fastlane, CocoaPods, and Carthage come to mind. Slightly more seasoned folks may remember the emergence of Pull to Refresh, or BWToolkit.
Sourcery from Krzysztof Zabłocki is the latest addition to this set.

と書いてあったので、タイトルにしてみました

以上です :tada: 
今回は参考リンクで勉強させていただきました
もっといろいろ触って学びたいです

読んでいただき、ありがとうございました