feat: Working on documentation
This commit is contained in:
1
.prettierignore
Normal file
1
.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
**/*.docc
|
||||
@@ -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
|
||||
|
||||
30
Sources/CliDocCore/Documentation.docc/CliDocCore.md
Normal file
30
Sources/CliDocCore/Documentation.docc/CliDocCore.md
Normal 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``
|
||||
BIN
Sources/CliDocCore/Documentation.docc/Resources/section.png
Normal file
BIN
Sources/CliDocCore/Documentation.docc/Resources/section.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.6 KiB |
@@ -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
|
||||
/// 
|
||||
///
|
||||
/// 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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user