feat: Working on node builder

This commit is contained in:
2024-12-01 15:25:42 -05:00
parent ff49b12198
commit 96a1fac07b
11 changed files with 357 additions and 21 deletions

View File

@@ -2,6 +2,7 @@ import ArgumentParser
public extension CommandConfiguration {
/// Use `NodeBuilder` syntax for generating the abstract and discussion parameters.
init(
commandName: String? = nil,
abstract: Abstract,

View File

@@ -0,0 +1,58 @@
public enum LabelContentStyle: Sendable {
case inline
case `default`
case custom(any NodeRepresentable)
}
public extension NodeRepresentable {
@inlinable
func labeledContentStyle(_ style: LabelContentStyle) -> any NodeRepresentable {
modifier(LabeledContentStyleModifier(style: style))
}
}
@usableFromInline
struct LabeledContentStyleModifier: NodeModifier {
@usableFromInline
let style: LabelContentStyle
@usableFromInline
init(style: LabelContentStyle) {
self.style = style
}
@usableFromInline
func render(_ node: any NodeRepresentable) -> any NodeRepresentable {
if let many = node as? _ManyNode {
var nodes = many.nodes
print("nodes: \(nodes)")
for (idx, node) in nodes.enumerated() {
if let labeled = node as? LabeledContent {
let newNode = labeled.applyingContentStyle(style)
nodes[idx] = newNode
}
}
return _ManyNode(nodes, separator: many.separator)
} else if let labeled = node as? LabeledContent {
return labeled.applyingContentStyle(style)
}
print("no many or labeled nodes found in: \(node)")
return node
}
}
private extension LabeledContent {
func applyingContentStyle(_ style: LabelContentStyle) -> Self {
switch style {
case .inline:
return .init(separator: " ") { label } content: { content }
case .default:
return .init(separator: "\n") { label } content: { content }
case let .custom(custom):
return .init(separator: custom) { label } content: { content }
}
}
}

26
Sources/CliDoc/Node.swift Normal file
View File

@@ -0,0 +1,26 @@
// swiftlint:disable type_name
public protocol Node: NodeRepresentable {
associatedtype _Body: NodeRepresentable
typealias Body = _Body
@NodeBuilder
var body: Body { get }
}
// swiftlint:enable type_name
public extension Node {
@inlinable
func render() -> String {
body.render()
}
}
public struct Foo: Node {
public init() {}
public var body: some NodeRepresentable {
LabeledContent("Foo") { "Bar" }
}
}

View File

@@ -16,16 +16,16 @@ public enum NodeBuilder {
.init(components, separator: .empty())
}
public static func buildEither<N0: NodeRepresentable, N1: NodeRepresentable>(
first component: N0
) -> _ConditionalNode<N0, N1> {
.first(component)
public static func buildEither<N: NodeRepresentable>(
first component: N
) -> N {
component
}
public static func buildEither<N0: NodeRepresentable, N1: NodeRepresentable>(
second component: N1
) -> _ConditionalNode<N0, N1> {
.second(component)
public static func buildEither<N: NodeRepresentable>(
second component: N
) -> N {
component
}
public static func buildBlock<N: NodeRepresentable>(_ components: N...) -> _ManyNode {
@@ -37,8 +37,13 @@ public enum NodeBuilder {
// expression.eraseToAnyNode()
// }
public static func buildOptional<N: NodeRepresentable>(_ component: N?) -> AnyNode {
component?.eraseToAnyNode() ?? AnyNode { Text.empty() }
public static func buildOptional<N: NodeRepresentable>(_ component: N?) -> _ConditionalNode<N, Text> {
switch component {
case let .some(node):
return .first(node)
case .none:
return .second(.empty())
}
}
public static func buildFinalResult<N: NodeRepresentable>(_ component: N) -> N {
@@ -79,15 +84,18 @@ public struct _ManyNode: NodeRepresentable {
@usableFromInline
init(
_ nodes: [any NodeRepresentable],
separator: AnyNode = Text.empty().eraseToAnyNode()
separator: any NodeRepresentable = "\n" as any NodeRepresentable
) {
self.nodes = nodes
self.separator = separator
}
@inlinable
public init(_ nodes: [any NodeRepresentable], separator: any NodeRepresentable) {
self.nodes = nodes
// Flatten the nodes.
var allNodes = [any NodeRepresentable]()
for node in nodes {
if let many = node as? Self {
allNodes += many.nodes
} else {
allNodes.append(node)
}
}
self.nodes = allNodes
self.separator = separator
}

View File

@@ -0,0 +1,30 @@
public struct Section: NodeRepresentable {
@usableFromInline
let content: any NodeRepresentable
public init(@NodeBuilder content: () -> any NodeRepresentable) {
self.content = content()
}
public func render() -> String {
guard let many = content as? _ManyNode else {
return content.render() + "\n\n"
}
let node = _ManyNode(many.nodes, separator: "\n\n")
return node.render()
// var output = ""
// let lastIndex = many.nodes.count - 1
//
// for (idx, content) in many.nodes.enumerated() {
// if idx != lastIndex {
// output += content.render() + "\n\n"
// } else {
// output += content.render() + "\n"
// }
// }
// return output
}
}

View File

@@ -0,0 +1,41 @@
public struct ShellCommand: NodeRepresentable {
@usableFromInline
let symbol: any NodeRepresentable
@usableFromInline
let command: any NodeRepresentable
@inlinable
public init(
@NodeBuilder symbol: () -> any NodeRepresentable,
@NodeBuilder command: () -> any NodeRepresentable
) {
self.symbol = symbol()
self.command = command()
}
@inlinable
public init(
symbol: String = " $",
@NodeBuilder command: () -> any NodeRepresentable
) {
self.init { symbol } command: { command() }
}
@inlinable
public init(
symbol: String = " $",
command: String
) {
self.init { symbol } command: { command }
}
@inlinable
public func render() -> String {
Group(separator: " ") {
symbol
command
}.render()
}
}