feat: Moves core functionality into it's own library.
This commit is contained in:
21
Sources/CliDocCore/Nodes/AnyTextNode.swift
Normal file
21
Sources/CliDocCore/Nodes/AnyTextNode.swift
Normal file
@@ -0,0 +1,21 @@
|
||||
/// A type-erased text node.
|
||||
public struct AnyTextNode: TextNode {
|
||||
@usableFromInline
|
||||
let makeString: () -> String
|
||||
|
||||
@inlinable
|
||||
public init<N: TextNode>(_ node: N) {
|
||||
self.makeString = node.render
|
||||
}
|
||||
|
||||
@inlinable
|
||||
public var body: some TextNode { makeString() }
|
||||
}
|
||||
|
||||
public extension TextNode {
|
||||
|
||||
@inlinable
|
||||
func eraseToAnyTextNode() -> AnyTextNode {
|
||||
AnyTextNode(self)
|
||||
}
|
||||
}
|
||||
11
Sources/CliDocCore/Nodes/Empty.swift
Normal file
11
Sources/CliDocCore/Nodes/Empty.swift
Normal file
@@ -0,0 +1,11 @@
|
||||
/// An empty text node.
|
||||
public struct Empty: TextNode {
|
||||
|
||||
@inlinable
|
||||
public init() {}
|
||||
|
||||
@inlinable
|
||||
public var body: some TextNode {
|
||||
""
|
||||
}
|
||||
}
|
||||
16
Sources/CliDocCore/Nodes/Group.swift
Normal file
16
Sources/CliDocCore/Nodes/Group.swift
Normal file
@@ -0,0 +1,16 @@
|
||||
public struct Group<Content: TextNode>: TextNode {
|
||||
@usableFromInline
|
||||
var content: Content
|
||||
|
||||
@inlinable
|
||||
public init(
|
||||
@TextBuilder content: () -> Content
|
||||
) {
|
||||
self.content = content()
|
||||
}
|
||||
|
||||
@inlinable
|
||||
public var body: some TextNode {
|
||||
content
|
||||
}
|
||||
}
|
||||
23
Sources/CliDocCore/Nodes/HStack.swift
Normal file
23
Sources/CliDocCore/Nodes/HStack.swift
Normal file
@@ -0,0 +1,23 @@
|
||||
public struct HStack: TextNode {
|
||||
|
||||
@usableFromInline
|
||||
let content: [any TextNode]
|
||||
|
||||
@usableFromInline
|
||||
let separator: any TextNode
|
||||
|
||||
@inlinable
|
||||
public init(
|
||||
spacing: Int = 1,
|
||||
@TextBuilder content: () -> any TextNode
|
||||
) {
|
||||
self.content = array(from: content())
|
||||
self.separator = seperator(" ", count: spacing > 0 ? spacing - 1 : 0)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
public var body: some TextNode {
|
||||
content.removingEmptys()
|
||||
.joined(separator: separator.render())
|
||||
}
|
||||
}
|
||||
96
Sources/CliDocCore/Nodes/Section.swift
Normal file
96
Sources/CliDocCore/Nodes/Section.swift
Normal file
@@ -0,0 +1,96 @@
|
||||
// TODO: Add vertical spacing.
|
||||
public struct Section<Header: TextNode, Content: TextNode, Footer: TextNode>: TextNode {
|
||||
|
||||
@usableFromInline
|
||||
let header: Header
|
||||
|
||||
@usableFromInline
|
||||
let content: Content
|
||||
|
||||
@usableFromInline
|
||||
let footer: Footer
|
||||
|
||||
@inlinable
|
||||
public init(
|
||||
@TextBuilder content: () -> Content,
|
||||
@TextBuilder header: () -> Header,
|
||||
@TextBuilder footer: () -> Footer
|
||||
) {
|
||||
self.header = header()
|
||||
self.content = content()
|
||||
self.footer = footer()
|
||||
}
|
||||
|
||||
public var body: some TextNode {
|
||||
style(.default)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Section where Footer == Empty {
|
||||
@inlinable
|
||||
init(
|
||||
@TextBuilder content: () -> Content,
|
||||
@TextBuilder header: () -> Header
|
||||
) {
|
||||
self.init(content: content, header: header) { Empty() }
|
||||
}
|
||||
}
|
||||
|
||||
public extension Section where Header == Empty {
|
||||
@inlinable
|
||||
init(
|
||||
@TextBuilder content: () -> Content,
|
||||
@TextBuilder footer: () -> Footer
|
||||
) {
|
||||
self.init(content: content, header: { Empty() }, footer: footer)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Section where Header == Empty, Footer == Empty {
|
||||
@inlinable
|
||||
init(
|
||||
@TextBuilder content: () -> Content
|
||||
) {
|
||||
self.init(content: content, header: { Empty() }, footer: { Empty() })
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Style
|
||||
|
||||
public extension Section {
|
||||
|
||||
@inlinable
|
||||
func style<S: SectionStyle>(_ style: S) -> some TextNode {
|
||||
style.render(content: .init(header: header, content: content, footer: footer))
|
||||
}
|
||||
}
|
||||
|
||||
public struct SectionConfiguration {
|
||||
public let header: any TextNode
|
||||
public let content: any TextNode
|
||||
public let footer: any TextNode
|
||||
|
||||
@usableFromInline
|
||||
init(header: any TextNode, content: any TextNode, footer: any TextNode) {
|
||||
self.header = header
|
||||
self.content = content
|
||||
self.footer = footer
|
||||
}
|
||||
}
|
||||
|
||||
public protocol SectionStyle: NodeModifier where Content == SectionConfiguration {}
|
||||
|
||||
public extension SectionStyle where Self == DefaultSectionStyle {
|
||||
static var `default`: Self { DefaultSectionStyle() }
|
||||
}
|
||||
|
||||
public struct DefaultSectionStyle: SectionStyle {
|
||||
|
||||
public func render(content: SectionConfiguration) -> some TextNode {
|
||||
VStack(spacing: 2) {
|
||||
content.header
|
||||
content.content
|
||||
content.footer
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Sources/CliDocCore/Nodes/VStack.swift
Normal file
23
Sources/CliDocCore/Nodes/VStack.swift
Normal file
@@ -0,0 +1,23 @@
|
||||
public struct VStack: TextNode {
|
||||
|
||||
@usableFromInline
|
||||
let content: [any TextNode]
|
||||
|
||||
@usableFromInline
|
||||
let separator: any TextNode
|
||||
|
||||
@inlinable
|
||||
public init(
|
||||
spacing: Int = 1,
|
||||
@TextBuilder content: () -> any TextNode
|
||||
) {
|
||||
self.content = array(from: content())
|
||||
self.separator = seperator("\n", count: spacing > 0 ? spacing - 1 : 0)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
public var body: some TextNode {
|
||||
content.removingEmptys()
|
||||
.joined(separator: separator.render())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user