ArgumentDefinition.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  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. struct ArgumentDefinition {
  12. /// A closure that modifies a `ParsedValues` instance to include this
  13. /// argument's value.
  14. enum Update {
  15. typealias Nullary = (InputOrigin, Name?, inout ParsedValues) throws -> Void
  16. typealias Unary = (InputOrigin, Name?, String, inout ParsedValues) throws ->
  17. Void
  18. /// An argument that gets its value solely from its presence.
  19. case nullary(Nullary)
  20. /// An argument that takes a string as its value.
  21. case unary(Unary)
  22. }
  23. typealias Initial = (InputOrigin, inout ParsedValues) throws -> Void
  24. enum Kind {
  25. /// An option or flag, with a name and an optional value.
  26. case named([Name])
  27. /// A positional argument.
  28. case positional
  29. /// A pseudo-argument that takes its value from a property's default value
  30. /// instead of from command-line arguments.
  31. case `default`
  32. }
  33. struct Help {
  34. struct Options: OptionSet {
  35. var rawValue: UInt
  36. static let isOptional = Options(rawValue: 1 << 0)
  37. static let isRepeating = Options(rawValue: 1 << 1)
  38. }
  39. var options: Options
  40. var defaultValue: String?
  41. var keys: [InputKey]
  42. var allValueStrings: [String]
  43. var isComposite: Bool
  44. var abstract: String
  45. var discussion: ArgumentDiscussion?
  46. var valueName: String
  47. var visibility: ArgumentVisibility
  48. var parentTitle: String
  49. init(
  50. allValueStrings: [String],
  51. options: Options,
  52. help: ArgumentHelp?,
  53. defaultValue: String?,
  54. key: InputKey,
  55. isComposite: Bool
  56. ) {
  57. self.options = options
  58. self.defaultValue = defaultValue
  59. self.keys = [key]
  60. self.allValueStrings = allValueStrings
  61. self.isComposite = isComposite
  62. self.abstract = help?.abstract ?? ""
  63. self.discussion = .init(help?.discussion, help?.argumentType)
  64. self.valueName = help?.valueName ?? ""
  65. self.visibility = help?.visibility ?? .default
  66. self.parentTitle = ""
  67. }
  68. }
  69. /// This folds the public `ArrayParsingStrategy` and `SingleValueParsingStrategy`
  70. /// into a single enum.
  71. enum ParsingStrategy {
  72. /// Expect the next `SplitArguments.Element` to be a value and parse it.
  73. /// Will fail if the next input is an option.
  74. case `default`
  75. /// Parse the next `SplitArguments.Element.value`
  76. case scanningForValue
  77. /// Parse the next `SplitArguments.Element` as a value, regardless of its type.
  78. case unconditional
  79. /// Parse multiple `SplitArguments.Element.value` up to the next non-`.value`
  80. case upToNextOption
  81. /// Parse all remaining `SplitArguments.Element` as values, regardless of its type.
  82. case allRemainingInput
  83. /// Collect all the elements after the terminator, preventing them from
  84. /// appearing in any other position.
  85. case postTerminator
  86. /// Collect all unused inputs once recognized arguments/options/flags have
  87. /// been parsed.
  88. case allUnrecognized
  89. }
  90. var kind: Kind
  91. var help: Help
  92. var completion: CompletionKind
  93. var parsingStrategy: ParsingStrategy
  94. var update: Update
  95. var initial: Initial
  96. var names: [Name] {
  97. switch kind {
  98. case .named(let n): return n
  99. case .positional, .default: return []
  100. }
  101. }
  102. var valueName: String {
  103. help.valueName.mapEmpty {
  104. names.preferredName?.valueString
  105. ?? help.keys.first?.name.convertedToSnakeCase(separator: "-")
  106. ?? "value"
  107. }
  108. }
  109. init(
  110. kind: Kind,
  111. help: Help,
  112. completion: CompletionKind,
  113. parsingStrategy: ParsingStrategy = .default,
  114. update: Update,
  115. initial: @escaping Initial = { _, _ in }
  116. ) {
  117. if case (.positional, .nullary) = (kind, update) {
  118. preconditionFailure("Can't create a nullary positional argument.")
  119. }
  120. self.kind = kind
  121. self.help = help
  122. self.completion = completion
  123. self.parsingStrategy = parsingStrategy
  124. self.update = update
  125. self.initial = initial
  126. }
  127. }
  128. extension ArgumentDefinition: CustomDebugStringConvertible {
  129. var debugDescription: String {
  130. switch (kind, update) {
  131. case (.named(let names), .nullary):
  132. return
  133. names
  134. .map { $0.synopsisString }
  135. .joined(separator: ",")
  136. case (.named(let names), .unary):
  137. return
  138. names
  139. .map { $0.synopsisString }
  140. .joined(separator: ",")
  141. + " <\(valueName)>"
  142. case (.positional, _):
  143. return "<\(valueName)>"
  144. case (.default, _):
  145. return ""
  146. }
  147. }
  148. }
  149. extension ArgumentDefinition {
  150. var optional: ArgumentDefinition {
  151. var result = self
  152. result.help.options.insert(.isOptional)
  153. return result
  154. }
  155. var nonOptional: ArgumentDefinition {
  156. var result = self
  157. result.help.options.remove(.isOptional)
  158. return result
  159. }
  160. }
  161. extension ArgumentDefinition {
  162. var isPositional: Bool {
  163. if case .positional = kind {
  164. return true
  165. }
  166. return false
  167. }
  168. var isRepeatingPositional: Bool {
  169. isPositional && help.options.contains(.isRepeating)
  170. }
  171. var isNullary: Bool {
  172. if case .nullary = update {
  173. return true
  174. } else {
  175. return false
  176. }
  177. }
  178. var allowsJoinedValue: Bool {
  179. names.contains(where: { $0.allowsJoined })
  180. }
  181. }
  182. extension ArgumentDefinition.Kind {
  183. static func name(key: InputKey, specification: NameSpecification)
  184. -> ArgumentDefinition.Kind
  185. {
  186. let names = specification.makeNames(key)
  187. return ArgumentDefinition.Kind.named(names)
  188. }
  189. }
  190. // MARK: - Common @Argument, @Option, Unparsed Initializer Path
  191. extension ArgumentDefinition {
  192. // MARK: Unparsed Keys
  193. /// Creates an argument definition for a property that isn't parsed from the
  194. /// command line.
  195. ///
  196. /// This initializer is used for any property defined on a `ParsableArguments`
  197. /// type that isn't decorated with one of ArgumentParser's property wrappers.
  198. init(unparsedKey: String, default defaultValue: Any?, parent: InputKey?) {
  199. self.init(
  200. container: Bare<Any>.self,
  201. key: InputKey(name: unparsedKey, parent: parent),
  202. kind: .default,
  203. allValueStrings: [],
  204. help: .private,
  205. defaultValueDescription: nil,
  206. parsingStrategy: .default,
  207. parser: { (key, origin, name, valueString) in
  208. throw ParserError.unableToParseValue(
  209. origin, name, valueString, forKey: key, originalError: nil)
  210. },
  211. initial: defaultValue,
  212. completion: nil)
  213. }
  214. init<Container>(
  215. container: Container.Type,
  216. key: InputKey,
  217. kind: ArgumentDefinition.Kind,
  218. help: ArgumentHelp?,
  219. parsingStrategy: ParsingStrategy,
  220. initial: Container.Initial?,
  221. completion: CompletionKind?
  222. ) where Container: ArgumentDefinitionContainerExpressibleByArgument {
  223. self.init(
  224. container: Container.self,
  225. key: key,
  226. kind: kind,
  227. allValueStrings: Container.Contained.allValueStrings,
  228. help: help,
  229. defaultValueDescription: Container.defaultValueDescription(initial),
  230. parsingStrategy: parsingStrategy,
  231. parser: { (key, origin, name, valueString) -> Container.Contained in
  232. guard let value = Container.Contained(argument: valueString) else {
  233. throw ParserError.unableToParseValue(
  234. origin, name, valueString, forKey: key, originalError: nil)
  235. }
  236. return value
  237. },
  238. initial: initial,
  239. completion: completion ?? Container.Contained.defaultCompletionKind)
  240. }
  241. init<Container>(
  242. container: Container.Type,
  243. key: InputKey,
  244. kind: ArgumentDefinition.Kind,
  245. help: ArgumentHelp?,
  246. parsingStrategy: ParsingStrategy,
  247. transform: @escaping (String) throws -> Container.Contained,
  248. initial: Container.Initial?,
  249. completion: CompletionKind?
  250. ) where Container: ArgumentDefinitionContainer {
  251. self.init(
  252. container: Container.self,
  253. key: key,
  254. kind: kind,
  255. allValueStrings: [],
  256. help: help,
  257. defaultValueDescription: nil,
  258. parsingStrategy: parsingStrategy,
  259. parser: { (key, origin, name, valueString) -> Container.Contained in
  260. do {
  261. return try transform(valueString)
  262. } catch {
  263. throw ParserError.unableToParseValue(
  264. origin, name, valueString, forKey: key, originalError: error)
  265. }
  266. },
  267. initial: initial,
  268. completion: completion)
  269. }
  270. private init<Container>(
  271. container: Container.Type,
  272. key: InputKey,
  273. kind: ArgumentDefinition.Kind,
  274. allValueStrings: [String],
  275. help: ArgumentHelp?,
  276. defaultValueDescription: String?,
  277. parsingStrategy: ParsingStrategy,
  278. parser:
  279. @escaping (InputKey, InputOrigin, Name?, String) throws ->
  280. Container.Contained,
  281. initial: Container.Initial?,
  282. completion: CompletionKind?
  283. ) where Container: ArgumentDefinitionContainer {
  284. self.init(
  285. kind: kind,
  286. help: .init(
  287. allValueStrings: allValueStrings,
  288. options: Container.helpOptions.union(
  289. initial != nil ? [.isOptional] : []),
  290. help: help,
  291. defaultValue: defaultValueDescription,
  292. key: key,
  293. isComposite: false),
  294. completion: completion ?? .default,
  295. parsingStrategy: parsingStrategy,
  296. update: .unary({ (origin, name, valueString, parsedValues) in
  297. let value = try parser(key, origin, name, valueString)
  298. Container.update(
  299. parsedValues: &parsedValues,
  300. value: value,
  301. key: key,
  302. origin: origin)
  303. }),
  304. initial: { origin, values in
  305. let inputOrigin: InputOrigin
  306. switch kind {
  307. case .default:
  308. inputOrigin = InputOrigin(element: .defaultValue)
  309. case .named, .positional:
  310. inputOrigin = origin
  311. }
  312. values.set(initial, forKey: key, inputOrigin: inputOrigin)
  313. })
  314. }
  315. }
  316. // MARK: - Abstraction over T, Option<T>, Array<T>
  317. protocol ArgumentDefinitionContainer {
  318. associatedtype Contained
  319. associatedtype Initial
  320. static var helpOptions: ArgumentDefinition.Help.Options { get }
  321. static func update(
  322. parsedValues: inout ParsedValues,
  323. value: Contained,
  324. key: InputKey,
  325. origin: InputOrigin)
  326. }
  327. protocol ArgumentDefinitionContainerExpressibleByArgument:
  328. ArgumentDefinitionContainer
  329. where Contained: ExpressibleByArgument {
  330. static func defaultValueDescription(_ initial: Initial?) -> String?
  331. }
  332. enum Bare<T> {}
  333. extension Bare: ArgumentDefinitionContainer {
  334. typealias Contained = T
  335. typealias Initial = T
  336. static var helpOptions: ArgumentDefinition.Help.Options { [] }
  337. static func update(
  338. parsedValues: inout ParsedValues,
  339. value: Contained,
  340. key: InputKey,
  341. origin: InputOrigin
  342. ) {
  343. parsedValues.set(value, forKey: key, inputOrigin: origin)
  344. }
  345. }
  346. extension Bare: ArgumentDefinitionContainerExpressibleByArgument
  347. where Contained: ExpressibleByArgument {
  348. static func defaultValueDescription(_ initial: T?) -> String? {
  349. guard let initial = initial else { return nil }
  350. if let initial = initial as? (any CaseIterable & RawRepresentable) {
  351. return String(describing: initial.rawValue)
  352. }
  353. return initial.defaultValueDescription
  354. }
  355. }
  356. extension Optional: ArgumentDefinitionContainer {
  357. typealias Contained = Wrapped
  358. typealias Initial = Wrapped
  359. static var helpOptions: ArgumentDefinition.Help.Options { [.isOptional] }
  360. static func update(
  361. parsedValues: inout ParsedValues,
  362. value: Contained,
  363. key: InputKey,
  364. origin: InputOrigin
  365. ) {
  366. parsedValues.set(value, forKey: key, inputOrigin: origin)
  367. }
  368. }
  369. extension Optional: ArgumentDefinitionContainerExpressibleByArgument
  370. where Contained: ExpressibleByArgument {
  371. static func defaultValueDescription(_ initial: Initial?) -> String? {
  372. guard let initial = initial else { return nil }
  373. if let initial = initial as? (any CaseIterable & RawRepresentable) {
  374. return String(describing: initial.rawValue)
  375. }
  376. return initial.defaultValueDescription
  377. }
  378. }
  379. extension Array: ArgumentDefinitionContainer {
  380. typealias Contained = Element
  381. typealias Initial = [Element]
  382. static var helpOptions: ArgumentDefinition.Help.Options { [.isRepeating] }
  383. static func update(
  384. parsedValues: inout ParsedValues,
  385. value: Element,
  386. key: InputKey,
  387. origin: InputOrigin
  388. ) {
  389. parsedValues.update(
  390. forKey: key,
  391. inputOrigin: origin,
  392. initial: .init(),
  393. closure: { $0.append(value) })
  394. }
  395. }
  396. extension Array: ArgumentDefinitionContainerExpressibleByArgument
  397. where Element: ExpressibleByArgument {
  398. static func defaultValueDescription(_ initial: [Element]?) -> String? {
  399. guard let initial = initial else { return nil }
  400. guard !initial.isEmpty else { return nil }
  401. return initial
  402. .lazy
  403. .map { element in
  404. if let element = element as? (any CaseIterable & RawRepresentable) {
  405. return String(describing: element.rawValue)
  406. }
  407. return element.defaultValueDescription
  408. }
  409. .joined(separator: ", ")
  410. }
  411. }