Skip to content

Commit c1ee8e0

Browse files
committed
Update README for newer CSVWriter
1 parent e7dfaea commit c1ee8e0

File tree

4 files changed

+145
-28
lines changed

4 files changed

+145
-28
lines changed

CodableCSV.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'CodableCSV'
3-
s.version = '0.4.1'
3+
s.version = '0.5.0'
44
s.summary = "Read and write CSV files row-by-row or through Swift's Codable interface."
55

66
s.homepage = 'https://github.com/dehesa/CodableCSV'

README.md

Lines changed: 139 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,50 +14,104 @@ This framework provides:
1414
- CSV encoding & configuration inference (e.g. what field/row delimiters are being used).
1515
- Multiplatform support with no dependencies.
1616

17+
> `CodableCSV` can _encode to_ or _decode from_ `String`s, `Data` blobs, or CSV files (represented by `URL` addresses).
18+
1719
# Usage
1820

19-
To use this library, you need to add it to you project (through SPM or Cocoapods) and import it.
21+
To use this library, you need to:
22+
23+
<ul>
24+
<details><summary>Add <code>CodableCSV</code> to your project.</summary><p>
25+
26+
You can choose to add the library through SPM or Cocoapods:
27+
28+
- [SPM](https://github.com/apple/swift-package-manager/tree/master/Documentation) (Swift Package Manager).
29+
30+
```swift
31+
// swift-tools-version:5.1
32+
import PackageDescription
33+
34+
let package = Package(
35+
/* Your package name, supported platforms, and generated products go here */
36+
dependencies: [ .package(url: "https://github.com/dehesa/CodableCSV.git", .upToNextMinor(from: "0.5.0")) ],
37+
targets: [ .target(name: /* Your target name here */, dependencies: ["CodableCSV"]) ]
38+
)
39+
```
40+
41+
- Cocoapods.
42+
43+
```
44+
pod 'CodableCSV', '~> 0.5.0'
45+
```
46+
47+
</p></details>
48+
49+
<details><summary>Import <code>CodableCSV</code> in the file that needs it.</summary><p>
2050

2151
```swift
2252
import CodableCSV
2353
```
2454

25-
There are two ways to use [CodableCSV](https://github.com/dehesa/CodableCSV):
55+
</p></details>
56+
</ul>
57+
58+
There are two ways to use this library:
2659

2760
1. as an active row-by-row and field-by-field reader or writer.
2861
2. through Swift's `Codable` interface.
2962

30-
> `CodableCSV` can _encode to_ or _decode from_ `String`s, `Data` blobs, or CSV files (represented by `URL` addresses).
31-
3263
## Active Decoding/Encoding
3364

3465
The _active entities_ provide imperative control on how to read or write CSV data.
3566

3667
<ul>
3768
<details><summary><code>CSVReader</code>.</summary><p>
3869

39-
A `CSVReadder` parses CSV data from a given input (`String`, or `Data`, or file) and returns CSV rows as a `String`s array. `CSVReader` can be used at a "high-level", in which case it parses an input completely; or at a lower level, in which each row is decoded when requested.
70+
A `CSVReadder` parses CSV data from a given input (`String`, or `Data`, or file) and returns CSV rows as a `String`s array. `CSVReader` can be used at a _high-level_, in which case it parses an input completely; or at a _low-level_, in which each row is decoded when requested.
4071

4172
- Complete input parsing.
4273

4374
```swift
44-
let file = try CSVReader.parse(input: ...)
45-
// file is of type: CSVReader.Output
75+
let data: Data = ...
76+
let result = try CSVReader.parse(input: data)
77+
78+
// `result` lets you access the CSV headers, all CSV rows, or access a specific row/record. For example:
79+
let headers = result.headers // [String]
80+
let content = result.rows // [[String]]
81+
let fieldA = result[row: 2, field: "Age"] // String? (crash if the row index are out of bounds)
82+
let fieldB = result[row: 3, field: 2] // String (crash if the row or field index are out of bounds)
4683
```
4784

48-
This type of parsing returns a simple structure containing the CSV headers and CSV rows. Additionally it lets you access each field through the header name or the field index.
49-
5085
- Row-by-row parsing.
5186

5287
```swift
53-
let reader = try CSVReader(input: "...")
54-
while let row = try reader.parseRow() {
55-
// Do something with the row: [String]
56-
}
88+
let string = """
89+
numA,numB,numC
90+
1,2,3
91+
4,5,6
92+
"""
93+
let reader = try CSVReader(input: string) { $0.headerStrategy = .firstLine }
94+
95+
let headers = reader.headers // ["numA", "numB", "numC"]
96+
let rowA = try reader.parseRow() // ["1", "2", "3"]
97+
let rowB = try reader.parseRow() // ["4", "5", "6"]
5798
```
5899

59100
Alternatively you can use the `parseRecord()` 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`).
60101

102+
```swift
103+
let reader = try CSVReader(input: string) { $0.headerStrategy = .firstLine }
104+
105+
let headers = reader.headers // ["numA", "numB", "numC"]
106+
107+
let recordA = try reader.parseRecord()
108+
let rowA = recordA.row // ["1", "2", "3"]
109+
let firstField = recordA[0] // "1"
110+
let secondField = recordA["numB"] // "2"
111+
112+
let recordB = try reader.parseRecord()
113+
```
114+
61115
- `Sequence` syntax parsing.
62116

63117
```swift
@@ -108,32 +162,95 @@ let reader = CSVReader(input: ...) {
108162

109163
<details><summary><code>CSVWriter</code>.</summary><p>
110164

111-
A `CSVWriter` encodes CSV information into a specified target (i.e. a `String`, or `Data`, or a file). It can be used at a "high-level", by encoding completely a prepared set of information; or at a lower level, in which case rows or fields can be writen individually.
165+
A `CSVWriter` encodes CSV information into a specified target (i.e. a `String`, or `Data`, or a file). It can be used at a _high-level_, by encoding completely a prepared set of information; or at a _low-level_, in which case rows or fields can be writen individually.
112166

113-
- Full encoding.
167+
- Complete CSV rows serialization.
114168

115169
```swift
116-
let data = try CSVWriter.serialize(rows: [...], into: Data.self)
170+
let input = [
171+
["numA", "numB", "name" ],
172+
["1" , "2" , "Marcos" ],
173+
["4" , "5" , "Marine-Anaïs"]
174+
]
175+
let data = try CSVWriter.serialize(rows: input, into: Data.self)
176+
let string = try CSVWriter.serialize(rows: input, into: String.self)
177+
let file = try CSVWriter.serialize(rows: input, into: URL("~/Desktop/Test.csv")!, append: false)
117178
```
118179

119180
- Row-by-row encoding.
120181

121182
```swift
122-
let writer = try CSVWriter()
123-
for row in customData {
183+
let writer = try CSVWriter(fileURL: URL("~/Desktop/Test.csv")!, append: false)
184+
for row in input {
185+
try writer.write(row: row)
186+
}
187+
try writer.endFile()
188+
```
189+
190+
Alternatively, you may write directly to a buffer in memory and access its `Data` representation.
191+
192+
```swift
193+
let writer = try CSVWriter { $0.headers = input[0] }
194+
for row in input.dropFirst() {
124195
try writer.write(row: row)
125196
}
126-
let outcome = writer.data()
197+
try writer.endFile()
198+
let result = try writer.data()
127199
```
128200

129201
- Field-by-field encoding.
130202

131203
```swift
132-
let writer = try CSVWriter(fileURL: ...)
133-
try writer.write(field: ...)
204+
let writer = try CSVWriter(fileURL: URL("~/Desktop/Test.csv")!, append: false)
205+
try writer.write(row: input[0])
206+
207+
input[1].forEach {
208+
try writer.write(field: field)
209+
}
210+
try writer.endRow()
211+
212+
try writer.write(fields: input[2])
213+
try writer.endRow()
214+
215+
try writer.endFile()
134216
```
135217

136-
#warning("TODO:")
218+
`CSVWriter` has a wealth of low-level imperative APIs, that let you write one field, several fields at a time, end a row, write an empty row, etc.
219+
220+
> Please notice that a CSV requires all rows to have the same amount of fields.
221+
222+
`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.
223+
224+
### Writer Configuration
225+
226+
`CSVWriter` accepts the following configuration properties:
227+
228+
- `delimiters` (default: `(field: ",", row: "\n")`) specify the field and row delimiters.
229+
230+
CSV fields are separated within a row with _field delimiters_ (commonly a "comma"). CSV rows are separated through _row delimiters_ (commonly a "line feed"). You can specify any unicode scalar, `String` value, or `nil` for unknown delimiters.
231+
232+
- `headers` (default: `[]`) indicates whether the CSV data has a header row or not.
233+
234+
CSV files may contain an optional header row at the very beginning. If this configuration value is empty, no header row is writen.
235+
236+
- `encoding` (default: `nil`) specify the CSV file encoding.
237+
238+
This `String.Encoding` value specify how each underlying byte is represented (e.g. `.utf8`, `.utf32littleEndian`, etc.). If it is `nil`, the library will try to figure out the file encoding through the file's [Byte Order Marker](https://en.wikipedia.org/wiki/Byte_order_mark). If the file doesn't contain a BOM, `.utf8` is presumed.
239+
240+
- `bomStrategy` (default: `.convention`) indicates whether a Byte Order Marker will be included at the beginning of the CSV representation.
241+
242+
The OS convention is that BOMs are never writen, except when `.utf16`, `.utf32`, or `.unicode` string encodings are specified. You could however indicate that you always want the BOM writen (`.always`) or that is never writer (`.never`).
243+
244+
The configuration values are set during initialization and can be passed to the `CSWriter` instance through a structure or with a convenience closure syntax:
245+
246+
```swift
247+
let writer = CSWriter(fileURL: ...) {
248+
$0.delimiters.row = "\r\n"
249+
$0.headers = ["Name", "Age", "Pet"]
250+
$0.encoding = .utf8
251+
$0.bomStrategy = .never
252+
}
253+
```
137254

138255
</p></details>
139256
</ul>

Sources/Active/Writer/WriterConfiguration.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ extension CSVWriter {
2121
self.delimiters = (field: ",", row: "\n")
2222
self.headers = .init()
2323
self.encoding = nil
24-
self.bomStrategy = .standard
24+
self.bomStrategy = .convention
2525
}
2626
}
2727
}
@@ -35,7 +35,7 @@ extension Strategy {
3535
/// - `.utf16` and `.unicode`, in which case the BOM for UTF 16 Big endian encoding will be used.
3636
/// - `.utf32` in which ase the BOM for UTF 32 Big endian encoding will be used.
3737
/// - For any other case, no BOM will be written.
38-
case standard
38+
case convention
3939
/// Always writes a BOM when possible (i.e. for Unicode encodings).
4040
case always
4141
/// Never writes a BOM.

Sources/Active/Writer/WriterEncoding.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ internal extension Strategy.BOM {
2121
case (.always, .utf8): return BOM.UTF8
2222
case (.always, .utf16LittleEndian): return BOM.UTF16.littleEndian
2323
case (.always, .utf16BigEndian),
24-
(.always, .utf16), (.standard, .utf16),
25-
(.always, .unicode), (.standard, .unicode): return BOM.UTF16.bigEndian
24+
(.always, .utf16), (.convention, .utf16),
25+
(.always, .unicode), (.convention, .unicode): return BOM.UTF16.bigEndian
2626
case (.always, .utf32LittleEndian): return BOM.UTF32.littleEndian
2727
case (.always, .utf32BigEndian),
28-
(.always, .utf32), (.standard, .utf32): return BOM.UTF32.bigEndian
28+
(.always, .utf32), (.convention, .utf32): return BOM.UTF32.bigEndian
2929
default: return .init()
3030
}
3131
}

0 commit comments

Comments
 (0)