mirror of
https://github.com/apple/swift-argument-parser.git
synced 2026-06-06 20:18:23 +00:00
Add experimental manual page generation (#332)
- Adds a swift package manager command plugin called
GenerateManualPlugin. The plugin can be invoked from the command line
using `swift package experimental-generate-manual`. The plugin is
prefixed for now with "experimental-" to indicate it is not mature and
may see breaking changes to its CLI and output in the future. The
plugin can be can be used to generate a manual in MDoc syntax for any
swift-argument-parser tool that can be executed via
`tool --experimental-dump-info`.
- The plugin works by converting the `ToolInfoV0` structure from the
`ArgumentParserToolInfo` library into MDoc AST nodes using a custom
(SwiftUI-esk) result builder DSL. The MDoc AST is then lowered to a
string and written to disk.
- The MDoc AST included is not general purpose and doesn't represent the
true language exactly, so it is private to the underlying
`generate-manual` tool. In the future it would be interesting to
finish fleshing out this MDoc library and spin it out, however this is
not a priority.
- Next steps include:
- Improving the command line interface for the plugin.
- Adding support for "extended discussions" to Commands and exposing
this information in manuals.
- Further improve the escaping logic to properly escape MDoc macros
that might happen to appear in user's help strings.
- Ingesting external content a-la swift-docc so the entire tool
documentation does not need to be included in the binary itself.
- Bug fixes and addressing developer/user feedback.
Built with love,
@rauhul
This commit is contained in:
@@ -21,6 +21,7 @@ var package = Package(
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
// Core Library
|
||||
.target(
|
||||
name: "ArgumentParser",
|
||||
dependencies: ["ArgumentParserToolInfo"],
|
||||
@@ -34,6 +35,7 @@ var package = Package(
|
||||
dependencies: [],
|
||||
exclude: ["CMakeLists.txt"]),
|
||||
|
||||
// Examples
|
||||
.executableTarget(
|
||||
name: "roll",
|
||||
dependencies: ["ArgumentParser"],
|
||||
@@ -47,6 +49,7 @@ var package = Package(
|
||||
dependencies: ["ArgumentParser"],
|
||||
path: "Examples/repeat"),
|
||||
|
||||
// Tests
|
||||
.testTarget(
|
||||
name: "ArgumentParserEndToEndTests",
|
||||
dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"],
|
||||
@@ -68,10 +71,13 @@ var package = Package(
|
||||
|
||||
#if swift(>=5.6) && os(macOS)
|
||||
package.targets.append(contentsOf: [
|
||||
// Examples
|
||||
.executableTarget(
|
||||
name: "count-lines",
|
||||
dependencies: ["ArgumentParser"],
|
||||
path: "Examples/count-lines"),
|
||||
|
||||
// Tools
|
||||
.executableTarget(
|
||||
name: "changelog-authors",
|
||||
dependencies: ["ArgumentParser"],
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
// swift-tools-version:5.6
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2020 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import PackageDescription
|
||||
|
||||
var package = Package(
|
||||
name: "swift-argument-parser",
|
||||
products: [
|
||||
.library(
|
||||
name: "ArgumentParser",
|
||||
targets: ["ArgumentParser"]),
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
// Core Library
|
||||
.target(
|
||||
name: "ArgumentParser",
|
||||
dependencies: ["ArgumentParserToolInfo"],
|
||||
exclude: ["CMakeLists.txt"]),
|
||||
.target(
|
||||
name: "ArgumentParserTestHelpers",
|
||||
dependencies: ["ArgumentParser", "ArgumentParserToolInfo"],
|
||||
exclude: ["CMakeLists.txt"]),
|
||||
.target(
|
||||
name: "ArgumentParserToolInfo",
|
||||
dependencies: [ ],
|
||||
exclude: ["CMakeLists.txt"]),
|
||||
|
||||
// Plugins
|
||||
.plugin(
|
||||
name: "GenerateManualPlugin",
|
||||
capability: .command(
|
||||
intent: .custom(
|
||||
verb: "experimental-generate-manual",
|
||||
description: "Generate a manual entry for a specified target.")),
|
||||
dependencies: ["generate-manual"]),
|
||||
|
||||
// Examples
|
||||
.executableTarget(
|
||||
name: "roll",
|
||||
dependencies: ["ArgumentParser"],
|
||||
path: "Examples/roll"),
|
||||
.executableTarget(
|
||||
name: "math",
|
||||
dependencies: ["ArgumentParser"],
|
||||
path: "Examples/math"),
|
||||
.executableTarget(
|
||||
name: "repeat",
|
||||
dependencies: ["ArgumentParser"],
|
||||
path: "Examples/repeat"),
|
||||
|
||||
// Tools
|
||||
.executableTarget(
|
||||
name: "generate-manual",
|
||||
dependencies: ["ArgumentParser", "ArgumentParserToolInfo"],
|
||||
path: "Tools/generate-manual"),
|
||||
|
||||
// Tests
|
||||
.testTarget(
|
||||
name: "ArgumentParserEndToEndTests",
|
||||
dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"],
|
||||
exclude: ["CMakeLists.txt"]),
|
||||
.testTarget(
|
||||
name: "ArgumentParserExampleTests",
|
||||
dependencies: ["ArgumentParserTestHelpers"],
|
||||
resources: [.copy("CountLinesTest.txt")]),
|
||||
.testTarget(
|
||||
name: "ArgumentParserGenerateManualTests",
|
||||
dependencies: ["ArgumentParserTestHelpers"]),
|
||||
.testTarget(
|
||||
name: "ArgumentParserPackageManagerTests",
|
||||
dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"],
|
||||
exclude: ["CMakeLists.txt"]),
|
||||
.testTarget(
|
||||
name: "ArgumentParserUnitTests",
|
||||
dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"],
|
||||
exclude: ["CMakeLists.txt"]),
|
||||
]
|
||||
)
|
||||
|
||||
#if os(macOS)
|
||||
package.targets.append(contentsOf: [
|
||||
// Examples
|
||||
.executableTarget(
|
||||
name: "count-lines",
|
||||
dependencies: ["ArgumentParser"],
|
||||
path: "Examples/count-lines"),
|
||||
|
||||
// Tools
|
||||
.executableTarget(
|
||||
name: "changelog-authors",
|
||||
dependencies: ["ArgumentParser"],
|
||||
path: "Tools/changelog-authors"),
|
||||
])
|
||||
#endif
|
||||
@@ -0,0 +1,95 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import PackagePlugin
|
||||
import Foundation
|
||||
|
||||
@main
|
||||
struct GenerateManualPlugin: CommandPlugin {
|
||||
func performCommand(
|
||||
context: PluginContext,
|
||||
arguments: [String]
|
||||
) async throws {
|
||||
// Locate generation tool.
|
||||
let generationToolFile = try context.tool(named: "generate-manual").path
|
||||
|
||||
// Create an extractor to extract plugin-only arguments from the `arguments`
|
||||
// array.
|
||||
var extractor = ArgumentExtractor(arguments)
|
||||
|
||||
// Run generation tool once if help is requested.
|
||||
if extractor.helpRequest() {
|
||||
try generationToolFile.exec(arguments: ["--help"])
|
||||
print("""
|
||||
ADDITIONAL OPTIONS:
|
||||
--configuration <configuration>
|
||||
Tool build configuration used to generate the
|
||||
manual. (default: release)
|
||||
|
||||
NOTE: The "GenerateManual" plugin handles passing the "<tool>" and
|
||||
"--output-directory <output-directory>" arguments. Manually supplying
|
||||
these arguments will result in a runtime failure.
|
||||
""")
|
||||
return
|
||||
}
|
||||
|
||||
// Extract configuration argument before making it to the
|
||||
// "generate-manual" tool.
|
||||
let configuration = try extractor.configuration()
|
||||
|
||||
// Build all products first.
|
||||
print("Building package in \(configuration) mode...")
|
||||
let buildResult = try packageManager.build(
|
||||
.all(includingTests: false),
|
||||
parameters: .init(configuration: configuration))
|
||||
|
||||
guard buildResult.succeeded else {
|
||||
throw GenerateManualPluginError.buildFailed(buildResult.logText)
|
||||
}
|
||||
print("Built package in \(configuration) mode")
|
||||
|
||||
// Run generate-manual on all executable artifacts.
|
||||
for builtArtifact in buildResult.builtArtifacts {
|
||||
// Skip non-executable targets
|
||||
guard builtArtifact.kind == .executable else { continue }
|
||||
|
||||
// Skip executables without a matching product.
|
||||
guard let product = builtArtifact.matchingProduct(context: context)
|
||||
else { continue }
|
||||
|
||||
// Skip products without a dependency on ArgumentParser.
|
||||
guard product.hasDependency(named: "ArgumentParser") else { continue }
|
||||
|
||||
// Get the artifacts name.
|
||||
let executableName = builtArtifact.path.lastComponent
|
||||
print("Generating manual for \(executableName)...")
|
||||
|
||||
// Create output directory.
|
||||
let outputDirectory = context
|
||||
.pluginWorkDirectory
|
||||
.appending(executableName)
|
||||
try outputDirectory.createOutputDirectory()
|
||||
|
||||
// Create generation tool arguments.
|
||||
var generationToolArguments = [
|
||||
builtArtifact.path.string,
|
||||
"--output-directory",
|
||||
outputDirectory.string
|
||||
]
|
||||
generationToolArguments.append(
|
||||
contentsOf: extractor.unextractedOptionsOrFlags)
|
||||
|
||||
// Spawn generation tool.
|
||||
try generationToolFile.exec(arguments: generationToolArguments)
|
||||
print("Generated manual in '\(outputDirectory)'")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import Foundation
|
||||
import PackagePlugin
|
||||
|
||||
enum GenerateManualPluginError: Error {
|
||||
case unknownBuildConfiguration(String)
|
||||
case buildFailed(String)
|
||||
case createOutputDirectoryFailed(Error)
|
||||
case subprocessFailedNonZeroExit(Path, Int32)
|
||||
case subprocessFailedError(Path, Error)
|
||||
}
|
||||
|
||||
extension GenerateManualPluginError: CustomStringConvertible {
|
||||
var description: String {
|
||||
switch self {
|
||||
case .unknownBuildConfiguration(let configuration):
|
||||
return "Build failed: Unknown build configuration '\(configuration)'."
|
||||
case .buildFailed(let logText):
|
||||
return "Build failed: \(logText)."
|
||||
case .createOutputDirectoryFailed(let error):
|
||||
return """
|
||||
Failed to create output directory: '\(error.localizedDescription)'
|
||||
"""
|
||||
case .subprocessFailedNonZeroExit(let tool, let exitCode):
|
||||
return """
|
||||
'\(tool.lastComponent)' invocation failed with a nonzero exit code: \
|
||||
'\(exitCode)'.
|
||||
"""
|
||||
case .subprocessFailedError(let tool, let error):
|
||||
return """
|
||||
'\(tool.lastComponent)' invocation failed: \
|
||||
'\(error.localizedDescription)'
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension GenerateManualPluginError: LocalizedError {
|
||||
var localizedDescription: String { self.description }
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import Foundation
|
||||
import PackagePlugin
|
||||
|
||||
extension ArgumentExtractor {
|
||||
mutating func helpRequest() -> Bool {
|
||||
self.extractFlag(named: "help") > 0
|
||||
}
|
||||
|
||||
mutating func configuration() throws -> PackageManager.BuildConfiguration {
|
||||
switch self.extractOption(named: "configuration").first {
|
||||
case .some(let configurationString):
|
||||
switch configurationString {
|
||||
case "debug":
|
||||
return .debug
|
||||
case "release":
|
||||
return .release
|
||||
default:
|
||||
throw GenerateManualPluginError
|
||||
.unknownBuildConfiguration(configurationString)
|
||||
}
|
||||
case .none:
|
||||
return .release
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Path {
|
||||
func createOutputDirectory() throws {
|
||||
do {
|
||||
try FileManager.default.createDirectory(
|
||||
atPath: self.string,
|
||||
withIntermediateDirectories: true)
|
||||
} catch {
|
||||
throw GenerateManualPluginError.createOutputDirectoryFailed(error)
|
||||
}
|
||||
}
|
||||
|
||||
func exec(arguments: [String]) throws {
|
||||
do {
|
||||
let process = Process()
|
||||
process.executableURL = URL(fileURLWithPath: self.string)
|
||||
process.arguments = arguments
|
||||
try process.run()
|
||||
process.waitUntilExit()
|
||||
guard
|
||||
process.terminationReason == .exit,
|
||||
process.terminationStatus == 0
|
||||
else {
|
||||
throw GenerateManualPluginError.subprocessFailedNonZeroExit(
|
||||
self, process.terminationStatus)
|
||||
}
|
||||
} catch {
|
||||
throw GenerateManualPluginError.subprocessFailedError(self, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PackageManager.BuildResult.BuiltArtifact {
|
||||
func matchingProduct(context: PluginContext) -> Product? {
|
||||
context
|
||||
.package
|
||||
.products
|
||||
.first { $0.name == self.path.lastComponent }
|
||||
}
|
||||
}
|
||||
|
||||
extension Product {
|
||||
func hasDependency(named name: String) -> Bool {
|
||||
recursiveTargetDependencies
|
||||
.contains { $0.name == name }
|
||||
}
|
||||
|
||||
var recursiveTargetDependencies: [Target] {
|
||||
var dependencies = [Target.ID: Target]()
|
||||
for target in self.targets {
|
||||
for dependency in target.recursiveTargetDependencies {
|
||||
dependencies[dependency.id] = dependency
|
||||
}
|
||||
}
|
||||
return Array(dependencies.values)
|
||||
}
|
||||
}
|
||||
@@ -212,10 +212,22 @@ extension XCTest {
|
||||
exitCode: ExitCode = .success,
|
||||
file: StaticString = #file, line: UInt = #line) throws
|
||||
{
|
||||
let splitCommand = command.split(separator: " ")
|
||||
let arguments = splitCommand.dropFirst().map(String.init)
|
||||
|
||||
let commandName = String(splitCommand.first!)
|
||||
try AssertExecuteCommand(
|
||||
command: command.split(separator: " ").map(String.init),
|
||||
expected: expected,
|
||||
exitCode: exitCode,
|
||||
file: file,
|
||||
line: line)
|
||||
}
|
||||
|
||||
public func AssertExecuteCommand(
|
||||
command: [String],
|
||||
expected: String? = nil,
|
||||
exitCode: ExitCode = .success,
|
||||
file: StaticString = #file, line: UInt = #line) throws
|
||||
{
|
||||
let arguments = Array(command.dropFirst())
|
||||
let commandName = String(command.first!)
|
||||
let commandURL = debugURL.appendingPathComponent(commandName)
|
||||
guard (try? commandURL.checkResourceIsReachable()) ?? false else {
|
||||
XCTFail("No executable at '\(commandURL.standardizedFileURL.path)'.",
|
||||
@@ -311,4 +323,32 @@ extension XCTest {
|
||||
throw XCTSkip("Not supported on this platform")
|
||||
#endif
|
||||
}
|
||||
|
||||
public func AssertGenerateManual(
|
||||
singlePage: Bool,
|
||||
command: String,
|
||||
expected: String,
|
||||
file: StaticString = #file,
|
||||
line: UInt = #line
|
||||
) throws {
|
||||
let commandURL = debugURL.appendingPathComponent(command)
|
||||
var command = [
|
||||
"generate-manual", commandURL.path,
|
||||
"--date", "1996-05-12",
|
||||
"--section", "9",
|
||||
"--authors", "Jane Appleseed",
|
||||
"--authors", "<johnappleseed@apple.com>",
|
||||
"--authors", "The Appleseeds<appleseeds@apple.com>",
|
||||
"--output-directory", "-",
|
||||
]
|
||||
if singlePage {
|
||||
command.append("--single-page")
|
||||
}
|
||||
try AssertExecuteCommand(
|
||||
command: command,
|
||||
expected: expected,
|
||||
exitCode: .success,
|
||||
file: file,
|
||||
line: line)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2020 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#if os(macOS)
|
||||
|
||||
import XCTest
|
||||
import ArgumentParserTestHelpers
|
||||
|
||||
final class CountLinesGenerateManualTests: XCTestCase {
|
||||
func testCountLines_SinglePageManual() throws {
|
||||
guard #available(macOS 12, *) else { return }
|
||||
try AssertGenerateManual(singlePage: true, command: "count-lines", expected: #"""
|
||||
.\" "Generated by swift-argument-parser"
|
||||
.Dd May 12, 1996
|
||||
.Dt COUNT-LINES 9
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm count-lines
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Ar input-file
|
||||
.Op Fl -prefix
|
||||
.Op Fl -verbose Ar verbose
|
||||
.Fl -help Ar help
|
||||
.Sh DESCRIPTION
|
||||
.Bl -tag -width 6n
|
||||
.It Ar input-file
|
||||
A file to count lines in. If omitted, counts the lines of stdin.
|
||||
.It Fl -prefix Ar prefix
|
||||
Only count lines with this prefix.
|
||||
.It Fl -verbose
|
||||
Include extra information in the output.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
reference was written by
|
||||
.An -nosplit
|
||||
.An "Jane Appleseed" ,
|
||||
.Mt johnappleseed@apple.com ,
|
||||
.An -nosplit
|
||||
.An "The Appleseeds"
|
||||
.Ao
|
||||
.Mt appleseeds@apple.com
|
||||
.Ac .
|
||||
"""#)
|
||||
}
|
||||
|
||||
func testCountLines_MultiPageManual() throws {
|
||||
guard #available(macOS 12, *) else { return }
|
||||
try AssertGenerateManual(singlePage: false, command: "count-lines", expected: #"""
|
||||
.\" "Generated by swift-argument-parser"
|
||||
.Dd May 12, 1996
|
||||
.Dt COUNT-LINES 9
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm count-lines
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Ar input-file
|
||||
.Op Fl -prefix
|
||||
.Op Fl -verbose Ar verbose
|
||||
.Fl -help Ar help
|
||||
.Sh DESCRIPTION
|
||||
.Bl -tag -width 6n
|
||||
.It Ar input-file
|
||||
A file to count lines in. If omitted, counts the lines of stdin.
|
||||
.It Fl -prefix Ar prefix
|
||||
Only count lines with this prefix.
|
||||
.It Fl -verbose
|
||||
Include extra information in the output.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
reference was written by
|
||||
.An -nosplit
|
||||
.An "Jane Appleseed" ,
|
||||
.Mt johnappleseed@apple.com ,
|
||||
.An -nosplit
|
||||
.An "The Appleseeds"
|
||||
.Ao
|
||||
.Mt appleseeds@apple.com
|
||||
.Ac .
|
||||
"""#)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,389 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2020 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import XCTest
|
||||
import ArgumentParser
|
||||
import ArgumentParserTestHelpers
|
||||
|
||||
final class MathGenerateManualTests: XCTestCase {
|
||||
func testMath_SinglePageManual() throws {
|
||||
try AssertGenerateManual(singlePage: true, command: "math", expected: #"""
|
||||
.\" "Generated by swift-argument-parser"
|
||||
.Dd May 12, 1996
|
||||
.Dt MATH 9
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm math
|
||||
.Nd "A utility for performing maths."
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Ar subcommand
|
||||
.Fl -version Ar version
|
||||
.Fl -help Ar help
|
||||
.Sh DESCRIPTION
|
||||
.Bl -tag -width 6n
|
||||
.It Fl -version
|
||||
Show the version.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.It Em add
|
||||
.Bl -tag -width 6n
|
||||
.It Fl x , -hex-output
|
||||
Use hexadecimal notation for the result.
|
||||
.It Ar values...
|
||||
A group of integers to operate on.
|
||||
.It Fl -version
|
||||
Show the version.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.It Em multiply
|
||||
.Bl -tag -width 6n
|
||||
.It Fl x , -hex-output
|
||||
Use hexadecimal notation for the result.
|
||||
.It Ar values...
|
||||
A group of integers to operate on.
|
||||
.It Fl -version
|
||||
Show the version.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.It Em stats
|
||||
.Bl -tag -width 6n
|
||||
.It Fl -version
|
||||
Show the version.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.It Em average
|
||||
.Bl -tag -width 6n
|
||||
.It Fl -kind Ar kind
|
||||
The kind of average to provide.
|
||||
.It Ar values...
|
||||
A group of floating-point values to operate on.
|
||||
.It Fl -version
|
||||
Show the version.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.It Em stdev
|
||||
.Bl -tag -width 6n
|
||||
.It Ar values...
|
||||
A group of floating-point values to operate on.
|
||||
.It Fl -version
|
||||
Show the version.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.It Em quantiles
|
||||
.Bl -tag -width 6n
|
||||
.It Ar one-of-four
|
||||
.It Ar custom-arg
|
||||
.It Ar values...
|
||||
A group of floating-point values to operate on.
|
||||
.It Fl -test-success-exit-code
|
||||
.It Fl -test-failure-exit-code
|
||||
.It Fl -test-validation-exit-code
|
||||
.It Fl -test-custom-exit-code Ar test-custom-exit-code
|
||||
.It Fl -file Ar file
|
||||
.It Fl -directory Ar directory
|
||||
.It Fl -shell Ar shell
|
||||
.It Fl -custom Ar custom
|
||||
.It Fl -version
|
||||
Show the version.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.El
|
||||
.El
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
reference was written by
|
||||
.An -nosplit
|
||||
.An "Jane Appleseed" ,
|
||||
.Mt johnappleseed@apple.com ,
|
||||
.An -nosplit
|
||||
.An "The Appleseeds"
|
||||
.Ao
|
||||
.Mt appleseeds@apple.com
|
||||
.Ac .
|
||||
"""#)
|
||||
}
|
||||
|
||||
func testMath_MultiPageManual() throws {
|
||||
try AssertGenerateManual(singlePage: false, command: "math", expected: #"""
|
||||
.\" "Generated by swift-argument-parser"
|
||||
.Dd May 12, 1996
|
||||
.Dt MATH 9
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm math
|
||||
.Nd "A utility for performing maths."
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Ar subcommand
|
||||
.Fl -version Ar version
|
||||
.Fl -help Ar help
|
||||
.Sh DESCRIPTION
|
||||
.Bl -tag -width 6n
|
||||
.It Fl -version
|
||||
Show the version.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.Sh "SEE ALSO"
|
||||
.Xr math.add 9 ,
|
||||
.Xr math.multiply 9 ,
|
||||
.Xr math.stats 9
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
reference was written by
|
||||
.An -nosplit
|
||||
.An "Jane Appleseed" ,
|
||||
.Mt johnappleseed@apple.com ,
|
||||
.An -nosplit
|
||||
.An "The Appleseeds"
|
||||
.Ao
|
||||
.Mt appleseeds@apple.com
|
||||
.Ac .
|
||||
.\" "Generated by swift-argument-parser"
|
||||
.Dd May 12, 1996
|
||||
.Dt MATH.ADD 9
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm "math add"
|
||||
.Nd "Print the sum of the values."
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl -hex-output Ar hex-output
|
||||
.Op Ar values...
|
||||
.Fl -version Ar version
|
||||
.Fl -help Ar help
|
||||
.Sh DESCRIPTION
|
||||
.Bl -tag -width 6n
|
||||
.It Fl x , -hex-output
|
||||
Use hexadecimal notation for the result.
|
||||
.It Ar values...
|
||||
A group of integers to operate on.
|
||||
.It Fl -version
|
||||
Show the version.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
reference was written by
|
||||
.An -nosplit
|
||||
.An "Jane Appleseed" ,
|
||||
.Mt johnappleseed@apple.com ,
|
||||
.An -nosplit
|
||||
.An "The Appleseeds"
|
||||
.Ao
|
||||
.Mt appleseeds@apple.com
|
||||
.Ac .
|
||||
.\" "Generated by swift-argument-parser"
|
||||
.Dd May 12, 1996
|
||||
.Dt MATH.MULTIPLY 9
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm "math multiply"
|
||||
.Nd "Print the product of the values."
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl -hex-output Ar hex-output
|
||||
.Op Ar values...
|
||||
.Fl -version Ar version
|
||||
.Fl -help Ar help
|
||||
.Sh DESCRIPTION
|
||||
.Bl -tag -width 6n
|
||||
.It Fl x , -hex-output
|
||||
Use hexadecimal notation for the result.
|
||||
.It Ar values...
|
||||
A group of integers to operate on.
|
||||
.It Fl -version
|
||||
Show the version.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
reference was written by
|
||||
.An -nosplit
|
||||
.An "Jane Appleseed" ,
|
||||
.Mt johnappleseed@apple.com ,
|
||||
.An -nosplit
|
||||
.An "The Appleseeds"
|
||||
.Ao
|
||||
.Mt appleseeds@apple.com
|
||||
.Ac .
|
||||
.\" "Generated by swift-argument-parser"
|
||||
.Dd May 12, 1996
|
||||
.Dt MATH.STATS 9
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm "math stats"
|
||||
.Nd "Calculate descriptive statistics."
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Ar subcommand
|
||||
.Fl -version Ar version
|
||||
.Fl -help Ar help
|
||||
.Sh DESCRIPTION
|
||||
.Bl -tag -width 6n
|
||||
.It Fl -version
|
||||
Show the version.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.Sh "SEE ALSO"
|
||||
.Xr math.stats.average 9 ,
|
||||
.Xr math.stats.quantiles 9 ,
|
||||
.Xr math.stats.stdev 9
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
reference was written by
|
||||
.An -nosplit
|
||||
.An "Jane Appleseed" ,
|
||||
.Mt johnappleseed@apple.com ,
|
||||
.An -nosplit
|
||||
.An "The Appleseeds"
|
||||
.Ao
|
||||
.Mt appleseeds@apple.com
|
||||
.Ac .
|
||||
.\" "Generated by swift-argument-parser"
|
||||
.Dd May 12, 1996
|
||||
.Dt MATH.STATS.AVERAGE 9
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm "math stats average"
|
||||
.Nd "Print the average of the values."
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl -kind
|
||||
.Op Ar values...
|
||||
.Fl -version Ar version
|
||||
.Fl -help Ar help
|
||||
.Sh DESCRIPTION
|
||||
.Bl -tag -width 6n
|
||||
.It Fl -kind Ar kind
|
||||
The kind of average to provide.
|
||||
.It Ar values...
|
||||
A group of floating-point values to operate on.
|
||||
.It Fl -version
|
||||
Show the version.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
reference was written by
|
||||
.An -nosplit
|
||||
.An "Jane Appleseed" ,
|
||||
.Mt johnappleseed@apple.com ,
|
||||
.An -nosplit
|
||||
.An "The Appleseeds"
|
||||
.Ao
|
||||
.Mt appleseeds@apple.com
|
||||
.Ac .
|
||||
.\" "Generated by swift-argument-parser"
|
||||
.Dd May 12, 1996
|
||||
.Dt MATH.STATS.STDEV 9
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm "math stats stdev"
|
||||
.Nd "Print the standard deviation of the values."
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Ar values...
|
||||
.Fl -version Ar version
|
||||
.Fl -help Ar help
|
||||
.Sh DESCRIPTION
|
||||
.Bl -tag -width 6n
|
||||
.It Ar values...
|
||||
A group of floating-point values to operate on.
|
||||
.It Fl -version
|
||||
Show the version.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
reference was written by
|
||||
.An -nosplit
|
||||
.An "Jane Appleseed" ,
|
||||
.Mt johnappleseed@apple.com ,
|
||||
.An -nosplit
|
||||
.An "The Appleseeds"
|
||||
.Ao
|
||||
.Mt appleseeds@apple.com
|
||||
.Ac .
|
||||
.\" "Generated by swift-argument-parser"
|
||||
.Dd May 12, 1996
|
||||
.Dt MATH.STATS.QUANTILES 9
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm "math stats quantiles"
|
||||
.Nd "Print the quantiles of the values (TBD)."
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Ar one-of-four
|
||||
.Op Ar custom-arg
|
||||
.Op Ar values...
|
||||
.Op Fl -test-success-exit-code Ar test-success-exit-code
|
||||
.Op Fl -test-failure-exit-code Ar test-failure-exit-code
|
||||
.Op Fl -test-validation-exit-code Ar test-validation-exit-code
|
||||
.Op Fl -test-custom-exit-code
|
||||
.Op Fl -file
|
||||
.Op Fl -directory
|
||||
.Op Fl -shell
|
||||
.Op Fl -custom
|
||||
.Fl -version Ar version
|
||||
.Fl -help Ar help
|
||||
.Sh DESCRIPTION
|
||||
.Bl -tag -width 6n
|
||||
.It Ar one-of-four
|
||||
.It Ar custom-arg
|
||||
.It Ar values...
|
||||
A group of floating-point values to operate on.
|
||||
.It Fl -test-success-exit-code
|
||||
.It Fl -test-failure-exit-code
|
||||
.It Fl -test-validation-exit-code
|
||||
.It Fl -test-custom-exit-code Ar test-custom-exit-code
|
||||
.It Fl -file Ar file
|
||||
.It Fl -directory Ar directory
|
||||
.It Fl -shell Ar shell
|
||||
.It Fl -custom Ar custom
|
||||
.It Fl -version
|
||||
Show the version.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
reference was written by
|
||||
.An -nosplit
|
||||
.An "Jane Appleseed" ,
|
||||
.Mt johnappleseed@apple.com ,
|
||||
.An -nosplit
|
||||
.An "The Appleseeds"
|
||||
.Ao
|
||||
.Mt appleseeds@apple.com
|
||||
.Ac .
|
||||
"""#)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2020 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import XCTest
|
||||
import ArgumentParserTestHelpers
|
||||
|
||||
final class RepeatGenerateManualTests: XCTestCase {
|
||||
func testMath_SinglePageManual() throws {
|
||||
try AssertGenerateManual(singlePage: true, command: "repeat", expected: #"""
|
||||
.\" "Generated by swift-argument-parser"
|
||||
.Dd May 12, 1996
|
||||
.Dt REPEAT 9
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm repeat
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl -count
|
||||
.Op Fl -include-counter Ar include-counter
|
||||
.Ar phrase
|
||||
.Fl -help Ar help
|
||||
.Sh DESCRIPTION
|
||||
.Bl -tag -width 6n
|
||||
.It Fl -count Ar count
|
||||
The number of times to repeat 'phrase'.
|
||||
.It Fl -include-counter
|
||||
Include a counter with each repetition.
|
||||
.It Ar phrase
|
||||
The phrase to repeat.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
reference was written by
|
||||
.An -nosplit
|
||||
.An "Jane Appleseed" ,
|
||||
.Mt johnappleseed@apple.com ,
|
||||
.An -nosplit
|
||||
.An "The Appleseeds"
|
||||
.Ao
|
||||
.Mt appleseeds@apple.com
|
||||
.Ac .
|
||||
"""#)
|
||||
}
|
||||
|
||||
func testMath_MultiPageManual() throws {
|
||||
try AssertGenerateManual(singlePage: false, command: "repeat", expected: #"""
|
||||
.\" "Generated by swift-argument-parser"
|
||||
.Dd May 12, 1996
|
||||
.Dt REPEAT 9
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm repeat
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl -count
|
||||
.Op Fl -include-counter Ar include-counter
|
||||
.Ar phrase
|
||||
.Fl -help Ar help
|
||||
.Sh DESCRIPTION
|
||||
.Bl -tag -width 6n
|
||||
.It Fl -count Ar count
|
||||
The number of times to repeat 'phrase'.
|
||||
.It Fl -include-counter
|
||||
Include a counter with each repetition.
|
||||
.It Ar phrase
|
||||
The phrase to repeat.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
reference was written by
|
||||
.An -nosplit
|
||||
.An "Jane Appleseed" ,
|
||||
.Mt johnappleseed@apple.com ,
|
||||
.An -nosplit
|
||||
.An "The Appleseeds"
|
||||
.Ao
|
||||
.Mt appleseeds@apple.com
|
||||
.Ac .
|
||||
"""#)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2020 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import XCTest
|
||||
import ArgumentParserTestHelpers
|
||||
|
||||
final class RollDiceGenerateManualTests: XCTestCase {
|
||||
func testRollDice_SinglePageManual() throws {
|
||||
try AssertGenerateManual(singlePage: true, command: "roll", expected: #"""
|
||||
.\" "Generated by swift-argument-parser"
|
||||
.Dd May 12, 1996
|
||||
.Dt ROLL 9
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm roll
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl -times
|
||||
.Op Fl -sides
|
||||
.Op Fl -seed
|
||||
.Op Fl -verbose Ar verbose
|
||||
.Fl -help Ar help
|
||||
.Sh DESCRIPTION
|
||||
.Bl -tag -width 6n
|
||||
.It Fl -times Ar n
|
||||
Rolls the dice <n> times.
|
||||
.It Fl -sides Ar m
|
||||
Rolls an <m>-sided dice.
|
||||
.Pp
|
||||
Use this option to override the default value of a six-sided die.
|
||||
.It Fl -seed Ar seed
|
||||
A seed to use for repeatable random generation.
|
||||
.It Fl v , -verbose
|
||||
Show all roll results.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
reference was written by
|
||||
.An -nosplit
|
||||
.An "Jane Appleseed" ,
|
||||
.Mt johnappleseed@apple.com ,
|
||||
.An -nosplit
|
||||
.An "The Appleseeds"
|
||||
.Ao
|
||||
.Mt appleseeds@apple.com
|
||||
.Ac .
|
||||
"""#)
|
||||
}
|
||||
|
||||
func testRollDice_MultiPageManual() throws {
|
||||
try AssertGenerateManual(singlePage: false, command: "roll", expected: #"""
|
||||
.\" "Generated by swift-argument-parser"
|
||||
.Dd May 12, 1996
|
||||
.Dt ROLL 9
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm roll
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl -times
|
||||
.Op Fl -sides
|
||||
.Op Fl -seed
|
||||
.Op Fl -verbose Ar verbose
|
||||
.Fl -help Ar help
|
||||
.Sh DESCRIPTION
|
||||
.Bl -tag -width 6n
|
||||
.It Fl -times Ar n
|
||||
Rolls the dice <n> times.
|
||||
.It Fl -sides Ar m
|
||||
Rolls an <m>-sided dice.
|
||||
.Pp
|
||||
Use this option to override the default value of a six-sided die.
|
||||
.It Fl -seed Ar seed
|
||||
A seed to use for repeatable random generation.
|
||||
.It Fl v , -verbose
|
||||
Show all roll results.
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
reference was written by
|
||||
.An -nosplit
|
||||
.An "Jane Appleseed" ,
|
||||
.Mt johnappleseed@apple.com ,
|
||||
.An -nosplit
|
||||
.An "The Appleseeds"
|
||||
.Ao
|
||||
.Mt appleseeds@apple.com
|
||||
.Ac .
|
||||
"""#)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ArgumentParser
|
||||
import ArgumentParserToolInfo
|
||||
|
||||
fileprivate extension Character {
|
||||
static let emailStart: Character = "<"
|
||||
static let emailEnd: Character = ">"
|
||||
}
|
||||
|
||||
fileprivate extension Substring {
|
||||
mutating func collecting(until terminator: (Element) throws -> Bool) rethrows -> String {
|
||||
let terminatorIndex = try firstIndex(where: terminator) ?? endIndex
|
||||
let collected = String(self[..<terminatorIndex])
|
||||
self = self[terminatorIndex...]
|
||||
return collected
|
||||
}
|
||||
|
||||
mutating func next() {
|
||||
if !isEmpty { removeFirst() }
|
||||
}
|
||||
}
|
||||
|
||||
enum AuthorArgument {
|
||||
case name(name: String)
|
||||
case email(email: String)
|
||||
case both(name: String, email: String)
|
||||
}
|
||||
|
||||
extension AuthorArgument: ExpressibleByArgument {
|
||||
// parsed as:
|
||||
// - name: `name`
|
||||
// - email: `<email>`
|
||||
// - both: `name<email>`
|
||||
public init?(argument: String) {
|
||||
var argument = argument[...]
|
||||
// collect until the email start character is seen.
|
||||
let name = argument.collecting(until: { $0 == .emailStart })
|
||||
// drop the email start character.
|
||||
argument.next()
|
||||
// collect until the email end character is seen.
|
||||
let email = argument.collecting(until: { $0 == .emailEnd })
|
||||
// drop the email end character.
|
||||
argument.next()
|
||||
// ensure no collected characters remain.
|
||||
guard argument.isEmpty else { return nil }
|
||||
|
||||
switch (name.isEmpty, email.isEmpty) {
|
||||
case (true, true):
|
||||
return nil
|
||||
case (false, true):
|
||||
self = .name(name: name)
|
||||
case (true, false):
|
||||
self = .email(email: email)
|
||||
case (false, false):
|
||||
self = .both(name: name, email: email)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ArgumentParser
|
||||
import ArgumentParserToolInfo
|
||||
|
||||
struct ArgumentSynopsis: MDocComponent {
|
||||
var argument: ArgumentInfoV0
|
||||
|
||||
var body: MDocComponent {
|
||||
if argument.isOptional {
|
||||
MDocMacro.OptionalCommandLineComponent(arguments: [synopsis])
|
||||
} else {
|
||||
synopsis
|
||||
}
|
||||
}
|
||||
|
||||
// ArgumentInfoV0 formatted as MDoc without optional bracket wrapper.
|
||||
var synopsis: MDocASTNode {
|
||||
switch argument.kind {
|
||||
case .positional:
|
||||
return argument.manualPageDescription
|
||||
case .option:
|
||||
// preferredName cannot be nil
|
||||
let name = argument.preferredName!
|
||||
return MDocMacro.CommandOption(options: [name.manualPage])
|
||||
case .flag:
|
||||
// preferredName cannot be nil
|
||||
let name = argument.preferredName!
|
||||
return MDocMacro.CommandOption(options: [name.manualPage])
|
||||
.withUnsafeChildren(nodes: [argument.manualPageValueName])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ArgumentParser
|
||||
import ArgumentParserToolInfo
|
||||
|
||||
struct Author: MDocComponent {
|
||||
var author: AuthorArgument
|
||||
var trailing: String
|
||||
|
||||
var body: MDocComponent {
|
||||
switch author {
|
||||
case let .name(name):
|
||||
MDocMacro.Author(split: false)
|
||||
MDocMacro.Author(name: name)
|
||||
.withUnsafeChildren(nodes: [trailing])
|
||||
case let .email(email):
|
||||
MDocMacro.MailTo(email: email)
|
||||
.withUnsafeChildren(nodes: [trailing])
|
||||
case let .both(name, email):
|
||||
MDocMacro.Author(split: false)
|
||||
MDocMacro.Author(name: name)
|
||||
MDocMacro.BeginAngleBrackets()
|
||||
MDocMacro.MailTo(email: email)
|
||||
MDocMacro.EndAngleBrackets()
|
||||
.withUnsafeChildren(nodes: [trailing])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ArgumentParser
|
||||
import ArgumentParserToolInfo
|
||||
|
||||
struct Authors: MDocComponent {
|
||||
var authors: [AuthorArgument]
|
||||
|
||||
var body: MDocComponent {
|
||||
Section(title: "authors") {
|
||||
if !authors.isEmpty {
|
||||
"The"
|
||||
MDocMacro.DocumentName()
|
||||
"reference was written by"
|
||||
ForEach(authors) { author, last in
|
||||
Author(author: author, trailing: last ? "." : ",")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ArgumentParser
|
||||
import ArgumentParserToolInfo
|
||||
|
||||
struct Container: MDocComponent {
|
||||
var ast: [MDocASTNode] { children.flatMap { $0.ast } }
|
||||
var body: MDocComponent { self }
|
||||
var children: [MDocComponent]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
struct Empty: MDocComponent {
|
||||
var ast: [MDocASTNode] { [] }
|
||||
var body: MDocComponent { self }
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
struct ForEach<C>: MDocComponent where C: Collection {
|
||||
var items: C
|
||||
var builder: (C.Element, Bool) -> MDocComponent
|
||||
|
||||
init(_ items: C, @MDocBuilder builder: @escaping (C.Element, Bool) -> MDocComponent) {
|
||||
self.items = items
|
||||
self.builder = builder
|
||||
}
|
||||
|
||||
var body: MDocComponent {
|
||||
guard !items.isEmpty else { return Empty() }
|
||||
var currentIndex = items.startIndex
|
||||
var last = false
|
||||
var components = [MDocComponent]()
|
||||
repeat {
|
||||
let item = items[currentIndex]
|
||||
currentIndex = items.index(after: currentIndex)
|
||||
last = currentIndex == items.endIndex
|
||||
components.append(builder(item, last))
|
||||
} while !last
|
||||
return Container(children: components)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
struct MDocASTNodeWrapper: MDocComponent {
|
||||
var ast: [MDocASTNode] { [node] }
|
||||
var body: MDocComponent { self }
|
||||
var node: MDocASTNode
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@resultBuilder
|
||||
struct MDocBuilder {
|
||||
static func buildBlock(_ components: MDocComponent...) -> MDocComponent { Container(children: components) }
|
||||
static func buildArray(_ components: [MDocComponent]) -> MDocComponent { Container(children: components) }
|
||||
static func buildOptional(_ component: MDocComponent?) -> MDocComponent { component ?? Empty() }
|
||||
static func buildEither(first component: MDocComponent) -> MDocComponent { component }
|
||||
static func buildEither(second component: MDocComponent) -> MDocComponent { component }
|
||||
static func buildExpression(_ expression: MDocComponent) -> MDocComponent { expression }
|
||||
static func buildExpression(_ expression: MDocASTNode) -> MDocComponent { MDocASTNodeWrapper(node: expression) }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
protocol MDocComponent {
|
||||
var ast: [MDocASTNode] { get }
|
||||
@MDocBuilder
|
||||
var body: MDocComponent { get }
|
||||
}
|
||||
|
||||
extension MDocComponent {
|
||||
var ast: [MDocASTNode] { body.ast }
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ArgumentParser
|
||||
import ArgumentParserToolInfo
|
||||
import Foundation
|
||||
|
||||
struct Document: MDocComponent {
|
||||
var singlePage: Bool
|
||||
var date: Date
|
||||
var section: Int
|
||||
var authors: [AuthorArgument]
|
||||
var command: CommandInfoV0
|
||||
|
||||
var body: MDocComponent {
|
||||
Preamble(date: date, section: section, command: command)
|
||||
Name(command: command)
|
||||
Synopsis(command: command)
|
||||
if singlePage {
|
||||
SinglePageDescription(command: command)
|
||||
} else {
|
||||
MultiPageDescription(command: command)
|
||||
}
|
||||
Exit(section: section)
|
||||
if !singlePage {
|
||||
SeeAlso(section: section, command: command)
|
||||
}
|
||||
Authors(authors: authors)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ArgumentParser
|
||||
import ArgumentParserToolInfo
|
||||
import Foundation
|
||||
|
||||
struct DocumentDate: MDocComponent {
|
||||
private var month: String
|
||||
private var day: Int
|
||||
private var year: Int
|
||||
|
||||
init(date: Date) {
|
||||
let calendar = Calendar(identifier: .iso8601)
|
||||
let timeZone = TimeZone(identifier: "UTC")!
|
||||
let formatter = DateFormatter()
|
||||
formatter.calendar = calendar
|
||||
formatter.timeZone = timeZone
|
||||
formatter.dateFormat = "MMMM"
|
||||
self.month = formatter.string(from: date)
|
||||
let components = calendar.dateComponents(in: timeZone, from: date)
|
||||
self.day = components.day!
|
||||
self.year = components.year!
|
||||
}
|
||||
|
||||
var body: MDocComponent {
|
||||
MDocMacro.DocumentDate(day: day, month: month, year: year)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ArgumentParser
|
||||
import ArgumentParserToolInfo
|
||||
|
||||
struct Exit: MDocComponent {
|
||||
var section: Int
|
||||
|
||||
var body: MDocComponent {
|
||||
Section(title: "exit status") {
|
||||
if [1, 6, 8].contains(section) {
|
||||
MDocMacro.ExitStandard()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
struct List: MDocComponent {
|
||||
var content: MDocComponent
|
||||
|
||||
init(@MDocBuilder content: () -> MDocComponent) {
|
||||
self.content = content()
|
||||
}
|
||||
|
||||
var body: MDocComponent {
|
||||
if !content.ast.isEmpty {
|
||||
MDocMacro.BeginList(style: .tag, width: 6)
|
||||
content
|
||||
MDocMacro.EndList()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ArgumentParser
|
||||
import ArgumentParserToolInfo
|
||||
|
||||
struct MultiPageDescription: MDocComponent {
|
||||
var command: CommandInfoV0
|
||||
|
||||
var body: MDocComponent {
|
||||
Section(title: "description") {
|
||||
if let discussion = command.discussion {
|
||||
discussion
|
||||
}
|
||||
|
||||
List {
|
||||
for argument in command.arguments ?? [] {
|
||||
MDocMacro.ListItem(title: argument.manualPageDescription)
|
||||
|
||||
if let abstract = argument.abstract {
|
||||
abstract
|
||||
}
|
||||
|
||||
if argument.abstract != nil, argument.discussion != nil {
|
||||
MDocMacro.ParagraphBreak()
|
||||
}
|
||||
|
||||
if let discussion = argument.discussion {
|
||||
discussion
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ArgumentParser
|
||||
import ArgumentParserToolInfo
|
||||
|
||||
struct Name: MDocComponent {
|
||||
var command: CommandInfoV0
|
||||
|
||||
var body: MDocComponent {
|
||||
Section(title: "name") {
|
||||
MDocMacro.DocumentName(name: command.manualPageName)
|
||||
if let abstract = command.abstract {
|
||||
MDocMacro.DocumentDescription(description: abstract)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ArgumentParser
|
||||
import ArgumentParserToolInfo
|
||||
import Foundation
|
||||
|
||||
struct Preamble: MDocComponent {
|
||||
var date: Date
|
||||
var section: Int
|
||||
var command: CommandInfoV0
|
||||
|
||||
var body: MDocComponent {
|
||||
MDocMacro.Comment("Generated by swift-argument-parser")
|
||||
DocumentDate(date: date)
|
||||
MDocMacro.DocumentTitle(title: command.manualPageDocumentTitle, section: section)
|
||||
MDocMacro.OperatingSystem()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
struct Section: MDocComponent {
|
||||
var title: String
|
||||
var content: MDocComponent
|
||||
|
||||
init(title: String, @MDocBuilder content: () -> MDocComponent) {
|
||||
self.title = title
|
||||
self.content = content()
|
||||
}
|
||||
|
||||
var body: MDocComponent {
|
||||
if !content.ast.isEmpty {
|
||||
MDocMacro.SectionHeader(title: title.uppercased())
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ArgumentParser
|
||||
import ArgumentParserToolInfo
|
||||
|
||||
struct SeeAlso: MDocComponent {
|
||||
var section: Int
|
||||
var command: CommandInfoV0
|
||||
private var references: [String] {
|
||||
(command.subcommands ?? [])
|
||||
.map(\.manualPageTitle)
|
||||
.sorted()
|
||||
}
|
||||
|
||||
var body: MDocComponent {
|
||||
Section(title: "see also") {
|
||||
ForEach(references) { reference, isLast in
|
||||
MDocMacro.CrossManualReference(title: reference, section: section)
|
||||
.withUnsafeChildren(nodes: isLast ? [] : [","])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ArgumentParser
|
||||
import ArgumentParserToolInfo
|
||||
|
||||
struct SinglePageDescription: MDocComponent {
|
||||
var command: CommandInfoV0
|
||||
|
||||
var body: MDocComponent {
|
||||
Section(title: "description") {
|
||||
core
|
||||
}
|
||||
}
|
||||
|
||||
@MDocBuilder
|
||||
var core: MDocComponent {
|
||||
if let discussion = command.discussion {
|
||||
discussion
|
||||
}
|
||||
|
||||
List {
|
||||
for argument in command.arguments ?? [] {
|
||||
MDocMacro.ListItem(title: argument.manualPageDescription)
|
||||
|
||||
if let abstract = argument.abstract {
|
||||
abstract
|
||||
}
|
||||
|
||||
if argument.abstract != nil, argument.discussion != nil {
|
||||
MDocMacro.ParagraphBreak()
|
||||
}
|
||||
|
||||
if let discussion = argument.discussion {
|
||||
discussion
|
||||
}
|
||||
}
|
||||
|
||||
for subcommand in command.subcommands ?? [] {
|
||||
MDocMacro.ListItem(title: MDocMacro.Emphasis(arguments: [subcommand.commandName]))
|
||||
SinglePageDescription(command: subcommand).core
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ArgumentParser
|
||||
import ArgumentParserToolInfo
|
||||
|
||||
struct Synopsis: MDocComponent {
|
||||
var command: CommandInfoV0
|
||||
|
||||
var body: MDocComponent {
|
||||
Section(title: "synopsis") {
|
||||
MDocMacro.DocumentName()
|
||||
|
||||
if command.subcommands != nil {
|
||||
if command.defaultSubcommand != nil {
|
||||
MDocMacro.BeginOptionalCommandLineComponent()
|
||||
}
|
||||
MDocMacro.CommandArgument(arguments: ["subcommand"])
|
||||
if command.defaultSubcommand != nil {
|
||||
MDocMacro.EndOptionalCommandLineComponent()
|
||||
}
|
||||
}
|
||||
for argument in command.arguments ?? [] {
|
||||
ArgumentSynopsis(argument: argument)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ArgumentParser
|
||||
import ArgumentParserToolInfo
|
||||
|
||||
extension CommandInfoV0 {
|
||||
func manualPageFileName(section: Int) -> String {
|
||||
manualPageTitle + ".\(section)"
|
||||
}
|
||||
|
||||
var manualPageDocumentTitle: String {
|
||||
let parts = (superCommands ?? []) + [commandName]
|
||||
return parts.joined(separator: ".").uppercased()
|
||||
}
|
||||
|
||||
var manualPageTitle: String {
|
||||
let parts = (superCommands ?? []) + [commandName]
|
||||
return parts.joined(separator: ".")
|
||||
}
|
||||
|
||||
var manualPageName: String {
|
||||
let parts = (superCommands ?? []) + [commandName]
|
||||
return parts.joined(separator: " ")
|
||||
}
|
||||
}
|
||||
|
||||
extension ArgumentInfoV0 {
|
||||
// ArgumentInfoV0 value name as MDoc with "..." appended if the argument is
|
||||
// repeating.
|
||||
var manualPageValueName: MDocASTNode {
|
||||
var valueName = valueName ?? ""
|
||||
if isRepeating {
|
||||
valueName += "..."
|
||||
}
|
||||
// FIXME: MDocMacro.Emphasis?
|
||||
return MDocMacro.CommandArgument(arguments: [valueName])
|
||||
}
|
||||
|
||||
// ArgumentDefinition formatted as MDoc for use in a description section.
|
||||
var manualPageDescription: MDocASTNode {
|
||||
// names.partitioned.map(\.manualPage).interspersed(with: ",")
|
||||
var synopses = (names ?? []).partitioned
|
||||
.flatMap { [$0.manualPage, ","] }
|
||||
synopses = synopses.dropLast()
|
||||
|
||||
switch kind {
|
||||
case .positional:
|
||||
return manualPageValueName
|
||||
case .option:
|
||||
return MDocMacro.CommandOption(options: synopses)
|
||||
.withUnsafeChildren(nodes: [manualPageValueName])
|
||||
case .flag:
|
||||
return MDocMacro.CommandOption(options: synopses)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ArgumentInfoV0.NameInfoV0 {
|
||||
// Name formatted as MDoc.
|
||||
var manualPage: MDocASTNode {
|
||||
switch kind {
|
||||
case .long:
|
||||
return "-\(name)"
|
||||
case .short:
|
||||
return name
|
||||
case .longWithSingleDash:
|
||||
return name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == ParsableCommand.Type {
|
||||
var commandNames: [String] {
|
||||
var commandNames = [String]()
|
||||
if let superName = first?.configuration._superCommandName {
|
||||
commandNames.append(superName)
|
||||
}
|
||||
commandNames.append(contentsOf: map { $0._commandName })
|
||||
return commandNames
|
||||
}
|
||||
}
|
||||
|
||||
extension BidirectionalCollection where Element == ArgumentInfoV0.NameInfoV0 {
|
||||
var preferredName: Element? {
|
||||
first { $0.kind != .short } ?? first
|
||||
}
|
||||
|
||||
var partitioned: [Element] {
|
||||
filter { $0.kind == .short } + filter { $0.kind != .short }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ArgumentParser
|
||||
import Foundation
|
||||
|
||||
extension Date: ExpressibleByArgument {
|
||||
// parsed as `yyyy-mm-dd`
|
||||
public init?(argument: String) {
|
||||
// ensure the input argument is composed of exactly 3 components separated
|
||||
// by dashes ('-')
|
||||
let components = argument.split(separator: "-")
|
||||
let empty = components.filter { $0.isEmpty }
|
||||
guard components.count == 3, empty.count == 0 else { return nil }
|
||||
|
||||
// ensure the year component is exactly 4 characters
|
||||
let _year = components[0]
|
||||
guard _year.count == 4, let year = Int(_year) else { return nil }
|
||||
|
||||
// ensure the month component is exactly 2 characters
|
||||
let _month = components[1]
|
||||
guard _month.count == 2, let month = Int(_month) else { return nil }
|
||||
|
||||
// ensure the day component is exactly 2 characters
|
||||
let _day = components[2]
|
||||
guard _day.count == 2, let day = Int(_day) else { return nil }
|
||||
|
||||
// ensure the combination of year, month, day is valid
|
||||
let dateComponents = DateComponents(
|
||||
calendar: Calendar(identifier: .iso8601),
|
||||
timeZone: TimeZone(identifier: "UTC"),
|
||||
year: year,
|
||||
month: month,
|
||||
day: day)
|
||||
guard dateComponents.isValidDate else { return nil }
|
||||
guard let date = dateComponents.date else { return nil }
|
||||
self = date
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum SubprocessError: Swift.Error, LocalizedError, CustomStringConvertible {
|
||||
case missingExecutable(url: URL)
|
||||
case failedToLaunch(error: Swift.Error)
|
||||
case nonZeroExitCode(code: Int)
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .missingExecutable(let url):
|
||||
return "No executable at '\(url.standardizedFileURL.path)'."
|
||||
case .failedToLaunch(let error):
|
||||
return "Couldn't run command process. \(error.localizedDescription)"
|
||||
case .nonZeroExitCode(let code):
|
||||
return "Process returned non-zero exit code '\(code)'."
|
||||
}
|
||||
}
|
||||
|
||||
var errorDescription: String? { description }
|
||||
}
|
||||
|
||||
func executeCommand(
|
||||
executable: URL,
|
||||
arguments: [String]
|
||||
) throws -> String {
|
||||
guard (try? executable.checkResourceIsReachable()) ?? false else {
|
||||
throw SubprocessError.missingExecutable(url: executable)
|
||||
}
|
||||
|
||||
let process = Process()
|
||||
if #available(macOS 10.13, *) {
|
||||
process.executableURL = executable
|
||||
} else {
|
||||
process.launchPath = executable.path
|
||||
}
|
||||
process.arguments = arguments
|
||||
|
||||
let output = Pipe()
|
||||
process.standardOutput = output
|
||||
process.standardError = FileHandle.nullDevice
|
||||
|
||||
if #available(macOS 10.13, *) {
|
||||
do {
|
||||
try process.run()
|
||||
} catch {
|
||||
throw SubprocessError.failedToLaunch(error: error)
|
||||
}
|
||||
} else {
|
||||
process.launch()
|
||||
}
|
||||
let outputData = output.fileHandleForReading.readDataToEndOfFile()
|
||||
process.waitUntilExit()
|
||||
|
||||
guard process.terminationStatus == 0 else {
|
||||
throw SubprocessError.nonZeroExitCode(code: Int(process.terminationStatus))
|
||||
}
|
||||
|
||||
let outputActual = String(data: outputData, encoding: .utf8)?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
?? ""
|
||||
|
||||
return outputActual
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ArgumentParser
|
||||
import ArgumentParserToolInfo
|
||||
import Foundation
|
||||
|
||||
@main
|
||||
struct GenerateManual: ParsableCommand {
|
||||
enum Error: Swift.Error {
|
||||
case failedToRunSubprocess(error: Swift.Error)
|
||||
case unableToParseToolOutput(error: Swift.Error)
|
||||
case unsupportedDumpHelpVersion(expected: Int, found: Int)
|
||||
case failedToGenerateManualPages(error: Swift.Error)
|
||||
}
|
||||
|
||||
static let configuration = CommandConfiguration(
|
||||
commandName: "generate-manual",
|
||||
abstract: "Generate a manual for the provided tool.")
|
||||
|
||||
@Argument(help: "Tool to generate manual for.")
|
||||
var tool: String
|
||||
|
||||
@Flag(help: "Generate a single page with information for all subcommands.")
|
||||
var singlePage = false
|
||||
|
||||
@Option(name: .long, help: "Override the creation date of the manual. Format: 'yyyy-mm-dd'.")
|
||||
var date: Date = Date()
|
||||
|
||||
@Option(name: .long, help: "Section of the manual.")
|
||||
var section: Int = 1
|
||||
|
||||
@Option(name: .long, help: "Names and/or emails of the tool's authors. Format: 'name<email>'.")
|
||||
var authors: [AuthorArgument] = []
|
||||
|
||||
@Option(name: .shortAndLong, help: "Directory to save generated manual. Use '-' for stdout.")
|
||||
var outputDirectory: String
|
||||
|
||||
func validate() throws {
|
||||
// Only man pages 1 through 9 are valid.
|
||||
if !(1...9).contains(section) {
|
||||
throw ValidationError("Invalid manual section passed to --section")
|
||||
}
|
||||
|
||||
if outputDirectory != "-" {
|
||||
// outputDirectory must already exist, `GenerateManual` will not create it.
|
||||
var objcBool: ObjCBool = true
|
||||
guard FileManager.default.fileExists(atPath: outputDirectory, isDirectory: &objcBool) else {
|
||||
throw ValidationError("Output directory \(outputDirectory) does not exist")
|
||||
}
|
||||
|
||||
guard objcBool.boolValue else {
|
||||
throw ValidationError("Output directory \(outputDirectory) is not a directory")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func run() throws {
|
||||
let data: Data
|
||||
do {
|
||||
let tool = URL(fileURLWithPath: tool)
|
||||
let output = try executeCommand(executable: tool, arguments: ["--experimental-dump-help"])
|
||||
data = output.data(using: .utf8) ?? Data()
|
||||
} catch {
|
||||
throw Error.failedToRunSubprocess(error: error)
|
||||
}
|
||||
|
||||
do {
|
||||
let toolInfoThin = try JSONDecoder().decode(ToolInfoHeader.self, from: data)
|
||||
guard toolInfoThin.serializationVersion == 0 else {
|
||||
throw Error.unsupportedDumpHelpVersion(
|
||||
expected: 0,
|
||||
found: toolInfoThin.serializationVersion)
|
||||
}
|
||||
} catch {
|
||||
throw Error.unableToParseToolOutput(error: error)
|
||||
}
|
||||
|
||||
let toolInfo: ToolInfoV0
|
||||
do {
|
||||
toolInfo = try JSONDecoder().decode(ToolInfoV0.self, from: data)
|
||||
} catch {
|
||||
throw Error.unableToParseToolOutput(error: error)
|
||||
}
|
||||
|
||||
do {
|
||||
if outputDirectory == "-" {
|
||||
try generatePages(from: toolInfo.command, savingTo: nil)
|
||||
} else {
|
||||
try generatePages(
|
||||
from: toolInfo.command,
|
||||
savingTo: URL(fileURLWithPath: outputDirectory))
|
||||
}
|
||||
} catch {
|
||||
throw Error.failedToGenerateManualPages(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
func generatePages(from command: CommandInfoV0, savingTo directory: URL?) throws {
|
||||
let document = Document(
|
||||
singlePage: singlePage,
|
||||
date: date,
|
||||
section: section,
|
||||
authors: authors,
|
||||
command: command)
|
||||
let page = document.ast.map { $0.serialized() }.joined(separator: "\n")
|
||||
|
||||
if let directory = directory {
|
||||
let fileName = command.manualPageFileName(section: section)
|
||||
let outputPath = directory.appendingPathComponent(fileName)
|
||||
try page.write(to: outputPath, atomically: false, encoding: .utf8)
|
||||
} else {
|
||||
print(page)
|
||||
}
|
||||
|
||||
if !singlePage {
|
||||
for subcommand in command.subcommands ?? [] {
|
||||
try generatePages(from: subcommand, savingTo: directory)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/// `MDocASTNode` represents a single abstract syntax tree node in an `mdoc`
|
||||
/// document. `mdoc` is a semantic markup language for formatting manual pages.
|
||||
///
|
||||
/// See: https://mandoc.bsd.lv/man/mdoc.7.html for more information.
|
||||
public protocol MDocASTNode {
|
||||
/// `_serialized` is an implementation detail and should not be used directly.
|
||||
/// Please use `serialized` instead.
|
||||
func _serialized(context: MDocSerializationContext) -> String
|
||||
}
|
||||
|
||||
extension MDocASTNode {
|
||||
/// `serialized` Serializes an MDocASTNode and children into its string
|
||||
/// representation for use with other tools.
|
||||
public func serialized() -> String {
|
||||
_serialized(context: MDocSerializationContext())
|
||||
}
|
||||
}
|
||||
|
||||
extension Int: MDocASTNode {
|
||||
public func _serialized(context: MDocSerializationContext) -> String {
|
||||
"\(self)"
|
||||
}
|
||||
}
|
||||
|
||||
extension String: MDocASTNode {
|
||||
public func _serialized(context: MDocSerializationContext) -> String {
|
||||
context.macroLine
|
||||
? self.escapedMacroArgument()
|
||||
: self.escapedTextLine()
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/// The context needed to serialize an AST Node to its string representation.
|
||||
public struct MDocSerializationContext {
|
||||
var macroLine: Bool = false
|
||||
|
||||
public init() { }
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
//===----------------------------------------------------------*- swift -*-===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// Escaping rules
|
||||
// https://mandoc.bsd.lv/mdoc/intro/escaping.html
|
||||
extension String {
|
||||
func escapedMacroArgument() -> String {
|
||||
var escaped = ""
|
||||
var containsBlankCharacter = false
|
||||
|
||||
// TODO: maybe drop `where character.isASCII` clause
|
||||
for character in self where character.isASCII {
|
||||
switch character {
|
||||
case " ":
|
||||
escaped.append(character)
|
||||
containsBlankCharacter = true
|
||||
|
||||
// backslashes:
|
||||
// To output a backslash, use the escape sequence `\e`. Never use the escape sequence `\\` in any context.
|
||||
case #"\"#:
|
||||
escaped += #"\e"#
|
||||
|
||||
// double quotes in macro arguments:
|
||||
// If a macro argument needs to contain a double quote character, write it as “\(dq”. No escaping is needed on text input lines.
|
||||
case "\"":
|
||||
escaped += #"\(dq"#
|
||||
|
||||
default:
|
||||
// Custom addition:
|
||||
// newlines in macro arguments:
|
||||
// If a macro argument contains a newline character, replace it with a blank character.
|
||||
if character.isNewline {
|
||||
escaped.append(" ")
|
||||
containsBlankCharacter = true
|
||||
} else {
|
||||
escaped.append(character)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME:
|
||||
// macro names as macro arguments:
|
||||
// If the name of another mdoc(7) macro occurs as an argument on an mdoc(7) macro line, the former macro is called, and any remaining arguments are passed to it. To prevent this call and instead render the name of the former macro literally, prepend the name with a zero-width space (‘\&’). See the MACRO SYNTAX section of the mdoc(7) manual for details.
|
||||
|
||||
// blanks in macro arguments
|
||||
// If a macro argument needs to contain a blank character, enclose the whole argument in double quotes. For example, this often occurs with Fa macros. See the MACRO SYNTAX in the roff(7) manual for details.
|
||||
if escaped.isEmpty || containsBlankCharacter {
|
||||
return "\"\(escaped)\""
|
||||
}
|
||||
|
||||
return escaped
|
||||
}
|
||||
|
||||
func escapedTextLine() -> String {
|
||||
var escaped = ""
|
||||
var atBeginning = true
|
||||
|
||||
// TODO: maybe drop `where character.isASCII` clause
|
||||
for character in self where character.isASCII {
|
||||
switch (character, atBeginning) {
|
||||
|
||||
// backslashes:
|
||||
// To output a backslash, use the escape sequence `\e`. Never use the escape sequence `\\` in any context.
|
||||
case (#"\"#, _):
|
||||
escaped += #"\e"#
|
||||
atBeginning = false
|
||||
|
||||
// dots and apostrophes at the beginning of text lines:
|
||||
// If a text input line needs to begin with a dot (`.`) or apostrophe (`'`), prepend a zero-width space (`\&`) to prevent the line from being mistaken for a macro line. Never use the escape sequence `\.` in any context.
|
||||
case (".", true), ("'", true):
|
||||
escaped += #"\&"#
|
||||
escaped.append(character)
|
||||
atBeginning = false
|
||||
|
||||
// blank characters at the beginning of text lines:
|
||||
// If a text input line needs to begin with a blank character (` `) and no line break is desired before that line, prepend a zero-width space (`\&`).
|
||||
case (" ", true):
|
||||
escaped += #"\&"#
|
||||
escaped.append(character)
|
||||
|
||||
default:
|
||||
escaped.append(character)
|
||||
atBeginning = false
|
||||
}
|
||||
}
|
||||
|
||||
return escaped
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user