feat: Working on node builder

This commit is contained in:
2024-12-01 01:41:36 -05:00
parent 56a406b231
commit 55d8888961
14 changed files with 480 additions and 39 deletions

View File

@@ -0,0 +1,40 @@
import Foundation
// swiftlint:disable type_name
public enum _ConditionalNode<
TrueNode: NodeRepresentable,
FalseNode: NodeRepresentable
>: NodeRepresentable {
case first(TrueNode)
case second(FalseNode)
public func render() -> String {
switch self {
case let .first(node): return node.render()
case let .second(node): return node.render()
}
}
}
public struct _ManyNode: NodeRepresentable {
@usableFromInline
let nodes: [any NodeRepresentable]
@usableFromInline
let separator: any NodeRepresentable
@inlinable
public init(_ nodes: [any NodeRepresentable], separator: any NodeRepresentable) {
self.nodes = nodes
self.separator = separator
}
@inlinable
public func render() -> String {
nodes.map { $0.render() }
.joined(separator: separator.render())
}
}
// swiftlint:enable type_name

View File

@@ -0,0 +1,31 @@
@resultBuilder
public enum NodeBuilder {
public static func buildPartialBlock<N: NodeRepresentable>(first: N) -> N {
first
}
public static func buildPartialBlock<N0: NodeRepresentable, N1: NodeRepresentable>(
accumulated: N0,
next: N1
) -> _ManyNode {
.init([accumulated, next], separator: .empty())
}
public static func buildArray<N: NodeRepresentable>(_ components: [N]) -> _ManyNode {
.init(components, separator: .empty())
}
public static func buildEither<N0: NodeRepresentable, N1: NodeRepresentable>(
first component: N0
) -> _ConditionalNode<N0, N1> {
.first(component)
}
public static func buildEither<N0: NodeRepresentable, N1: NodeRepresentable>(
second component: N1
) -> _ConditionalNode<N0, N1> {
.second(component)
}
}

View File

@@ -0,0 +1,3 @@
public protocol NodeRepresentable: Sendable {
func render() -> String
}

View File

@@ -0,0 +1,27 @@
public struct AnyNode: NodeRepresentable {
@usableFromInline
let node: any NodeRepresentable
@inlinable
public init<N: NodeRepresentable>(@NodeBuilder _ build: () -> N) {
self.node = build()
}
@inlinable
public init<N: NodeRepresentable>(_ node: N) {
self.node = node
}
@inlinable
public func render() -> String {
node.render()
}
}
public extension NodeRepresentable {
@inlinable
func eraseToAnyNode() -> AnyNode {
.init(self)
}
}

View File

@@ -0,0 +1,38 @@
public struct Group: NodeRepresentable {
@usableFromInline
let node: any NodeRepresentable
@usableFromInline
init(
separator: AnyNode,
node: any NodeRepresentable
) {
if let many = node as? _ManyNode {
self.node = _ManyNode(many.nodes, separator: separator)
} else {
self.node = node
}
}
@inlinable
public init(
separator: any NodeRepresentable,
@NodeBuilder _ build: () -> any NodeRepresentable
) {
self.init(separator: separator.eraseToAnyNode(), node: build())
}
@inlinable
public init(
separator: AnyNode = Space().eraseToAnyNode(),
@NodeBuilder _ build: () -> any NodeRepresentable
) {
self.init(separator: separator, node: build())
}
@inlinable
public func render() -> String {
node.render()
}
}

View File

@@ -0,0 +1,32 @@
@preconcurrency import Rainbow
public struct Header: NodeRepresentable {
@usableFromInline
let node: Text
@usableFromInline
let styles: [Style]
@inlinable
public init(
_ text: String,
color: NamedColor? = .yellow,
styles: [Style] = [.bold]
) {
var text = Text(text)
if let color {
text = text.color(color)
}
self.node = text
self.styles = styles
}
@inlinable
public func render() -> String {
styles.reduce(node) { node, style in
node.style(style)
}
.render()
}
}

View File

@@ -0,0 +1,34 @@
public extension NodeRepresentable {
@inlinable
func repeating(_ count: Int) -> some NodeRepresentable {
RepeatingNode(self, count: count)
}
}
@usableFromInline
struct RepeatingNode: NodeRepresentable {
@usableFromInline
let node: any NodeRepresentable
@usableFromInline
let count: Int
@usableFromInline
init(_ node: any NodeRepresentable, count: Int) {
self.node = node
self.count = count
}
@usableFromInline
func render() -> String {
var string = node.render()
guard count > 0 else { return string }
var count = count - 1
while count > 0 {
string = "\(string)\(node.render())"
count -= 1
}
return string
}
}

View File

@@ -0,0 +1,41 @@
// TODO: Remove init(header:...) initializers.
public struct Section: NodeRepresentable {
@usableFromInline
let node: any NodeRepresentable
@inlinable
public init<N: NodeRepresentable>(@NodeBuilder _ build: () -> N) {
self.node = build()
}
@inlinable
public func render() -> String {
node.render()
}
@inlinable
public init<Content: NodeRepresentable, Separator: NodeRepresentable>(
header: String,
separator: Separator,
@NodeBuilder content: () -> Content
) {
self.init {
Text(header)
separator
content()
}
}
@inlinable
public init<Content: NodeRepresentable>(
header: String,
inline: Bool = false,
@NodeBuilder content: () -> Content
) {
self.init(
header: header,
separator: inline ? Space().eraseToAnyNode() : NewLine().eraseToAnyNode(),
content: content
)
}
}

View File

@@ -0,0 +1,69 @@
@preconcurrency import Rainbow
public struct Text: NodeRepresentable {
@usableFromInline
let text: String
@inlinable
public init(_ text: String) {
self.text = text
}
@inlinable
public func render() -> String {
text
}
@inlinable
public func color(_ color: NamedColor) -> Self {
.init(text.applyingColor(color))
}
@inlinable
public func style(_ style: Style) -> Self {
.init(text.applyingStyle(style))
}
}
public struct NewLine: NodeRepresentable {
@usableFromInline
let node: any NodeRepresentable
@inlinable
public init(count: Int = 1) {
self.node = Text("\n").repeating(count)
}
@inlinable
public func render() -> String {
node.render()
}
}
public struct Space: NodeRepresentable {
let node: any NodeRepresentable
public init(count: Int = 1) {
self.node = Text(" ").repeating(count)
}
public func render() -> String {
node.render()
}
}
public extension NodeRepresentable {
static func empty() -> Self where Self == Text {
Text("")
}
static func newLine(count: Int = 1) -> Self where Self == NewLine {
NewLine(count: count)
}
static func space(count: Int = 1) -> Self where Self == Space {
Space(count: count)
}
}