feat: Adds extension to argument parser's command configuration

This commit is contained in:
2024-12-07 09:20:02 -05:00
parent 1a559e0236
commit 45ab7ca578
13 changed files with 250 additions and 5 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.png filter=lfs diff=lfs merge=lfs -text

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ DerivedData/
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc .netrc
.nvim/* .nvim/*
docs/*

View File

@@ -1,5 +1,5 @@
{ {
"originHash" : "d1c093149081cbc269ce401633fdb3414e17bc9f7c2290173f3f77bf0537c8a9", "originHash" : "d5e07b58f04c94c37ba09a0f1c9d09fc24e97c047c48b2c5a4a573fcda7f6c98",
"pins" : [ "pins" : [
{ {
"identity" : "rainbow", "identity" : "rainbow",
@@ -10,6 +10,15 @@
"version" : "4.0.1" "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", "identity" : "swift-docc-plugin",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",

View File

@@ -10,7 +10,8 @@ let package = Package(
], ],
dependencies: [ dependencies: [
.package(url: "https://github.com/onevcat/Rainbow", from: "4.0.0"), .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: [ targets: [
.target( .target(
@@ -27,6 +28,7 @@ let package = Package(
name: "CliDoc", name: "CliDoc",
dependencies: [ dependencies: [
"CliDocCore", "CliDocCore",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "Rainbow", package: "Rainbow") .product(name: "Rainbow", package: "Rainbow")
] ]
), ),

View File

@@ -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<A: TextNode, U: TextNode, D: TextNode>(
commandName: String? = nil,
abstract: Abstract<A>,
usage: Usage<U>,
discussion: Discussion<D>,
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
)
}
}

View File

@@ -0,0 +1,14 @@
import CliDocCore
public struct Abstract<Content: TextNode>: TextNode {
@usableFromInline
let content: Content
public init(@TextBuilder content: () -> Content) {
self.content = content()
}
public var body: some TextNode {
content
}
}

View File

@@ -0,0 +1,14 @@
import CliDocCore
public struct Discussion<Content: TextNode>: TextNode {
@usableFromInline
let content: Content
public init(@TextBuilder content: () -> Content) {
self.content = content()
}
public var body: some TextNode {
content
}
}

View File

@@ -1,6 +1,7 @@
import CliDocCore import CliDocCore
import Rainbow import Rainbow
// TODO: Use labeled content.
public struct Note<Label: TextNode, Content: TextNode>: TextNode { public struct Note<Label: TextNode, Content: TextNode>: TextNode {
@usableFromInline @usableFromInline
let label: Label let label: Label

View File

@@ -0,0 +1,14 @@
import CliDocCore
public struct Usage<Content: TextNode>: TextNode {
@usableFromInline
let content: Content
public init(@TextBuilder content: () -> Content) {
self.content = content()
}
public var body: some TextNode {
content
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 129 B

View File

@@ -0,0 +1,114 @@
/// A text node that consists of a label and content.
///
///
public struct LabeledContent<Label: TextNode, Content: TextNode>: 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<S: LabeledContentStyle>(_ 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
}
}
}

View File

@@ -86,11 +86,30 @@ struct CliDocCoreTests {
#expect(array.render() == "foo bar") #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: [ @Test(arguments: [
Style.bold, .italic, .dim, .underline, .blink, .strikethrough Style.bold, .italic, .dim, .underline, .blink, .strikethrough
]) ])
func testTextStyles(style: Style) { func testTextStyles(style: Style) {
let node = Group { "foo" }.textStyle(StyledText(style)) let node = Group { "foo" }.textStyle(_StyledText(style))
let string = "foo".applyingStyle(style) let string = "foo".applyingStyle(style)
#expect(node.render() == string) #expect(node.render() == string)
} }

View File

@@ -1,8 +1,28 @@
[private]
default:
@just --list
clean:
@rm -rf .build
preview-documentation target="CliDoc": preview-documentation target="CliDoc":
swift package \ # using the --enable-experimental-combined-documentation doesn't work in previews currently.
@swift package \
--disable-sandbox \ --disable-sandbox \
--allow-writing-to-directory "docs/" \
preview-documentation \ preview-documentation \
--target {{target}} \ --target {{target}} \
--include-extended-types \ --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