Skip to content

Commit

Permalink
Merge pull request #12 from MFB-Technologies-Inc/feature/option-value…
Browse files Browse the repository at this point in the history
…-formatting

Improve Option formatting
  • Loading branch information
r-jarvis authored Feb 15, 2024
2 parents f5a0682 + ef4335e commit 98f9fd9
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 61 deletions.
65 changes: 37 additions & 28 deletions Sources/ArgumentEncoding/Formatters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import XCTestDynamicOverlay
public struct FlagFormatter: Sendable {
/// Formats a key string
public let prefix: @Sendable () -> String
public let body: @Sendable (_ key: String) -> String
public let key: @Sendable (_ key: String) -> String

@Sendable
public func format(key: String) -> String {
prefix() + body(key)
prefix() + self.key(key)
}

@Sendable
Expand All @@ -29,73 +29,80 @@ public struct FlagFormatter: Sendable {
///
/// - Parameters
/// - prefix: Closure that returns the prefix string
/// - body: Closure that transforms the key string for formatting
/// - key: Closure that transforms the key string for formatting
public init(
prefix: @escaping @Sendable () -> String,
body: @escaping @Sendable (_ key: String) -> String
key: @escaping @Sendable (_ key: String) -> String
) {
self.prefix = prefix
self.body = body
self.key = key
}

/// Initialize a new formatter
///
/// - Parameters
/// - prefix: Name spaced closure that returns the prefix string for a Flag
/// - body: Name spaced closure that transforms the key string for formatting
public init(prefix: PrefixFormatter = .empty, body: BodyFormatter = .empty) {
/// - key: Name spaced closure that transforms the key string for formatting
public init(prefix: PrefixFormatter = .empty, key: KeyFormatter = .empty) {
self.init(
prefix: prefix.transform,
body: body.transform
key: key.transform
)
}
}

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

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

func format(encoding: OptionEncoding) -> String {
func format(encoding: OptionEncoding) -> [String] {
format(key: encoding.key, value: encoding.value)
}

/// Initialize a new formatter
///
/// - Parameters
/// - prefix: Closure that returns the prefix string
/// - body: Closure that transforms the key string for formatting
/// - key: Closure that transforms the key string for formatting
/// - separator: Closure that returns the string that separates the key and value
/// - value: Closure that transforms the value string for formatting
public init(
prefix: @escaping @Sendable () -> String,
body: @escaping @Sendable (_ key: String) -> String,
separator: @escaping @Sendable () -> String
key: @escaping @Sendable (_ key: String) -> String,
separator: @escaping @Sendable (_ key: String, _ value: String) -> [String],
value: @escaping @Sendable (_ value: String) -> String
) {
self.prefix = prefix
self.body = body
self.key = key
self.separator = separator
self.value = value
}

/// Initialize a new formatter
///
/// - Parameters
/// - prefix: Name spaced closure that returns the prefix string for a Flag
/// - body: Name spaced closure that transforms the key string for formatting
/// - key: Name spaced closure that transforms the key string for formatting
/// - separator: Name spaced closure that returns the string that separates the key and value
/// - value: Name spaced closure that transforms the value string for formatting
public init(
prefix: PrefixFormatter = .empty,
body: BodyFormatter = .empty,
separator: SeparatorFormatter = .space
key: KeyFormatter = .empty,
separator: SeparatorFormatter = .separate,
value: KeyFormatter = .empty
) {
self.init(
prefix: prefix.transform,
body: body.transform,
separator: separator.transform
key: key.transform,
separator: separator.transform,
value: value.transform
)
}
}
Expand All @@ -116,7 +123,7 @@ public struct PrefixFormatter: Sendable {
}

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

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

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

public init(_ transform: @escaping @Sendable () -> String) {
public init(_ transform: @escaping @Sendable (_ key: String, _ value: String) -> [String]) {
self.transform = transform
}

public static let space = Self { StaticString.space.description }
public static let equal = Self { StaticString.equal.description }
public static let separate = Self { [$0, $1] }
public static let equal = Self { ["\($0)\(StaticString.equal.description)\($1)"] }
}

// MARK: Dependency
Expand Down
2 changes: 1 addition & 1 deletion Sources/ArgumentEncoding/Option.swift
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ struct OptionEncoding {
let value: String

func arguments() -> [String] {
[formatter.format(encoding: self)]
formatter.format(encoding: self)
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/ArgumentEncoding/OptionSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ struct OptionSetEncoding {
let values: [OptionEncoding]

func arguments() -> [String] {
values.map { formatter.format(encoding: $0) }
values.flatMap { formatter.format(encoding: $0) }
}
}

Expand Down
10 changes: 5 additions & 5 deletions Tests/ArgumentEncodingTests/ArgumentGroupTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ final class ArgumentGroupTests: XCTestCase {
numThreads: 2,
target: "target"
).arguments(),
["--numThreads 2", "target"]
["--numThreads", "2", "target"]
)

XCTAssertEqual(
Expand All @@ -51,7 +51,7 @@ final class ArgumentGroupTests: XCTestCase {
numThreads: 0,
target: "target"
).arguments(),
["--asyncMain", "--numThreads 0", "target"]
["--asyncMain", "--numThreads", "0", "target"]
)
}

Expand Down Expand Up @@ -106,7 +106,7 @@ final class ArgumentGroupTests: XCTestCase {
target: "target"
)
).arguments(),
["--numThreads 2", "target", "-configuration arm64", "target"]
["--numThreads", "2", "target", "-configuration", "arm64", "target"]
)

