Skip to content

Commit 98f9fd9

Browse files
authored
Merge pull request #12 from MFB-Technologies-Inc/feature/option-value-formatting
Improve Option formatting
2 parents f5a0682 + ef4335e commit 98f9fd9

9 files changed

+79
-61
lines changed

Sources/ArgumentEncoding/Formatters.swift

+37-28
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import XCTestDynamicOverlay
1313
public struct FlagFormatter: Sendable {
1414
/// Formats a key string
1515
public let prefix: @Sendable () -> String
16-
public let body: @Sendable (_ key: String) -> String
16+
public let key: @Sendable (_ key: String) -> String
1717

1818
@Sendable
1919
public func format(key: String) -> String {
20-
prefix() + body(key)
20+
prefix() + self.key(key)
2121
}
2222

2323
@Sendable
@@ -29,73 +29,80 @@ public struct FlagFormatter: Sendable {
2929
///
3030
/// - Parameters
3131
/// - prefix: Closure that returns the prefix string
32-
/// - body: Closure that transforms the key string for formatting
32+
/// - key: Closure that transforms the key string for formatting
3333
public init(
3434
prefix: @escaping @Sendable () -> String,
35-
body: @escaping @Sendable (_ key: String) -> String
35+
key: @escaping @Sendable (_ key: String) -> String
3636
) {
3737
self.prefix = prefix
38-
self.body = body
38+
self.key = key
3939
}
4040

4141
/// Initialize a new formatter
4242
///
4343
/// - Parameters
4444
/// - prefix: Name spaced closure that returns the prefix string for a Flag
45-
/// - body: Name spaced closure that transforms the key string for formatting
46-
public init(prefix: PrefixFormatter = .empty, body: BodyFormatter = .empty) {
45+
/// - key: Name spaced closure that transforms the key string for formatting
46+
public init(prefix: PrefixFormatter = .empty, key: KeyFormatter = .empty) {
4747
self.init(
4848
prefix: prefix.transform,
49-
body: body.transform
49+
key: key.transform
5050
)
5151
}
5252
}
5353

5454
/// Formats `Option`s to match how different executables format arguments
5555
public struct OptionFormatter: Sendable {
5656
public let prefix: @Sendable () -> String
57-
public let body: @Sendable (_ key: String) -> String
58-
public let separator: @Sendable () -> String
57+
public let key: @Sendable (_ key: String) -> String
58+
public let separator: @Sendable (_ key: String, _ value: String) -> [String]
59+
public let value: @Sendable (_ value: String) -> String
5960

60-
public func format(key: String, value: String) -> String {
61-
prefix() + body(key) + separator() + value
61+
public func format(key: String, value: String) -> [String] {
62+
separator(prefix() + self.key(key), self.value(value))
6263
}
6364

64-
func format(encoding: OptionEncoding) -> String {
65+
func format(encoding: OptionEncoding) -> [String] {
6566
format(key: encoding.key, value: encoding.value)
6667
}
6768

6869
/// Initialize a new formatter
6970
///
7071
/// - Parameters
7172
/// - prefix: Closure that returns the prefix string
72-
/// - body: Closure that transforms the key string for formatting
73+
/// - key: Closure that transforms the key string for formatting
7374
/// - separator: Closure that returns the string that separates the key and value
75+
/// - value: Closure that transforms the value string for formatting
7476
public init(
7577
prefix: @escaping @Sendable () -> String,
76-
body: @escaping @Sendable (_ key: String) -> String,
77-
separator: @escaping @Sendable () -> String
78+
key: @escaping @Sendable (_ key: String) -> String,
79+
separator: @escaping @Sendable (_ key: String, _ value: String) -> [String],
80+
value: @escaping @Sendable (_ value: String) -> String
7881
) {
7982
self.prefix = prefix
80-
self.body = body
83+
self.key = key
8184
self.separator = separator
85+
self.value = value
8286
}
8387

8488
/// Initialize a new formatter
8589
///
8690
/// - Parameters
8791
/// - prefix: Name spaced closure that returns the prefix string for a Flag
88-
/// - body: Name spaced closure that transforms the key string for formatting
92+
/// - key: Name spaced closure that transforms the key string for formatting
8993
/// - separator: Name spaced closure that returns the string that separates the key and value
94+
/// - value: Name spaced closure that transforms the value string for formatting
9095
public init(
9196
prefix: PrefixFormatter = .empty,
92-
body: BodyFormatter = .empty,
93-
separator: SeparatorFormatter = .space
97+
key: KeyFormatter = .empty,
98+
separator: SeparatorFormatter = .separate,
99+
value: KeyFormatter = .empty
94100
) {
95101
self.init(
96102
prefix: prefix.transform,
97-
body: body.transform,
98-
separator: separator.transform
103+
key: key.transform,
104+
separator: separator.transform,
105+
value: value.transform
99106
)
100107
}
101108
}
@@ -116,7 +123,7 @@ public struct PrefixFormatter: Sendable {
116123
}
117124

118125
/// Name space for a closure that transforms a Flag or Option's key
119-
public struct BodyFormatter: Sendable {
126+
public struct KeyFormatter: Sendable {
120127
public let transform: @Sendable (_ key: String) -> String
121128

122129
public init(_ transform: @escaping @Sendable (_ key: String) -> String) {
@@ -126,18 +133,20 @@ public struct BodyFormatter: Sendable {
126133
public static let empty = Self { $0 }
127134
public static let kebabCase = Self(CaseConverter.kebabCase)
128135
public static let snakeCase = Self(CaseConverter.snakeCase)
136+
public static let singleQuote = Self { "'\($0)'" }
129137
}
130138

131-
/// Name space for a closure that returns the separator string between an Option's key and value
139+
/// Name space for a closure that returns the Option's key and value separated by a string or as separate elements in an
140+
/// array
132141
public struct SeparatorFormatter: Sendable {
133-
public let transform: @Sendable () -> String
142+
public let transform: @Sendable (_ key: String, _ value: String) -> [String]
134143

135-
public init(_ transform: @escaping @Sendable () -> String) {
144+
public init(_ transform: @escaping @Sendable (_ key: String, _ value: String) -> [String]) {
136145
self.transform = transform
137146
}
138147

139-
public static let space = Self { StaticString.space.description }
140-
public static let equal = Self { StaticString.equal.description }
148+
public static let separate = Self { [$0, $1] }
149+
public static let equal = Self { ["\($0)\(StaticString.equal.description)\($1)"] }
141150
}
142151

143152
// MARK: Dependency

Sources/ArgumentEncoding/Option.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ struct OptionEncoding {
338338
let value: String
339339

340340
func arguments() -> [String] {
341-
[formatter.format(encoding: self)]
341+
formatter.format(encoding: self)
342342
}
343343
}
344344

Sources/ArgumentEncoding/OptionSet.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ struct OptionSetEncoding {
263263
let values: [OptionEncoding]
264264

265265
func arguments() -> [String] {
266-
values.map { formatter.format(encoding: $0) }
266+
values.flatMap { formatter.format(encoding: $0) }
267267
}
268268
}
269269

Tests/ArgumentEncodingTests/ArgumentGroupTests.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ final class ArgumentGroupTests: XCTestCase {
4242
numThreads: 2,
4343
target: "target"
4444
).arguments(),
45-
["--numThreads 2", "target"]
45+
["--numThreads", "2", "target"]
4646
)
4747

4848
XCTAssertEqual(
@@ -51,7 +51,7 @@ final class ArgumentGroupTests: XCTestCase {
5151
numThreads: 0,
5252
target: "target"
5353
).arguments(),
54-
["--asyncMain", "--numThreads 0", "target"]
54+
["--asyncMain", "--numThreads", "0", "target"]
5555
)
5656
}
5757

@@ -106,7 +106,7 @@ final class ArgumentGroupTests: XCTestCase {
106106
target: "target"
107107
)
108108
).arguments(),
109-
["--numThreads 2", "target", "-configuration arm64", "target"]
109+
["--numThreads", "2", "target", "-configuration", "arm64", "target"]
110110
)
111111

112112
XCTAssertEqual(
@@ -120,7 +120,7 @@ final class ArgumentGroupTests: XCTestCase {
120120
target: "target"
121121
)
122122
).arguments(),
123-
["--asyncMain", "--numThreads 1", "target", "-configuration x86_64", "-buildTests", "target"]
123+
["--asyncMain", "--numThreads", "1", "target", "-configuration", "x86_64", "-buildTests", "target"]
124124
)
125125
}
126126

@@ -206,7 +206,7 @@ final class ArgumentGroupTests: XCTestCase {
206206
func testEnumGroupTest() throws {
207207
XCTAssertEqual(
208208
ParentEnumGroup.test(numWorkers: 2, testProduct: "PackageTarget").arguments(),
209-
["-numWorkers 2", "-testProduct PackageTarget"]
209+
["-numWorkers", "2", "-testProduct", "PackageTarget"]
210210
)
211211
}
212212

Tests/ArgumentEncodingTests/CommandRepresentableTests.swift

+6-5
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ final class CommandRepresentableTests: XCTestCase {
5353
)).arguments(),
5454
[
5555
"command",
56-
"--product Target",
56+
"--product",
57+
"Target",
5758
]
5859
)
5960

@@ -116,7 +117,7 @@ final class CommandRepresentableTests: XCTestCase {
116117
buildTests: true
117118
)
118119
)).arguments(),
119-
["command", "--product OtherTarget", "child", "-configuration arm64", "-buildTests"]
120+
["command", "--product", "OtherTarget", "child", "-configuration", "arm64", "-buildTests"]
120121
)
121122

