Skip to content

Commit 447f0c0

Browse files
committed
Add OptionSet
feature/add-positional-and-option-set
1 parent 7cff84a commit 447f0c0

File tree

5 files changed

+372
-61
lines changed

5 files changed

+372
-61
lines changed

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ struct MyCommand: TopLevelCommandRepresentable {
3737
}
3838
```
3939

40-
In addition to modeling the ability to enable/disable a feature, we need to set a value against some variable. For this, we can use `Option`.
40+
In addition to modeling the ability to enable/disable a feature, we need to set a value against some variable. For this, we can use `Option`. For options that can have multiple values, there is `OptionSet`.
4141

4242
```swift
4343
struct MyCommand: TopLevelCommandRepresentable {
@@ -47,6 +47,7 @@ struct MyCommand: TopLevelCommandRepresentable {
4747

4848
@Flag var myFlag: Bool = false
4949
@Option var myOption: Int = 0
50+
@OptionSet var myOptions: [String] = ["value1", "value2"]
5051
}
5152
```
5253

@@ -141,8 +142,6 @@ struct TestCommand: CommandRepresentable {
141142
@Flag var parallel: Bool = true
142143
@Option var numWorkers: Int = 1
143144
@Flag var showCodecovPath: Bool = false
144-
var testProducts: [Command]
145+
@OptionSet var testProducts: [String] = []
145146
}
146-
147-
extension [Command]: ArgumentGroup {}
148-
```
147+
```

Sources/ArgumentEncoding/ArgumentGroup.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ extension ArgumentGroup {
100100
return container
101101
} else if let option = value as? OptionProtocol {
102102
return .option(option)
103+
} else if let optionSet = value as? OptionSetProtocol {
104+
return .optionSet(optionSet)
103105
} else if let flag = value as? Flag {
104106
return .flag(flag)
105107
} else if let command = value as? Command {
@@ -122,6 +124,8 @@ extension ArgumentGroup {
122124
switch value {
123125
case let .option(option):
124126
return option.arguments(key: label)
127+
case let .optionSet(optionSet):
128+
return optionSet.arguments(key: label)
125129
case let .flag(flag):
126130
return flag.arguments(key: label)
127131
case let .command(command):
@@ -250,6 +254,7 @@ extension ArgumentGroup {
250254
// Represents the possible underlying argument types
251255
private enum Container {
252256
case option(any OptionProtocol)
257+
case optionSet(any OptionSetProtocol)
253258
case flag(Flag)
254259
case command(Command)
255260
case topLevelCommandRep(any TopLevelCommandRepresentable)

Sources/ArgumentEncoding/Option.swift

Lines changed: 24 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,19 @@ public struct Option<Value>: OptionProtocol {
3737

3838
// Different Value types will encode to arguments differently.
3939
// Using unwrap, this can be handled individually per type or collectively by protocol
40-
private let unwrap: @Sendable (Value) -> [String]
41-
internal var unwrapped: [String] {
40+
private let unwrap: @Sendable (Value) -> String?
41+
internal var unwrapped: String? {
4242
unwrap(wrappedValue)
4343
}
4444

45-
func encoding(key: String? = nil) -> [OptionEncoding] {
45+
func encoding(key: String? = nil) -> OptionEncoding? {
4646
guard let _key = keyOverride ?? key else {
47-
return []
47+
return nil
4848
}
49-
return unwrapped.map { OptionEncoding(key: _key, value: $0) }
49+
guard let unwrapped else {
50+
return nil
51+
}
52+
return OptionEncoding(key: _key, value: unwrapped)
5053
}
5154

5255
/// Get the Option's argument encoding. If `keyOverRide` and `key` are both `nil`, it will return an empty array.
@@ -55,7 +58,7 @@ public struct Option<Value>: OptionProtocol {
5558
/// - key: Optionally provide a key value.
5659
/// - Returns: The argument encoding which is an array of strings
5760
public func arguments(key: String? = nil) -> [String] {
58-
encoding(key: key).flatMap { $0.arguments() }
61+
encoding(key: key)?.arguments() ?? []
5962
}
6063

6164
/// Initializes a new option when not used as a `@propertyWrapper`
@@ -64,7 +67,7 @@ public struct Option<Value>: OptionProtocol {
6467
/// - key: Explicit key value
6568
/// - wrappedValue: The underlying value
6669
/// - unwrap: A closure for mapping a Value to [String]
67-
public init(key: some CustomStringConvertible, value: Value, unwrap: @escaping @Sendable (Value) -> [String]) {
70+
public init(key: some CustomStringConvertible, value: Value, unwrap: @escaping @Sendable (Value) -> String?) {
6871
keyOverride = key.description
6972
wrappedValue = value
7073
self.unwrap = unwrap
@@ -76,7 +79,7 @@ public struct Option<Value>: OptionProtocol {
7679
/// - wrappedValue: The underlying value
7780
/// - _ key: Optional explicit key value
7881
/// - _ unwrap: A closure for mapping a Value to [String]
79-
public init(wrappedValue: Value, _ key: String? = nil, _ unwrap: @escaping @Sendable (Value) -> [String]) {
82+
public init(wrappedValue: Value, _ key: String? = nil, _ unwrap: @escaping @Sendable (Value) -> String?) {
8083
keyOverride = key
8184
self.wrappedValue = wrappedValue
8285
self.unwrap = unwrap
@@ -128,8 +131,8 @@ extension Option where Value: CustomStringConvertible {
128131
}
129132

130133
@Sendable
131-
public static func unwrap(_ value: Value) -> [String] {
132-
[value.description]
134+
public static func unwrap(_ value: Value) -> String? {
135+
value.description
133136
}
134137
}
135138

@@ -157,8 +160,8 @@ extension Option where Value: RawRepresentable, Value.RawValue: CustomStringConv
157160
}
158161

159162
@Sendable
160-
public static func unwrap(_ value: Value) -> [String] {
161-
[value.rawValue.description]
163+
public static func unwrap(_ value: Value) -> String? {
164+
value.rawValue.description
162165
}
163166
}
164167

@@ -188,8 +191,8 @@ extension Option where Value: CustomStringConvertible, Value: RawRepresentable,
188191
}
189192

190193
@Sendable
191-
public static func unwrap(_ value: Value) -> [String] {
192-
[value.rawValue.description]
194+
public static func unwrap(_ value: Value) -> String? {
195+
value.rawValue.description
193196
}
194197
}
195198

@@ -223,62 +226,25 @@ extension Option {
223226
}
224227

225228
@Sendable
226-
public static func unwrap<Wrapped>(_ value: Wrapped?) -> [String] where Wrapped: CustomStringConvertible,
229+
public static func unwrap<Wrapped>(_ value: Wrapped?) -> String? where Wrapped: CustomStringConvertible,
227230
Value == Wrapped?
228231
{
229-
[value?.description].compactMap { $0 }
230-
}
231-
}
232-
233-
// MARK: Convenience initializers when Value == Sequence<E>
234-
235-
extension Option {
236-
/// Initializes a new option when not used as a `@propertyWrapper`
237-
///
238-
/// - Parameters
239-
/// - key: Explicit key value
240-
/// - wrappedValue: The underlying value
241-
public init<E>(key: some CustomStringConvertible, values: Value) where Value: Sequence, Value.Element == E,
242-
E: CustomStringConvertible
243-
{
244-
keyOverride = key.description
245-
wrappedValue = values
246-
unwrap = Self.unwrap(_:)
247-
}
248-
249-
/// Initializes a new option when used as a `@propertyWrapper`
250-
///
251-
/// - Parameters
252-
/// - wrappedValue: The underlying value
253-
/// - _ key: Optional explicit key value
254-
public init<E>(wrappedValue: Value, _ key: String? = nil) where Value: Sequence, Value.Element == E,
255-
E: CustomStringConvertible
256-
{
257-
keyOverride = key
258-
self.wrappedValue = wrappedValue
259-
unwrap = Self.unwrap(_:)
260-
}
261-
262-
@Sendable
263-
public static func unwrap<E>(_ value: Value) -> [String] where Value: Sequence, Value.Element == E,
264-
E: CustomStringConvertible
265-
{
266-
value.map(\E.description)
232+
value?.description
267233
}
268234
}
269235

270236
// MARK: ExpressibleBy...Literal conformances
271237

272238
extension Option: ExpressibleByIntegerLiteral where Value: BinaryInteger, Value.IntegerLiteralType == Int {
273239
public init(integerLiteral value: IntegerLiteralType) {
274-
self.init(wrappedValue: Value(integerLiteral: value), nil) { [$0.description] }
240+
self.init(wrappedValue: Value(integerLiteral: value), nil) { $0.description }
275241
}
276242
}
277243

278244
#if os(macOS)
279245
extension Option: ExpressibleByFloatLiteral where Value: BinaryFloatingPoint {
280246
public init(floatLiteral value: FloatLiteralType) {
281-
self.init(wrappedValue: Value(value), nil) { [$0.formatted()] }
247+
self.init(wrappedValue: Value(value), nil) { $0.formatted() }
282248
}
283249
}
284250
#endif
@@ -307,8 +273,10 @@ extension Option: ExpressibleByStringInterpolation where Value: StringProtocol {
307273
}
308274
}
309275

276+
// MARK: Coding
277+
310278
extension Option: DecodableWithConfiguration where Value: Decodable {
311-
public init(from decoder: Decoder, configuration: @escaping @Sendable (Value) -> [String]) throws {
279+
public init(from decoder: Decoder, configuration: @escaping @Sendable (Value) -> String?) throws {
312280
let container = try decoder.singleValueContainer()
313281
try self.init(wrappedValue: container.decode(Value.self), nil, configuration)
314282
}

0 commit comments

Comments
 (0)