XCTAssertEqual(
Expand All @@ -120,7 +120,7 @@ final class ArgumentGroupTests: XCTestCase {
target: "target"
)
).arguments(),
["--asyncMain", "--numThreads 1", "target", "-configuration x86_64", "-buildTests", "target"]
["--asyncMain", "--numThreads", "1", "target", "-configuration", "x86_64", "-buildTests", "target"]
)
}

Expand Down Expand Up @@ -206,7 +206,7 @@ final class ArgumentGroupTests: XCTestCase {
func testEnumGroupTest() throws {
XCTAssertEqual(
ParentEnumGroup.test(numWorkers: 2, testProduct: "PackageTarget").arguments(),
["-numWorkers 2", "-testProduct PackageTarget"]
["-numWorkers", "2", "-testProduct", "PackageTarget"]
)
}

Expand Down
11 changes: 6 additions & 5 deletions Tests/ArgumentEncodingTests/CommandRepresentableTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ final class CommandRepresentableTests: XCTestCase {
)).arguments(),
[
"command",
"--product Target",
"--product",
"Target",
]
)

Expand Down Expand Up @@ -116,7 +117,7 @@ final class CommandRepresentableTests: XCTestCase {
buildTests: true
)
)).arguments(),
["command", "--product OtherTarget", "child", "-configuration arm64", "-buildTests"]
["command", "--product", "OtherTarget", "child", "-configuration", "arm64", "-buildTests"]
)

XCTAssertEqual(
Expand All @@ -128,7 +129,7 @@ final class CommandRepresentableTests: XCTestCase {
buildTests: false
)
)).arguments(),
["command", "--verbose", "child", "-configuration x86_64"]
["command", "--verbose", "child", "-configuration", "x86_64"]
)
}

