Skip to content

Commit c6f056e

Browse files
committed
Improve reader delimiter handling
1 parent 4eec6af commit c6f056e

File tree

10 files changed

+79
-41
lines changed

10 files changed

+79
-41
lines changed

.github/workflows/tests.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
name: Tests
1+
name: Unit Tests
22
on: [ push, pull_request ]
33

44
jobs:
5-
tests_on_macOS:
6-
name: Tests on macOS
5+
unittests_on_macOS:
6+
name: Unit tests on macOS
77
runs-on: macos-latest
88
steps:
99
- uses: actions/checkout@v2
1010
- name: Build
1111
run: swift build -v
1212
- name: Run tests
13-
run: swift test -v
13+
run: swift test --filter UnitTests -v
1414

15-
tests_on_Ubuntu:
16-
name: Tests on Ubuntu
15+
unittests_on_Ubuntu:
16+
name: Unit tests on Ubuntu
1717
runs-on: ubuntu-latest
1818
container:
1919
image: swift:latest
@@ -22,4 +22,4 @@ jobs:
2222
- name: Build
2323
run: swift build -v
2424
- name: Run tests
25-
run: swift test -v --enable-test-discovery
25+
run: swift test --filter UnitTests --enable-test-discovery -v

Package.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@ let package = Package(
88
],
99
products: [
1010
.library(name: "CodableCSV", targets: ["CodableCSV"]),
11-
// .executable(name: "CodableCSV-Benchmarks", targets: ["CodableCSV-Benchmarks"])
1211
],
1312
dependencies: [],
1413
targets: [
1514
.target(name: "CodableCSV", dependencies: [], path: "sources"),
16-
.testTarget(name: "CodableCSVTests", dependencies: ["CodableCSV"], path: "tests"),
17-
// .target(name: "CodableCSV-Benchmarks", dependencies: ["CodableCSV"], path: "benchmarks"),
15+
.testTarget(name: "UnitTests", dependencies: ["CodableCSV"], path: "tests"),
16+
.testTarget(name: "Benchmarks", dependencies: ["CodableCSV"], path: "benchmarks")
1817
]
1918
)

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@
1111

