Skip to content

Commit 6d200bb

Browse files
committed
Expand reader convenience collections & tests added
1 parent bc90c5a commit 6d200bb

File tree

6 files changed

+352
-179
lines changed

6 files changed

+352
-179
lines changed

README.md

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,12 @@ import CodableCSV
6464

6565
There are two ways to use this library:
6666

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

70-
## Active decoding/encoding
70+
## Imperative Reader/Writer
7171

72-
The _active entities_ provide imperative control on how to read or write CSV data.
72+
The following types provide imperative control on how to read/write CSV data.
7373

7474
<ul>
7575
<details><summary><code>CSVReader</code>.</summary><p>
@@ -81,40 +81,52 @@ A `CSVReadder` parses CSV data from a given input (`String`, or `Data`, or file)
8181
```swift
8282
let data: Data = ...
8383
let result = try CSVReader.decode(input: data)
84-
85-
// `result` lets you access the CSV headers, all CSV rows, or access a specific row/record. For example:
86-
let headers = result.headers // [String]
87-
let content = result.rows // [[String]]
88-
let fieldA = result[row: 2, field: "Age"] // String? (crash if the row index are out of bounds)
89-
let fieldB = result[row: 3, field: 2] // String (crash if the row or field index are out of bounds)
84+
```
85+
Once the input is completely parsed, you can choose how to access the decoded data:
86+
```swift
87+
let headers: [String] = result.headers
88+
// Access the CSV rows (i.e. raw [String] values)
89+
let rows = result.rows
90+
let row = result[0]
91+
// Access the CSV record (i.e. convenience structure over a single row)
92+
let records = result.records
93+
let record = result[record: 0]
94+
// Access the CSV columns through indices or header values.
95+
let columns = result.columns
96+
let column = result[column: 0]
97+
let column = result[column: "Name"]
98+
// Access fields through indices or header values.
99+
let fieldB: String = result[row: 3, column: 2]
100+
let fieldA: String? = result[row: 2, column: "Age"]
90101
```
91102

92103
- Row-by-row parsing.
93104

94105
```swift
95-
let string = """
96-
numA,numB,numC
97-
1,2,3
98-
4,5,6
99-
"""
100106
let reader = try CSVReader(input: string) { $0.headerStrategy = .firstLine }
101-
102-
let headers = reader.headers // ["numA", "numB", "numC"]
103-
let rowA = try reader.readRow() // ["1", "2", "3"]
104-
let rowB = try reader.readRow() // ["4", "5", "6"]
107+
let rowA = try reader.readRow()
108+
```
109+
Parse a row at a time, till `nil` is return; or exit the scope and the reader will clean up all used memory.
110+
```swift
111+
// Let's assume the input is:
112+
let string = "numA,numB,numC\n1,2,3\n4,5,6\n7,8,9"
113+
// The headers property can be accessed at any point after initialization.
114+
let headers: [String] = reader.headers // ["numA", "numB", "numC"]
115+
// Keep querying rows till `nil` is received.
116+
guard let rowB = try reader.readRow(), // ["4", "5", "6"]
117+
let rowC = try reader.readRow() /* ["7", "8", "9"] */ else { ... }
105118
```
106119

107120
Alternatively you can use the `readRecord()` function which also returns the next CSV row, but it wraps the result in a convenience structure. This structure lets you access each field with the header name (as long as the `headerStrategy` is marked with `.firstLine`).
108121

109122
```swift
110123
let reader = try CSVReader(input: string) { $0.headerStrategy = .firstLine }
111-
112124
let headers = reader.headers // ["numA", "numB", "numC"]
113125

114126
let recordA = try reader.readRecord()
115-
let rowA = recordA.row // ["1", "2", "3"]
116-
let firstField = recordA[0] // "1"
117-
let secondField = recordA["numB"] // "2"
127+
let rowA = recordA.row // ["1", "2", "3"]
128+
let fieldA = recordA[0] // "1"
129+
let fieldB = recordA["numB"] // "2"
118130

119131
let recordB = try reader.readRecord()
120132
```
@@ -130,7 +142,7 @@ A `CSVReadder` parses CSV data from a given input (`String`, or `Data`, or file)
130142

