feat: Working on node builder
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user