Exploring new style
This commit is contained in:
@@ -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"]
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
@@ -17,15 +17,17 @@ public extension NodeModifier {
|
||||
}
|
||||
|
||||
public extension Node {
|
||||
|
||||
func modifier<T: NodeModifier>(_ modifier: T) -> ModifiedNode<Self, T> {
|
||||
.init(content: self, modifier: modifier)
|
||||
}
|
||||
}
|
||||
|
||||
public struct ConcatModifier<Content, Modifier1, Modifier2>: NodeModifier where Content: Node,
|
||||
Modifier1: NodeModifier, Modifier2: NodeModifier,
|
||||
Modifier1.Content == Content, Modifier1.Body == Modifier2.Content
|
||||
public struct ConcatModifier<Content, Modifier1, Modifier2>: 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<Content, Modifier1, Modifier2>: NodeModifier where
|
||||
public func render(content: Content) -> some Node {
|
||||
second.render(content: first.render(content: content))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct ModifiedNode<Content, Modifier> {
|
||||
@@ -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<T: NodeModifier>(_ modifier: T) -> ModifiedNode<Content, ConcatModifier<Content, Modifier, T>> {
|
||||
.init(content: content, modifier: self.modifier.concat(modifier))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
149
Sources/CliDoc2/Node.swift
Normal file
149
Sources/CliDoc2/Node.swift
Normal file
@@ -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<N: Node>(first: N) -> N {
|
||||
first
|
||||
}
|
||||
|
||||
static func buildPartialBlock<N0: Node, N1: Node>(accumulated: N0, next: N1) -> ManyNode {
|
||||
.init(nodes: [accumulated, next])
|
||||
}
|
||||
|
||||
public static func buildArray<N: Node>(_ components: [N]) -> ManyNode {
|
||||
.init(nodes: components)
|
||||
}
|
||||
|
||||
static func buildBlock<N: Node>(_ components: N...) -> ManyNode {
|
||||
.init(nodes: components)
|
||||
}
|
||||
|
||||
static func buildEither<N: Node>(first component: N) -> N {
|
||||
component
|
||||
}
|
||||
|
||||
static func buildEither<N: Node>(second component: N) -> N {
|
||||
component
|
||||
}
|
||||
|
||||
static func buildOptional<N: Node>(_ 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()
|
||||
}
|
||||
}
|
||||
31
Tests/CliDoc2Tests/CliDoc2Tests.swift
Normal file
31
Tests/CliDoc2Tests/CliDoc2Tests.swift
Normal file
@@ -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))
|
||||
}
|
||||
@@ -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<Group, GroupLabelModifier> {
|
||||
// print("Modified Node")
|
||||
// bar.modifier = bar.modifier.concat(GroupLabelModifier(color: .green))
|
||||
// print(type(of: bar.body))
|
||||
// }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user