1212
[CodableCSV](https://github.com/dehesa/CodableCSV) provides:
1313

14-
- Row-by-row CSV reader & writer.
15-
- Codable interface.
16-
- Support for multiple inputs/outputs: `String`s, `Data` blobs, and CSV files (represented by `URL` addresses).
17-
- CSV encoding & configuration inference (e.g. what field/row delimiters are being used).
14+
- Imperative CSV reader/writer (row-by-row and/or field-by-field).
15+
- Declarative `Codable` encoder/decoder.
16+
- Support for multiple inputs/outputs: `String`s, `Data` blobs, and `URL`s.
17+
- Support for multiple string encodings and Byte Order Markers (BOM).
18+
- Extremely configurable: delimiters, escaping scalar, trim strategy, presampling, and numerous codable strategies.
1819
- Multiplatform support with no dependencies.
1920

20-
> The Swift standard library and Foundation are considered implicit requirements.
21+
> The Swift Standard Library and Foundation are considered implicit requirements.
2122
2223
# Usage
2324

@@ -64,7 +65,7 @@ import CodableCSV
6465

6566
There are two ways to use this library:
6667

67-
1. imperatively, as a row-by-row and field-by-field reader or writer.
68+
1. imperatively, as a row-by-row and field-by-field reader/writer.
6869
2. declaratively, through Swift's `Codable` interface.
6970

7071
## Imperative Reader/Writer

benchmarks/PerformanceTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import XCTest
2+
import CodableCSV
3+
4+
/// Tests checking the regular encoding usage.
5+
final class PerformanceTests: XCTestCase {
6+
override func setUp() {
7+
self.continueAfterFailure = false
8+
}
9+
}
10+
11+
extension PerformanceTests {
12+
/// Tests the encoding of an empty.
13+
func testEmptyFile() throws {
14+
// XCTSkipUnless(<#T##expression: Bool##Bool#>, <#T##message: String?##String?#>)
15+
#if !DEBUG
16+
print("Hello RELEASE")
17+
#endif
18+
}
19+
}

benchmarks/main.swift

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

sources/imperative/reader/internal/ReaderInternals.swift

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,28 +54,29 @@ extension CSVReader {
5454
/// - throws: `CSVError<CSVReader>` exclusively.
5555
init(configuration: Configuration, decoder: ScalarDecoder, buffer: ScalarBuffer) throws {
5656
// 1. Figure out the field and row delimiters.
57-
switch (configuration.delimiters.field.rawValue, configuration.delimiters.row.rawValue) {
58-
case (nil, nil):
57+
let (field, row) = (configuration.delimiters.field.rawValue, configuration.delimiters.row.rawValue)
58+
switch (field.isEmpty, row.isEmpty) {
59+
case (true, true):
5960
self.delimiters = try CSVReader.inferDelimiters(decoder: decoder, buffer: buffer)
60-
case (nil, let row):
61+
case (true, false):
6162
self.delimiters = try CSVReader.inferFieldDelimiter(rowDelimiter: row, decoder: decoder, buffer: buffer)
62-
case (let field, nil):
63+
case (false, true):
6364
self.delimiters = try CSVReader.inferRowDelimiter(fieldDelimiter: field, decoder: decoder, buffer: buffer)
64-
case (let field, let row) where !field.elementsEqual(row):
65+
case (false, false) where field.elementsEqual(row):
66+
throw Error.invalidDelimiters(field)
67+
default:
6568
self.delimiters = (.init(field), .init(row))
66-
case (let delimiter, _):
67-
throw Error.invalidDelimiters(delimiter)
6869
}
6970
// 2. Set the escaping scalar.
7071
self.escapingScalar = configuration.escapingStrategy.scalar
7172
// 3. Set the trim characters set.
7273
self.trimCharacters = configuration.trimStrategry
7374
// 4. Ensure trim character set doesn't contain the field delimiter.
74-
guard delimiters.field.allSatisfy({ !self.trimCharacters.contains($0) }) else {
75+
guard self.delimiters.field.allSatisfy({ !self.trimCharacters.contains($0) }) else {
7576
throw Error.invalidTrimCharacters(self.trimCharacters, delimiter: configuration.delimiters.field.rawValue)
7677
}
7778
// 5. Ensure trim character set doesn't contain the row delimiter.
78-
guard delimiters.row.allSatisfy({ !self.trimCharacters.contains($0) }) else {
79+
guard self.delimiters.row.allSatisfy({ !self.trimCharacters.contains($0) }) else {
7980
throw Error.invalidTrimCharacters(self.trimCharacters, delimiter: configuration.delimiters.row.rawValue)
8081
}
8182
// 6. Ensure trim character set does not include escaping scalar

sources/imperative/writer/Writer.swift

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,14 @@ extension CSVWriter {
175175
}
176176

177177
// MARK: -
178-
179178
extension CSVWriter {
179+
/// Writes the given delimiter using the instance's `encoder`.
180+
/// - parameter delimiter: The array of `Unicode.Scalar` representing a delimiter.
181+
/// - throws: `CSVError<CSVWriter>` exclusively.
182+
@inline(__always) private func lowlevelWrite(delimiter: [Unicode.Scalar]) throws {
183+
try delimiter.forEach { try self.encoder($0) }
184+
}
185+
180186
/// Writes the given `String` into the receiving writer's stream.
181187
/// - parameter field: The field to be checked for characters to escape and subsequently written.
182188
/// - throws: `CSVError<CSVWriter>` exclusively.
@@ -239,13 +245,6 @@ extension CSVWriter {
239245

240246
try result.forEach { try self.encoder($0) }
241247
}
242-
243-
/// Writes the given delimiter using the instance's `encoder`.
244-
/// - parameter delimiter: The array of `Unicode.Scalar` representing a delimiter.
245-
/// - throws: `CSVError<CSVWriter>` exclusively.
246-
@inline(__always) private func lowlevelWrite(delimiter: [Unicode.Scalar]) throws {
247-
try delimiter.forEach { try self.encoder($0) }
248-
}
249248
}
250249

251250
fileprivate extension CSVWriter.Error {

sources/imperative/writer/internal/WriterInternals.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ extension CSVWriter {
4444
if field.isEmpty || row.isEmpty {
4545
throw Error.invalidEmptyDelimiter()
4646
} else if field.elementsEqual(row) {
47-
throw Error.invalidDelimiters(field)
47+
throw Error.invalidSameDelimiters(field)
4848
} else {
4949
self.delimiters = (.init(field), .init(row))
5050
}
@@ -67,7 +67,7 @@ fileprivate extension CSVWriter.Error {
6767

6868
/// Error raised when the field and row delimiters are the same.
6969
/// - parameter delimiter: The indicated field and row delimiters.
70-
static func invalidDelimiters(_ delimiter: String.UnicodeScalarView) -> CSVError<CSVWriter> {
70+
static func invalidSameDelimiters(_ delimiter: String.UnicodeScalarView) -> CSVError<CSVWriter> {
7171
.init(.invalidConfiguration,
7272
reason: "The field and row delimiters cannot be the same.",
7373
help: "Set different delimiters for field and rows.",

tests/CodableTests/DecodingSinglesTests.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import XCTest
2-
@testable import CodableCSV
2+
import CodableCSV
33

44
/// Tests for empty or single value CSVs.
55
final class DecodingSinglesTests: XCTestCase {
@@ -8,6 +8,29 @@ final class DecodingSinglesTests: XCTestCase {
88
}
99
}
1010

11+
extension DecodingSinglesTests {
12+
/// The coding key used to identify encoding/decoding containers.
13+
private struct IndexKey: CodingKey {
14+
/// The integer value of the coding key.
15+
let index: Int
16+
/// Designated initializer.
17+
init(_ index: Int) { self.index = index }
18+
19+
init?(intValue: Int) {
20+
guard intValue >= 0 else { return nil }
21+
self.init(intValue)
22+
}
23+
24+
init?(stringValue: String) {
25+
guard let intValue = Int(stringValue) else { return nil }
26+
self.init(intValue)
27+
}
28+
29+
var stringValue: String { String(self.index) }
30+
var intValue: Int? { self.index }
31+
}
32+
}
33+
1134
extension DecodingSinglesTests {
1235
/// Tests the decoding of a completely empty file.
1336
func testEmptyFile() throws {

tests/CodableTests/DecodingWrappersTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import XCTest
2-
@testable import CodableCSV
2+
import CodableCSV
33

44
/// Tests for the decodable car dealer data.
55
final class DecodingWrappersTests: XCTestCase {

0 commit comments

Comments
 (0)