Skip to content

Commit faa43b0

Browse files
authored
Merge pull request #7 from MFB-Technologies-Inc/feature/add-positional-and-option-set
Feature/add positional and option set
2 parents 7cff84a + 4a27513 commit faa43b0

22 files changed

+1121
-325
lines changed

Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ Typically, modeling a CLI tool will begin with a `TopLevelCommandRepresentable`.
1818
```swift
1919
struct MyCommand: TopLevelCommandRepresentable {
2020
func commandValue() -> Command { "my-command" }
21-
var flagFormatter: FlagFormatter { .doubleDashPrefix }
22-
var optionFormatter: OptionFormatter { .doubleDashPrefix }
21+
let flagFormatter = FlagFormatter(prefix: .doubleDash) }
22+
let optionFormatter = OptionFormatter(prefix: .doubleDash) }
2323
}
2424
```
2525

@@ -30,23 +30,39 @@ Within `MyCommand` we need the ability to model a boolean value to enable/disabl
3030
```swift
3131
struct MyCommand: TopLevelCommandRepresentable {
3232
func commandValue() -> Command { "my-command" }
33-
var flagFormatter: FlagFormatter { .doubleDashPrefix }
34-
var optionFormatter: OptionFormatter { .doubleDashPrefix }
33+
let flagFormatter = FlagFormatter(prefix: .doubleDash) }
34+
let optionFormatter = OptionFormatter(prefix: .doubleDash) }
3535

3636
@Flag var myFlag: Bool = false
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 {
4444
func commandValue() -> Command { "my-command" }
45-
var flagFormatter: FlagFormatter { .doubleDashPrefix }
46-
var optionFormatter: OptionFormatter { .doubleDashPrefix }
45+
let flagFormatter = FlagFormatter(prefix: .doubleDash) }
46+
let optionFormatter = OptionFormatter(prefix: .doubleDash) }
4747

4848
@Flag var myFlag: Bool = false
4949
@Option var myOption: Int = 0
50+
@OptionSet var myOptions: [String] = ["value1", "value2"]
51+
}
52+
```
53+
54+
Positional arguments that are just a value, with no key are supported through the `Positional` type.
55+
56+
```swift
57+
struct MyCommand: TopLevelCommandRepresentable {
58+
func commandValue() -> Command { "my-command" }
59+
let flagFormatter = FlagFormatter(prefix: .doubleDash) }
60+
let optionFormatter = OptionFormatter(prefix: .doubleDash) }
61+
62+
@Flag var myFlag: Bool = false
63+
@Option var myOption: Int = 0
64+
@OptionSet var myOptions: [String] = ["value1", "value2"]
65+
@Positional var myPositional: String = "positional"
5066
}
5167
```
5268

@@ -114,35 +130,27 @@ import ArgumentEncoding
114130
enum SwiftCommand: TopLevelCommandRepresentable {
115131
func commandValue() -> Command { "swift" }
116132

117-
var flagFormatter: FlagFormatter { .doubleDashPrefixKebabCase }
118-
var optionFormatter: OptionFormatter { .doubleDashPrefixKebabCase }
133+
var flagFormatter: FlagFormatter { FlagFormatter(prefix: .doubleDash, body: .kebabCase) }
134+
var optionFormatter: OptionFormatter { OptionFormatter(prefix: .doubleDash, body: .kebabCase) }
119135

120136
case run(RunCommand)
121137
case test(TestCommand)
122138
}
123139

124140
struct RunCommand: CommandRepresentable {
125-
let flagFormatter: FlagFormatter = .doubleDashPrefixKebabCase
126-
let optionFormatter: OptionFormatter = .doubleDashPrefixKebabCase
127-
128-
let executable: Command
141+
@Positional var executable: String
129142
}
130143

131144
extension RunCommand: ExpressibleByStringLiteral {
132145
init(stringLiteral value: StringLiteralType) {
133-
self.init(executable: Command(rawValue: value))
146+
self.init(executable: Positional(wrapped: value))
134147
}
135148
}
136149

