From 57600be2d953572309aaea575372ba9eb5212629 Mon Sep 17 00:00:00 2001 From: Chris Eidhof Date: Sun, 27 Jun 2021 14:39:24 +0200 Subject: [PATCH] Preference WIP --- Sources/Swim/Node.swift | 4 ++ Sources/Swim/Preferences.swift | 76 +++++++++++++++++++++++++++ Sources/Swim/Visitor.swift | 8 +++ Tests/HTMLTests/HTMLTests.swift | 4 ++ Tests/SwimTests/PreferenceTests.swift | 55 +++++++++++++++++++ 5 files changed, 147 insertions(+) create mode 100644 Sources/Swim/Preferences.swift create mode 100644 Tests/SwimTests/PreferenceTests.swift diff --git a/Sources/Swim/Node.swift b/Sources/Swim/Node.swift index 5d0947c..f1fd79d 100644 --- a/Sources/Swim/Node.swift +++ b/Sources/Swim/Node.swift @@ -18,6 +18,8 @@ public enum Node: Hashable { // The `Node`'s children. case fragment([Node]) + + indirect case preference(PreferenceDict, Node) // Indicates that no whitespace should be added between the surrounding // nodes. @@ -108,6 +110,8 @@ extension Node: TextOutputStreamable { } case .trim: break + case let .preference(_, child): + child.write(to: &target, depth: &depth, didVisitTrim: &didVisitTrim) } } } diff --git a/Sources/Swim/Preferences.swift b/Sources/Swim/Preferences.swift new file mode 100644 index 0000000..4f58207 --- /dev/null +++ b/Sources/Swim/Preferences.swift @@ -0,0 +1,76 @@ +// +// File.swift +// +// +// Created by Chris Eidhof on 27.06.21. +// + +import Foundation + +public struct PreferenceDict: Hashable { + fileprivate var dict: [ObjectIdentifier:AnyHashable] = [:] +} + +extension PreferenceDict: Equatable { + public static func ==(lhs: PreferenceDict, rhs: PreferenceDict) -> Bool { + return false + } +} + +public protocol PreferenceKey { + associatedtype Value: Hashable + static var defaultValue: Value { get } + static func reduce(value: inout Value, nextValue: () -> Value) +} + +extension Node { + public func preference(key: Key.Type = Key.self, _ value: Key.Value) -> Node { + let dict = [ObjectIdentifier(Key.self): value] + return Node.preference(PreferenceDict(dict: dict), self) + } + public func readPreference(key: Key.Type = Key.self) -> Key.Value { + ReadPreferenceVisitor().visitNode(self) + } +} + +struct ReadPreferenceVisitor: Visitor { + func visitElement(name: String, attributes: [String : String], child: Node?) -> Key.Value { + child.map { visitNode($0) } ?? Key.defaultValue + } + + func visitText(text: String) -> Key.Value { + Key.defaultValue + } + + func visitRaw(raw: String) -> Key.Value { + Key.defaultValue + } + + func visitComment(text: String) -> Key.Value { + Key.defaultValue + } + + func visitDocumentType(name: String) -> Key.Value { + Key.defaultValue + } + + func visitFragment(children: [Node]) -> Key.Value { + guard let f = children.first else { return Key.defaultValue } + var result = visitNode(f) + for other in children.dropFirst() { + Key.reduce(value: &result, nextValue: { visitNode(other) }) + } + return result + } + + func visitTrim() -> Key.Value { + Key.defaultValue + } + + typealias Result = Key.Value + let key = ObjectIdentifier(Key.self) + + func visitPreference(_ dict: PreferenceDict, child: Node) -> Key.Value { + (dict.dict[key] as? Key.Value) ?? Key.defaultValue + } +} diff --git a/Sources/Swim/Visitor.swift b/Sources/Swim/Visitor.swift index e3cdcb7..0d142f3 100644 --- a/Sources/Swim/Visitor.swift +++ b/Sources/Swim/Visitor.swift @@ -18,6 +18,8 @@ public protocol Visitor { func visitNode(_ node: Node) -> Result func visitTrim() -> Result + + func visitPreference(_ dict: PreferenceDict, child: Node) -> Result } extension Visitor { @@ -37,6 +39,8 @@ extension Visitor { return visitFragment(children: children) case .trim: return visitTrim() + case .preference(let p, let child): + return visitPreference(p, child: child) } } } @@ -69,6 +73,10 @@ public extension Visitor where Result == Node { func visitTrim() -> Result { .trim } + + func visitPreference(_ p: PreferenceDict, child: Node) -> Result { + .preference(p, child) + } } public extension Visitor where Result == Void { diff --git a/Tests/HTMLTests/HTMLTests.swift b/Tests/HTMLTests/HTMLTests.swift index 674fff8..8ebe132 100644 --- a/Tests/HTMLTests/HTMLTests.swift +++ b/Tests/HTMLTests/HTMLTests.swift @@ -216,6 +216,10 @@ final class HTMLTests: XCTestCase { func visitTrim() -> [String] { [] } + + func visitPreference(_ dict: PreferenceDict, child: Node) -> [String] { + visitNode(child) + } } let root = body { diff --git a/Tests/SwimTests/PreferenceTests.swift b/Tests/SwimTests/PreferenceTests.swift new file mode 100644 index 0000000..469e66a --- /dev/null +++ b/Tests/SwimTests/PreferenceTests.swift @@ -0,0 +1,55 @@ +// +// File.swift +// +// +// Created by Chris Eidhof on 27.06.21. +// + +import Foundation +import XCTest +import Swim +import HTML + +final class PreferenceTests: XCTestCase { + func testSimplePreference() { + struct UseJavascript: PreferenceKey { + static var defaultValue = false + static func reduce(value: inout Bool, nextValue: () -> Bool) { + value = value || nextValue() + } + } + + let none: Node = Node.text("Hello") + XCTAssertEqual(none.readPreference(key: UseJavascript.self), false) + + + let n: Node = div { + "Hello" + "Test".asNode().preference(key: UseJavascript.self, true) + } + let o = n.readPreference(key: UseJavascript.self) + XCTAssertEqual(o, true) + } + + func testReduce() { + struct Counter: PreferenceKey { + static var defaultValue = 0 + static func reduce(value: inout Int, nextValue: () -> Int) { + value += nextValue() + } + } + + let none: Node = Node.text("Hello") + XCTAssertEqual(none.readPreference(key: Counter.self), 0) + + + let n: Node = div { + "Hello" + "Test".asNode().preference(key: Counter.self, 1) + div { + "Hi".asNode().preference(key: Counter.self, 1) + } + } + XCTAssertEqual(n.readPreference(key: Counter.self), 2) + } +}