feat: Working on node builder

This commit is contained in:
2024-12-01 01:41:36 -05:00
parent 56a406b231
commit 55d8888961
14 changed files with 480 additions and 39 deletions

View File

@@ -83,7 +83,7 @@ struct Discussion {
if usesExtraArgs {
if let lastExampleIndex = nodes.lastIndex(where: \.isExampleNode) {
guard case let .example(.container(exampleNodes, separator)) = nodes[lastExampleIndex] else {
guard case let .example(.container(exampleNodes, _)) = nodes[lastExampleIndex] else {
// this should never happen.
print(nodes[lastExampleIndex])
fatalError()
@@ -99,14 +99,11 @@ struct Discussion {
// replace the first element, which is the header so that we can
// replace it with a new header.
nodes.insert(
.example(
.container(
[
.exampleLabel("Passing extra args."),
.container([exampleNode, .text("-- --vault-id \"myId@$SCRIPTS/vault-gopass-client\"")], separator: " ")
],
separator: separator
)),
.example(.exampleContainer(
"Passing extra args.",
"\(exampleNode.render().replacingOccurrences(of: " $ ", with: ""))"
+ " -- --vault-id \"myId@$SCRIPTS/vault-gopass-client\""
)),
at: nodes.index(after: lastExampleIndex)
)
}
@@ -119,8 +116,35 @@ struct Discussion {
enum Node {
struct Separator {
let string: String
let count: Int
init(_ string: String, repeating count: Int = 1) {
self.string = string
self.count = count
}
var value: String { string.repeating(count) }
static var empty: Self { .init("") }
static func space(_ count: Int = 1) -> Self {
.init(" ", repeating: count)
}
static func newLine(_ count: Int = 1) -> Self {
.init("\n", repeating: count)
}
static func tab(_ count: Int) -> Self {
.init("\t", repeating: count)
}
}
/// A container that holds onto other nodes to be rendered.
indirect case container(_ nodes: [Node], separator: String = "\n\n")
indirect case container(_ nodes: [Node], separator: Separator = .newLine())
/// A container to identify example nodes, so some other nodes can get injected
/// when this is rendered.
@@ -134,6 +158,14 @@ enum Node {
/// A root node that renders the given string without modification.
case text(_ text: String)
static func container(separator: Separator = .newLine(), _ nodes: Node...) -> Self {
.container(nodes, separator: separator)
}
static func container(_ lhs: Node, _ rhs: Node, separator: Separator = .newLine()) -> Self {
.container([lhs, rhs], separator: separator)
}
static func styledText(_ text: String, color: NamedColor? = nil, styles: [Style]? = nil) -> Self {
var string = text
if let color {
@@ -151,32 +183,28 @@ enum Node {
.styledText(text, color: color, styles: [style])
}
static func header(_ text: String) -> Self {
static func boldYellowText(_ text: String) -> Self {
.styledText(text, color: .yellow, styles: [.bold])
}
static func section(header: Node, content: Node, inline: Bool = false) -> Self {
.container([header, content], separator: inline ? " " : "\n")
static func section(header: Node, content: Node, separator: Separator = .newLine()) -> Self {
.container([header, content], separator: separator)
}
static func section(header: String, label: Node, inline: Bool = false) -> Self {
.section(header: .header(header), content: label, inline: inline)
static func section(header: String, label: Node, separator: Separator = .newLine()) -> Self {
.section(header: .boldYellowText(header), content: label, separator: separator)
}
static func important(label: String) -> Self {
.section(
header: .text("IMPORTANT NOTE:".red.bold.underline),
content: .text(label.red.italic),
inline: true
content: .text(label.italic),
separator: .newLine()
)
}
static func note(label: String, labelInline: Bool = true) -> Self {
.section(header: "NOTE:", label: .text(label.italic), inline: labelInline)
}
static func container(separator: String = "\n\n", _ nodes: Node...) -> Self {
.container(nodes, separator: separator)
static func note(_ text: String, inline: Bool = true) -> Self {
.section(header: "NOTE:", label: .text(text.italic), separator: inline ? .space() : .newLine())
}
static func shellCommand(_ text: String, symbol: String = " $") -> Self {
@@ -184,14 +212,14 @@ enum Node {
}
static func seeAlso(label: String, command: String, appendHelpToCommand: Bool = true) -> Self {
.container(
.container([.header("See Also:"), .text(label.italic)], separator: " "),
.container([
.container(.boldYellowText("See Also:"), .text(label.italic), separator: .space()),
.shellCommand("\(appendHelpToCommand ? command + " --help" : command)")
)
])
}
static var exampleHeading: Self {
.section(header: "Examples:", label: .text("Some common examples."), inline: true)
.section(header: "Examples:", label: .text("Some common examples."), separator: .space())
}
var isExampleNode: Bool {
@@ -199,6 +227,14 @@ enum Node {
return false
}
fileprivate static func exampleContainer(_ label: String, _ command: String) -> Self {
.container(
.exampleLabel("\(label)\n"),
.shellCommand(command, symbol: " $"),
separator: .empty
)
}
/// Renders an example usage of the command.
static func example(label: String, example: String, parentCommand: String?) -> Self {
let string: String
@@ -207,10 +243,7 @@ enum Node {
} else {
string = "\(Constants.appName) \(example)"
}
return .example(.section(
header: .exampleLabel(label),
content: .shellCommand(string, symbol: " $")
))
return .example(.exampleContainer(label, string))
}
static func exampleLabel(_ label: String) -> Node {
@@ -256,10 +289,10 @@ private extension Array where Element == (label: String, example: String) {
extension Array where Element == Node {
static var defaultNodes: Self {
[.note(label: "Most options are not required if you have a configuration file setup.")]
[.note("Most options are not required if you have a configuration file setup.")]
}
fileprivate func render(separator: String = "\n\n") -> String {
fileprivate func render(separator: Node.Separator = .newLine()) -> String {
map {
// Strip of any new-line characters from the last section of the rendered string
// of the node. This allows us to have a consistent single new-line between each
@@ -269,6 +302,21 @@ extension Array where Element == Node {
string = string.dropLast()
}
return String(string)
}.joined(separator: separator)
}
.joined(separator: separator.value)
}
}
private extension String {
func repeating(_ count: Int) -> Self {
guard count > 0 else { return self }
var count = count
var output = self
while count > 0 {
output = "\(output)\(self)"
count -= 1
}
return output
}
}