DumpHelpGenerator.swift 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  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. #if compiler(>=6.0)
  12. // import ArgumentParserToolInfo
  13. #else
  14. // import ArgumentParserToolInfo
  15. #endif
  16. internal struct DumpHelpGenerator {
  17. private var toolInfo: ToolInfoV0
  18. init(_ type: ParsableArguments.Type) {
  19. self.init(commandStack: [type.asCommand])
  20. }
  21. init(commandStack: [ParsableCommand.Type]) {
  22. self.toolInfo = ToolInfoV0(commandStack: commandStack)
  23. }
  24. func rendered() -> String {
  25. return ArgParserJSONEncoder.encode(self.toolInfo)
  26. }
  27. }
  28. extension BidirectionalCollection where Element == ParsableCommand.Type {
  29. /// Returns the ArgumentSet for the last command in this stack, including
  30. /// help and version flags, when appropriate.
  31. fileprivate func allArguments() -> ArgumentSet {
  32. guard
  33. var arguments = self.last.map({
  34. ArgumentSet($0, visibility: .private, parent: nil)
  35. })
  36. else { return ArgumentSet() }
  37. self.versionArgumentDefinition().map { arguments.append($0) }
  38. self.helpArgumentDefinition().map { arguments.append($0) }
  39. return arguments
  40. }
  41. }
  42. extension ToolInfoV0 {
  43. init(commandStack: [ParsableCommand.Type]) {
  44. self.init(command: CommandInfoV0(commandStack: commandStack))
  45. // FIXME: This is a hack to inject the help command into the tool info
  46. // instead we should try to lift this into the parseable command tree
  47. self.command.subcommands =
  48. (self.command.subcommands ?? []) + [
  49. CommandInfoV0(commandStack: commandStack + [HelpCommand.self])
  50. ]
  51. }
  52. }
  53. extension CommandInfoV0 {
  54. fileprivate init(commandStack: [ParsableCommand.Type]) {
  55. guard let command = commandStack.last else {
  56. preconditionFailure("commandStack must not be empty")
  57. }
  58. let parents = commandStack.dropLast()
  59. var superCommands = parents.map { $0._commandName }
  60. if let superName = parents.first?.configuration._superCommandName {
  61. superCommands.insert(superName, at: 0)
  62. }
  63. let defaultSubcommand = command.configuration.defaultSubcommand?
  64. .configuration.commandName
  65. let subcommands = command.configuration.subcommands
  66. .map { subcommand -> CommandInfoV0 in
  67. var commandStack = commandStack
  68. commandStack.append(subcommand)
  69. return CommandInfoV0(commandStack: commandStack)
  70. }
  71. let arguments =
  72. commandStack
  73. .allArguments()
  74. .compactMap(ArgumentInfoV0.init)
  75. self = CommandInfoV0(
  76. superCommands: superCommands,
  77. shouldDisplay: command.configuration.shouldDisplay,
  78. commandName: command._commandName,
  79. aliases: command.configuration.aliases,
  80. abstract: command.configuration.abstract,
  81. discussion: command.configuration.discussion,
  82. defaultSubcommand: defaultSubcommand,
  83. subcommands: subcommands,
  84. arguments: arguments)
  85. }
  86. }
  87. extension ArgumentInfoV0 {
  88. fileprivate init?(argument: ArgumentDefinition) {
  89. guard let kind = ArgumentInfoV0.KindV0(argument: argument) else {
  90. return nil
  91. }
  92. let discussion: String?
  93. let allValueDescriptions: [String: String]?
  94. switch argument.help.discussion {
  95. case .none:
  96. discussion = nil
  97. allValueDescriptions = nil
  98. case .staticText(let _discussion):
  99. discussion = _discussion
  100. allValueDescriptions = nil
  101. case .enumerated(let _discussion, let options):
  102. discussion = _discussion
  103. allValueDescriptions = options.allValueDescriptions
  104. }
  105. self.init(
  106. kind: kind,
  107. shouldDisplay: argument.help.visibility.base == .default,
  108. sectionTitle: argument.help.parentTitle.nonEmpty,
  109. isOptional: argument.help.options.contains(.isOptional),
  110. isRepeating: argument.help.options.contains(.isRepeating),
  111. parsingStrategy: ArgumentInfoV0.ParsingStrategyV0(argument: argument),
  112. names: argument.names.map(ArgumentInfoV0.NameInfoV0.init),
  113. preferredName: argument.names.preferredName.map(
  114. ArgumentInfoV0.NameInfoV0.init),
  115. valueName: argument.valueName,
  116. defaultValue: argument.help.defaultValue,
  117. allValueStrings: argument.help.allValueStrings,
  118. allValueDescriptions: allValueDescriptions,
  119. completionKind: ArgumentInfoV0.CompletionKindV0(
  120. completion: argument.completion),
  121. abstract: argument.help.abstract,
  122. discussion: discussion)
  123. }
  124. }
  125. extension ArgumentInfoV0.KindV0 {
  126. fileprivate init?(argument: ArgumentDefinition) {
  127. switch argument.kind {
  128. case .named:
  129. switch argument.update {
  130. case .nullary:
  131. self = .flag
  132. case .unary:
  133. self = .option
  134. }
  135. case .positional:
  136. self = .positional
  137. case .default:
  138. return nil
  139. }
  140. }
  141. }
  142. extension ArgumentInfoV0.ParsingStrategyV0 {
  143. fileprivate init(argument: ArgumentDefinition) {
  144. switch argument.parsingStrategy {
  145. case .`default`:
  146. self = .default
  147. case .scanningForValue:
  148. self = .scanningForValue
  149. case .unconditional:
  150. self = .unconditional
  151. case .upToNextOption:
  152. self = .upToNextOption
  153. case .allRemainingInput:
  154. self = .allRemainingInput
  155. case .postTerminator:
  156. self = .postTerminator
  157. case .allUnrecognized:
  158. self = .allUnrecognized
  159. }
  160. }
  161. }
  162. extension ArgumentInfoV0.NameInfoV0 {
  163. fileprivate init(name: Name) {
  164. switch name {
  165. case .long(let n):
  166. self.init(kind: .long, name: n)
  167. case .short(let n, _):
  168. self.init(kind: .short, name: String(n))
  169. case .longWithSingleDash(let n):
  170. self.init(kind: .longWithSingleDash, name: n)
  171. }
  172. }
  173. }
  174. extension ArgumentInfoV0.CompletionKindV0 {
  175. fileprivate init?(completion: CompletionKind) {
  176. switch completion.kind {
  177. case .`default`:
  178. return nil
  179. case .list(let values):
  180. self = .list(values: values)
  181. case .file(let extensions):
  182. self = .file(extensions: extensions)
  183. case .directory:
  184. self = .directory
  185. case .shellCommand(let command):
  186. self = .shellCommand(command: command)
  187. case .custom(_):
  188. self = .custom
  189. case .customAsync(_):
  190. self = .customAsync
  191. case .customDeprecated(_):
  192. self = .customDeprecated
  193. }
  194. }
  195. }