122123
XCTAssertEqual(
@@ -128,7 +129,7 @@ final class CommandRepresentableTests: XCTestCase {
128129
buildTests: false
129130
)
130131
)).arguments(),
131-
["command", "--verbose", "child", "-configuration x86_64"]
132+
["command", "--verbose", "child", "-configuration", "x86_64"]
132133
)
133134
}
134135

@@ -165,14 +166,14 @@ final class CommandRepresentableTests: XCTestCase {
165166
func testEnumTest() throws {
166167
XCTAssertEqual(
167168
ParentEnumCommand.test(numWorkers: 2, testProduct: "PackageTarget").arguments(),
168-
["test", "-numWorkers 2", "-testProduct PackageTarget"]
169+
["test", "-numWorkers", "2", "-testProduct", "PackageTarget"]
169170
)
170171
}
171172

172173
func testEnumChild() throws {
173174
XCTAssertEqual(
174175
ParentEnumCommand.child(ChildCommand(configuration: .arm64, buildTests: true)).arguments(),
175-
["child", "-configuration arm64", "-buildTests"]
176+
["child", "-configuration", "arm64", "-buildTests"]
176177
)
177178
}
178179
}

Tests/ArgumentEncodingTests/FormatterTests.swift

+17-10
Original file line numberDiff line numberDiff line change
@@ -34,57 +34,64 @@ final class FormatterTests: XCTestCase {
3434

3535
func testFlagFormatterKebabCaseBody() throws {
3636
XCTAssertEqual(
37-
FlagFormatter(body: .kebabCase).format(key: "flagKey"),
37+
FlagFormatter(key: .kebabCase).format(key: "flagKey"),
3838
"flag-key"
3939
)
4040
}
4141

4242
func testFlagFormatterSnakeCaseBody() throws {
4343
XCTAssertEqual(
44-
FlagFormatter(body: .snakeCase).format(key: "flagKey"),
44+
FlagFormatter(key: .snakeCase).format(key: "flagKey"),
4545
"flag_key"
4646
)
4747
}
4848

4949
func testOptionFormatterSingleDashPrefix() throws {
5050
XCTAssertEqual(
5151
OptionFormatter(prefix: .singleDash).format(key: "optionKey", value: "optionValue"),
52-
"-optionKey optionValue"
52+
["-optionKey", "optionValue"]
5353
)
5454
}
5555

5656
func testOptionFormatterDoubleDashPrefix() throws {
5757
XCTAssertEqual(
5858
OptionFormatter(prefix: .doubleDash).format(key: "optionKey", value: "optionValue"),
59-
"--optionKey optionValue"
59+
["--optionKey", "optionValue"]
6060
)
6161
}
6262

6363
func testOptionFormatterEmptyPrefix() throws {
6464
XCTAssertEqual(
6565
OptionFormatter(prefix: .empty).format(key: "optionKey", value: "optionValue"),
66-
"optionKey optionValue"
66+
["optionKey", "optionValue"]
6767
)
6868
}
6969

7070
func testOptionFormatterKebabCaseBody() throws {
7171
XCTAssertEqual(
72-
OptionFormatter(body: .kebabCase).format(key: "optionKey", value: "optionValue"),
73-
"option-key optionValue"
72+
OptionFormatter(key: .kebabCase).format(key: "optionKey", value: "optionValue"),
73+
["option-key", "optionValue"]
7474
)
7575
}
7676

7777
func testOptionFormatterSnakeCaseBody() throws {
7878
XCTAssertEqual(
79-
OptionFormatter(body: .snakeCase).format(key: "optionKey", value: "optionValue"),
80-
"option_key optionValue"
79+
OptionFormatter(key: .snakeCase).format(key: "optionKey", value: "optionValue"),
80+
["option_key", "optionValue"]
8181
)
8282
}
8383

8484
func testOptionFormatterEqualSeparator() throws {
8585
XCTAssertEqual(
8686
OptionFormatter(separator: .equal).format(key: "optionKey", value: "optionValue"),
87-
"optionKey=optionValue"
87+
["optionKey=optionValue"]
88+
)
89+
}
90+
91+
func testOptionFormatterSingleQuoteValue() throws {
92+
XCTAssertEqual(
93+
OptionFormatter(value: .singleQuote).format(key: "optionKey", value: "optionValue"),
94+
["optionKey", "'optionValue'"]
8895
)
8996
}
9097
}

