From 2a9b350b26b47456bedc6016abb6e723858f7ca4 Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Wed, 4 Dec 2024 16:15:21 -0500 Subject: [PATCH] feat: More styles, renames some items. --- Sources/CliDoc/Modifiers/ExamplesStyle.swift | 105 ++++++++++++++++++ .../CliDoc/Modifiers/LabelStyleModifier.swift | 2 +- .../CliDoc/Modifiers/NoteStyleModifier.swift | 2 +- .../CliDoc/Modifiers/ShellCommandStyle.swift | 28 +++++ ...Modifier.swift => TextStyleModifier.swift} | 4 +- Sources/CliDoc/Nodes/Empty.swift | 11 ++ Sources/CliDoc/Nodes/Examples.swift | 19 +--- Sources/CliDoc/Nodes/Note.swift | 5 +- Sources/CliDoc/Nodes/ShellCommand.swift | 15 ++- Tests/CliDocTests/CliDocTests.swift | 37 +++++- 10 files changed, 198 insertions(+), 30 deletions(-) create mode 100644 Sources/CliDoc/Modifiers/ExamplesStyle.swift create mode 100644 Sources/CliDoc/Modifiers/ShellCommandStyle.swift rename Sources/CliDoc/Modifiers/{StyleModifier.swift => TextStyleModifier.swift} (82%) create mode 100644 Sources/CliDoc/Nodes/Empty.swift diff --git a/Sources/CliDoc/Modifiers/ExamplesStyle.swift b/Sources/CliDoc/Modifiers/ExamplesStyle.swift new file mode 100644 index 0000000..0b12c50 --- /dev/null +++ b/Sources/CliDoc/Modifiers/ExamplesStyle.swift @@ -0,0 +1,105 @@ +import Rainbow + +public extension ExampleSection { + + func style(_ style: S) -> some TextNode { + style.render(content: .init( + header: header, + label: label, + examples: examples + )) + } + + func exampleStyle(_ style: S) -> some TextNode { + DefaultExamplesStyle().render(content: .init( + header: header, + label: label, + examples: examples + )) + } +} + +extension Array where Element == ExampleSection.Example { + func exampleStyle(_ style: S) -> some TextNode { + style.render(content: .init(examples: self)) + } +} + +public struct ExampleSectionConfiguration { + @usableFromInline + let header: any TextNode + + @usableFromInline + let label: any TextNode + + @usableFromInline + let examples: [ExampleSection.Example] + + @usableFromInline + init(header: any TextNode, label: any TextNode, examples: [ExampleSection.Example]) { + self.header = header + self.label = label + self.examples = examples + } +} + +public struct ExampleConfiguration { + @usableFromInline + let examples: [ExampleSection.Example] + + @usableFromInline + init(examples: [ExampleSection.Example]) { + self.examples = examples + } +} + +public protocol ExampleSectionStyle: NodeModifier where Content == ExampleSectionConfiguration {} +public protocol ExampleStyle: NodeModifier where Content == ExampleConfiguration {} + +public extension ExampleSectionStyle where Self == DefaultExamplesStyle { + static func `default`(exampleStyle: any ExampleStyle = .default) -> Self { + DefaultExamplesStyle(exampleStyle: exampleStyle) + } +} + +public extension ExampleStyle where Self == DefaultExampleStyle { + static var `default`: Self { + DefaultExampleStyle() + } +} + +public struct DefaultExamplesStyle: ExampleSectionStyle { + + @usableFromInline + let exampleStyle: any ExampleStyle + + @inlinable + public init(exampleStyle: any ExampleStyle = .default) { + self.exampleStyle = exampleStyle + } + + @inlinable + public func render(content: ExampleSectionConfiguration) -> some TextNode { + VStack(spacing: 2) { + HStack { + content.header + content.label + } + exampleStyle.render(content: .init(examples: content.examples)) + } + } +} + +public struct DefaultExampleStyle: ExampleStyle { + + public func render(content: ExampleConfiguration) -> some TextNode { + VStack(spacing: 2) { + content.examples.map { example in + VStack { + Label(example.label.green.bold) + ShellCommand { example.example }.style(.default) + } + } + } + } +} diff --git a/Sources/CliDoc/Modifiers/LabelStyleModifier.swift b/Sources/CliDoc/Modifiers/LabelStyleModifier.swift index 618dd9a..a8dd61a 100644 --- a/Sources/CliDoc/Modifiers/LabelStyleModifier.swift +++ b/Sources/CliDoc/Modifiers/LabelStyleModifier.swift @@ -26,7 +26,7 @@ public struct LabelStyle: NodeModifier { @inlinable public func render(content: Label) -> some TextNode { var label: any TextNode = content.content - label = label.style(styles) + label = label.textStyle(styles) if let color { label = label.color(color) } diff --git a/Sources/CliDoc/Modifiers/NoteStyleModifier.swift b/Sources/CliDoc/Modifiers/NoteStyleModifier.swift index c12ffbd..3d76249 100644 --- a/Sources/CliDoc/Modifiers/NoteStyleModifier.swift +++ b/Sources/CliDoc/Modifiers/NoteStyleModifier.swift @@ -23,7 +23,7 @@ public struct DefaultNoteStyle: NoteStyleModifier { public func render(content: NoteStyleConfiguration) -> some TextNode { HStack { - content.label.color(.yellow).style(.bold) + content.label.color(.yellow).textStyle(.bold) content.content } } diff --git a/Sources/CliDoc/Modifiers/ShellCommandStyle.swift b/Sources/CliDoc/Modifiers/ShellCommandStyle.swift new file mode 100644 index 0000000..a41ddca --- /dev/null +++ b/Sources/CliDoc/Modifiers/ShellCommandStyle.swift @@ -0,0 +1,28 @@ +import Rainbow + +public struct ShellCommandConfiguration { + let symbol: any TextNode + let content: any TextNode +} + +public extension ShellCommand { + func style(_ style: S) -> some TextNode { + style.render(content: .init(symbol: symbol, content: content)) + } +} + +public protocol ShellCommandStyle: NodeModifier where Self.Content == ShellCommandConfiguration {} + +public extension ShellCommandStyle where Self == DefaultShellCommandStyle { + static var `default`: Self { DefaultShellCommandStyle() } +} + +public struct DefaultShellCommandStyle: ShellCommandStyle { + + public func render(content: ShellCommandConfiguration) -> some TextNode { + HStack { + content.symbol + content.content.textStyle(.italic) + } + } +} diff --git a/Sources/CliDoc/Modifiers/StyleModifier.swift b/Sources/CliDoc/Modifiers/TextStyleModifier.swift similarity index 82% rename from Sources/CliDoc/Modifiers/StyleModifier.swift rename to Sources/CliDoc/Modifiers/TextStyleModifier.swift index 74c4e8d..64cae01 100644 --- a/Sources/CliDoc/Modifiers/StyleModifier.swift +++ b/Sources/CliDoc/Modifiers/TextStyleModifier.swift @@ -2,12 +2,12 @@ import Rainbow public extension TextNode { @inlinable - func style(_ styles: Style...) -> some TextNode { + func textStyle(_ styles: Style...) -> some TextNode { modifier(StyleModifier(styles: styles)) } @inlinable - func style(_ styles: [Style]) -> some TextNode { + func textStyle(_ styles: [Style]) -> some TextNode { modifier(StyleModifier(styles: styles)) } } diff --git a/Sources/CliDoc/Nodes/Empty.swift b/Sources/CliDoc/Nodes/Empty.swift new file mode 100644 index 0000000..573f09c --- /dev/null +++ b/Sources/CliDoc/Nodes/Empty.swift @@ -0,0 +1,11 @@ +/// An empty text node. +public struct Empty: TextNode { + + @inlinable + public init() {} + + @inlinable + public var body: some TextNode { + "" + } +} diff --git a/Sources/CliDoc/Nodes/Examples.swift b/Sources/CliDoc/Nodes/Examples.swift index 08eefbf..b2d4678 100644 --- a/Sources/CliDoc/Nodes/Examples.swift +++ b/Sources/CliDoc/Nodes/Examples.swift @@ -1,6 +1,6 @@ import Rainbow -public struct Examples: TextNode { +public struct ExampleSection: TextNode { public typealias Example = (label: String, example: String) @usableFromInline @@ -25,24 +25,11 @@ public struct Examples: TextNode { @inlinable public var body: some TextNode { - VStack(spacing: 2) { - HStack { - header - label - } - VStack(spacing: 2) { - self.examples.map { example in - VStack { - CliDoc.Label(example.label.green.bold) - ShellCommand { example.example.italic } - } - } - } - } + style(.default()) } } -public extension Examples where Header == String, Label == String { +public extension ExampleSection where Header == String, Label == String { @inlinable init( header: String = "Examples:".yellow.bold, diff --git a/Sources/CliDoc/Nodes/Note.swift b/Sources/CliDoc/Nodes/Note.swift index 30453a3..01cf69a 100644 --- a/Sources/CliDoc/Nodes/Note.swift +++ b/Sources/CliDoc/Nodes/Note.swift @@ -18,10 +18,7 @@ public struct Note: TextNode { @inlinable public var body: some TextNode { - HStack { - label - content - } + noteStyle(.default) } } diff --git a/Sources/CliDoc/Nodes/ShellCommand.swift b/Sources/CliDoc/Nodes/ShellCommand.swift index 06a454f..5063d01 100644 --- a/Sources/CliDoc/Nodes/ShellCommand.swift +++ b/Sources/CliDoc/Nodes/ShellCommand.swift @@ -17,9 +17,16 @@ public struct ShellCommand: TextNode { @inlinable public var body: some TextNode { - HStack { - symbol - content - } + style(.default) + } +} + +public extension ShellCommand where Content == String { + @inlinable + init( + _ content: String, + symbol: any TextNode = "$" + ) { + self.init(symbol: symbol) { content } } } diff --git a/Tests/CliDocTests/CliDocTests.swift b/Tests/CliDocTests/CliDocTests.swift index 91c0e9d..95a66a1 100644 --- a/Tests/CliDocTests/CliDocTests.swift +++ b/Tests/CliDocTests/CliDocTests.swift @@ -45,7 +45,7 @@ func testVStack() { @Test func testNote() { #expect(setupRainbow) - let note = Note(content: "Some note.").noteStyle(.default) + let note = Note(content: "Some note.") let expected = """ \("NOTE:".yellow.bold) Some note. """ @@ -55,7 +55,7 @@ func testNote() { @Test func testExamples() { #expect(setupRainbow) - let examples = Examples( + let examples = ExampleSection( examples: [("First", "ls -lah"), ("Second", "find . -name foo")] ) @@ -70,3 +70,36 @@ func testExamples() { """ #expect(examples.render() == expected) } + +@Test +func testExamplesWithCustomExampleOnlyStyle() { + #expect(setupRainbow) + let examples = ExampleSection( + examples: [("First", "ls -lah"), ("Second", "find . -name foo")] + ) + .exampleStyle(CustomExampleOnlyStyle()) + + let expected = """ + \("Examples:".yellow.bold) Some common usage examples. + + \("First".red) + $ \("ls -lah".italic) + + \("Second".red) + $ \("find . -name foo".italic) + """ + #expect(examples.render() == expected) +} + +struct CustomExampleOnlyStyle: ExampleStyle { + func render(content: ExampleConfiguration) -> some TextNode { + VStack(spacing: 2) { + content.examples.map { example in + VStack { + Label(example.label.red) + ShellCommand { example.example } + } + } + } + } +}