feat: Working on documentation

This commit is contained in:
2024-12-06 17:01:11 -05:00
parent f0873d3b44
commit 1a559e0236
11 changed files with 191 additions and 73 deletions

1
.prettierignore Normal file
View File

@@ -0,0 +1 @@
**/*.docc

View File

@@ -1,3 +1,6 @@
/// A result builder for creating ``TextNode`` types, similar to how
/// `ViewBuilder` works in `SwiftUI`.
///
@resultBuilder @resultBuilder
public enum TextBuilder { public enum TextBuilder {
@@ -7,27 +10,27 @@ public enum TextBuilder {
} }
@inlinable @inlinable
public static func buildPartialBlock<N0: TextNode, N1: TextNode>(accumulated: N0, next: N1) -> NodeContainer { public static func buildPartialBlock<N0: TextNode, N1: TextNode>(accumulated: N0, next: N1) -> _NodeContainer {
.init(nodes: [accumulated, next]) .init(nodes: [accumulated, next])
} }
@inlinable @inlinable
public static func buildArray<N: TextNode>(_ components: [N]) -> NodeContainer { public static func buildArray<N: TextNode>(_ components: [N]) -> _NodeContainer {
.init(nodes: components) .init(nodes: components)
} }
@inlinable @inlinable
public static func buildBlock<N: TextNode>(_ components: N...) -> NodeContainer { public static func buildBlock<N: TextNode>(_ components: N...) -> _NodeContainer {
.init(nodes: components) .init(nodes: components)
} }
@inlinable @inlinable
public static func buildEither<N: TextNode, N1: TextNode>(first component: N) -> EitherNode<N, N1> { public static func buildEither<N: TextNode, N1: TextNode>(first component: N) -> _EitherNode<N, N1> {
.first(component) .first(component)
} }
@inlinable @inlinable
public static func buildEither<N: TextNode, N1: TextNode>(second component: N1) -> EitherNode<N, N1> { public static func buildEither<N: TextNode, N1: TextNode>(second component: N1) -> _EitherNode<N, N1> {
.second(component) .second(component)
} }
@@ -38,7 +41,8 @@ public enum TextBuilder {
} }
public enum EitherNode<N: TextNode, N1: TextNode>: TextNode { // swiftlint:disable type_name
public enum _EitherNode<N: TextNode, N1: TextNode>: TextNode {
case first(N) case first(N)
case second(N1) case second(N1)
@@ -50,7 +54,7 @@ public enum EitherNode<N: TextNode, N1: TextNode>: TextNode {
} }
} }
public struct NodeContainer: TextNode { public struct _NodeContainer: TextNode {
@usableFromInline @usableFromInline
var nodes: [any TextNode] var nodes: [any TextNode]
@@ -58,7 +62,7 @@ public struct NodeContainer: TextNode {
@usableFromInline @usableFromInline
init(nodes: [any TextNode]) { init(nodes: [any TextNode]) {
self.nodes = nodes.reduce(into: [any TextNode]()) { array, next in self.nodes = nodes.reduce(into: [any TextNode]()) { array, next in
if let many = next as? NodeContainer { if let many = next as? _NodeContainer {
array += many.nodes array += many.nodes
} else { } else {
array.append(next) array.append(next)
@@ -71,3 +75,5 @@ public struct NodeContainer: TextNode {
nodes.reduce("") { $0 + $1.render() } nodes.reduce("") { $0 + $1.render() }
} }
} }
// swiftlint:enable type_name

View File

@@ -0,0 +1,30 @@
# ``CliDocCore``
A framework for writing `cli` documentation in a way similar to `SwiftUI`, where
your types conform to ``TextNode`` and implement a ``TextNode/body`` that
returns a ``TextNode``, generally using the essential types described below.
## Topics
### Essentials
- ``VStack``
- ``HStack``
- ``Section``
- ``Group``
- ``Empty``
- ``AnyTextNode``
### Styling
- ``SectionStyle``
- ``DefaultSectionStyle``
- ``SectionConfiguration``
- ``TextStyleConfiguration``
### Base Protocols
- ``TextNode``
- ``TextNodeRepresentable``
- ``TextStyle``
- ``TextModifier``

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@@ -37,16 +37,11 @@
/// ///
/// print(mySection.render()) /// print(mySection.render())
/// ``` /// ```
/// **Note:** colored output / styling only shows in the terminal.
/// ///
/// ```bash /// _Below is an image of the output from the `mySection.render()` above._
/// ///
/// Awesome /// ![Custom section style image](section.png)
/// ///
/// My super awesome section
///
/// Note: this is super awesome
/// ```
public struct Section<Header: TextNode, Content: TextNode, Footer: TextNode>: TextNode { public struct Section<Header: TextNode, Content: TextNode, Footer: TextNode>: TextNode {
@usableFromInline @usableFromInline
@@ -124,7 +119,8 @@ public extension Section {
} }
} }
/// Holds the type-erased values of a ``Section``, used to style a section. /// Holds the type-erased values of a ``Section``, that can be used to create
/// custom styling for a section.
public struct SectionConfiguration { public struct SectionConfiguration {
/// The type-erased header of a section. /// The type-erased header of a section.
public let header: any TextNode public let header: any TextNode
@@ -143,6 +139,9 @@ public struct SectionConfiguration {
} }
} }
/// Used to declare a custom style for a ``Section``. Your custom section style
/// must conform to this protocol.
///
public protocol SectionStyle: TextModifier where Content == SectionConfiguration {} public protocol SectionStyle: TextModifier where Content == SectionConfiguration {}
public extension SectionStyle where Self == DefaultSectionStyle { public extension SectionStyle where Self == DefaultSectionStyle {

View File

@@ -1,7 +1,23 @@
/// A namespace for separator types that are used in text nodes.
///
/// These are generally used in the ``HStack``'s and ``VStack``'s to
/// specifiy how the nodes they contain are separated.
///
///
/// **Note:**
///
/// > By default nodes do not contain any separators, unless they are written inline.
/// > However, both ``HStack`` and ``VStack`` allow you to specify the separator to use
/// > to control how the individual nodes they contain are separated.
///
public enum Separator { public enum Separator {
/// Represents a horizontal separator that can be used between text nodes, typically inside /// Represents a horizontal separator that can be used between text nodes, typically inside
/// an ``HStack`` /// an ``HStack``
///
/// **Note:**
/// > By default nodes do not contain any separators, unless they are written inline.
///
public enum Horizontal: TextNode { public enum Horizontal: TextNode {
/// Separate nodes by spaces of the given count. /// Separate nodes by spaces of the given count.
case space(count: Int = 1) case space(count: Int = 1)
@@ -10,6 +26,12 @@ public enum Separator {
case tab(count: Int = 1) case tab(count: Int = 1)
/// Separate nodes by the provided string of the given count. /// Separate nodes by the provided string of the given count.
///
/// **Note:**
///
/// > This can allow for non-sensical separators, so should only be used
/// > if the provided horizontal separators do not work for you.
///
case custom(String, count: Int = 1) case custom(String, count: Int = 1)
@TextBuilder @TextBuilder
@@ -28,8 +50,34 @@ public enum Separator {
/// Represents a vertical separator that can be used between text nodes, typically inside /// Represents a vertical separator that can be used between text nodes, typically inside
/// a ``VStack`` /// a ``VStack``
///
/// **Note:**
/// > By default nodes do not contain any separators, so if you would
/// > like a blank line separating nodes, then a count of `2` is required.
/// > A count of `1` will place nodes on the next line.
///
public enum Vertical: TextNode { public enum Vertical: TextNode {
/// Separate nodes by new line characters of the given count.
///
/// **Note:**
/// > By default nodes do not contain any separtors, so if you would
/// > like a blank line separating nodes, then a count of `2` is required.
/// > A count of `1` will place nodes on the next line.
///
///
/// - Parameters:
/// - count: The count of the new lines to use.
case newLine(count: Int = 1) case newLine(count: Int = 1)
/// Separate nodes by the supplied string with the given count.
///
/// **Note:**
///
/// > This can allow for non-sensical separators, so should only be used
/// > if the provided vertical separators do not work for you.
///
/// - Parameters:
/// - count: The count of the new lines to use.
case custom(String, count: Int = 1) case custom(String, count: Int = 1)
@TextBuilder @TextBuilder

View File

@@ -1,3 +1,7 @@
/// A type that can modify a text node before it is rendered.
///
/// This allows you to create custom styles for your text-nodes.
///
public protocol TextModifier { public protocol TextModifier {
// swiftlint:disable type_name // swiftlint:disable type_name
associatedtype _Body: TextNode associatedtype _Body: TextNode
@@ -6,6 +10,10 @@ public protocol TextModifier {
associatedtype Content associatedtype Content
/// Apply custom styling to the text node.
///
/// - Parameters:
/// - content: The text node to be styled.
@TextBuilder @TextBuilder
func render(content: Content) -> Body func render(content: Content) -> Body
} }

View File

@@ -1,8 +1,14 @@
public protocol NodeRepresentable { /// A type that can produce a string to be used as a documentation
/// text node.
public protocol TextNodeRepresentable {
/// Produces the string output to use as the documentation string.
func render() -> String func render() -> String
} }
public protocol TextNode: NodeRepresentable { /// A type that can produce a string to be used as a documentation
/// text node.
public protocol TextNode: TextNodeRepresentable {
// swiftlint:disable type_name // swiftlint:disable type_name
associatedtype _Body: TextNode associatedtype _Body: TextNode
typealias Body = _Body typealias Body = _Body
@@ -19,16 +25,12 @@ public extension TextNode {
// MARK: - String // MARK: - String
extension String: NodeRepresentable { extension String: TextNodeRepresentable {
public func render() -> String { public func render() -> String { self }
self
}
} }
extension String: TextNode { extension String: TextNode {
public var body: some TextNode { public var body: some TextNode { self }
self
}
} }
// MARK: - Optional // MARK: - Optional
@@ -45,7 +47,7 @@ extension Optional: TextNode where Wrapped: TextNode {
} }
} }
extension Optional: NodeRepresentable where Wrapped: NodeRepresentable, Wrapped: TextNode { extension Optional: TextNodeRepresentable where Wrapped: TextNodeRepresentable, Wrapped: TextNode {
public func render() -> String { public func render() -> String {
body.render() body.render()
@@ -56,11 +58,11 @@ extension Optional: NodeRepresentable where Wrapped: NodeRepresentable, Wrapped:
extension Array: TextNode where Element: TextNode { extension Array: TextNode where Element: TextNode {
public var body: some TextNode { public var body: some TextNode {
NodeContainer(nodes: self) _NodeContainer(nodes: self)
} }
} }
extension Array: NodeRepresentable where Element: NodeRepresentable, Element: TextNode { extension Array: TextNodeRepresentable where Element: TextNodeRepresentable, Element: TextNode {
public func render() -> String { public func render() -> String {
body.render() body.render()
} }

View File

@@ -2,36 +2,68 @@ import Rainbow
public extension TextNode { public extension TextNode {
/// Apply coloring to a text node.
///
/// - Parameters:
/// - color: The color to apply to the text node.
@inlinable @inlinable
func color(_ color: NamedColor) -> some TextNode { func color(_ color: NamedColor) -> some TextNode {
textStyle(.color(color)) textStyle(_ColorTextStyle(.foreground(.named(color))))
} }
/// Apply coloring to a text node.
///
/// - Parameters:
/// - bit8: The color to apply to the text node.
@inlinable @inlinable
func color(_ bit8: UInt8) -> some TextNode { func color(_ bit8: UInt8) -> some TextNode {
textStyle(.color(bit8: bit8)) textStyle(_ColorTextStyle(.foreground(.bit8(bit8))))
} }
/// Apply coloring to a text node using RGB.
///
/// - Parameters:
/// - red: The red color to apply to the text node.
/// - green: The green color to apply to the text node.
/// - blue: The blue color to apply to the text node.
@inlinable @inlinable
func color(_ red: UInt8, _ green: UInt8, _ blue: UInt8) -> some TextNode { func color(_ red: UInt8, _ green: UInt8, _ blue: UInt8) -> some TextNode {
textStyle(.color(rgb: (red, green, blue))) textStyle(_ColorTextStyle(.foreground(.bit24((red, green, blue)))))
} }
/// Apply background coloring to a text node.
///
/// - Parameters:
/// - color: The color to apply to the text node.
@inlinable @inlinable
func backgroundColor(_ name: NamedBackgroundColor) -> some TextNode { func backgroundColor(_ color: NamedBackgroundColor) -> some TextNode {
textStyle(.backgroundColor(name)) textStyle(_ColorTextStyle(.background(.named(color))))
} }
/// Apply background coloring to a text node.
///
/// - Parameters:
/// - bit8: The color to apply to the text node.
@inlinable @inlinable
func backgroundColor(_ bit8: UInt8) -> some TextNode { func backgroundColor(_ bit8: UInt8) -> some TextNode {
textStyle(.backgroundColor(bit8: bit8)) textStyle(_ColorTextStyle(.background(.bit8(bit8))))
} }
/// Apply background coloring to a text node using RGB.
///
/// - Parameters:
/// - red: The red color to apply to the text node.
/// - green: The green color to apply to the text node.
/// - blue: The blue color to apply to the text node.
@inlinable @inlinable
func backgroundColor(_ red: UInt8, _ green: UInt8, _ blue: UInt8) -> some TextNode { func backgroundColor(_ red: UInt8, _ green: UInt8, _ blue: UInt8) -> some TextNode {
textStyle(.backgroundColor(rgb: (red, green, blue))) textStyle(_ColorTextStyle(.background(.bit24((red, green, blue)))))
} }
/// Apply styles to a text node.
///
/// - Parameters:
/// - styles: The styles to apply.
@inlinable @inlinable
func textStyle<S: TextStyle>(_ styles: S...) -> some TextNode { func textStyle<S: TextStyle>(_ styles: S...) -> some TextNode {
styles.reduce(render()) { string, style in styles.reduce(render()) { string, style in
@@ -39,28 +71,49 @@ public extension TextNode {
} }
} }
/// Apply a bold text style to a text node.
@inlinable @inlinable
func bold() -> some TextNode { textStyle(.bold) } func bold() -> some TextNode { textStyle(.bold) }
/// Apply a dim text style to a text node.
@inlinable @inlinable
func dim() -> some TextNode { textStyle(.dim) } func dim() -> some TextNode { textStyle(.dim) }
/// Apply an italic text style to a text node.
@inlinable @inlinable
func italic() -> some TextNode { textStyle(.italic) } func italic() -> some TextNode { textStyle(.italic) }
/// Apply an underline text style to a text node.
@inlinable @inlinable
func underline() -> some TextNode { textStyle(.underline) } func underline() -> some TextNode { textStyle(.underline) }
/// Apply a blink text style to a text node.
@inlinable @inlinable
func blink() -> some TextNode { textStyle(.blink) } func blink() -> some TextNode { textStyle(.blink) }
/// Apply a strike-through text style to a text node.
@inlinable @inlinable
func strikeThrough() -> some TextNode { textStyle(.strikeThrough) } func strikeThrough() -> some TextNode { textStyle(.strikeThrough) }
} }
/// A general purpose way of styling a text node.
///
/// This is generally used for applying styling that can work with any text node.
///
/// Most of the time you will want to customize styles for a text node's type instead
/// of using this.
public protocol TextStyle: TextModifier where Content == TextStyleConfiguration {} public protocol TextStyle: TextModifier where Content == TextStyleConfiguration {}
/// A type-erased text node that can be used for creating
/// custom styles.
///
/// This is generally used to change the style of text nodes as
/// a whole, such as applying text colors or styling such as `bold`.
///
/// Most of the time you will want to customize styles for a text node's
/// type, instead of using this.
///
public struct TextStyleConfiguration { public struct TextStyleConfiguration {
public let node: any TextNode public let node: any TextNode
@@ -70,7 +123,7 @@ public struct TextStyleConfiguration {
} }
} }
public extension TextStyle where Self == StyledText { public extension TextStyle where Self == _StyledText {
@inlinable @inlinable
static var bold: Self { .init(.bold) } static var bold: Self { .init(.bold) }
@@ -91,40 +144,8 @@ public extension TextStyle where Self == StyledText {
static var strikeThrough: Self { .init(.strikethrough) } static var strikeThrough: Self { .init(.strikethrough) }
} }
public extension TextStyle where Self == ColorTextStyle { // swiftlint:disable type_name
public struct _ColorTextStyle: TextStyle {
@inlinable
static func color(_ name: NamedColor) -> Self {
.init(.foreground(.named(name)))
}
@inlinable
static func color(bit8: UInt8) -> Self {
.init(.foreground(.bit8(bit8)))
}
@inlinable
static func color(rgb: RGB) -> Self {
.init(.foreground(.bit24(rgb)))
}
@inlinable
static func backgroundColor(_ name: NamedBackgroundColor) -> Self {
.init(.background(.named(name)))
}
@inlinable
static func backgroundColor(bit8: UInt8) -> Self {
.init(.background(.bit8(bit8)))
}
@inlinable
static func backgroundColor(rgb: RGB) -> Self {
.init(.background(.bit24(rgb)))
}
}
public struct ColorTextStyle: TextStyle {
@usableFromInline @usableFromInline
enum Style { enum Style {
case foreground(ColorType) case foreground(ColorType)
@@ -150,7 +171,7 @@ public struct ColorTextStyle: TextStyle {
} }
} }
public struct StyledText: TextStyle { public struct _StyledText: TextStyle {
@usableFromInline @usableFromInline
let style: Style let style: Style
@@ -164,3 +185,5 @@ public struct StyledText: TextStyle {
content.node.render().applyingStyle(style) content.node.render().applyingStyle(style)
} }
} }
// swiftlint:enable type_name

View File

@@ -1,6 +1,6 @@
@usableFromInline @usableFromInline
func array(from node: any TextNode) -> [any TextNode] { func array(from node: any TextNode) -> [any TextNode] {
if let container = node as? NodeContainer { if let container = node as? _NodeContainer {
return container.nodes return container.nodes
} else if let array = node as? [any TextNode] { } else if let array = node as? [any TextNode] {
return array return array

View File

@@ -4,4 +4,5 @@ preview-documentation target="CliDoc":
--disable-sandbox \ --disable-sandbox \
preview-documentation \ preview-documentation \
--target {{target}} \ --target {{target}} \
--include-extended-types --include-extended-types \
--enable-inherited-docs