ArgumentSet.swift 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  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. /// A nested tree of argument definitions.
  12. ///
  13. /// The main reason for having a nested representation is to build help output.
  14. /// For output like:
  15. ///
  16. /// Usage: mytool [-v | -f] <input> <output>
  17. ///
  18. /// The `-v | -f` part is one *set* that’s optional, `<input> <output>` is
  19. /// another. Both of these can then be combined into a third set.
  20. struct ArgumentSet {
  21. var content: [ArgumentDefinition] = []
  22. var namePositions: [Name: Int] = [:]
  23. init<S: Sequence>(_ arguments: S) where S.Element == ArgumentDefinition {
  24. self.content = Array(arguments)
  25. self.namePositions = Dictionary(
  26. content.enumerated().flatMap { i, arg in
  27. arg.names.map { ($0.nameToMatch, i) }
  28. },
  29. uniquingKeysWith: { first, _ in first })
  30. }
  31. init() {}
  32. init(_ arg: ArgumentDefinition) {
  33. self.init([arg])
  34. }
  35. init(sets: [ArgumentSet]) {
  36. self.init(sets.joined())
  37. }
  38. mutating func append(_ arg: ArgumentDefinition) {
  39. let newPosition = content.count
  40. content.append(arg)
  41. for name in arg.names where namePositions[name.nameToMatch] == nil {
  42. namePositions[name.nameToMatch] = newPosition
  43. }
  44. }
  45. }
  46. extension ArgumentSet: CustomDebugStringConvertible {
  47. var debugDescription: String {
  48. content
  49. .map { $0.debugDescription }
  50. .joined(separator: " / ")
  51. }
  52. }
  53. extension ArgumentSet: RandomAccessCollection {
  54. var startIndex: Int { content.startIndex }
  55. var endIndex: Int { content.endIndex }
  56. subscript(position: Int) -> ArgumentDefinition {
  57. content[position]
  58. }
  59. }
  60. // MARK: Flag
  61. extension ArgumentSet {
  62. /// Creates an argument set for a single Boolean flag.
  63. static func flag(
  64. key: InputKey, name: NameSpecification, default initialValue: Bool?,
  65. help: ArgumentHelp?
  66. ) -> ArgumentSet {
  67. // The flag is required if initialValue is `nil`, otherwise it's optional
  68. let helpOptions: ArgumentDefinition.Help.Options =
  69. initialValue != nil ? .isOptional : []
  70. let defaultValueString = initialValue == true ? "true" : nil
  71. let help = ArgumentDefinition.Help(
  72. allValueStrings: [],
  73. options: helpOptions,
  74. help: help,
  75. defaultValue: defaultValueString,
  76. key: key,
  77. isComposite: false)
  78. let arg = ArgumentDefinition(
  79. kind: .name(key: key, specification: name), help: help,
  80. completion: .default,
  81. update: .nullary({ (origin, name, values) in
  82. values.set(true, forKey: key, inputOrigin: origin)
  83. }),
  84. initial: { origin, values in
  85. if let initialValue = initialValue {
  86. values.set(initialValue, forKey: key, inputOrigin: origin)
  87. }
  88. })
  89. return ArgumentSet(arg)
  90. }
  91. static func updateFlag<Value: Equatable>(
  92. key: InputKey, value: Value, origin: InputOrigin,
  93. values: inout ParsedValues, exclusivity: FlagExclusivity
  94. ) throws {
  95. let hasUpdated: Bool
  96. if let previous = values.element(forKey: key) {
  97. hasUpdated = !previous.inputOrigin.elements.isEmpty
  98. } else {
  99. hasUpdated = false
  100. }
  101. switch (hasUpdated, exclusivity.base) {
  102. case (true, .exclusive):
  103. // This value has already been set.
  104. if let previous = values.element(forKey: key) {
  105. if (previous.value as? Value) == value {
  106. // setting the value again will consume the argument
  107. values.set(value, forKey: key, inputOrigin: origin)
  108. } else {
  109. throw ParserError.duplicateExclusiveValues(
  110. previous: previous.inputOrigin, duplicate: origin,
  111. originalInput: values.originalInput)
  112. }
  113. }
  114. case (true, .chooseFirst):
  115. values.update(
  116. forKey: key, inputOrigin: origin, initial: value, closure: { _ in })
  117. case (false, _), (_, .chooseLast):
  118. values.set(value, forKey: key, inputOrigin: origin)
  119. }
  120. }
  121. /// Creates an argument set for a pair of inverted Boolean flags.
  122. static func flag(
  123. key: InputKey,
  124. name: NameSpecification,
  125. default initialValue: Bool?,
  126. required: Bool,
  127. inversion: FlagInversion,
  128. exclusivity: FlagExclusivity,
  129. help: ArgumentHelp?
  130. ) -> ArgumentSet {
  131. let helpOptions: ArgumentDefinition.Help.Options =
  132. required ? [] : .isOptional
  133. let (enableNames, disableNames) = inversion.enableDisableNamePair(
  134. for: key, name: name)
  135. let initialValueNames = initialValue.map {
  136. $0 ? enableNames : disableNames
  137. }
  138. let enableHelp = ArgumentDefinition.Help(
  139. allValueStrings: [], options: helpOptions, help: help,
  140. defaultValue: initialValueNames?.first?.synopsisString, key: key,
  141. isComposite: true)
  142. let disableHelp = ArgumentDefinition.Help(
  143. allValueStrings: [], options: [.isOptional], help: help,
  144. defaultValue: nil, key: key, isComposite: false)
  145. let enableArg = ArgumentDefinition(
  146. kind: .named(enableNames), help: enableHelp, completion: .default,
  147. update: .nullary({ (origin, name, values) in
  148. try ArgumentSet.updateFlag(
  149. key: key, value: true, origin: origin, values: &values,
  150. exclusivity: exclusivity)
  151. }),
  152. initial: { origin, values in
  153. if let initialValue = initialValue {
  154. values.set(initialValue, forKey: key, inputOrigin: origin)
  155. }
  156. })
  157. let disableArg = ArgumentDefinition(
  158. kind: .named(disableNames), help: disableHelp, completion: .default,
  159. update: .nullary({ (origin, name, values) in
  160. try ArgumentSet.updateFlag(
  161. key: key, value: false, origin: origin, values: &values,
  162. exclusivity: exclusivity)
  163. }), initial: { _, _ in })
  164. return ArgumentSet([enableArg, disableArg])
  165. }
  166. /// Creates an argument set for an incrementing integer flag.
  167. static func counter(
  168. key: InputKey, name: NameSpecification, help: ArgumentHelp?
  169. ) -> ArgumentSet {
  170. let help = ArgumentDefinition.Help(
  171. allValueStrings: [], options: [.isOptional, .isRepeating], help: help,
  172. defaultValue: nil, key: key, isComposite: false)
  173. let arg = ArgumentDefinition(
  174. kind: .name(key: key, specification: name), help: help,
  175. completion: .default,
  176. update: .nullary({ (origin, name, values) in
  177. guard let a = values.element(forKey: key)?.value, let b = a as? Int
  178. else {
  179. throw ParserError.invalidState
  180. }
  181. values.set(b + 1, forKey: key, inputOrigin: origin)
  182. }),
  183. initial: { origin, values in
  184. values.set(0, forKey: key, inputOrigin: origin)
  185. })
  186. return ArgumentSet(arg)
  187. }
  188. }
  189. extension ArgumentSet {
  190. /// Fills the given `ParsedValues` instance with initial values from this
  191. /// argument set.
  192. func setInitialValues(into parsed: inout ParsedValues) throws {
  193. for arg in self {
  194. try arg.initial(InputOrigin(), &parsed)
  195. }
  196. }
  197. }
  198. extension ArgumentSet {
  199. /// Find an `ArgumentDefinition` that matches the given `ParsedArgument`.
  200. ///
  201. /// As we iterate over the values from the command line, we try to find a
  202. /// definition that matches the particular element.
  203. ///
  204. /// - Parameter parsed: The argument from the command line
  205. ///
  206. /// - Returns: The matching definition.
  207. func first(
  208. matching parsed: ParsedArgument
  209. ) -> ArgumentDefinition? {
  210. namePositions[parsed.name].map { content[$0] }
  211. }
  212. func firstPositional(
  213. withKey key: InputKey
  214. ) -> ArgumentDefinition? {
  215. first(where: { $0.help.keys.contains(key) })
  216. }
  217. func positional(
  218. at index: Int
  219. ) -> ArgumentDefinition? {
  220. let positionals = content.filter { $0.isPositional }
  221. guard positionals.count > index else { return nil }
  222. return positionals[index]
  223. }
  224. }
  225. /// A parser for a given input and set of arguments defined by the given
  226. /// command.
  227. ///
  228. /// This parser will consume only the arguments that it understands. If any
  229. /// arguments are declared to capture all remaining input, or a subcommand
  230. /// is configured as such, parsing stops on the first positional argument or
  231. /// unrecognized dash-prefixed argument.
  232. struct LenientParser {
  233. var command: ParsableCommand.Type
  234. var argumentSet: ArgumentSet
  235. var inputArguments: SplitArguments
  236. init(_ command: ParsableCommand.Type, _ split: SplitArguments) {
  237. self.command = command
  238. self.argumentSet = ArgumentSet(command, visibility: .private, parent: nil)
  239. self.inputArguments = split
  240. }
  241. var defaultCapturesForPassthrough: Bool {
  242. command.defaultIncludesPassthroughArguments
  243. }
  244. var subcommands: [ParsableCommand.Type] {
  245. command.configuration.subcommands
  246. }
  247. func errorForMissingValue(
  248. _ originElement: InputOrigin.Element,
  249. _ parsed: ParsedArgument
  250. ) -> ParserError {
  251. if case .argumentIndex(let index) = originElement,
  252. index.subIndex != .complete,
  253. let originalInput =
  254. inputArguments
  255. .originalInput(at: .argumentIndex(index.completeIndex))
  256. {
  257. let completeName = Name(originalInput[...])
  258. return ParserError.missingValueOrUnknownCompositeOption(
  259. InputOrigin(element: originElement), parsed.name, completeName)
  260. } else {
  261. return ParserError.missingValueForOption(
  262. InputOrigin(element: originElement), parsed.name)
  263. }
  264. }
  265. mutating func parseValue(
  266. _ argument: ArgumentDefinition,
  267. _ parsed: ParsedArgument,
  268. _ originElement: InputOrigin.Element,
  269. _ update: ArgumentDefinition.Update.Unary,
  270. _ result: inout ParsedValues,
  271. _ usedOrigins: inout InputOrigin
  272. ) throws {
  273. let origin = InputOrigin(elements: [originElement])
  274. switch argument.parsingStrategy {
  275. case .default:
  276. // We need a value for this option.
  277. if let value = parsed.value {
  278. // This was `--foo=bar` style:
  279. try update(origin, parsed.name, value, &result)
  280. usedOrigins.formUnion(origin)
  281. } else if argument.allowsJoinedValue,
  282. let (origin2, value) = inputArguments.extractJoinedElement(
  283. at: originElement)
  284. {
  285. // Found a joined argument
  286. let origins = origin.inserting(origin2)
  287. try update(origins, parsed.name, String(value), &result)
  288. usedOrigins.formUnion(origins)
  289. } else if let (origin2, value) = inputArguments.popNextElementIfValue(
  290. after: originElement)
  291. {
  292. // Use `popNextElementIfValue(after:)` to handle cases where short option
  293. // labels are combined
  294. let origins = origin.inserting(origin2)
  295. try update(origins, parsed.name, value, &result)
  296. usedOrigins.formUnion(origins)
  297. } else {
  298. throw errorForMissingValue(originElement, parsed)
  299. }
  300. case .scanningForValue:
  301. // We need a value for this option.
  302. if let value = parsed.value {
  303. // This was `--foo=bar` style:
  304. try update(origin, parsed.name, value, &result)
  305. usedOrigins.formUnion(origin)
  306. } else if argument.allowsJoinedValue,
  307. let (origin2, value) = inputArguments.extractJoinedElement(
  308. at: originElement)
  309. {
  310. // Found a joined argument
  311. let origins = origin.inserting(origin2)
  312. try update(origins, parsed.name, String(value), &result)
  313. usedOrigins.formUnion(origins)
  314. } else if let (origin2, value) = inputArguments.popNextValue(
  315. after: originElement)
  316. {
  317. // Use `popNext(after:)` to handle cases where short option
  318. // labels are combined
  319. let origins = origin.inserting(origin2)
  320. try update(origins, parsed.name, value, &result)
  321. usedOrigins.formUnion(origins)
  322. } else {
  323. throw errorForMissingValue(originElement, parsed)
  324. }
  325. case .unconditional:
  326. // Use an attached value if it exists...
  327. if let value = parsed.value {
  328. // This was `--foo=bar` style:
  329. try update(origin, parsed.name, value, &result)
  330. usedOrigins.formUnion(origin)
  331. } else if argument.allowsJoinedValue,
  332. let (origin2, value) = inputArguments.extractJoinedElement(
  333. at: originElement)
  334. {
  335. // Found a joined argument
  336. let origins = origin.inserting(origin2)
  337. try update(origins, parsed.name, String(value), &result)
  338. usedOrigins.formUnion(origins)
  339. } else {
  340. guard
  341. let (origin2, value) = inputArguments.popNextElementAsValue(
  342. after: originElement)
  343. else {
  344. throw errorForMissingValue(originElement, parsed)
  345. }
  346. let origins = origin.inserting(origin2)
  347. try update(origins, parsed.name, value, &result)
  348. usedOrigins.formUnion(origins)
  349. }
  350. case .allRemainingInput:
  351. // Reset initial value with the found input origins:
  352. try argument.initial(origin, &result)
  353. // Use an attached value if it exists...
  354. if let value = parsed.value {
  355. // This was `--foo=bar` style:
  356. try update(origin, parsed.name, value, &result)
  357. usedOrigins.formUnion(origin)
  358. } else if argument.allowsJoinedValue,
  359. let (origin2, value) = inputArguments.extractJoinedElement(
  360. at: originElement)
  361. {
  362. // Found a joined argument
  363. let origins = origin.inserting(origin2)
  364. try update(origins, parsed.name, String(value), &result)
  365. usedOrigins.formUnion(origins)
  366. inputArguments.removeAll(in: usedOrigins)
  367. }
  368. // ...and then consume the rest of the arguments
  369. while let (origin2, value) = inputArguments.popNextElementAsValue(
  370. after: originElement)
  371. {
  372. let origins = origin.inserting(origin2)
  373. try update(origins, parsed.name, value, &result)
  374. usedOrigins.formUnion(origins)
  375. }
  376. case .upToNextOption:
  377. // Use an attached value if it exists...
  378. var foundAttachedValue = false
  379. if let value = parsed.value {
  380. // This was `--foo=bar` style:
  381. try update(origin, parsed.name, value, &result)
  382. usedOrigins.formUnion(origin)
  383. foundAttachedValue = true
  384. } else if argument.allowsJoinedValue,
  385. let (origin2, value) = inputArguments.extractJoinedElement(
  386. at: originElement)
  387. {
  388. // Found a joined argument
  389. let origins = origin.inserting(origin2)
  390. try update(origins, parsed.name, String(value), &result)
  391. usedOrigins.formUnion(origins)
  392. inputArguments.removeAll(in: usedOrigins)
  393. foundAttachedValue = true
  394. }
  395. // Clear out the initial origin first, since it can include
  396. // the exploded elements of an options group (see issue #327).
  397. usedOrigins.formUnion(origin)
  398. inputArguments.removeAll(in: origin)
  399. // Fix incorrect error message
  400. // for @Option array without values (see issue #434).
  401. guard let first = inputArguments.elements.first,
  402. first.isValue
  403. else {
  404. // No independent values to be found, which is an error if there was
  405. // no `--foo=bar`-style value already found.
  406. if foundAttachedValue {
  407. break
  408. } else {
  409. throw errorForMissingValue(originElement, parsed)
  410. }
  411. }
  412. // ...and then consume the arguments until hitting an option
  413. while let (origin2, value) = inputArguments.popNextElementIfValue() {
  414. let origins = origin.inserting(origin2)
  415. try update(origins, parsed.name, value, &result)
  416. usedOrigins.formUnion(origins)
  417. }
  418. case .postTerminator, .allUnrecognized:
  419. // These parsing kinds are for arguments only.
  420. throw ParserError.invalidState
  421. }
  422. }
  423. mutating func parsePositionalValues(
  424. from unusedInput: SplitArguments,
  425. into result: inout ParsedValues
  426. ) throws {
  427. var endOfInput = unusedInput.elements.endIndex
  428. // If this argument set includes a definition that should collect all the
  429. // post-terminator inputs, capture them before trying to fill other
  430. // `@Argument` definitions.
  431. if let postTerminatorArg = argumentSet.first(where: { def in
  432. def.isRepeatingPositional && def.parsingStrategy == .postTerminator
  433. }),
  434. case .unary(let update) = postTerminatorArg.update,
  435. let terminatorIndex = unusedInput.elements.firstIndex(
  436. where: \.isTerminator)
  437. {
  438. for input in unusedInput.elements[(terminatorIndex + 1)...] {
  439. // swift-format-ignore: NeverForceUnwrap
  440. // Everything post-terminator is a value, force-unwrapping here is safe:
  441. let value = input.value.valueString!
  442. try update([.argumentIndex(input.index)], nil, value, &result)
  443. }
  444. endOfInput = terminatorIndex
  445. }
  446. // Create a stack out of the remaining unused inputs that aren't "partial"
  447. // arguments (i.e. the individual components of a `-vix` grouped short
  448. // option input).
  449. var argumentStack = unusedInput.elements[..<endOfInput].filter {
  450. $0.index.subIndex == .complete
  451. }[...]
  452. guard !argumentStack.isEmpty else { return }
  453. /// Pops arguments off the stack until the next valid value.
  454. ///
  455. /// Skips over dash-prefixed inputs unless `unconditional` is `true`.
  456. func next(unconditional: Bool) -> SplitArguments.Element? {
  457. while let arg = argumentStack.popFirst() {
  458. if arg.isValue || unconditional {
  459. return arg
  460. }
  461. }
  462. return nil
  463. }
  464. // For all positional arguments, consume one or more inputs.
  465. var usedOrigins = InputOrigin()
  466. ArgumentLoop: for argumentDefinition in argumentSet {
  467. guard case .positional = argumentDefinition.kind else { continue }
  468. switch argumentDefinition.parsingStrategy {
  469. case .default, .allRemainingInput:
  470. break
  471. default:
  472. continue ArgumentLoop
  473. }
  474. guard case .unary(let update) = argumentDefinition.update else {
  475. preconditionFailure("Shouldn't see a nullary positional argument.")
  476. }
  477. let allowOptionsAsInput =
  478. argumentDefinition.parsingStrategy == .allRemainingInput
  479. repeat {
  480. guard let arg = next(unconditional: allowOptionsAsInput) else {
  481. break ArgumentLoop
  482. }
  483. let origin: InputOrigin.Element = .argumentIndex(arg.index)
  484. // swift-format-ignore: NeverForceUnwrap
  485. // FIXME: I dont actually know why this is safe
  486. let value = unusedInput.originalInput(at: origin)!
  487. try update([origin], nil, value, &result)
  488. usedOrigins.insert(origin)
  489. } while argumentDefinition.isRepeatingPositional
  490. }
  491. // If there's an `.allUnrecognized` argument array, collect leftover args.
  492. if let allUnrecognizedArg = argumentSet.first(where: { def in
  493. def.isRepeatingPositional && def.parsingStrategy == .allUnrecognized
  494. }),
  495. case .unary(let update) = allUnrecognizedArg.update
  496. {
  497. result.capturedUnrecognizedArguments = SplitArguments(
  498. _elements: Array(argumentStack),
  499. originalInput: [])
  500. while let arg = argumentStack.popFirst() {
  501. let origin: InputOrigin.Element = .argumentIndex(arg.index)
  502. // swift-format-ignore: NeverForceUnwrap
  503. // FIXME: I dont actually know why this is safe
  504. let value = unusedInput.originalInput(at: origin)!
  505. try update([origin], nil, value, &result)
  506. }
  507. }
  508. }
  509. mutating func parse() throws -> ParsedValues {
  510. let originalInput = inputArguments
  511. defer { inputArguments = originalInput }
  512. // If this argument set includes a positional argument that unconditionally
  513. // captures all remaining input, we use a different behavior, where we
  514. // shortcut out at the first sign of a positional argument or unrecognized
  515. // option/flag label.
  516. let capturesForPassthrough =
  517. defaultCapturesForPassthrough
  518. || argumentSet.contains(where: { arg in
  519. arg.isRepeatingPositional && arg.parsingStrategy == .allRemainingInput
  520. })
  521. var result = ParsedValues(
  522. elements: [:], originalInput: inputArguments.originalInput)
  523. var allUsedOrigins = InputOrigin()
  524. try argumentSet.setInitialValues(into: &result)
  525. // Loop over all arguments:
  526. ArgumentLoop: while let (origin, next) = inputArguments.popNext() {
  527. var usedOrigins = InputOrigin()
  528. defer {
  529. inputArguments.removeAll(in: usedOrigins)
  530. allUsedOrigins.formUnion(usedOrigins)
  531. }
  532. switch next.value {
  533. case .value(let argument):
  534. // Special handling for matching subcommand names. We generally want
  535. // parsing to skip over unrecognized input, but if the current
  536. // command or the matched subcommand captures all remaining input,
  537. // then we want to break out of parsing at this point.
  538. let matchedSubcommand = subcommands.first(where: {
  539. $0._commandName == argument
  540. || $0.configuration.aliases.contains(argument)
  541. })
  542. if let matchedSubcommand {
  543. if !matchedSubcommand.includesPassthroughArguments
  544. && defaultCapturesForPassthrough
  545. {
  546. continue ArgumentLoop
  547. } else if matchedSubcommand.includesPassthroughArguments {
  548. break ArgumentLoop
  549. }
  550. }
  551. // If we're capturing all, the first positional value represents the
  552. // start of positional input.
  553. if capturesForPassthrough { break ArgumentLoop }
  554. // We'll parse positional values later.
  555. break
  556. case .option(let parsed):
  557. // Look for an argument that matches this `--option` or `-o`-style
  558. // input. If we can't find one, just move on to the next input. We
  559. // defer catching leftover arguments until we've fully extracted all
  560. // the information for the selected command.
  561. guard let argument = argumentSet.first(matching: parsed) else {
  562. // If we're capturing all, an unrecognized option/flag is the start
  563. // of positional input. However, the first time we see an option
  564. // pack (like `-fi`) it looks like a long name with a single-dash
  565. // prefix, which may not match an argument even if its subcomponents
  566. // will match.
  567. if capturesForPassthrough && parsed.subarguments.isEmpty {
  568. break ArgumentLoop
  569. }
  570. // Otherwise, continue parsing. This option/flag may get picked up
  571. // by a child command.
  572. continue
  573. }
  574. switch argument.update {
  575. case .nullary(let update):
  576. // We don’t expect a value for this option.
  577. if let value = parsed.value {
  578. throw ParserError.unexpectedValueForOption(
  579. origin, parsed.name, value)
  580. }
  581. _ = try update([origin], parsed.name, &result)
  582. usedOrigins.insert(origin)
  583. case .unary(let update):
  584. try parseValue(
  585. argument, parsed, origin, update, &result, &usedOrigins)
  586. }
  587. case .terminator:
  588. // Ignore the terminator, it might get picked up as a positional value later.
  589. break
  590. }
  591. }
  592. // We have parsed all non-positional values at this point.
  593. // Next: parse / consume the positional values.
  594. var unusedArguments = originalInput
  595. unusedArguments.removeAll(in: allUsedOrigins)
  596. try parsePositionalValues(from: unusedArguments, into: &result)
  597. return result
  598. }
  599. }