Skip to content

Commit c84b99a

Browse files
committed
added environment system
1 parent 6db4472 commit c84b99a

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
@propertyWrapper
2+
public struct Environment<T: Sendable>: Sendable {
3+
enum _Storage {
4+
case taskLocal(TaskLocal<T>)
5+
case optionalTaskLocal(TaskLocal<T?>)
6+
}
7+
8+
var storage: _Storage
9+
10+
public init(requiring taskLocal: TaskLocal<T?>) {
11+
storage = .optionalTaskLocal(taskLocal)
12+
}
13+
14+
public init(_ taskLocal: TaskLocal<T>) {
15+
storage = .taskLocal(taskLocal)
16+
}
17+
18+
public var wrappedValue: T {
19+
switch storage {
20+
case let .taskLocal(taskLocal): return taskLocal.wrappedValue
21+
case let .optionalTaskLocal(taskLocal):
22+
guard let value = taskLocal.wrappedValue else {
23+
fatalError("No value set for \(T.self) in \(taskLocal)")
24+
}
25+
26+
return value
27+
}
28+
}
29+
}
30+
31+
public extension HTML {
32+
func environment<T: Sendable>(_ taskLocal: TaskLocal<T>, _ value: T) -> _ModifiedTaskLocal<T, Self> {
33+
_ModifiedTaskLocal(wrappedContent: self, taskLocal: taskLocal, value: value)
34+
}
35+
}
36+
37+
public struct _ModifiedTaskLocal<T: Sendable, Content: HTML>: HTML {
38+
public typealias Tag = Content.Tag
39+
40+
var wrappedContent: Content
41+
var taskLocal: TaskLocal<T>
42+
var value: T
43+
44+
@_spi(Rendering)
45+
public static func _render<Renderer>(_ html: consuming _ModifiedTaskLocal<T, Content>, into renderer: inout Renderer, with context: consuming _RenderingContext) where Renderer: _HTMLRendering {
46+
html.taskLocal.withValue(html.value) {
47+
Content._render(html.wrappedContent, into: &renderer, with: context)
48+
}
49+
}
50+
51+
@_spi(Rendering)
52+
public static func _render<Renderer>(_ html: consuming Self, into renderer: inout Renderer, with context: consuming _RenderingContext) async throws where Renderer: _AsyncHTMLRendering {
53+
try await html.taskLocal.withValue(html.value) {
54+
try await Content._render(html.wrappedContent, into: &renderer, with: context)
55+
}
56+
}
57+
}
58+
59+
extension _ModifiedTaskLocal: Sendable where Content: Sendable {}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import Elementary
2+
import XCTest
3+
4+
final class EnvironmentRenderingTests: XCTestCase {
5+
func testSetsEnvironment() async throws {
6+
try await HTMLAssertEqual(
7+
div {
8+
MyNumber()
9+
MyNumber()
10+
.environment(Values.$number, 1)
11+
MyNumber()
12+
},
13+
"<div>010</div>"
14+
)
15+
}
16+
17+
func testGetsOptionalEnvironment() async throws {
18+
try await HTMLAssertEqualAsyncOnly(
19+
div {
20+
MyDatabaseValue()
21+
.environment(Values.$database, Database())
22+
},
23+
"<div><p>Hello</p></div>"
24+
)
25+
}
26+
}
27+
28+
struct MyNumber: HTML {
29+
@Environment(Values.$number) var number
30+
31+
var content: some HTML {
32+
"\(number)"
33+
}
34+
}
35+
36+
struct MyDatabaseValue: HTML {
37+
@Environment(requiring: Values.$database) var database
38+
var content: some HTML {
39+
p {
40+
await database.value
41+
}
42+
}
43+
}
44+
45+
enum Values {
46+
@TaskLocal static var number = 0
47+
@TaskLocal static var database: Database?
48+
}
49+
50+
actor Database {
51+
var value: String = "Hello"
52+
}

0 commit comments

Comments
 (0)