Files

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
}
}
}