Expand Down Expand Up @@ -165,14 +166,14 @@ final class CommandRepresentableTests: XCTestCase {
func testEnumTest() throws {
XCTAssertEqual(
ParentEnumCommand.test(numWorkers: 2, testProduct: "PackageTarget").arguments(),
["test", "-numWorkers 2", "-testProduct PackageTarget"]
["test", "-numWorkers", "2", "-testProduct", "PackageTarget"]
)
}

func testEnumChild() throws {
XCTAssertEqual(
ParentEnumCommand.child(ChildCommand(configuration: .arm64, buildTests: true)).arguments(),
["child", "-configuration arm64", "-buildTests"]
["child", "-configuration", "arm64", "-buildTests"]
)
}
}
27 changes: 17 additions & 10 deletions Tests/ArgumentEncodingTests/FormatterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,57 +34,64 @@ final class FormatterTests: XCTestCase {

func testFlagFormatterKebabCaseBody() throws {
XCTAssertEqual(
FlagFormatter(body: .kebabCase).format(key: "flagKey"),
FlagFormatter(key: .kebabCase).format(key: "flagKey"),
"flag-key"
)
}

func testFlagFormatterSnakeCaseBody() throws {
XCTAssertEqual(
FlagFormatter(body: .snakeCase).format(key: "flagKey"),
FlagFormatter(key: .snakeCase).format(key: "flagKey"),
"flag_key"
)
}

func testOptionFormatterSingleDashPrefix() throws {
XCTAssertEqual(
OptionFormatter(prefix: .singleDash).format(key: "optionKey", value: "optionValue"),
"-optionKey optionValue"
["-optionKey", "optionValue"]
)
}

func testOptionFormatterDoubleDashPrefix() throws {
XCTAssertEqual(
OptionFormatter(prefix: .doubleDash).format(key: "optionKey", value: "optionValue"),
"--optionKey optionValue"
["--optionKey", "optionValue"]
)
}

func testOptionFormatterEmptyPrefix() throws {
XCTAssertEqual(
OptionFormatter(prefix: .empty).format(key: "optionKey", value: "optionValue"),
"optionKey optionValue"
["optionKey", "optionValue"]
)
}

func testOptionFormatterKebabCaseBody() throws {
XCTAssertEqual(
OptionFormatter(body: .kebabCase).format(key: "optionKey", value: "optionValue"),
"option-key optionValue"
OptionFormatter(key: .kebabCase).format(key: "optionKey", value: "optionValue"),
["option-key", "optionValue"]
)
}

func testOptionFormatterSnakeCaseBody() throws {
XCTAssertEqual(
OptionFormatter(body: .snakeCase).format(key: "optionKey", value: "optionValue"),
"option_key optionValue"
OptionFormatter(key: .snakeCase).format(key: "optionKey", value: "optionValue"),
["option_key", "optionValue"]
)
}

func testOptionFormatterEqualSeparator() throws {
XCTAssertEqual(
OptionFormatter(separator: .equal).format(key: "optionKey", value: "optionValue"),
"optionKey=optionValue"
["optionKey=optionValue"]
)
}

func testOptionFormatterSingleQuoteValue() throws {
XCTAssertEqual(
OptionFormatter(value: .singleQuote).format(key: "optionKey", value: "optionValue"),
["optionKey", "'optionValue'"]
)
}
}
6 changes: 3 additions & 3 deletions Tests/ArgumentEncodingTests/OptionSetTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ final class OptionSetTests: XCTestCase {
} operation: {
optionSet.arguments()
}
XCTAssertEqual(args, ["--configuration release", "--configuration debug"])
XCTAssertEqual(args, ["--configuration", "release", "--configuration", "debug"])
}

func testBothRawValueAndStringConvertible() throws {
Expand All @@ -34,7 +34,7 @@ final class OptionSetTests: XCTestCase {
} operation: {
optionSet.arguments()
}
XCTAssertEqual(args, ["--configuration release", "--configuration debug"])
XCTAssertEqual(args, ["--configuration", "release", "--configuration", "debug"])
}

func testBothRawValueAndStringConvertibleContainer() throws {
Expand All @@ -47,7 +47,7 @@ final class OptionSetTests: XCTestCase {
} operation: {
container.arguments()
}
XCTAssertEqual(args, ["--configuration release", "--configuration debug"])
XCTAssertEqual(args, ["--configuration", "release", "--configuration", "debug"])
}
}

Expand Down
6 changes: 3 additions & 3 deletions Tests/ArgumentEncodingTests/OptionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ final class OptionTests: XCTestCase {
} operation: {
option.arguments()
}
XCTAssertEqual(args, ["--configuration release"])
XCTAssertEqual(args, ["--configuration", "release"])
}

func testBothRawValueAndStringConvertible() throws {
Expand All @@ -28,7 +28,7 @@ final class OptionTests: XCTestCase {
} operation: {
option.arguments()
}
XCTAssertEqual(args, ["--configuration release"])
XCTAssertEqual(args, ["--configuration", "release"])
}

func testBothRawValueAndStringConvertibleContainer() throws {
Expand All @@ -38,7 +38,7 @@ final class OptionTests: XCTestCase {
} operation: {
container.arguments()
}
XCTAssertEqual(args, ["--configuration release"])
XCTAssertEqual(args, ["--configuration", "release"])
}
}

Expand Down
Loading

0 comments on commit 98f9fd9

Please sign in to comment.