wip: Can't seem to get the node modifiers working properly.

This commit is contained in:
2024-12-03 15:40:07 -05:00
parent 8658035d51
commit f73ded3314
6 changed files with 403 additions and 140 deletions

View File

@@ -0,0 +1,73 @@
@resultBuilder
public enum TextBuilder {
@inlinable
public static func buildPartialBlock<N: TextNode>(first: N) -> N {
first
}
@inlinable
public static func buildPartialBlock<N0: TextNode, N1: TextNode>(accumulated: N0, next: N1) -> NodeContainer {
.init(nodes: [accumulated, next])
}
@inlinable
public static func buildArray<N: TextNode>(_ components: [N]) -> NodeContainer {
.init(nodes: components)
}
@inlinable
public static func buildBlock<N: TextNode>(_ components: N...) -> NodeContainer {
.init(nodes: components)
}
@inlinable
public static func buildEither<N: TextNode, N1: TextNode>(first component: N) -> EitherNode<N, N1> {
.first(component)
}
@inlinable
public static func buildEither<N: TextNode, N1: TextNode>(second component: N1) -> EitherNode<N, N1> {
.second(component)
}
@inlinable
public static func buildOptional<N: TextNode>(_ component: N?) -> N? {
component
}
}
public enum EitherNode<N: TextNode, N1: TextNode>: TextNode {
case first(N)
case second(N1)
public func render() -> String {
switch self {
case let .first(node): return node.render()
case let .second(node): return node.render()
}
}
}
public struct NodeContainer: TextNode {
@usableFromInline
var nodes: [any TextNode]
@usableFromInline
init(nodes: [any TextNode]) {
self.nodes = nodes.reduce(into: [any TextNode]()) { array, next in
if let many = next as? NodeContainer {
array += many.nodes
} else {
array.append(next)
}
}
}
@inlinable
public func render() -> String {
nodes.reduce("") { $0 + $1.render() }
}
}

View File

@@ -0,0 +1,118 @@
import Rainbow
public protocol NodeModifier {
// swiftlint:disable type_name
associatedtype _Body: TextNode
typealias Body = _Body
// swiftlint:enable type_name
associatedtype Content: TextNode
@TextBuilder
func render(content: Content) -> Body
}
public extension NodeModifier {
func concat<T: NodeModifier>(_ modifier: T) -> ConcatModifier<Self, T> {
print("Concat: \(type(of: modifier))")
return .init(firstModifier: self, secondModifier: modifier)
}
}
public struct ConcatModifier<M0: NodeModifier, M1: NodeModifier>: NodeModifier where M1.Content == M0.Body {
let firstModifier: M0
let secondModifier: M1
public func render(content: M0.Content) -> some TextNode {
let firstOutput = firstModifier.render(content: content)
return secondModifier.render(content: firstOutput)
}
}
public struct ModifiedNode<Content: TextNode, Modifier: NodeModifier> {
@usableFromInline
let content: Content
@usableFromInline
let modifier: Modifier
@usableFromInline
init(content: Content, modifier: Modifier) {
self.content = content
self.modifier = modifier
}
}
extension ModifiedNode: TextNode where Modifier.Content == Content {
public func render() -> String {
modifier.render(content: content).render()
}
@inlinable
func apply<M: NodeModifier>(_ modifier: M) -> ModifiedNode<Content, ConcatModifier<Modifier, M>> {
print("ModifiedNode modifier called.")
return .init(content: content, modifier: self.modifier.concat(modifier))
}
}
public extension TextNode {
@inlinable
func modifier<M: NodeModifier>(_ modifier: M) -> ModifiedNode<Self, M> {
.init(content: self, modifier: modifier)
}
}
@usableFromInline
struct ColorModifier<Content: TextNode>: NodeModifier {
@usableFromInline
let color: NamedColor
@usableFromInline
init(color: NamedColor) {
self.color = color
}
@usableFromInline
func render(content: Content) -> some TextNode {
content.render().applyingColor(color)
}
}
public extension TextNode {
@inlinable
func color(_ color: NamedColor) -> some TextNode {
modifier(ColorModifier(color: color))
}
}
@usableFromInline
struct StyleModifier<Content: TextNode>: NodeModifier {
@usableFromInline
let styles: [Style]
@usableFromInline
init(styles: [Style]) {
self.styles = styles
}
@usableFromInline
func render(content: Content) -> some TextNode {
styles.reduce(content.render()) {
$0.applyingStyle($1)
}
}
}
public extension TextNode {
@inlinable
func style(_ styles: Style...) -> some TextNode {
modifier(StyleModifier(styles: styles))
}
@inlinable
func style(_ styles: [Style]) -> some TextNode {
modifier(StyleModifier(styles: styles))
}
}

