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
}