feat: Working on documentation
This commit is contained in:
8
Examples/.gitignore
vendored
Normal file
8
Examples/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.DS_Store
|
||||||
|
/.build
|
||||||
|
/Packages
|
||||||
|
xcuserdata/
|
||||||
|
DerivedData/
|
||||||
|
.swiftpm/configuration/registries.json
|
||||||
|
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||||
|
.netrc
|
||||||
24
Examples/Package.resolved
Normal file
24
Examples/Package.resolved
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"originHash" : "afba34e2c2164a53d5b868cb4ece6f0e542fcaed383f1b226ed9179283d0e060",
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "rainbow",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/onevcat/Rainbow",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "e0dada9cd44e3fa7ec3b867e49a8ddbf543e3df3",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 3
|
||||||
|
}
|
||||||
27
Examples/Package.swift
Normal file
27
Examples/Package.swift
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// swift-tools-version: 6.0
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "CliDoc-Examples",
|
||||||
|
products: [
|
||||||
|
.executable(name: "examples", targets: ["CliDoc-Examples"])
|
||||||
|
],
|
||||||
|
dependencies: [
|
||||||
|
.package(path: "../"),
|
||||||
|
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0")
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
.executableTarget(
|
||||||
|
name: "CliDoc-Examples",
|
||||||
|
dependencies: [
|
||||||
|
.product(name: "CliDoc", package: "swift-cli-doc"),
|
||||||
|
.product(name: "ArgumentParser", package: "swift-argument-parser")
|
||||||
|
]
|
||||||
|
),
|
||||||
|
.testTarget(
|
||||||
|
name: "CliDoc-ExamplesTests",
|
||||||
|
dependencies: ["CliDoc-Examples"]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
14
Examples/Sources/CliDoc-Examples/CliDoc_Examples.swift
Normal file
14
Examples/Sources/CliDoc-Examples/CliDoc_Examples.swift
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import ArgumentParser
|
||||||
|
import CliDoc
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct Application: ParsableCommand {
|
||||||
|
static var configuration: CommandConfiguration {
|
||||||
|
.init(
|
||||||
|
commandName: "examples",
|
||||||
|
subcommands: [
|
||||||
|
SectionCommand.self
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
30
Examples/Sources/CliDoc-Examples/SectionCommand.swift
Normal file
30
Examples/Sources/CliDoc-Examples/SectionCommand.swift
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import ArgumentParser
|
||||||
|
import CliDocCore
|
||||||
|
|
||||||
|
struct SectionCommand: ParsableCommand {
|
||||||
|
|
||||||
|
static var configuration: CommandConfiguration {
|
||||||
|
.init(commandName: "section")
|
||||||
|
}
|
||||||
|
|
||||||
|
func run() throws {
|
||||||
|
let section = Section {
|
||||||
|
"My super awesome section"
|
||||||
|
} header: {
|
||||||
|
"Awesome"
|
||||||
|
} footer: {
|
||||||
|
"Note: this is super awesome"
|
||||||
|
}
|
||||||
|
print(section.style(MySectionStyle()).render())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MySectionStyle: SectionStyle {
|
||||||
|
func render(content: SectionConfiguration) -> some TextNode {
|
||||||
|
VStack(separator: .newLine(count: 2)) {
|
||||||
|
content.header.color(.green).bold().underline()
|
||||||
|
content.content
|
||||||
|
content.footer.italic()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import Testing
|
||||||
|
@testable import CliDoc_Examples
|
||||||
|
|
||||||
|
@Test func example() async throws {
|
||||||
|
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
|
||||||
|
}
|
||||||
3
Examples/justfile
Normal file
3
Examples/justfile
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
run command="section":
|
||||||
|
swift run examples {{command}}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "ce5e40ef3be5875abe47bbfe26413215dee9b937de7df5293343757c7476d755",
|
"originHash" : "d1c093149081cbc269ce401633fdb3414e17bc9f7c2290173f3f77bf0537c8a9",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "rainbow",
|
"identity" : "rainbow",
|
||||||
@@ -9,6 +9,24 @@
|
|||||||
"revision" : "e0dada9cd44e3fa7ec3b867e49a8ddbf543e3df3",
|
"revision" : "e0dada9cd44e3fa7ec3b867e49a8ddbf543e3df3",
|
||||||
"version" : "4.0.1"
|
"version" : "4.0.1"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-docc-plugin",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/apple/swift-docc-plugin",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "85e4bb4e1cd62cec64a4b8e769dcefdf0c5b9d64",
|
||||||
|
"version" : "1.4.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-docc-symbolkit",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/swiftlang/swift-docc-symbolkit",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "b45d1f2ed151d057b54504d653e0da5552844e34",
|
||||||
|
"version" : "1.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version" : 3
|
"version" : 3
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ let package = Package(
|
|||||||
.library(name: "CliDoc", targets: ["CliDoc"])
|
.library(name: "CliDoc", targets: ["CliDoc"])
|
||||||
],
|
],
|
||||||
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")
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import Rainbow
|
import Rainbow
|
||||||
|
|
||||||
public struct ExampleSection<Header: TextNode, Label: TextNode>: TextNode {
|
|
||||||
public typealias Example = (label: String, example: String)
|
public typealias Example = (label: String, example: String)
|
||||||
|
|
||||||
|
public struct ExampleSection<Header: TextNode, Label: TextNode>: TextNode {
|
||||||
|
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
let configuration: ExampleSectionConfiguration
|
let configuration: ExampleSectionConfiguration
|
||||||
|
|
||||||
@@ -40,29 +41,26 @@ public struct ExampleSection<Header: TextNode, Label: TextNode>: TextNode {
|
|||||||
|
|
||||||
/// The type-erased configuration of an ``ExampleSection``
|
/// The type-erased configuration of an ``ExampleSection``
|
||||||
public struct ExampleSectionConfiguration {
|
public struct ExampleSectionConfiguration {
|
||||||
@usableFromInline
|
public let title: any TextNode
|
||||||
let title: any TextNode
|
|
||||||
|
public let label: any TextNode
|
||||||
|
|
||||||
|
public let examples: [Example]
|
||||||
|
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
let label: any TextNode
|
init(title: any TextNode, label: any TextNode, examples: [Example]) {
|
||||||
|
|
||||||
@usableFromInline
|
|
||||||
let examples: [ExampleSection.Example]
|
|
||||||
|
|
||||||
@usableFromInline
|
|
||||||
init(title: any TextNode, label: any TextNode, examples: [ExampleSection.Example]) {
|
|
||||||
self.title = title
|
self.title = title
|
||||||
self.label = label
|
self.label = label
|
||||||
self.examples = examples
|
self.examples = examples
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The configuration context for the `examples` of an ``ExampleSection``.
|
||||||
public struct ExampleConfiguration {
|
public struct ExampleConfiguration {
|
||||||
@usableFromInline
|
public let examples: [Example]
|
||||||
let examples: [ExampleSection.Example]
|
|
||||||
|
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
init(examples: [ExampleSection.Example]) {
|
init(examples: [Example]) {
|
||||||
self.examples = examples
|
self.examples = examples
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +83,7 @@ public extension ExampleSection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Array where Element == ExampleSection.Example {
|
extension Array where Element == Example {
|
||||||
@inlinable
|
@inlinable
|
||||||
func exampleStyle<S: ExampleStyle>(_ style: S) -> some TextNode {
|
func exampleStyle<S: ExampleStyle>(_ style: S) -> some TextNode {
|
||||||
style.render(content: .init(examples: self))
|
style.render(content: .init(examples: self))
|
||||||
@@ -152,8 +150,8 @@ public struct DefaultExampleStyle: ExampleStyle {
|
|||||||
VStack(separator: .newLine(count: 2)) {
|
VStack(separator: .newLine(count: 2)) {
|
||||||
content.examples.map { example in
|
content.examples.map { example in
|
||||||
VStack {
|
VStack {
|
||||||
Label(example.label.green.bold)
|
example.label.color(.green).bold()
|
||||||
ShellCommand { example.example }.style(.default)
|
ShellCommand(example.example).style(.default)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import CliDocCore
|
||||||
import Rainbow
|
import Rainbow
|
||||||
|
|
||||||
public struct Note<Label: TextNode, Content: TextNode>: TextNode {
|
public struct Note<Label: TextNode, Content: TextNode>: TextNode {
|
||||||
@@ -18,7 +19,7 @@ public struct Note<Label: TextNode, Content: TextNode>: TextNode {
|
|||||||
|
|
||||||
@inlinable
|
@inlinable
|
||||||
public var body: some TextNode {
|
public var body: some TextNode {
|
||||||
noteStyle(.default)
|
style(.default)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,11 +27,10 @@ public extension Note where Label == String {
|
|||||||
|
|
||||||
@inlinable
|
@inlinable
|
||||||
init(
|
init(
|
||||||
_ label: String = "NOTE:",
|
_ label: @autoclosure () -> String = "NOTE:",
|
||||||
@TextBuilder content: () -> Content
|
@TextBuilder content: () -> Content
|
||||||
) {
|
) {
|
||||||
self.label = label
|
self.init(label, content: content)
|
||||||
self.content = content()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static func important(
|
static func important(
|
||||||
@@ -48,6 +48,7 @@ public extension Note where Label == String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove the important and see also.
|
||||||
public extension Note where Label == String, Content == String {
|
public extension Note where Label == String, Content == String {
|
||||||
|
|
||||||
@inlinable
|
@inlinable
|
||||||
@@ -74,28 +75,39 @@ public extension Note where Label == String, Content == String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct NoteStyleConfiguration {
|
public struct NoteStyleConfiguration {
|
||||||
|
@usableFromInline
|
||||||
let label: any TextNode
|
let label: any TextNode
|
||||||
|
|
||||||
|
@usableFromInline
|
||||||
let content: any TextNode
|
let content: any TextNode
|
||||||
|
|
||||||
|
@usableFromInline
|
||||||
|
init(label: any TextNode, content: any TextNode) {
|
||||||
|
self.label = label
|
||||||
|
self.content = content
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension Note {
|
public extension Note {
|
||||||
func noteStyle<S: NoteStyleModifier>(_ modifier: S) -> some TextNode {
|
@inlinable
|
||||||
modifier.render(content: .init(label: label, content: content))
|
func style<S: NoteStyle>(_ modifier: S) -> some TextNode {
|
||||||
|
modifier.render(content: NoteStyleConfiguration(label: label, content: content))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Style
|
// MARK: - Style
|
||||||
|
|
||||||
public protocol NoteStyleModifier: TextModifier where Content == NoteStyleConfiguration {}
|
public protocol NoteStyle: TextModifier where Content == NoteStyleConfiguration {}
|
||||||
|
|
||||||
public extension NoteStyleModifier where Self == DefaultNoteStyle {
|
public extension NoteStyle where Self == DefaultNoteStyle {
|
||||||
static var `default`: Self {
|
static var `default`: Self {
|
||||||
DefaultNoteStyle()
|
DefaultNoteStyle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct DefaultNoteStyle: NoteStyleModifier {
|
public struct DefaultNoteStyle: NoteStyle {
|
||||||
|
|
||||||
|
@inlinable
|
||||||
public func render(content: NoteStyleConfiguration) -> some TextNode {
|
public func render(content: NoteStyleConfiguration) -> some TextNode {
|
||||||
HStack {
|
HStack {
|
||||||
content.label.color(.yellow).textStyle(.bold)
|
content.label.color(.yellow).textStyle(.bold)
|
||||||
|
|||||||
@@ -1,20 +1,43 @@
|
|||||||
|
import CliDocCore
|
||||||
import Rainbow
|
import Rainbow
|
||||||
|
|
||||||
public struct ShellCommand<Content: TextNode>: TextNode {
|
/// Represents a shell command text node, with a symbol and the content of
|
||||||
|
/// the command. Used for displaying example shell commands.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
public struct ShellCommand<Content: TextNode, Symbol: TextNode>: TextNode {
|
||||||
|
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
var symbol: any TextNode
|
let symbol: Symbol
|
||||||
|
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
var content: Content
|
let content: Content
|
||||||
|
|
||||||
|
/// Create a new shell command with the given content and symbol.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - content: The shell command to display.
|
||||||
|
/// - symbol: The symbol to use in front of the shell command.
|
||||||
@inlinable
|
@inlinable
|
||||||
public init(
|
public init(
|
||||||
symbol: any TextNode = "$",
|
@TextBuilder content: () -> Content,
|
||||||
|
@TextBuilder symbol: () -> Symbol
|
||||||
|
) {
|
||||||
|
self.symbol = symbol()
|
||||||
|
self.content = content()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new shell command with the given content and symbol.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - symbol: The symbol to use in front of the shell command.
|
||||||
|
/// - content: The shell command to display.
|
||||||
|
@inlinable
|
||||||
|
public init(
|
||||||
|
symbol: @autoclosure () -> Symbol,
|
||||||
@TextBuilder content: () -> Content
|
@TextBuilder content: () -> Content
|
||||||
) {
|
) {
|
||||||
self.symbol = symbol
|
self.init(content: content, symbol: symbol)
|
||||||
self.content = content()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@inlinable
|
@inlinable
|
||||||
@@ -23,22 +46,35 @@ public struct ShellCommand<Content: TextNode>: TextNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension ShellCommand where Content == String {
|
public extension ShellCommand where Content == String, Symbol == String {
|
||||||
|
/// Create a new shell command with the given content and symbol.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - content: The shell command to display.
|
||||||
|
/// - symbol: The symbol to use in front of the shell command.
|
||||||
@inlinable
|
@inlinable
|
||||||
init(
|
init(
|
||||||
_ content: String,
|
_ content: @autoclosure () -> String,
|
||||||
symbol: any TextNode = "$"
|
symbol: @autoclosure () -> String = "$"
|
||||||
) {
|
) {
|
||||||
self.init(symbol: symbol) { content }
|
self.init(content: content, symbol: symbol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ShellCommandConfiguration {
|
public struct ShellCommandConfiguration {
|
||||||
let symbol: any TextNode
|
public let symbol: any TextNode
|
||||||
let content: any TextNode
|
|
||||||
|
public let content: any TextNode
|
||||||
|
|
||||||
|
@usableFromInline
|
||||||
|
init(symbol: any TextNode, content: any TextNode) {
|
||||||
|
self.symbol = symbol
|
||||||
|
self.content = content
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension ShellCommand {
|
public extension ShellCommand {
|
||||||
|
@inlinable
|
||||||
func style<S: ShellCommandStyle>(_ style: S) -> some TextNode {
|
func style<S: ShellCommandStyle>(_ style: S) -> some TextNode {
|
||||||
style.render(content: .init(symbol: symbol, content: content))
|
style.render(content: .init(symbol: symbol, content: content))
|
||||||
}
|
}
|
||||||
@@ -46,7 +82,7 @@ public extension ShellCommand {
|
|||||||
|
|
||||||
// MARK: - Style
|
// MARK: - Style
|
||||||
|
|
||||||
public protocol ShellCommandStyle: TextModifier where Self.Content == ShellCommandConfiguration {}
|
public protocol ShellCommandStyle: TextModifier where Content == ShellCommandConfiguration {}
|
||||||
|
|
||||||
public extension ShellCommandStyle where Self == DefaultShellCommandStyle {
|
public extension ShellCommandStyle where Self == DefaultShellCommandStyle {
|
||||||
static var `default`: Self { DefaultShellCommandStyle() }
|
static var `default`: Self { DefaultShellCommandStyle() }
|
||||||
@@ -57,7 +93,7 @@ public struct DefaultShellCommandStyle: ShellCommandStyle {
|
|||||||
public func render(content: ShellCommandConfiguration) -> some TextNode {
|
public func render(content: ShellCommandConfiguration) -> some TextNode {
|
||||||
HStack {
|
HStack {
|
||||||
content.symbol
|
content.symbol
|
||||||
content.content.textStyle(.italic)
|
content.content.italic()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,52 @@
|
|||||||
// TODO: Add vertical spacing.
|
/// A section of text nodes, that can contain a header, content, and footer.
|
||||||
|
///
|
||||||
|
/// This allows nodes to be grouped and styled together.
|
||||||
|
///
|
||||||
|
/// **Example:**
|
||||||
|
///
|
||||||
|
/// ```swift
|
||||||
|
/// let mySection = Section {
|
||||||
|
/// "My super awesome section content"
|
||||||
|
/// } header: {
|
||||||
|
/// "Awesome"
|
||||||
|
/// } footer: {
|
||||||
|
/// "Note: this is super awesome".italic()
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// **Styling Sections:**
|
||||||
|
///
|
||||||
|
/// You can style a section by creating a custom ``SectionStyle``, which gives you the
|
||||||
|
/// opportunity to arrange and style the nodes within the section.
|
||||||
|
///
|
||||||
|
/// ```swift
|
||||||
|
/// struct MySectionStyle: SectionStyle {
|
||||||
|
/// func render(content: SectionConfiguration) -> some TextNode {
|
||||||
|
/// VStack(separator: .newLine(count: 2)) {
|
||||||
|
/// content.header
|
||||||
|
/// .color(.green)
|
||||||
|
/// .bold()
|
||||||
|
/// .underline()
|
||||||
|
/// content.content
|
||||||
|
/// content.footer.italic()
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// mySection.style(MySectionStyle())
|
||||||
|
///
|
||||||
|
/// print(mySection.render())
|
||||||
|
/// ```
|
||||||
|
/// **Note:** colored output / styling only shows in the terminal.
|
||||||
|
///
|
||||||
|
/// ```bash
|
||||||
|
///
|
||||||
|
/// Awesome
|
||||||
|
///
|
||||||
|
/// My super awesome section
|
||||||
|
///
|
||||||
|
/// Note: this is super awesome
|
||||||
|
/// ```
|
||||||
public struct Section<Header: TextNode, Content: TextNode, Footer: TextNode>: TextNode {
|
public struct Section<Header: TextNode, Content: TextNode, Footer: TextNode>: TextNode {
|
||||||
|
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
@@ -10,6 +58,12 @@ public struct Section<Header: TextNode, Content: TextNode, Footer: TextNode>: Te
|
|||||||
@usableFromInline
|
@usableFromInline
|
||||||
let footer: Footer
|
let footer: Footer
|
||||||
|
|
||||||
|
/// Create a new section with the given content, header, and footer.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - content: The content of the section.
|
||||||
|
/// - header: The header for the section.
|
||||||
|
/// - footer: The footer for the section.
|
||||||
@inlinable
|
@inlinable
|
||||||
public init(
|
public init(
|
||||||
@TextBuilder content: () -> Content,
|
@TextBuilder content: () -> Content,
|
||||||
@@ -27,6 +81,11 @@ public struct Section<Header: TextNode, Content: TextNode, Footer: TextNode>: Te
|
|||||||
}
|
}
|
||||||
|
|
||||||
public extension Section where Footer == Empty {
|
public extension Section where Footer == Empty {
|
||||||
|
/// Create a new section with the given content and header, with no footer.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - content: The content of the section.
|
||||||
|
/// - header: The header for the section.
|
||||||
@inlinable
|
@inlinable
|
||||||
init(
|
init(
|
||||||
@TextBuilder content: () -> Content,
|
@TextBuilder content: () -> Content,
|
||||||
@@ -37,6 +96,11 @@ public extension Section where Footer == Empty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public extension Section where Header == Empty {
|
public extension Section where Header == Empty {
|
||||||
|
/// Create a new section with the given content and footer, with no header.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - content: The content of the section.
|
||||||
|
/// - footer: The footer for the section.
|
||||||
@inlinable
|
@inlinable
|
||||||
init(
|
init(
|
||||||
@TextBuilder content: () -> Content,
|
@TextBuilder content: () -> Content,
|
||||||
@@ -46,28 +110,29 @@ public extension Section where Header == Empty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension Section where Header == Empty, Footer == Empty {
|
|
||||||
@inlinable
|
|
||||||
init(
|
|
||||||
@TextBuilder content: () -> Content
|
|
||||||
) {
|
|
||||||
self.init(content: content, header: { Empty() }, footer: { Empty() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Style
|
// MARK: - Style
|
||||||
|
|
||||||
public extension Section {
|
public extension Section {
|
||||||
|
|
||||||
|
/// Style a ``Section`` using the given ``SectionStyle``.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - style: The section style to use.
|
||||||
@inlinable
|
@inlinable
|
||||||
func style<S: SectionStyle>(_ style: S) -> some TextNode {
|
func style<S: SectionStyle>(_ style: S) -> some TextNode {
|
||||||
style.render(content: .init(header: header, content: content, footer: footer))
|
style.render(content: .init(header: header, content: content, footer: footer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Holds the type-erased values of a ``Section``, used to style a section.
|
||||||
public struct SectionConfiguration {
|
public struct SectionConfiguration {
|
||||||
|
/// The type-erased header of a section.
|
||||||
public let header: any TextNode
|
public let header: any TextNode
|
||||||
|
|
||||||
|
/// The type-erased content of a section.
|
||||||
public let content: any TextNode
|
public let content: any TextNode
|
||||||
|
|
||||||
|
/// The type-erased footer of a section.
|
||||||
public let footer: any TextNode
|
public let footer: any TextNode
|
||||||
|
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
@@ -81,13 +146,33 @@ public struct SectionConfiguration {
|
|||||||
public protocol SectionStyle: TextModifier where Content == SectionConfiguration {}
|
public protocol SectionStyle: TextModifier where Content == SectionConfiguration {}
|
||||||
|
|
||||||
public extension SectionStyle where Self == DefaultSectionStyle {
|
public extension SectionStyle where Self == DefaultSectionStyle {
|
||||||
static var `default`: Self { DefaultSectionStyle() }
|
|
||||||
|
/// Style a section using the default style, separating the content with
|
||||||
|
/// a new line between the elements.
|
||||||
|
static var `default`: Self { `default`(separator: .newLine(count: 2)) }
|
||||||
|
|
||||||
|
/// Style a section using the default style, separating the content with
|
||||||
|
/// given separator between the elements.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - separator: The separator to use to separate elements in a section.
|
||||||
|
static func `default`(separator: Separator.Vertical) -> Self {
|
||||||
|
DefaultSectionStyle(separator: separator)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents the default ``SectionStyle``, which arranges the nodes in
|
||||||
|
/// a ``VStack``, using the separator passed in.
|
||||||
|
///
|
||||||
|
/// - SeeAlso: ``SectionStyle/default(separator:)``
|
||||||
|
///
|
||||||
public struct DefaultSectionStyle: SectionStyle {
|
public struct DefaultSectionStyle: SectionStyle {
|
||||||
|
|
||||||
|
@usableFromInline
|
||||||
|
let separator: Separator.Vertical
|
||||||
|
|
||||||
public func render(content: SectionConfiguration) -> some TextNode {
|
public func render(content: SectionConfiguration) -> some TextNode {
|
||||||
VStack(separator: .newLine(count: 2)) {
|
VStack(separator: separator) {
|
||||||
content.header
|
content.header
|
||||||
content.content
|
content.content
|
||||||
content.footer
|
content.footer
|
||||||
|
|||||||
@@ -62,8 +62,7 @@ public extension TextNode {
|
|||||||
public protocol TextStyle: TextModifier where Content == TextStyleConfiguration {}
|
public protocol TextStyle: TextModifier where Content == TextStyleConfiguration {}
|
||||||
|
|
||||||
public struct TextStyleConfiguration {
|
public struct TextStyleConfiguration {
|
||||||
@usableFromInline
|
public let node: any TextNode
|
||||||
let node: any TextNode
|
|
||||||
|
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
init(_ node: any TextNode) {
|
init(_ node: any TextNode) {
|
||||||
|
|||||||
@@ -58,10 +58,10 @@ struct CliDocTests {
|
|||||||
\("Examples:".applyingStyle(.bold).applyingColor(.yellow)) \("Some common usage examples.".italic)
|
\("Examples:".applyingStyle(.bold).applyingColor(.yellow)) \("Some common usage examples.".italic)
|
||||||
|
|
||||||
\("First".red)
|
\("First".red)
|
||||||
$ \("ls -lah".italic)
|
> \("ls -lah".italic)
|
||||||
|
|
||||||
\("Second".red)
|
\("Second".red)
|
||||||
$ \("find . -name foo".italic)
|
> \("find . -name foo".italic)
|
||||||
"""
|
"""
|
||||||
let result = printIfNotEqual(
|
let result = printIfNotEqual(
|
||||||
examples.exampleStyle(CustomExampleOnlyStyle()).render(),
|
examples.exampleStyle(CustomExampleOnlyStyle()).render(),
|
||||||
@@ -99,7 +99,7 @@ struct CustomExampleOnlyStyle: ExampleStyle {
|
|||||||
content.examples.map { example in
|
content.examples.map { example in
|
||||||
VStack {
|
VStack {
|
||||||
example.label.red
|
example.label.red
|
||||||
ShellCommand { example.example }
|
ShellCommand(symbol: ">") { example.example }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user