Tests/ArgumentEncodingTests/OptionSetTests.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ final class OptionSetTests: XCTestCase {
1818
} operation: {
1919
optionSet.arguments()
2020
}
21-
XCTAssertEqual(args, ["--configuration release", "--configuration debug"])
21+
XCTAssertEqual(args, ["--configuration", "release", "--configuration", "debug"])
2222
}
2323

2424
func testBothRawValueAndStringConvertible() throws {
@@ -34,7 +34,7 @@ final class OptionSetTests: XCTestCase {
3434
} operation: {
3535
optionSet.arguments()
3636
}
37-
XCTAssertEqual(args, ["--configuration release", "--configuration debug"])
37+
XCTAssertEqual(args, ["--configuration", "release", "--configuration", "debug"])
3838
}
3939

4040
func testBothRawValueAndStringConvertibleContainer() throws {
@@ -47,7 +47,7 @@ final class OptionSetTests: XCTestCase {
4747
} operation: {
4848
container.arguments()
4949
}
50-
XCTAssertEqual(args, ["--configuration release", "--configuration debug"])
50+
XCTAssertEqual(args, ["--configuration", "release", "--configuration", "debug"])
5151
}
5252
}
5353

Tests/ArgumentEncodingTests/OptionTests.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ final class OptionTests: XCTestCase {
1818
} operation: {
1919
option.arguments()
2020
}
21-
XCTAssertEqual(args, ["--configuration release"])
21+
XCTAssertEqual(args, ["--configuration", "release"])
2222
}
2323

2424
func testBothRawValueAndStringConvertible() throws {
@@ -28,7 +28,7 @@ final class OptionTests: XCTestCase {
2828
} operation: {
2929
option.arguments()
3030
}
31-
XCTAssertEqual(args, ["--configuration release"])
31+
XCTAssertEqual(args, ["--configuration", "release"])
3232
}
3333

3434
func testBothRawValueAndStringConvertibleContainer() throws {
@@ -38,7 +38,7 @@ final class OptionTests: XCTestCase {
3838
} operation: {
3939
container.arguments()
4040
}
41-
XCTAssertEqual(args, ["--configuration release"])
41+
XCTAssertEqual(args, ["--configuration", "release"])
4242
}
4343
}
4444

0 commit comments

Comments
 (0)