feat: Adds extension to argument parser's command configuration
This commit is contained in:
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ DerivedData/
|
|||||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||||
.netrc
|
.netrc
|
||||||
.nvim/*
|
.nvim/*
|
||||||
|
docs/*
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|||||||
36
Sources/CliDoc/CommandConfiguration+TextNode.swift
Normal file
36
Sources/CliDoc/CommandConfiguration+TextNode.swift
Normal 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Sources/CliDoc/Nodes/Abstract.swift
Normal file
14
Sources/CliDoc/Nodes/Abstract.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Sources/CliDoc/Nodes/Discussion.swift
Normal file
14
Sources/CliDoc/Nodes/Discussion.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
14
Sources/CliDoc/Nodes/Usage.swift
Normal file
14
Sources/CliDoc/Nodes/Usage.swift
Normal 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 |
114
Sources/CliDocCore/Nodes/LabeledContent.swift
Normal file
114
Sources/CliDocCore/Nodes/LabeledContent.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
24
justfile
24
justfile
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user