From 1d3256186dfb5b408dc534e49a136ae73d3607a8 Mon Sep 17 00:00:00 2001
From: Mattes Mohr
Date: Mon, 28 Oct 2024 19:54:18 +0100
Subject: [PATCH 01/15] Provide the code with more detailed documentation
comments
---
.../Framework/Environment/Environment.swift | 24 ++++++++++++-----
.../Environment/EnvironmentKeys.swift | 7 +++++
.../Environment/EnvironmentModifier.swift | 18 +++++++------
.../Environment/EnvironmentObject.swift | 26 +++++++++++++------
.../Environment/EnvironmentValue.swift | 11 ++++++--
.../Primitives/Elements/Element.swift | 22 ++++++++++++----
6 files changed, 78 insertions(+), 30 deletions(-)
diff --git a/Sources/HTMLKit/Framework/Environment/Environment.swift b/Sources/HTMLKit/Framework/Environment/Environment.swift
index 32f89fac..eb0413a8 100644
--- a/Sources/HTMLKit/Framework/Environment/Environment.swift
+++ b/Sources/HTMLKit/Framework/Environment/Environment.swift
@@ -1,30 +1,36 @@
import Foundation
-/// A type that represents the environment
+/// A class that represents the environment
+///
+/// The environment provides storage for various settings used by the renderer
public class Environment {
/// The storage of the environment
private var storage: [AnyKeyPath: Any]
- /// Initiates a manager
+ /// Initializes the environment
public init() {
self.storage = [:]
}
- /// The current time zone of the environment
+ /// The current time zone of the environment
public var timeZone: TimeZone?
- /// The current calender of the environment
+ /// The current calendar of the environment
public var calendar: Calendar?
- /// The current local of the environment
+ /// The current locale of the environment
public var locale: Locale?
/// The current color scheme of the environment
public var colorScheme: String?
- /// Retrieves an item from storage by its path
+ /// Retrieves a value from environment for a given key path
+ ///
+ /// - Parameter path: The key path used to look up the value
+ ///
+ /// - Returns: The value
public func retrieve(for path: AnyKeyPath) -> Any? {
if let value = self.storage[path] {
@@ -34,7 +40,11 @@ public class Environment {
return nil
}
- /// Adds und updates an item to the storage
+ /// Inserts or updates a value in the environment for the given key path
+ ///
+ /// - Parameters:
+ /// - value: The value to be stored or updated
+ /// - path: The key path that identifies where the value is stored
public func upsert(_ value: T, for path: AnyKeyPath) {
self.storage[path] = value
}
diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentKeys.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentKeys.swift
index df68b701..39e5f4b9 100644
--- a/Sources/HTMLKit/Framework/Environment/EnvironmentKeys.swift
+++ b/Sources/HTMLKit/Framework/Environment/EnvironmentKeys.swift
@@ -1,12 +1,19 @@
import Foundation
+/// A set of predefined environment keys
+///
+/// The keys are used on the environment modifiers to configure various settings for the environment.
public struct EnvironmentKeys: Hashable {
+ /// A key used to configure the environment's calendar
public var calender: Calendar
+ /// A key used to configure the environment's time zone
public var timeZone: TimeZone
+ /// A key used to configure the environment's locale
public var locale: Locale
+ /// A key used to configure the environment's color scheme
public var colorScheme: String
}
diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift
index 06a8aab6..3d9a15d3 100644
--- a/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift
+++ b/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift
@@ -1,9 +1,6 @@
-/*
- Abstract:
- The file contains the environment modifier.
- */
-
-/// A type that contains the value and the following content, after modifing the environment.
+/// A type that holds the modification details for the environment
+///
+/// The modifier is received by the renderer and applied to the environment.
@_documentation(visibility: internal)
public struct EnvironmentModifier: Content {
@@ -13,10 +10,15 @@ public struct EnvironmentModifier: Content {
/// The environment value
public var value: Any?
- /// The following content
+ /// The sub-content
public var content: [Content]
- /// Initiates a environment modifier
+ /// Initializes an environment modifier
+ ///
+ /// - Parameters:
+ /// - key: The key path of the environment value to be modified
+ /// - value: The new value to update the environment value with
+ /// - content: The sub-content to be rendered
public init(key: AnyKeyPath, value: Any? = nil, content: [Content]) {
self.key = key
diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentObject.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentObject.swift
index d030f9ce..f47aa845 100644
--- a/Sources/HTMLKit/Framework/Environment/EnvironmentObject.swift
+++ b/Sources/HTMLKit/Framework/Environment/EnvironmentObject.swift
@@ -1,18 +1,20 @@
import Foundation
-/// A property wrapper type to initate an environment object
+/// A property wrapper type to provide access to an environment object
+///
+/// An environment object allows to read shared environment data.
@frozen @propertyWrapper public struct EnvironmentObject {
- /// The wrapped value
+ /// The wrapped object
public var wrappedValue: Wrapper
- /// Converts the type into the wrapped value
+ /// Initialiizes the environment object
public init(_ type: ObjectType.Type) {
self.wrappedValue = .init()
}
- /// A type, that holds the environment object informationen
+ /// A wrapper, that holds the object informationen
@dynamicMemberLookup public struct Wrapper {
/// The path of the parent
@@ -21,20 +23,26 @@ import Foundation
/// The path of the value
internal var path: AnyKeyPath
- /// Initiates a wrapper
+ /// Initializes the wrapper
public init() {
self.path = \WrapperType.self
}
- /// Initiates a wrapper with the necessary information for the environment object
+ /// Initializes the wrapper
+ ///
+ /// - Parameters:
+ /// - parent: The path of the parent
+ /// - path: The path of the value
internal init(parent: AnyKeyPath, path: AnyKeyPath) {
self.parent = parent
self.path = path
}
- /// Looks up for a containing property
+ /// Accesses a wrapped value for a given key path dynamically
+ ///
+ /// - Returns: An environment value
public subscript(dynamicMember member: KeyPath) -> EnvironmentValue {
guard let newPath = self.path.appending(path: member) else {
@@ -48,7 +56,9 @@ import Foundation
return .init(parentPath: self.path, valuePath: newPath)
}
- /// Looks up for a containing model
+ /// Accesses a wrapped model object for a given key path dynamically
+ ///
+ /// - Returns: An environment value
public subscript(dynamicMember member: KeyPath) -> Wrapper where T: ViewModel {
guard let newPath = self.path.appending(path: member) else {
diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift
index e9022c67..033ace20 100644
--- a/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift
+++ b/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift
@@ -1,6 +1,8 @@
import Foundation
-/// A type, that acts as a binding value
+/// A type that serves as a placeholder for an environment value
+///
+/// The placeholder will be evaluated and resolved by the renderer when needed.
public struct EnvironmentValue: Content {
/// The path of the values parent
@@ -9,7 +11,11 @@ public struct EnvironmentValue: Content {
/// The path of the value
internal var valuePath: AnyKeyPath
- /// Initiates a environment value
+ /// Initializes a environment value
+ ///
+ /// - Parameters:
+ /// - parentPath: The key path of the parent
+ /// - valuePath: The key path of the value
public init(parentPath: AnyKeyPath, valuePath: AnyKeyPath) {
self.parentPath = parentPath
@@ -19,6 +25,7 @@ public struct EnvironmentValue: Content {
extension EnvironmentValue {
+ /// Concatenates an environment value with another value
static public func + (lhs: Content, rhs: Self) -> Content {
return [lhs, rhs]
}
diff --git a/Sources/HTMLKit/Framework/Primitives/Elements/Element.swift b/Sources/HTMLKit/Framework/Primitives/Elements/Element.swift
index 106e569c..e678ff83 100644
--- a/Sources/HTMLKit/Framework/Primitives/Elements/Element.swift
+++ b/Sources/HTMLKit/Framework/Primitives/Elements/Element.swift
@@ -1,8 +1,3 @@
-/*
- Abstract:
- The file contains the default definition of an element. It defines which properties and methods an element should come with.
- */
-
/// A type that represents any html-element.
@_documentation(visibility: internal)
public protocol Element: Content {
@@ -10,14 +5,31 @@ public protocol Element: Content {
extension Element {
+ /// Sets the environment for the sub-content
+ ///
+ /// - Parameter key: The key
+ ///
+ /// - Returns: The environment modifier
public func environment(key: KeyPath) -> EnvironmentModifier {
return .init(key: key, content: [self])
}
+ /// Supplies a value to the environment
+ ///
+ /// - Parameters:
+ /// - key: The key to store the value with
+ /// - value: The value to be stored
+ ///
+ /// - Returns: The environment modifier
public func environment(key: KeyPath, value: V) -> EnvironmentModifier {
return .init(key: key, value: value, content: [self])
}
+ /// Supplies the object to the environment
+ ///
+ /// - Parameter object: The object to be stored
+ ///
+ /// - Returns: The environment modifier
public func environment(object: T) -> EnvironmentModifier {
return .init(key: \T.self, value: object, content: [self])
}
From 62e02e3624cf94d273f215f7ecee77600963bbb3 Mon Sep 17 00:00:00 2001
From: Mattes Mohr
Date: Mon, 28 Oct 2024 19:54:58 +0100
Subject: [PATCH 02/15] Fix typo
---
Sources/HTMLKit/Framework/Environment/EnvironmentKeys.swift | 2 +-
Sources/HTMLKit/Framework/Rendering/Renderer.swift | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentKeys.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentKeys.swift
index 39e5f4b9..39c2154a 100644
--- a/Sources/HTMLKit/Framework/Environment/EnvironmentKeys.swift
+++ b/Sources/HTMLKit/Framework/Environment/EnvironmentKeys.swift
@@ -6,7 +6,7 @@ import Foundation
public struct EnvironmentKeys: Hashable {
/// A key used to configure the environment's calendar
- public var calender: Calendar
+ public var calendar: Calendar
/// A key used to configure the environment's time zone
public var timeZone: TimeZone
diff --git a/Sources/HTMLKit/Framework/Rendering/Renderer.swift b/Sources/HTMLKit/Framework/Rendering/Renderer.swift
index 2be7f67e..f44d860d 100644
--- a/Sources/HTMLKit/Framework/Rendering/Renderer.swift
+++ b/Sources/HTMLKit/Framework/Rendering/Renderer.swift
@@ -380,7 +380,7 @@ public final class Renderer {
case \.locale:
self.environment.locale = value as? Locale
- case \.calender:
+ case \.calendar:
self.environment.calendar = value as? Calendar
case \.timeZone:
From 4bc848a93d854cdcfac868938effd3d73513f518 Mon Sep 17 00:00:00 2001
From: Mattes Mohr
Date: Mon, 28 Oct 2024 21:25:18 +0100
Subject: [PATCH 03/15] Refactor the code a bit
---
.../Framework/Environment/Environment.swift | 39 ++++++---
.../Environment/EnvironmentModifier.swift | 6 +-
.../Framework/Rendering/Renderer.swift | 25 ------
Tests/HTMLKitTests/EnvironmentTests.swift | 85 ++++++++++---------
Tests/HTMLKitVaporTests/ProviderTests.swift | 6 +-
5 files changed, 81 insertions(+), 80 deletions(-)
diff --git a/Sources/HTMLKit/Framework/Environment/Environment.swift b/Sources/HTMLKit/Framework/Environment/Environment.swift
index eb0413a8..fa73971f 100644
--- a/Sources/HTMLKit/Framework/Environment/Environment.swift
+++ b/Sources/HTMLKit/Framework/Environment/Environment.swift
@@ -3,7 +3,7 @@ import Foundation
/// A class that represents the environment
///
/// The environment provides storage for various settings used by the renderer
-public class Environment {
+public final class Environment {
/// The storage of the environment
private var storage: [AnyKeyPath: Any]
@@ -15,16 +15,36 @@ public class Environment {
}
/// The current time zone of the environment
- public var timeZone: TimeZone?
+ public var timeZone: TimeZone? {
+
+ get {
+ retrieve(for: \EnvironmentKeys.timeZone) as? TimeZone
+ }
+ }
/// The current calendar of the environment
- public var calendar: Calendar?
+ public var calendar: Calendar? {
+
+ get {
+ retrieve(for: \EnvironmentKeys.calendar) as? Calendar
+ }
+ }
/// The current locale of the environment
- public var locale: Locale?
+ public var locale: Locale? {
+
+ get {
+ retrieve(for: \EnvironmentKeys.locale) as? Locale
+ }
+ }
/// The current color scheme of the environment
- public var colorScheme: String?
+ public var colorScheme: String? {
+
+ get {
+ retrieve(for: \EnvironmentKeys.colorScheme) as? String
+ }
+ }
/// Retrieves a value from environment for a given key path
///
@@ -32,12 +52,7 @@ public class Environment {
///
/// - Returns: The value
public func retrieve(for path: AnyKeyPath) -> Any? {
-
- if let value = self.storage[path] {
- return value
- }
-
- return nil
+ return storage[path]
}
/// Inserts or updates a value in the environment for the given key path
@@ -46,6 +61,6 @@ public class Environment {
/// - value: The value to be stored or updated
/// - path: The key path that identifies where the value is stored
public func upsert(_ value: T, for path: AnyKeyPath) {
- self.storage[path] = value
+ storage[path] = value
}
}
diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift
index 3d9a15d3..cca4b822 100644
--- a/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift
+++ b/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift
@@ -5,13 +5,13 @@
public struct EnvironmentModifier: Content {
/// The environment key
- public var key: AnyKeyPath
+ internal var key: AnyKeyPath
/// The environment value
- public var value: Any?
+ internal var value: Any?
/// The sub-content
- public var content: [Content]
+ internal var content: [Content]
/// Initializes an environment modifier
///
diff --git a/Sources/HTMLKit/Framework/Rendering/Renderer.swift b/Sources/HTMLKit/Framework/Rendering/Renderer.swift
index f44d860d..e7fda879 100644
--- a/Sources/HTMLKit/Framework/Rendering/Renderer.swift
+++ b/Sources/HTMLKit/Framework/Rendering/Renderer.swift
@@ -369,31 +369,6 @@ public final class Renderer {
if let value = modifier.value {
self.environment.upsert(value, for: modifier.key)
-
- } else {
-
- if let value = self.environment.retrieve(for: modifier.key) {
-
- if let key = modifier.key as? PartialKeyPath {
-
- switch key {
- case \.locale:
- self.environment.locale = value as? Locale
-
- case \.calendar:
- self.environment.calendar = value as? Calendar
-
- case \.timeZone:
- self.environment.timeZone = value as? TimeZone
-
- case \.colorScheme:
- self.environment.colorScheme = value as? String
-
- default:
- throw Errors.unindendedEnvironmentKey
- }
- }
- }
}
return try render(contents: modifier.content)
diff --git a/Tests/HTMLKitTests/EnvironmentTests.swift b/Tests/HTMLKitTests/EnvironmentTests.swift
index 85176dd9..795dd5e5 100644
--- a/Tests/HTMLKitTests/EnvironmentTests.swift
+++ b/Tests/HTMLKitTests/EnvironmentTests.swift
@@ -1,64 +1,71 @@
-/*
- Abstract:
- The file tests the annotations.
- */
-
import HTMLKit
import XCTest
final class EnvironmentTests: XCTestCase {
- struct Object: Encodable {
-
- var title: String = "Welcome to WWDC"
- var name: String = "Mattes!"
- var image: String = "wwdc.jpeg"
- }
+ var renderer = Renderer()
- struct ParentView: View {
+ /// Tests the environment access through the environment object
+ ///
+ /// The renderer is expected to evaluate the placeholder and renderer the resulting value.
+ func testEnvironmentAccess() throws {
- var content: [Content]
+ struct FamilyObject: ViewModel {
+
+ let name: String = "Doe"
+ let father: FatherObject = FatherObject()
+ }
- init(@ContentBuilder content: () -> [Content]) {
- self.content = content()
+ struct FatherObject: ViewModel {
+
+ let avatar: String = "john_doe.jpeg"
+ let name: String = "John"
}
- var body: Content {
- Division {
- content
+ struct ParentView: View {
+
+ let content: [Content]
+
+ init(@ContentBuilder content: () -> [Content]) {
+ self.content = content()
+ }
+
+ var body: Content {
+ Division {
+ content
+ }
+ .environment(object: FamilyObject())
}
- .environment(object: Object())
}
- }
-
- struct ChildView: View {
- @EnvironmentObject(Object.self)
- var object
-
- var body: Content {
- ParentView {
- Section{
- Image()
- .source(object.image)
- Heading2 {
- object.title + " " + object.name
+ struct ChildView: View {
+
+ @EnvironmentObject(FamilyObject.self)
+ var object
+
+ var body: Content {
+ ParentView {
+ Section{
+ Image()
+ .source(object.father.avatar)
+ Heading1 {
+ object.name
+ }
+ Paragraph {
+ object.father.name + " " + object.name
+ }
}
}
}
}
- }
-
- var renderer = Renderer()
-
- func testEnvironment() throws {
XCTAssertEqual(try renderer.render(view: ChildView()),
"""
\
\
- \
- Welcome to WWDC Mattes!
\
+ \
+ Doe
\
+ John Doe
\
\
"""
diff --git a/Tests/HTMLKitVaporTests/ProviderTests.swift b/Tests/HTMLKitVaporTests/ProviderTests.swift
index 135ec129..0df9c9e1 100644
--- a/Tests/HTMLKitVaporTests/ProviderTests.swift
+++ b/Tests/HTMLKitVaporTests/ProviderTests.swift
@@ -42,7 +42,6 @@ final class ProviderTests: XCTestCase {
content
}
}
- .environment(object: TestObject())
}
}
@@ -192,12 +191,17 @@ final class ProviderTests: XCTestCase {
}
}
+ /// Tests the access to environment through provider
+ ///
+ /// The provider is expected to recieve the environment object and resolve it based on the request.
func testEnvironmentIntegration() throws {
let app = Application(.testing)
defer { app.shutdown() }
+ app.htmlkit.environment.upsert(TestObject(), for: \TestObject.self)
+
app.get("test") { request async throws -> Vapor.View in
return try await request.htmlkit.render(TestPage.SipplingView())
}
From dabe61393f597e282b99f7383ed22714c6990bd9 Mon Sep 17 00:00:00 2001
From: Mattes Mohr
Date: Fri, 3 Jan 2025 11:33:30 +0100
Subject: [PATCH 04/15] Use the right environment key
---
Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift b/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift
index fd8f5bfa..b768eb40 100644
--- a/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift
+++ b/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift
@@ -112,7 +112,7 @@ extension Request {
public var htmlkit: ViewRenderer {
if let acceptLanguage = self.acceptLanguage {
- self.application.htmlkit.environment.locale = HTMLKit.Locale(tag: acceptLanguage)
+ self.application.htmlkit.environment.upsert(HTMLKit.Locale(tag: acceptLanguage), for: \HTMLKit.EnvironmentKeys.locale)
}
return .init(eventLoop: self.eventLoop, configuration: self.application.htmlkit.configuration, logger: self.logger)
From 587bac5bfa8f0a35e920a1ccbb057816421a48cc Mon Sep 17 00:00:00 2001
From: Mattes Mohr
Date: Fri, 3 Jan 2025 12:09:02 +0100
Subject: [PATCH 05/15] Add a conditional statement to handle environment
specific conditions
---
.../Framework/Environment/Condition.swift | 42 ++++
.../Framework/Environment/Conditionable.swift | 4 +
.../Framework/Environment/Environment.swift | 26 ++
.../Environment/EnvironmentValue.swift | 32 ++-
.../Framework/Environment/Relation.swift | 60 +++++
.../Framework/Environment/Statement.swift | 26 ++
.../Extensions/Comparable+HTMLKit.swift | 52 ++++
.../Framework/Rendering/Renderer.swift | 125 ++++++++++
Tests/HTMLKitTests/EnvironmentTests.swift | 229 ++++++++++++++++++
9 files changed, 594 insertions(+), 2 deletions(-)
create mode 100644 Sources/HTMLKit/Framework/Environment/Condition.swift
create mode 100644 Sources/HTMLKit/Framework/Environment/Conditionable.swift
create mode 100644 Sources/HTMLKit/Framework/Environment/Relation.swift
create mode 100644 Sources/HTMLKit/Framework/Environment/Statement.swift
create mode 100644 Sources/HTMLKit/Framework/Extensions/Comparable+HTMLKit.swift
diff --git a/Sources/HTMLKit/Framework/Environment/Condition.swift b/Sources/HTMLKit/Framework/Environment/Condition.swift
new file mode 100644
index 00000000..1bbece69
--- /dev/null
+++ b/Sources/HTMLKit/Framework/Environment/Condition.swift
@@ -0,0 +1,42 @@
+/// A type representing a condition that compares an environment value against another value
+@_documentation(visibility: internal)
+public struct Condition: Conditionable {
+
+ /// A enumeration of potential comparison
+ public enum Comparison {
+
+ /// Indicates an equal comparison
+ case equal
+
+ /// Indicates a not-equal comparison
+ case unequal
+
+ /// Indicates a greater-than comparison
+ case greater
+
+ /// Indicates a less-than comparison
+ case less
+ }
+
+ /// The left-hand side value
+ public let lhs: EnvironmentValue
+
+ /// The right-hand side value to test against
+ public let rhs: any Comparable
+
+ /// The comparison to perfom
+ public let comparison: Comparison
+
+ /// Initializes a condition
+ ///
+ /// - Parameters:
+ /// - lhs: The origin value
+ /// - rhs: The value to atest against
+ /// - operation: The comparison to perfom
+ public init(lhs: EnvironmentValue, rhs: any Comparable, comparison: Comparison) {
+
+ self.lhs = lhs
+ self.rhs = rhs
+ self.comparison = comparison
+ }
+}
diff --git a/Sources/HTMLKit/Framework/Environment/Conditionable.swift b/Sources/HTMLKit/Framework/Environment/Conditionable.swift
new file mode 100644
index 00000000..772eb7d3
--- /dev/null
+++ b/Sources/HTMLKit/Framework/Environment/Conditionable.swift
@@ -0,0 +1,4 @@
+/// A type that defines a conditonal value which will be evualuated by the renderer.
+@_documentation(visibility: internal)
+public protocol Conditionable {
+}
diff --git a/Sources/HTMLKit/Framework/Environment/Environment.swift b/Sources/HTMLKit/Framework/Environment/Environment.swift
index fa73971f..dd6be60b 100644
--- a/Sources/HTMLKit/Framework/Environment/Environment.swift
+++ b/Sources/HTMLKit/Framework/Environment/Environment.swift
@@ -64,3 +64,29 @@ public final class Environment {
storage[path] = value
}
}
+
+extension Environment {
+
+ /// Evaluates one condition
+ ///
+ /// - Parameters:
+ /// - condition: The condition to evaluate
+ /// - content: The content for the true statement
+ ///
+ /// - Returns: A environment condition
+ public static func when(_ condition: Conditionable, @ContentBuilder content: () -> [Content]) -> Statement {
+ return Statement(compound: condition, first: content(), second: [])
+ }
+
+ /// Evaluates one condition
+ ///
+ /// - Parameters:
+ /// - condition: The condition to evaluate
+ /// - content: The content for the true statement
+ /// - then: The content for the false statement
+ ///
+ /// - Returns: A environment condition
+ public static func when(_ condition: Conditionable, @ContentBuilder content: () -> [Content], @ContentBuilder then: () -> [Content]) -> Statement {
+ return Statement(compound: condition, first: content(), second: then())
+ }
+}
diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift
index 033ace20..1a62dbd9 100644
--- a/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift
+++ b/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift
@@ -25,8 +25,36 @@ public struct EnvironmentValue: Content {
extension EnvironmentValue {
- /// Concatenates an environment value with another value
- static public func + (lhs: Content, rhs: Self) -> Content {
+ /// Concat environment value with environment value
+ public static func + (lhs: Content, rhs: Self) -> Content {
return [lhs, rhs]
}
+
+ /// Compare an environment value with another comparable value
+ ///
+ /// Makes an unequal evaluation
+ public static func != (lhs: Self, rhs: some Comparable) -> Condition {
+ return Condition(lhs: lhs, rhs: rhs, comparison: .unequal)
+ }
+
+ /// Compare an environment value with another comparable value
+ ///
+ /// Makes an equal evaluation
+ public static func == (lhs: Self, rhs: some Comparable) -> Condition {
+ return Condition(lhs: lhs, rhs: rhs, comparison: .equal)
+ }
+
+ /// Compare an environment value with another comparable value
+ ///
+ /// Makes an less than evaluation
+ public static func < (lhs: Self, rhs: some Comparable) -> Condition {
+ return Condition(lhs: lhs, rhs: rhs, comparison: .less)
+ }
+
+ /// Compare an environment value with another comparable value
+ ///
+ /// Makes an greater than evaluation
+ public static func > (lhs: Self, rhs: some Comparable) -> Condition {
+ return Condition(lhs: lhs, rhs: rhs, comparison: .greater)
+ }
}
diff --git a/Sources/HTMLKit/Framework/Environment/Relation.swift b/Sources/HTMLKit/Framework/Environment/Relation.swift
new file mode 100644
index 00000000..10a1d0b6
--- /dev/null
+++ b/Sources/HTMLKit/Framework/Environment/Relation.swift
@@ -0,0 +1,60 @@
+/// A type representing the logical relation between two conditionals
+@_documentation(visibility: internal)
+public struct Relation: Conditionable {
+
+ /// A enumeration of potential logical terms
+ public enum Term {
+
+ /// Indicates a conjunction
+ ///
+ /// All conditions must be true for the relation to be true
+ case conjunction
+
+ /// Indicates a disjunction
+ ///
+ /// One condition must be at least true for the relation to be true
+ case disjunction
+ }
+
+ /// The logical term specifying the relation
+ public let term: Term
+
+ /// The left-hand side conditional
+ public let lhs: Conditionable
+
+ /// The right-hand side conditional
+ public let rhs: Conditionable
+}
+
+/// Creates a conjunctional relation between two conditionals
+///
+/// ```swift
+/// Environment.when(value > 0 && value < 2) {
+/// }
+/// ```
+///
+/// - Parameters:
+/// - lhs: The left-hand side conditional
+/// - rhs: The right-hand side conditional
+///
+/// - Returns: A conjunctional relation
+public func && (lhs: Conditionable, rhs: Conditionable) -> Relation {
+ return Relation(term: .conjunction, lhs: lhs, rhs: rhs)
+}
+
+/// Creates a disjunctional relation between two conditionals
+///
+/// ```swift
+/// Environment.when(value > 0 || value < 2) {
+/// }
+/// ```
+///
+/// - Parameters:
+/// - lhs: The left-hand side conditional
+/// - rhs: The right-hand side conditional
+///
+/// - Returns: A disjunctional relation
+public func || (lhs: Conditionable, rhs: Conditionable) -> Relation {
+ return Relation(term: .disjunction, lhs: lhs, rhs: rhs)
+}
+
diff --git a/Sources/HTMLKit/Framework/Environment/Statement.swift b/Sources/HTMLKit/Framework/Environment/Statement.swift
new file mode 100644
index 00000000..e58d5b1a
--- /dev/null
+++ b/Sources/HTMLKit/Framework/Environment/Statement.swift
@@ -0,0 +1,26 @@
+/// A type representing a conditional block within the environment
+@_documentation(visibility: internal)
+public struct Statement: Content {
+
+ /// The compound condition
+ let compound: Conditionable
+
+ /// The first statement
+ let first: [Content]
+
+ /// The second statement
+ let second: [Content]
+
+ /// Initializes a statement
+ ///
+ /// - Parameters:
+ /// - compound: The compound of conditionals
+ /// - first: The statement to execute if conditionals are true
+ /// - second: The statement to execute if conditionals are false
+ init(compound: Conditionable, first: [Content], second: [Content]) {
+
+ self.compound = compound
+ self.first = first
+ self.second = second
+ }
+}
diff --git a/Sources/HTMLKit/Framework/Extensions/Comparable+HTMLKit.swift b/Sources/HTMLKit/Framework/Extensions/Comparable+HTMLKit.swift
new file mode 100644
index 00000000..d3b6dde6
--- /dev/null
+++ b/Sources/HTMLKit/Framework/Extensions/Comparable+HTMLKit.swift
@@ -0,0 +1,52 @@
+extension Comparable {
+
+ /// Checks for equality
+ ///
+ /// - Parameters:
+ /// - other: The other value to compare
+ ///
+ /// - Returns: The result
+ public func equal(_ other: Any) -> Bool {
+
+ guard let other = other as? Self else {
+ return false
+ }
+
+ return other == self
+ }
+
+ /// Checks for inequality
+ ///
+ /// - Parameters:
+ /// - other: The other value to compare
+ ///
+ /// - Returns: The result
+ public func unequal(_ other: Any) -> Bool {
+ return !equal(other)
+ }
+
+ /// Checks for a greater value
+ ///
+ /// - Parameters:
+ /// - other: The other value to compare
+ ///
+ /// - Returns: The result
+ public func greater(_ other: Any) -> Bool {
+
+ guard let other = other as? Self else {
+ return false
+ }
+
+ return other > self
+ }
+
+ /// Checks for smaller value
+ ///
+ /// - Parameters:
+ /// - other: The other value to compare
+ ///
+ /// - Returns: The result
+ public func less(_ other: Any) -> Bool {
+ return !greater(other)
+ }
+}
diff --git a/Sources/HTMLKit/Framework/Rendering/Renderer.swift b/Sources/HTMLKit/Framework/Rendering/Renderer.swift
index 2eba02bd..e610e9d0 100644
--- a/Sources/HTMLKit/Framework/Rendering/Renderer.swift
+++ b/Sources/HTMLKit/Framework/Rendering/Renderer.swift
@@ -136,6 +136,13 @@ public final class Renderer {
result += escape(content: try render(value: value))
}
+ if let statement = content as? Statement {
+
+ if let yield = try render(statement: statement) {
+ result += yield
+ }
+ }
+
if let string = content as? MarkdownString {
if !features.contains(.markdown) {
@@ -211,6 +218,13 @@ public final class Renderer {
result += escape(content: try render(value: value))
}
+ if let statement = content as? Statement {
+
+ if let yield = try render(statement: statement) {
+ result += yield
+ }
+ }
+
if let string = content as? MarkdownString {
if !features.contains(.markdown) {
@@ -329,6 +343,13 @@ public final class Renderer {
result += escape(content: try render(value: value))
}
+ if let statement = content as? Statement {
+
+ if let yield = try render(statement: statement) {
+ result += yield
+ }
+ }
+
if let string = content as? MarkdownString {
if !features.contains(.markdown) {
@@ -428,6 +449,110 @@ public final class Renderer {
throw Errors.unableToCastEnvironmentValue
}
}
+
+ /// Renders a environment statement
+ private func render(statement: Statement) throws -> String? {
+
+ var result = false
+
+ if let condition = statement.compound as? Condition {
+ result = try render(condition: condition)
+ }
+
+ if let relation = statement.compound as? Relation {
+ result = try render(relation: relation)
+ }
+
+ if result {
+ return try render(contents: statement.first)
+ }
+
+ return try render(contents: statement.second)
+ }
+
+ private func render(relation: Relation) throws -> Bool {
+
+ switch relation.term {
+ case .conjunction:
+
+ var result = true
+
+ if let condition = relation.lhs as? Condition {
+ result = try render(condition: condition)
+ }
+
+ if let relation = relation.lhs as? Relation {
+ result = try render(relation: relation)
+ }
+
+ if !result {
+ /// Bail early if the first result already is false
+ return result
+ }
+
+ if let condition = relation.rhs as? Condition {
+ result = try render(condition: condition)
+ }
+
+ if let relation = relation.rhs as? Relation {
+ result = try render(relation: relation)
+ }
+
+ return result
+
+ case .disjunction:
+
+ var result = false
+
+ if let condition = relation.lhs as? Condition {
+ result = try render(condition: condition)
+ }
+
+ if let relation = relation.lhs as? Relation {
+ result = try render(relation: relation)
+ }
+
+ if result {
+ /// Bail early if the first result is already true
+ return result
+ }
+
+ if let condition = relation.rhs as? Condition {
+ result = try render(condition: condition)
+ }
+
+ if let relation = relation.rhs as? Relation {
+ result = try render(relation: relation)
+ }
+
+ return result
+ }
+ }
+
+ private func render(condition: Condition) throws -> Bool {
+
+ guard let parent = self.environment.retrieve(for: condition.lhs.parentPath) else {
+ throw Errors.environmentObjectNotFound
+ }
+
+ guard let value = parent[keyPath: condition.lhs.valuePath] else {
+ throw Errors.environmentValueNotFound
+ }
+
+ switch condition.comparison {
+ case .equal:
+ return condition.rhs.equal(value)
+
+ case .greater:
+ return condition.rhs.greater(value)
+
+ case .unequal:
+ return condition.rhs.unequal(value)
+
+ case .less:
+ return condition.rhs.less(value)
+ }
+ }
/// Renders the node attributes.
private func render(attributes: OrderedDictionary) throws -> String {
diff --git a/Tests/HTMLKitTests/EnvironmentTests.swift b/Tests/HTMLKitTests/EnvironmentTests.swift
index 795dd5e5..7d5e7c13 100644
--- a/Tests/HTMLKitTests/EnvironmentTests.swift
+++ b/Tests/HTMLKitTests/EnvironmentTests.swift
@@ -71,4 +71,233 @@ final class EnvironmentTests: XCTestCase {
"""
)
}
+
+ /// Tests condtion evaluation for the environment
+ ///
+ /// The renderer is expected to evaluated the condition correctly and renderer the right statement based on the condition.
+ func testEnvironmentCondition() throws {
+
+ struct TestObject: ViewModel {
+
+ let firstName: String = "Jane"
+ let lastName: String = "Doe"
+ let age: Int = 40
+ }
+
+ struct TestView: View {
+
+ @EnvironmentObject(TestObject.self)
+ var object
+
+ var body: Content {
+ Paragraph {
+
+ // Should return false
+ Environment.when(object.firstName == "John") {
+ "True"
+ } then: {
+ "False"
+ }
+
+ // The counter test, should return true
+ Environment.when(object.firstName == "Jane") {
+ "True"
+ }
+
+ // Should return true
+ Environment.when(object.firstName != "John") {
+ "True"
+ }
+
+ // The counter test, should return false
+ Environment.when(object.firstName != "Jane") {
+ "True"
+ } then: {
+ "False"
+ }
+
+ // Should return false
+ Environment.when(object.age > 41) {
+ "True"
+ } then: {
+ "False"
+ }
+
+ // The counter test, should return true
+ Environment.when(object.age > 39) {
+ "True"
+ }
+
+ // Should return true
+ Environment.when(object.age < 41) {
+ "True"
+ }
+
+ // The counter test, should return false
+ Environment.when(object.age < 39) {
+ "True"
+ } then: {
+ "False"
+ }
+ }
+ .environment(object: TestObject())
+ }
+ }
+
+ XCTAssertEqual(try renderer.render(view: TestView()),
+ """
+ FalseTrueTrueFalseFalseTrueTrueFalse
+ """
+ )
+ }
+
+ /// Tests the evaluation of a statement with a conjunctional relation
+ func testConditionConjuction() throws {
+
+ struct TestObject: ViewModel {
+
+ let firstName: String = "Jane"
+ let lastName: String = "Doe"
+ let age: Int = 40
+ }
+
+ struct TestView: View {
+
+ @EnvironmentObject(TestObject.self)
+ var object
+
+ var body: Content {
+ Paragraph {
+
+ // The relation is true, cause both conditions are true
+ Environment.when(object.age > 39 && object.age < 41) {
+ "True"
+ } then: {
+ "False"
+ }
+
+ // The relation is false, cause both conditions are false
+ Environment.when(object.age < 39 && object.age > 41) {
+ "True"
+ } then: {
+ "False"
+ }
+
+ // The relation is false, cause the first condition is false
+ Environment.when(object.age > 41 && object.age > 39) {
+ "True"
+ } then: {
+ "False"
+ }
+
+ // The relation is false, cause the second condition is false
+ Environment.when(object.age > 39 && object.age > 41) {
+ "True"
+ } then: {
+ "False"
+ }
+ }
+ .environment(object: TestObject())
+ }
+ }
+
+ XCTAssertEqual(try renderer.render(view: TestView()),
+ """
+ TrueFalseFalseFalse
+ """
+ )
+ }
+
+ /// Tests the evaluation of a statement with a disjunctional relation
+ func testConditionDisjunction() throws {
+
+ struct TestObject: ViewModel {
+
+ let firstName: String = "Jane"
+ let lastName: String = "Doe"
+ let age: Int = 40
+ }
+
+ struct TestView: View {
+
+ @EnvironmentObject(TestObject.self)
+ var object
+
+ var body: Content {
+ Paragraph {
+
+ // The relation is true, cause the second condition is true
+ Environment.when(object.age > 41 || object.age == 40) {
+ "True"
+ } then: {
+ "False"
+ }
+
+ // The relation is true, cause the first condition is true
+ Environment.when(object.age == 40 || object.age > 41) {
+ "True"
+ } then: {
+ "False"
+ }
+
+ // The relation is false, cause all conditions are false
+ Environment.when(object.age == 50 || object.age > 41) {
+ "True"
+ } then: {
+ "False"
+ }
+ }
+ .environment(object: TestObject())
+ }
+ }
+
+ XCTAssertEqual(try renderer.render(view: TestView()),
+ """
+ TrueTrueFalse
+ """
+ )
+ }
+
+ /// Tests the evaluation of various relation combinations
+ func testConditionConjunctionAndDisjunction() throws {
+
+ struct TestObject: ViewModel {
+
+ let firstName = "Jane"
+ let lastName = "Doe"
+ let age = 40
+ }
+
+ struct TestView: View {
+
+ @EnvironmentObject(TestObject.self)
+ var object
+
+ var body: Content {
+ Paragraph {
+
+ // The statement is true, cause the first relation is true
+ Environment.when(object.age == 40 || object.age > 41 && object.age < 39) {
+ "True"
+ } then: {
+ "False"
+ }
+
+ // The statement is true, cause the second relation is true
+ Environment.when(object.age == 50 || object.age < 41 && object.age > 39) {
+ "True"
+ } then: {
+ "False"
+ }
+ }
+ .environment(object: TestObject())
+ }
+ }
+
+ XCTAssertEqual(try renderer.render(view: TestView()),
+ """
+ TrueTrue
+ """
+ )
+ }
}
From fe9b6c377aedd21df93bacacb57720dfd8239790 Mon Sep 17 00:00:00 2001
From: Mattes Mohr
Date: Fri, 3 Jan 2025 13:04:00 +0100
Subject: [PATCH 06/15] Add a loop statement to handle environment specific
sequences
---
.../Framework/Environment/Environment.swift | 11 ++
.../Framework/Environment/Sequence.swift | 20 +++
.../Framework/Rendering/Renderer.swift | 125 ++++++++++++++++++
Tests/HTMLKitTests/EnvironmentTests.swift | 36 +++++
4 files changed, 192 insertions(+)
create mode 100644 Sources/HTMLKit/Framework/Environment/Sequence.swift
diff --git a/Sources/HTMLKit/Framework/Environment/Environment.swift b/Sources/HTMLKit/Framework/Environment/Environment.swift
index dd6be60b..b8720657 100644
--- a/Sources/HTMLKit/Framework/Environment/Environment.swift
+++ b/Sources/HTMLKit/Framework/Environment/Environment.swift
@@ -89,4 +89,15 @@ extension Environment {
public static func when(_ condition: Conditionable, @ContentBuilder content: () -> [Content], @ContentBuilder then: () -> [Content]) -> Statement {
return Statement(compound: condition, first: content(), second: then())
}
+
+ /// Iterates through a sequence of values
+ ///
+ /// - Parameters:
+ /// - sequence: The sequence to iterate over
+ /// - content: The content for the iteration
+ ///
+ /// - Returns: A environment condition
+ public static func loop(_ sequence: EnvironmentValue, @ContentBuilder content: (EnvironmentValue) -> [Content]) -> Sequence {
+ return Sequence(value: sequence, content: content(sequence))
+ }
}
diff --git a/Sources/HTMLKit/Framework/Environment/Sequence.swift b/Sources/HTMLKit/Framework/Environment/Sequence.swift
new file mode 100644
index 00000000..800986e9
--- /dev/null
+++ b/Sources/HTMLKit/Framework/Environment/Sequence.swift
@@ -0,0 +1,20 @@
+/// A type representing a loop rendering content within the environment
+public struct Sequence: Content {
+
+ /// The environment value of the sequence
+ let value: EnvironmentValue
+
+ /// The accumulated content
+ let content: [Content]
+
+ /// Initializes a loop
+ ///
+ /// - Parameters:
+ /// - value: The environment value to retrieve the sequence from
+ /// - content: The content to render for each item in the sequence
+ init(value: EnvironmentValue, content: [Content]) {
+
+ self.value = value
+ self.content = content
+ }
+}
diff --git a/Sources/HTMLKit/Framework/Rendering/Renderer.swift b/Sources/HTMLKit/Framework/Rendering/Renderer.swift
index e610e9d0..4582fcf6 100644
--- a/Sources/HTMLKit/Framework/Rendering/Renderer.swift
+++ b/Sources/HTMLKit/Framework/Rendering/Renderer.swift
@@ -143,6 +143,10 @@ public final class Renderer {
}
}
+ if let loop = content as? Sequence {
+ result += try render(loop: loop)
+ }
+
if let string = content as? MarkdownString {
if !features.contains(.markdown) {
@@ -218,6 +222,10 @@ public final class Renderer {
result += escape(content: try render(value: value))
}
+ if let loop = content as? Sequence {
+ result += try render(loop: loop)
+ }
+
if let statement = content as? Statement {
if let yield = try render(statement: statement) {
@@ -350,6 +358,10 @@ public final class Renderer {
}
}
+ if let loop = content as? Sequence {
+ result += try render(loop: loop)
+ }
+
if let string = content as? MarkdownString {
if !features.contains(.markdown) {
@@ -603,4 +615,117 @@ public final class Renderer {
return value
}
+
+ private func render(loop: Sequence) throws -> String {
+
+ guard let parent = self.environment.retrieve(for: loop.value.parentPath) else {
+ throw Errors.environmentObjectNotFound
+ }
+
+ guard let values = parent[keyPath: loop.value.valuePath] as? (any Swift.Sequence) else {
+ throw Errors.environmentValueNotFound
+ }
+
+ var result = ""
+
+ for value in values {
+ try render(loop: loop.content, with: value, on: &result)
+ }
+
+ return result
+ }
+
+ private func render(loop contents: [Content], with value: Any, on result: inout String) throws {
+
+ for content in contents {
+
+ if let element = content as? (any ContentNode) {
+ try render(loop: element, with: value, on: &result)
+ }
+
+ if let element = content as? (any CustomNode) {
+ try render(loop: element, with: value, on: &result)
+ }
+
+ if let element = content as? (any EmptyNode) {
+ result += try render(element: element)
+ }
+
+ if let element = content as? (any CommentNode) {
+ result += render(element: element)
+ }
+
+ if let string = content as? LocalizedString {
+ result += try render(localized: string)
+ }
+
+ if let string = content as? MarkdownString {
+
+ if !features.contains(.markdown) {
+ result += escape(content: string.raw)
+
+ } else {
+ result += try render(markdown: string)
+ }
+ }
+
+ if let element = content as? String {
+ result += escape(content: element)
+ }
+
+ if content is EnvironmentValue {
+
+ switch value {
+ case let floatValue as Float:
+ result += String(floatValue)
+
+ case let intValue as Int:
+ result += String(intValue)
+
+ case let doubleValue as Double:
+ result += String(doubleValue)
+
+ case let stringValue as String:
+ result += stringValue
+
+ default:
+ break
+ }
+ }
+ }
+ }
+
+ private func render(loop element: some ContentNode, with value: Any, on result: inout String) throws {
+
+ result += "<\(element.name)"
+
+ if let attributes = element.attributes {
+ result += try render(attributes: attributes)
+ }
+
+ result += ">"
+
+ if let contents = element.content as? [Content] {
+ try render(loop: contents, with: value, on: &result)
+ }
+
+ result += "\(element.name)>"
+ }
+
+ private func render(loop element: some CustomNode, with value: Any, on result: inout String) throws {
+
+ result += "<\(element.name)"
+
+ if let attributes = element.attributes {
+ result += try render(attributes: attributes)
+ }
+
+ result += ">"
+
+ if let contents = element.content as? [Content] {
+ try render(loop: contents, with: value, on: &result)
+ }
+
+ result += "\(element.name)>"
+ }
}
diff --git a/Tests/HTMLKitTests/EnvironmentTests.swift b/Tests/HTMLKitTests/EnvironmentTests.swift
index 7d5e7c13..2cb61169 100644
--- a/Tests/HTMLKitTests/EnvironmentTests.swift
+++ b/Tests/HTMLKitTests/EnvironmentTests.swift
@@ -300,4 +300,40 @@ final class EnvironmentTests: XCTestCase {
"""
)
}
+
+ /// Tests the iteration over a sequence of environment values
+ func testEnvironmentLoop() throws {
+
+ struct TestObject: ViewModel {
+
+ let name: String = "Jane"
+ let children: [String] = ["Janek", "Janet"]
+ }
+
+ struct TestView: View {
+
+ @EnvironmentObject(TestObject.self)
+ var object
+
+ var body: Content {
+ Paragraph {
+ Environment.loop(object.children) { child in
+ Paragraph {
+ child
+ }
+ }
+ }
+ .environment(object: TestObject())
+ }
+ }
+
+ XCTAssertEqual(try renderer.render(view: TestView()),
+ """
+ \
+
Janek
\
+ Janet
\
+
+ """
+ )
+ }
}
From 60483d4228267422da3ba51fb5c15f258a69035f Mon Sep 17 00:00:00 2001
From: Mattes Mohr
Date: Fri, 3 Jan 2025 13:58:02 +0100
Subject: [PATCH 07/15] Make string interpolation of a environment value
possible
---
.../Environment/EnvironmentString.swift | 31 +++++++++++++++++++
.../Framework/Rendering/Renderer.swift | 25 +++++++++++++++
Tests/HTMLKitTests/EnvironmentTests.swift | 30 ++++++++++++++++++
3 files changed, 86 insertions(+)
create mode 100644 Sources/HTMLKit/Framework/Environment/EnvironmentString.swift
diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentString.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentString.swift
new file mode 100644
index 00000000..c80b5840
--- /dev/null
+++ b/Sources/HTMLKit/Framework/Environment/EnvironmentString.swift
@@ -0,0 +1,31 @@
+/// A type that represents an environment string
+@_documentation(visibility: internal)
+public struct EnvironmentString: ExpressibleByStringLiteral, ExpressibleByStringInterpolation, Content {
+
+ internal var values: [Content] = []
+
+ public init(stringLiteral: String) {
+ self.values.append(stringLiteral)
+ }
+
+ public init(stringInterpolation: StringInterpolation) {
+ self.values.append(contentsOf: stringInterpolation.values)
+ }
+
+ public struct StringInterpolation: StringInterpolationProtocol {
+
+ internal var values: [Content] = []
+
+ public init(literalCapacity: Int, interpolationCount: Int) {
+ values.reserveCapacity(interpolationCount)
+ }
+
+ public mutating func appendLiteral(_ literal: String) {
+ values.append(literal)
+ }
+
+ public mutating func appendInterpolation(_ env: EnvironmentValue) {
+ values.append(env)
+ }
+ }
+}
diff --git a/Sources/HTMLKit/Framework/Rendering/Renderer.swift b/Sources/HTMLKit/Framework/Rendering/Renderer.swift
index 4582fcf6..1e410392 100644
--- a/Sources/HTMLKit/Framework/Rendering/Renderer.swift
+++ b/Sources/HTMLKit/Framework/Rendering/Renderer.swift
@@ -157,6 +157,10 @@ public final class Renderer {
}
}
+ if let envstring = content as? EnvironmentString {
+ result += try render(envstring: envstring)
+ }
+
if let element = content as? String {
result += escape(content: element)
}
@@ -243,6 +247,10 @@ public final class Renderer {
}
}
+ if let envstring = content as? EnvironmentString {
+ result += try render(envstring: envstring)
+ }
+
if let element = content as? String {
result += escape(content: element)
}
@@ -372,6 +380,10 @@ public final class Renderer {
}
}
+ if let envstring = content as? EnvironmentString {
+ result += try render(envstring: envstring)
+ }
+
if let element = content as? String {
result += escape(content: element)
}
@@ -593,6 +605,11 @@ public final class Renderer {
return self.markdown.render(string: escape(content: markdown.raw))
}
+ /// Renders a environment interpolation
+ private func render(envstring: EnvironmentString) throws -> String {
+ return try render(contents: envstring.values)
+ }
+
/// Converts specific charaters into encoded values.
private func escape(attribute value: String) -> String {
@@ -669,6 +686,10 @@ public final class Renderer {
}
}
+ if let envstring = content as? EnvironmentString {
+ result += try render(envstring: envstring)
+ }
+
if let element = content as? String {
result += escape(content: element)
}
@@ -728,4 +749,8 @@ public final class Renderer {
result += "\(element.name)>"
}
+
+ private func render(loop envstring: EnvironmentString, with value: Any, on result: inout String) throws {
+ try render(loop: envstring.values, with: value, on: &result)
+ }
}
diff --git a/Tests/HTMLKitTests/EnvironmentTests.swift b/Tests/HTMLKitTests/EnvironmentTests.swift
index 2cb61169..b5161346 100644
--- a/Tests/HTMLKitTests/EnvironmentTests.swift
+++ b/Tests/HTMLKitTests/EnvironmentTests.swift
@@ -336,4 +336,34 @@ final class EnvironmentTests: XCTestCase {
"""
)
}
+
+ /// Tests the string interpolation with an environment value
+ ///
+ /// The renderer is expected to render the string correctly
+ func testStringInterpolationWithEnvironment() throws {
+
+ struct TestObject: ViewModel {
+
+ let name: String = "Jane"
+ }
+
+ struct TestView: View {
+
+ @EnvironmentObject(TestObject.self)
+ var object
+
+ var body: Content {
+ Paragraph {
+ EnvironmentString("Hello, how are you \(object.name)?")
+ }
+ .environment(object: TestObject())
+ }
+ }
+
+ XCTAssertEqual(try renderer.render(view: TestView()),
+ """
+ Hello, how are you Jane?
+ """
+ )
+ }
}
From f6ecd7ac190f43c619a492dbd44d44e531cd25bb Mon Sep 17 00:00:00 2001
From: Mattes Mohr
Date: Mon, 6 Jan 2025 09:55:21 +0100
Subject: [PATCH 08/15] Make environmentvalue conditionable
---
.../Environment/EnvironmentValue.swift | 2 +-
.../Framework/Environment/Negation.swift | 23 +++++++++
.../Framework/Rendering/Renderer.swift | 47 +++++++++++++++++++
Tests/HTMLKitTests/EnvironmentTests.swift | 15 +++++-
4 files changed, 85 insertions(+), 2 deletions(-)
create mode 100644 Sources/HTMLKit/Framework/Environment/Negation.swift
diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift
index 1a62dbd9..5e36e159 100644
--- a/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift
+++ b/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift
@@ -23,7 +23,7 @@ public struct EnvironmentValue: Content {
}
}
-extension EnvironmentValue {
+extension EnvironmentValue: Conditionable {
/// Concat environment value with environment value
public static func + (lhs: Content, rhs: Self) -> Content {
diff --git a/Sources/HTMLKit/Framework/Environment/Negation.swift b/Sources/HTMLKit/Framework/Environment/Negation.swift
new file mode 100644
index 00000000..129d7f42
--- /dev/null
+++ b/Sources/HTMLKit/Framework/Environment/Negation.swift
@@ -0,0 +1,23 @@
+/// A type thats represents an invert condition
+@_documentation(visibility: internal)
+public struct Negation: Conditionable {
+
+ /// The left-hand side conditional
+ public let lhs: Conditionable
+}
+
+
+/// Creates a invert condition
+///
+/// ```swift
+/// Environment.when(!value) {
+/// }
+/// ```
+///
+/// - Parameters:
+/// - lhs: The left-hand side conditional
+///
+/// - Returns: A invert
+public prefix func ! (lhs: Conditionable) -> Negation {
+ return Negation(lhs: lhs)
+}
diff --git a/Sources/HTMLKit/Framework/Rendering/Renderer.swift b/Sources/HTMLKit/Framework/Rendering/Renderer.swift
index 1e410392..a7cf4d05 100644
--- a/Sources/HTMLKit/Framework/Rendering/Renderer.swift
+++ b/Sources/HTMLKit/Framework/Rendering/Renderer.swift
@@ -479,10 +479,33 @@ public final class Renderer {
var result = false
+ if let value = statement.compound as? EnvironmentValue {
+
+ guard let parent = self.environment.retrieve(for: value.parentPath) else {
+ throw Errors.environmentObjectNotFound
+ }
+
+ guard let value = parent[keyPath: value.valuePath] else {
+ throw Errors.environmentValueNotFound
+ }
+
+ guard let boolValue = value as? Bool else {
+ throw Errors.unableToCastEnvironmentValue
+ }
+
+ if boolValue {
+ result = true
+ }
+ }
+
if let condition = statement.compound as? Condition {
result = try render(condition: condition)
}
+ if let negation = statement.compound as? Negation {
+ result = try render(negation: negation)
+ }
+
if let relation = statement.compound as? Relation {
result = try render(relation: relation)
}
@@ -494,6 +517,30 @@ public final class Renderer {
return try render(contents: statement.second)
}
+ private func render(negation: Negation) throws -> Bool {
+
+ if let value = negation.lhs as? EnvironmentValue {
+
+ guard let parent = self.environment.retrieve(for: value.parentPath) else {
+ throw Errors.environmentObjectNotFound
+ }
+
+ guard let value = parent[keyPath: value.valuePath] else {
+ throw Errors.environmentValueNotFound
+ }
+
+ guard let boolValue = value as? Bool else {
+ throw Errors.unableToCastEnvironmentValue
+ }
+
+ if !boolValue {
+ return true
+ }
+ }
+
+ return false
+ }
+
private func render(relation: Relation) throws -> Bool {
switch relation.term {
diff --git a/Tests/HTMLKitTests/EnvironmentTests.swift b/Tests/HTMLKitTests/EnvironmentTests.swift
index b5161346..eb1b7f8b 100644
--- a/Tests/HTMLKitTests/EnvironmentTests.swift
+++ b/Tests/HTMLKitTests/EnvironmentTests.swift
@@ -82,6 +82,7 @@ final class EnvironmentTests: XCTestCase {
let firstName: String = "Jane"
let lastName: String = "Doe"
let age: Int = 40
+ let loggedIn: Bool = true
}
struct TestView: View {
@@ -92,6 +93,18 @@ final class EnvironmentTests: XCTestCase {
var body: Content {
Paragraph {
+ // Should return true
+ Environment.when(object.loggedIn) {
+ "True"
+ }
+
+ // Should return false
+ Environment.when(!object.loggedIn) {
+ "True"
+ } then: {
+ "False"
+ }
+
// Should return false
Environment.when(object.firstName == "John") {
"True"
@@ -146,7 +159,7 @@ final class EnvironmentTests: XCTestCase {
XCTAssertEqual(try renderer.render(view: TestView()),
"""
- FalseTrueTrueFalseFalseTrueTrueFalse
+ TrueFalseFalseTrueTrueFalseFalseTrueTrueFalse
"""
)
}
From 144cc226d50f50085673d8ad0a409b9081e5e6c1 Mon Sep 17 00:00:00 2001
From: Mattes Mohr
Date: Wed, 8 Jan 2025 21:08:46 +0100
Subject: [PATCH 09/15] Refactor the code a bit
---
.../Framework/Environment/Condition.swift | 6 +-
.../Framework/Environment/Environment.swift | 198 +++++++++++++++
.../Environment/EnvironmentModifier.swift | 6 +-
.../Environment/EnvironmentValue.swift | 7 +-
.../Framework/Environment/Negation.swift | 14 +-
.../Framework/Environment/Relation.swift | 19 +-
.../Framework/Environment/Sequence.swift | 7 +-
.../Framework/Environment/Statement.swift | 8 +-
.../Framework/Rendering/Renderer.swift | 239 ++++--------------
.../Extensions/Vapor+HTMLKit.swift | 4 +-
Tests/HTMLKitTests/EnvironmentTests.swift | 34 +--
11 files changed, 309 insertions(+), 233 deletions(-)
diff --git a/Sources/HTMLKit/Framework/Environment/Condition.swift b/Sources/HTMLKit/Framework/Environment/Condition.swift
index 1bbece69..781e78b4 100644
--- a/Sources/HTMLKit/Framework/Environment/Condition.swift
+++ b/Sources/HTMLKit/Framework/Environment/Condition.swift
@@ -19,13 +19,13 @@ public struct Condition: Conditionable {
}
/// The left-hand side value
- public let lhs: EnvironmentValue
+ internal let lhs: EnvironmentValue
/// The right-hand side value to test against
- public let rhs: any Comparable
+ internal let rhs: any Comparable
/// The comparison to perfom
- public let comparison: Comparison
+ internal let comparison: Comparison
/// Initializes a condition
///
diff --git a/Sources/HTMLKit/Framework/Environment/Environment.swift b/Sources/HTMLKit/Framework/Environment/Environment.swift
index b8720657..f4b53149 100644
--- a/Sources/HTMLKit/Framework/Environment/Environment.swift
+++ b/Sources/HTMLKit/Framework/Environment/Environment.swift
@@ -3,8 +3,43 @@ import Foundation
/// A class that represents the environment
///
/// The environment provides storage for various settings used by the renderer
+@_documentation(visibility: internal)
public final class Environment {
+ /// An enumeration of possible rendering errors.
+ public enum Errors: Error {
+
+ /// Indicates a casting error.
+ case unableToCastEnvironmentValue
+
+ /// Indicates a wrong environment key.
+ case unindendedEnvironmentKey
+
+ /// Indicates a missing environment object.
+ case environmentObjectNotFound
+
+ /// Indicates a missing environment value.
+ case environmentValueNotFound
+
+ /// A brief error description.
+ public var description: String {
+
+ switch self {
+ case .unableToCastEnvironmentValue:
+ return "Unable to cast the environment value."
+
+ case .unindendedEnvironmentKey:
+ return "The environment key is not indended."
+
+ case .environmentValueNotFound:
+ return "Unable to retrieve environment value."
+
+ case .environmentObjectNotFound:
+ return "Unable to retrieve environment object."
+ }
+ }
+ }
+
/// The storage of the environment
private var storage: [AnyKeyPath: Any]
@@ -63,6 +98,169 @@ public final class Environment {
public func upsert(_ value: T, for path: AnyKeyPath) {
storage[path] = value
}
+
+ /// Resolves an environment value
+ ///
+ /// - Parameter value: The environment value to resolve
+ ///
+ /// - Returns: The resolved environment value
+ internal func resolve(value: EnvironmentValue) throws -> Any {
+
+ guard let parent = retrieve(for: value.parentPath) else {
+ throw Errors.environmentObjectNotFound
+ }
+
+ guard let value = parent[keyPath: value.valuePath] else {
+ throw Errors.environmentValueNotFound
+ }
+
+ return value
+ }
+
+ /// Evaluates an environment value
+ ///
+ /// - Parameter value: The value to evaluate
+ ///
+ /// - Returns: The result of evaluation
+ internal func evaluate(value: EnvironmentValue) throws -> Bool {
+
+ let value = try resolve(value: value)
+
+ guard let boolValue = value as? Bool else {
+ throw Errors.unableToCastEnvironmentValue
+ }
+
+ return boolValue
+ }
+
+ /// Evaluates an environment relation
+ ///
+ /// - Parameter relation: The relation to evaluate
+ ///
+ /// - Returns: The result of the evaluation
+ internal func evaluate(relation: Relation) throws -> Bool {
+
+ switch relation.term {
+ case .conjunction:
+
+ var result = true
+
+ if let condition = relation.lhs as? Condition {
+ result = try evaluate(condition: condition)
+ }
+
+ if let relation = relation.lhs as? Relation {
+ result = try evaluate(relation: relation)
+ }
+
+ if let negation = relation.lhs as? Negation {
+ result = try evaluate(negation: negation)
+ }
+
+ if let value = relation.lhs as? EnvironmentValue {
+ result = try evaluate(value: value)
+ }
+
+ if !result {
+ /// Bail early if the first result already is false
+ return result
+ }
+
+ if let condition = relation.rhs as? Condition {
+ result = try evaluate(condition: condition)
+ }
+
+ if let relation = relation.rhs as? Relation {
+ result = try evaluate(relation: relation)
+ }
+
+ if let negation = relation.lhs as? Negation {
+ result = try evaluate(negation: negation)
+ }
+
+ if let value = relation.lhs as? EnvironmentValue {
+ result = try evaluate(value: value)
+ }
+
+ return result
+
+ case .disjunction:
+
+ var result = false
+
+ if let condition = relation.lhs as? Condition {
+ result = try evaluate(condition: condition)
+ }
+
+ if let relation = relation.lhs as? Relation {
+ result = try evaluate(relation: relation)
+ }
+
+ if let negation = relation.lhs as? Negation {
+ result = try evaluate(negation: negation)
+ }
+
+ if let value = relation.lhs as? EnvironmentValue {
+ result = try evaluate(value: value)
+ }
+
+ if result {
+ /// Bail early if the first result is already true
+ return result
+ }
+
+ if let condition = relation.rhs as? Condition {
+ result = try evaluate(condition: condition)
+ }
+
+ if let relation = relation.rhs as? Relation {
+ result = try evaluate(relation: relation)
+ }
+
+ if let negation = relation.lhs as? Negation {
+ result = try evaluate(negation: negation)
+ }
+
+ if let value = relation.lhs as? EnvironmentValue {
+ result = try evaluate(value: value)
+ }
+
+ return result
+ }
+ }
+
+ /// Evaluates an environment condition
+ ///
+ /// - Parameter condition: The condition to evaluate
+ ///
+ /// - Returns: The result of the evaluation
+ internal func evaluate(condition: Condition) throws -> Bool {
+
+ let value = try resolve(value: condition.lhs)
+
+ switch condition.comparison {
+ case .equal:
+ return condition.rhs.equal(value)
+
+ case .greater:
+ return condition.rhs.greater(value)
+
+ case .unequal:
+ return condition.rhs.unequal(value)
+
+ case .less:
+ return condition.rhs.less(value)
+ }
+ }
+
+ /// Evaluates an environment negation
+ ///
+ /// - Parameter negation: The negation to evaluate
+ ///
+ /// - Returns: The result of the evaluation
+ internal func evaluate(negation: Negation) throws -> Bool {
+ return try !evaluate(value: negation.value)
+ }
}
extension Environment {
diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift
index cca4b822..46438175 100644
--- a/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift
+++ b/Sources/HTMLKit/Framework/Environment/EnvironmentModifier.swift
@@ -5,13 +5,13 @@
public struct EnvironmentModifier: Content {
/// The environment key
- internal var key: AnyKeyPath
+ internal let key: AnyKeyPath
/// The environment value
- internal var value: Any?
+ internal let value: Any?
/// The sub-content
- internal var content: [Content]
+ internal let content: [Content]
/// Initializes an environment modifier
///
diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift
index 5e36e159..8727046c 100644
--- a/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift
+++ b/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift
@@ -3,20 +3,21 @@ import Foundation
/// A type that serves as a placeholder for an environment value
///
/// The placeholder will be evaluated and resolved by the renderer when needed.
+@_documentation(visibility: internal)
public struct EnvironmentValue: Content {
/// The path of the values parent
- internal var parentPath: AnyKeyPath
+ internal let parentPath: AnyKeyPath
/// The path of the value
- internal var valuePath: AnyKeyPath
+ internal let valuePath: AnyKeyPath
/// Initializes a environment value
///
/// - Parameters:
/// - parentPath: The key path of the parent
/// - valuePath: The key path of the value
- public init(parentPath: AnyKeyPath, valuePath: AnyKeyPath) {
+ internal init(parentPath: AnyKeyPath, valuePath: AnyKeyPath) {
self.parentPath = parentPath
self.valuePath = valuePath
diff --git a/Sources/HTMLKit/Framework/Environment/Negation.swift b/Sources/HTMLKit/Framework/Environment/Negation.swift
index 129d7f42..8d333073 100644
--- a/Sources/HTMLKit/Framework/Environment/Negation.swift
+++ b/Sources/HTMLKit/Framework/Environment/Negation.swift
@@ -3,7 +3,15 @@
public struct Negation: Conditionable {
/// The left-hand side conditional
- public let lhs: Conditionable
+ internal let value: EnvironmentValue
+
+ /// Initializes the negation
+ ///
+ /// - Parameter lhs: The conditional to evaluate
+ public init(value: EnvironmentValue) {
+
+ self.value = value
+ }
}
@@ -18,6 +26,6 @@ public struct Negation: Conditionable {
/// - lhs: The left-hand side conditional
///
/// - Returns: A invert
-public prefix func ! (lhs: Conditionable) -> Negation {
- return Negation(lhs: lhs)
+public prefix func ! (value: EnvironmentValue) -> Negation {
+ return Negation(value: value)
}
diff --git a/Sources/HTMLKit/Framework/Environment/Relation.swift b/Sources/HTMLKit/Framework/Environment/Relation.swift
index 10a1d0b6..78993fdc 100644
--- a/Sources/HTMLKit/Framework/Environment/Relation.swift
+++ b/Sources/HTMLKit/Framework/Environment/Relation.swift
@@ -17,13 +17,26 @@ public struct Relation: Conditionable {
}
/// The logical term specifying the relation
- public let term: Term
+ internal let term: Term
/// The left-hand side conditional
- public let lhs: Conditionable
+ internal let lhs: Conditionable
/// The right-hand side conditional
- public let rhs: Conditionable
+ internal let rhs: Conditionable
+
+ /// Initializes a relation
+ ///
+ /// - Parameters:
+ /// - term: The term on which the relation acts on
+ /// - lhs: The left-hand side conditional
+ /// - rhs: The right-hand side conditional to test against
+ public init(term: Term, lhs: Conditionable, rhs: Conditionable) {
+
+ self.term = term
+ self.lhs = lhs
+ self.rhs = rhs
+ }
}
/// Creates a conjunctional relation between two conditionals
diff --git a/Sources/HTMLKit/Framework/Environment/Sequence.swift b/Sources/HTMLKit/Framework/Environment/Sequence.swift
index 800986e9..b2498712 100644
--- a/Sources/HTMLKit/Framework/Environment/Sequence.swift
+++ b/Sources/HTMLKit/Framework/Environment/Sequence.swift
@@ -1,18 +1,19 @@
/// A type representing a loop rendering content within the environment
+@_documentation(visibility: internal)
public struct Sequence: Content {
/// The environment value of the sequence
- let value: EnvironmentValue
+ internal let value: EnvironmentValue
/// The accumulated content
- let content: [Content]
+ internal let content: [Content]
/// Initializes a loop
///
/// - Parameters:
/// - value: The environment value to retrieve the sequence from
/// - content: The content to render for each item in the sequence
- init(value: EnvironmentValue, content: [Content]) {
+ public init(value: EnvironmentValue, content: [Content]) {
self.value = value
self.content = content
diff --git a/Sources/HTMLKit/Framework/Environment/Statement.swift b/Sources/HTMLKit/Framework/Environment/Statement.swift
index e58d5b1a..09c6c1e1 100644
--- a/Sources/HTMLKit/Framework/Environment/Statement.swift
+++ b/Sources/HTMLKit/Framework/Environment/Statement.swift
@@ -3,13 +3,13 @@
public struct Statement: Content {
/// The compound condition
- let compound: Conditionable
+ internal let compound: Conditionable
/// The first statement
- let first: [Content]
+ internal let first: [Content]
/// The second statement
- let second: [Content]
+ internal let second: [Content]
/// Initializes a statement
///
@@ -17,7 +17,7 @@ public struct Statement: Content {
/// - compound: The compound of conditionals
/// - first: The statement to execute if conditionals are true
/// - second: The statement to execute if conditionals are false
- init(compound: Conditionable, first: [Content], second: [Content]) {
+ public init(compound: Conditionable, first: [Content], second: [Content]) {
self.compound = compound
self.first = first
diff --git a/Sources/HTMLKit/Framework/Rendering/Renderer.swift b/Sources/HTMLKit/Framework/Rendering/Renderer.swift
index a7cf4d05..eacde90a 100644
--- a/Sources/HTMLKit/Framework/Rendering/Renderer.swift
+++ b/Sources/HTMLKit/Framework/Rendering/Renderer.swift
@@ -9,40 +9,6 @@ import Logging
@_documentation(visibility: internal)
public final class Renderer {
-
- /// An enumeration of possible rendering errors.
- public enum Errors: Error {
-
- /// Indicates a casting error.
- case unableToCastEnvironmentValue
-
- /// Indicates a wrong environment key.
- case unindendedEnvironmentKey
-
- /// Indicates a missing environment object.
- case environmentObjectNotFound
-
- /// Indicates a missing environment value.
- case environmentValueNotFound
-
- /// A brief error description.
- public var description: String {
-
- switch self {
- case .unableToCastEnvironmentValue:
- return "Unable to cast the environment value."
-
- case .unindendedEnvironmentKey:
- return "The environment key is not indended."
-
- case .environmentValueNotFound:
- return "Unable to retrieve environment value."
-
- case .environmentObjectNotFound:
- return "Unable to retrieve environment object."
- }
- }
- }
/// The context environment
private var environment: Environment
@@ -137,10 +103,7 @@ public final class Renderer {
}
if let statement = content as? Statement {
-
- if let yield = try render(statement: statement) {
- result += yield
- }
+ result += try render(statement: statement)
}
if let loop = content as? Sequence {
@@ -231,10 +194,7 @@ public final class Renderer {
}
if let statement = content as? Statement {
-
- if let yield = try render(statement: statement) {
- result += yield
- }
+ result += try render(statement: statement)
}
if let string = content as? MarkdownString {
@@ -360,10 +320,7 @@ public final class Renderer {
}
if let statement = content as? Statement {
-
- if let yield = try render(statement: statement) {
- result += yield
- }
+ result += try render(statement: statement)
}
if let loop = content as? Sequence {
@@ -439,13 +396,7 @@ public final class Renderer {
/// Renders a environment value.
private func render(value: EnvironmentValue) throws -> String {
- guard let parent = self.environment.retrieve(for: value.parentPath) else {
- throw Errors.environmentObjectNotFound
- }
-
- guard let value = parent[keyPath: value.valuePath] else {
- throw Errors.environmentValueNotFound
- }
+ let value = try self.environment.resolve(value: value)
switch value {
case let floatValue as Float:
@@ -470,44 +421,29 @@ public final class Renderer {
return formatter.string(from: dateValue)
default:
- throw Errors.unableToCastEnvironmentValue
+ throw Environment.Errors.unableToCastEnvironmentValue
}
}
/// Renders a environment statement
- private func render(statement: Statement) throws -> String? {
+ private func render(statement: Statement) throws -> String {
var result = false
- if let value = statement.compound as? EnvironmentValue {
-
- guard let parent = self.environment.retrieve(for: value.parentPath) else {
- throw Errors.environmentObjectNotFound
- }
-
- guard let value = parent[keyPath: value.valuePath] else {
- throw Errors.environmentValueNotFound
- }
-
- guard let boolValue = value as? Bool else {
- throw Errors.unableToCastEnvironmentValue
- }
-
- if boolValue {
- result = true
- }
- }
-
if let condition = statement.compound as? Condition {
- result = try render(condition: condition)
+ result = try environment.evaluate(condition: condition)
}
if let negation = statement.compound as? Negation {
- result = try render(negation: negation)
+ result = try environment.evaluate(negation: negation)
}
if let relation = statement.compound as? Relation {
- result = try render(relation: relation)
+ result = try environment.evaluate(relation: relation)
+ }
+
+ if let value = statement.compound as? EnvironmentValue {
+ result = try environment.evaluate(value: value)
}
if result {
@@ -517,114 +453,6 @@ public final class Renderer {
return try render(contents: statement.second)
}
- private func render(negation: Negation) throws -> Bool {
-
- if let value = negation.lhs as? EnvironmentValue {
-
- guard let parent = self.environment.retrieve(for: value.parentPath) else {
- throw Errors.environmentObjectNotFound
- }
-
- guard let value = parent[keyPath: value.valuePath] else {
- throw Errors.environmentValueNotFound
- }
-
- guard let boolValue = value as? Bool else {
- throw Errors.unableToCastEnvironmentValue
- }
-
- if !boolValue {
- return true
- }
- }
-
- return false
- }
-
- private func render(relation: Relation) throws -> Bool {
-
- switch relation.term {
- case .conjunction:
-
- var result = true
-
- if let condition = relation.lhs as? Condition {
- result = try render(condition: condition)
- }
-
- if let relation = relation.lhs as? Relation {
- result = try render(relation: relation)
- }
-
- if !result {
- /// Bail early if the first result already is false
- return result
- }
-
- if let condition = relation.rhs as? Condition {
- result = try render(condition: condition)
- }
-
- if let relation = relation.rhs as? Relation {
- result = try render(relation: relation)
- }
-
- return result
-
- case .disjunction:
-
- var result = false
-
- if let condition = relation.lhs as? Condition {
- result = try render(condition: condition)
- }
-
- if let relation = relation.lhs as? Relation {
- result = try render(relation: relation)
- }
-
- if result {
- /// Bail early if the first result is already true
- return result
- }
-
- if let condition = relation.rhs as? Condition {
- result = try render(condition: condition)
- }
-
- if let relation = relation.rhs as? Relation {
- result = try render(relation: relation)
- }
-
- return result
- }
- }
-
- private func render(condition: Condition) throws -> Bool {
-
- guard let parent = self.environment.retrieve(for: condition.lhs.parentPath) else {
- throw Errors.environmentObjectNotFound
- }
-
- guard let value = parent[keyPath: condition.lhs.valuePath] else {
- throw Errors.environmentValueNotFound
- }
-
- switch condition.comparison {
- case .equal:
- return condition.rhs.equal(value)
-
- case .greater:
- return condition.rhs.greater(value)
-
- case .unequal:
- return condition.rhs.unequal(value)
-
- case .less:
- return condition.rhs.less(value)
- }
- }
-
/// Renders the node attributes.
private func render(attributes: OrderedDictionary) throws -> String {
@@ -680,25 +508,34 @@ public final class Renderer {
return value
}
+ /// Renders an environment loop
+ ///
+ /// - Parameter loop: The loop to resolve
+ ///
+ /// - Returns: The rendered loop
private func render(loop: Sequence) throws -> String {
- guard let parent = self.environment.retrieve(for: loop.value.parentPath) else {
- throw Errors.environmentObjectNotFound
- }
-
- guard let values = parent[keyPath: loop.value.valuePath] as? (any Swift.Sequence) else {
- throw Errors.environmentValueNotFound
+ let value = try environment.resolve(value: loop.value)
+
+ guard let sequence = value as? (any Swift.Sequence) else {
+ throw Environment.Errors.unableToCastEnvironmentValue
}
var result = ""
- for value in values {
+ for value in sequence {
try render(loop: loop.content, with: value, on: &result)
}
return result
}
+ /// Renders the content within an environment loop
+ ///
+ /// - Parameters:
+ /// - contents: The content to render
+ /// - value: The value to resolve the environment value with
+ /// - result: The rendered content
private func render(loop contents: [Content], with value: Any, on result: inout String) throws {
for content in contents {
@@ -763,6 +600,12 @@ public final class Renderer {
}
}
+ /// Renders a content element within an environment loop
+ ///
+ /// - Parameters:
+ /// - element: The element to render
+ /// - value: The value to resolve the environment value with
+ /// - result: The result
private func render(loop element: some ContentNode, with value: Any, on result: inout String) throws {
result += "<\(element.name)"
@@ -780,6 +623,12 @@ public final class Renderer {
result += "\(element.name)>"
}
+ /// Renders a custom element within an environment loop
+ ///
+ /// - Parameters:
+ /// - element: The element to render
+ /// - value: The value to resolve the environment value with
+ /// - result: The rendered content
private func render(loop element: some CustomNode, with value: Any, on result: inout String) throws {
result += "<\(element.name)"
@@ -797,6 +646,12 @@ public final class Renderer {
result += "\(element.name)>"
}
+ /// Renders an environment string within a environment loop
+ ///
+ /// - Parameters:
+ /// - envstring: The environment string to render
+ /// - value: The raw value to resolve the environment value with
+ /// - result: The result
private func render(loop envstring: EnvironmentString, with value: Any, on result: inout String) throws {
try render(loop: envstring.values, with: value, on: &result)
}
diff --git a/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift b/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift
index b768eb40..311adc2c 100644
--- a/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift
+++ b/Sources/HTMLKitVapor/Extensions/Vapor+HTMLKit.swift
@@ -119,7 +119,7 @@ extension Request {
}
}
-extension HTMLKit.Renderer.Errors: AbortError {
+extension HTMLKit.Environment.Errors: AbortError {
@_implements(AbortError, reason)
public var abortReason: String { self.description }
@@ -127,7 +127,7 @@ extension HTMLKit.Renderer.Errors: AbortError {
public var status: HTTPResponseStatus { .internalServerError }
}
-extension HTMLKit.Renderer.Errors: DebuggableError {
+extension HTMLKit.Environment.Errors: DebuggableError {
@_implements(DebuggableError, reason)
public var debuggableReason: String { self.description }
diff --git a/Tests/HTMLKitTests/EnvironmentTests.swift b/Tests/HTMLKitTests/EnvironmentTests.swift
index eb1b7f8b..15c0cefa 100644
--- a/Tests/HTMLKitTests/EnvironmentTests.swift
+++ b/Tests/HTMLKitTests/EnvironmentTests.swift
@@ -12,14 +12,14 @@ final class EnvironmentTests: XCTestCase {
struct FamilyObject: ViewModel {
- let name: String = "Doe"
- let father: FatherObject = FatherObject()
+ let name = "Doe"
+ let father = FatherObject()
}
struct FatherObject: ViewModel {
- let avatar: String = "john_doe.jpeg"
- let name: String = "John"
+ let avatar = "john_doe.jpeg"
+ let name = "John"
}
struct ParentView: View {
@@ -79,10 +79,10 @@ final class EnvironmentTests: XCTestCase {
struct TestObject: ViewModel {
- let firstName: String = "Jane"
- let lastName: String = "Doe"
- let age: Int = 40
- let loggedIn: Bool = true
+ let firstName = "Jane"
+ let lastName = "Doe"
+ let age = 40
+ let loggedIn = true
}
struct TestView: View {
@@ -169,9 +169,9 @@ final class EnvironmentTests: XCTestCase {
struct TestObject: ViewModel {
- let firstName: String = "Jane"
- let lastName: String = "Doe"
- let age: Int = 40
+ let firstName = "Jane"
+ let lastName = "Doe"
+ let age = 40
}
struct TestView: View {
@@ -226,9 +226,9 @@ final class EnvironmentTests: XCTestCase {
struct TestObject: ViewModel {
- let firstName: String = "Jane"
- let lastName: String = "Doe"
- let age: Int = 40
+ let firstName = "Jane"
+ let lastName = "Doe"
+ let age = 40
}
struct TestView: View {
@@ -319,8 +319,8 @@ final class EnvironmentTests: XCTestCase {
struct TestObject: ViewModel {
- let name: String = "Jane"
- let children: [String] = ["Janek", "Janet"]
+ let name = "Jane"
+ let children = ["Janek", "Janet"]
}
struct TestView: View {
@@ -357,7 +357,7 @@ final class EnvironmentTests: XCTestCase {
struct TestObject: ViewModel {
- let name: String = "Jane"
+ let name = "Jane"
}
struct TestView: View {
From 801b1ed77dfc207610a56fe81bc38e49517eac21 Mon Sep 17 00:00:00 2001
From: Mattes Mohr
Date: Wed, 8 Jan 2025 21:28:47 +0100
Subject: [PATCH 10/15] Avoid type casting by using a type erasure
---
.../Framework/Environment/Condition.swift | 4 +-
.../Framework/Environment/Conditionable.swift | 4 -
.../Framework/Environment/Conditional.swift | 16 ++++
.../Framework/Environment/Environment.swift | 93 ++++++++++---------
.../Environment/EnvironmentValue.swift | 18 ++--
.../Framework/Environment/Negation.swift | 13 ++-
.../Framework/Environment/Relation.swift | 83 +++++++++++++++--
.../Framework/Environment/Statement.swift | 4 +-
.../Framework/Rendering/Renderer.swift | 24 ++---
9 files changed, 170 insertions(+), 89 deletions(-)
delete mode 100644 Sources/HTMLKit/Framework/Environment/Conditionable.swift
create mode 100644 Sources/HTMLKit/Framework/Environment/Conditional.swift
diff --git a/Sources/HTMLKit/Framework/Environment/Condition.swift b/Sources/HTMLKit/Framework/Environment/Condition.swift
index 781e78b4..2fa8c671 100644
--- a/Sources/HTMLKit/Framework/Environment/Condition.swift
+++ b/Sources/HTMLKit/Framework/Environment/Condition.swift
@@ -1,6 +1,6 @@
-/// A type representing a condition that compares an environment value against another value
+/// A type representing a conditional that compares an environment value against another value
@_documentation(visibility: internal)
-public struct Condition: Conditionable {
+public struct Condition {
/// A enumeration of potential comparison
public enum Comparison {
diff --git a/Sources/HTMLKit/Framework/Environment/Conditionable.swift b/Sources/HTMLKit/Framework/Environment/Conditionable.swift
deleted file mode 100644
index 772eb7d3..00000000
--- a/Sources/HTMLKit/Framework/Environment/Conditionable.swift
+++ /dev/null
@@ -1,4 +0,0 @@
-/// A type that defines a conditonal value which will be evualuated by the renderer.
-@_documentation(visibility: internal)
-public protocol Conditionable {
-}
diff --git a/Sources/HTMLKit/Framework/Environment/Conditional.swift b/Sources/HTMLKit/Framework/Environment/Conditional.swift
new file mode 100644
index 00000000..6b7957c2
--- /dev/null
+++ b/Sources/HTMLKit/Framework/Environment/Conditional.swift
@@ -0,0 +1,16 @@
+/// A type that defines a conditonal value which will be evualuated by the renderer.
+@_documentation(visibility: internal)
+public indirect enum Conditional {
+
+ /// Holds an relation
+ case relation(Relation)
+
+ /// Holds a condition
+ case condition(Condition)
+
+ /// Holds a negation
+ case negation(Negation)
+
+ /// Holds a value
+ case value(EnvironmentValue)
+}
diff --git a/Sources/HTMLKit/Framework/Environment/Environment.swift b/Sources/HTMLKit/Framework/Environment/Environment.swift
index f4b53149..91a12605 100644
--- a/Sources/HTMLKit/Framework/Environment/Environment.swift
+++ b/Sources/HTMLKit/Framework/Environment/Environment.swift
@@ -145,40 +145,36 @@ public final class Environment {
var result = true
- if let condition = relation.lhs as? Condition {
+ switch relation.lhs {
+ case .condition(let condition):
result = try evaluate(condition: condition)
- }
-
- if let relation = relation.lhs as? Relation {
+
+ case .relation(let relation):
result = try evaluate(relation: relation)
- }
-
- if let negation = relation.lhs as? Negation {
+
+ case .negation(let negation):
result = try evaluate(negation: negation)
- }
-
- if let value = relation.lhs as? EnvironmentValue {
+
+ case .value(let value):
result = try evaluate(value: value)
}
if !result {
- /// Bail early if the first result already is false
+ // Bail early if the first result already is false
return result
}
-
- if let condition = relation.rhs as? Condition {
- result = try evaluate(condition: condition)
- }
- if let relation = relation.rhs as? Relation {
+ switch relation.rhs {
+ case .condition(let condition):
+ result = try evaluate(condition: condition)
+
+ case .relation(let relation):
result = try evaluate(relation: relation)
- }
-
- if let negation = relation.lhs as? Negation {
+
+ case .negation(let negation):
result = try evaluate(negation: negation)
- }
-
- if let value = relation.lhs as? EnvironmentValue {
+
+ case .value(let value):
result = try evaluate(value: value)
}
@@ -188,40 +184,36 @@ public final class Environment {
var result = false
- if let condition = relation.lhs as? Condition {
+ switch relation.lhs {
+ case .condition(let condition):
result = try evaluate(condition: condition)
- }
-
- if let relation = relation.lhs as? Relation {
+
+ case .relation(let relation):
result = try evaluate(relation: relation)
- }
-
- if let negation = relation.lhs as? Negation {
+
+ case .negation(let negation):
result = try evaluate(negation: negation)
- }
-
- if let value = relation.lhs as? EnvironmentValue {
+
+ case .value(let value):
result = try evaluate(value: value)
}
if result {
- /// Bail early if the first result is already true
+ // Bail early if the first result is already true
return result
}
- if let condition = relation.rhs as? Condition {
+ switch relation.rhs {
+ case .condition(let condition):
result = try evaluate(condition: condition)
- }
-
- if let relation = relation.rhs as? Relation {
+
+ case .relation(let relation):
result = try evaluate(relation: relation)
- }
-
- if let negation = relation.lhs as? Negation {
+
+ case .negation(let negation):
result = try evaluate(negation: negation)
- }
-
- if let value = relation.lhs as? EnvironmentValue {
+
+ case .value(let value):
result = try evaluate(value: value)
}
@@ -272,7 +264,18 @@ extension Environment {
/// - content: The content for the true statement
///
/// - Returns: A environment condition
- public static func when(_ condition: Conditionable, @ContentBuilder content: () -> [Content]) -> Statement {
+ public static func when(_ condition: EnvironmentValue, @ContentBuilder content: () -> [Content]) -> Statement {
+ return Statement(compound: .value(condition), first: content(), second: [])
+ }
+
+ /// Evaluates one condition
+ ///
+ /// - Parameters:
+ /// - condition: The condition to evaluate
+ /// - content: The content for the true statement
+ ///
+ /// - Returns: A environment condition
+ public static func when(_ condition: Conditional, @ContentBuilder content: () -> [Content]) -> Statement {
return Statement(compound: condition, first: content(), second: [])
}
@@ -284,7 +287,7 @@ extension Environment {
/// - then: The content for the false statement
///
/// - Returns: A environment condition
- public static func when(_ condition: Conditionable, @ContentBuilder content: () -> [Content], @ContentBuilder then: () -> [Content]) -> Statement {
+ public static func when(_ condition: Conditional, @ContentBuilder content: () -> [Content], @ContentBuilder then: () -> [Content]) -> Statement {
return Statement(compound: condition, first: content(), second: then())
}
diff --git a/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift b/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift
index 8727046c..06afbc0f 100644
--- a/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift
+++ b/Sources/HTMLKit/Framework/Environment/EnvironmentValue.swift
@@ -24,7 +24,7 @@ public struct EnvironmentValue: Content {
}
}
-extension EnvironmentValue: Conditionable {
+extension EnvironmentValue {
/// Concat environment value with environment value
public static func + (lhs: Content, rhs: Self) -> Content {
@@ -34,28 +34,28 @@ extension EnvironmentValue: Conditionable {
/// Compare an environment value with another comparable value
///
/// Makes an unequal evaluation
- public static func != (lhs: Self, rhs: some Comparable) -> Condition {
- return Condition(lhs: lhs, rhs: rhs, comparison: .unequal)
+ public static func != (lhs: Self, rhs: some Comparable) -> Conditional {
+ return .condition(Condition(lhs: lhs, rhs: rhs, comparison: .unequal))
}
/// Compare an environment value with another comparable value
///
/// Makes an equal evaluation
- public static func == (lhs: Self, rhs: some Comparable) -> Condition {
- return Condition(lhs: lhs, rhs: rhs, comparison: .equal)
+ public static func == (lhs: Self, rhs: some Comparable) -> Conditional {
+ return .condition(Condition(lhs: lhs, rhs: rhs, comparison: .equal))
}
/// Compare an environment value with another comparable value
///
/// Makes an less than evaluation
- public static func < (lhs: Self, rhs: some Comparable) -> Condition {
- return Condition(lhs: lhs, rhs: rhs, comparison: .less)
+ public static func < (lhs: Self, rhs: some Comparable) -> Conditional {
+ return .condition(Condition(lhs: lhs, rhs: rhs, comparison: .less))
}
/// Compare an environment value with another comparable value
///
/// Makes an greater than evaluation
- public static func > (lhs: Self, rhs: some Comparable) -> Condition {
- return Condition(lhs: lhs, rhs: rhs, comparison: .greater)
+ public static func > (lhs: Self, rhs: some Comparable) -> Conditional {
+ return .condition(Condition(lhs: lhs, rhs: rhs, comparison: .greater))
}
}
diff --git a/Sources/HTMLKit/Framework/Environment/Negation.swift b/Sources/HTMLKit/Framework/Environment/Negation.swift
index 8d333073..042cad6f 100644
--- a/Sources/HTMLKit/Framework/Environment/Negation.swift
+++ b/Sources/HTMLKit/Framework/Environment/Negation.swift
@@ -1,6 +1,6 @@
-/// A type thats represents an invert condition
+/// A type thats represents an invert conditional
@_documentation(visibility: internal)
-public struct Negation: Conditionable {
+public struct Negation {
/// The left-hand side conditional
internal let value: EnvironmentValue
@@ -14,8 +14,7 @@ public struct Negation: Conditionable {
}
}
-
-/// Creates a invert condition
+/// Creates a invert conditional
///
/// ```swift
/// Environment.when(!value) {
@@ -25,7 +24,7 @@ public struct Negation: Conditionable {
/// - Parameters:
/// - lhs: The left-hand side conditional
///
-/// - Returns: A invert
-public prefix func ! (value: EnvironmentValue) -> Negation {
- return Negation(value: value)
+/// - Returns: A invert conditional
+public prefix func ! (value: EnvironmentValue) -> Conditional {
+ return .negation(Negation(value: value))
}
diff --git a/Sources/HTMLKit/Framework/Environment/Relation.swift b/Sources/HTMLKit/Framework/Environment/Relation.swift
index 78993fdc..fc78dc44 100644
--- a/Sources/HTMLKit/Framework/Environment/Relation.swift
+++ b/Sources/HTMLKit/Framework/Environment/Relation.swift
@@ -1,6 +1,6 @@
/// A type representing the logical relation between two conditionals
@_documentation(visibility: internal)
-public struct Relation: Conditionable {
+public struct Relation {
/// A enumeration of potential logical terms
public enum Term {
@@ -20,10 +20,10 @@ public struct Relation: Conditionable {
internal let term: Term
/// The left-hand side conditional
- internal let lhs: Conditionable
+ internal let lhs: Conditional
/// The right-hand side conditional
- internal let rhs: Conditionable
+ internal let rhs: Conditional
/// Initializes a relation
///
@@ -31,7 +31,7 @@ public struct Relation: Conditionable {
/// - term: The term on which the relation acts on
/// - lhs: The left-hand side conditional
/// - rhs: The right-hand side conditional to test against
- public init(term: Term, lhs: Conditionable, rhs: Conditionable) {
+ public init(term: Term, lhs: Conditional, rhs: Conditional) {
self.term = term
self.lhs = lhs
@@ -40,7 +40,40 @@ public struct Relation: Conditionable {
}
/// Creates a conjunctional relation between two conditionals
-///
+///
+/// ```swift
+/// Environment.when(value > 0 && value) {
+/// }
+/// ```
+///
+/// - Parameters:
+/// - lhs: The left-hand side conditional
+/// - rhs: An environment value
+///
+/// - Returns: A conjunctional relation
+public func && (lhs: Conditional, rhs: EnvironmentValue) -> Conditional {
+ return .relation(Relation(term: .conjunction, lhs: lhs, rhs: .value(rhs)))
+}
+
+/// Creates a conjunctional relation between two conditionals
+///
+/// ```swift
+/// Environment.when(value && value > 0) {
+/// }
+/// ```
+///
+/// - Parameters:
+/// - lhs: The left-hand side conditional
+/// - rhs: An environment value
+///
+/// - Returns: A conjunctional relation
+public func && (lhs: EnvironmentValue, rhs: Conditional) -> Conditional {
+ return .relation(Relation(term: .conjunction, lhs: .value(lhs), rhs: rhs))
+}
+
+
+/// Creates a conjunctional relation between two conditionals
+///
/// ```swift
/// Environment.when(value > 0 && value < 2) {
/// }
@@ -51,8 +84,40 @@ public struct Relation: Conditionable {
/// - rhs: The right-hand side conditional
///
/// - Returns: A conjunctional relation
-public func && (lhs: Conditionable, rhs: Conditionable) -> Relation {
- return Relation(term: .conjunction, lhs: lhs, rhs: rhs)
+public func && (lhs: Conditional, rhs: Conditional) -> Conditional {
+ return .relation(Relation(term: .conjunction, lhs: lhs, rhs: rhs))
+}
+
+/// Creates a disjunctional relation between two conditionals
+///
+/// ```swift
+/// Environment.when(value > 0 || value) {
+/// }
+/// ```
+///
+/// - Parameters:
+/// - lhs: The left-hand side conditional
+/// - rhs: The right-hand side conditional
+///
+/// - Returns: A disjunctional relation
+public func || (lhs: Conditional, rhs: EnvironmentValue) -> Conditional {
+ return .relation(Relation(term: .disjunction, lhs: lhs, rhs: .value(rhs)))
+}
+
+/// Creates a disjunctional relation between two conditionals
+///
+/// ```swift
+/// Environment.when(value || value > 0) {
+/// }
+/// ```
+///
+/// - Parameters:
+/// - lhs: The left-hand side conditional
+/// - rhs: The right-hand side conditional
+///
+/// - Returns: A disjunctional relation
+public func || (lhs: EnvironmentValue, rhs: Conditional) -> Conditional {
+ return .relation(Relation(term: .disjunction, lhs: .value(lhs), rhs: rhs))
}
/// Creates a disjunctional relation between two conditionals
@@ -67,7 +132,7 @@ public func && (lhs: Conditionable, rhs: Conditionable) -> Relation {
/// - rhs: The right-hand side conditional
///
/// - Returns: A disjunctional relation
-public func || (lhs: Conditionable, rhs: Conditionable) -> Relation {
- return Relation(term: .disjunction, lhs: lhs, rhs: rhs)
+public func || (lhs: Conditional, rhs: Conditional) -> Conditional {
+ return .relation(Relation(term: .disjunction, lhs: lhs, rhs: rhs))
}
diff --git a/Sources/HTMLKit/Framework/Environment/Statement.swift b/Sources/HTMLKit/Framework/Environment/Statement.swift
index 09c6c1e1..3a6888f3 100644
--- a/Sources/HTMLKit/Framework/Environment/Statement.swift
+++ b/Sources/HTMLKit/Framework/Environment/Statement.swift
@@ -3,7 +3,7 @@
public struct Statement: Content {
/// The compound condition
- internal let compound: Conditionable
+ internal let compound: Conditional
/// The first statement
internal let first: [Content]
@@ -17,7 +17,7 @@ public struct Statement: Content {
/// - compound: The compound of conditionals
/// - first: The statement to execute if conditionals are true
/// - second: The statement to execute if conditionals are false
- public init(compound: Conditionable, first: [Content], second: [Content]) {
+ public init(compound: Conditional, first: [Content], second: [Content]) {
self.compound = compound
self.first = first
diff --git a/Sources/HTMLKit/Framework/Rendering/Renderer.swift b/Sources/HTMLKit/Framework/Rendering/Renderer.swift
index eacde90a..b6cefa8f 100644
--- a/Sources/HTMLKit/Framework/Rendering/Renderer.swift
+++ b/Sources/HTMLKit/Framework/Rendering/Renderer.swift
@@ -426,26 +426,28 @@ public final class Renderer {
}
/// Renders a environment statement
+ ///
+ /// - Parameter statement: The statement to resolve
+ ///
+ /// - Returns: The rendered condition
private func render(statement: Statement) throws -> String {
var result = false
- if let condition = statement.compound as? Condition {
+ switch statement.compound {
+ case .value(let value):
+ result = try environment.evaluate(value: value)
+
+ case .condition(let condition):
result = try environment.evaluate(condition: condition)
- }
-
- if let negation = statement.compound as? Negation {
+
+ case .negation(let negation):
result = try environment.evaluate(negation: negation)
- }
-
- if let relation = statement.compound as? Relation {
+
+ case .relation(let relation):
result = try environment.evaluate(relation: relation)
}
- if let value = statement.compound as? EnvironmentValue {
- result = try environment.evaluate(value: value)
- }
-
if result {
return try render(contents: statement.first)
}
From 3bb47e391169180c70162335af59816b7151feed Mon Sep 17 00:00:00 2001
From: Mattes Mohr
Date: Wed, 8 Jan 2025 21:38:01 +0100
Subject: [PATCH 11/15] Test the error reporting for the environment through
the provider
---
Tests/HTMLKitVaporTests/ProviderTests.swift | 72 +++++++++++++++++++++
1 file changed, 72 insertions(+)
diff --git a/Tests/HTMLKitVaporTests/ProviderTests.swift b/Tests/HTMLKitVaporTests/ProviderTests.swift
index a96a9fb3..2877188b 100644
--- a/Tests/HTMLKitVaporTests/ProviderTests.swift
+++ b/Tests/HTMLKitVaporTests/ProviderTests.swift
@@ -393,4 +393,76 @@ final class ProviderTests: XCTestCase {
)
}
}
+
+ /// Tests the error reporting to Vapor for issues that may occur during environment access.
+ ///
+ /// The error is expected to be classified as an internal server error and includes a error message.
+ func testEnvironmentErrorReporting() throws {
+
+ struct TestObject {
+
+ let firstName = "Jane"
+ }
+
+ struct UnkownObject: HTMLKit.View {
+
+ @EnvironmentObject(TestObject.self)
+ var object
+
+ var body: HTMLKit.Content {
+ Paragraph {
+ Environment.when(object.firstName) {
+ "True"
+ }
+ }
+ }
+ }
+
+ struct WrongCast: HTMLKit.View {
+
+ @EnvironmentObject(TestObject.self)
+ var object
+
+ var body: HTMLKit.Content {
+ Paragraph {
+ Environment.when(object.firstName) {
+ "True"
+ }
+ }
+ .environment(object: TestObject())
+ }
+ }
+
+ let app = Application(.testing)
+
+ defer { app.shutdown() }
+
+ app.get("unkownobject") { request async throws -> Vapor.View in
+
+ return try await request.htmlkit.render(UnkownObject())
+ }
+
+ app.get("wrongcast") { request async throws -> Vapor.View in
+
+ return try await request.htmlkit.render(WrongCast())
+ }
+
+ try app.test(.GET, "unkownobject") { response in
+
+ XCTAssertEqual(response.status, .internalServerError)
+
+ let abort = try response.content.decode(AbortResponse.self)
+
+ XCTAssertEqual(abort.reason, "Unable to retrieve environment object.")
+ }
+
+ try app.test(.GET, "wrongcast") { response in
+
+ XCTAssertEqual(response.status, .internalServerError)
+
+ let abort = try response.content.decode(AbortResponse.self)
+
+ XCTAssertEqual(abort.reason, "Unable to cast the environment value.")
+ }
+ }
}
From a86602c480df8664babf49e80d1b8c7248659567 Mon Sep 17 00:00:00 2001
From: Mattes Mohr
Date: Fri, 10 Jan 2025 17:42:15 +0100
Subject: [PATCH 12/15] Make environment statement and sequence a global
element
---
Sources/HTMLKit/Framework/Environment/Sequence.swift | 2 +-
Sources/HTMLKit/Framework/Environment/Statement.swift | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Sources/HTMLKit/Framework/Environment/Sequence.swift b/Sources/HTMLKit/Framework/Environment/Sequence.swift
index b2498712..f591ec81 100644
--- a/Sources/HTMLKit/Framework/Environment/Sequence.swift
+++ b/Sources/HTMLKit/Framework/Environment/Sequence.swift
@@ -1,6 +1,6 @@
/// A type representing a loop rendering content within the environment
@_documentation(visibility: internal)
-public struct Sequence: Content {
+public struct Sequence: GlobalElement {
/// The environment value of the sequence
internal let value: EnvironmentValue
diff --git a/Sources/HTMLKit/Framework/Environment/Statement.swift b/Sources/HTMLKit/Framework/Environment/Statement.swift
index 3a6888f3..2db7286e 100644
--- a/Sources/HTMLKit/Framework/Environment/Statement.swift
+++ b/Sources/HTMLKit/Framework/Environment/Statement.swift
@@ -1,6 +1,6 @@
/// A type representing a conditional block within the environment
@_documentation(visibility: internal)
-public struct Statement: Content {
+public struct Statement: GlobalElement {
/// The compound condition
internal let compound: Conditional
From 5e405c105cdcfb7a81584cad5479a66c82dada7c Mon Sep 17 00:00:00 2001
From: Mattes Mohr
Date: Fri, 10 Jan 2025 19:37:30 +0100
Subject: [PATCH 13/15] Introduce a method to unwrap optional environment
values
---
.../Framework/Environment/Conditional.swift | 3 +
.../Framework/Environment/Environment.swift | 57 ++++++++++++++++++-
.../Framework/Environment/Nullable.swift | 16 ++++++
.../Framework/Rendering/Renderer.swift | 3 +
Tests/HTMLKitTests/EnvironmentTests.swift | 43 ++++++++++++++
5 files changed, 119 insertions(+), 3 deletions(-)
create mode 100644 Sources/HTMLKit/Framework/Environment/Nullable.swift
diff --git a/Sources/HTMLKit/Framework/Environment/Conditional.swift b/Sources/HTMLKit/Framework/Environment/Conditional.swift
index 6b7957c2..d8904f7b 100644
--- a/Sources/HTMLKit/Framework/Environment/Conditional.swift
+++ b/Sources/HTMLKit/Framework/Environment/Conditional.swift
@@ -2,6 +2,9 @@
@_documentation(visibility: internal)
public indirect enum Conditional {
+ /// Holds an optional
+ case optional(EnvironmentValue)
+
/// Holds an relation
case relation(Relation)
diff --git a/Sources/HTMLKit/Framework/Environment/Environment.swift b/Sources/HTMLKit/Framework/Environment/Environment.swift
index 91a12605..d0f5e977 100644
--- a/Sources/HTMLKit/Framework/Environment/Environment.swift
+++ b/Sources/HTMLKit/Framework/Environment/Environment.swift
@@ -124,9 +124,7 @@ public final class Environment {
/// - Returns: The result of evaluation
internal func evaluate(value: EnvironmentValue) throws -> Bool {
- let value = try resolve(value: value)
-
- guard let boolValue = value as? Bool else {
+ guard let boolValue = try resolve(value: value) as? Bool else {
throw Errors.unableToCastEnvironmentValue
}
@@ -146,6 +144,9 @@ public final class Environment {
var result = true
switch relation.lhs {
+ case .optional(let optional):
+ result = try evaluate(optional: optional)
+
case .condition(let condition):
result = try evaluate(condition: condition)
@@ -165,6 +166,9 @@ public final class Environment {
}
switch relation.rhs {
+ case .optional(let optional):
+ result = try evaluate(optional: optional)
+
case .condition(let condition):
result = try evaluate(condition: condition)
@@ -185,6 +189,9 @@ public final class Environment {
var result = false
switch relation.lhs {
+ case .optional(let optional):
+ result = try evaluate(optional: optional)
+
case .condition(let condition):
result = try evaluate(condition: condition)
@@ -204,6 +211,9 @@ public final class Environment {
}
switch relation.rhs {
+ case .optional(let optional):
+ result = try evaluate(optional: optional)
+
case .condition(let condition):
result = try evaluate(condition: condition)
@@ -253,6 +263,24 @@ public final class Environment {
internal func evaluate(negation: Negation) throws -> Bool {
return try !evaluate(value: negation.value)
}
+
+ /// Evaluates an environment optional
+ ///
+ /// - Parameter optional: The optional to evaluate
+ ///
+ /// - Returns: The result of the evaluation
+ internal func evaluate(optional: EnvironmentValue) throws -> Bool {
+
+ guard let optionalValue = try resolve(value: optional) as? Nullable else {
+ throw Errors.unableToCastEnvironmentValue
+ }
+
+ if !optionalValue.isNull {
+ return true
+ }
+
+ return false
+ }
}
extension Environment {
@@ -279,6 +307,29 @@ extension Environment {
return Statement(compound: condition, first: content(), second: [])
}
+ /// Unwraps a optional environment value
+ ///
+ /// - Parameters:
+ /// - value: The optional value to unwrap
+ /// - content: The content for some statement
+ ///
+ /// - Returns: A environment statement
+ public static func unwrap(_ value: EnvironmentValue, @ContentBuilder content: (EnvironmentValue) -> [Content]) -> Statement {
+ return Statement(compound: .optional(value), first: content(value), second: [])
+ }
+
+ /// Unwraps a optional environment value
+ ///
+ /// - Parameters:
+ /// - value: The optional value to unwrap
+ /// - content: The content for some statement
+ /// - then: The content for none statement
+ ///
+ /// - Returns: A environment statement
+ public static func unwrap(_ value: EnvironmentValue, @ContentBuilder content: (EnvironmentValue) -> [Content], @ContentBuilder then: () -> [Content]) -> Statement {
+ return Statement(compound: .optional(value), first: content(value), second: then())
+ }
+
/// Evaluates one condition
///
/// - Parameters:
diff --git a/Sources/HTMLKit/Framework/Environment/Nullable.swift b/Sources/HTMLKit/Framework/Environment/Nullable.swift
new file mode 100644
index 00000000..70fb35a1
--- /dev/null
+++ b/Sources/HTMLKit/Framework/Environment/Nullable.swift
@@ -0,0 +1,16 @@
+/// A type that represent
+///
+/// > Note: This protocol is intended as a temporary workaround.
+@_documentation(visibility: internal)
+internal protocol Nullable {
+
+ /// Checks whether the value is absent without needing to know the underlying type.
+ var isNull: Bool { get }
+}
+
+extension Optional: Nullable {
+
+ internal var isNull: Bool {
+ return self == nil
+ }
+}
diff --git a/Sources/HTMLKit/Framework/Rendering/Renderer.swift b/Sources/HTMLKit/Framework/Rendering/Renderer.swift
index b6cefa8f..503f28c6 100644
--- a/Sources/HTMLKit/Framework/Rendering/Renderer.swift
+++ b/Sources/HTMLKit/Framework/Rendering/Renderer.swift
@@ -435,6 +435,9 @@ public final class Renderer {
var result = false
switch statement.compound {
+ case .optional(let optional):
+ result = try environment.evaluate(optional: optional)
+
case .value(let value):
result = try environment.evaluate(value: value)
diff --git a/Tests/HTMLKitTests/EnvironmentTests.swift b/Tests/HTMLKitTests/EnvironmentTests.swift
index 15c0cefa..e49a47dc 100644
--- a/Tests/HTMLKitTests/EnvironmentTests.swift
+++ b/Tests/HTMLKitTests/EnvironmentTests.swift
@@ -350,6 +350,49 @@ final class EnvironmentTests: XCTestCase {
)
}
+ /// Tests the conditional rendering based on an optional environment value
+ ///
+ /// The renderer is expected to evaluate the presence of the value and render the right content
+ /// accordingly.
+ func testEnvironmentUnwrap() throws {
+
+ struct TestObject: ViewModel {
+
+ let some: String? = "Some"
+ let none: String? = nil
+ }
+
+ struct TestView: View {
+
+ @EnvironmentObject(TestObject.self)
+ var object
+
+ var body: Content {
+ Paragraph {
+
+ // Should return none, cause the value is nil
+ Environment.unwrap(object.none) { value in
+ value
+ } then: {
+ "None"
+ }
+
+ // Should return the value
+ Environment.unwrap(object.some) { value in
+ value
+ }
+ }
+ .environment(object: TestObject())
+ }
+ }
+
+ XCTAssertEqual(try renderer.render(view: TestView()),
+ """
+ NoneSome
+ """
+ )
+ }
+
/// Tests the string interpolation with an environment value
///
/// The renderer is expected to render the string correctly
From a65cd382b3dd575e1ae96e2bdc8acc863d2da9d0 Mon Sep 17 00:00:00 2001
From: Mattes Mohr
Date: Fri, 10 Jan 2025 19:53:42 +0100
Subject: [PATCH 14/15] Tidy up a bit
---
.../Framework/Environment/Environment.swift | 42 ++++++++++++-------
.../Framework/Environment/Nullable.swift | 9 +---
.../Extensions/Comparable+HTMLKit.swift | 8 ++--
.../Extensions/Datatypes+Content.swift | 2 -
.../Extensions/Optional+HTMLKit.swift | 9 ++++
Tests/HTMLKitTests/EnvironmentTests.swift | 8 ----
6 files changed, 41 insertions(+), 37 deletions(-)
create mode 100644 Sources/HTMLKit/Framework/Extensions/Optional+HTMLKit.swift
diff --git a/Sources/HTMLKit/Framework/Environment/Environment.swift b/Sources/HTMLKit/Framework/Environment/Environment.swift
index d0f5e977..b199294b 100644
--- a/Sources/HTMLKit/Framework/Environment/Environment.swift
+++ b/Sources/HTMLKit/Framework/Environment/Environment.swift
@@ -291,22 +291,46 @@ extension Environment {
/// - condition: The condition to evaluate
/// - content: The content for the true statement
///
- /// - Returns: A environment condition
+ /// - Returns: A environment statement
public static func when(_ condition: EnvironmentValue, @ContentBuilder content: () -> [Content]) -> Statement {
return Statement(compound: .value(condition), first: content(), second: [])
}
+ /// Evaluates one condition
+ ///
+ /// - Parameters:
+ /// - condition: The condition to evaluate
+ /// - content: The content for the true statement
+ /// - then: The content for the false statement
+ ///
+ /// - Returns: A environment statement
+ public static func when(_ condition: EnvironmentValue, @ContentBuilder content: () -> [Content], @ContentBuilder then: () -> [Content]) -> Statement {
+ return Statement(compound: .value(condition), first: content(), second: then())
+ }
+
/// Evaluates one condition
///
/// - Parameters:
/// - condition: The condition to evaluate
/// - content: The content for the true statement
///
- /// - Returns: A environment condition
+ /// - Returns: A environment statement
public static func when(_ condition: Conditional, @ContentBuilder content: () -> [Content]) -> Statement {
return Statement(compound: condition, first: content(), second: [])
}
+ /// Evaluates one condition
+ ///
+ /// - Parameters:
+ /// - condition: The condition to evaluate
+ /// - content: The content for the true statement
+ /// - then: The content for the false statement
+ ///
+ /// - Returns: A environment statement
+ public static func when(_ condition: Conditional, @ContentBuilder content: () -> [Content], @ContentBuilder then: () -> [Content]) -> Statement {
+ return Statement(compound: condition, first: content(), second: then())
+ }
+
/// Unwraps a optional environment value
///
/// - Parameters:
@@ -330,25 +354,13 @@ extension Environment {
return Statement(compound: .optional(value), first: content(value), second: then())
}
- /// Evaluates one condition
- ///
- /// - Parameters:
- /// - condition: The condition to evaluate
- /// - content: The content for the true statement
- /// - then: The content for the false statement
- ///
- /// - Returns: A environment condition
- public static func when(_ condition: Conditional, @ContentBuilder content: () -> [Content], @ContentBuilder then: () -> [Content]) -> Statement {
- return Statement(compound: condition, first: content(), second: then())
- }
-
/// Iterates through a sequence of values
///
/// - Parameters:
/// - sequence: The sequence to iterate over
/// - content: The content for the iteration
///
- /// - Returns: A environment condition
+ /// - Returns: A environment sequence
public static func loop(_ sequence: EnvironmentValue, @ContentBuilder content: (EnvironmentValue) -> [Content]) -> Sequence {
return Sequence(value: sequence, content: content(sequence))
}
diff --git a/Sources/HTMLKit/Framework/Environment/Nullable.swift b/Sources/HTMLKit/Framework/Environment/Nullable.swift
index 70fb35a1..4ef47815 100644
--- a/Sources/HTMLKit/Framework/Environment/Nullable.swift
+++ b/Sources/HTMLKit/Framework/Environment/Nullable.swift
@@ -1,4 +1,4 @@
-/// A type that represent
+/// A type that represent a nullable value.
///
/// > Note: This protocol is intended as a temporary workaround.
@_documentation(visibility: internal)
@@ -7,10 +7,3 @@ internal protocol Nullable {
/// Checks whether the value is absent without needing to know the underlying type.
var isNull: Bool { get }
}
-
-extension Optional: Nullable {
-
- internal var isNull: Bool {
- return self == nil
- }
-}
diff --git a/Sources/HTMLKit/Framework/Extensions/Comparable+HTMLKit.swift b/Sources/HTMLKit/Framework/Extensions/Comparable+HTMLKit.swift
index d3b6dde6..ef966e0f 100644
--- a/Sources/HTMLKit/Framework/Extensions/Comparable+HTMLKit.swift
+++ b/Sources/HTMLKit/Framework/Extensions/Comparable+HTMLKit.swift
@@ -6,7 +6,7 @@ extension Comparable {
/// - other: The other value to compare
///
/// - Returns: The result
- public func equal(_ other: Any) -> Bool {
+ internal func equal(_ other: Any) -> Bool {
guard let other = other as? Self else {
return false
@@ -21,7 +21,7 @@ extension Comparable {
/// - other: The other value to compare
///
/// - Returns: The result
- public func unequal(_ other: Any) -> Bool {
+ internal func unequal(_ other: Any) -> Bool {
return !equal(other)
}
@@ -31,7 +31,7 @@ extension Comparable {
/// - other: The other value to compare
///
/// - Returns: The result
- public func greater(_ other: Any) -> Bool {
+ internal func greater(_ other: Any) -> Bool {
guard let other = other as? Self else {
return false
@@ -46,7 +46,7 @@ extension Comparable {
/// - other: The other value to compare
///
/// - Returns: The result
- public func less(_ other: Any) -> Bool {
+ internal func less(_ other: Any) -> Bool {
return !greater(other)
}
}
diff --git a/Sources/HTMLKit/Framework/Extensions/Datatypes+Content.swift b/Sources/HTMLKit/Framework/Extensions/Datatypes+Content.swift
index b5444a21..b20c7978 100644
--- a/Sources/HTMLKit/Framework/Extensions/Datatypes+Content.swift
+++ b/Sources/HTMLKit/Framework/Extensions/Datatypes+Content.swift
@@ -15,8 +15,6 @@ extension Float: Content {}
extension Int: Content {}
-extension Optional: Content{}
-
extension String: Content {
static public func + (lhs: Content, rhs: Self) -> Content {
diff --git a/Sources/HTMLKit/Framework/Extensions/Optional+HTMLKit.swift b/Sources/HTMLKit/Framework/Extensions/Optional+HTMLKit.swift
new file mode 100644
index 00000000..d03bc9c4
--- /dev/null
+++ b/Sources/HTMLKit/Framework/Extensions/Optional+HTMLKit.swift
@@ -0,0 +1,9 @@
+extension Optional: Nullable {
+
+ internal var isNull: Bool {
+ return self == nil
+ }
+}
+
+extension Optional: Content {
+}
diff --git a/Tests/HTMLKitTests/EnvironmentTests.swift b/Tests/HTMLKitTests/EnvironmentTests.swift
index e49a47dc..f6c12dd0 100644
--- a/Tests/HTMLKitTests/EnvironmentTests.swift
+++ b/Tests/HTMLKitTests/EnvironmentTests.swift
@@ -80,7 +80,6 @@ final class EnvironmentTests: XCTestCase {
struct TestObject: ViewModel {
let firstName = "Jane"
- let lastName = "Doe"
let age = 40
let loggedIn = true
}
@@ -169,8 +168,6 @@ final class EnvironmentTests: XCTestCase {
struct TestObject: ViewModel {
- let firstName = "Jane"
- let lastName = "Doe"
let age = 40
}
@@ -226,8 +223,6 @@ final class EnvironmentTests: XCTestCase {
struct TestObject: ViewModel {
- let firstName = "Jane"
- let lastName = "Doe"
let age = 40
}
@@ -276,8 +271,6 @@ final class EnvironmentTests: XCTestCase {
struct TestObject: ViewModel {
- let firstName = "Jane"
- let lastName = "Doe"
let age = 40
}
@@ -319,7 +312,6 @@ final class EnvironmentTests: XCTestCase {
struct TestObject: ViewModel {
- let name = "Jane"
let children = ["Janek", "Janet"]
}
From 2b64212ff158ce1287766782a06716b7f8ba1fe7 Mon Sep 17 00:00:00 2001
From: Mattes Mohr
Date: Fri, 10 Jan 2025 20:51:56 +0100
Subject: [PATCH 15/15] Rename the method for the conditional statement
---
.../Framework/Environment/Environment.swift | 8 ++--
Tests/HTMLKitTests/EnvironmentTests.swift | 38 +++++++++----------
Tests/HTMLKitVaporTests/ProviderTests.swift | 4 +-
3 files changed, 25 insertions(+), 25 deletions(-)
diff --git a/Sources/HTMLKit/Framework/Environment/Environment.swift b/Sources/HTMLKit/Framework/Environment/Environment.swift
index b199294b..0e35029d 100644
--- a/Sources/HTMLKit/Framework/Environment/Environment.swift
+++ b/Sources/HTMLKit/Framework/Environment/Environment.swift
@@ -292,7 +292,7 @@ extension Environment {
/// - content: The content for the true statement
///
/// - Returns: A environment statement
- public static func when(_ condition: EnvironmentValue, @ContentBuilder content: () -> [Content]) -> Statement {
+ public static func check(_ condition: EnvironmentValue, @ContentBuilder content: () -> [Content]) -> Statement {
return Statement(compound: .value(condition), first: content(), second: [])
}
@@ -304,7 +304,7 @@ extension Environment {
/// - then: The content for the false statement
///
/// - Returns: A environment statement
- public static func when(_ condition: EnvironmentValue, @ContentBuilder content: () -> [Content], @ContentBuilder then: () -> [Content]) -> Statement {
+ public static func check(_ condition: EnvironmentValue, @ContentBuilder content: () -> [Content], @ContentBuilder then: () -> [Content]) -> Statement {
return Statement(compound: .value(condition), first: content(), second: then())
}
@@ -315,7 +315,7 @@ extension Environment {
/// - content: The content for the true statement
///
/// - Returns: A environment statement
- public static func when(_ condition: Conditional, @ContentBuilder content: () -> [Content]) -> Statement {
+ public static func check(_ condition: Conditional, @ContentBuilder content: () -> [Content]) -> Statement {
return Statement(compound: condition, first: content(), second: [])
}
@@ -327,7 +327,7 @@ extension Environment {
/// - then: The content for the false statement
///
/// - Returns: A environment statement
- public static func when(_ condition: Conditional, @ContentBuilder content: () -> [Content], @ContentBuilder then: () -> [Content]) -> Statement {
+ public static func check(_ condition: Conditional, @ContentBuilder content: () -> [Content], @ContentBuilder then: () -> [Content]) -> Statement {
return Statement(compound: condition, first: content(), second: then())
}
diff --git a/Tests/HTMLKitTests/EnvironmentTests.swift b/Tests/HTMLKitTests/EnvironmentTests.swift
index f6c12dd0..700cee9a 100644
--- a/Tests/HTMLKitTests/EnvironmentTests.swift
+++ b/Tests/HTMLKitTests/EnvironmentTests.swift
@@ -93,60 +93,60 @@ final class EnvironmentTests: XCTestCase {
Paragraph {
// Should return true
- Environment.when(object.loggedIn) {
+ Environment.check(object.loggedIn) {
"True"
}
// Should return false
- Environment.when(!object.loggedIn) {
+ Environment.check(!object.loggedIn) {
"True"
} then: {
"False"
}
// Should return false
- Environment.when(object.firstName == "John") {
+ Environment.check(object.firstName == "John") {
"True"
} then: {
"False"
}
// The counter test, should return true
- Environment.when(object.firstName == "Jane") {
+ Environment.check(object.firstName == "Jane") {
"True"
}
// Should return true
- Environment.when(object.firstName != "John") {
+ Environment.check(object.firstName != "John") {
"True"
}
// The counter test, should return false
- Environment.when(object.firstName != "Jane") {
+ Environment.check(object.firstName != "Jane") {
"True"
} then: {
"False"
}
// Should return false
- Environment.when(object.age > 41) {
+ Environment.check(object.age > 41) {
"True"
} then: {
"False"
}
// The counter test, should return true
- Environment.when(object.age > 39) {
+ Environment.check(object.age > 39) {
"True"
}
// Should return true
- Environment.when(object.age < 41) {
+ Environment.check(object.age < 41) {
"True"
}
// The counter test, should return false
- Environment.when(object.age < 39) {
+ Environment.check(object.age < 39) {
"True"
} then: {
"False"
@@ -180,28 +180,28 @@ final class EnvironmentTests: XCTestCase {
Paragraph {
// The relation is true, cause both conditions are true
- Environment.when(object.age > 39 && object.age < 41) {
+ Environment.check(object.age > 39 && object.age < 41) {
"True"
} then: {
"False"
}
// The relation is false, cause both conditions are false
- Environment.when(object.age < 39 && object.age > 41) {
+ Environment.check(object.age < 39 && object.age > 41) {
"True"
} then: {
"False"
}
// The relation is false, cause the first condition is false
- Environment.when(object.age > 41 && object.age > 39) {
+ Environment.check(object.age > 41 && object.age > 39) {
"True"
} then: {
"False"
}
// The relation is false, cause the second condition is false
- Environment.when(object.age > 39 && object.age > 41) {
+ Environment.check(object.age > 39 && object.age > 41) {
"True"
} then: {
"False"
@@ -235,21 +235,21 @@ final class EnvironmentTests: XCTestCase {
Paragraph {
// The relation is true, cause the second condition is true
- Environment.when(object.age > 41 || object.age == 40) {
+ Environment.check(object.age > 41 || object.age == 40) {
"True"
} then: {
"False"
}
// The relation is true, cause the first condition is true
- Environment.when(object.age == 40 || object.age > 41) {
+ Environment.check(object.age == 40 || object.age > 41) {
"True"
} then: {
"False"
}
// The relation is false, cause all conditions are false
- Environment.when(object.age == 50 || object.age > 41) {
+ Environment.check(object.age == 50 || object.age > 41) {
"True"
} then: {
"False"
@@ -283,14 +283,14 @@ final class EnvironmentTests: XCTestCase {
Paragraph {
// The statement is true, cause the first relation is true
- Environment.when(object.age == 40 || object.age > 41 && object.age < 39) {
+ Environment.check(object.age == 40 || object.age > 41 && object.age < 39) {
"True"
} then: {
"False"
}
// The statement is true, cause the second relation is true
- Environment.when(object.age == 50 || object.age < 41 && object.age > 39) {
+ Environment.check(object.age == 50 || object.age < 41 && object.age > 39) {
"True"
} then: {
"False"
diff --git a/Tests/HTMLKitVaporTests/ProviderTests.swift b/Tests/HTMLKitVaporTests/ProviderTests.swift
index 2877188b..5940ebcc 100644
--- a/Tests/HTMLKitVaporTests/ProviderTests.swift
+++ b/Tests/HTMLKitVaporTests/ProviderTests.swift
@@ -411,7 +411,7 @@ final class ProviderTests: XCTestCase {
var body: HTMLKit.Content {
Paragraph {
- Environment.when(object.firstName) {
+ Environment.check(object.firstName) {
"True"
}
}
@@ -425,7 +425,7 @@ final class ProviderTests: XCTestCase {
var body: HTMLKit.Content {
Paragraph {
- Environment.when(object.firstName) {
+ Environment.check(object.firstName) {
"True"
}
}