From 1a559e0236853f65deccc93ee52e4e4957c27abe Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Fri, 6 Dec 2024 17:01:11 -0500 Subject: [PATCH] feat: Working on documentation --- .prettierignore | 1 + Sources/CliDocCore/Builder.swift | 22 ++-- .../Documentation.docc/CliDocCore.md | 30 +++++ .../Documentation.docc/Resources/section.png | Bin 0 -> 9843 bytes Sources/CliDocCore/Nodes/Section.swift | 15 ++- Sources/CliDocCore/Nodes/Separator.swift | 48 ++++++++ Sources/CliDocCore/TextModifier.swift | 8 ++ Sources/CliDocCore/TextNode.swift | 26 +++-- Sources/CliDocCore/TextStyle.swift | 109 +++++++++++------- Sources/CliDocCore/Utils.swift | 2 +- justfile | 3 +- 11 files changed, 191 insertions(+), 73 deletions(-) create mode 100644 .prettierignore create mode 100644 Sources/CliDocCore/Documentation.docc/CliDocCore.md create mode 100644 Sources/CliDocCore/Documentation.docc/Resources/section.png diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..8c96693 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +**/*.docc diff --git a/Sources/CliDocCore/Builder.swift b/Sources/CliDocCore/Builder.swift index 3039cfd..40bbb46 100644 --- a/Sources/CliDocCore/Builder.swift +++ b/Sources/CliDocCore/Builder.swift @@ -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(accumulated: N0, next: N1) -> NodeContainer { + public static func buildPartialBlock(accumulated: N0, next: N1) -> _NodeContainer { .init(nodes: [accumulated, next]) } @inlinable - public static func buildArray(_ components: [N]) -> NodeContainer { + public static func buildArray(_ components: [N]) -> _NodeContainer { .init(nodes: components) } @inlinable - public static func buildBlock(_ components: N...) -> NodeContainer { + public static func buildBlock(_ components: N...) -> _NodeContainer { .init(nodes: components) } @inlinable - public static func buildEither(first component: N) -> EitherNode { + public static func buildEither(first component: N) -> _EitherNode { .first(component) } @inlinable - public static func buildEither(second component: N1) -> EitherNode { + public static func buildEither(second component: N1) -> _EitherNode { .second(component) } @@ -38,7 +41,8 @@ public enum TextBuilder { } -public enum EitherNode: TextNode { +// swiftlint:disable type_name +public enum _EitherNode: TextNode { case first(N) case second(N1) @@ -50,7 +54,7 @@ public enum EitherNode: 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 diff --git a/Sources/CliDocCore/Documentation.docc/CliDocCore.md b/Sources/CliDocCore/Documentation.docc/CliDocCore.md new file mode 100644 index 0000000..70c69ab --- /dev/null +++ b/Sources/CliDocCore/Documentation.docc/CliDocCore.md @@ -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`` diff --git a/Sources/CliDocCore/Documentation.docc/Resources/section.png b/Sources/CliDocCore/Documentation.docc/Resources/section.png new file mode 100644 index 0000000000000000000000000000000000000000..631346e77c0275ca3510d5528127cd5a40d01adf GIT binary patch literal 9843 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: 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 { diff --git a/Sources/CliDocCore/Nodes/Separator.swift b/Sources/CliDocCore/Nodes/Separator.swift index 4e7e9b6..b6fea09 100644 --- a/Sources/CliDocCore/Nodes/Separator.swift +++ b/Sources/CliDocCore/Nodes/Separator.swift @@ -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 diff --git a/Sources/CliDocCore/TextModifier.swift b/Sources/CliDocCore/TextModifier.swift index 46a9e1c..5d413a3 100644 --- a/Sources/CliDocCore/TextModifier.swift +++ b/Sources/CliDocCore/TextModifier.swift @@ -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 } diff --git a/Sources/CliDocCore/TextNode.swift b/Sources/CliDocCore/TextNode.swift index fd3ffce..0cdcb02 100644 --- a/Sources/CliDocCore/TextNode.swift +++ b/Sources/CliDocCore/TextNode.swift @@ -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() } diff --git a/Sources/CliDocCore/TextStyle.swift b/Sources/CliDocCore/TextStyle.swift index 3f8644a..06e837e 100644 --- a/Sources/CliDocCore/TextStyle.swift +++ b/Sources/CliDocCore/TextStyle.swift @@ -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(_ 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 diff --git a/Sources/CliDocCore/Utils.swift b/Sources/CliDocCore/Utils.swift index 183aa9b..9ec4384 100644 --- a/Sources/CliDocCore/Utils.swift +++ b/Sources/CliDocCore/Utils.swift @@ -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 diff --git a/justfile b/justfile index 8f93a6e..b7ecd9e 100644 --- a/justfile +++ b/justfile @@ -4,4 +4,5 @@ preview-documentation target="CliDoc": --disable-sandbox \ preview-documentation \ --target {{target}} \ - --include-extended-types + --include-extended-types \ + --enable-inherited-docs