wip
This commit is contained in:
@@ -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() }
|
||||
}
|
||||
}
|
||||
|
||||
24
Sources/CliDoc2/Modifiers/ColorModifier.swift
Normal file
24
Sources/CliDoc2/Modifiers/ColorModifier.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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() }
|
||||
}
|
||||
}
|
||||
|
||||
32
Sources/CliDoc2/Modifiers/StyleModifier.swift
Normal file
32
Sources/CliDoc2/Modifiers/StyleModifier.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
37
Sources/CliDoc2/Nodes/Group.swift
Normal file
37
Sources/CliDoc2/Nodes/Group.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Sources/CliDoc2/Nodes/Label.swift
Normal file
19
Sources/CliDoc2/Nodes/Label.swift
Normal 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
|
||||
}
|
||||
}
|
||||
51
Sources/CliDoc2/Nodes/Note.swift
Normal file
51
Sources/CliDoc2/Nodes/Note.swift
Normal 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)
|
||||
}
|
||||
|
||||
}
|
||||
21
Sources/CliDoc2/Nodes/ShellCommand.swift
Normal file
21
Sources/CliDoc2/Nodes/ShellCommand.swift
Normal 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: " ")
|
||||
}
|
||||
}
|
||||
61
Sources/CliDoc2/TextNode.swift
Normal file
61
Sources/CliDoc2/TextNode.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user