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

View File

@@ -1,3 +1,6 @@
/// A result builder for creating ``TextNode`` types, similar to how
/// `ViewBuilder` works in `SwiftUI`.
///
@resultBuilder
public enum TextBuilder {
@@ -7,27 +10,27 @@ public enum TextBuilder {
}
@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])
}
@inlinable
public static func buildArray<N: TextNode>(_ components: [N]) -> NodeContainer {
public static func buildArray<N: TextNode>(_ components: [N]) -> _NodeContainer {
.init(nodes: components)
}
@inlinable
public static func buildBlock<N: TextNode>(_ components: N...) -> NodeContainer {
public static func buildBlock<N: TextNode>(_ components: N...) -> _NodeContainer {
.init(nodes: components)
}
@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)
}
@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)
}
@@ -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 second(N1)
@@ -50,7 +54,7 @@ public enum EitherNode<N: TextNode, N1: TextNode>: TextNode {
}
}
public struct NodeContainer: TextNode {
public struct _NodeContainer: TextNode {
@usableFromInline
var nodes: [any TextNode]
@@ -58,7 +62,7 @@ public struct NodeContainer: TextNode {
@usableFromInline
init(nodes: [any TextNode]) {
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
} else {
array.append(next)
@@ -71,3 +75,5 @@ public struct NodeContainer: TextNode {
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())
/// ```
/// **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 {
@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 {
/// The type-erased header of a section.
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 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 {
/// Represents a horizontal separator that can be used between text nodes, typically inside
/// an ``HStack``
///
/// **Note:**
/// > By default nodes do not contain any separators, unless they are written inline.
///
public enum Horizontal: TextNode {
/// Separate nodes by spaces of the given count.
case space(count: Int = 1)
@@ -10,6 +26,12 @@ public enum Separator {
case tab(count: Int = 1)
/// 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)
@TextBuilder
@@ -28,8 +50,34 @@ public enum Separator {
/// Represents a vertical separator that can be used between text nodes, typically inside
/// 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 {
/// 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)
/// 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)
@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 {
// swiftlint:disable type_name
associatedtype _Body: TextNode
@@ -6,6 +10,10 @@ public protocol TextModifier {
associatedtype Content
/// Apply custom styling to the text node.
///
/// - Parameters:
/// - content: The text node to be styled.
@TextBuilder
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
}
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
associatedtype _Body: TextNode
typealias Body = _Body
@@ -19,16 +25,12 @@ public extension TextNode {
// MARK: - String
extension String: NodeRepresentable {
public func render() -> String {
self
}
extension String: TextNodeRepresentable {
public func render() -> String { self }
}
extension String: TextNode {
public var body: some TextNode {
self
}
public var body: some TextNode { self }
}
// 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 {
body.render()
@@ -56,11 +58,11 @@ extension Optional: NodeRepresentable where Wrapped: NodeRepresentable, Wrapped:
extension Array: TextNode where Element: 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 {
body.render()
}

View File

@@ -2,36 +2,68 @@ import Rainbow
public extension TextNode {
/// Apply coloring to a text node.
///
/// - Parameters:
/// - color: The color to apply to the text node.
@inlinable
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
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
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
func backgroundColor(_ name: NamedBackgroundColor) -> some TextNode {
textStyle(.backgroundColor(name))
func backgroundColor(_ color: NamedBackgroundColor) -> some TextNode {
textStyle(_ColorTextStyle(.background(.named(color))))
}
/// Apply background coloring to a text node.
///
/// - Parameters:
/// - bit8: The color to apply to the text node.
@inlinable
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
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
func textStyle<S: TextStyle>(_ styles: S...) -> some TextNode {
styles.reduce(render()) { string, style in
@@ -39,28 +71,49 @@ public extension TextNode {
}
}
/// Apply a bold text style to a text node.
@inlinable
func bold() -> some TextNode { textStyle(.bold) }
/// Apply a dim text style to a text node.
@inlinable
func dim() -> some TextNode { textStyle(.dim) }
/// Apply an italic text style to a text node.
@inlinable
func italic() -> some TextNode { textStyle(.italic) }
/// Apply an underline text style to a text node.
@inlinable
func underline() -> some TextNode { textStyle(.underline) }
/// Apply a blink text style to a text node.
@inlinable
func blink() -> some TextNode { textStyle(.blink) }
/// Apply a strike-through text style to a text node.
@inlinable
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 {}
/// 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 let node: any TextNode
@@ -70,7 +123,7 @@ public struct TextStyleConfiguration {
}
}
public extension TextStyle where Self == StyledText {
public extension TextStyle where Self == _StyledText {
@inlinable
static var bold: Self { .init(.bold) }
@@ -91,40 +144,8 @@ public extension TextStyle where Self == StyledText {
static var strikeThrough: Self { .init(.strikethrough) }
}
public extension TextStyle where Self == ColorTextStyle {
@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 {
// swiftlint:disable type_name
public struct _ColorTextStyle: TextStyle {
@usableFromInline
enum Style {
case foreground(ColorType)
@@ -150,7 +171,7 @@ public struct ColorTextStyle: TextStyle {
}
}
public struct StyledText: TextStyle {
public struct _StyledText: TextStyle {
@usableFromInline
let style: Style
@@ -164,3 +185,5 @@ public struct StyledText: TextStyle {
content.node.render().applyingStyle(style)
}
}
// swiftlint:enable type_name

View File

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