Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
280 changes: 280 additions & 0 deletions Sources/ArgumentParser/Parsable Properties/Option.swift
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,105 @@ extension Option {
}
}

// MARK: - @Option T Initializers (name-aware transform)
extension Option {
/// Creates a property with a default value that reads its value from a
/// labeled option, parsing with a closure that receives the matched option
/// name along with the value string.
///
/// This initializer is useful when a single `@Option` property has multiple
/// names and the transform needs to know which name was used. For example:
///
/// ```swift
/// @Option(
/// name: [.customLong("resize"), .customLong("crop")],
/// transformIncludingName: { name, value in
/// switch name {
/// case "resize": return .resize(value)
/// case "crop": return .crop(value)
/// default: fatalError()
/// }
/// }
/// ) var operations: [Operation] = []
/// ```
///
/// - Parameters:
/// - wrappedValue: The default value to use for this property, provided
/// implicitly by the compiler during property wrapper initialization.
/// - name: A specification for what names are allowed for this option.
/// - parsingStrategy: The behavior to use when looking for this option's
/// value.
/// - help: Information about how to use this option.
/// - completion: The type of command-line completion provided for this
/// option.
/// - transformIncludingName: A closure that receives the matched option
/// name (without leading dashes) and the value string, and returns this
/// property's type or throws an error.
@preconcurrency
public init(
wrappedValue: Value,
name: NameSpecification = .long,
parsing parsingStrategy: SingleValueParsingStrategy = .next,
help: ArgumentHelp? = nil,
completion: CompletionKind? = nil,
transformIncludingName: @Sendable @escaping (_ optionName: String, _ value: String) throws -> Value
) {
self.init(
_parsedValue: .init { key in
let arg = ArgumentDefinition(
container: Bare<Value>.self,
key: key,
kind: .name(key: key, specification: name),
help: help,
parsingStrategy: parsingStrategy.base,
transformIncludingName: transformIncludingName,
initial: wrappedValue,
completion: completion)

return ArgumentSet(arg)
})
}

/// Creates a required property that reads its value from a labeled option,
/// parsing with a closure that receives the matched option name along with
/// the value string.
///
/// - Parameters:
/// - name: A specification for what names are allowed for this option.
/// - parsingStrategy: The behavior to use when looking for this option's
/// value.
/// - help: Information about how to use this option.
/// - completion: The type of command-line completion provided for this
/// option.
/// - transformIncludingName: A closure that receives the matched option
/// name (without leading dashes) and the value string, and returns this
/// property's type or throws an error.
@preconcurrency
@_disfavoredOverload
public init(
name: NameSpecification = .long,
parsing parsingStrategy: SingleValueParsingStrategy = .next,
help: ArgumentHelp? = nil,
completion: CompletionKind? = nil,
transformIncludingName: @Sendable @escaping (_ optionName: String, _ value: String) throws -> Value
) {
self.init(
_parsedValue: .init { key in
let arg = ArgumentDefinition(
container: Bare<Value>.self,
key: key,
kind: .name(key: key, specification: name),
help: help,
parsingStrategy: parsingStrategy.base,
transformIncludingName: transformIncludingName,
initial: nil,
completion: completion)

return ArgumentSet(arg)
})
}
}

// MARK: - @Option Optional<T: ExpressibleByArgument> Initializers
extension Option {
/// Creates an optional property that reads its value from a labeled option,
Expand Down Expand Up @@ -911,3 +1010,184 @@ extension Option {
})
}
}

// MARK: - @Option Optional<T> Initializers (name-aware transform)
extension Option {
/// Creates an optional property that reads its value from a labeled option,
/// parsing with a closure that receives the matched option name, with an
/// explicit `nil` default.
///
/// - Parameters:
/// - wrappedValue: A default value to use for this property, provided
/// implicitly by the compiler during property wrapper initialization.
/// - name: A specification for what names are allowed for this option.
/// - parsingStrategy: The behavior to use when looking for this option's
/// value.
/// - help: Information about how to use this option.
/// - completion: The type of command-line completion provided for this
/// option.
/// - transformIncludingName: A closure that receives the matched option
/// name (without leading dashes) and the value string, and returns this
/// property's type or throws an error.
@preconcurrency
public init<T>(
wrappedValue: _OptionalNilComparisonType,
name: NameSpecification = .long,
parsing parsingStrategy: SingleValueParsingStrategy = .next,
help: ArgumentHelp? = nil,
completion: CompletionKind? = nil,
transformIncludingName: @Sendable @escaping (_ optionName: String, _ value: String) throws -> T
) where Value == T? {
self.init(
_parsedValue: .init { key in
let arg = ArgumentDefinition(
container: Optional<T>.self,
key: key,
kind: .name(key: key, specification: name),
help: help,
parsingStrategy: parsingStrategy.base,
transformIncludingName: transformIncludingName,
initial: nil,
completion: completion)

return ArgumentSet(arg)
})
}

/// Creates an optional property that reads its value from a labeled option,
/// parsing with a closure that receives the matched option name.
///
/// - Parameters:
/// - name: A specification for what names are allowed for this option.
/// - parsingStrategy: The behavior to use when looking for this option's
/// value.
/// - help: Information about how to use this option.
/// - completion: The type of command-line completion provided for this
/// option.
/// - transformIncludingName: A closure that receives the matched option
/// name (without leading dashes) and the value string, and returns this
/// property's type or throws an error.
@preconcurrency
public init<T>(
name: NameSpecification = .long,
parsing parsingStrategy: SingleValueParsingStrategy = .next,
help: ArgumentHelp? = nil,
completion: CompletionKind? = nil,
transformIncludingName: @Sendable @escaping (_ optionName: String, _ value: String) throws -> T
) where Value == T? {
self.init(
_parsedValue: .init { key in
let arg = ArgumentDefinition(
container: Optional<T>.self,
key: key,
kind: .name(key: key, specification: name),
help: help,
parsingStrategy: parsingStrategy.base,
transformIncludingName: transformIncludingName,
initial: nil,
completion: completion)

return ArgumentSet(arg)
})
}
}

