From 45ab7ca57818b733ceb330a305e068cfaa62b445 Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Sat, 7 Dec 2024 09:20:02 -0500 Subject: [PATCH] feat: Adds extension to argument parser's command configuration --- .gitattributes | 1 + .gitignore | 1 + Package.resolved | 11 +- Package.swift | 4 +- .../CommandConfiguration+TextNode.swift | 36 ++++++ Sources/CliDoc/Nodes/Abstract.swift | 14 +++ Sources/CliDoc/Nodes/Discussion.swift | 14 +++ Sources/CliDoc/Nodes/Note.swift | 1 + Sources/CliDoc/Nodes/Usage.swift | 14 +++ .../Documentation.docc/Resources/section.png | Bin 9843 -> 129 bytes Sources/CliDocCore/Nodes/LabeledContent.swift | 114 ++++++++++++++++++ Tests/CliDocCoreTests/CliDocCoreTests.swift | 21 +++- justfile | 24 +++- 13 files changed, 250 insertions(+), 5 deletions(-) create mode 100644 .gitattributes create mode 100644 Sources/CliDoc/CommandConfiguration+TextNode.swift create mode 100644 Sources/CliDoc/Nodes/Abstract.swift create mode 100644 Sources/CliDoc/Nodes/Discussion.swift create mode 100644 Sources/CliDoc/Nodes/Usage.swift create mode 100644 Sources/CliDocCore/Nodes/LabeledContent.swift diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..24a8e87 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.png filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index 3d9d43e..adfc5fe 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ DerivedData/ .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata .netrc .nvim/* +docs/* diff --git a/Package.resolved b/Package.resolved index 1f848a9..5c6e720 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "d1c093149081cbc269ce401633fdb3414e17bc9f7c2290173f3f77bf0537c8a9", + "originHash" : "d5e07b58f04c94c37ba09a0f1c9d09fc24e97c047c48b2c5a4a573fcda7f6c98", "pins" : [ { "identity" : "rainbow", @@ -10,6 +10,15 @@ "version" : "4.0.1" } }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "41982a3656a71c768319979febd796c6fd111d5c", + "version" : "1.5.0" + } + }, { "identity" : "swift-docc-plugin", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index 2dafdfb..27b1b7d 100644 --- a/Package.swift +++ b/Package.swift @@ -10,7 +10,8 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/onevcat/Rainbow", from: "4.0.0"), - .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0") + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0") ], targets: [ .target( @@ -27,6 +28,7 @@ let package = Package( name: "CliDoc", dependencies: [ "CliDocCore", + .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "Rainbow", package: "Rainbow") ] ), diff --git a/Sources/CliDoc/CommandConfiguration+TextNode.swift b/Sources/CliDoc/CommandConfiguration+TextNode.swift new file mode 100644 index 0000000..291685c --- /dev/null +++ b/Sources/CliDoc/CommandConfiguration+TextNode.swift @@ -0,0 +1,36 @@ +import ArgumentParser + +public extension CommandConfiguration { + + /// Generate a new command configuration, using ``TextNode``'s for the abstract, + /// usage, and discussion parameters. + /// + /// + init( + commandName: String? = nil, + abstract: Abstract, + usage: Usage, + discussion: Discussion, + version: String = "", + shouldDisplay: Bool = true, + subcommands ungroupedSubcommands: [ParsableCommand.Type] = [], + groupedSubcommands: [CommandGroup] = [], + defaultSubcommand: ParsableCommand.Type? = nil, + helpNames: NameSpecification? = nil, + aliases: [String] = [] + ) { + self.init( + commandName: commandName, + abstract: abstract.render(), + usage: usage.render(), + discussion: discussion.render(), + version: version, + shouldDisplay: shouldDisplay, + subcommands: ungroupedSubcommands, + groupedSubcommands: groupedSubcommands, + defaultSubcommand: defaultSubcommand, + helpNames: helpNames, + aliases: aliases + ) + } +} diff --git a/Sources/CliDoc/Nodes/Abstract.swift b/Sources/CliDoc/Nodes/Abstract.swift new file mode 100644 index 0000000..66b83a1 --- /dev/null +++ b/Sources/CliDoc/Nodes/Abstract.swift @@ -0,0 +1,14 @@ +import CliDocCore + +public struct Abstract: TextNode { + @usableFromInline + let content: Content + + public init(@TextBuilder content: () -> Content) { + self.content = content() + } + + public var body: some TextNode { + content + } +} diff --git a/Sources/CliDoc/Nodes/Discussion.swift b/Sources/CliDoc/Nodes/Discussion.swift new file mode 100644 index 0000000..5803502 --- /dev/null +++ b/Sources/CliDoc/Nodes/Discussion.swift @@ -0,0 +1,14 @@ +import CliDocCore + +public struct Discussion: TextNode { + @usableFromInline + let content: Content + + public init(@TextBuilder content: () -> Content) { + self.content = content() + } + + public var body: some TextNode { + content + } +} diff --git a/Sources/CliDoc/Nodes/Note.swift b/Sources/CliDoc/Nodes/Note.swift index 44f8d0c..4e9412f 100644 --- a/Sources/CliDoc/Nodes/Note.swift +++ b/Sources/CliDoc/Nodes/Note.swift @@ -1,6 +1,7 @@ import CliDocCore import Rainbow +// TODO: Use labeled content. public struct Note: TextNode { @usableFromInline let label: Label diff --git a/Sources/CliDoc/Nodes/Usage.swift b/Sources/CliDoc/Nodes/Usage.swift new file mode 100644 index 0000000..884da08 --- /dev/null +++ b/Sources/CliDoc/Nodes/Usage.swift @@ -0,0 +1,14 @@ +import CliDocCore + +public struct Usage: TextNode { + @usableFromInline + let content: Content + + public init(@TextBuilder content: () -> Content) { + self.content = content() + } + + public var body: some TextNode { + content + } +} diff --git a/Sources/CliDocCore/Documentation.docc/Resources/section.png b/Sources/CliDocCore/Documentation.docc/Resources/section.png index 631346e77c0275ca3510d5528127cd5a40d01adf..4851e8000afc4b24df6ce52bc906143322eb0de5 100644 GIT binary patch literal 129 zcmWN?%MrpL5CG6SRnUNe<+DJ#VG)EGm5fVruzG!$chwJ%`IddHgLkDKW8I#OxBva5 zt<0C=qZW06nBy+FXXA0l;4}h3wh%1<<_JwkPbs&O9hijHHq?R8P)$C?ngA>47HuG) MEO>mYOwn2K1B6N@L;wH) literal 9843 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: TextNode { + + @usableFromInline + let label: Label + + @usableFromInline + let content: Content + + @inlinable + public init( + @TextBuilder _ content: () -> Content, + @TextBuilder label: () -> Label + ) { + self.label = label() + self.content = content() + } + + @inlinable + public var body: some TextNode { + style(.default) + } +} + +public extension LabeledContent { + + /// Apply the given style to the labeled content. + /// + /// - Parameters: + /// - style: The labeled content style to apply. + @inlinable + func style(_ style: S) -> some TextNode { + style.render(content: .init(label: label, content: content)) + } +} + +/// Holds the type-erased label and content of a ``LabeledContent`` text node. +/// +/// This is used when creating custom styles for the ``LabeledContent``. +/// +public struct LabeledContentConfiguration { + + /// The type-erased label text node. + public let label: any TextNode + + /// The type-erased content text node. + public let content: any TextNode + + @usableFromInline + init(label: any TextNode, content: any TextNode) { + self.label = label + self.content = content + } +} + +public protocol LabeledContentStyle: TextModifier where Content == LabeledContentConfiguration {} + +public extension LabeledContentStyle where Self == HorizontalLabeledContentStyle { + + static var `default`: Self { + horizontal() + } + + @inlinable + static func horizontal(separator: Separator.Horizontal = .space()) -> Self { + HorizontalLabeledContentStyle(separator: separator) + } +} + +public extension LabeledContentStyle where Self == VerticalLabeledContentStyle { + + @inlinable + static func vertical(separator: Separator.Vertical = .newLine()) -> Self { + VerticalLabeledContentStyle(separator: separator) + } +} + +public struct HorizontalLabeledContentStyle: LabeledContentStyle { + + @usableFromInline + let separator: Separator.Horizontal + + @usableFromInline + init(separator: Separator.Horizontal) { + self.separator = separator + } + + public func render(content: LabeledContentConfiguration) -> some TextNode { + HStack(separator: separator) { + content.label + content.content + } + } +} + +public struct VerticalLabeledContentStyle: LabeledContentStyle { + + @usableFromInline + let separator: Separator.Vertical + + @usableFromInline + init(separator: Separator.Vertical) { + self.separator = separator + } + + public func render(content: LabeledContentConfiguration) -> some TextNode { + VStack(separator: separator) { + content.label + content.content + } + } +} diff --git a/Tests/CliDocCoreTests/CliDocCoreTests.swift b/Tests/CliDocCoreTests/CliDocCoreTests.swift index 8ce5dea..bf3cb8a 100644 --- a/Tests/CliDocCoreTests/CliDocCoreTests.swift +++ b/Tests/CliDocCoreTests/CliDocCoreTests.swift @@ -86,11 +86,30 @@ struct CliDocCoreTests { #expect(array.render() == "foo bar") } + @Test + func testLabeledContent() { + let horizontal = LabeledContent { + "Content" + } label: { + "Label:".color(.yellow).bold() + } + + let expected = """ + \("Label:".yellow.bold) Content + """ + #expect(horizontal.render() == expected) + + #expect(horizontal.style(.vertical()).render() == """ + \("Label:".yellow.bold) + Content + """) + } + @Test(arguments: [ Style.bold, .italic, .dim, .underline, .blink, .strikethrough ]) func testTextStyles(style: Style) { - let node = Group { "foo" }.textStyle(StyledText(style)) + let node = Group { "foo" }.textStyle(_StyledText(style)) let string = "foo".applyingStyle(style) #expect(node.render() == string) } diff --git a/justfile b/justfile index b7ecd9e..d5b903f 100644 --- a/justfile +++ b/justfile @@ -1,8 +1,28 @@ +[private] +default: + @just --list + +clean: + @rm -rf .build + preview-documentation target="CliDoc": - swift package \ + # using the --enable-experimental-combined-documentation doesn't work in previews currently. + @swift package \ --disable-sandbox \ + --allow-writing-to-directory "docs/" \ preview-documentation \ --target {{target}} \ --include-extended-types \ - --enable-inherited-docs + --enable-inherited-docs \ + +build-documentation: + swift package \ + --disable-sandbox \ + --allow-writing-to-directory "docs/" \ + generate-documentation \ + --target CliDoc \ + --target CliDocCore \ + --output-path "docs/" \ + --transform-for-static-hosting \ + --enable-experimental-combined-documentation