Skip to content

Commit d810868

Browse files
Allow Boolean encoding to be governed by new BoolEncodingStrategy (#10)
* Add boolean encoding strategy support This adds support for encoding boolean values in a variety of ways: - true/false (the default) - TRUE/FALSE - yes/no - YES/NO - 1/0 - Custom user-supplied strings * Simplify boolean encoding Rather than having a switch statement with identical ternary checks, move the true/false values to be computed properties on the strategy enum, which then simplifies the encode function.
1 parent 4d87e49 commit d810868

File tree

4 files changed

+127
-6
lines changed

4 files changed

+127
-6
lines changed

Sources/SwiftCSVEncoder/CSVEncodable.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ extension Double: CSVEncodable {
8282

8383
extension Bool: CSVEncodable {
8484
public func encode(configuration: CSVEncoderConfiguration) -> String {
85-
self == true ? "true" : "false"
85+
let (trueValue, falseValue) = configuration.boolEncodingStrategy.encodingValues
86+
87+
return self == true ? trueValue : falseValue
8688
}
8789
}
8890

Sources/SwiftCSVEncoder/CSVEncoderConfiguration.swift

+48-3
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,20 @@ public struct CSVEncoderConfiguration {
1616
/// The default strategy is the ``DateEncodingStrategy-swift.enum/iso8601`` strategy.
1717
public private(set) var dateEncodingStrategy: DateEncodingStrategy = .iso8601
1818

19+
/// The strategy to use when encoding Boolean values.
20+
///
21+
/// The default strategy is the ``BoolEncodingStrategy-swift.enum/trueFalse`` strategy.
22+
public private(set) var boolEncodingStrategy: BoolEncodingStrategy = .trueFalse
23+
1924
/// Creates a new instance of ``CSVEncoderConfiguration`` with the requisite configuration values
2025
/// - Parameter dateEncodingStrategy: The strategy to use when encoding dates
21-
public init(dateEncodingStrategy: DateEncodingStrategy) {
26+
/// - Parameter boolEncodingStrategy: The strategy to use when encoding Boolean values
27+
public init(
28+
dateEncodingStrategy: DateEncodingStrategy = .iso8601,
29+
boolEncodingStrategy: BoolEncodingStrategy = .trueFalse
30+
) {
2231
self.dateEncodingStrategy = dateEncodingStrategy
32+
self.boolEncodingStrategy = boolEncodingStrategy
2333
}
2434

2535
/// The strategy to use when encoding `Date` objects for CSV output.
@@ -34,10 +44,45 @@ public struct CSVEncoderConfiguration {
3444
/// - Parameter custom: A closure that receives the `Date` to encode, and returns the `String` to include in the CSV output.
3545
case custom(@Sendable (Date) -> String)
3646
}
37-
47+
48+
/// The strategy to use when encoding `Bool` objects for CSV output.
49+
public enum BoolEncodingStrategy {
50+
/// The strategy that emits `true` and `false` for Boolean fields
51+
case trueFalse
52+
/// The strategy that emits `TRUE` and `FALSE` for Boolean fields
53+
case trueFalseUppercase
54+
/// The strategy that emite `yes` and `no` for Boolean fields
55+
case yesNo
56+
/// The strategy that emits `YES` and `NO` for Boolean fields
57+
case yesNoUppercase
58+
/// The strategy that emits `1` and `0` for Boolean fields
59+
case integer
60+
/// A custom strategy that emitss the custom supplied strings for Boolean fields
61+
case custom(true: String, false: String)
62+
}
63+
3864
/// A default set of configuration values.
3965
///
4066
/// This configuration set will be used when a ``CSVTable`` is initialized with setting a custom
4167
/// configuration.
42-
public static var `default`: CSVEncoderConfiguration = .init(dateEncodingStrategy: .iso8601)
68+
public static var `default`: CSVEncoderConfiguration = CSVEncoderConfiguration()
69+
}
70+
71+
internal extension CSVEncoderConfiguration.BoolEncodingStrategy {
72+
var encodingValues: (String, String) {
73+
switch self {
74+
case .trueFalse:
75+
return ("true", "false")
76+
case .trueFalseUppercase:
77+
return ("TRUE", "FALSE")
78+
case .yesNo:
79+
return ("yes", "no")
80+
case .yesNoUppercase:
81+
return ("YES", "NO")
82+
case .integer:
83+
return ("1", "0")
84+
case .custom(let trueValue, let falseValue):
85+
return (trueValue, falseValue)
86+
}
87+
}
4388
}

Sources/SwiftCSVEncoder/SwiftCSVEncoder.docc/SwiftCSVEncoder.md

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ CSVColumn("Description", \.description)
5252

5353
``SwiftCSVEncoder`` adds ``CSVEncodable`` conformance to the Swift primitives `String`, `Int`, `Double`, `Bool` and Foundation data types `Date` and `UUID`. Optional forms are automatically handled, with `nil` values being output as empty cells.
5454

55+
``CSVTable/init(columns:configuration:)`` optionally takes a `configuration:` object that specifies strategies for converting `Date` and `Bool` values into strings. This can be essential for some CSV importers which expect columns of those types to be in a specific format in order to correctly recognise them.
56+
5557
To generate the CSV file, call ``CSVTable/export(rows:)``. The return value is the full CSV file, including a header row. String items will be enclosed in double quotes where needed:
5658

5759
```csv

Tests/SwiftCSVEncoderTests/CSVEncodableTests.swift

+74-2
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,87 @@ final class CSVEncodableTests: XCTestCase {
7474
XCTAssertEqual(date.encode(configuration: configuration), "Custom returned value")
7575
}
7676

77-
func testBoolTrueEncodedAsString() {
77+
func testBoolTrueEncodedAsStringByDefault() {
7878
let input: Bool = true
7979

8080
XCTAssertEqual(input.encode(configuration: .default), "true")
8181
}
8282

83-
func testBoolFalseEncodedAsString() {
83+
func testBoolFalseEncodedAsStringByDefault() {
8484
let input: Bool = false
8585

8686
XCTAssertEqual(input.encode(configuration: .default), "false")
8787
}
88+
89+
func testBoolTrueEncodedAsStringByTrueFalse() {
90+
let input: Bool = true
91+
92+
XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .trueFalse)), "true")
93+
}
94+
95+
func testBoolFalseEncodedAsStringByTrueFalse() {
96+
let input: Bool = false
97+
98+
XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .trueFalse)), "false")
99+
}
100+
101+
func testBoolTrueEncodedAsStringByTrueFalseUppercase() {
102+
let input: Bool = true
103+
104+
XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .trueFalseUppercase)), "TRUE")
105+
}
106+
107+
func testBoolFalseEncodedAsStringByTrueFalseUppercase() {
108+
let input: Bool = false
109+
110+
XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .trueFalseUppercase)), "FALSE")
111+
}
112+
113+
func testBoolTrueEncodedAsStringByYesNo() {
114+
let input: Bool = true
115+
116+
XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .yesNo)), "yes")
117+
}
118+
119+
func testBoolFalseEncodedAsStringByYesNo() {
120+
let input: Bool = false
121+
122+
XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .yesNo)), "no")
123+
}
124+
125+
func testBoolTrueEncodedAsStringByYesNoUppercase() {
126+
let input: Bool = true
127+
128+
XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .yesNoUppercase)), "YES")
129+
}
130+
131+
func testBoolFalseEncodedAsStringByYesNoUppercase() {
132+
let input: Bool = false
133+
134+
XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .yesNoUppercase)), "NO")
135+
}
136+
137+
func testBoolTrueEncodedAsStringByInteger() {
138+
let input: Bool = true
139+
140+
XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .integer)), "1")
141+
}
142+
143+
func testBoolFalseEncodedAsStringByInteger() {
144+
let input: Bool = false
145+
146+
XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .integer)), "0")
147+
}
148+
149+
func testBoolTrueEncodedAsCustom() {
150+
let input: Bool = true
151+
152+
XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .custom(true: "❤️", false: "☠️"))), "❤️")
153+
}
154+
155+
func testBoolFalseEncodedAsCustom() {
156+
let input: Bool = false
157+
158+
XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .custom(true: "❤️", false: "☠️"))), "☠️")
159+
}
88160
}

0 commit comments

Comments
 (0)