smarter_csv 1.16.0 → 1.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '082f1c3a20d98a975bc3992ab4dd71d30a3008308f8c8dcbc116f60a2206b824'
4
- data.tar.gz: 936107710f1588d54b7655e96781a677567d83a302269b1ddf7f2e63d0a31b63
3
+ metadata.gz: 043745aedb1c63fd4a044b9ae46bb8e5d98324c14e609214ee3d895acfd5f501
4
+ data.tar.gz: c39a10521b767daf51887278c9020c9ff6d8d93c32c5ec3f95a17ec575ebdab5
5
5
  SHA512:
6
- metadata.gz: 212ba64f768895c1250cbae766a6dd91dfe7e83e6ca856b5d94008c8c295bda7a1a1bdaa88e71b25fcacee46fe1400f68fe2574173043e0c1537fa40be90fb9d
7
- data.tar.gz: 4dfdc5ce2275b3b4a4e82f2abcc45a112a4eb5e90e3324218688317ed72eadde9b59d64ffc9b4bbf108fcd8e36c702172b22600b766762d393831c3404d8a560
6
+ metadata.gz: 5f1d125138443f02e0276e964dac9e584b996de6acafe8b3856316852a38220094e69a2c14302f922bcc93b6d23cc594bbf926940ccd70a2bd65ab08c5a18b49
7
+ data.tar.gz: '0929051996781c8643c0239556d123c840e7041d9c12f7d867e3800dfb2c2eb92e6f6fb77b5fc08660a36f34f470cb826f255943fa445d3e29d231e648da51b4'
data/.rspec CHANGED
@@ -1 +1,3 @@
1
1
  --require spec_helper
2
+ --color
3
+ --format documentation
data/CHANGELOG.md CHANGED
@@ -1,10 +1,54 @@
1
1
 
2
2
  # SmarterCSV 1.x Change Log
3
3
 
