diff --git a/Package.swift b/Package.swift index 5adda92..59f9ce2 100644 --- a/Package.swift +++ b/Package.swift @@ -16,11 +16,20 @@ let package = Package( dependencies: [ .product(name: "Rainbow", package: "Rainbow") ] - + ), + .target( + name: "CliDoc2", + dependencies: [ + .product(name: "Rainbow", package: "Rainbow") + ] ), .testTarget( name: "CliDocTests", dependencies: ["CliDoc"] + ), + .testTarget( + name: "CliDoc2Tests", + dependencies: ["CliDoc2"] ) ] ) diff --git a/Sources/CliDoc/NodeModifier.swift b/Sources/CliDoc/NodeModifier.swift index e2f6935..aab7614 100644 --- a/Sources/CliDoc/NodeModifier.swift +++ b/Sources/CliDoc/NodeModifier.swift @@ -17,15 +17,17 @@ public extension NodeModifier { } public extension Node { - func modifier(_ modifier: T) -> ModifiedNode { .init(content: self, modifier: modifier) } } -public struct ConcatModifier: NodeModifier where Content: Node, - Modifier1: NodeModifier, Modifier2: NodeModifier, - Modifier1.Content == Content, Modifier1.Body == Modifier2.Content +public struct ConcatModifier: NodeModifier where + Content: Node, + Modifier1: NodeModifier, + Modifier2: NodeModifier, + Modifier1.Content == Content, + Modifier1.Body == Modifier2.Content { let first: Modifier1 let second: Modifier2 @@ -33,7 +35,6 @@ public struct ConcatModifier: NodeModifier where public func render(content: Content) -> some Node { second.render(content: first.render(content: content)) } - } public struct ModifiedNode { @@ -54,7 +55,7 @@ extension ModifiedNode: Node where Content: Node, Modifier: NodeModifier, Modifier.Content == Content { - public var body: some Node { + public var body: Content { content } } @@ -70,11 +71,3 @@ extension ModifiedNode: NodeModifier where return modifier.render(content: body).render() } } - -extension ModifiedNode where Modifier: NodeModifier { - - func modifier(_ modifier: T) -> ModifiedNode> { - .init(content: content, modifier: self.modifier.concat(modifier)) - } - -} diff --git a/Sources/CliDoc2/Node.swift b/Sources/CliDoc2/Node.swift new file mode 100644 index 0000000..d903ae7 --- /dev/null +++ b/Sources/CliDoc2/Node.swift @@ -0,0 +1,149 @@ +import Rainbow + +public protocol Node { + func render() -> String +} + +extension String: Node { + public func render() -> String { + self + } +} + +@resultBuilder +enum NodeBuilder { + + public static func buildPartialBlock(first: N) -> N { + first + } + + static func buildPartialBlock(accumulated: N0, next: N1) -> ManyNode { + .init(nodes: [accumulated, next]) + } + + public static func buildArray(_ components: [N]) -> ManyNode { + .init(nodes: components) + } + + static func buildBlock(_ components: N...) -> ManyNode { + .init(nodes: components) + } + + static func buildEither(first component: N) -> N { + component + } + + static func buildEither(second component: N) -> N { + component + } + + static func buildOptional(_ component: N?) -> any Node { + component + } + +} + +extension Optional: Node where Wrapped: Node { + public func render() -> String { + guard let node = self else { return "" } + return node.render() + } +} + +struct ManyNode: Node { + + var nodes: [any Node] + + init(nodes: [any Node]) { + self.nodes = nodes.reduce(into: [any Node]()) { array, next in + if let many = next as? ManyNode { + array += many.nodes + } else { + array.append(next) + } + } + } + + func render() -> String { + nodes.reduce("") { $0 + $1.render() } + } +} + +struct Group: Node { + var content: [any Node] + var separator: any Node + + init( + content: [any Node], + separator: any Node = "\n" + ) { + self.content = content + self.separator = separator + } + + init( + separator: any Node = "\n", + @NodeBuilder content: () -> any Node + ) { + let content = content() + if let many = content as? ManyNode { + self.content = many.nodes + } else { + self.content = [content] + } + self.separator = separator + } + + func render() -> String { + content.reduce("") { + $0 + $1.render() + separator.render() + } + } +} + +struct ColorNode: Node { + let color: NamedColor + let node: any Node + + func render() -> String { + node.render().applyingColor(color) + } +} + +extension Node { + func color(_ color: NamedColor) -> some Node { + ColorNode(color: color, node: self) + } +} + +struct Label: Node { + var node: any Node + + init(@NodeBuilder _ content: () -> any Node) { + self.node = content() + } + + func render() -> String { + node.render() + } +} + +struct Note: Node { + var label: any Node + var content: any Node + var separator: any Node = " " + + init( + separator: any Node = " ", + @NodeBuilder _ label: () -> any Node, + @NodeBuilder content: () -> any Node + ) { + self.separator = separator + self.label = label() + self.content = content() + } + + func render() -> String { + Group(content: [label, content], separator: separator).render() + } +} diff --git a/Tests/CliDoc2Tests/CliDoc2Tests.swift b/Tests/CliDoc2Tests/CliDoc2Tests.swift new file mode 100644 index 0000000..dadfdb8 --- /dev/null +++ b/Tests/CliDoc2Tests/CliDoc2Tests.swift @@ -0,0 +1,31 @@ +@testable import CliDoc2 +@preconcurrency import Rainbow +import Testing + +let setupRainbow: Bool = { + Rainbow.enabled = true + Rainbow.outputTarget = .console + return true +}() + +@Test +func testGroup() { + #expect(setupRainbow) + let group = Group { + Label { "Foo:" }.color(.blue) + "Bar" + "Baz" + Note { "Bang:" } content: { "boom" } + if setupRainbow { + Label("Hello, rainbow").color(.blue) + } else { + Label("No color for you!").color(.green) + } + }.color(.green) + + print(group.render()) + + let note = Note { "Bang:" } content: { "boom" } + print(note.render()) + print(type(of: note.label)) +} diff --git a/Tests/CliDocTests/CliDocTests.swift b/Tests/CliDocTests/CliDocTests.swift index 979bef0..92ddeef 100644 --- a/Tests/CliDocTests/CliDocTests.swift +++ b/Tests/CliDocTests/CliDocTests.swift @@ -41,7 +41,7 @@ func checkLabelColorModifier() { } } .labelColor(.blue) - // .labelColor(.green) + print(type(of: group)) print(type(of: group.body)) @@ -52,6 +52,13 @@ func checkLabelColorModifier() { \("Bang".green) boom """ #expect(group.render() == expected) + +// var foo = group +// if var bar = group as? ModifiedNode { +// print("Modified Node") +// bar.modifier = bar.modifier.concat(GroupLabelModifier(color: .green)) +// print(type(of: bar.body)) +// } } @Test