smarter_csv 1.18.0 → 1.19.0
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 +4 -4
- data/CHANGELOG.md +85 -0
- data/CONTRIBUTORS.md +2 -1
- data/README.md +24 -1
- data/UPGRADING.md +22 -7
- data/docs/_introduction.md +22 -0
- data/docs/bad_row_quarantine.md +3 -1
- data/docs/basic_read_api.md +1 -1
- data/docs/data_transformations.md +6 -2
- data/docs/header_transformations.md +3 -1
- data/docs/migrating_from_csv.md +1 -1
- data/docs/options.md +3 -3
- data/docs/real_world_csv.md +1 -0
- data/docs/upgrade_path.json +35 -2
- data/docs/upgrade_wizard.html +16 -3
- data/ext/smarter_csv/cpu_flags.rb +55 -0
- data/ext/smarter_csv/extconf.rb +23 -2
- data/ext/smarter_csv/smarter_csv.c +153 -57
- data/lib/smarter_csv/hash_transformations.rb +16 -15
- data/lib/smarter_csv/header_transformations.rb +14 -1
- data/lib/smarter_csv/headers.rb +16 -1
- data/lib/smarter_csv/parser.rb +59 -11
- data/lib/smarter_csv/reader.rb +126 -62
- data/lib/smarter_csv/reader_options.rb +44 -6
- data/lib/smarter_csv/version.rb +1 -1
- data/lib/smarter_csv/writer.rb +3 -3
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dec0abc82b39809fd4822789f0e3f5bc80437eccf484435a540ea4c8ac83d500
|
|
4
|
+
data.tar.gz: 210f7a67b2a5e0470a97f7d7c05d8fb2ec644a945d96e3580a951fdf72ff2d2b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a31c7b13246f6abcb14fa882ad3cb962e74ad2d4b150dd034eb437dfb6a9c0eccb53c266d361b2b8d9bff1e461e4638b452d2c32868bd5e2e208dbaffc7bbd75
|
|
7
|
+
data.tar.gz: fe8d6066b2f25e6bb598ff0e93188ed77b0a409e412ebf1f03636d760abae8111eba63e291b943eacf7e2cac1504ebe818cb51e1ecd8eef2adf6396794748acf
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,91 @@
|
|
|
4
4
|
> [!TIP]
|
|
5
5
|
> **Upgrading?** The [SmarterCSV Upgrade Wizard](https://tilo.github.io/smarter_csv/upgrade_wizard.html) walks you through what (if anything) you need to change for your specific version. Most steps do not require any changes.
|
|
6
6
|
|
|
7
|
+
## 1.19.0 (2026-08-10)
|
|
8
|
+
|
|
9
|
+
RSpec tests: **2,595 → 3,164** (+569 tests)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Reverted Behavior Changes
|
|
13
|
+
|
|
14
|
+
- **Exponent forms are no longer auto-converted to numbers ([#345](https://github.com/tilo/smarter_csv/issues/345)).**
|
|
15
|
+
|
|
16
|
+
Version 1.18.0 started converting scientific notation (`"1e3"`, `"12E5"`, `"1.5e3"`) to Floats. In real-world CSV data, digits-E-digits values are far more often identifiers (short codes, hex IDs) than scientific notation, and the auto-conversion corrupted them irreversibly — an ID like `"0047583311587E590003"` came back as `Infinity`.
|
|
17
|
+
|
|
18
|
+
As of 1.19.0, exponent forms always stay Strings — as they did in every version before 1.18.0. If a column really does contain scientific notation, convert it per-column with `value_converters`:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
SmarterCSV.process(file, value_converters: { measurement: ->(v) { v.to_f } })
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Thanks to [Denis Sadomowski](https://github.com/sonicdes) for the report.
|
|
25
|
+
|
|
26
|
+
### Behavior Changes
|
|
27
|
+
|
|
28
|
+
- **`field_size_limit` values below `4096` now raise a `ValidationError`** — the option is overrun protection (a hard upper bound against runaway fields), not per-field validation.
|
|
29
|
+
|
|
30
|
+
- **The Hash form of `convert_values_to_numeric` is now validated and normalized.** It requires exactly one of `only:`/`except:` with field name(s) (String/Symbol or an Array of them); an empty hash, unknown keys, both keys together, empty lists, or `nil`/boolean values raise a `ValidationError` (the two parser paths previously disagreed on these shapes). The listed names are normalized to the row-key type, so `only:`/`except:` now also works with `strings_as_keys` / `keep_original_headers`.
|
|
31
|
+
|
|
32
|
+
- **All empty field values are ONE shared, frozen, UTF-8 empty-string object — on both paths** (no String allocation per empty field). Mutating an empty value now raises `FrozenError` instead of silently changing every other empty value in the result. Relevant with `remove_empty_values: false`; the default `true` removes empties anyway.
|
|
33
|
+
|
|
34
|
+
### Bug Fixes
|
|
35
|
+
|
|
36
|
+
- **Writer**: fields needing quoting are wrapped in the configured `quote_char`, not a hard-coded `"` — output with a custom `quote_char` round-trips again.
|
|
37
|
+
- **`Reader#each` without a block** no longer clears the configured `chunk_size` for a later `each_chunk` on the same Reader.
|
|
38
|
+
- **Duplicate-header disambiguation** no longer steals a real column's name — `name,name,name2` no longer raises `DuplicateHeaders` (the second `name` becomes `name3`).
|
|
39
|
+
- **The caller's `user_provided_headers` array** (and a reused options hash) is no longer mutated when rows have more columns than headers.
|
|
40
|
+
- **`headers: { only: }` / `{ except: }`** now works with `strings_as_keys` / `keep_original_headers` — selectors are normalized to the row-key type (with `only:`, nothing matched and every row came back empty: silent total data loss).
|
|
41
|
+
- **A quoted header containing an embedded newline** is stitched across physical lines like data rows (`"first\nname"` → `:first_name`); previously the first fragment was silently lost. An unclosed header quote at end-of-file raises `MalformedCSV`.
|
|
42
|
+
- **`quote_char: :auto`** raises a `ValidationError` instead of crashing with `NoMethodError` — quote_char has no auto-detection.
|
|
43
|
+
|
|
44
|
+
### Bug Fixes — C/Ruby parity
|
|
45
|
+
|
|
46
|
+
The C-accelerated and pure-Ruby parsers now behave identically in all of the following cases (same input, same output — verified by differential fuzzing and by running every parsing spec on both paths):
|
|
47
|
+
|
|
48
|
+
- a partial multi-char separator at end-of-line is field content, not a separator — with `col_sep: '||'` the C path silently dropped the lone `|` from `"y|"` (also fixes an out-of-bounds read near end-of-line)
|
|
49
|
+
- a trailing `\r` before an LF row separator is part of the line terminator — a CRLF line with a quoted last field raised `MalformedCSV` on the C path, and `strip_whitespace: false` kept `"x\r"` / `"1\r"` as values
|
|
50
|
+
- with `strip_whitespace: true`, values are stripped of Ruby's full `String#strip` character set on the C path too (a stray `\r` from mixed LF/CRLF files survived before)
|
|
51
|
+
- an empty line yields `nil` for ALL columns with `remove_empty_values: false` (the C path gave the first column `""`)
|
|
52
|
+
- a row consisting only of NUL bytes counts as blank on the C path too (`String#strip` semantics; the NUL byte itself remains data with `remove_empty_hashes: false`)
|
|
53
|
+
- a `nil` entry in `user_provided_headers` drops that column on the C path too
|
|
54
|
+
- an empty-string header key (`strings_as_keys` + `duplicate_header_suffix: nil`) is dropped on the C path too
|
|
55
|
+
- non-ASCII `missing_header_prefix` (e.g. `"spalte_ä_"`) no longer raises `EncodingError` on the C path — extra-column keys are interned as UTF-8 symbols
|
|
56
|
+
- `col_sep` / `row_sep` / `missing_header_prefix` values longer than the C parser's internal buffers fall back to the pure-Ruby parser instead of being silently truncated
|
|
57
|
+
- `nil_values_matching` matches the RAW string value on the C path (a pattern like `/\A007\z/` only ever saw the converted `7`) — and no longer switches off numeric conversion and zero-removal for the non-matching values
|
|
58
|
+
- `field_size_limit` is checked against the raw field size BEFORE numeric conversion, so an oversized digit-only field raises on the C path too instead of being converted to a huge Integer (the exact overrun the option exists to prevent)
|
|
59
|
+
- `headers: { only: }` short-cuts on the pure-Ruby path too — parsing stops right after the last wanted column, matching the C path (no `:column_N` discovery behind it, and faster)
|
|
60
|
+
- a one-character multi-byte `col_sep` (e.g. `'é'`) no longer crashes the pure-Ruby parser
|
|
61
|
+
- a multi-byte character directly before a literal quote (`é"x`) no longer crashes the pure-Ruby parser (`IndexError` from a mid-character byte offset)
|
|
62
|
+
- invalid bytes in the input (typically Latin-1 data mislabeled as UTF-8) no longer crash the pure-Ruby parser — fields keep their raw bytes and encoding tag exactly (never transcoded, so the data stays recoverable via `force_encoding`); cleanup remains opt-in via `force_utf8` / `invalid_byte_sequence`
|
|
63
|
+
- the multiline stitch gate models the parser's rules exactly (doubled-quote precedence, backslash escapes, end-of-line chomp) — no more fabricated `MalformedCSV` on the pure-Ruby path for rows the parser can close
|
|
64
|
+
|
|
65
|
+
### Tests
|
|
66
|
+
|
|
67
|
+
- **Every parsing spec now runs on BOTH the C-accelerated and the pure-Ruby path** via `[true, false]` acceleration loops (~420 additional examples), a seeded differential parity-fuzz spec (2,000 randomized inputs per run, including combined option sets) guards C/Ruby parity permanently, and line coverage is at **100%**.
|
|
68
|
+
|
|
69
|
+
## 1.18.1 (2026-06-30)
|
|
70
|
+
|
|
71
|
+
### Bug Fixes
|
|
72
|
+
|
|
73
|
+
- **Portable builds by default — fixes the "Illegal instruction" crash on heterogeneous CPUs ([#343](https://github.com/tilo/smarter_csv/issues/343)).**
|
|
74
|
+
|
|
75
|
+
Since 1.14.3 the C extension was compiled with `-march=native` on every platform except Apple Silicon, baking-in the build host's CPU instructions (e.g. AVX-512).
|
|
76
|
+
A binary built on one machine then could encounter `Illegal instruction` when run on a CPU lacking those instructions — common when the build host differs from the run host (CI/build servers, Docker images, mixed-hardware fleets).
|
|
77
|
+
|
|
78
|
+
The C extension is now built **portable** by default (no host-specific instructions). Thanks to [@paholg](https://github.com/paholg) for the report.
|
|
79
|
+
|
|
80
|
+
### New Features
|
|
81
|
+
|
|
82
|
+
- **`SMARTER_CSV_PERFORMANCE` build option** (`portable` default, `tuned`, or `max`)
|
|
83
|
+
|
|
84
|
+
| Level | Flags added | Portable? | Use when |
|
|
85
|
+
|----------------------|-------------------------------------------|----------------------------------|---------------------------------------|
|
|
86
|
+
| `portable` (default) | none | Yes, any CPU of the arch | Build host may differ from run host |
|
|
87
|
+
| `tuned` | `-mtune=native` | Yes, instruction scheduling only | Build and run hosts share a microarch |
|
|
88
|
+
| `max` | `-march=native`, or `-mcpu=native` on ARM | No, host instruction optimization| Build host and run host are the same |
|
|
89
|
+
|
|
90
|
+
See the [Introduction](docs/_introduction.md#build-time-performance-tuning-smarter_csv_performance) for details.
|
|
91
|
+
|
|
7
92
|
## 1.18.0 (2026-06-17)
|
|
8
93
|
|
|
9
94
|
This release is focused on both performance and the introduction of automatic conversion of decimals to big_decimal or float, preserving the precision, and also supporting scientific notation.
|
data/CONTRIBUTORS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# A Big Thank You to all
|
|
1
|
+
# A Big Thank You to all 66 Contributors!!
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
A Big Thank you to everyone who filed issues, sent comments, and who contributed with pull requests:
|
|
@@ -68,3 +68,4 @@ A Big Thank you to everyone who filed issues, sent comments, and who contributed
|
|
|
68
68
|
* [Jonas Staškevičius](https://github.com/pirminis)
|
|
69
69
|
* [conorg](https://github.com/conorg)
|
|
70
70
|
* [Alex Shenia](https://github.com/alexshenia)
|
|
71
|
+
* [Denis Sadomowski](https://github.com/sonicdes)
|
data/README.md
CHANGED
|
@@ -297,6 +297,29 @@ Or install it yourself as:
|
|
|
297
297
|
$ gem install smarter_csv
|
|
298
298
|
```
|
|
299
299
|
|
|
300
|
+
The C extension is built on install and used automatically. On platforms where it can't build, the pure-Ruby implementation runs instead and produces identical results.
|
|
301
|
+
|
|
302
|
+
### CPU Optimization (`SMARTER_CSV_PERFORMANCE`)
|
|
303
|
+
|
|
304
|
+
The C extension is compiled when the gem is installed. By default it is built **portable**: it uses no CPU-specific instructions, so a binary built on one machine runs on any other CPU of the same architecture. Set `SMARTER_CSV_PERFORMANCE` at install time to trade portability for speed:
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
| Level | Flags added | Portable? | Use when |
|
|
308
|
+
|----------------------|-------------------------------------------|----------------------------------|---------------------------------------|
|
|
309
|
+
| `portable` (default) | none | Yes, any CPU of the arch | Build host may differ from run host |
|
|
310
|
+
| `tuned` | `-mtune=native` | Yes, instruction scheduling only | Build and run hosts share a microarch |
|
|
311
|
+
| `max` | `-march=native`, or `-mcpu=native` on ARM | No, host instruction optimization| Build host and run host are the same |
|
|
312
|
+
|
|
313
|
+
`max` enables host-specific instructions, so a binary built with it can crash with `Illegal instruction` if it later runs on a CPU that lacks them (for example, built on an AVX-512 machine and run on one without). `tuned` only changes instruction scheduling, never the instruction set, so it stays portable. Every flag is probed against your compiler at build time and skipped if unsupported, so an unavailable flag never breaks the build.
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
SMARTER_CSV_PERFORMANCE=tuned gem install smarter_csv # portable, tuned for this machine's microarchitecture
|
|
317
|
+
SMARTER_CSV_PERFORMANCE=max gem install smarter_csv # fastest, NOT portable — only when you build on the machine you run on
|
|
318
|
+
SMARTER_CSV_PERFORMANCE=tuned bundle install # same, under Bundler
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
For a fixed baseline instead of `native` (e.g. a portable-but-newer instruction set), pass flags directly via `CFLAGS`, which the build also honors: `CFLAGS="-march=x86-64-v2" gem install smarter_csv`.
|
|
322
|
+
|
|
300
323
|
## Documentation
|
|
301
324
|
|
|
302
325
|
* [Introduction](docs/_introduction.md)
|
|
@@ -341,7 +364,7 @@ For reporting issues, please:
|
|
|
341
364
|
* open a pull-request adding a test that demonstrates the issue
|
|
342
365
|
* mention your version of SmarterCSV, Ruby, Rails
|
|
343
366
|
|
|
344
|
-
# [A Special Thanks to all
|
|
367
|
+
# [A Special Thanks to all Contributors!](CONTRIBUTORS.md) 🎉🎉🎉
|
|
345
368
|
|
|
346
369
|
|
|
347
370
|
## Contributing
|
data/UPGRADING.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> [!TIP]
|
|
4
4
|
> Prefer the interactive [Upgrade Wizard](https://tilo.github.io/smarter_csv/upgrade_wizard.html) for a guided walk-through with Yes/No questions.
|
|
5
|
-
> This document is auto-generated from `CHANGELOG.md` and `docs/upgrade_path.json` by `bin/
|
|
5
|
+
> This document is auto-generated from `CHANGELOG.md` and `docs/upgrade_path.json` by `bin/generate-upgrading-md`.
|
|
6
6
|
|
|
7
7
|
## How to use this guide
|
|
8
8
|
|
|
@@ -12,25 +12,40 @@
|
|
|
12
12
|
|
|
13
13
|
Prefer an interactive walk-through? The [Upgrade Wizard](https://tilo.github.io/smarter_csv/upgrade_wizard.html) asks one question at a time and only shows the migration steps that apply to your code.
|
|
14
14
|
|
|
15
|
-
**Latest release:** `1.
|
|
15
|
+
**Latest release:** `1.18.1` (in the `1.18.x` series).
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
-
## 1.
|
|
19
|
+
## 1.18.x — latest series
|
|
20
20
|
|
|
21
21
|
**Versions in this series:**
|
|
22
|
-
[1.
|
|
22
|
+
[1.18.0, 1.18.1]
|
|
23
23
|
|
|
24
|
-
**
|
|
24
|
+
> ⚠️ **In-series notes** worth checking:
|
|
25
|
+
> - **1.18.0:** This version is particularly interesting if you have geolocation, scientific, or high-precision data.
|
|
26
|
+
|
|
27
|
+
**Latest release:** `1.18.1`
|
|
25
28
|
|
|
26
29
|
Update your Gemfile to:
|
|
27
30
|
|
|
28
31
|
```ruby
|
|
29
|
-
gem 'smarter_csv', '~> 1.
|
|
32
|
+
gem 'smarter_csv', '~> 1.18.0'
|
|
30
33
|
```
|
|
31
34
|
|
|
32
35
|
Then run `bundle update smarter_csv`.
|
|
33
36
|
|
|
37
|
+
## Series 1.17 → 1.18
|
|
38
|
+
|
|
39
|
+
**Coming from any 1.17 version:**
|
|
40
|
+
[1.17.0, 1.17.1, 1.17.2, 1.17.3, 1.17.4]
|
|
41
|
+
|
|
42
|
+
**Upgrading to 1.18.x** (latest: `1.18.1`):
|
|
43
|
+
|
|
44
|
+
- **If** you build and run the gem on the same machine (or a fleet of identical CPUs) and want the previous native-optimized build for maximum speed:
|
|
45
|
+
→ set `SMARTER_CSV_PERFORMANCE=max` (or `tuned`) at install time — 1.18.1 builds <strong>portable</strong> by default (no host-specific CPU instructions) to fix an `Illegal instruction` crash when a binary built on one CPU is run on another (<a href="https://github.com/tilo/smarter_csv/issues/343">#343</a>). The default is safe everywhere; the env var opts back into host optimization.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
34
49
|
## Series 1.16 → 1.17
|
|
35
50
|
|
|
36
51
|
**Coming from any 1.16 version:**
|
|
@@ -40,7 +55,7 @@ Then run `bundle update smarter_csv`.
|
|
|
40
55
|
> - **1.16.1:** **Fibers:** `SmarterCSV.errors` uses `Thread.current` for storage, which is **shared across all fibers running in the same thread**. If you process CSV files concurrently in fibers (e.g. with `Async`, `Falcon`, or manual `Fiber` scheduling), `SmarterCSV.errors` may return stale or wrong results. **Use `SmarterCSV::Reader` directly** — errors are scoped to the reader instance and are always correct regardless of fiber context.
|
|
41
56
|
> - **1.16.2:** If your code references auto-generated keys for blank headers, update those to use the absolute column position.
|
|
42
57
|
|
|
43
|
-
**Upgrading to 1.17.x** (latest: `1.17.
|
|
58
|
+
**Upgrading to 1.17.x** (latest: `1.17.4`): you can upgrade all the way — no code changes needed.
|
|
44
59
|
|
|
45
60
|
---
|
|
46
61
|
|
data/docs/_introduction.md
CHANGED
|
@@ -75,6 +75,28 @@ SmarterCSV was created to solve exactly these problems: nightly imports of large
|
|
|
75
75
|
* **CSV writing:**
|
|
76
76
|
`SmarterCSV.generate` writes arrays of hashes to CSV, with support for header renaming and value converters on output. See [The Basic Write API](./basic_write_api.md).
|
|
77
77
|
|
|
78
|
+
## Build-Time Performance Tuning (`SMARTER_CSV_PERFORMANCE`)
|
|
79
|
+
|
|
80
|
+
The C extension is compiled when the gem is installed. By default it is built **portable**: it uses no CPU-specific instructions, so a binary compiled on one machine runs on any other CPU of the same architecture. This matters whenever the machine that builds the gem differs from the machine that runs it — a CI or build server, a Docker image moved between hosts, or a mixed-hardware fleet. A build that bakes in instructions the run host lacks (such as AVX-512) would otherwise crash with `Illegal instruction`.
|
|
81
|
+
|
|
82
|
+
Set `SMARTER_CSV_PERFORMANCE` at install time to trade portability for speed:
|
|
83
|
+
|
|
84
|
+
| Level | Flags added | Portable? | Use when |
|
|
85
|
+
|----------------------|-------------------------------------------|----------------------------------|---------------------------------------|
|
|
86
|
+
| `portable` (default) | none | Yes, any CPU of the arch | Build host may differ from run host |
|
|
87
|
+
| `tuned` | `-mtune=native` | Yes, instruction scheduling only | Build and run hosts share a microarch |
|
|
88
|
+
| `max` | `-march=native`, or `-mcpu=native` on ARM | No, host instruction optimization| Build host and run host are the same |
|
|
89
|
+
|
|
90
|
+
`tuned` only changes instruction scheduling, never the instruction set, so it stays portable — and it pays off when the build and run hosts share a microarchitecture (the same chip, or a fleet of identical instances). `max` enables host-specific instructions and is the fastest, but a binary built with it can crash on a different CPU. Every flag is probed against your compiler at build time and skipped if unsupported, so an unavailable flag never breaks the build.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
SMARTER_CSV_PERFORMANCE=tuned gem install smarter_csv # portable, tuned for this machine's microarchitecture
|
|
94
|
+
SMARTER_CSV_PERFORMANCE=max gem install smarter_csv # fastest, NOT portable — only when you build on the machine you run on
|
|
95
|
+
SMARTER_CSV_PERFORMANCE=tuned bundle install # same, under Bundler
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
For a fixed baseline instead of `native` (e.g. a portable-but-newer instruction set), pass flags directly via `CFLAGS`, which the build also honors: `CFLAGS="-march=x86-64-v2" gem install smarter_csv`.
|
|
99
|
+
|
|
78
100
|
---------------
|
|
79
101
|
|
|
80
102
|
NEXT: [Migrating from Ruby CSV](./migrating_from_csv.md) | UP: [README](../README.md)
|
data/docs/bad_row_quarantine.md
CHANGED
|
@@ -266,7 +266,9 @@ until it either finds the closing quote or reaches end-of-file, potentially cons
|
|
|
266
266
|
of megabytes.
|
|
267
267
|
|
|
268
268
|
`field_size_limit` sets a hard cap (in bytes) on the size of any individual extracted field.
|
|
269
|
-
The default is `nil` (no limit)
|
|
269
|
+
The default is `nil` (no limit); the minimum allowed value is `4096` — this option is overrun
|
|
270
|
+
protection against runaway or crafted fields, not a per-field validation tool, so small values
|
|
271
|
+
are rejected with a `ValidationError`. When a field exceeds the limit a
|
|
270
272
|
`SmarterCSV::FieldSizeLimitExceeded` exception is raised — and because it inherits from
|
|
271
273
|
`SmarterCSV::Error`, the `on_bad_row` option handles it exactly like any other parse error.
|
|
272
274
|
|
data/docs/basic_read_api.md
CHANGED
|
@@ -223,7 +223,7 @@ comment_regexp → strip_chars_from_headers → split on col_sep → strip quote
|
|
|
223
223
|
→ disambiguate_headers → symbolize → key_mapping
|
|
224
224
|
```
|
|
225
225
|
|
|
226
|
-
`user_provided_headers` bypasses the file header and all transformation steps — your array is used as-is.
|
|
226
|
+
`user_provided_headers` bypasses the file header and all transformation steps — your array is used as-is; a `nil` entry drops that column.
|
|
227
227
|
|
|
228
228
|
See [Header Transformations](./header_transformations.md) for the full step-by-step table and options.
|
|
229
229
|
|
|
@@ -114,6 +114,8 @@ data = SmarterCSV.process(file, remove_empty_values: false)
|
|
|
114
114
|
# => [{name: "Alice", score: 42, notes: nil}, {name: nil, score: nil, notes: "great player"}]
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
+
With `remove_empty_values: false`, all kept empty-string values are ONE shared frozen String (an allocation optimization) — `dup` before mutating one in place *(1.19.0+)*.
|
|
118
|
+
|
|
117
119
|
---
|
|
118
120
|
|
|
119
121
|
## `remove_zero_values`
|
|
@@ -156,7 +158,9 @@ data = SmarterCSV.process(file,
|
|
|
156
158
|
convert_values_to_numeric: { only: [:quantity, :price] })
|
|
157
159
|
```
|
|
158
160
|
|
|
159
|
-
|
|
161
|
+
The Hash form requires exactly one of `only:`/`except:` with field name(s) — anything else (empty hash, unknown keys, both keys, empty lists, `nil`/boolean values) raises a `ValidationError` *(1.19.0+)*.
|
|
162
|
+
|
|
163
|
+
Exponent forms (e.g. `"1e3"`, `"12E5"`, `"1.5e3"`) are NOT converted — they stay Strings *(changed in 1.19.0; only 1.18.x converted them)*. In real-world CSV data such values are far more often identifiers (short codes, hex IDs) than scientific notation, and auto-converting them corrupts data — e.g. an ID like `"0047583311587E590003"` became `Infinity`. If a column really does contain scientific notation, convert it per-column with [`value_converters`](./value_converters.md). Bare-dot forms like `".5"` and `"3."` are left as Strings (they are not valid numbers here). Integers and floats convert identically on the C-accelerated and pure-Ruby paths.
|
|
160
164
|
|
|
161
165
|
---
|
|
162
166
|
|
|
@@ -164,7 +168,7 @@ Scientific notation (e.g. `"1.5e3"`, `"6.022e23"`) is recognized and converted t
|
|
|
164
168
|
|
|
165
169
|
**Default: `:auto`**
|
|
166
170
|
|
|
167
|
-
Controls how decimal values (those with a `.`
|
|
171
|
+
Controls how decimal values (those with a `.`) are converted. Integers are unaffected — they are always returned as `Integer`.
|
|
168
172
|
|
|
169
173
|
| Value | Result |
|
|
170
174
|
|---------------|-----------------------------------------------------------------------------------------|
|
|
@@ -56,7 +56,9 @@ comment_regexp ──► strip_chars_from_headers ──► split on col_sep
|
|
|
56
56
|
| 9 | `strings_as_keys` | `false` | Converts headers to symbols (skipped if `true` or `keep_original_headers`) |
|
|
57
57
|
| 10 | `key_mapping` | `nil` | Renames or drops headers; use post-transformation key names as input |
|
|
58
58
|
|
|
59
|
-
> `user_provided_headers` bypasses all file header reading and transformation entirely — your array is used as-is. Versions >1.13 automatically set `headers_in_file: false` when `user_provided_headers` is given; if the file has a header row you want to skip, set `headers_in_file: true` explicitly.
|
|
59
|
+
> `user_provided_headers` bypasses all file header reading and transformation entirely — your array is used as-is; a `nil` entry drops that column. Versions >1.13 automatically set `headers_in_file: false` when `user_provided_headers` is given; if the file has a header row you want to skip, set `headers_in_file: true` explicitly.
|
|
60
|
+
|
|
61
|
+
A quoted header containing an embedded newline is stitched across physical lines like a data row *(1.19.0+)* — the newline becomes `_` via the standard transformations (`"first\nname"` → `:first_name`).
|
|
60
62
|
|
|
61
63
|
See [Configuration Options](./options.md) for full option reference.
|
|
62
64
|
|
data/docs/migrating_from_csv.md
CHANGED
|
@@ -223,7 +223,7 @@ rows = SmarterCSV.process('sample.csv',
|
|
|
223
223
|
convert_values_to_numeric: { except: [:zip_code, :phone, :account_number] })
|
|
224
224
|
```
|
|
225
225
|
|
|
226
|
-
**High-precision decimals — scientific data and geo coordinates.** GPS/geo coordinates, scientific measurements, and financial figures routinely carry 16+ significant digits, where Ruby's `Float()`-based conversion (`converters: :numeric` / `:float`) silently rounds the value. SmarterCSV's default `decimal_precision: :auto` returns a `BigDecimal` once a value exceeds 16 significant digits (and a `Float` otherwise), so the full value is preserved
|
|
226
|
+
**High-precision decimals — scientific data and geo coordinates.** GPS/geo coordinates, scientific measurements, and financial figures routinely carry 16+ significant digits, where Ruby's `Float()`-based conversion (`converters: :numeric` / `:float`) silently rounds the value. SmarterCSV's default `decimal_precision: :auto` returns a `BigDecimal` once a value exceeds 16 significant digits (and a `Float` otherwise), so the full value is preserved. (Exponent forms like `6.022e23` are not auto-converted — in CSV data they are usually identifiers, not numbers; use `value_converters` for columns that really contain scientific notation.)
|
|
227
227
|
|
|
228
228
|
**With Ruby CSV (precision lost):**
|
|
229
229
|
```ruby
|
data/docs/options.md
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
|--------|---------|-------------|
|
|
34
34
|
| `:row_sep` | `$/` | Separates rows. Defaults to your OS row separator: `\n` on UNIX, `\r\n` on Windows. |
|
|
35
35
|
| `:col_sep` | `","` | Separates each value in a row. |
|
|
36
|
-
| `:quote_char` | `'"'` | Character used to quote CSV fields. |
|
|
36
|
+
| `:quote_char` | `'"'` | Character used to quote CSV fields. Must be a single byte. |
|
|
37
37
|
| `:force_quotes` | `false` | Forces each individual value to be quoted. |
|
|
38
38
|
| `:headers` | `[]` | List of keys from the input to use as headers in the CSV file. ⚠️ Disables automatic header detection! |
|
|
39
39
|
| `:map_headers` | `{}` | Like `:headers`, but also maps each key to a user-specified header value. ⚠️ Disables automatic header detection! |
|
|
@@ -121,7 +121,7 @@ See [Parsing Strategy](./parsing_strategy.md) for full details on quote handling
|
|
|
121
121
|
| Option | Default | Explanation |
|
|
122
122
|
|--------|---------|-------------|
|
|
123
123
|
| `:strip_whitespace` | `true` | Remove whitespace before/after values and headers. |
|
|
124
|
-
| `:convert_values_to_numeric` | `true` | Convert strings containing integers or floats
|
|
124
|
+
| `:convert_values_to_numeric` | `true` | Convert strings containing integers or floats to the appropriate numeric type. Exponent forms like `1.5e3` or `12E5` stay Strings (1.19.0+) — they are usually identifiers, not numbers. Accepts `{except: [:key1, :key2]}` or `{only: :key3}` to limit which columns. |
|
|
125
125
|
| `:decimal_precision` | `:auto` | How decimals are converted: `:auto` returns `Float` but `BigDecimal` above 16 significant digits (no precision loss); `:float` always returns `Float`; `:bigdecimal` always returns `BigDecimal`. Integers are unaffected. |
|
|
126
126
|
| `:value_converters` | `nil` | Hash of `:header => converter`; converter can be a lambda/Proc or a class implementing `self.convert(value)`. See [Value Converters](./value_converters.md). |
|
|
127
127
|
| `:remove_empty_values` | `true` | Remove key/value pairs where the value is `nil`, empty, or whitespace-only — any Unicode whitespace, same as Ruby's `String#blank?`. |
|
|
@@ -138,7 +138,7 @@ See [Bad Row Quarantine](./bad_row_quarantine.md) for full details.
|
|
|
138
138
|
| `:on_bad_row` | `:raise` | Behavior when a row raises a parse error. `:raise` (default): re-raise, stopping processing. `:skip`: skip the bad row and continue. `:collect`: skip and append an error record to `reader.errors[:bad_rows]`. callable: called with the error record per bad row; processing continues. |
|
|
139
139
|
| `:collect_raw_lines` | `true` | When collecting bad rows, include the raw stitched line in the error record. |
|
|
140
140
|
| `:bad_row_limit` | `nil` | If set, raises `SmarterCSV::TooManyBadRows` after this many bad rows. |
|
|
141
|
-
| `:field_size_limit` | `nil` | Maximum size of any extracted field in bytes. `nil` means no limit. Raises `SmarterCSV::FieldSizeLimitExceeded` (handled by `on_bad_row`) if a field or accumulating multiline buffer exceeds this size. Prevents DoS from runaway quoted fields or huge inline payloads. See [Bad Row Quarantine](./bad_row_quarantine.md#limiting-field-size-field_size_limit). |
|
|
141
|
+
| `:field_size_limit` | `nil` | Maximum size of any extracted field in bytes. `nil` means no limit; the minimum allowed value is `4096` (it is overrun protection, not per-field validation). Raises `SmarterCSV::FieldSizeLimitExceeded` (handled by `on_bad_row`) if a field or accumulating multiline buffer exceeds this size. Prevents DoS from runaway quoted fields or huge inline payloads. See [Bad Row Quarantine](./bad_row_quarantine.md#limiting-field-size-field_size_limit). |
|
|
142
142
|
|
|
143
143
|
### Output & Diagnostics
|
|
144
144
|
|
data/docs/real_world_csv.md
CHANGED
|
@@ -51,6 +51,7 @@ Real-world files come from dozens of different systems, each with their own defa
|
|
|
51
51
|
| Windows-1252 / Latin-1 | 🔘 | Specify `file_encoding: 'windows-1252'`. Common in European financial exports, older SAP systems, QuickBooks. |
|
|
52
52
|
| UTF-16 LE with BOM | 🔘 | Specify `file_encoding: 'utf-16le'`. Some Microsoft SQL Server and Access exports default to this. |
|
|
53
53
|
| Shift-JIS / EUC-JP | 🔘 | Specify `file_encoding: 'shift_jis'` or `'euc-jp'`. Japanese ERP and POS systems. |
|
|
54
|
+
| Invalid bytes / mislabeled encoding | ✅ | Never crashes — the affected field keeps its raw bytes exactly, recoverable via `force_encoding`. Opt into cleanup with `force_utf8` / `invalid_byte_sequence`. |
|
|
54
55
|
|
|
55
56
|
---
|
|
56
57
|
|
data/docs/upgrade_path.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"latest": "1.
|
|
3
|
-
"latest_release": "1.
|
|
2
|
+
"latest": "1.19",
|
|
3
|
+
"latest_release": "1.19.0",
|
|
4
4
|
"path": {
|
|
5
5
|
"1.0": {
|
|
6
6
|
"to": "1.1",
|
|
@@ -170,6 +170,39 @@
|
|
|
170
170
|
"to": "1.17",
|
|
171
171
|
"latest_release": "1.16.6",
|
|
172
172
|
"actions": []
|
|
173
|
+
},
|
|
174
|
+
"1.17": {
|
|
175
|
+
"to": "1.18",
|
|
176
|
+
"latest_release": "1.17.4",
|
|
177
|
+
"actions": [
|
|
178
|
+
{
|
|
179
|
+
"if": "you build and run the gem on the same machine (or a fleet of identical CPUs) and want the previous native-optimized build for maximum speed",
|
|
180
|
+
"then": "set <code>SMARTER_CSV_PERFORMANCE=max</code> (or <code>tuned</code>) at install time — 1.18.1 builds <strong>portable</strong> by default (no host-specific CPU instructions) to fix an <code>Illegal instruction</code> crash when a binary built on one CPU is run on another (<a href=\"https://github.com/tilo/smarter_csv/issues/343\">#343</a>). The default is safe everywhere; the env var opts back into host optimization."
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
"note": "<strong>Bonus:</strong> 1.18 automatically converts scientific notation (e.g. <code>1e3</code>, <code>6.022e23</code>) to numbers, and preserves full precision on long decimals by returning a <code>BigDecimal</code> when a value carries more than 16 significant digits (<code>decimal_precision: :auto</code>, the default). Nothing to change — this just works."
|
|
184
|
+
},
|
|
185
|
+
"1.18": {
|
|
186
|
+
"to": "1.19",
|
|
187
|
+
"latest_release": "1.18.1",
|
|
188
|
+
"actions": [
|
|
189
|
+
{
|
|
190
|
+
"if": "you relied on 1.18.x auto-converting scientific notation (<code>\"1e3\"</code>, <code>\"1.5e3\"</code>) to numbers",
|
|
191
|
+
"then": "convert those columns explicitly with <code>value_converters: { column: ->(v) { v.to_f } }</code> — 1.19.0 keeps all exponent forms as Strings again (as every version before 1.18.0 did), because in real-world data they are usually identifiers, and IDs like <code>\"0047583311587E590003\"</code> were corrupted to <code>Infinity</code> (<a href=\"https://github.com/tilo/smarter_csv/issues/345\">#345</a>)."
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"if": "you set <code>field_size_limit:</code> to a value below 4096",
|
|
195
|
+
"then": "raise it to at least <code>4096</code> — the option is overrun protection (a hard upper bound against runaway fields), not per-field validation, and 1.19.0 rejects smaller values with a <code>ValidationError</code>."
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"if": "you pass <code>convert_values_to_numeric:</code> as a Hash with anything other than exactly one of <code>only:</code>/<code>except:</code> and field name(s)",
|
|
199
|
+
"then": "fix the option — an empty hash, unknown keys, both keys together, empty lists, or <code>nil</code>/boolean values now raise a <code>ValidationError</code> (the two parser paths previously disagreed silently on these shapes)."
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"if": "you mutate empty-string values in the results (only possible with <code>remove_empty_values: false</code>)",
|
|
203
|
+
"then": "<code>dup</code> them first — all empty field values are now one shared, frozen empty string (an allocation optimization), so in-place mutation raises <code>FrozenError</code>."
|
|
204
|
+
}
|
|
205
|
+
]
|
|
173
206
|
}
|
|
174
207
|
}
|
|
175
208
|
}
|
data/docs/upgrade_wizard.html
CHANGED
|
@@ -94,6 +94,15 @@ input[type="text"] {
|
|
|
94
94
|
color: var(--green);
|
|
95
95
|
font-weight: 600;
|
|
96
96
|
}
|
|
97
|
+
.benefit {
|
|
98
|
+
background: var(--green-soft);
|
|
99
|
+
border: 1px solid #b6dab6;
|
|
100
|
+
border-radius: 8px;
|
|
101
|
+
padding: 1em 1.25em;
|
|
102
|
+
margin-bottom: 1em;
|
|
103
|
+
color: var(--fg);
|
|
104
|
+
}
|
|
105
|
+
.benefit strong { color: var(--green); }
|
|
97
106
|
.check {
|
|
98
107
|
padding: 0.85em 0;
|
|
99
108
|
border-top: 1px solid var(--border);
|
|
@@ -244,6 +253,8 @@ function renderHop(series, originalVersion) {
|
|
|
244
253
|
`).join("")}
|
|
245
254
|
</div>`;
|
|
246
255
|
}
|
|
256
|
+
// The bonus note renders below the actions (or below the drop-in box) regardless of whether this hop has actions.
|
|
257
|
+
if (hop.note) body += `<div class="benefit">${hop.note}</div>`;
|
|
247
258
|
|
|
248
259
|
const nextLabel = hop.to === LATEST ? `Finish at ${targetRelease} →` : `Continue to ${targetRelease} →`;
|
|
249
260
|
const reminder = hop.actions.length === 0 ? `<p class="reminder">You can upgrade directly to version ${targetRelease}. No changes needed.</p>` :
|
|
@@ -324,6 +335,7 @@ function renderHop(series, originalVersion) {
|
|
|
324
335
|
from: series,
|
|
325
336
|
to: hop.to,
|
|
326
337
|
dropIn: hop.actions.length === 0,
|
|
338
|
+
note: hop.note || null,
|
|
327
339
|
matched
|
|
328
340
|
});
|
|
329
341
|
|
|
@@ -376,14 +388,15 @@ function renderSummary() {
|
|
|
376
388
|
const list = decisions.map(d => {
|
|
377
389
|
const targetRelease = latestReleaseFor(d.to);
|
|
378
390
|
const heading = `<p class="summary-hop-heading"><strong>${d.from}.x → ${targetRelease}</strong></p>`;
|
|
391
|
+
const noteHTML = d.note ? `<p>${d.note}</p>` : "";
|
|
379
392
|
if (d.dropIn) {
|
|
380
|
-
return `<div class="summary-hop">${heading}<p class="muted">No code changes needed for this step.</p
|
|
393
|
+
return `<div class="summary-hop">${heading}<p class="muted">No code changes needed for this step.</p>${noteHTML}</div>`;
|
|
381
394
|
}
|
|
382
395
|
if (d.matched.length === 0) {
|
|
383
|
-
return `<div class="summary-hop">${heading}<p class="muted">None of the conditions in this step applied to your code.</p
|
|
396
|
+
return `<div class="summary-hop">${heading}<p class="muted">None of the conditions in this step applied to your code.</p>${noteHTML}</div>`;
|
|
384
397
|
}
|
|
385
398
|
const items = d.matched.map(a => `<li><strong>If</strong> ${a["if"]}<br>→ ${a.then}</li>`).join("");
|
|
386
|
-
return `<div class="summary-hop">${heading}<ul>${items}</ul
|
|
399
|
+
return `<div class="summary-hop">${heading}<ul>${items}</ul>${noteHTML}</div>`;
|
|
387
400
|
}).join("");
|
|
388
401
|
|
|
389
402
|
return `
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SmarterCSV
|
|
4
|
+
# Pure (mkmf-free) selection of CPU-optimization flags from the
|
|
5
|
+
# SMARTER_CSV_PERFORMANCE environment variable. Kept separate from extconf.rb
|
|
6
|
+
# so the logic can be unit-tested without invoking a compiler.
|
|
7
|
+
#
|
|
8
|
+
# Levels:
|
|
9
|
+
# portable - no host-specific flags. The binary runs on any CPU of the same
|
|
10
|
+
# architecture. The safe default: a binary built here will not
|
|
11
|
+
# crash with "Illegal instruction" on an older/different CPU.
|
|
12
|
+
# tuned - -mtune=native: tunes instruction scheduling for the build host's
|
|
13
|
+
# microarchitecture WITHOUT changing the instruction set, so the
|
|
14
|
+
# binary stays portable. A real win when build and run hosts share
|
|
15
|
+
# a microarchitecture (same chip or a homogeneous fleet).
|
|
16
|
+
# max - host-specific instructions: -march=native, or -mcpu=native on
|
|
17
|
+
# ARM/Clang where -march=native is rejected. Fastest, but NOT
|
|
18
|
+
# portable -- may crash on a CPU lacking the build host's
|
|
19
|
+
# instructions. Use only when build host and run host match.
|
|
20
|
+
#
|
|
21
|
+
# `accepts` is a predicate (in the real build, a wrapper over mkmf's
|
|
22
|
+
# try_compile) returning true when the compiler accepts a given flag; each
|
|
23
|
+
# candidate is probed so an unsupported flag is skipped rather than breaking
|
|
24
|
+
# the build.
|
|
25
|
+
module CpuFlags
|
|
26
|
+
LEVELS = %w[portable tuned max].freeze
|
|
27
|
+
|
|
28
|
+
# Candidate flags per level, in preference order. The first one the compiler
|
|
29
|
+
# accepts wins. `max` degrades march -> mcpu -> mtune; tuned only ever
|
|
30
|
+
# considers -mtune=native (never an instruction-set flag).
|
|
31
|
+
CANDIDATES = {
|
|
32
|
+
'portable' => [].freeze,
|
|
33
|
+
'tuned' => ['-mtune=native'].freeze,
|
|
34
|
+
'max' => ['-march=native', '-mcpu=native', '-mtune=native'].freeze,
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
# Returns a Hash: { level: String, flags: Array<String>, warning: String|nil }.
|
|
38
|
+
def self.select(raw_level, accepts:)
|
|
39
|
+
level, warning = normalize(raw_level)
|
|
40
|
+
chosen = CANDIDATES[level].find { |flag| accepts.call(flag) }
|
|
41
|
+
{ level: level, flags: chosen ? [chosen] : [], warning: warning }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Normalizes the env value to a known level. Unknown values fall back to
|
|
45
|
+
# 'portable' (a typo can then only ever be slower, never non-portable) and
|
|
46
|
+
# return a warning naming the bad value and the fallback.
|
|
47
|
+
def self.normalize(raw_level)
|
|
48
|
+
value = raw_level.to_s.strip.downcase
|
|
49
|
+
return ['portable', nil] if value.empty?
|
|
50
|
+
return [value, nil] if LEVELS.include?(value)
|
|
51
|
+
|
|
52
|
+
['portable', "SMARTER_CSV_PERFORMANCE=#{raw_level.inspect} is not one of #{LEVELS.join('|')}; using 'portable'."]
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
data/ext/smarter_csv/extconf.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'mkmf'
|
|
4
4
|
require "rbconfig"
|
|
5
|
+
require_relative 'cpu_flags'
|
|
5
6
|
|
|
6
7
|
if RbConfig::MAKEFILE_CONFIG["CFLAGS"].include?("-g -O3")
|
|
7
8
|
fixed_CFLAGS = RbConfig::MAKEFILE_CONFIG["CFLAGS"].sub("-g -O3", "-O3 $(cflags)")
|
|
@@ -9,11 +10,31 @@ if RbConfig::MAKEFILE_CONFIG["CFLAGS"].include?("-g -O3")
|
|
|
9
10
|
RbConfig::MAKEFILE_CONFIG["CFLAGS"] = fixed_CFLAGS
|
|
10
11
|
end
|
|
11
12
|
|
|
13
|
+
# Probe whether the compiler accepts a flag by compiling a trivial program with
|
|
14
|
+
# it. Lets us skip flags the toolchain rejects (e.g. -march=native on Clang/ARM,
|
|
15
|
+
# or GCC-only flags on MSVC) instead of breaking the build. Replaces the old
|
|
16
|
+
# RUBY_PLATFORM string guesses: ask the actual compiler, don't infer from the OS.
|
|
17
|
+
def compiler_accepts?(flag)
|
|
18
|
+
try_compile("int main(void){return 0;}", flag)
|
|
19
|
+
end
|
|
20
|
+
|
|
12
21
|
optflags = "-O3 -flto -fomit-frame-pointer -DNDEBUG".dup
|
|
13
|
-
|
|
22
|
+
|
|
23
|
+
# CPU optimization level, set via SMARTER_CSV_PERFORMANCE (default: portable).
|
|
24
|
+
# See cpu_flags.rb for the full description of each level.
|
|
25
|
+
#
|
|
26
|
+
# portable (default) - no host-specific flags; runs on any CPU of the same arch.
|
|
27
|
+
# tuned - -mtune=native; host scheduling tuning, still portable.
|
|
28
|
+
# max - host instruction set (-march/-mcpu native); fastest, but
|
|
29
|
+
# NOT portable -- may crash on a CPU lacking those instructions.
|
|
30
|
+
cpu = SmarterCSV::CpuFlags.select(ENV["SMARTER_CSV_PERFORMANCE"], accepts: method(:compiler_accepts?))
|
|
31
|
+
warn(cpu[:warning]) if cpu[:warning]
|
|
32
|
+
cpu[:flags].each { |flag| optflags << " #{flag}" }
|
|
33
|
+
puts("SmarterCSV performance level: #{cpu[:level]} -- optflags: #{optflags}")
|
|
34
|
+
|
|
14
35
|
# -fno-semantic-interposition: GCC/Clang only (not MSVC). Allows intra-library
|
|
15
36
|
# calls to bypass the PLT on Linux and enables more aggressive LTO inlining.
|
|
16
|
-
optflags << " -fno-semantic-interposition"
|
|
37
|
+
optflags << " -fno-semantic-interposition" if compiler_accepts?("-fno-semantic-interposition")
|
|
17
38
|
|
|
18
39
|
append_cflags('-Wno-compound-token-split-by-macro')
|
|
19
40
|
|