func lenientParse()

in Sources/ArgumentParser/Parsing/ArgumentSet.swift [205:411]


  func lenientParse(_ all: SplitArguments) throws -> ParsedValues {
    // Create a local, mutable copy of the arguments:
    var inputArguments = all
    
    func parseValue(
      _ argument: ArgumentDefinition,
      _ parsed: ParsedArgument,
      _ originElement: InputOrigin.Element,
      _ update: ArgumentDefinition.Update.Unary,
      _ result: inout ParsedValues,
      _ usedOrigins: inout InputOrigin
    ) throws {
      let origin = InputOrigin(elements: [originElement])
      switch argument.parsingStrategy {
      case .default:
        // We need a value for this option.
        if let value = parsed.value {
          // This was `--foo=bar` style:
          try update(origin, parsed.name, value, &result)
          usedOrigins.formUnion(origin)
        } else if argument.allowsJoinedValue,
           let (origin2, value) = inputArguments.extractJoinedElement(at: originElement)
        {
          // Found a joined argument
          let origins = origin.inserting(origin2)
          try update(origins, parsed.name, String(value), &result)
          usedOrigins.formUnion(origins)
        } else if let (origin2, value) = inputArguments.popNextElementIfValue(after: originElement) {
          // Use `popNextElementIfValue(after:)` to handle cases where short option
          // labels are combined
          let origins = origin.inserting(origin2)
          try update(origins, parsed.name, value, &result)
          usedOrigins.formUnion(origins)
        } else {
          throw ParserError.missingValueForOption(origin, parsed.name)
        }
        
      case .scanningForValue:
        // We need a value for this option.
        if let value = parsed.value {
          // This was `--foo=bar` style:
          try update(origin, parsed.name, value, &result)
          usedOrigins.formUnion(origin)
        } else if argument.allowsJoinedValue,
            let (origin2, value) = inputArguments.extractJoinedElement(at: originElement) {
          // Found a joined argument
          let origins = origin.inserting(origin2)
          try update(origins, parsed.name, String(value), &result)
          usedOrigins.formUnion(origins)
        } else if let (origin2, value) = inputArguments.popNextValue(after: originElement) {
          // Use `popNext(after:)` to handle cases where short option
          // labels are combined
          let origins = origin.inserting(origin2)
          try update(origins, parsed.name, value, &result)
          usedOrigins.formUnion(origins)
        } else {
          throw ParserError.missingValueForOption(origin, parsed.name)
        }
        
      case .unconditional:
        // Use an attached value if it exists...
        if let value = parsed.value {
          // This was `--foo=bar` style:
          try update(origin, parsed.name, value, &result)
          usedOrigins.formUnion(origin)
        } else if argument.allowsJoinedValue,
            let (origin2, value) = inputArguments.extractJoinedElement(at: originElement) {
          // Found a joined argument
          let origins = origin.inserting(origin2)
          try update(origins, parsed.name, String(value), &result)
          usedOrigins.formUnion(origins)
        } else {
          guard let (origin2, value) = inputArguments.popNextElementAsValue(after: originElement) else {
            throw ParserError.missingValueForOption(origin, parsed.name)
          }
          let origins = origin.inserting(origin2)
          try update(origins, parsed.name, value, &result)
          usedOrigins.formUnion(origins)
        }
        
      case .allRemainingInput:
        // Reset initial value with the found input origins:
        try argument.initial(origin, &result)
        
        // Use an attached value if it exists...
        if let value = parsed.value {
          // This was `--foo=bar` style:
          try update(origin, parsed.name, value, &result)
          usedOrigins.formUnion(origin)
        } else if argument.allowsJoinedValue,
            let (origin2, value) = inputArguments.extractJoinedElement(at: originElement) {
          // Found a joined argument
          let origins = origin.inserting(origin2)
          try update(origins, parsed.name, String(value), &result)
          usedOrigins.formUnion(origins)
          inputArguments.removeAll(in: usedOrigins)
        }
        
        // ...and then consume the rest of the arguments
        while let (origin2, value) = inputArguments.popNextElementAsValue(after: originElement) {
          let origins = origin.inserting(origin2)
          try update(origins, parsed.name, value, &result)
          usedOrigins.formUnion(origins)
        }
        
      case .upToNextOption:
        // Use an attached value if it exists...
        if let value = parsed.value {
          // This was `--foo=bar` style:
          try update(origin, parsed.name, value, &result)
          usedOrigins.formUnion(origin)
        } else if argument.allowsJoinedValue,
            let (origin2, value) = inputArguments.extractJoinedElement(at: originElement) {
          // Found a joined argument
          let origins = origin.inserting(origin2)
          try update(origins, parsed.name, String(value), &result)
          usedOrigins.formUnion(origins)
          inputArguments.removeAll(in: usedOrigins)
        }
        
        // Clear out the initial origin first, since it can include
        // the exploded elements of an options group (see issue #327).
        usedOrigins.formUnion(origin)
        inputArguments.removeAll(in: origin)
        
        // ...and then consume the arguments until hitting an option
        while let (origin2, value) = inputArguments.popNextElementIfValue() {
          let origins = origin.inserting(origin2)
          try update(origins, parsed.name, value, &result)
          usedOrigins.formUnion(origins)
        }
      }
    }
    
    // If this argument set includes a positional argument that unconditionally
    // captures all remaining input, we use a different behavior, where we
    // shortcut out at the first sign of a positional argument or unrecognized
    // option/flag label.
    let capturesAll = self.contains(where: { arg in
      arg.isRepeatingPositional && arg.parsingStrategy == .allRemainingInput
    })
    
    var result = ParsedValues(elements: [:], originalInput: all.originalInput)
    var allUsedOrigins = InputOrigin()
    
    try setInitialValues(into: &result)
    
    // Loop over all arguments:
    ArgumentLoop:
    while let (origin, next) = inputArguments.popNext() {
      var usedOrigins = InputOrigin()
      defer {
        inputArguments.removeAll(in: usedOrigins)
        allUsedOrigins.formUnion(usedOrigins)
      }
      
      switch next.value {
      case .value:
        // If we're capturing all, the first positional value represents the
        // start of positional input.
        if capturesAll { break ArgumentLoop }
        // We'll parse positional values later.
        break
      case let .option(parsed):
        // Look for an argument that matches this `--option` or `-o`-style
        // input. If we can't find one, just move on to the next input. We
        // defer catching leftover arguments until we've fully extracted all
        // the information for the selected command.
        guard let argument = first(matching: parsed) else
        {
          // If we're capturing all, an unrecognized option/flag is the start
          // of positional input. However, the first time we see an option
          // pack (like `-fi`) it looks like a long name with a single-dash
          // prefix, which may not match an argument even if its subcomponents
          // will match.
          if capturesAll && parsed.subarguments.isEmpty { break ArgumentLoop }
          
          // Otherwise, continue parsing. This option/flag may get picked up
          // by a child command.
          continue
        }
        
        switch argument.update {
        case let .nullary(update):
          // We don’t expect a value for this option.
          guard parsed.value == nil else {
            throw ParserError.unexpectedValueForOption(origin, parsed.name, parsed.value!)
          }
          try update([origin], parsed.name, &result)
          usedOrigins.insert(origin)
        case let .unary(update):
          try parseValue(argument, parsed, origin, update, &result, &usedOrigins)
        }
      case .terminator:
        // Ignore the terminator, it might get picked up as a positional value later.
        break
      }
    }
    
    // We have parsed all non-positional values at this point.
    // Next: parse / consume the positional values.
    var unusedArguments = all
    unusedArguments.removeAll(in: allUsedOrigins)
    try parsePositionalValues(from: unusedArguments, into: &result)

    return result
  }