Using Sourcery to write the boring code for you

Equatable, NSLocalizedString, JSON decoding – we’ve all been there. That repetitive work gets boring really fast. So wouldn’t it be great if you didn’t have to do it?

This is where Sourcery comes in. Sourcery is a code generation tool that allows you to create your own templates to generate all that dull code for you! Android and Java apps have had something like this built in, called an Annotation Processor, which a lot of great libraries utilise to reduce the amount of code you need to write. While Sourcery isn’t anywhere close to the level of adoption that Annotation Processors are in the Android community, we can still write our own templates!

You can use Sourcery in a couple different ways, either by adding it as a cocoapod to your project or installing it in the shell on your mac. Personally I prefer it as a cocoapod, since anyone else who checks out the git repo won’t have to download anything additional, and you can add a custom build phase to automatically run the sourcery script each time you build.

We’re gonna look at building a strongly typed way of using NSLocalizedString in this post, but there’s a lot more you can do with Sourcery.

First create a podfile for your project and add pod ‘Sourcery’, then pod install. Now open your workspace and we’re ready to configure sourcery. First create two new groups at the root level of your project – Templates and GeneratedCode.

Screen Shot 2018-01-23 at 09.46.09.png.png

Inside GeneratedCode, create a new blank Swift file called SourceryGenerated.swift.

Screen Shot 2018-01-23 at 09.47.21.png.png

Now we’re gonna go ahead and add the sourcery script as a custom build phase. Click on your target, head over to build settings, and click the plus button and hit New Run Script Phase.

Screen Shot 2018-01-23 at 09.50.11.png.png

This will add  a new build phase, which you can rename to Run Sourcery. Then just go ahead and add this command, changing LegoLand for your project name. Last thing, drag this phase up above the Compile Sources phase.

Screen Shot 2018-01-23 at 09.51.41.png.png


$PODS_ROOT/Sourcery/bin/sourcery –sources ./YourProjectName –templates ./Templates –output ./GeneratedCode/SourceryGenerated.swift

Now we’re ready to add our templates! Sourcery uses .stencil files to generate code. We want to generate NSLocalizedString structs from enums we write, so write click on the Templates group and add a new file, calling it something like Localised+String.stencil.

With Stencil, we can write raw Swift code and it will be copy and pasted into our SourceryGenerated.swift file when we build. But we can also take advantage of Stencil’s features to do things like for loops, if statements, etc. First, because NSLocalizedString is in Foundation, go ahead and add import Foundation to the top of your file. For readability, I’m also gonna create a mark for this. Finally, because we want all this in a struct, go ahead and define a new struct.

Screen Shot 2018-01-23 at 09.58.20.png.png

Now build with cmd+B, and once it succeeds, go into the SourceryGenerated.swift file and you should see the code we just wrote.

Screen Shot 2018-01-23 at 09.59.20.png.png

So now let’s actually make this work. The way I want to implement this is have an enum in my project, with inner enums, and those inner enums have cases with each represent a string in my strings file. So to show you the finished result, I want to write an enum like this:

Screen Shot 2018-01-23 at 10.09.52.png.png

And after Sourcery generates the code, I want it to look something like this:

Screen Shot 2018-01-23 at 10.04.46.png.png

Then I can just use Localised.Buildings.house to retrieve that string in a strongly-typed way. So let’s get to it.

Back in the stencil file, inside our struct, we need to iterate over all inner enums in our parent enum. To do this, I’m gonna go through all types, seeing if they conform to a Localisable protocol. If they do, I want to create a new struct from that enum, and loop through each of its cases and create a static variable from them. That looks like this:

Screen Shot 2018-01-23 at 10.12.14.png.png


import Foundation
// MARK: – Localisation
struct Localised {
{% for enum in types.implementing.Localisable %}
struct {{ enum.name|replace:"SourceryLocaliser.",""|replace:".","_" }} {
{% for case in enum.cases %}
static var {{ case.name }}: String { return NSLocalizedString("{{ enum.name|replace:"SourceryLocaliser.",""}}.{{case.name|capitalize}}", comment: "") }
{% endfor %}
}
{% endfor %}
}

The first thing we do is loop through all types in our project that conform to Localisable, then create a struct where the name is the enum’s name, removing the parent enum’s name first and replacing “.” characters with “_”, as structs can’t have a . in their name. Then we loop through each of the cases in that enum and create a static var, using the name of the enum for the first parameter in the call to NSLocalizedString. Then we need to manually call endfor to end the loop.

Once that code is in your stencil, we’re ready to use it. Create a new file, and inside a new protocol called Locasable, which is empty. Then create your enum – this needs to be named SourceryLocaliser, since this is what we look for to remove it in the stencil. Then go ahead and define your enums, make them conform to Localisable, and add some cases! It’s actually exactly how I’ve shown above if you’re still a bit confused.

Now build, and check out SourceryGenerated.swift. You should see there that we’ve now got our code generated! Now all you have to do is copy the generated key string into your strings file and provide a value!

The next thing I wanted to look at was creating a CodingKeys using Sourcery annotations. Sometimes, the names for values from an API aren’t particularly nice, and you want to use different ones. With Codable, if you do this you need to create a CodingKeys enum within your Codable struct and provide the keys that the API uses. Rather than doing this manually, we can use Sourcery to do it for us!

Create a new stencil file in the Templates group, and paste the following code:


{% for struct in types.all.implementing.Codable|struct %}
{% ifnot struct.annotations.ignore %}
extension {{ struct.name }} {
enum CodingKeys: String, CodingKey {
{% for var in struct.storedVariables %}
{% if var.annotations.key %}
case {{ var.name }} = "{{ var.annotations.key }}"
{% endif %}
{% ifnot var.annotations.key %}
case {{ var.name }}
{% endif %}
{% endfor %}
}
}
{% endif %}
{% endfor %}

Walking through this, first we’re going for all types in our project that conform to Coddle and are structs – you can remove the constraint for the type to be a struct if you wish. Then we add an if check, so if you annotate your Codable-conforming structure with /// sourcery: ignore, Sourcery won’t generate CodingKeys for you. Then we create an extension to the struct, create a CodingKeys enum, and loop through each of the stored variables, generating a case for each one. If you have annotated the variable with /// sourcery: key = “keyName”, Sourcery will make the raw value the key you provide, otherwise the raw value will be the same as the variable name.

Sourcery can do much more, and you should definitely check it out! For more info, head over to https://github.com/krzysztofzablocki/Sourcery.

See you next time!

Leave a comment