// MARK: - @Option Array<T> Initializers (name-aware transform)
extension Option {
/// Creates an array property that reads its values from zero or more labeled
/// options, parsing each element with a closure that receives the matched
/// option name along with the value string.
///
/// This is particularly useful when multiple option names map to a single
/// array property and the relative ordering of different options matters:
///
/// ```swift
/// @Option(
/// name: [.customLong("resize"), .customLong("crop")],
/// transformIncludingName: { name, value in
/// switch name {
/// case "resize": return .resize(value)
/// case "crop": return .crop(value)
/// default: fatalError()
/// }
/// }
/// ) var operations: [Operation] = []
/// ```
///
/// - Parameters:
/// - wrappedValue: A default value to use for this property, provided
/// implicitly by the compiler during property wrapper initialization.
/// If this initial value is non-empty, elements passed from the command
/// line are appended to the original contents.
/// - name: A specification for what names are allowed for this option.
/// - parsingStrategy: The behavior to use when parsing the elements for
/// this option.
/// - help: Information about how to use this option.
/// - completion: The type of command-line completion provided for this
/// option.
/// - transformIncludingName: A closure that receives the matched option
/// name (without leading dashes) and the value string, and returns the
/// element type or throws an error.
@preconcurrency
public init<T>(
wrappedValue: [T],
name: NameSpecification = .long,
parsing parsingStrategy: ArrayParsingStrategy = .singleValue,
help: ArgumentHelp? = nil,
completion: CompletionKind? = nil,
transformIncludingName: @Sendable @escaping (_ optionName: String, _ value: String) throws -> T
) where Value == [T] {
self.init(
_parsedValue: .init { key in
let arg = ArgumentDefinition(
container: Array<T>.self,
key: key,
kind: .name(key: key, specification: name),
help: help,
parsingStrategy: parsingStrategy.base,
transformIncludingName: transformIncludingName,
initial: wrappedValue,
completion: completion)

return ArgumentSet(arg)
})
}

/// Creates a required array property that reads its values from zero or more
/// labeled options, parsing each element with a closure that receives the
/// matched option name along with the value string.
///
/// - Parameters:
/// - name: A specification for what names are allowed for this option.
/// - parsingStrategy: The behavior to use when parsing the elements for
/// this option.
/// - help: Information about how to use this option.
/// - completion: The type of command-line completion provided for this
/// option.
/// - transformIncludingName: A closure that receives the matched option
/// name (without leading dashes) and the value string, and returns the
/// element type or throws an error.
@preconcurrency
public init<T>(
name: NameSpecification = .long,
parsing parsingStrategy: ArrayParsingStrategy = .singleValue,
help: ArgumentHelp? = nil,
completion: CompletionKind? = nil,
transformIncludingName: @Sendable @escaping (_ optionName: String, _ value: String) throws -> T
) where Value == [T] {
self.init(
_parsedValue: .init { key in
let arg = ArgumentDefinition(
container: Array<T>.self,
key: key,
kind: .name(key: key, specification: name),
help: help,
parsingStrategy: parsingStrategy.base,
transformIncludingName: transformIncludingName,
initial: nil,
completion: completion)

return ArgumentSet(arg)
})
}
}
30 changes: 30 additions & 0 deletions Sources/ArgumentParser/Parsing/ArgumentDefinition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,36 @@ extension ArgumentDefinition {
completion: completion)
}

init<Container>(
container: Container.Type,
key: InputKey,
kind: ArgumentDefinition.Kind,
help: ArgumentHelp?,
parsingStrategy: ParsingStrategy,
transformIncludingName: @escaping (String, String) throws -> Container.Contained,
initial: Container.Initial?,
completion: CompletionKind?
) where Container: ArgumentDefinitionContainer {
self.init(
container: Container.self,
key: key,
kind: kind,
allValueStrings: [],
help: help,
defaultValueDescription: nil,
parsingStrategy: parsingStrategy,
parser: { (key, origin, name, valueString) -> Container.Contained in
do {
return try transformIncludingName(name?.valueString ?? "", valueString)
} catch {
throw ParserError.unableToParseValue(
origin, name, valueString, forKey: key, originalError: error)
}
},
initial: initial,
completion: completion)
}

private init<Container>(
container: Container.Type,
key: InputKey,
Expand Down
Loading
Loading