131143
Please note the `Sequence` syntax (i.e. `IteratorProtocol`) doesn't throw errors; therefore if the CSV data is invalid, the previous code will crash. If you don't control the CSV data origin, use `readRow()` instead.
132144

133-
### Reader configuration
145+
### Reader Configuration
134146

135147
`CSVReader` accepts the following configuration properties:
136148

@@ -232,7 +244,7 @@ A `CSVWriter` encodes CSV information into a specified target (i.e. a `String`,
232244

233245
`CSVWriter` enforces this by throwing an error when you try to write more the expected amount of fields, or filling a row with empty fields when you call `endRow()` but not all fields has been written.
234246

235-
### Writer configuration
247+
### Writer Configuration
236248

237249
`CSVWriter` accepts the following configuration properties:
238250

@@ -298,7 +310,7 @@ You can get all the information by simply printing the error or calling the `loc
298310
</p></details>
299311
</ul>
300312
301-
## `Codable`'s decoder/encoder
313+
## Declarative Decoder/Encoder
302314
303315
The encoders/decoders provided by this library let you use Swift's `Codable` declarative approach to encode/decode CSV data.
304316
@@ -321,9 +333,9 @@ let content: [Student] = try decoder.decode([Student].self, from: URL("~/Desktop
321333
322334
If you are dealing with a big CSV file, it is preferred to used direct file decoding, a `.sequential` or `.unrequested` buffering strategy, and set *presampling* to false; since then memory usage is drastically reduced.
323335
324-
### Decoder configuration
336+
### Decoder Configuration
325337
326-
The decoding process can be tweaked by specifying configuration values at initialization time. `CSVDecoder` accepts the [same configuration values as `CSVReader`](#Reader-configuration) plus the following ones:
338+
The decoding process can be tweaked by specifying configuration values at initialization time. `CSVDecoder` accepts the [same configuration values as `CSVReader`](#Reader-Configuration) plus the following ones:
327339
328340
- `nilStrategy` (default: `.empty`) indicates how the `nil` *concept* (absence of value) is represented on the CSV.
329341
@@ -377,9 +389,9 @@ try encoder.encode(value, into: URL("~/Desktop/Students.csv"))
377389
378390
If you are dealing with a big CSV content, it is preferred to use direct file encoding and a `.sequential` or `.assembled` buffering strategy, since then memory usage is drastically reduced.
379391
380-
### Encoder configuration
392+
### Encoder Configuration
381393
382-
The encoding process can be tweaked by specifying configuration values. `CSVEncoder` accepts the [same configuration values as `CSVWriter`](#Writer-configuration) plus the following ones:
394+
The encoding process can be tweaked by specifying configuration values. `CSVEncoder` accepts the [same configuration values as `CSVWriter`](#Writer-Configuration) plus the following ones:
383395
384396
- `nilStrategy` (default: `.empty`) indicates how the `nil` *concept* (absence of value) is represented on the CSV.
385397

sources/Active/Reader/Reader.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public final class CSVReader: IteratorProtocol, Sequence {
6565

6666
extension CSVReader {
6767
/// Advances to the next row and returns it, or `nil` if no next row exists.
68-
/// - warning: If the CSV file being parsed contains invalid characters, this function will crash. For safer parsing use `readRow()`.
68+
/// - warning: If the CSV file being parsed contains invalid characters, this function will crash. For safer parsing use `readRow()` or `readRecord()`.
6969
/// - seealso: readRow()
7070
@inlinable public func next() -> [String]? {
7171
return try! self.readRow()

sources/Active/Reader/ReaderAPI.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ extension CSVReader {
116116
/// - parameter configuration: Recipe detailing how to parse the CSV data (i.e. delimiters, date strategy, etc.).
117117
/// - throws: `CSVError<CSVReader>` exclusively.
118118
/// - returns: Tuple with the CSV headers (empty if none) and all records within the CSV file.
119-
public static func decode<S>(input: S, configuration: Configuration = .init()) throws -> Output where S:StringProtocol {
119+
public static func decode<S>(input: S, configuration: Configuration = .init()) throws -> FileView where S:StringProtocol {
120120
let reader = try CSVReader(input: input, configuration: configuration)
121121
let lookup = try reader.headers.lookupDictionary(onCollision: Error.invalidHashableHeader)
122122

@@ -133,7 +133,7 @@ extension CSVReader {
133133
/// - parameter configuration: Recipe detailing how to parse the CSV data (i.e. delimiters, date strategy, etc.).
134134
/// - throws: `CSVError<CSVReader>` exclusively.
135135
/// - returns: Tuple with the CSV headers (empty if none) and all records within the CSV file.
136-
public static func decode(input: Data, configuration: Configuration = .init()) throws -> Output {
136+
public static func decode(input: Data, configuration: Configuration = .init()) throws -> FileView {
137137
let reader = try CSVReader(input: input, configuration: configuration)
138138
let lookup = try reader.headers.lookupDictionary(onCollision: Error.invalidHashableHeader)
139139

@@ -150,7 +150,7 @@ extension CSVReader {
150150
/// - parameter configuration: Recipe detailing how to parse the CSV data (i.e. delimiters, date strategy, etc.).
151151
/// - throws: `CSVError<CSVReader>` exclusively.
152152
/// - returns: Tuple with the CSV headers (empty if none) and all records within the CSV file.
153-
public static func decode(input: URL, configuration: Configuration = .init()) throws -> Output {
153+
public static func decode(input: URL, configuration: Configuration = .init()) throws -> FileView {
154154
let reader = try CSVReader(input: input, configuration: configuration)
155155
let lookup = try reader.headers.lookupDictionary(onCollision: Error.invalidHashableHeader)
156156

@@ -170,7 +170,7 @@ extension CSVReader {
170170
/// - parameter configuration: Default configuration values for the `CSVReader`.
171171
/// - throws: `CSVError<CSVReader>` exclusively.
172172
/// - returns: Tuple with the CSV headers (empty if none) and all records within the CSV file.
173-
@inlinable public static func decode<S>(input: S, setter: (_ configuration: inout Configuration)->Void) throws -> Output where S:StringProtocol {
173+
@inlinable public static func decode<S>(input: S, setter: (_ configuration: inout Configuration)->Void) throws -> FileView where S:StringProtocol {
174174
var configuration = Configuration()
175175
setter(&configuration)
176176
return try CSVReader.decode(input: input, configuration: configuration)
@@ -182,7 +182,7 @@ extension CSVReader {
182182
/// - parameter configuration: Default configuration values for the `CSVReader`.
183183
/// - throws: `CSVError<CSVReader>` exclusively.
184184
/// - returns: Tuple with the CSV headers (empty if none) and all records within the CSV file.
185-
@inlinable public static func decode(input: Data, setter: (_ configuration: inout Configuration)->Void) throws -> Output {
185+
@inlinable public static func decode(input: Data, setter: (_ configuration: inout Configuration)->Void) throws -> FileView {
186186
var configuration = Configuration()
187187
setter(&configuration)
188188
return try CSVReader.decode(input: input, configuration: configuration)
@@ -194,7 +194,7 @@ extension CSVReader {
194194
/// - parameter configuration: Default configuration values for the `CSVReader`.
195195
/// - throws: `CSVError<CSVReader>` exclusively.
196196
/// - returns: Tuple with the CSV headers (empty if none) and all records within the CSV file.
197-
@inlinable public static func decode(input: URL, setter: (_ configuration: inout Configuration)->Void) throws -> Output {
197+
@inlinable public static func decode(input: URL, setter: (_ configuration: inout Configuration)->Void) throws -> FileView {
198198
var configuration = Configuration()
199199
setter(&configuration)
200200
return try CSVReader.decode(input: input, configuration: configuration)
@@ -231,6 +231,9 @@ fileprivate extension CSVReader.Error {
231231
// MARK: - Deprecations
232232

233233
extension CSVReader {
234+
@available(*, deprecated, renamed: "FileView")
235+
public typealias Output = FileView
236+
234237
@available(*, deprecated, renamed: "readRecord()")
235238
public func parseRecord() throws -> Record? {
236239
try self.readRecord()

0 commit comments

Comments
 (0)