Skip to content

Allow Boolean encoding to be governed by new BoolEncodingStrategy #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Sources/SwiftCSVEncoder/CSVEncodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ extension Double: CSVEncodable {

extension Bool: CSVEncodable {
public func encode(configuration: CSVEncoderConfiguration) -> String {
self == true ? "true" : "false"
let (trueValue, falseValue) = configuration.boolEncodingStrategy.encodingValues

return self == true ? trueValue : falseValue
}
}

Expand Down
51 changes: 48 additions & 3 deletions Sources/SwiftCSVEncoder/CSVEncoderConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,20 @@ public struct CSVEncoderConfiguration {
/// The default strategy is the ``DateEncodingStrategy-swift.enum/iso8601`` strategy.
public private(set) var dateEncodingStrategy: DateEncodingStrategy = .iso8601

/// The strategy to use when encoding Boolean values.
///
/// The default strategy is the ``BoolEncodingStrategy-swift.enum/trueFalse`` strategy.
public private(set) var boolEncodingStrategy: BoolEncodingStrategy = .trueFalse

/// Creates a new instance of ``CSVEncoderConfiguration`` with the requisite configuration values
/// - Parameter dateEncodingStrategy: The strategy to use when encoding dates
public init(dateEncodingStrategy: DateEncodingStrategy) {
/// - Parameter boolEncodingStrategy: The strategy to use when encoding Boolean values
public init(
dateEncodingStrategy: DateEncodingStrategy = .iso8601,
boolEncodingStrategy: BoolEncodingStrategy = .trueFalse
) {
self.dateEncodingStrategy = dateEncodingStrategy
self.boolEncodingStrategy = boolEncodingStrategy
}

/// The strategy to use when encoding `Date` objects for CSV output.
Expand All @@ -34,10 +44,45 @@ public struct CSVEncoderConfiguration {
/// - Parameter custom: A closure that receives the `Date` to encode, and returns the `String` to include in the CSV output.
case custom(@Sendable (Date) -> String)
}


/// The strategy to use when encoding `Bool` objects for CSV output.
public enum BoolEncodingStrategy {
/// The strategy that emits `true` and `false` for Boolean fields
case trueFalse
/// The strategy that emits `TRUE` and `FALSE` for Boolean fields
case trueFalseUppercase
/// The strategy that emite `yes` and `no` for Boolean fields
case yesNo
/// The strategy that emits `YES` and `NO` for Boolean fields
case yesNoUppercase
/// The strategy that emits `1` and `0` for Boolean fields
case integer
/// A custom strategy that emitss the custom supplied strings for Boolean fields
case custom(true: String, false: String)
}

/// A default set of configuration values.
///
/// This configuration set will be used when a ``CSVTable`` is initialized with setting a custom
/// configuration.
public static var `default`: CSVEncoderConfiguration = .init(dateEncodingStrategy: .iso8601)
public static var `default`: CSVEncoderConfiguration = CSVEncoderConfiguration()
}

internal extension CSVEncoderConfiguration.BoolEncodingStrategy {
var encodingValues: (String, String) {
switch self {
case .trueFalse:
return ("true", "false")
case .trueFalseUppercase:
return ("TRUE", "FALSE")
case .yesNo:
return ("yes", "no")
case .yesNoUppercase:
return ("YES", "NO")
case .integer:
return ("1", "0")
case .custom(let trueValue, let falseValue):
return (trueValue, falseValue)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ CSVColumn("Description", \.description)

``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.

``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.

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:

```csv
Expand Down
76 changes: 74 additions & 2 deletions Tests/SwiftCSVEncoderTests/CSVEncodableTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,87 @@ final class CSVEncodableTests: XCTestCase {
XCTAssertEqual(date.encode(configuration: configuration), "Custom returned value")
}

func testBoolTrueEncodedAsString() {
func testBoolTrueEncodedAsStringByDefault() {
let input: Bool = true

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

func testBoolFalseEncodedAsString() {
func testBoolFalseEncodedAsStringByDefault() {
let input: Bool = false

XCTAssertEqual(input.encode(configuration: .default), "false")
}

func testBoolTrueEncodedAsStringByTrueFalse() {
let input: Bool = true

XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .trueFalse)), "true")
}

func testBoolFalseEncodedAsStringByTrueFalse() {
let input: Bool = false

XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .trueFalse)), "false")
}

func testBoolTrueEncodedAsStringByTrueFalseUppercase() {
let input: Bool = true

XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .trueFalseUppercase)), "TRUE")
}

func testBoolFalseEncodedAsStringByTrueFalseUppercase() {
let input: Bool = false

XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .trueFalseUppercase)), "FALSE")
}

func testBoolTrueEncodedAsStringByYesNo() {
let input: Bool = true

XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .yesNo)), "yes")
}

func testBoolFalseEncodedAsStringByYesNo() {
let input: Bool = false

XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .yesNo)), "no")
}

func testBoolTrueEncodedAsStringByYesNoUppercase() {
let input: Bool = true

XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .yesNoUppercase)), "YES")
}

func testBoolFalseEncodedAsStringByYesNoUppercase() {
let input: Bool = false

XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .yesNoUppercase)), "NO")
}

func testBoolTrueEncodedAsStringByInteger() {
let input: Bool = true

XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .integer)), "1")
}

func testBoolFalseEncodedAsStringByInteger() {
let input: Bool = false

XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .integer)), "0")
}

func testBoolTrueEncodedAsCustom() {
let input: Bool = true

XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .custom(true: "❤️", false: "☠️"))), "❤️")
}

func testBoolFalseEncodedAsCustom() {
let input: Bool = false

XCTAssertEqual(input.encode(configuration: CSVEncoderConfiguration(boolEncodingStrategy: .custom(true: "❤️", false: "☠️"))), "☠️")
}
}