Latest 0.2.0
Homepage https://github.com/krzysztofzablocki/Insanity
License MIT
Platforms osx , ios , tvos , watchos
Authors

CircleCI
codecov
Version
License
Platform

What is 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.

Insanity

Using it offers many benefits:

  • Write less repetitive code and make it easy to adhere to DRY principle.
  • It allows you to create better code, one that would be hard to maintain without it, e.g. performing automatic property level difference in tests
  • Limits the risk of introducing human error when refactoring.
  • Sourcery doesn’t use runtime tricks, in fact, it allows you to leverage compiler, even more, creating more safety.
  • Immediate feedback: Sourcery features built-in daemon support, enabling you to write your templates in real-time side-by-side with generated code.

Daemon demo

Sourcery is so meta that it is used to code-generate its boilerplate code


Table of Contents generated with DocToc

Why?

Swift features very limited runtime and no meta-programming features. Which leads our projects to contain boilerplate code.

Sourcery exists to allow Swift developers to stop doing the same thing over and over again while still maintaining strong typing, preventing bugs and leveraging compiler.

Have you ever?

  • Had to write equatable/hashable?
  • Had to write NSCoding support?
  • Had to implement JSON serialization?
  • Wanted to use Lenses?

If you did then you probably found yourself writing repetitive code to deal with those scenarios, does this feel right?

Even worse, if you ever add a new property to a type all of those implementations have to be updated, or you will end up with bugs.
In those scenarios usually compiler will not generate the error for you, which leads to error prone code.

Examples

I want to generate `Equatable` implementation

Template used to generate equality for all types that conform to `:AutoEquatable`, allowing us to avoid writing boilerplate code.

It adds `:Equatable` conformance to all types, except protocols (because it would require turning them into PAT’s).
For protocols it’s just generating `func ==`.

#### [Stencil template](Templates/AutoEquatable.stencil)

#### Available variable annotations:

– `skipEquality` allows you to skip variable from being compared.
– `arrayEquality` mark this to use array comparsion for variables that have array of items that don’t implement `Equatable` but have `==` operator e.g. Protocols

#### Example output:

“`swift
// MARK: – AdNodeViewModel AutoEquatable
extension AdNodeViewModel: Equatable {}

internal func == (lhs: AdNodeViewModel, rhs: AdNodeViewModel) -> Bool {
guard lhs.remoteAdView == rhs.remoteAdView else { return false }
guard lhs.hidesDisclaimer == rhs.hidesDisclaimer else { return false }
guard lhs.type == rhs.type else { return false }
guard lhs.height == rhs.height else { return false }

guard lhs.attributedDisclaimer == rhs.attributedDisclaimer else { return false }

return true
}
“`

I want to generate `Hashable` implementation

Template used to generate hashing for all types that conform to `:AutoHashable`, allowing us to avoid writing boilerplate code.

It adds `:Hashable` conformance to all types, except protocols (because it would require turning them into PAT’s).
For protocols it’s just generating `var hashValue` comparator.

#### [Stencil template](Templates/AutoHashable.stencil)

#### Available variable annotations:

– `skipHashing` allows you to skip variable from being compared.
– `includeInHashing` is only applied on enums and allows us to add some computed variable into hashing logic

#### Example output:

“`swift
// MARK: – AdNodeViewModel AutoHashable
extension AdNodeViewModel: Hashable {

internal var hashValue: Int {
return combineHashes(remoteAdView.hashValue, hidesDisclaimer.hashValue, type.hashValue, height.hashValue, attributedDisclaimer.hashValue, 0)
}
}
“`

I want to list all cases in an enum

Generate `count` and `allCases` for any enumeration that is marked with `AutoCases` phantom protocol.

#### [Stencil Template](Templates/AutoCases.stencil)

#### Example output:

“`swift
extension BetaSettingsGroup {
static var count: Int { return 8 }

static var allCases: [BetaSettingsGroup] {
return [
.featuresInDevelopment,
.advertising,
.analytics,
.marketing,
.news,
.notifications,
.tech,
.appInformation
]
}
}
“`

