From ac09f716dca4e941d340d712a39a519521042405 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Mon, 25 Mar 2019 10:05:15 -0400 Subject: [PATCH] String Interpolation, Equatability, Hashability (#48) * String Interpolation, Equatability, Hashability * More * Add default for String --- Sources/Html/Node.swift | 131 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 6 deletions(-) diff --git a/Sources/Html/Node.swift b/Sources/Html/Node.swift index 1bf678c..2a3f277 100644 --- a/Sources/Html/Node.swift +++ b/Sources/Html/Node.swift @@ -20,6 +20,39 @@ public enum Node { } extension Node { + /// Default HTML DOCTYPE. + public static let doctype: Node = .doctype("html") + + /// A root document node including the default HTML DOCTYPE. + public static func document(_ children: Node...) -> Node { + return .fragment([doctype] + children) + } + + public mutating func append(_ node: Node) { + switch (self, node) { + case (.fragment(var nodes), .fragment(let moreNodes)): + nodes.append(contentsOf: moreNodes) + self = .fragment(nodes) + case (.fragment(var nodes), let node): + nodes.append(node) + self = .fragment(nodes) + case (let node, .fragment(var nodes)): + nodes.insert(node, at: nodes.startIndex) + self = .fragment(nodes) + case (.comment(var comment), .comment(let anotherComment)): + comment.append(contentsOf: anotherComment) + self = .comment(comment) + case (.raw(var raw), .raw(let anotherRaw)): + raw.append(contentsOf: anotherRaw) + self = .raw(raw) + case (.text(var text), .text(let anotherText)): + text.append(contentsOf: anotherText) + self = .text(text) + default: + self = .fragment([self, node]) + } + } + public var isEmpty: Bool { switch self { case let .comment(string), let .doctype(string), let .raw(string), let .text(string): @@ -32,6 +65,64 @@ extension Node { } } +extension Node: Equatable { + public static func == (lhs: Node, rhs: Node) -> Bool { + switch (lhs, rhs) { + case + let (.comment(lhs), .comment(rhs)), + let (.doctype(lhs), .doctype(rhs)), + let (.raw(lhs), .raw(rhs)), + let (.text(lhs), .text(rhs)): + return lhs == rhs + case let ( + .element(lhsTag, lhsAttributes, lhsChildren), + .element(rhsTag, rhsAttributes, rhsChildren)): + + return lhsTag == rhsTag + && zip(lhsAttributes, rhsAttributes).allSatisfy { $0 == $1 } + && lhsChildren == rhsChildren + case let (.fragment(lhs), .fragment(rhs)): + return lhs == rhs + default: + return false + } + } +} + +extension Node: Hashable { + public func hash(into hasher: inout Hasher) { + enum HashingTag: String { + case comment, doctype, element, fragment, raw, text + } + + switch self { + case let .comment(comment): + hasher.combine(HashingTag.comment) + hasher.combine(comment) + case let .doctype(doctype): + hasher.combine(HashingTag.doctype) + hasher.combine(doctype) + case let .element(tag, attributes, children): + hasher.combine(HashingTag.element) + hasher.combine(tag) + attributes.forEach { + hasher.combine($0) + hasher.combine($1) + } + hasher.combine(children) + case let .fragment(nodes): + hasher.combine(HashingTag.fragment) + hasher.combine(nodes) + case let .raw(raw): + hasher.combine(HashingTag.raw) + hasher.combine(raw) + case let .text(text): + hasher.combine(HashingTag.text) + hasher.combine(text) + } + } +} + extension Node: ExpressibleByArrayLiteral { public init(arrayLiteral elements: Node...) { self = .fragment(elements) @@ -44,12 +135,40 @@ extension Node: ExpressibleByStringLiteral { } } -extension Node { - /// Default HTML DOCTYPE. - public static let doctype: Node = .doctype("html") +#if swift(>=5.0) +public struct NodeStringInterpolation: StringInterpolationProtocol { + var node: Node - /// A root document node including the default HTML DOCTYPE. - public static func document(_ children: Node...) -> Node { - return .fragment([doctype] + children) + public init(literalCapacity: Int, interpolationCount: Int) { + self.node = .fragment([]) + } + + public mutating func appendInterpolation(_ value: Node) { + self.node.append(value) + } + + public mutating func appendInterpolation(_ value: String) { + self.node.append(.text(value)) + } + + public mutating func appendLiteral(_ literal: String) { + self.node.append(.text(literal)) + } +} + +extension Node: ExpressibleByStringInterpolation { + public init(stringInterpolation: NodeStringInterpolation) { + self = stringInterpolation.node + } +} +#else +extension Node: ExpressibleByStringInterpolation { + public init(stringInterpolation strings: Node...) { + self = .fragment(strings) + } + + public init(stringInterpolationSegment expr: T) { + self = .text("\(expr)") } } +#endif