SplitArguments.swift 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  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 single `-f`, `--foo`, or `--foo=bar`.
  12. ///
  13. /// When parsing, we might see `"--foo"` or `"--foo=bar"`.
  14. enum ParsedArgument: Equatable, CustomStringConvertible {
  15. /// `--foo` or `-f`
  16. case name(Name)
  17. /// `--foo=bar`
  18. case nameWithValue(Name, String)
  19. init<S: StringProtocol>(_ str: S) where S.SubSequence == Substring {
  20. let indexOfEqualSign = str.firstIndex(of: "=") ?? str.endIndex
  21. let (baseName, value) = (
  22. str[..<indexOfEqualSign], str[indexOfEqualSign...].dropFirst()
  23. )
  24. let name = Name(baseName)
  25. self =
  26. value.isEmpty
  27. ? .name(name)
  28. : .nameWithValue(name, String(value))
  29. }
  30. /// An array of short arguments and their indices in the original base
  31. /// name, if this argument could be a combined pack of short arguments.
  32. ///
  33. /// For `subarguments` to be non-empty:
  34. ///
  35. /// 1) This must have a single-dash prefix (not `--foo`)
  36. /// 2) This must not have an attached value (not `-foo=bar`)
  37. var subarguments: [(Int, ParsedArgument)] {
  38. switch self {
  39. case .nameWithValue: return []
  40. case .name(let name):
  41. switch name {
  42. case .longWithSingleDash(let base):
  43. return base.enumerated().map {
  44. ($0, .name(.short($1)))
  45. }
  46. case .long, .short:
  47. return []
  48. }
  49. }
  50. }
  51. var name: Name {
  52. switch self {
  53. case .name(let n): return n
  54. case .nameWithValue(let n, _): return n
  55. }
  56. }
  57. var value: String? {
  58. switch self {
  59. case .name: return nil
  60. case .nameWithValue(_, let v): return v
  61. }
  62. }
  63. var description: String {
  64. switch self {
  65. case .name(let name):
  66. return name.synopsisString
  67. case .nameWithValue(let name, let value):
  68. return "\(name.synopsisString)=\(value)"
  69. }
  70. }
  71. }
  72. /// A collection of parsed command-line arguments.
  73. ///
  74. /// This is a flat list of *values* and *options*. E.g. the
  75. /// arguments `["--foo", "bar"]` would be parsed into
  76. /// `[.option(.name(.long("foo"))), .value("bar")]`.
  77. struct SplitArguments {
  78. struct Element: Equatable {
  79. enum Value: Equatable {
  80. case option(ParsedArgument)
  81. case value(String)
  82. /// The `--` marker
  83. case terminator
  84. var valueString: String? {
  85. switch self {
  86. case .value(let str):
  87. return str
  88. case .option, .terminator:
  89. return nil
  90. }
  91. }
  92. }
  93. var value: Value
  94. var index: Index
  95. static func option(_ arg: ParsedArgument, index: Index) -> Element {
  96. Element(value: .option(arg), index: index)
  97. }
  98. static func value(_ str: String, index: Index) -> Element {
  99. Element(value: .value(str), index: index)
  100. }
  101. static func terminator(index: Index) -> Element {
  102. Element(value: .terminator, index: index)
  103. }
  104. }
  105. /// The position of the original input string for an element.
  106. ///
  107. /// For example, if `originalInput` is `["--foo", "-vh"]`, there are index
  108. /// positions 0 (`--foo`) and 1 (`-vh`).
  109. struct InputIndex: RawRepresentable, Hashable, Comparable {
  110. var rawValue: Int
  111. static func < (lhs: InputIndex, rhs: InputIndex) -> Bool {
  112. lhs.rawValue < rhs.rawValue
  113. }
  114. }
  115. /// The position within an option for an element.
  116. ///
  117. /// Single-dash prefixed options can be treated as a whole option or as a
  118. /// group of individual short options. For example, the input `-vh` is split
  119. /// into three elements, with distinct sub-indexes:
  120. ///
  121. /// - `-vh`: `.complete`
  122. /// - `-v`: `.sub(0)`
  123. /// - `-h`: `.sub(1)`
  124. enum SubIndex: Hashable, Comparable {
  125. case complete
  126. case sub(Int)
  127. static func < (lhs: SubIndex, rhs: SubIndex) -> Bool {
  128. switch (lhs, rhs) {
  129. case (.complete, .sub):
  130. return true
  131. case (.sub(let l), .sub(let r)) where l < r:
  132. return true
  133. default:
  134. return false
  135. }
  136. }
  137. }
  138. /// An index into the original input and the sub-index of an element.
  139. struct Index: Hashable, Comparable {
  140. static func < (lhs: SplitArguments.Index, rhs: SplitArguments.Index) -> Bool
  141. {
  142. if lhs.inputIndex < rhs.inputIndex {
  143. return true
  144. } else if lhs.inputIndex == rhs.inputIndex {
  145. return lhs.subIndex < rhs.subIndex
  146. } else {
  147. return false
  148. }
  149. }
  150. var inputIndex: InputIndex
  151. var subIndex: SubIndex = .complete
  152. var completeIndex: Index {
  153. Index(inputIndex: inputIndex)
  154. }
  155. }
  156. /// The parsed arguments.
  157. var _elements: [Element] = []
  158. var firstUnused: Int = 0
  159. /// The original array of arguments that was used to generate this instance.
  160. var originalInput: [String]
  161. /// The unused arguments represented by this instance.
  162. var elements: ArraySlice<Element> {
  163. _elements[firstUnused...]
  164. }
  165. var count: Int {
  166. elements.count
  167. }
  168. }
  169. extension SplitArguments: Equatable {}
  170. extension SplitArguments.Element: CustomDebugStringConvertible {
  171. var debugDescription: String {
  172. switch value {
  173. case .option(.name(let name)):
  174. return name.synopsisString
  175. case .option(.nameWithValue(let name, let value)):
  176. return name.synopsisString + "; value '\(value)'"
  177. case .value(let value):
  178. return "value '\(value)'"
  179. case .terminator:
  180. return "terminator"
  181. }
  182. }
  183. }
  184. extension SplitArguments.Index: CustomStringConvertible {
  185. var description: String {
  186. switch subIndex {
  187. case .complete: return "\(inputIndex.rawValue)"
  188. case .sub(let sub): return "\(inputIndex.rawValue).\(sub)"
  189. }
  190. }
  191. }
  192. extension SplitArguments: CustomStringConvertible {
  193. var description: String {
  194. guard !isEmpty else { return "<empty>" }
  195. return
  196. elements
  197. .map { element -> String in
  198. switch element.value {
  199. case .option(.name(let name)):
  200. return "[\(element.index)] \(name.synopsisString)"
  201. case .option(.nameWithValue(let name, let value)):
  202. return "[\(element.index)] \(name.synopsisString)='\(value)'"
  203. case .value(let value):
  204. return "[\(element.index)] '\(value)'"
  205. case .terminator:
  206. return "[\(element.index)] --"
  207. }
  208. }
  209. .joined(separator: " ")
  210. }
  211. }
  212. extension SplitArguments.Element {
  213. var isValue: Bool {
  214. switch value {
  215. case .value: return true
  216. case .option, .terminator: return false
  217. }
  218. }
  219. var isTerminator: Bool {
  220. switch value {
  221. case .terminator: return true
  222. case .option, .value: return false
  223. }
  224. }
  225. }
  226. extension SplitArguments {
  227. /// `true` if the arguments are empty.
  228. var isEmpty: Bool {
  229. elements.isEmpty
  230. }
  231. // swift-format-ignore: BeginDocumentationCommentWithOneLineSummary
  232. // https://github.com/swiftlang/swift-format/issues/924
  233. /// Returns `false` if the arguments are empty, or if the only remaining
  234. /// argument is the `--` terminator.
  235. var containsNonTerminatorArguments: Bool {
  236. if elements.isEmpty { return false }
  237. if elements.count > 1 { return true }
  238. if elements.first?.isTerminator == true {
  239. return false
  240. } else {
  241. return true
  242. }
  243. }
  244. /// Returns the original input string at the given origin, or `nil` if
  245. /// `origin` is a sub-index.
  246. func originalInput(at origin: InputOrigin.Element) -> String? {
  247. guard case .argumentIndex(let index) = origin else {
  248. return nil
  249. }
  250. return originalInput[index.inputIndex.rawValue]
  251. }
  252. /// Returns the position in `elements` of the given input origin.
  253. func position(of origin: InputOrigin.Element) -> Int? {
  254. guard case .argumentIndex(let index) = origin else { return nil }
  255. return elements.firstIndex(where: { $0.index == index })
  256. }
  257. /// Returns the position in `elements` of the first element after the given
  258. /// input origin.
  259. func position(after origin: InputOrigin.Element) -> Int? {
  260. guard case .argumentIndex(let index) = origin else { return nil }
  261. return elements.firstIndex(where: { $0.index > index })
  262. }
  263. mutating func popNext() -> (InputOrigin.Element, Element)? {
  264. guard let element = elements.first else { return nil }
  265. removeFirst()
  266. return (.argumentIndex(element.index), element)
  267. }
  268. func peekNext() -> (InputOrigin.Element, Element)? {
  269. guard let element = elements.first else { return nil }
  270. return (.argumentIndex(element.index), element)
  271. }
  272. func extractJoinedElement(
  273. at origin: InputOrigin.Element
  274. ) -> (InputOrigin.Element, String)? {
  275. guard case .argumentIndex(let index) = origin else { return nil }
  276. // Joined arguments only apply when parsing the first sub-element of a
  277. // larger input argument.
  278. guard index.subIndex == .sub(0) else { return nil }
  279. // Rebuild the origin position for the full argument string, e.g. `-Ddebug`
  280. // instead of just the `-D` portion.
  281. let completeOrigin = InputOrigin.Element.argumentIndex(index.completeIndex)
  282. // Get the value from the original string, following the dash and short
  283. // option name. For example, for `-Ddebug`, drop the `-D`, leaving `debug`
  284. // as the value.
  285. // swift-format-ignore: NeverForceUnwrap
  286. // I don't know why this is safe
  287. let value = String(originalInput(at: completeOrigin)!.dropFirst(2))
  288. return (completeOrigin, value)
  289. }
  290. /// Pops the element immediately after the given index, if it is a `.value`.
  291. ///
  292. /// This is used to get the next value in `-fb name` where `name` is the
  293. /// value for `-f`, or `--foo name` where `name` is the value for `--foo`.
  294. /// If `--foo` expects a value, an input of `--foo --bar name` will return
  295. /// `nil`, since the option `--bar` comes before the value `name`.
  296. mutating func popNextElementIfValue(after origin: InputOrigin.Element) -> (
  297. InputOrigin.Element, String
  298. )? {
  299. // Look for the index of the input that comes from immediately after
  300. // `origin` in the input string. We look at the input index so that
  301. // packed short options can be followed, in order, by their values.
  302. // e.g. "-fn f-value n-value"
  303. guard let start = position(after: origin),
  304. let elementIndex = elements[start...].firstIndex(where: {
  305. $0.index.subIndex == .complete
  306. })
  307. else { return nil }
  308. // Only succeed if the element is a value (not prefixed with a dash)
  309. guard case .value(let value) = elements[elementIndex].value
  310. else { return nil }
  311. defer { remove(at: elementIndex) }
  312. let matchedArgumentIndex = elements[elementIndex].index
  313. return (.argumentIndex(matchedArgumentIndex), value)
  314. }
  315. /// Pops the next `.value` after the given index.
  316. ///
  317. /// This is used to get the next value in `-f -b name` where `name` is the value of `-f`.
  318. mutating func popNextValue(
  319. after origin: InputOrigin.Element
  320. ) -> (InputOrigin.Element, String)? {
  321. guard let start = position(after: origin) else { return nil }
  322. guard let resultIndex = elements[start...].firstIndex(where: { $0.isValue })
  323. else { return nil }
  324. defer { remove(at: resultIndex) }
  325. // swift-format-ignore: NeverForceUnwrap
  326. // This is safe because we know `resultIndex` is refers to a value
  327. return (
  328. .argumentIndex(elements[resultIndex].index),
  329. elements[resultIndex].value.valueString!
  330. )
  331. }
  332. /// Pops the element after the given index as a value.
  333. ///
  334. /// This will re-interpret `.option` and `.terminator` as values, i.e.
  335. /// read from the `originalInput`.
  336. ///
  337. /// For an input such as `--a --b foo`, if passed the origin of `--a`,
  338. /// this will first pop the value `--b`, then the value `foo`.
  339. mutating func popNextElementAsValue(after origin: InputOrigin.Element) -> (
  340. InputOrigin.Element, String
  341. )? {
  342. guard let start = position(after: origin) else { return nil }
  343. // Elements are sorted by their `InputIndex`. Find the first `InputIndex`
  344. // after `origin`:
  345. guard
  346. let nextIndex = elements[start...].first(where: {
  347. $0.index.subIndex == .complete
  348. })?.index
  349. else { return nil }
  350. // Remove all elements with this `InputIndex`:
  351. remove(at: nextIndex)
  352. // Return the original input
  353. return (
  354. .argumentIndex(nextIndex), originalInput[nextIndex.inputIndex.rawValue]
  355. )
  356. }
  357. /// Pops the next element if it is a value.
  358. ///
  359. /// If the current elements are `--b foo`, this will return `nil`. If the
  360. /// elements are `foo --b`, this will return the value `foo`.
  361. mutating func popNextElementIfValue() -> (InputOrigin.Element, String)? {
  362. guard let element = elements.first, element.isValue else { return nil }
  363. removeFirst()
  364. // swift-format-ignore: NeverForceUnwrap
  365. // This is safe because we know `element` is a value.
  366. return (.argumentIndex(element.index), element.value.valueString!)
  367. }
  368. /// Finds and "pops" the next element that is a value.
  369. ///
  370. /// If the current elements are `--a --b foo`, this will remove and return
  371. /// `foo`.
  372. mutating func popNextValue() -> (Index, String)? {
  373. guard let idx = elements.firstIndex(where: { $0.isValue })
  374. else { return nil }
  375. let e = elements[idx]
  376. remove(at: idx)
  377. // swift-format-ignore: NeverForceUnwrap
  378. // This is safe because we know `element` is a value.
  379. return (e.index, e.value.valueString!)
  380. }
  381. /// Finds and returns the next element that is a value.
  382. func peekNextValue() -> (Index, String)? {
  383. guard let idx = elements.firstIndex(where: { $0.isValue })
  384. else { return nil }
  385. let e = elements[idx]
  386. // swift-format-ignore: NeverForceUnwrap
  387. // This is safe because we know `element` is a value.
  388. return (e.index, e.value.valueString!)
  389. }
  390. /// Removes the first element in `elements`.
  391. mutating func removeFirst() {
  392. firstUnused += 1
  393. }
  394. /// Removes the element at the given position.
  395. mutating func remove(at position: Int) {
  396. guard position >= firstUnused else {
  397. return
  398. }
  399. // This leaves duplicates of still to-be-used arguments in the unused
  400. // portion of the _elements array.
  401. for i in (firstUnused..<position).reversed() {
  402. _elements[i + 1] = _elements[i]
  403. }
  404. firstUnused += 1
  405. }
  406. /// Removes the elements in the given subrange.
  407. mutating func remove(subrange: Range<Int>) {
  408. var lo = subrange.startIndex
  409. var hi = subrange.endIndex
  410. // This leaves duplicates of still to-be-used arguments in the unused
  411. // portion of the _elements array.
  412. while lo > firstUnused {
  413. hi -= 1
  414. lo -= 1
  415. _elements[hi] = _elements[lo]
  416. }
  417. firstUnused += subrange.count
  418. }
  419. /// Removes the element(s) at the given `Index`.
  420. ///
  421. /// - Note: This may remove multiple elements.
  422. ///
  423. /// For combined _short_ arguments such as `-ab`, these will gets parsed into
  424. /// 3 elements: The _long with short dash_ `ab`, and 2 _short_ `a` and `b`. All of these
  425. /// will have the same `inputIndex` but different `subIndex`. When either of the short ones
  426. /// is removed, that will remove the _long with short dash_ as well. Likewise, if the
  427. /// _long with short dash_ is removed, that will remove both of the _short_ elements.
  428. mutating func remove(at position: Index) {
  429. guard !isEmpty else { return }
  430. // Find the first element at the given input index. Since `elements` is
  431. // always sorted by input index, we can leave this method if we see a
  432. // higher value than `position`.
  433. var start = elements.startIndex
  434. while start < elements.endIndex {
  435. if elements[start].index.inputIndex == position.inputIndex { break }
  436. if elements[start].index.inputIndex > position.inputIndex { return }
  437. start += 1
  438. }
  439. guard start < elements.endIndex else { return }
  440. if case .complete = position.subIndex {
  441. // When removing a `.complete` position, we need to remove both the
  442. // complete element and any sub-elements with the same input index.
  443. // Remove up to the first element where the input index doesn't match.
  444. let end =
  445. elements[start...].firstIndex(where: {
  446. $0.index.inputIndex != position.inputIndex
  447. })
  448. ?? elements.endIndex
  449. remove(subrange: start..<end)
  450. } else {
  451. // When removing a `.sub` (i.e. non-`.complete`) position, we need to
  452. // also remove the `.complete` position, if it exists. Since `.complete`
  453. // positions always come before sub-positions, if one exists it will be
  454. // the position found as `start`.
  455. if elements[start].index.subIndex == .complete {
  456. remove(at: start)
  457. start += 1
  458. }
  459. if let sub = elements[start...].firstIndex(where: { $0.index == position }
  460. ) {
  461. remove(at: sub)
  462. }
  463. }
  464. }
  465. mutating func removeAll(in origin: InputOrigin) {
  466. // swift-format-ignore: ReplaceForEachWithForLoop
  467. // does not conform to collection.
  468. origin.forEach {
  469. remove(at: $0)
  470. }
  471. }
  472. /// Removes the element(s) at the given position.
  473. ///
  474. /// - Note: This may remove multiple elements.
  475. mutating func remove(at origin: InputOrigin.Element) {
  476. guard case .argumentIndex(let i) = origin else { return }
  477. remove(at: i)
  478. }
  479. func coalescedExtraElements() -> [(InputOrigin, String)] {
  480. let completeIndexes: [InputIndex] =
  481. elements
  482. .compactMap {
  483. guard case .complete = $0.index.subIndex else { return nil }
  484. return $0.index.inputIndex
  485. }
  486. // Now return all non-terminator elements that are either:
  487. // 1) `.complete`
  488. // 2) `.sub` but not in `completeIndexes`
  489. let extraElements = elements.filter {
  490. if $0.isTerminator { return false }
  491. switch $0.index.subIndex {
  492. case .complete:
  493. return true
  494. case .sub:
  495. return !completeIndexes.contains($0.index.inputIndex)
  496. }
  497. }
  498. return extraElements.map { element -> (InputOrigin, String) in
  499. let input: String
  500. switch element.index.subIndex {
  501. case .complete:
  502. input = originalInput[element.index.inputIndex.rawValue]
  503. case .sub:
  504. if case .option(let option) = element.value {
  505. input = String(describing: option)
  506. } else {
  507. // Odd case. Fall back to entire input at that index:
  508. input = originalInput[element.index.inputIndex.rawValue]
  509. }
  510. }
  511. return (.init(argumentIndex: element.index), input)
  512. }
  513. }
  514. }
  515. func parseIndividualArg(_ arg: String, at position: Int) throws
  516. -> [SplitArguments.Element]
  517. {
  518. let index = SplitArguments.Index(inputIndex: .init(rawValue: position))
  519. if let nonDashIdx = arg.firstIndex(where: { $0 != "-" }) {
  520. let dashCount = arg.distance(from: arg.startIndex, to: nonDashIdx)
  521. let remainder = arg[nonDashIdx..<arg.endIndex]
  522. switch dashCount {
  523. case 0:
  524. return [.value(arg, index: index)]
  525. case 1:
  526. // Long option:
  527. let parsed = try ParsedArgument(longArgWithSingleDashRemainder: remainder)
  528. // Short options:
  529. let parts = parsed.subarguments
  530. switch parts.count {
  531. case 0:
  532. // This is a '-name=value' style argument
  533. return [.option(parsed, index: index)]
  534. case 1:
  535. // This is a single short '-n' style argument
  536. // swift-format-ignore: NeverForceUnwrap
  537. // this is safe because we know `parts` is non-empty
  538. return [.option(.name(.short(remainder.first!)), index: index)]
  539. default:
  540. var result: [SplitArguments.Element] = [.option(parsed, index: index)]
  541. for (sub, a) in parts {
  542. var i = index
  543. i.subIndex = .sub(sub)
  544. result.append(.option(a, index: i))
  545. }
  546. return result
  547. }
  548. case 2:
  549. return [.option(ParsedArgument(arg), index: index)]
  550. default:
  551. throw ParserError.invalidOption(arg)
  552. }
  553. } else {
  554. // All dashes
  555. let dashCount = arg.count
  556. switch dashCount {
  557. case 0, 1:
  558. // Empty string or single dash
  559. return [.value(arg, index: index)]
  560. case 2:
  561. // We found the 1st "--". All the remaining are positional.
  562. return [.terminator(index: index)]
  563. default:
  564. throw ParserError.invalidOption(arg)
  565. }
  566. }
  567. }
  568. extension SplitArguments {
  569. /// Parses the given input into an array of `Element`.
  570. ///
  571. /// - Parameter arguments: The input from the command line.
  572. ///
  573. /// - Throws: If parsing fails.
  574. init(arguments: [String]) throws {
  575. self.init(originalInput: arguments)
  576. var position = 0
  577. var args = arguments[...]
  578. argLoop: while let arg = args.popFirst() {
  579. defer {
  580. position += 1
  581. }
  582. let parsedElements = try parseIndividualArg(arg, at: position)
  583. _elements.append(contentsOf: parsedElements)
  584. if parsedElements.first?.isTerminator ?? false {
  585. break
  586. }
  587. }
  588. for arg in args {
  589. let i = Index(inputIndex: InputIndex(rawValue: position))
  590. _elements.append(.value(arg, index: i))
  591. position += 1
  592. }
  593. }
  594. }
  595. extension ParsedArgument {
  596. fileprivate init(longArgRemainder remainder: Substring) throws {
  597. try self.init(
  598. longArgRemainder: remainder, makeName: { Name.long(String($0)) })
  599. }
  600. fileprivate init(longArgWithSingleDashRemainder remainder: Substring) throws {
  601. try self.init(
  602. longArgRemainder: remainder,
  603. makeName: {
  604. /// If an argument has a single dash and single character,
  605. /// followed by a value, treat it as a short name.
  606. /// `-c=1` -> `Name.short("c")`
  607. /// Otherwise, treat it as a long name with single dash.
  608. /// `-count=1` -> `Name.longWithSingleDash("count")`
  609. if let first = $0.first, $0.count == 1 {
  610. return .short(first)
  611. } else {
  612. return .longWithSingleDash(String($0))
  613. }
  614. })
  615. }
  616. fileprivate init(
  617. longArgRemainder remainder: Substring, makeName: (Substring) -> Name
  618. ) throws {
  619. if let equalIdx = remainder.firstIndex(of: "=") {
  620. let name = remainder[remainder.startIndex..<equalIdx]
  621. guard !name.isEmpty else {
  622. throw ParserError.invalidOption(makeName(remainder).synopsisString)
  623. }
  624. let after = remainder.index(after: equalIdx)
  625. let value = String(remainder[after..<remainder.endIndex])
  626. self = .nameWithValue(makeName(name), value)
  627. } else {
  628. self = .name(makeName(remainder))
  629. }
  630. }
  631. fileprivate static func shortOptions(shortArgRemainder: Substring) throws
  632. -> [ParsedArgument]
  633. {
  634. var result: [ParsedArgument] = []
  635. var remainder = shortArgRemainder
  636. while let char = remainder.popFirst() {
  637. guard char.isLetter || char.isNumber else {
  638. throw ParserError.nonAlphanumericShortOption(char)
  639. }
  640. result.append(.name(.short(char)))
  641. }
  642. return result
  643. }
  644. }