Skip to content

Commit 3b352bc

Browse files
author
David Scheutz
committed
add code generation for Bindable views and BaseViewProvider
1 parent d0ff1b8 commit 3b352bc

File tree

5 files changed

+281
-4
lines changed

5 files changed

+281
-4
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"schemaVersion": "1.0",
3+
"artifacts": {
4+
"sourcery": {
5+
"type": "executable",
6+
"version": "2.1.7",
7+
"variants": [
8+
{
9+
"path": "sourcery-2.1.7/bin/sourcery",
10+
"supportedTriples": ["x86_64-apple-macosx", "arm64-apple-macosx"]
11+
},
12+
]
13+
}
14+
}
15+
}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
// Template name: Annotations
2+
// Template version: 1.0
3+
4+
{% macro includeImports type %}
5+
{% for import in type.imports %}
6+
import {{ import }}
7+
{% endfor %}
8+
{% endmacro %}
9+
10+
/*
11+
Example:
12+
/// @State(JournalView)
13+
*/
14+
{% for type in types.structs %}
15+
{% for comment in type.documentation %}
16+
{% if comment|contains:"@State(" %}
17+
{% call includeImports type %}
18+
{% set state %}{{ comment | replace:"@State(","" | replace:")","" }}{% endset %}
19+
extension {{ state }} {
20+
typealias State = {{ type.name }}
21+
}
22+
{% endif %}
23+
{% endfor %}
24+
{% endfor %}
25+
26+
/*
27+
Example:
28+
/// @Event(JournalView)
29+
*/
30+
{% for type in types.enums %}
31+
{% for comment in type.documentation %}
32+
{% if comment|contains:"@Event(" %}
33+
{% call includeImports type %}
34+
{% set event %}{{ comment | replace:"@Event(","" | replace:")","" }}{% endset %}
35+
extension {{ event }} {
36+
typealias Event = {{ type.name }}
37+
}
38+
{% endif %}
39+
{% endfor %}
40+
{% endfor %}
41+
42+
// TODO: find a better way to handle default external names
43+
{% macro associatedValueName value index -%}
44+
{%- if value.localName == nil -%}{%- if value.externalName == nil or value.externalName == "0" or value.externalName == "1" or value.externalName == "2" -%}{{ value.typeName|lowerFirstLetter|replace:".","" }}{{ index }}{%- else -%}{{ value.externalName }}{%- endif -%}{%- else -%}{{ value.localName }}{%- endif -%}
45+
{%- endmacro %}
46+
47+
/*
48+
Example
49+
/// @Loop(StateModel, EventEnum)
50+
*/
51+
52+
{% macro propertyUpdates state %}
53+
{% for type in types.structs where type.name == state %}
54+
{% for property in type.variables|instance where property.readAccess != "private" and property.readAccess != "fileprivate" %}
55+
56+
var {{ property.name }}: {{ property.typeName }} {
57+
currentState.{{ property.name }}
58+
}
59+
60+
{% if property.defaultValue == nil and property|!computed %}
61+
func update{{ property.name|upperFirstLetter }}(_ {{ property.name }}: {{ property.typeName }}) {
62+
{% if property.isOptional %}
63+
update { $0.copy({{ property.name }}: .use({{ property.name }})) }
64+
{% else %}
65+
update { $0.copy({{ property.name }}: {{ property.name }}) }
66+
{% endif %}
67+
}
68+
69+
func update{{ property.name|upperFirstLetter }}(_ update: ({{ property.typeName }}) -> {{ property.typeName }}) {
70+
let {{ property.name }} = update(mutableState.value.{{ property.name }})
71+
update{{ property.name|upperFirstLetter }}({{ property.name }})
72+
}
73+
{% endif %}
74+
{% endfor %}
75+
{% break %}
76+
{% endfor %}
77+
{% endmacro %}
78+
79+
{% for type in types.all %}
80+
{% for comment in type.documentation %}
81+
{% if comment|contains:"@Loop" %}
82+
import SwiftUDF
83+
import SwiftEvolution
84+
import Combine
85+
{% call includeImports type %}
86+
87+
class GeneratedBase{{ type.name }}: ViewProvider {
88+
let state: CurrentValuePublisher<State>
89+
private let mutableState: MutableState<State>
90+
91+
// TODO: Default init for State thtat implements 'Initialable'
92+
init(initial: State) {
93+
mutableState = MutableState(initial)
94+
state = .init(mutableState)
95+
}
96+
97+
var subscriptions = [AnyCancellable]()
98+
99+
{% set typePair %}{{ comment|replace:"@Loop(",""|replace:")","" }}{% endset %}
100+
101+
{% for innerType in typePair|split: "," %}
102+
{% if forloop.counter == 1 %}
103+
{% set state %}{{ innerType }}{% endset %}
104+
var currentState: State { state.value }
105+
106+
{% call propertyUpdates state %}
107+
108+
func update(_ newState: State) {
109+
mutableState.send(newState)
110+
}
111+
112+
func update(_ update: (State) -> State) {
113+
mutableState.send(update(mutableState.value))
114+
}
115+
116+
func update(_ update: (MutableState<State>) -> Void) {
117+
update(mutableState)
118+
}
119+
120+
typealias State = {{ state }}
121+
{% endif %}
122+
123+
{% if forloop.counter == 2 %}
124+
{% set event %}{{ innerType|replace:" ","" }}{% endset %}
125+
typealias Event = {{ event }}
126+
{% for eventEnum in types.enums where eventEnum.name == event %}
127+
128+
/*
129+
Events entry point
130+
*/
131+
func handle(_ event: Event) {
132+
{{ "switch event {" if eventEnum.cases.count > 0 }}
133+
{% for case in eventEnum.cases %}
134+
{% if case.hasAssociatedValue %}
135+
case .{{ case.name }}(
136+
{% for associatedValue in case.associatedValues %}
137+
{% set name %}{% call associatedValueName associatedValue forloop.counter %}{% endset %}
138+
let {{ name }}{{ ", " if not forloop.last }}
139+
{% endfor %}
140+
): {{ case.name }}(
141+
{% for associatedValue in case.associatedValues %}
142+
{% set name %}{% call associatedValueName associatedValue forloop.counter %}{% endset %}
143+
{{ name }}: {{ name }}{{ ", " if not forloop.last }}
144+
{% endfor %}
145+
)
146+
{% else %}
147+
case .{{ case.name }}: {{ case.name }}()
148+
{% endif %}
149+
{% endfor %}
150+
{{ "}" if eventEnum.cases.count > 0 }}
151+
}
152+
153+
/*
154+
Override functions
155+
*/
156+
157+
func start() {}
158+
func stop() {}
159+
160+
{% for case in eventEnum.cases %}
161+
{% if case.hasAssociatedValue %}
162+
func {{ case.name }}(
163+
{% for associatedValue in case.associatedValues %}
164+
{% set name %}{% call associatedValueName associatedValue forloop.counter %}{% endset %}
165+
{{ name }}: {{ associatedValue.typeName }}{{ ", " if not forloop.last }}
166+
{% endfor %}
167+
) {
168+
{% else %}
169+
func {{ case.name }}() {
170+
{% endif %}
171+
fatalError("{{ case.name }} not implemented - needs to be overriden!")
172+
}
173+
{% endfor %}
174+
175+
{% break %}
176+
{% endfor %}
177+
{% endif %}
178+
{% endfor %}
179+
}
180+
181+
{% endif %}
182+
{% endfor %}
183+
{% endfor %}
184+
185+
/*
186+
Bundle extensions
187+
*/
188+
189+
import Foundation
190+
191+
private final class {{ argument.target }}Class {}
192+
193+
extension Bundle {
194+
static var {{ argument.target|lowerFirstLetter }}: Bundle {
195+
Bundle(for: {{ argument.target }}Class.self)
196+
}
197+
}
Binary file not shown.

Package.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ let package = Package(
77
name: "SwiftUDF",
88
platforms: [.iOS(.v14), .macOS(.v10_15)],
99
products: [
10-
// Products define the executables and libraries a package produces, making them visible to other packages.
11-
.library(
12-
name: "SwiftUDF",
13-
targets: ["SwiftUDF"]),
10+
.library(name: "SwiftUDF", targets: ["SwiftUDF"]),
11+
.plugin(name: "SwiftUDFCodeGeneratorPlugin", targets: ["SwiftUDFCodeGeneratorPlugin"])
1412
],
1513
dependencies: [
1614
.package(url: "https://github.com/davidscheutz/SwiftEvolution.git", branch: "main")
@@ -22,5 +20,16 @@ let package = Package(
2220
.product(name: "SwiftEvolution", package: "SwiftEvolution")
2321
]
2422
),
23+
.plugin(
24+
name: "SwiftUDFCodeGeneratorPlugin",
25+
capability: .buildTool(),
26+
dependencies: [
27+
.target(name: "SwiftUDFSourcery")
28+
]
29+
),
30+
.binaryTarget(
31+
name: "SwiftUDFSourcery",
32+
path: "Binaries/sourcery.artifactbundle"
33+
),
2534
]
2635
)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import Foundation
2+
import PackagePlugin
3+
4+
@main
5+
struct SwiftUDFCodeGeneratorPlugin: BuildToolPlugin {
6+
func createBuildCommands (context: PluginContext, target: Target) throws -> [Command] {
7+
[]
8+
}
9+
}
10+
11+
#if canImport (XcodeProjectPlugin)
12+
import XcodeProjectPlugin
13+
14+
extension SwiftUDFCodeGeneratorPlugin: XcodeBuildToolPlugin {
15+
func createBuildCommands(context: XcodeProjectPlugin.XcodePluginContext, target: XcodeProjectPlugin.XcodeTarget) throws -> [PackagePlugin.Command] {
16+
[
17+
try sourcery(target: target, in: context)
18+
]
19+
}
20+
21+
private func sourcery(target: XcodeProjectPlugin.XcodeTarget, in context: XcodeProjectPlugin.XcodePluginContext) throws -> Command {
22+
let toolPath = try context.tool(named: "sourcery")
23+
let templatesPath = toolPath.path.removingLastComponent().removingLastComponent().appending("Templates")
24+
25+
return command(
26+
for: target,
27+
executable: toolPath.path,
28+
templates: templatesPath.string,
29+
root: context.xcodeProject.directory,
30+
output: context.pluginWorkDirectory
31+
)
32+
}
33+
34+
private func command(for target: XcodeTarget, executable: Path, templates: String, root: Path, output: Path) -> Command {
35+
Command.prebuildCommand(
36+
displayName: "SwiftUDF generate: \(target.displayName)",
37+
executable: executable,
38+
arguments: [
39+
"--templates",
40+
templates,
41+
"--args",
42+
"target=\(target.displayName)",
43+
"--sources",
44+
root.appending(subpath: target.displayName),
45+
"--output",
46+
output,
47+
"--parseDocumentation",
48+
"--disableCache",
49+
"--verbose"
50+
],
51+
environment: [:],
52+
outputFilesDirectory: output
53+
)
54+
}
55+
}
56+
#endif

0 commit comments

Comments
 (0)