| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- //===----------------------------------------------------------------------===//
- //
- // 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
- }
- }
- }
|