mirror of
https://github.com/apple/swift-argument-parser.git
synced 2026-06-06 20:18:23 +00:00
189 lines
6.1 KiB
Swift
189 lines
6.1 KiB
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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
enum MessageInfo {
|
|
case help(text: String)
|
|
case validation(message: String, usage: String, help: String)
|
|
case other(message: String, exitCode: ExitCode)
|
|
|
|
init(error: Error, type: ParsableArguments.Type, columns: Int? = nil) {
|
|
var commandStack: [ParsableCommand.Type]
|
|
var parserError: ParserError? = nil
|
|
|
|
switch error {
|
|
case let e as CommandError:
|
|
commandStack = e.commandStack
|
|
parserError = e.parserError
|
|
|
|
// Exit early on built-in requests
|
|
switch e.parserError {
|
|
case .helpRequested(let visibility):
|
|
self = .help(
|
|
text: HelpGenerator(
|
|
commandStack: e.commandStack, visibility: visibility
|
|
).rendered(screenWidth: columns))
|
|
return
|
|
|
|
case .dumpHelpRequested:
|
|
self = .help(
|
|
text: DumpHelpGenerator(commandStack: e.commandStack).rendered())
|
|
return
|
|
|
|
case .versionRequested:
|
|
let versionString =
|
|
commandStack
|
|
.map { $0.configuration.version }
|
|
.last(where: { !$0.isEmpty })
|
|
?? "Unspecified version"
|
|
self = .help(text: versionString)
|
|
return
|
|
|
|
case .completionScriptRequested(let shell):
|
|
do {
|
|
let completionsGenerator = try CompletionsGenerator(
|
|
command: type.asCommand, shellName: shell)
|
|
self = .help(text: completionsGenerator.generateCompletionScript())
|
|
return
|
|
} catch {
|
|
self.init(error: error, type: type)
|
|
return
|
|
}
|
|
|
|
case .completionScriptCustomResponse(let output):
|
|
self = .help(text: output)
|
|
return
|
|
|
|
default:
|
|
break
|
|
}
|
|
|
|
case let e as ParserError:
|
|
// Send ParserErrors back through the CommandError path
|
|
self.init(
|
|
error: CommandError(commandStack: [type.asCommand], parserError: e),
|
|
type: type, columns: columns)
|
|
return
|
|
|
|
default:
|
|
commandStack = [type.asCommand]
|
|
// if the error wasn't one of our two Error types, wrap it as a userValidationError
|
|
// to be handled appropriately below
|
|
parserError = .userValidationError(error)
|
|
}
|
|
|
|
var usage = HelpGenerator(commandStack: commandStack, visibility: .default)
|
|
.usageMessage()
|
|
|
|
let commandNames = commandStack.map { $0._commandName }.joined(
|
|
separator: " ")
|
|
if let helpName = commandStack.getPrimaryHelpName() {
|
|
if !usage.isEmpty {
|
|
usage += "\n"
|
|
}
|
|
usage +=
|
|
" See '\(commandNames) \(helpName.synopsisString)' for more information."
|
|
}
|
|
|
|
// Parsing errors and user-thrown validation errors have the usage
|
|
// string attached. Other errors just get the error message.
|
|
|
|
if case .userValidationError(let error) = parserError {
|
|
switch error {
|
|
case let error as ValidationError:
|
|
self = .validation(message: error.message, usage: usage, help: "")
|
|
case let error as CleanExit:
|
|
switch error.base {
|
|
case .helpRequest(let command):
|
|
if let command = command {
|
|
commandStack = CommandParser(type.asCommand).commandStack(
|
|
for: command)
|
|
}
|
|
self = .help(
|
|
text: HelpGenerator(
|
|
commandStack: commandStack, visibility: .default
|
|
).rendered(screenWidth: columns))
|
|
case .dumpRequest(let command):
|
|
if let command = command {
|
|
commandStack = CommandParser(type.asCommand).commandStack(
|
|
for: command)
|
|
}
|
|
self = .help(
|
|
text: DumpHelpGenerator(commandStack: commandStack).rendered())
|
|
case .message(let message):
|
|
self = .help(text: message)
|
|
}
|
|
case let exitCode as ExitCode:
|
|
self = .other(message: "", exitCode: exitCode)
|
|
default:
|
|
self = .other(message: error.describe(), exitCode: .failure)
|
|
}
|
|
} else if let parserError = parserError {
|
|
let usage: String = {
|
|
guard case ParserError.noArguments = parserError else { return usage }
|
|
return "\n"
|
|
+ HelpGenerator(commandStack: [type.asCommand], visibility: .default)
|
|
.rendered(screenWidth: columns)
|
|
}()
|
|
// swift-format-ignore: NeverForceUnwrap
|
|
// FIXME: refactor to avoid force unwrap
|
|
// this requires a lot of non-local reasoning to understand why the force
|
|
// unwrap is safe
|
|
let argumentSet = ArgumentSet(
|
|
commandStack.last!, visibility: .default, parent: nil)
|
|
let message = argumentSet.errorDescription(error: parserError) ?? ""
|
|
let helpAbstract = argumentSet.helpDescription(error: parserError) ?? ""
|
|
self = .validation(message: message, usage: usage, help: helpAbstract)
|
|
} else {
|
|
self = .other(message: String(describing: error), exitCode: .failure)
|
|
}
|
|
}
|
|
|
|
var message: String {
|
|
switch self {
|
|
case .help(let text):
|
|
return text
|
|
case .validation(let message, usage: _, help: _):
|
|
return message
|
|
case .other(let message, _):
|
|
return message
|
|
}
|
|
}
|
|
|
|
func fullText(for args: ParsableArguments.Type) -> String {
|
|
switch self {
|
|
case .help(let text):
|
|
return text
|
|
case .validation(let message, let usage, let help):
|
|
let helpMessage = help.isEmpty ? "" : "Help: \(help)\n"
|
|
let errorMessage =
|
|
message.isEmpty ? "" : "\(args._errorPrefix)\(message)\n"
|
|
return errorMessage + helpMessage + usage
|
|
case .other(let message, _):
|
|
return message.isEmpty ? "" : "\(args._errorPrefix)\(message)"
|
|
}
|
|
}
|
|
|
|
var shouldExitCleanly: Bool {
|
|
switch self {
|
|
case .help: return true
|
|
case .validation, .other: return false
|
|
}
|
|
}
|
|
|
|
var exitCode: ExitCode {
|
|
switch self {
|
|
case .help: return ExitCode.success
|
|
case .validation: return ExitCode.validationFailure
|
|
case .other(_, let exitCode): return exitCode
|
|
}
|
|
}
|
|
}
|