4
+ ## 1.16.1 (2026-03-16) — Bug Fixes & New Features
5
+
6
+ RSpec tests: **1,247 → 1,410** (+163 tests)
7
+
8
+ ### New Features
9
+
10
+ * **`SmarterCSV.errors`** — class-level error access after any `process`, `parse`, `each`, or `each_chunk` call.
11
+ Exposes the same `reader.errors` hash without requiring access to the `Reader` instance.
12
+ Errors are cleared at the start of each call and stored per-thread (safe in Puma/Sidekiq).
13
+
14
+ ```ruby
15
+ # Previously — required Reader instance to access errors
16
+ reader = SmarterCSV::Reader.new('data.csv', on_bad_row: :skip)
17
+ reader.process
18
+ puts reader.errors[:bad_row_count]
19
+
20
+ # Now — works with the class-level API too
21
+ SmarterCSV.process('data.csv', on_bad_row: :skip)
22
+ puts SmarterCSV.errors[:bad_row_count]
23
+ ```
24
+
25
+ > **Note:** `SmarterCSV.errors` only surfaces errors from the **most recent run on the
26
+ > current thread**. In a multi-threaded environment (Puma, Sidekiq), each thread maintains
27
+ > its own error state independently. If you call `SmarterCSV.process` twice in the same
28
+ > thread, the second call's errors replace the first's. For long-running or complex
29
+ > pipelines where you need to aggregate errors across multiple files, use the Reader API.
30
+ >
31
+ > ⚠️ **Fibers:** `SmarterCSV.errors` uses `Thread.current` for storage, which is **shared
32
+ > across all fibers running in the same thread**. If you process CSV files concurrently
33
+ > in fibers (e.g. with `Async`, `Falcon`, or manual `Fiber` scheduling), `SmarterCSV.errors`
34
+ > may return stale or wrong results. **Use `SmarterCSV::Reader` directly** — errors are
35
+ > scoped to the reader instance and are always correct regardless of fiber context.
36
+
37
+ ### Bug Fixes
38
+
39
+ * fixed [#325](https://github.com/tilo/smarter_csv/issues/325): `col_sep` in quoted headers was handled incorrectly; Thanks to Paho Lurie-Gregg.
40
+ * fixed issue with quoted numeric fields that were not converted to numeric
41
+
42
+ ### Tests
43
+
44
+ * Added 163 tests covering new features and corner cases
45
+
4
46
  ## 1.16.0 (2026-03-12) — Minor Breaking Change
5
47
 
6
48
  [Full details](docs/releases/1.16.0/changes.md) · [Benchmarks](docs/releases/1.16.0/benchmarks.md) · [Performance notes](docs/releases/1.16.0/performance_notes.md)
7
49
 
50
+ RSpec tests: **714 → 1,247** (+533 tests)
51
+
8
52
  ### Minor Breaking Change
9
53
 
10
54
  New option **`quote_boundary:`**
data/CONTRIBUTORS.md CHANGED
@@ -64,3 +64,4 @@ A Big Thank you to everyone who filed issues, sent comments, and who contributed
64
64
  * [Mark Bumiller](https://github.com/makrsmark)
65
65
  * [Tophe](https://github.com/tophe)
66
66
  * [Dom Lebron](https://github.com/biglebronski)
67
+ * [Paho Lurie-Gregg](https://github.com/paholg)
data/README.md CHANGED
@@ -3,9 +3,14 @@
3
3
 
4
4
  ![Gem Version](https://img.shields.io/gem/v/smarter_csv) [![codecov](https://codecov.io/gh/tilo/smarter_csv/branch/main/graph/badge.svg?token=1L7OD80182)](https://codecov.io/gh/tilo/smarter_csv) [View on RubyGems](https://rubygems.org/gems/smarter_csv) [View on RubyToolbox](https://www.ruby-toolbox.com/search?q=smarter_csv)
5
5
 
6
- SmarterCSV is a high-performance CSV ingestion and generation for Ruby, focused on fastest end-to-end CSV ingestion — not just parsing.
6
+ SmarterCSV is a high-performance CSV ingestion and generation for Ruby, focused on fast end-to-end CSV ingestion of real-world data no silent failures, no surprises, not just tokenization.
7
7
 
8
- ⭐ If SmarterCSV saved you hours of import time, please star the repo.
8
+ ⭐ If SmarterCSV saved you hours of import time, please star the repo, and consider sponsoring this project.
9
+
10
+ Ruby's built-in CSV library has 10 documented failure modes that can silently corrupt or lose data — duplicate headers, blank header cells, extra columns, BOMs, whitespace, encoding issues, and more — all without raising an exception.
11
+ SmarterCSV handles 8 our of 10 by default, and the remaining 2 with a single option each.
12
+
13
+ > See [**Ruby CSV Pitfalls**](docs/ruby_csv_pitfalls.md) for 10 ways `CSV.read` silently corrupts or loses data, and how SmarterCSV handles them.
9
14
 
10
15
  Beyond raw speed, SmarterCSV is designed to provide a significantly more convenient and developer-friendly interface than traditional CSV libraries. Instead of returning raw arrays that require substantial post-processing, SmarterCSV produces Rails-ready hashes for each row, making the data immediately usable with ActiveRecord, Sidekiq pipelines, parallel processing, and JSON-based workflows such as S3.
11
16
 
@@ -66,7 +71,7 @@ rows = SmarterCSV.process('data.csv')
66
71
  data = SmarterCSV.parse(csv_string)
67
72
  ```
68
73
 
69
- See [**Migrating from Ruby CSV**](docs/migrating_from_csv.md) for a full comparison of options, behavior differences, and a quick-reference table.
74
+ * See [**Migrating from Ruby CSV**](docs/migrating_from_csv.md) for a full comparison of options, behavior differences, and a quick-reference table.
70
75
 
71
76
  ## Examples
72
77
 
@@ -205,6 +210,7 @@ Or install it yourself as:
205
210
 
206
211
  * [Introduction](docs/_introduction.md)
207
212
  * [**Migrating from Ruby CSV**](docs/migrating_from_csv.md)
213
+ * [Ruby CSV Pitfalls](docs/ruby_csv_pitfalls.md)
208
214
  * [Parsing Strategy](docs/parsing_strategy.md)
209
215
  * [The Basic Read API](docs/basic_read_api.md)
210
216
  * [The Basic Write API](docs/basic_write_api.md)
@@ -243,7 +249,7 @@ For reporting issues, please:
243
249
  * open a pull-request adding a test that demonstrates the issue
244
250
  * mention your version of SmarterCSV, Ruby, Rails
245
251
 
246
- # [A Special Thanks to all 61 Contributors!](CONTRIBUTORS.md) 🎉🎉🎉
252
+ # [A Special Thanks to all 62 Contributors!](CONTRIBUTORS.md) 🎉🎉🎉
247
253
 
248
254
 
249
255
  ## Contributing
@@ -3,6 +3,7 @@
3
3
 
4
4
  * [**Introduction**](./_introduction.md)
5
5
  * [Migrating from Ruby CSV](./migrating_from_csv.md)
6
+ * [Ruby CSV Pitfalls](./ruby_csv_pitfalls.md)
6
7
  * [Parsing Strategy](./parsing_strategy.md)
7
8
  * [The Basic Read API](./basic_read_api.md)
8
9
  * [The Basic Write API](./basic_write_api.md)
@@ -29,7 +30,11 @@
29
30
 
30
31
  ## Why another CSV library?
31
32
 
32
- Ruby's built-in `csv` library is **slow** — up to 129× slower than SmarterCSV for equivalent work — and its API is inconvenient. It returns arrays of arrays, which means your application code must handle column indexing, header normalization, type conversion, and whitespace stripping manually. It also has no built-in support for chunked or parallel processing of large files.
33
+ **Inconvenient.** Ruby's built-in `csv` library returns arrays of arrays, which means your application code must handle column indexing, header normalization, type conversion, and whitespace stripping manually. It also has no built-in support for chunked or parallel processing of large files.
34
+
35
+ **Hidden failure modes.** `CSV.read` has 10 ways to silently corrupt or lose data — no exception, no warning, no log line. Duplicate headers, blank header cells, extra columns, BOMs, whitespace, inconsistent empty-field representation, runaway quoted fields, and encoding issues all fail silently. See [Ruby CSV Pitfalls](./ruby_csv_pitfalls.md) for reproducible examples and the SmarterCSV fix for each.
36
+
37
+ **Slow.** On top of everything else, it is up to 129× slower than SmarterCSV for equivalent end-to-end work.
33
38
 
34
39
  ![SmarterCSV 1.16.0 vs Ruby CSV 3.3.5 speedup](../images/SmarterCSV_1.16.0_vs_RubyCSV_3.3.5_speedup.png)
35
40
 
@@ -3,6 +3,7 @@
3
3
 
4
4
  * [Introduction](./_introduction.md)
5
5
  * [Migrating from Ruby CSV](./migrating_from_csv.md)
6
+ * [Ruby CSV Pitfalls](./ruby_csv_pitfalls.md)
6
7
  * [Parsing Strategy](./parsing_strategy.md)
7
8
  * [The Basic Read API](./basic_read_api.md)
8
9
  * [The Basic Write API](./basic_write_api.md)
@@ -55,27 +56,33 @@ SmarterCSV.process('data.csv')
55
56
  # => raises SmarterCSV::MalformedCSV on the first bad row
56
57
  ```
57
58
 
58
- ### `:skip`
59
+ The `on_bad_row` option controls what happens when a bad row is encountered:
59
60
 
60
- Silently skip bad rows and continue. The count of skipped rows is available on
61
- `reader.errors[:bad_row_count]`. No error records are stored.
61
+ * `on_bad_row: :raise` (default) fails fast.
62
+ * `on_bad_row: :collect` quarantines them — error records available via `SmarterCSV.errors` or `reader.errors`.
63
+ * `on_bad_row: ->(rec) { ... }` calls your lambda per bad row — works with both `SmarterCSV.process` and `SmarterCSV::Reader`.
64
+ * `on_bad_row: :skip` discards bad rows silently — count available via `SmarterCSV.errors` or `reader.errors`.
62
65
 
63
- ```ruby
64
- reader = SmarterCSV::Reader.new('data.csv', on_bad_row: :skip)
65
- result = reader.process
66
+ ### `:collect`
66
67
 
67
- puts "Processed: #{result.size} good rows"
68
- puts "Skipped: #{reader.errors[:bad_row_count] || 0} bad rows"
69
- ```
68
+ Continue processing and store a structured error record for each bad row.
69
+ Error records are available via `SmarterCSV.errors[:bad_rows]` (class-level API)
70
+ or `reader.errors[:bad_rows]` (Reader API).
70
71
 
71
- ### `:collect`
72
+ ```ruby
73
+ # Class-level API — use SmarterCSV.errors after the call
74
+ good_rows = SmarterCSV.process('data.csv', on_bad_row: :collect)
72
75
 
73
- Continue processing and store a structured error record for each bad row in
74
- `reader.errors[:bad_rows]`. Requires using `SmarterCSV::Reader` directly (the
75
- `SmarterCSV.process` convenience method discards the reader instance and cannot
76
- return the collected errors).
76
+ good_rows.each { |row| MyModel.create!(row) }
77
+
78
+ SmarterCSV.errors[:bad_rows].each do |rec|
79
+ Rails.logger.warn "Bad row at line #{rec[:csv_line_number]}: #{rec[:error_message]}"
80
+ Rails.logger.warn "Raw content: #{rec[:raw_logical_line]}"
81
+ end
82
+ ```
77
83
 
78
84
  ```ruby
85
+ # Reader API — use when you also need access to headers or other reader state
79
86
  reader = SmarterCSV::Reader.new('data.csv', on_bad_row: :collect)
80
87
  result = reader.process
81
88
 
@@ -90,35 +97,50 @@ end
90
97
  ### Callable (lambda / proc)
91
98
 
92
99
  Pass any object that responds to `#call`. It is invoked once per bad row with the
93
- error record hash, then processing continues. Useful for streaming errors to a
94
- dead-letter queue, a metrics system, or a separate file.
100
+ error record hash, then processing continues. Because the lambda receives errors
101
+ inline, **this works with both `SmarterCSV.process` and `SmarterCSV::Reader`** —
102
+ you do not need a `Reader` instance to handle bad rows.
103
+
104
+ ```ruby
105
+ # Works with SmarterCSV.process — no Reader instance needed
106
+ bad_rows = []
107
+ good_rows = SmarterCSV.process('data.csv',
108
+ on_bad_row: ->(rec) { bad_rows << rec })
109
+ ```
95
110
 
96
111
  ```ruby
97
112
  # Log to a dead-letter file
98
113
  quarantine = File.open('quarantine.csv', 'w')
99
-
100
- reader = SmarterCSV::Reader.new('data.csv',
101
- on_bad_row: ->(rec) { quarantine.puts(rec[:raw_logical_line]) }
102
- )
103
- reader.process
114
+ SmarterCSV.process('data.csv',
115
+ on_bad_row: ->(rec) { quarantine.puts(rec[:raw_logical_line]) })
104
116
  quarantine.close
105
117
  ```
106
118
 
107
119
  ```ruby
108
120
  # Send to a monitoring system
109
- reader = SmarterCSV::Reader.new('data.csv',
110
- on_bad_row: ->(rec) { Metrics.increment('csv.bad_rows', tags: { error: rec[:error_class].name }) }
111
- )
112
- reader.process
121
+ SmarterCSV.process('data.csv',
122
+ on_bad_row: ->(rec) { Metrics.increment('csv.bad_rows', tags: { error: rec[:error_class].name }) })
113
123
  ```
114
124
 
125
+ ### `:skip`
126
+
127
+ Silently skip bad rows and continue. The count of skipped rows is available via
128
+ `SmarterCSV.errors[:bad_row_count]` (class-level API) or `reader.errors[:bad_row_count]`
129
+ (Reader API). No error records are stored.
130
+
115
131
  ```ruby
116
- # Collect into your own structure
117
- errors = []
118
- reader = SmarterCSV::Reader.new('data.csv',
119
- on_bad_row: ->(rec) { errors << rec }
120
- )
132
+ # Class-level API use SmarterCSV.errors after the call
133
+ SmarterCSV.process('data.csv', on_bad_row: :skip)
134
+ puts "Skipped: #{SmarterCSV.errors[:bad_row_count] || 0} bad rows"
135
+ ```
136
+
137
+ ```ruby
138
+ # Reader API — access reader.errors directly
139
+ reader = SmarterCSV::Reader.new('data.csv', on_bad_row: :skip)
121
140
  result = reader.process
141
+
142
+ puts "Processed: #{result.size} good rows"
143
+ puts "Skipped: #{reader.errors[:bad_row_count] || 0} bad rows"
122
144
  ```
123
145
 
124
146
  ## Error record structure
@@ -173,15 +195,50 @@ end
173
195
 
174
196
  ## Accessing errors
175
197
 
176
- Bad row data is stored on the `Reader` instance:
198
+ There are two ways to access bad row data after processing:
199
+
200
+ ### Via `SmarterCSV.errors` (class-level API)
201
+
202
+ `SmarterCSV.errors` returns the errors from the most recent call to `process`, `parse`,
203
+ `each`, or `each_chunk` on the current thread. It is cleared at the start of each new call.
204
+
205
+ ```ruby
206
+ SmarterCSV.process('data.csv', on_bad_row: :skip)
207
+ puts SmarterCSV.errors[:bad_row_count] # => 3
208
+
209
+ SmarterCSV.process('data.csv', on_bad_row: :collect)
210
+ puts SmarterCSV.errors[:bad_row_count] # => 3
211
+ puts SmarterCSV.errors[:bad_rows].size # => 3
212
+ ```
213
+
214
+ > **Note:** `SmarterCSV.errors` only surfaces errors from the **most recent run on the
215
+ > current thread**. In a multi-threaded environment (Puma, Sidekiq), each thread maintains
216
+ > its own error state independently. If you call `SmarterCSV.process` twice in the same
217
+ > thread, the second call's errors replace the first's. For long-running or complex
218
+ > pipelines where you need to aggregate errors across multiple files, use the Reader API.
219
+ >
220
+ > ⚠️ **Fibers:** `SmarterCSV.errors` uses `Thread.current` for storage, which is **shared
221
+ > across all fibers running in the same thread**. If you process CSV files concurrently
222
+ > in fibers (e.g. with `Async`, `Falcon`, or manual `Fiber` scheduling), `SmarterCSV.errors`
223
+ > may return stale or wrong results. **Use `SmarterCSV::Reader` directly** — errors are
224
+ > scoped to the reader instance and are always correct regardless of fiber context.
225
+
226
+ ### Via `reader.errors` (Reader API)
227
+
228
+ For full control — including access to headers, raw headers, and errors from a specific
229
+ call — use `SmarterCSV::Reader` directly:
177
230
 
178
231
  | Attribute | Description |
179
232
  |-----------|-------------|
180
233
  | `reader.errors[:bad_row_count]` | Total bad rows encountered (all modes) |
181
234
  | `reader.errors[:bad_rows]` | Array of error records (`:collect` mode only) |
182
235
 
183
- Note: `SmarterCSV.process` (the convenience method) discards the `Reader` instance after
184
- returning. To access `reader.errors`, always instantiate `SmarterCSV::Reader` directly.
236
+ ```ruby
237
+ reader = SmarterCSV::Reader.new('data.csv', on_bad_row: :collect)
238
+ reader.process
239
+ puts reader.errors[:bad_row_count]
240
+ puts reader.headers.inspect
241
+ ```
185
242
 
186
243
  ## Chunked processing
187
244
 
@@ -3,6 +3,7 @@
3
3
 
4
4
  * [Introduction](./_introduction.md)
5
5
  * [Migrating from Ruby CSV](./migrating_from_csv.md)
6
+ * [Ruby CSV Pitfalls](./ruby_csv_pitfalls.md)
6
7
  * [Parsing Strategy](./parsing_strategy.md)
7
8
  * [**The Basic Read API**](./basic_read_api.md)
8
9
  * [The Basic Write API](./basic_write_api.md)
@@ -3,6 +3,7 @@
3
3
 
4
4
  * [Introduction](./_introduction.md)
5
5
  * [Migrating from Ruby CSV](./migrating_from_csv.md)
6
+ * [Ruby CSV Pitfalls](./ruby_csv_pitfalls.md)
6
7
  * [Parsing Strategy](./parsing_strategy.md)
7
8
  * [The Basic Read API](./basic_read_api.md)
8
9
  * [**The Basic Write API**](./basic_write_api.md)
@@ -3,6 +3,7 @@
3
3
 
4
4
  * [Introduction](./_introduction.md)
5
5
  * [Migrating from Ruby CSV](./migrating_from_csv.md)
6
+ * [Ruby CSV Pitfalls](./ruby_csv_pitfalls.md)
6
7
  * [Parsing Strategy](./parsing_strategy.md)
7
8
  * [The Basic Read API](./basic_read_api.md)
8
9
  * [The Basic Write API](./basic_write_api.md)
@@ -3,6 +3,7 @@
3
3
 
4
4
  * [Introduction](./_introduction.md)
5
5
  * [Migrating from Ruby CSV](./migrating_from_csv.md)
6
+ * [Ruby CSV Pitfalls](./ruby_csv_pitfalls.md)
6
7
  * [Parsing Strategy](./parsing_strategy.md)
7
8
  * [The Basic Read API](./basic_read_api.md)
8
9
  * [The Basic Write API](./basic_write_api.md)
@@ -3,6 +3,7 @@
3
3
 
4
4
  * [Introduction](./_introduction.md)
5
5
  * [Migrating from Ruby CSV](./migrating_from_csv.md)
6
+ * [Ruby CSV Pitfalls](./ruby_csv_pitfalls.md)
6
7
  * [Parsing Strategy](./parsing_strategy.md)
7
8
  * [The Basic Read API](./basic_read_api.md)
8
9
  * [The Basic Write API](./basic_write_api.md)
data/docs/examples.md CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  * [Introduction](./_introduction.md)
5
5
  * [Migrating from Ruby CSV](./migrating_from_csv.md)
6
+ * [Ruby CSV Pitfalls](./ruby_csv_pitfalls.md)
6
7
  * [Parsing Strategy](./parsing_strategy.md)
7
8
  * [The Basic Read API](./basic_read_api.md)
8
9
  * [The Basic Write API](./basic_write_api.md)
@@ -3,6 +3,7 @@
3
3
 
4
4
  * [Introduction](./_introduction.md)
5
5
  * [Migrating from Ruby CSV](./migrating_from_csv.md)
6
+ * [Ruby CSV Pitfalls](./ruby_csv_pitfalls.md)
6
7
  * [Parsing Strategy](./parsing_strategy.md)
7
8
  * [The Basic Read API](./basic_read_api.md)
8
9
  * [The Basic Write API](./basic_write_api.md)
@@ -3,6 +3,7 @@
3
3
 
4
4
  * [Introduction](./_introduction.md)
5
5
  * [Migrating from Ruby CSV](./migrating_from_csv.md)
6
+ * [Ruby CSV Pitfalls](./ruby_csv_pitfalls.md)
6
7
  * [Parsing Strategy](./parsing_strategy.md)
7
8
  * [The Basic Read API](./basic_read_api.md)
8
9
  * [The Basic Write API](./basic_write_api.md)
data/docs/history.md CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  * [Introduction](./_introduction.md)
5
5
  * [Migrating from Ruby CSV](./migrating_from_csv.md)
6
+ * [Ruby CSV Pitfalls](./ruby_csv_pitfalls.md)
6
7
  * [Parsing Strategy](./parsing_strategy.md)
7
8
  * [The Basic Read API](./basic_read_api.md)
8
9
  * [The Basic Write API](./basic_write_api.md)
@@ -59,6 +60,7 @@ The first gem release was **v1.0.1 on 2012-07-30**.
59
60
  | 1.15.1 | 2026-02-17 | Fix for backslash in quoted fields (`quote_escaping:` option) |
60
61
  | 1.15.2 | 2026-02-20 | Further C-path optimisations; 5.4×–37.4× faster than 1.14.4 |
61
62
  | **1.16.0** | **2026-03-12** | **New `each`/`each_chunk` enumerator API; `SmarterCSV.parse`; bad row quarantine; column selection `headers: { only: }`; 1.8×–8.6× faster than Ruby CSV.read; new features for Reader and Writer; minor breaking: `quote_boundary: :standard`** |
63
+ | 1.16.1 | 2026-03-16 | `SmarterCSV.errors` class-level error access; fix `col_sep` in quoted headers (#325); fix quoted numeric conversion |
62
64
 
63
65
  ---
64
66
 
@@ -3,6 +3,7 @@
3
3
 
4
4
  * [Introduction](./_introduction.md)
5
5
  * [Migrating from Ruby CSV](./migrating_from_csv.md)
6
+ * [Ruby CSV Pitfalls](./ruby_csv_pitfalls.md)
6
7
  * [Parsing Strategy](./parsing_strategy.md)
7
8
  * [The Basic Read API](./basic_read_api.md)
8
9
  * [The Basic Write API](./basic_write_api.md)