This tiny Swift script allows to generate an implementation for static let allValues: [Self] on all enum that are marked as conforming to CasesEnumerable
This way if you declare:
enum Directons: CasesEnumerable { case north, south, east, west }The script will be able to generate:
extension Directions {
static let allValues: [Directions] = [.north, .south, .east, .west]
}- Install SourceKitten
- Download the
generate-enum-allValuesSwift script - Ensure the script has executable flag (
chmod +x generate-enum-allValues)
- Declare an empty protocol
protocol CasesEnumerable {}in your source code somewhere - For every
enumfor which you want anallValuesimplementation, mark them as conforming to thisCasesEnumerableprotocol - Run
sourcekitten structureon your source code and pipe the result to thegenerate-enum-allValuesSwift script to generate the implementation
sourcekitten structure --file inputfile.swift | generate-enum-allValuesThe Swift script will use the AST analysis from SourceKitten to find all the enum in your code that conform to CasesEnumerable, go over all the case declarations they contain, and generate the appropriate implementation for the static let allValues property in an extension.
Imagine you have the following model.swift source code:
// Some demo code that will be analyzed by SourceKitten + the genum Swift script
protocol CasesEnumerable {}
enum Boolish: Int, CasesEnumerable {
case Yeah = 1
case Nah = 0
case Meh = 2
}
struct Deep {
enum Nested {
enum Key: CasesEnumerable {
case `public`
case `private`
}
}
}
//: FooClass
class Foo {
enum Direction: CasesEnumerable {
case north, south
case east
case west
}
enum Silent {
case does, not, conform, to, magic, proto
case so, wont, be, extended
}
enum CardSymbol: Character, CasesEnumerable {
case hearts = "\u{2661}"
case spades = "\u{2664}"
case clubs = "\u{2667}"
case diamonds = "\u{2662}"
}
func bar(value: String) -> Int {
return 42
}
}Then running the command:
sourcekitten structure --file model.swift | generate-enum-allValues >allValues.generated.swiftWill generate the following allValues.generate.swift output content:
extension Boolish {
static let allValues: [Boolish] = [.Yeah, .Nah, .Meh]
}
extension Deep.Nested.Key {
static let allValues: [Deep.Nested.Key] = [.public, .private]
}
extension Foo.Direction {
static let allValues: [Foo.Direction] = [.north, .south, .east, .west]
}
extension Foo.CardSymbol {
static let allValues: [Foo.CardSymbol] = [.hearts, .spades, .clubs, .diamonds]
}And thus you could then do some stuff like this elsewhere in your code:
print("- Boolish :", Boolish.allValues.map { $0.rawValue })
print("- Deep.Nested.Key :", Deep.Nested.Key.allValues)
print("- Foo.Direction :", Foo.Direction.allValues)
print("- Foo.CardSymbol :", Foo.CardSymbol.allValues.map { $0.rawValue })So that when you run your code, it will print:
- Boolish : [1, 0, 2]
- Deep.Nested.Key : [main.Deep.Nested.Key.public, main.Deep.Nested.Key.private]
- Foo.Direction : [main.Foo.Direction.north, main.Foo.Direction.south, main.Foo.Direction.east, main.Foo.Direction.west]
- Foo.CardSymbol : ["♡", "♤", "♧", "♢"]
You can find this example in the
./exampledirectory of that repo, and you can try it out using therun_demo.shshell script (which invokes SourceKitten + thegenerate-enum-allValuesScript, thencateverything in a singleswiftfile to interpret and run it and print the values).
-
I hoped at first that this script would be able to use
SourceKittenFramework(which happen to be included in SwiftLint so you probably already have it on your machine already) to parse the AST instead of requiring the user to invokesourcekitten structureon the command line themselves. Unfortunately, I haven't succeeded in importing the Framework from the script as easily as I thought just yet. -
For this first version, I've decided to make this
allValuesgeneration opt-in, by only generating the extension for enums that are explicitly marked asCasesEnumerabledirectly. This is a deliberate choice, to avoid the risk of generating too much useless code if you were to generateallValuesfor all your enums even for those for which you don't need it
- I don't analyze the whole inheritance tree of the
enum, just the list of its direct conformances, soprotocol Foo: CasesEnumerable+enum Bar: Foowon't be matched - The script could easily changed if you don't want this filtering and instead generate the
allValuesfor all theenumsin your code. That's just a design choice. - So in a future version maybe a command line flag or something could be added to allow the user to specify if they want to filter
enumby a given protocol and even provide the protocol name themselves
- Every other interesting idea is welcome, don't hesitate to contribute. The script is quite small and all written in Swift, so it should be fairly easy to understand and contribute 😉