MessageInfo.swift 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. //===----------------------------------------------------------------------===//
  2. //
  3. // This source file is part of the Swift Argument Parser open source project
  4. //
  5. // Copyright (c) 2020 Apple Inc. and the Swift project authors
  6. // Licensed under Apache License v2.0 with Runtime Library Exception
  7. //
  8. // See https://swift.org/LICENSE.txt for license information
  9. //
  10. //===----------------------------------------------------------------------===//
  11. enum MessageInfo {
  12. case help(text: String)
  13. case validation(message: String, usage: String, help: String)
  14. case other(message: String, exitCode: ExitCode)
  15. init(error: Error, type: ParsableArguments.Type, columns: Int? = nil) {
  16. var commandStack: [ParsableCommand.Type]
  17. var parserError: ParserError? = nil
  18. switch error {
  19. case let e as CommandError:
  20. commandStack = e.commandStack
  21. parserError = e.parserError
  22. // Exit early on built-in requests
  23. switch e.parserError {
  24. case .helpRequested(let visibility):
  25. self = .help(
  26. text: HelpGenerator(
  27. commandStack: e.commandStack, visibility: visibility
  28. ).rendered(screenWidth: columns))
  29. return
  30. case .dumpHelpRequested:
  31. self = .help(
  32. text: DumpHelpGenerator(commandStack: e.commandStack).rendered())
  33. return
  34. case .versionRequested:
  35. let versionString =
  36. commandStack
  37. .map { $0.configuration.version }
  38. .last(where: { !$0.isEmpty })
  39. ?? "Unspecified version"
  40. self = .help(text: versionString)
  41. return
  42. case .completionScriptRequested(let shell):
  43. do {
  44. let completionsGenerator = try CompletionsGenerator(
  45. command: type.asCommand, shellName: shell)
  46. self = .help(text: completionsGenerator.generateCompletionScript())
  47. return
  48. } catch {
  49. self.init(error: error, type: type)
  50. return
  51. }
  52. case .completionScriptCustomResponse(let output):
  53. self = .help(text: output)
  54. return
  55. default:
  56. break
  57. }
  58. case let e as ParserError:
  59. // Send ParserErrors back through the CommandError path
  60. self.init(
  61. error: CommandError(commandStack: [type.asCommand], parserError: e),
  62. type: type, columns: columns)
  63. return
  64. default:
  65. commandStack = [type.asCommand]
  66. // if the error wasn't one of our two Error types, wrap it as a userValidationError
  67. // to be handled appropriately below
  68. parserError = .userValidationError(error)
  69. }
  70. var usage = HelpGenerator(commandStack: commandStack, visibility: .default)
  71. .usageMessage()
  72. let commandNames = commandStack.map { $0._commandName }.joined(
  73. separator: " ")
  74. if let helpName = commandStack.getPrimaryHelpName() {
  75. if !usage.isEmpty {
  76. usage += "\n"
  77. }
  78. usage +=
  79. " See '\(commandNames) \(helpName.synopsisString)' for more information."
  80. }
  81. // Parsing errors and user-thrown validation errors have the usage
  82. // string attached. Other errors just get the error message.
  83. if case .userValidationError(let error) = parserError {
  84. switch error {
  85. case let error as ValidationError:
  86. self = .validation(message: error.message, usage: usage, help: "")
  87. case let error as CleanExit:
  88. switch error.base {
  89. case .helpRequest(let command):
  90. if let command = command {
  91. commandStack = CommandParser(type.asCommand).commandStack(
  92. for: command)
  93. }
  94. self = .help(
  95. text: HelpGenerator(
  96. commandStack: commandStack, visibility: .default
  97. ).rendered(screenWidth: columns))
  98. case .dumpRequest(let command):
  99. if let command = command {
  100. commandStack = CommandParser(type.asCommand).commandStack(
  101. for: command)
  102. }
  103. self = .help(
  104. text: DumpHelpGenerator(commandStack: commandStack).rendered())
  105. case .message(let message):
  106. self = .help(text: message)
  107. }
  108. case let exitCode as ExitCode:
  109. self = .other(message: "", exitCode: exitCode)
  110. default:
  111. self = .other(message: error.describe(), exitCode: .failure)
  112. }
  113. } else if let parserError = parserError {
  114. let usage: String = {
  115. guard case ParserError.noArguments = parserError else { return usage }
  116. return "\n"
  117. + HelpGenerator(commandStack: [type.asCommand], visibility: .default)
  118. .rendered(screenWidth: columns)
  119. }()
  120. // swift-format-ignore: NeverForceUnwrap
  121. // FIXME: refactor to avoid force unwrap
  122. // this requires a lot of non-local reasoning to understand why the force
  123. // unwrap is safe
  124. let argumentSet = ArgumentSet(
  125. commandStack.last!, visibility: .default, parent: nil)
  126. let message = argumentSet.errorDescription(error: parserError) ?? ""
  127. let helpAbstract = argumentSet.helpDescription(error: parserError) ?? ""
  128. self = .validation(message: message, usage: usage, help: helpAbstract)
  129. } else {
  130. self = .other(message: String(describing: error), exitCode: .failure)
  131. }
  132. }
  133. var message: String {
  134. switch self {
  135. case .help(let text):
  136. return text
  137. case .validation(let message, usage: _, help: _):
  138. return message
  139. case .other(let message, _):
  140. return message
  141. }
  142. }
  143. func fullText(for args: ParsableArguments.Type) -> String {
  144. switch self {
  145. case .help(let text):
  146. return text
  147. case .validation(let message, let usage, let help):
  148. let helpMessage = help.isEmpty ? "" : "Help: \(help)\n"
  149. let errorMessage =
  150. message.isEmpty ? "" : "\(args._errorPrefix)\(message)\n"
  151. return errorMessage + helpMessage + usage
  152. case .other(let message, _):
  153. return message.isEmpty ? "" : "\(args._errorPrefix)\(message)"
  154. }
  155. }
  156. var shouldExitCleanly: Bool {
  157. switch self {
  158. case .help: return true
  159. case .validation, .other: return false
  160. }
  161. }
  162. var exitCode: ExitCode {
  163. switch self {
  164. case .help: return ExitCode.success
  165. case .validation: return ExitCode.validationFailure
  166. case .other(_, let exitCode): return exitCode
  167. }
  168. }
  169. }