137150
struct TestCommand: CommandRepresentable {
138-
let flagFormatter: FlagFormatter = .doubleDashPrefixKebabCase
139-
let optionFormatter: OptionFormatter = .doubleDashPrefixKebabCase
140-
141151
@Flag var parallel: Bool = true
142152
@Option var numWorkers: Int = 1
143153
@Flag var showCodecovPath: Bool = false
144-
var testProducts: [Command]
154+
@OptionSet var testProducts: [String] = []
145155
}
146-
147-
extension [Command]: ArgumentGroup {}
148-
```
156+
```

Sources/ArgumentEncoding/ArgumentGroup.swift

Lines changed: 10 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 {
@@ -110,6 +112,8 @@ extension ArgumentGroup {
110112
return .commandRep(commandRep)
111113
} else if let group = value as? (any ArgumentGroup) {
112114
return .group(group)
115+
} else if let positional = value as? PositionalProtocol {
116+
return .positional(positional)
113117
} else {
114118
return nil
115119
}
@@ -122,6 +126,8 @@ extension ArgumentGroup {
122126
switch value {
123127
case let .option(option):
124128
return option.arguments(key: label)
129+
case let .optionSet(optionSet):
130+
return optionSet.arguments(key: label)
125131
case let .flag(flag):
126132
return flag.arguments(key: label)
127133
case let .command(command):
@@ -136,6 +142,8 @@ extension ArgumentGroup {
136142
}
137143
case let .group(group):
138144
return group.arguments()
145+
case let .positional(positional):
146+
return positional.arguments()
139147
}
140148
})
141149
}
@@ -250,9 +258,11 @@ extension ArgumentGroup {
250258
// Represents the possible underlying argument types
251259
private enum Container {
252260
case option(any OptionProtocol)
261+
case optionSet(any OptionSetProtocol)
253262
case flag(Flag)
254263
case command(Command)
255264
case topLevelCommandRep(any TopLevelCommandRepresentable)
256265
case commandRep(any CommandRepresentable)
257266
case group(any ArgumentGroup)
267+
case positional(any PositionalProtocol)
258268
}

Sources/ArgumentEncoding/CaseConverter.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ import Foundation
77

88
/// Convert from Swift's typical camelCase to kebab-case and snake_case as some argument formats require them.
99
public enum CaseConverter {
10-
public static let kebabCase: (String) -> String = fromCamelCase(template: "$1-$2")
10+
public static let kebabCase: @Sendable (String) -> String = fromCamelCase(template: "$1-$2")
1111

12-
public static let snakeCase: (String) -> String = fromCamelCase(template: "$1_$2")
12+
public static let snakeCase: @Sendable (String) -> String = fromCamelCase(template: "$1_$2")
1313

14-
private static func fromCamelCase(template: String) -> (String) -> String {
14+
@Sendable
15+
private static func fromCamelCase(template: String) -> @Sendable (String) -> String {
1516
guard let regex = try? NSRegularExpression(pattern: "([a-z0-9])([A-Z])", options: []) else {
1617
return { $0 }
1718
}

Sources/ArgumentEncoding/CommandRepresentable.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import Dependencies
1313
/// struct ParentGroup: CommandRepresentable {
1414
/// // Formatters to satisfy `FormatterNode` requirements
1515
/// let flagFormatter: FlagFormatter = .doubleDashPrefix
16-
/// let optionFormatter: OptionFormatter = .doubleDashPrefix
16+
/// let optionFormatter: OptionFormatter = OptionFormatter(prefix: .doubleDash)
1717
///
1818
/// // Properties that represent the child arguments
1919
/// @Flag var asyncMain: Bool

Sources/ArgumentEncoding/Flag.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import Dependencies
2222
/// ```swift
2323
/// struct FlagContainer: ArgumentGroup, FormatterNode {
2424
/// let flagFormatter: FlagFormatter = .doubleDashPrefix
25-
/// let optionFormatter: OptionFormatter = .doubleDashPrefix
25+
/// let optionFormatter: OptionFormatter = OptionFormatter(prefix: .doubleDash)
2626
///
2727
/// @Flag var name: Bool = true
2828
/// }

Sources/ArgumentEncoding/FlagFormatter.swift

Lines changed: 0 additions & 68 deletions
This file was deleted.

0 commit comments

Comments
 (0)