import ArgumentParser import Rainbow extension CommandConfiguration { static func create( commandName: String, abstract: String, usesExtraArgs: Bool = true, discussion nodes: [Node] ) -> Self { .init( commandName: commandName, abstract: createAbstract(abstract), discussion: Discussion(nodes: nodes, usesExtraArgs: usesExtraArgs).render() ) } static func playbook( commandName: String, abstract: String, parentCommand: String? = nil, examples: (label: String, example: String)... ) -> Self { .create( commandName: commandName, abstract: abstract, discussion: [.note(label: "Most options are not required if you have a configuration file setup.")] + examples.nodes(parentCommand) + [.seeAlso(label: "Ansible playbook options.", command: "ansible-playbook")] ) } } func createAbstract(_ string: String) -> String { "\(string.blue)" } struct Discussion { var nodes: [Node] init(usesExtraArgs: Bool = true, _ nodes: Node...) { self.init(nodes: nodes, usesExtraArgs: usesExtraArgs) } init(nodes: [Node], usesExtraArgs: Bool = true) { var nodes = nodes if let firstExampleIndex = nodes.firstIndex(where: \.isExampleNode) { nodes.insert(.exampleHeading, at: firstExampleIndex) } if usesExtraArgs { if let lastExampleIndex = nodes.lastIndex(where: \.isExampleNode) { let last = nodes[lastExampleIndex] if case let .example(_, example, parent) = last { nodes.insert( .example(label: "Passing extra args.", example: example, parentCommand: parent), at: nodes.index(after: lastExampleIndex) ) } } } self.nodes = nodes } func render() -> String { nodes.map { $0.render() }.joined(separator: "\n") } } enum Node: Equatable { case note(header: String = "NOTE:", label: String, appendNewLine: Bool = true) case example(label: String, example: String, parentCommand: String?) case seeAlso(label: String, command: String, appendHelpToCommand: Bool = true) static var exampleHeading: Self { .note(header: "Examples:", label: "Some common examples.", appendNewLine: false) } var isExampleNode: Bool { if case .example = self { return true } return false } func render() -> String { switch self { case let .note(header, note, appendNewLine): return "\(header.yellow) \(note.italic)\(appendNewLine ? "\n" : "")" case let .example(label: label, example: example, parentCommand: parent): return """ \(label.green.italic) $ \(Constants.appName) \(parent ?? "")\(parent != nil ? " \(example)" : example) """ case let .seeAlso(label: label, command: command, appendHelpToCommand: appendHelp): return """ \("See Also:".yellow) \(label.italic) $ \(command)\(appendHelp ? " --help" : "") """ } } } private extension Array where Element == (label: String, example: String) { func nodes(_ parentCommand: String?) -> [Node] { map { .example(label: $0.label, example: $0.example, parentCommand: parentCommand) } } }