I want to generate test mocks for protocols

_Contributed by [@marinbenc](http://twitter.com/marinbenc)_

#### For each protocol implementing `AutoMockable` it will…
Create a class called `ProtocolNameMock` in which it will…

**For each function:**
– Implement the function
– Add a `functionCalled` boolean to check if the function was called
– Add a `functionRecievedArguments` tuple to check the arguments that were passed to the function
– Add a `functionReturnValue` variable and return it when the function is called.

**For each variable:**
– Add a gettable and settable variable with the same name and type

#### Issues and limitations:
* Overloaded methods will produce compiler erros since the variables above the functions have the same name. Workaround: delete the variables on top of one of the functions, or rename them.
* Handling success/failure cases (for callbacks) is tricky to do automatically, so you have to do that yourself.
* This is **not** a full replacement for hand-written mocks, but it will get you 90% of the way there. Any more complex logic than changing return types, you will have to implement yourself. This only removes the most boring boilerplate you have to write.

#### [Stencil template](Templates/AutoMockable.stencil)

#### Example output:

“`swift
class MockableServiceMock: MockableService {
//MARK: – functionWithArguments
var functionWithArgumentsCalled = false
var functionWithArgumentsRecievedArguments: (firstArgument: String, onComplete: (String)-> Void)?

//MARK: – functionWithCallback
var functionWithCallbackCalled = false
var functionWithCallbackRecievedArguments: (firstArgument: String, onComplete: (String)-> Void)?

func functionWithCallback(_ firstArgument: String, onComplete: @escaping (String)-> Void) {
functionWithCallbackCalled = true
functionWithCallbackRecievedArguments = (firstArgument: firstArgument, onComplete: onComplete)
}

“`

I want to generate Lenses for all structs

_Contributed by [@filip_zawada](http://twitter.com/filip_zawada)_

What are Lenses? Great explanation by @mbrandonw

This script assumes you follow swift naming convention, e.g. structs start with an upper letter.

#### [Stencil template](Templates/AutoLenses.stencil)

#### Example output:

“`swift
extension House {

static let roomsLens = Lens(
get: { $0.rooms },
set: { rooms, house in
House(rooms: rooms, address: house.address, size: house.size)
}
)
static let addressLens = Lens(
get: { $0.address },
set: { address, house in
House(rooms: house.rooms, address: address, size: house.size)
}
)

“`

I want to have diffing in tests

Template used to generate much better output when using equality in tests, instead of having to read wall of text it’s used to generate precise property level differences. This template uses [Sourcery Diffable implementation](../../Sourcery/Models/Diffable.swift)

from this:
before

to this:
after

#### [Stencil Template](Sourcery/Templates/Diffable.stencil)

#### Available annotations:

– `skipEquality` allows you to skip variable from being compared.

I want to generate `LinuxMain.swift` for all my tests

For all test cases generates `allTests` static variable and passes all of them as `XCTestCaseEntry` to `XCTMain`. Run with `–args testimports=’import MyTests’` parameter to import test modules.

#### [Stencil template](Templates/LinuxMain.stencil)

#### Available annotations:

– `disableTests` allows you to disable the whole test case.

#### Example output:

“`swift
import XCTest
//testimports

extension AutoInjectionTests {
static var allTests = [
(“testThatItResolvesAutoInjectedDependencies”, testThatItResolvesAutoInjectedDependencies),

]
}

extension AutoWiringTests {
static var allTests = [
(“testThatItCanResolveWithAutoWiring”, testThatItCanResolveWithAutoWiring),

]
}

XCTMain([
testCase(AutoInjectionTests.allTests),
testCase(AutoWiringTests.allTests),

])

“`

Writing templates

Sourcery templates are powered by Stencil

Make sure you leverage Sourcery built-in daemon to make writing templates a pleasure:
you can open template side-by-side with generated code and see it change live.

There are multiple ways to access your types:

  • type.TypeName => access specific type by name
  • types.all => all types, excluding protocols
  • types.classes
  • types.structs
  • types.enums
  • types.protocols => lists all protocols (that were defined in the project)
  • types.inheriting.BaseClass => lists all types inherting from known BaseClass (only those that were defined in source code that Sourcery scanned)
  • types.implementing.Protocol => lists all types conforming to given Protocol (only those that were defined in source code that Sourcery scanned)
  • types.based.BaseClassOrProtocol => lists all types implementing or inheriting from BaseClassOrProtocol (all type names encountered, even those that Sourcery didn’t scan)

All of these properties return Type objects.

Available types:

**Type**. Properties:

– `name` info whether type inherits from known base class
– `implements.Protocol` => info whether type implements known protocol
– `based.BaseClassOrProtocol` => info whether type implements or inherits from `BaseClassOrProtocol` (all type names encountered, even those that Sourcery didn’t scan)
– `containedTypes`

**Enum**. Built on top of `Type` and provides some additional properties:

– `rawType`

**EnumCase**. Properties:

– `name`

**AssociatedValue**. Properties:

– `localName`

**Variable**. Properties:

– `name`

**Method**. Properties:

– `name` (bar: T)`
– `selectorName` Bar` it is `foo(bar:)`, for `func foo(bar: T)` it is `foo(bar:)`
– `shortName` Bar` it is `foo`, for `func foo(bar: T)` it is `foo`
– `parameters` (bar: T) -> T where T: Equatable` it is `T where T: Equatable`
– `unwrappedReturnTypeName`

**MethodParameter**. Properties:

– `name`

**TypeName**. Properties:

– `name`

**TupleType**. Properties:

– `name`

**TupleElement**. Properties:

– `name`

Custom Stencil tags and filter

  • {{ name|upperFirst }} – makes first letter in name uppercase
  • {{ name|replace:"substring","replacement" }} – replaces occurances of substring with replacement in name (case sensitive)
  • {% if name|contains:"Foo" %} – check if name contains arbitrary substring, can be negated with ! prefix.
  • {% if name|hasPrefix:"Foo" %}– check if name starts with arbitrary substring, can be negated with ! prefix.
  • {% if name|hasSuffix:"Foo" %}– check if name ends with arbitrary substring, can be negated with ! prefix.
  • static, instance, computed, stored, tuple – can be used on Variable[s] as filter e.g. {% for var in variables|instance %}, can be negated with ! prefix.
  • static, instance, class, initializer – can be used on Method[s] as filter e.g. {% for method in allMethods|instance %}, can be negated with ! prefix.
  • enum, class, struct, protocol – can be used for Type[s] as filter, can be negated with ! prefix.
  • based, implements, inherits – can be used for Type[s], Variable[s], Associated value[s], can be negated with ! prefix.
  • count – can be used to get count of filtered array
  • annotated – can be used on Type[s], Variable[s], Method[s] and Enum Case[s] to filter by annotation, e.g. {% for var in variable|annotated: "skipDescription"%}, can be negated with ! prefix.

Using Source Annotations

Sourcery supports annotating your classes and variables with special annotations, similar how attributes work in Rust / Java

/// sourcery: skipPersistence
/// Some documentation comment
/// sourcery: anotherAnnotation = 232, yetAnotherAnnotation = "value"
/// Documentation
var precomputedHash: Int

If you want to attribute multiple items with same attributes, you can use section annotations:

/// sourcery:begin: skipEquality, skipPersistence
  var firstVariable: Int
  var secondVariable: Int
/// sourcery:end

Rules:

  • Multiple annotations can occur on the same line
  • You can add multiline annotations
  • You can interleave annotations with documentation
  • Sourcery scans all sourcery: annotations in the given comment block above the source until first non-comment/doc line

Format:

  • simple entry, e.g. sourcery: skipPersistence
  • key = number, e.g. sourcery: another = 123
  • key = string, e.g. sourcery: jsonKey = "json_key"

Accessing in templates:

{% ifnot variable.annotations.skipPersistence %}
  var local{{ variable.name|capitalize }} = json["{{ variable.annotations.jsonKey }}"] as? {{ variable.typeName }}
{% endif %}

Inline code generation

Sourcery supports inline code generation, you just need to put same markup in your code and template, e.g.

// sourcery.inline:TypeName.TemplateName
// sourcery:end

Sourcery will generate the template code and then perform replacement in your source file. Inlined generated code is not parsed to avoid chicken-egg problem.

Installing

Binary form

The easiest way to download the tool right now is to just grab a newest `.zip` distribution from [releases tab](https://github.com/krzysztofzablocki/Sourcery/releases).

Via CocoaPods

If you’re using CocoaPods, you can simply add pod ‘Sourcery’ to your Podfile.

This will download the Sourcery binaries and dependencies in `Pods/`.
You just need to add `$PODS_ROOT/Sourcery/bin/sourcery {source} {templates} {output}` in your Script Build Phases.

Via Swift Package Manager

If you’re using SwiftPM, you can simply add ‘Sourcery’ to your manifest.

Sourcery is placed in `Packages`.
After your first `swift build`, you can run `.build/debug/sourcery {source} {templates} {output}`.

From Source

You can clone it from the repo and just run `Sourcery.xcworkspace`.

Usage

Sourcery is a command line tool sourcery:

$ ./sourcery <source> <templates> <output> [--args arg1=value,arg2]

Arguments:

  • source – Path to a source swift files.
  • templates – Path to templates. File or Directory.
  • output – Path to output. File or Directory.
  • args – Additional arguments to pass to templates. Each argument can have explicit value or will have implicit true value. Arguments should be separated with , without spaces. Arguments are accessible in templates via argument.name

Options:

  • --watch [default: false] – Watch both code and template folders for changes and regenerate automatically.
  • --verbose [default: false] – Turn on verbose logging for ignored entities

Contributing

Contributions to Sourcery are welcomed and encouraged!

It is easy to get involved. Please see the Contributing guide for more details.

A list of contributors is available through GitHub.

To give clarity of what is expected of our community, Sourcery has adopted the code of conduct defined by the Contributor Covenant. This document is used across many open source communities, and I think it articulates my values well. For more, see the Code of Conduct.

License

Sourcery is available under the MIT license. See LICENSE for more information.

Attributions

This tool is powered by

Thank you! for:

  • Mariusz Ostrowski for creating the logo.
  • Artsy Eidolon team, because we use their codebase as a stub data for performance testing the parser.
  • Olivier Halligon for showing me his setup scripts for CLI tools which are powering our rakefile.

Other Libraries / Tools

If you want to generate code for asset related data like .xib, .storyboards etc. use SwiftGen. SwiftGen and Sourcery are complementary tools.

Make sure to check my other libraries and tools, especially:

  • KZPlayground – Powerful playgrounds for Swift and Objective-C
  • KZFileWatchers – Daemon for observing local and remote file changes, used for building other developer tools (Sourcery uses it)

You can follow me on twitter for news/updates about other projects I am creating.

Latest podspec

{
    "name": "Insanity",
    "version": "0.2.0",
    "summary": "A tool that brings meta-programming to Swift, allowing you to code generate Swift code.",
    "description": "A tool that brings meta-programming to Swift, allowing you to code generate Swift code.n* Featuring daemon mode that allows you to write templates side-by-side with generated code.n* Using SourceKit so you can scan your regular code.",
    "homepage": "https://github.com/krzysztofzablocki/Insanity",
    "license": "MIT",
    "authors": {
        "Krzysztof Zabu0142ocki": "[email protected]"
    },
    "social_media_url": "https://twitter.com/merowing_",
    "source": {
        "http": "https://github.com/krzysztofzablocki/Insanity/releases/download/0.2.0/insanity-0.2.0.zip"
    },
    "preserve_paths": "*",
    "platforms": {
        "osx": null,
        "ios": null,
        "tvos": null,
        "watchos": null
    }
}

Pin It on Pinterest

Share This