diff --git a/.gitignore b/.gitignore index adfc5fe..2002e92 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ DerivedData/ .netrc .nvim/* docs/* +Examples/*.png +./*.png diff --git a/Examples/Sources/CliDoc-Examples/CliDoc_Examples.swift b/Examples/Sources/CliDoc-Examples/CliDoc_Examples.swift index 1fddc03..21748ee 100644 --- a/Examples/Sources/CliDoc-Examples/CliDoc_Examples.swift +++ b/Examples/Sources/CliDoc-Examples/CliDoc_Examples.swift @@ -7,7 +7,9 @@ struct Application: ParsableCommand { .init( commandName: "examples", subcommands: [ - SectionCommand.self + SectionCommand.self, + VStackCommand.self, + HStackCommand.self ] ) } diff --git a/Examples/Sources/CliDoc-Examples/HStackCommand.swift b/Examples/Sources/CliDoc-Examples/HStackCommand.swift new file mode 100644 index 0000000..778841f --- /dev/null +++ b/Examples/Sources/CliDoc-Examples/HStackCommand.swift @@ -0,0 +1,16 @@ +import ArgumentParser +import CliDocCore + +struct HStackCommand: ParsableCommand { + static let configuration = CommandConfiguration(commandName: "hstack") + + func run() throws { + let note = HStack { + "NOTE:".color(.cyan).bold() + "This is my super cool note.".italic() + } + + print() + print(note.render()) + } +} diff --git a/Examples/Sources/CliDoc-Examples/SectionCommand.swift b/Examples/Sources/CliDoc-Examples/SectionCommand.swift index 99f7c47..5c23625 100644 --- a/Examples/Sources/CliDoc-Examples/SectionCommand.swift +++ b/Examples/Sources/CliDoc-Examples/SectionCommand.swift @@ -21,10 +21,11 @@ struct SectionCommand: ParsableCommand { struct MySectionStyle: SectionStyle { func render(content: SectionConfiguration) -> some TextNode { - VStack(separator: .newLine(count: 2)) { + VStack { content.header.color(.green).bold().underline() content.content content.footer.italic() } + .separator(.newLine(count: 2)) } } diff --git a/Examples/Sources/CliDoc-Examples/VStackCommand.swift b/Examples/Sources/CliDoc-Examples/VStackCommand.swift new file mode 100644 index 0000000..c4725e5 --- /dev/null +++ b/Examples/Sources/CliDoc-Examples/VStackCommand.swift @@ -0,0 +1,21 @@ +import ArgumentParser +import CliDocCore + +struct VStackCommand: ParsableCommand { + static let configuration = CommandConfiguration(commandName: "vstack") + + func run() throws { + let vstack = VStack { + "Blob Esquire" + .color(.yellow) + .bold() + .underline() + + "Blob is a super awesome worker.".italic() + } + + print() + print(vstack.render()) + } + +} diff --git a/Examples/justfile b/Examples/justfile index 327c6f4..eb27662 100644 --- a/Examples/justfile +++ b/Examples/justfile @@ -1,3 +1,12 @@ +executable_path := "./.build/release/examples" -run command="section": - swift run examples {{command}} +build: + @swift build -c release + +run command="section": build + @{{executable_path}} {{command}} + +snapshot command="section" outputDir="${PWD}": build + @freeze --execute "{{executable_path}} {{command}}" \ + --output {{outputDir}}/"{{command}}.png" \ + --width 500 diff --git a/Sources/CliDocCore/Documentation.docc/Resources/hstack.png b/Sources/CliDocCore/Documentation.docc/Resources/hstack.png new file mode 100644 index 0000000..60a6b83 --- /dev/null +++ b/Sources/CliDocCore/Documentation.docc/Resources/hstack.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:173d651e0796486446f2e7da25c67a0171ecf1663d7d4364ea1f1b31e1712a36 +size 7949 diff --git a/Sources/CliDocCore/Documentation.docc/Resources/vstack.png b/Sources/CliDocCore/Documentation.docc/Resources/vstack.png new file mode 100644 index 0000000..8cba864 --- /dev/null +++ b/Sources/CliDocCore/Documentation.docc/Resources/vstack.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7ab2a82853234376cc7ac8829c8dbb468bf75bda0c6feba0ab239128a02df2f +size 11159 diff --git a/Sources/CliDocCore/Nodes/HStack.swift b/Sources/CliDocCore/Nodes/HStack.swift index de53b26..87b57fe 100644 --- a/Sources/CliDocCore/Nodes/HStack.swift +++ b/Sources/CliDocCore/Nodes/HStack.swift @@ -1,9 +1,28 @@ -/// A horizontal group of text nodes. +/// A horizontal stack of text nodes. +/// +/// ### Example: +/// +/// ```swift +/// let note = HStack { +/// "NOTE:".color(.cyan).bold() +/// "This is my super cool note".italic() +/// } +/// +/// print(note.render()) +/// ``` +/// +/// ![HStack example](hstack.png) +/// +/// public struct HStack: TextNode { @usableFromInline let content: [any TextNode] + /// Create a new ``HStack`` with the given text nodes. + /// + /// - Parameters: + /// - content: The content of the hstack. @inlinable public init( @TextBuilder content: () -> any TextNode @@ -18,11 +37,18 @@ public struct HStack: TextNode { } public extension HStack { - + /// Apply the given style to a ``HStack``. + /// + /// - Parameters: + /// - style: The style to apply to the ``HStack``. func style(_ style: S) -> some TextNode { style.render(content: .init(content: content)) } + /// Apply the given separator to a ``HStack``. + /// + /// - Parameters: + /// - separator: The horizontal separator to use with the ``HStack``. func separator(_ separator: Separator.Horizontal) -> some TextNode { style(.separator(separator)) } @@ -30,16 +56,28 @@ public extension HStack { // MARK: - Style +/// Style a ``HStack`` by creating a type that conforms to ``HStackStyle`` and use the +/// style by calling the ``HStack/style(_:)`` method on your instance. +/// public protocol HStackStyle: TextModifier where Content == StackConfiguration {} public extension HStackStyle where Self == HStackSeparatorStyle { - + /// Apply the given separator on a ``HStack``. + /// + /// - See Also: ``HStack/separator(_:)`` + /// + /// - Parameters: + /// - separator: The vertical separator to use with the ``HStack``. static func separator(_ separator: Separator.Horizontal) -> Self { HStackSeparatorStyle(separator: separator) } } +/// Separate items in a ``HStack`` with a given horizontal separator. +/// +/// - See Also: ``HStack/separator(_:)``. +/// public struct HStackSeparatorStyle: HStackStyle { @usableFromInline let separator: Separator.Horizontal diff --git a/Sources/CliDocCore/Nodes/StackConfiguration.swift b/Sources/CliDocCore/Nodes/StackConfiguration.swift index e55fb7f..14ee7a5 100644 --- a/Sources/CliDocCore/Nodes/StackConfiguration.swift +++ b/Sources/CliDocCore/Nodes/StackConfiguration.swift @@ -5,6 +5,9 @@ public struct StackConfiguration { public let content: [any TextNode] } +/// A helper type that removes empty text nodes, and applies a separtor between +/// the array of text nodes. +/// @usableFromInline struct AnySeparatableStackNode: TextNode { diff --git a/Sources/CliDocCore/Nodes/VStack.swift b/Sources/CliDocCore/Nodes/VStack.swift index c3162c8..bf81636 100644 --- a/Sources/CliDocCore/Nodes/VStack.swift +++ b/Sources/CliDocCore/Nodes/VStack.swift @@ -1,10 +1,30 @@ /// A vertical stack of text nodes. /// +/// ### Example: +/// +/// ```swift +/// let vStack = VStack { +/// "Blob Esquire" +/// .color(.yellow) +/// .bold() +/// .underline() +/// +/// "Blob is a super awesome worker.".italic() +/// } +/// +/// print(vStack.render()) +/// ``` +/// ![VStack rendered output](vstack.png) +/// /// public struct VStack: TextNode { @usableFromInline let content: [any TextNode] + /// Create a new ``VStack`` with the given text nodes. + /// + /// - Parameters: + /// - content: The content of the vstack. @inlinable public init( @TextBuilder content: () -> any TextNode @@ -20,10 +40,18 @@ public struct VStack: TextNode { public extension VStack { + /// Apply the given style to a ``VStack``. + /// + /// - Parameters: + /// - style: The style to apply to the ``VStack``. func style(_ style: S) -> some TextNode { style.render(content: .init(content: content.removingEmptys())) } + /// Apply the given separator to a ``VStack``. + /// + /// - Parameters: + /// - separator: The vertical separator to use with the ``VStack``. func separator(_ separator: Separator.Vertical) -> some TextNode { style(.separator(separator)) } @@ -31,16 +59,29 @@ public extension VStack { // MARK: - Style +/// Style a ``VStack`` by creating a type that conforms to ``VStackStyle`` and use the +/// style by calling the ``VStack/style(_:)`` method on your instance. +/// public protocol VStackStyle: TextModifier where Content == StackConfiguration {} public extension VStackStyle where Self == VStackSeparatorStyle { + /// Apply the given separator on a ``VStack``. + /// + /// - See Also: ``VStack/separator(_:)`` + /// + /// - Parameters: + /// - separator: The vertical separator to use with the ``VStack``. static func separator(_ separator: Separator.Vertical) -> Self { VStackSeparatorStyle(separator: separator) } } +/// Separate items in a ``VStack`` with a given vertical separator. +/// +/// - See Also: ``VStack/separator(_:)``. +/// public struct VStackSeparatorStyle: VStackStyle { @usableFromInline let separator: Separator.Vertical diff --git a/justfile b/justfile index 8cd7a93..f7ec5f0 100644 --- a/justfile +++ b/justfile @@ -7,6 +7,9 @@ default: clean: @rm -rf .build +snapshot command outputDir="./Sources/CliDocCore/Documentation.docc/Resources": + @just -f Examples/justfile snapshot {{command}} ".{{outputDir}}" + test-docker: build-docker @docker run -t --rm {{docker_image_name}}:test swift test