Skip to content
Open
4 changes: 4 additions & 0 deletions Sources/ArgumentParser/Parsable Properties/Argument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -225,12 +225,14 @@ extension Argument {
let help = ArgumentDefinition.Help(options: [], help: help, key: key)
let arg = ArgumentDefinition(kind: .positional, help: help, completion: completion ?? .default, update: .unary({
(origin, name, valueString, parsedValues) in
guard let valueString = valueString else { return false }
do {
let transformedValue = try transform(valueString)
parsedValues.set(transformedValue, forKey: key, inputOrigin: origin)
} catch {
throw ParserError.unableToParseValue(origin, name, valueString, forKey: key, originalError: error)
}
return true
}), initial: { origin, values in
if let v = initial {
values.set(v, forKey: key, inputOrigin: origin)
Expand Down Expand Up @@ -412,6 +414,7 @@ extension Argument {
parsingStrategy: parsingStrategy.base,
update: .unary({
(origin, name, valueString, parsedValues) in
guard let valueString = valueString else { return false }
do {
let transformedElement = try transform(valueString)
parsedValues.update(forKey: key, inputOrigin: origin, initial: [Element](), closure: {
Expand All @@ -420,6 +423,7 @@ extension Argument {
} catch {
throw ParserError.unableToParseValue(origin, name, valueString, forKey: key, originalError: error)
}
return true
}),
initial: setInitialValue)
arg.help.defaultValue = helpDefaultValue
Expand Down
155 changes: 155 additions & 0 deletions Sources/ArgumentParser/Parsable Properties/Option.swift
Original file line number Diff line number Diff line change
Expand Up @@ -338,12 +338,14 @@ extension Option {
let help = ArgumentDefinition.Help(options: initial != nil ? .isOptional : [], help: help, key: key)
var arg = ArgumentDefinition(kind: kind, help: help, completion: completion ?? .default, parsingStrategy: parsingStrategy.base, update: .unary({
(origin, name, valueString, parsedValues) in
guard let valueString = valueString else { return false }
do {
let transformedValue = try transform(valueString)
parsedValues.set(transformedValue, forKey: key, inputOrigin: origin)
} catch {
throw ParserError.unableToParseValue(origin, name, valueString, forKey: key, originalError: error)
}
return true
}), initial: { origin, values in
if let v = initial {
values.set(v, forKey: key, inputOrigin: origin)
Expand Down Expand Up @@ -456,6 +458,45 @@ extension Option {
})
}

/// Creates an array property with an optional value, intended to be called by other constructors to centralize logic.
///
/// This private `init` allows us to expose multiple other similar constructors to allow for standard default property initialization while reducing code duplication.
private init<Element>(
initial: [Element]?,
name: NameSpecification,
parsingStrategy: ArrayParsingStrategy,
help: ArgumentHelp?,
completion: CompletionKind?
) where Element: ExpressibleByArgument, Value == Array<Element>? {
self.init(_parsedValue: .init { key in
// Assign the initial-value setter and help text for default value based on if an initial value was provided.
let setInitialValue: ArgumentDefinition.Initial
let helpDefaultValue: String?
if let initial = initial {
setInitialValue = { origin, values in
values.set(initial, forKey: key, inputOrigin: origin)
}
helpDefaultValue = !initial.isEmpty ? initial.defaultValueDescription : nil
} else {
setInitialValue = { _, _ in }
helpDefaultValue = nil
}

let kind = ArgumentDefinition.Kind.name(key: key, specification: name)
let help = ArgumentDefinition.Help(options: [.isOptional, .isRepeating], help: help, key: key)
var arg = ArgumentDefinition(
kind: kind,
help: help,
completion: completion ?? Element.defaultCompletionKind,
parsingStrategy: parsingStrategy.base,
update: .appendToArray(forType: Element.self, key: key),
initial: setInitialValue
)
arg.help.defaultValue = helpDefaultValue
return ArgumentSet(arg)
})
}

/// Creates an array property that reads its values from zero or more
/// labeled options.
///
Expand Down Expand Up @@ -508,6 +549,29 @@ extension Option {
)
}

/// Creates an optional array property that reads its values from zero or more
/// labeled options.
///
/// - Parameters:
/// - name: A specification for what names are allowed for this flag.
/// - initial: A default value to use for this property.
/// - parsingStrategy: The behavior to use when parsing multiple values
/// from the command-line arguments.
/// - help: Information about how to use this option.
public init<Element>(
name: NameSpecification = .long,
parsing parsingStrategy: ArrayParsingStrategy = .singleValue,
help: ArgumentHelp? = nil,
completion: CompletionKind? = nil
) where Element: ExpressibleByArgument, Value == Array<Element>? {
self.init(
initial: nil,
name: name,
parsingStrategy: parsingStrategy,
help: help,
completion: completion
)
}

/// Creates an array property with an optional default value, intended to be called by other constructors to centralize logic.
///
Expand Down Expand Up @@ -542,6 +606,64 @@ extension Option {
completion: completion ?? .default,
parsingStrategy: parsingStrategy.base,
update: .unary({ (origin, name, valueString, parsedValues) in
// First of all we need to create an empty array.
parsedValues.update(forKey: key, inputOrigin: origin, initial: [Element]()) { _ in }
// Returns true anyway, because we always create an empty array above.
guard let valueString = valueString else { return true }
do {
let transformedElement = try transform(valueString)
parsedValues.update(forKey: key, inputOrigin: origin, initial: [Element](), closure: {
$0.append(transformedElement)
})
} catch {
throw ParserError.unableToParseValue(origin, name, valueString, forKey: key, originalError: error)
}
return true
}),
initial: setInitialValue
)
arg.help.defaultValue = helpDefaultValue
return ArgumentSet(arg)
})
}

/// Creates an array property with an optional default value, intended to be called by other constructors to centralize logic.
///
/// This private `init` allows us to expose multiple other similar constructors to allow for standard default property initialization while reducing code duplication.
private init<Element>(
initial: [Element]?,
name: NameSpecification,
parsingStrategy: ArrayParsingStrategy,
help: ArgumentHelp?,
completion: CompletionKind?,
transform: @escaping (String) throws -> Element
) where Value == Array<Element>? {
self.init(_parsedValue: .init { key in
// Assign the initial-value setter and help text for default value based on if an initial value was provided.
let setInitialValue: ArgumentDefinition.Initial
let helpDefaultValue: String?
if let initial = initial {
setInitialValue = { origin, values in
values.set(initial, forKey: key, inputOrigin: origin)
}
helpDefaultValue = !initial.isEmpty ? "\(initial)" : nil
} else {
setInitialValue = { _, _ in }
helpDefaultValue = nil
}

let kind = ArgumentDefinition.Kind.name(key: key, specification: name)
let help = ArgumentDefinition.Help(options: [.isOptional, .isRepeating], help: help, key: key)
var arg = ArgumentDefinition(
kind: kind,
help: help,
completion: completion ?? .default,
parsingStrategy: parsingStrategy.base,
update: .unary({ (origin, name, valueString, parsedValues) in
// First of all we need to create an empty array.
parsedValues.update(forKey: key, inputOrigin: origin, initial: [Element]()) { _ in }
// Returns true anyway, because we always create an empty array above.
guard let valueString = valueString else { return true }
do {
let transformedElement = try transform(valueString)
parsedValues.update(forKey: key, inputOrigin: origin, initial: [Element](), closure: {
Expand All @@ -550,6 +672,7 @@ extension Option {
} catch {
throw ParserError.unableToParseValue(origin, name, valueString, forKey: key, originalError: error)
}
return true
}),
initial: setInitialValue
)
Expand Down Expand Up @@ -591,6 +714,38 @@ extension Option {
)
}

/// Creates an optional array property that reads its values from zero or more
/// labeled options, parsing with the given closure.
///
/// This property defaults to an empty array if the `initial` parameter
/// is not specified.
///
/// - Parameters:
/// - name: A specification for what names are allowed for this flag.
/// - initial: A default value to use for this property. If `initial` is
/// `nil`, this option defaults to an empty array.
/// - parsingStrategy: The behavior to use when parsing multiple values
/// from the command-line arguments.
/// - help: Information about how to use this option.
/// - transform: A closure that converts a string into this property's
/// element type or throws an error.
public init<Element>(
name: NameSpecification = .long,
parsing parsingStrategy: ArrayParsingStrategy = .singleValue,
help: ArgumentHelp? = nil,
completion: CompletionKind? = nil,
transform: @escaping (String) throws -> Element
) where Value == Array<Element>? {
self.init(
initial: nil,
name: name,
parsingStrategy: parsingStrategy,
help: help,
completion: completion,
transform: transform
)
}

/// Creates an array property with no default value that reads its values from zero or more labeled options, parsing each element with the given closure.
///
/// This method is called to initialize an array `Option` with no default value such as:
Expand Down
13 changes: 10 additions & 3 deletions Sources/ArgumentParser/Parsing/ArgumentDefinition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ struct ArgumentDefinition {
/// argument's value.
enum Update {
typealias Nullary = (InputOrigin, Name?, inout ParsedValues) throws -> Void
typealias Unary = (InputOrigin, Name?, String, inout ParsedValues) throws -> Void
/// - Returns: true if value was updated, otherwise false
typealias Unary = (InputOrigin, Name?, String?, inout ParsedValues) throws -> Bool

/// An argument that gets its value solely from its presence.
case nullary(Nullary)
Expand Down Expand Up @@ -199,12 +200,18 @@ extension ArgumentDefinition.Update {
static func appendToArray<A: ExpressibleByArgument>(forType type: A.Type, key: InputKey) -> ArgumentDefinition.Update {
return ArgumentDefinition.Update.unary {
(origin, name, value, values) in
// First of all we need to create an empty array.
values.update(forKey: key, inputOrigin: origin, initial: [A]()) { _ in }
// Returns true anyway, because we always create an empty array above.
guard let value = value else { return true }

guard let v = A(argument: value) else {
throw ParserError.unableToParseValue(origin, name, value, forKey: key)
}
values.update(forKey: key, inputOrigin: origin, initial: [A](), closure: {
values.update(forKey: key, inputOrigin: origin, initial: [A]()) {
$0.append(v)
})
}
return true
}
}
}
Expand Down
Loading