View File

@@ -0,0 +1,88 @@
import Rainbow
public extension TextNode {
@inlinable
func labelStyle(color: NamedColor? = nil, styles: Style...) -> some TextNode {
labelStyle(color: color, styles: styles)
}
@inlinable
func labelStyle(color: NamedColor? = nil, styles: [Style]) -> some TextNode {
modifier(LabelStyle(color: color, styles: styles))
}
}
public extension ModifiedNode where Self: TextNode {
@inlinable
func labelStyle<M: NodeModifier>(
color: NamedColor? = nil, styles: Style...
) -> some TextNode where
Modifier.Body == Content,
M.Content == Modifier.Body
{
apply(LabelStyle<Content>(color: color, styles: styles))
}
}
public struct LabelStyle<Content: TextNode>: NodeModifier {
@usableFromInline
let color: NamedColor?
@usableFromInline
let styles: [Style]
@usableFromInline
init(color: NamedColor? = nil, styles: [Style] = []) {
self.color = color
self.styles = styles
}
@inlinable
public func render(content: Content) -> some TextNode {
print("Handling node: \(type(of: content))")
return handleNode(content)
}
@TextBuilder
@usableFromInline
func handleNode<N: TextNode>(_ content: N) -> some TextNode {
if let label = content as? Label {
handleLabel(label)
} else if let container = content as? NodeContainer {
handleContainer(container)
} else if let group = content as? Group {
handleGroup(group)
} else {
content
}
}
@usableFromInline
func handleLabel(_ label: Label) -> Label {
var label = label
if let color {
label.node = label.node.color(color)
}
label.node = label.node.style(styles)
return label
}
@usableFromInline
func handleContainer(_ container: NodeContainer) -> NodeContainer {
var container = container
for (idx, node) in container.nodes.enumerated() {
container.nodes[idx] = handleNode(node)
}
return container
}
@usableFromInline
func handleGroup(_ group: Group) -> Group {
var group = group
for (idx, node) in group.content.enumerated() {
group.content[idx] = handleNode(node)
}
return group
}
}

View File

@@ -1,149 +1,24 @@
import Rainbow
public protocol Node {
public protocol TextNode {
func render() -> String
}
extension String: Node {
public extension TextNode {
func map<T: TextNode>(_ transform: (Self) -> T) -> T {
transform(self)
}
}
extension String: TextNode {
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 {
extension Optional: TextNode where Wrapped: TextNode {
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()
}
}

105
Sources/CliDoc2/Nodes.swift Normal file
View File

@@ -0,0 +1,105 @@
public struct AnyTextNode: TextNode {
@usableFromInline
var makeContent: () -> String
@inlinable
public init<T: TextNode>(_ node: T) {
self.makeContent = node.render
}
@inlinable
public func render() -> String {
makeContent()
}
}
public extension TextNode {
func eraseToAnyTextNode() -> AnyTextNode {
.init(self)
}
}
public struct Group: TextNode {
@usableFromInline
var content: [any TextNode]
@usableFromInline
var separator: any TextNode
@usableFromInline
init(
content: [any TextNode],
separator: any TextNode = "\n"
) {
self.content = content
self.separator = separator
}
@inlinable
public init(
separator: any TextNode = "\n",
@TextBuilder content: () -> any TextNode
) {
let content = content()
if let many = content as? NodeContainer {
self.content = many.nodes
} else {
self.content = [content]
}
self.separator = separator
}
@inlinable
public func render() -> String {
content.reduce("") {
$0 + $1.render() + separator.render()
}
}
}
public struct Label: TextNode {
@usableFromInline
var node: any TextNode
@inlinable
public init(@TextBuilder _ content: () -> any TextNode) {
self.node = content()
}
@inlinable
public init(_ node: any TextNode) {
self.node = node
}
@inlinable
public func render() -> String {
node.render()
}
}
public struct Note: TextNode {
@usableFromInline
var label: any TextNode
@usableFromInline
var content: any TextNode
@usableFromInline
var separator: any TextNode = " "
@inlinable
public init(
separator: any TextNode = " ",
@TextBuilder _ label: () -> any TextNode,
@TextBuilder content: () -> any TextNode
) {
self.separator = separator
self.label = label()
self.content = content()
}
@inlinable
public func render() -> String {
Group(content: [label, content], separator: separator).render()
}
}