This commit is contained in:
2024-12-03 17:21:14 -05:00
parent f73ded3314
commit 590df275cc
13 changed files with 266 additions and 254 deletions

View File

@@ -42,10 +42,10 @@ public enum EitherNode<N: TextNode, N1: TextNode>: TextNode {
case first(N)
case second(N1)
public func render() -> String {
public var body: some TextNode {
switch self {
case let .first(node): return node.render()
case let .second(node): return node.render()
case let .first(node): return node.eraseToAnyTextNode()
case let .second(node): return node.eraseToAnyTextNode()
}
}
}
@@ -67,7 +67,7 @@ public struct NodeContainer: TextNode {
}
@inlinable
public func render() -> String {
public var body: some TextNode {
nodes.reduce("") { $0 + $1.render() }
}
}

View File

@@ -0,0 +1,24 @@
import Rainbow
public extension TextNode {
@inlinable
func color(_ color: NamedColor) -> some TextNode {
modifier(ColorModifier(color: color))
}
}
@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)
}
}

View File

@@ -1,31 +1,16 @@
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 {
func labelStyle<C: TextNode>(color: NamedColor? = nil, styles: [Style] = []) -> some TextNode where Self == Label<C> {
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))
func labelStyle<C: TextNode>(color: NamedColor? = nil, styles: Style...) -> some TextNode where Self == Label<C> {
labelStyle(color: color, styles: styles)
}
}
public struct LabelStyle<Content: TextNode>: NodeModifier {
public struct LabelStyle<C: TextNode>: NodeModifier {
@usableFromInline
let color: NamedColor?
@@ -39,50 +24,12 @@ public struct LabelStyle<Content: TextNode>: NodeModifier {
}
@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
public func render(content: Label<C>) -> some TextNode {
var label: any TextNode = content.content
label = label.style(styles)
if let color {
label.node = label.node.color(color)
label = label.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
return Label { label.eraseToAnyTextNode() }
}
}

View File

@@ -0,0 +1,32 @@
import Rainbow
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))
}
}
@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)
}
}
}

View File

@@ -1,24 +0,0 @@
import Rainbow
public protocol TextNode {
func render() -> String
}
public extension TextNode {
func map<T: TextNode>(_ transform: (Self) -> T) -> T {
transform(self)
}
}
extension String: TextNode {
public func render() -> String {
self
}
}
extension Optional: TextNode where Wrapped: TextNode {
public func render() -> String {
guard let node = self else { return "" }
return node.render()
}
}

View File

@@ -15,7 +15,6 @@ public protocol NodeModifier {
public extension NodeModifier {
func concat<T: NodeModifier>(_ modifier: T) -> ConcatModifier<Self, T> {
print("Concat: \(type(of: modifier))")
return .init(firstModifier: self, secondModifier: modifier)
}
}
@@ -45,74 +44,25 @@ public struct ModifiedNode<Content: TextNode, Modifier: NodeModifier> {
}
extension ModifiedNode: TextNode where Modifier.Content == Content {
public func render() -> String {
modifier.render(content: content).render()
public var body: some TextNode {
modifier.render(content: content)
}
@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))
}
}
extension ModifiedNode: NodeRepresentable where Self: TextNode {
public func render() -> String {
body.render()
}
}
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

@@ -1,105 +0,0 @@
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()
}
}

View File

@@ -0,0 +1,37 @@
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 var body: some TextNode {
content.reduce("") {
$0 + $1.render() + separator.render()
}
}
}

View File

@@ -0,0 +1,19 @@
public struct Label<Content: TextNode>: TextNode {
@usableFromInline
let content: Content
@inlinable
public init(@TextBuilder _ content: () -> Content) {
self.content = content()
}
@inlinable
public init(_ content: Content) {
self.content = content
}
@inlinable
public var body: some TextNode {
content
}
}

View File

@@ -0,0 +1,51 @@
import Rainbow
public struct Note<Label: TextNode, Content: TextNode>: TextNode {
@usableFromInline
let label: Label
@usableFromInline
let content: Content
@usableFromInline
var separator: any TextNode = " "
@inlinable
public init(
separator: any TextNode = " ",
@TextBuilder _ label: () -> Label,
@TextBuilder content: () -> Content
) {
self.separator = separator
self.label = label()
self.content = content()
}
@inlinable
public var body: some TextNode {
Group(content: [label, content], separator: separator)
}
}
public extension Note where Label == String {
@inlinable
init(
separator: any TextNode = " ",
_ label: String = "NOTE:".yellow.bold,
@TextBuilder content: () -> Content
) {
self.separator = separator
self.label = label
self.content = content()
}
static func important(
separator: any TextNode = " ",
_ label: String = "IMPORTANT NOTE:".red.underline,
@TextBuilder content: () -> Content
) {
self.init(separator: separator, label, content: content)
}
}

View File

@@ -0,0 +1,21 @@
public struct ShellCommand<Content: TextNode>: TextNode {
@usableFromInline
var symbol: any TextNode
@usableFromInline
var content: Content
@inlinable
public init(
symbol: any TextNode = "$",
@TextBuilder content: () -> Content
) {
self.symbol = symbol
self.content = content()
}
public var body: some TextNode {
Group(content: [symbol, content], separator: " ")
}
}

View File

@@ -0,0 +1,61 @@
public protocol NodeRepresentable {
func render() -> String
}
public protocol TextNode: NodeRepresentable {
// swiftlint:disable type_name
associatedtype _Body: TextNode
typealias Body = _Body
// swiftlint:enable type_name
var body: Body { get }
}
public extension TextNode {
func render() -> String {
body.render()
}
}
extension String: NodeRepresentable {
public func render() -> String {
self
}
}
extension String: TextNode {
public var body: some TextNode {
self
}
}
public struct AnyTextNode: TextNode {
let makeString: () -> String
public init<N: TextNode>(_ node: N) {
self.makeString = node.render
}
public var body: some TextNode { makeString() }
}
public extension TextNode {
func eraseToAnyTextNode() -> AnyTextNode {
AnyTextNode(self)
}
}
extension Optional: TextNode where Wrapped: TextNode {
public var body: some TextNode {
guard let node = self else { return "".eraseToAnyTextNode() }
return node.eraseToAnyTextNode()
}
}
extension Optional: NodeRepresentable where Wrapped: NodeRepresentable {
public func render() -> String {
guard let node = self else { return "" }
return node.render()
}
}

View File

@@ -24,7 +24,6 @@ func testGroup() {
}
.color(.green)
.style(.italic)
.labelStyle(color: .blue, styles: .bold)
print(type(of: group))
print(group.render())