diff --git a/Sources/ArgumentParserTestHelpers/TestHelpers.swift b/Sources/ArgumentParserTestHelpers/TestHelpers.swift index c2b8bb4e6..a71e324f8 100644 --- a/Sources/ArgumentParserTestHelpers/TestHelpers.swift +++ b/Sources/ArgumentParserTestHelpers/TestHelpers.swift @@ -448,7 +448,7 @@ extension XCTest { ProcessInfo.processInfo.environment["RECORD_SNAPSHOTS"] != nil if record || recordEnvironment || !snapshotExists { - let recordedValue = actual + "\n" + let recordedValue = actual try FileManager.default.createDirectory( at: snapshotDirectoryURL, withIntermediateDirectories: true, @@ -507,8 +507,9 @@ extension XCTest { line: line) } - public func assertGenerateDoccReference( + public func assertGeneratedReference( command: String, + doccFlavored: Bool, record: Bool = false, test: StaticString = #function, file: StaticString = #filePath, @@ -519,10 +520,19 @@ extension XCTest { #endif let commandURL = debugURL.appendingPathComponent(command) - let command = [ - "generate-docc-reference", commandURL.path, - "--output-directory", "-", - ] + let command: [String] + if doccFlavored { + command = [ + "generate-docc-reference", commandURL.path, + "--output-directory", "-", + "--style", "docc", + ] + } else { + command = [ + "generate-docc-reference", commandURL.path, + "--output-directory", "-", + ] + } let actual = try AssertExecuteCommand( command: command, file: file, diff --git a/Tests/ArgumentParserGenerateDoccReferenceTests/GenerateDoccReferenceTests.swift b/Tests/ArgumentParserGenerateDoccReferenceTests/GenerateDoccReferenceTests.swift index 599ee86cd..a976f26da 100644 --- a/Tests/ArgumentParserGenerateDoccReferenceTests/GenerateDoccReferenceTests.swift +++ b/Tests/ArgumentParserGenerateDoccReferenceTests/GenerateDoccReferenceTests.swift @@ -14,25 +14,42 @@ import XCTest final class GenerateDoccReferenceTests: XCTestCase { #if os(macOS) + func testCountLinesMarkdownReference() throws { + guard #available(macOS 12, *) else { return } + try assertGeneratedReference(command: "count-lines", doccFlavored: false) + } + func testCountLinesDoccReference() throws { guard #available(macOS 12, *) else { return } - try assertGenerateDoccReference(command: "count-lines") + try assertGeneratedReference(command: "count-lines", doccFlavored: true) } #endif + func testColorMarkdownReference() throws { + try assertGeneratedReference(command: "color", doccFlavored: false) + } func testColorDoccReference() throws { - try assertGenerateDoccReference(command: "color") + try assertGeneratedReference(command: "color", doccFlavored: true) } + func testMathMarkdownReference() throws { + try assertGeneratedReference(command: "math", doccFlavored: false) + } func testMathDoccReference() throws { - try assertGenerateDoccReference(command: "math") + try assertGeneratedReference(command: "math", doccFlavored: true) } + func testRepeatMarkdownReference() throws { + try assertGeneratedReference(command: "repeat", doccFlavored: false) + } func testRepeatDoccReference() throws { - try assertGenerateDoccReference(command: "repeat") + try assertGeneratedReference(command: "repeat", doccFlavored: true) } + func testRollMarkdownReference() throws { + try assertGeneratedReference(command: "roll", doccFlavored: false) + } func testRollDoccReference() throws { - try assertGenerateDoccReference(command: "roll") + try assertGeneratedReference(command: "roll", doccFlavored: true) } } diff --git a/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testColorDoccReference().md b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testColorDoccReference().md index 966fd9cdd..ccdb71eb2 100644 --- a/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testColorDoccReference().md +++ b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testColorDoccReference().md @@ -6,19 +6,19 @@ color --fav= [--second=] [--help] ``` -**--fav=\:** +- term **--fav=\:** *Your favorite color.* -**--second=\:** +- term **--second=\:** *Your second favorite color.* This is optional. -**--help:** +- term **--help:** *Show help information.* @@ -31,7 +31,7 @@ Show subcommand help information. color help [...] ``` -**subcommands:** +- term **subcommands:** diff --git a/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testColorMarkdownReference().md b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testColorMarkdownReference().md new file mode 100644 index 000000000..966fd9cdd --- /dev/null +++ b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testColorMarkdownReference().md @@ -0,0 +1,39 @@ +# color + + + +``` +color --fav= [--second=] [--help] +``` + +**--fav=\:** + +*Your favorite color.* + + +**--second=\:** + +*Your second favorite color.* + +This is optional. + + +**--help:** + +*Show help information.* + + +## color.help + +Show subcommand help information. + +``` +color help [...] +``` + +**subcommands:** + + + + + diff --git a/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testCountLinesDoccReference().md b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testCountLinesDoccReference().md index 88b97b538..ca99aa54b 100644 --- a/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testCountLinesDoccReference().md +++ b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testCountLinesDoccReference().md @@ -6,22 +6,22 @@ count-lines [] [--prefix=] [--verbose] [--help] ``` -**input-file:** +- term **input-file:** *A file to count lines in. If omitted, counts the lines of stdin.* -**--prefix=\:** +- term **--prefix=\:** *Only count lines with this prefix.* -**--verbose:** +- term **--verbose:** *Include extra information in the output.* -**--help:** +- term **--help:** *Show help information.* @@ -34,7 +34,7 @@ Show subcommand help information. count-lines help [...] ``` -**subcommands:** +- term **subcommands:** diff --git a/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testCountLinesMarkdownReference().md b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testCountLinesMarkdownReference().md new file mode 100644 index 000000000..88b97b538 --- /dev/null +++ b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testCountLinesMarkdownReference().md @@ -0,0 +1,42 @@ +# count-lines + + + +``` +count-lines [] [--prefix=] [--verbose] [--help] +``` + +**input-file:** + +*A file to count lines in. If omitted, counts the lines of stdin.* + + +**--prefix=\:** + +*Only count lines with this prefix.* + + +**--verbose:** + +*Include extra information in the output.* + + +**--help:** + +*Show help information.* + + +## count-lines.help + +Show subcommand help information. + +``` +count-lines help [...] +``` + +**subcommands:** + + + + + diff --git a/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testMathDoccReference().md b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testMathDoccReference().md index adcaac132..f7d37d8fa 100644 --- a/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testMathDoccReference().md +++ b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testMathDoccReference().md @@ -8,12 +8,12 @@ A utility for performing maths. math [--version] [--help] ``` -**--version:** +- term **--version:** *Show the version.* -**--help:** +- term **--help:** *Show help information.* @@ -26,22 +26,22 @@ Print the sum of the values. math add [--hex-output] [...] [--version] [--help] ``` -**--hex-output:** +- term **--hex-output:** *Use hexadecimal notation for the result.* -**values:** +- term **values:** *A group of integers to operate on.* -**--version:** +- term **--version:** *Show the version.* -**--help:** +- term **--help:** *Show help information.* @@ -56,22 +56,22 @@ Print the product of the values. math multiply [--hex-output] [...] [--version] [--help] ``` -**--hex-output:** +- term **--hex-output:** *Use hexadecimal notation for the result.* -**values:** +- term **values:** *A group of integers to operate on.* -**--version:** +- term **--version:** *Show the version.* -**--help:** +- term **--help:** *Show help information.* @@ -86,12 +86,12 @@ Calculate descriptive statistics. math stats [--version] [--help] ``` -**--version:** +- term **--version:** *Show the version.* -**--help:** +- term **--help:** *Show help information.* @@ -104,22 +104,22 @@ Print the average of the values. math stats average [--kind=] [...] [--version] [--help] ``` -**--kind=\:** +- term **--kind=\:** *The kind of average to provide.* -**values:** +- term **values:** *A group of floating-point values to operate on.* -**--version:** +- term **--version:** *Show the version.* -**--help:** +- term **--help:** *Show help information.* @@ -134,17 +134,17 @@ Print the standard deviation of the values. math stats stdev [...] [--version] [--help] ``` -**values:** +- term **values:** *A group of floating-point values to operate on.* -**--version:** +- term **--version:** *Show the version.* -**--help:** +- term **--help:** *Show help information.* @@ -159,35 +159,35 @@ Print the quantiles of the values (TBD). math stats quantiles [] [] [...] [--file=] [--directory=] [--shell=] [--custom=] [--version] [--help] ``` -**one-of-four:** +- term **one-of-four:** -**custom-arg:** +- term **custom-arg:** -**values:** +- term **values:** *A group of floating-point values to operate on.* -**--file=\:** +- term **--file=\:** -**--directory=\:** +- term **--directory=\:** -**--shell=\:** +- term **--shell=\:** -**--custom=\:** +- term **--custom=\:** -**--version:** +- term **--version:** *Show the version.* -**--help:** +- term **--help:** *Show help information.* @@ -204,7 +204,7 @@ Show subcommand help information. math help [...] ``` -**subcommands:** +- term **subcommands:** diff --git a/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testMathMarkdownReference().md b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testMathMarkdownReference().md new file mode 100644 index 000000000..adcaac132 --- /dev/null +++ b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testMathMarkdownReference().md @@ -0,0 +1,212 @@ +# math + + + +A utility for performing maths. + +``` +math [--version] [--help] +``` + +**--version:** + +*Show the version.* + + +**--help:** + +*Show help information.* + + +## math.add + +Print the sum of the values. + +``` +math add [--hex-output] [...] [--version] [--help] +``` + +**--hex-output:** + +*Use hexadecimal notation for the result.* + + +**values:** + +*A group of integers to operate on.* + + +**--version:** + +*Show the version.* + + +**--help:** + +*Show help information.* + + + + +## math.multiply + +Print the product of the values. + +``` +math multiply [--hex-output] [...] [--version] [--help] +``` + +**--hex-output:** + +*Use hexadecimal notation for the result.* + + +**values:** + +*A group of integers to operate on.* + + +**--version:** + +*Show the version.* + + +**--help:** + +*Show help information.* + + + + +## math.stats + +Calculate descriptive statistics. + +``` +math stats [--version] [--help] +``` + +**--version:** + +*Show the version.* + + +**--help:** + +*Show help information.* + + +### math.stats.average + +Print the average of the values. + +``` +math stats average [--kind=] [...] [--version] [--help] +``` + +**--kind=\:** + +*The kind of average to provide.* + + +**values:** + +*A group of floating-point values to operate on.* + + +**--version:** + +*Show the version.* + + +**--help:** + +*Show help information.* + + + + +### math.stats.stdev + +Print the standard deviation of the values. + +``` +math stats stdev [...] [--version] [--help] +``` + +**values:** + +*A group of floating-point values to operate on.* + + +**--version:** + +*Show the version.* + + +**--help:** + +*Show help information.* + + + + +### math.stats.quantiles + +Print the quantiles of the values (TBD). + +``` +math stats quantiles [] [] [...] [--file=] [--directory=] [--shell=] [--custom=] [--version] [--help] +``` + +**one-of-four:** + + +**custom-arg:** + + +**values:** + +*A group of floating-point values to operate on.* + + +**--file=\:** + + +**--directory=\:** + + +**--shell=\:** + + +**--custom=\:** + + +**--version:** + +*Show the version.* + + +**--help:** + +*Show help information.* + + + + + + +## math.help + +Show subcommand help information. + +``` +math help [...] +``` + +**subcommands:** + + + + + diff --git a/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testRepeatDoccReference().md b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testRepeatDoccReference().md index 8d0714b48..a7ec2089d 100644 --- a/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testRepeatDoccReference().md +++ b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testRepeatDoccReference().md @@ -6,22 +6,22 @@ repeat [--count=] [--include-counter] [--help] ``` -**--count=\:** +- term **--count=\:** *The number of times to repeat 'phrase'.* -**--include-counter:** +- term **--include-counter:** *Include a counter with each repetition.* -**phrase:** +- term **phrase:** *The phrase to repeat.* -**--help:** +- term **--help:** *Show help information.* @@ -34,7 +34,7 @@ Show subcommand help information. repeat help [...] ``` -**subcommands:** +- term **subcommands:** diff --git a/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testRepeatMarkdownReference().md b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testRepeatMarkdownReference().md new file mode 100644 index 000000000..8d0714b48 --- /dev/null +++ b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testRepeatMarkdownReference().md @@ -0,0 +1,42 @@ +# repeat + + + +``` +repeat [--count=] [--include-counter] [--help] +``` + +**--count=\:** + +*The number of times to repeat 'phrase'.* + + +**--include-counter:** + +*Include a counter with each repetition.* + + +**phrase:** + +*The phrase to repeat.* + + +**--help:** + +*Show help information.* + + +## repeat.help + +Show subcommand help information. + +``` +repeat help [...] +``` + +**subcommands:** + + + + + diff --git a/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testRollDoccReference().md b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testRollDoccReference().md index d84e3e2b0..03a66a718 100644 --- a/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testRollDoccReference().md +++ b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testRollDoccReference().md @@ -6,29 +6,29 @@ roll [--times=] [--sides=] [--seed=] [--verbose] [--help] ``` -**--times=\:** +- term **--times=\:** *Rolls the dice times.* -**--sides=\:** +- term **--sides=\:** *Rolls an -sided dice.* Use this option to override the default value of a six-sided die. -**--seed=\:** +- term **--seed=\:** *A seed to use for repeatable random generation.* -**--verbose:** +- term **--verbose:** *Show all roll results.* -**--help:** +- term **--help:** *Show help information.* @@ -41,7 +41,7 @@ Show subcommand help information. roll help [...] ``` -**subcommands:** +- term **subcommands:** diff --git a/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testRollMarkdownReference().md b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testRollMarkdownReference().md new file mode 100644 index 000000000..d84e3e2b0 --- /dev/null +++ b/Tests/ArgumentParserGenerateDoccReferenceTests/Snapshots/testRollMarkdownReference().md @@ -0,0 +1,49 @@ +# roll + + + +``` +roll [--times=] [--sides=] [--seed=] [--verbose] [--help] +``` + +**--times=\:** + +*Rolls the dice times.* + + +**--sides=\:** + +*Rolls an -sided dice.* + +Use this option to override the default value of a six-sided die. + + +**--seed=\:** + +*A seed to use for repeatable random generation.* + + +**--verbose:** + +*Show all roll results.* + + +**--help:** + +*Show help information.* + + +## roll.help + +Show subcommand help information. + +``` +roll help [...] +``` + +**subcommands:** + + + + + diff --git a/Tools/generate-docc-reference/Extensions/ArgumentParser+Markdown.swift b/Tools/generate-docc-reference/Extensions/ArgumentParser+Markdown.swift index 05e187061..6ec7850aa 100644 --- a/Tools/generate-docc-reference/Extensions/ArgumentParser+Markdown.swift +++ b/Tools/generate-docc-reference/Extensions/ArgumentParser+Markdown.swift @@ -34,7 +34,15 @@ extension CommandInfoV0 { } extension CommandInfoV0 { - func toMarkdown(_ path: [String]) -> String { + /// Recursively parses a command to generate markdown content that describes the command. + /// - Parameters: + /// - path: The path of subcommands from the root command. + /// - markdownStyle: The flavor of markdown to emit, either `docc` or `github` + /// - Returns: A multi-line markdown file that describes the command. + /// + /// If `path` is empty, it represents a top-level command. + /// Otherwise it's a subcommand, potentially recursive to multiple levels. + func toMarkdown(_ path: [String], markdownStyle: OutputStyle) -> String { var result = String(repeating: "#", count: path.count + 1) + " \(self.doccReferenceTitle)\n\n" @@ -64,7 +72,13 @@ extension CommandInfoV0 { continue } - result += "**\(arg.identity()):**\n\n" + switch markdownStyle { + case .docc: + result += "- term **\(arg.identity()):**\n\n" + case .github: + result += "**\(arg.identity()):**\n\n" + } + if let abstract = arg.abstract { result += "*\(abstract)*\n\n" } @@ -76,7 +90,9 @@ extension CommandInfoV0 { } for subcommand in self.subcommands ?? [] { - result += subcommand.toMarkdown(path + [self.commandName]) + "\n\n" + result += + subcommand.toMarkdown( + path + [self.commandName], markdownStyle: markdownStyle) + "\n\n" } return result @@ -92,6 +108,9 @@ extension CommandInfoV0 { } extension ArgumentInfoV0 { + /// Returns a string that describes the use of the argument. + /// + /// If `shouldDisplay` is `false`, an empty string is returned. public func usage() -> String { guard self.shouldDisplay else { return "" diff --git a/Tools/generate-docc-reference/GenerateDoccReference.swift b/Tools/generate-docc-reference/GenerateDoccReference.swift index c5f1c6a0e..922ded496 100644 --- a/Tools/generate-docc-reference/GenerateDoccReference.swift +++ b/Tools/generate-docc-reference/GenerateDoccReference.swift @@ -36,6 +36,14 @@ extension GenerateDoccReferenceError: CustomStringConvertible { } } +/// The flavor of generated markdown to emit. +enum OutputStyle: String, EnumerableFlag, ExpressibleByArgument { + /// DocC-supported markdown + case docc + /// GitHub-flavored markdown + case github +} + @main struct GenerateDoccReference: ParsableCommand { static let configuration = CommandConfiguration( @@ -50,6 +58,11 @@ struct GenerateDoccReference: ParsableCommand { help: "Directory to save generated docc reference. Use '-' for stdout.") var outputDirectory: String + @Option( + name: .shortAndLong, + help: "Use docc flavored markdown for the generated output.") + var style: OutputStyle = .github + func validate() throws { if outputDirectory != "-" { // outputDirectory must already exist, `GenerateDoccReference` will not create it. @@ -71,6 +84,8 @@ struct GenerateDoccReference: ParsableCommand { func run() throws { let data: Data + // runs the tool with the --experimental-dump-help argument to capture + // the output. do { let tool = URL(fileURLWithPath: tool) let output = try executeCommand( @@ -80,9 +95,12 @@ struct GenerateDoccReference: ParsableCommand { throw GenerateDoccReferenceError.failedToRunSubprocess(error: error) } + // ToolInfoHeader is intentionally kept internal to argument parser to + // allow the library some flexibility to update/change its content/format. do { let toolInfoThin = try JSONDecoder().decode( ToolInfoHeader.self, from: data) + // verify the serialization version is known/expected guard toolInfoThin.serializationVersion == 0 else { throw GenerateDoccReferenceError.unsupportedDumpHelpVersion( expected: 0, @@ -101,11 +119,13 @@ struct GenerateDoccReference: ParsableCommand { do { if self.outputDirectory == "-" { - try self.generatePages(from: toolInfo.command, savingTo: nil) + try self.generatePages( + from: toolInfo.command, savingTo: nil, flavor: style) } else { try self.generatePages( from: toolInfo.command, - savingTo: URL(fileURLWithPath: outputDirectory)) + savingTo: URL(fileURLWithPath: outputDirectory), + flavor: style) } } catch { throw GenerateDoccReferenceError.failedToGenerateDoccReference( @@ -113,10 +133,18 @@ struct GenerateDoccReference: ParsableCommand { } } - func generatePages(from command: CommandInfoV0, savingTo directory: URL?) + /// Generates a markdown file from the CommandInfoV0 object you provide. + /// - Parameters: + /// - command: The command to parse into a markdown output. + /// - directory: The directory to save the generated markdown file, printing it if `nil`. + /// - flavor: The flavor of markdown to use when generating the content. + /// - Throws: An error if the markdown file cannot be generated or saved. + func generatePages( + from command: CommandInfoV0, savingTo directory: URL?, flavor: OutputStyle + ) throws { - let page = command.toMarkdown([]) + let page = command.toMarkdown([], markdownStyle: style) if let directory = directory { let fileName = command.doccReferenceFileName diff --git a/Tools/generate-manual/GenerateManual.swift b/Tools/generate-manual/GenerateManual.swift index 0b42af303..00b8f60d0 100644 --- a/Tools/generate-manual/GenerateManual.swift +++ b/Tools/generate-manual/GenerateManual.swift @@ -92,6 +92,8 @@ struct GenerateManual: ParsableCommand { func run() throws { let data: Data + // runs the tool with the --experimental-dump-help argument to capture + // the output. do { let tool = URL(fileURLWithPath: tool) let output = try executeCommand( @@ -101,9 +103,12 @@ struct GenerateManual: ParsableCommand { throw GenerateManualError.failedToRunSubprocess(error: error) } + // ToolInfoHeader is intentionally kept internal to argument parser to + // allow the library some flexibility to update/change its content/format. do { let toolInfoThin = try JSONDecoder().decode( ToolInfoHeader.self, from: data) + // verify the serialization version is known/expected guard toolInfoThin.serializationVersion == 0 else { throw GenerateManualError.unsupportedDumpHelpVersion( expected: 0,