csv 3.2.0 → 3.2.3
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/NEWS.md +99 -0
- data/README.md +1 -1
- data/doc/csv/options/generating/write_headers.rdoc +1 -1
- data/doc/csv/recipes/generating.rdoc +1 -1
- data/doc/csv/recipes/parsing.rdoc +1 -1
- data/lib/csv/fields_converter.rb +6 -2
- data/lib/csv/input_record_separator.rb +18 -0
- data/lib/csv/parser.rb +202 -65
- data/lib/csv/table.rb +14 -4
- data/lib/csv/version.rb +1 -1
- data/lib/csv/writer.rb +2 -1
- data/lib/csv.rb +61 -17
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 915b3ed5a51bf4836f08f7bb06efc3b07bdc90e09209a5253092130e2cad2ab6
|
4
|
+
data.tar.gz: 6bce2e39329afcf200691b4b2f422b6a48d45da66368f5d5e136e0c761cd6217
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c1434c8e91c16de40d19d4d1200f193248e786720b67f2bbecf26a481859fe814b8cbaa02d22027668ff02588541266c8ff5d00b9fc1cfc2163b358b8e9ece9
|
7
|
+
data.tar.gz: 1978e933549049129f0ec99e80a10f2838b3c75a282103aa177d8421fe7589d428308e2786b29a961a4a7a5565ede77e3b1ef44ba8f4bc91b593a5a884ded7aa
|
data/NEWS.md
CHANGED
@@ -1,5 +1,104 @@
|
|
1
1
|
# News
|
2
2
|
|
3
|
+
## 3.2.3 - 2022-04-09
|
4
|
+
|
5
|
+
### Improvements
|
6
|
+
|
7
|
+
* Added contents summary to `CSV::Table#inspect`.
|
8
|
+
[GitHub#229][Patch by Eriko Sugiyama]
|
9
|
+
[GitHub#235][Patch by Sampat Badhe]
|
10
|
+
|
11
|
+
* Suppressed `$INPUT_RECORD_SEPARATOR` deprecation warning by
|
12
|
+
`Warning.warn`.
|
13
|
+
[GitHub#233][Reported by Jean byroot Boussier]
|
14
|
+
|
15
|
+
* Improved error message for liberal parsing with quoted values.
|
16
|
+
[GitHub#231][Patch by Nikolay Rys]
|
17
|
+
|
18
|
+
* Fixed typos in documentation.
|
19
|
+
[GitHub#236][Patch by Sampat Badhe]
|
20
|
+
|
21
|
+
* Added `:max_field_size` option and deprecated `:field_size_limit` option.
|
22
|
+
[GitHub#238][Reported by Dan Buettner]
|
23
|
+
|
24
|
+
* Added `:symbol_raw` to built-in header converters.
|
25
|
+
[GitHub#237][Reported by taki]
|
26
|
+
[GitHub#239][Patch by Eriko Sugiyama]
|
27
|
+
|
28
|
+
### Fixes
|
29
|
+
|
30
|
+
* Fixed a bug that some texts may be dropped unexpectedly.
|
31
|
+
[Bug #18245][ruby-core:105587][Reported by Hassan Abdul Rehman]
|
32
|
+
|
33
|
+
* Fixed a bug that `:field_size_limit` doesn't work with not complex row.
|
34
|
+
[GitHub#238][Reported by Dan Buettner]
|
35
|
+
|
36
|
+
### Thanks
|
37
|
+
|
38
|
+
* Hassan Abdul Rehman
|
39
|
+
|
40
|
+
* Eriko Sugiyama
|
41
|
+
|
42
|
+
* Jean byroot Boussier
|
43
|
+
|
44
|
+
* Nikolay Rys
|
45
|
+
|
46
|
+
* Sampat Badhe
|
47
|
+
|
48
|
+
* Dan Buettner
|
49
|
+
|
50
|
+
* taki
|
51
|
+
|
52
|
+
## 3.2.2 - 2021-12-24
|
53
|
+
|
54
|
+
### Improvements
|
55
|
+
|
56
|
+
* Added a validation for invalid option combination.
|
57
|
+
[GitHub#225][Patch by adamroyjones]
|
58
|
+
|
59
|
+
* Improved documentation for developers.
|
60
|
+
[GitHub#227][Patch by Eriko Sugiyama]
|
61
|
+
|
62
|
+
### Fixes
|
63
|
+
|
64
|
+
* Fixed a bug that all of `ARGF` contents may not be consumed.
|
65
|
+
[GitHub#228][Reported by Rafael Navaza]
|
66
|
+
|
67
|
+
### Thanks
|
68
|
+
|
69
|
+
* adamroyjones
|
70
|
+
|
71
|
+
* Eriko Sugiyama
|
72
|
+
|
73
|
+
* Rafael Navaza
|
74
|
+
|
75
|
+
## 3.2.1 - 2021-10-23
|
76
|
+
|
77
|
+
### Improvements
|
78
|
+
|
79
|
+
* doc: Fixed wrong class name.
|
80
|
+
[GitHub#217][Patch by Vince]
|
81
|
+
|
82
|
+
* Changed to always use `"\n"` for the default row separator on Ruby
|
83
|
+
3.0 or later because `$INPUT_RECORD_SEPARATOR` was deprecated
|
84
|
+
since Ruby 3.0.
|
85
|
+
|
86
|
+
* Added support for Ractor.
|
87
|
+
[GitHub#218][Patch by rm155]
|
88
|
+
|
89
|
+
* Users who want to use the built-in converters in non-main
|
90
|
+
Ractors need to call `Ractor.make_shareable(CSV::Converters)`
|
91
|
+
and/or `Ractor.make_shareable(CSV::HeaderConverters)` before
|
92
|
+
creating non-main Ractors.
|
93
|
+
|
94
|
+
### Thanks
|
95
|
+
|
96
|
+
* Vince
|
97
|
+
|
98
|
+
* Joakim Antman
|
99
|
+
|
100
|
+
* rm155
|
101
|
+
|
3
102
|
## 3.2.0 - 2021-06-06
|
4
103
|
|
5
104
|
### Improvements
|
data/README.md
CHANGED
@@ -35,7 +35,7 @@ end
|
|
35
35
|
|
36
36
|
## Development
|
37
37
|
|
38
|
-
After checking out the repo, run `
|
38
|
+
After checking out the repo, run `ruby run-test.rb` to check if your changes can pass the test.
|
39
39
|
|
40
40
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
41
41
|
|
@@ -148,7 +148,7 @@ This example defines and uses a custom write converter to strip whitespace from
|
|
148
148
|
|
149
149
|
==== Recipe: Specify Multiple Write Converters
|
150
150
|
|
151
|
-
Use option <tt>:write_converters</tt> and multiple custom
|
151
|
+
Use option <tt>:write_converters</tt> and multiple custom converters
|
152
152
|
to convert field values when generating \CSV.
|
153
153
|
|
154
154
|
This example defines and uses two custom write converters to strip and upcase generated fields:
|
@@ -83,7 +83,7 @@ Use instance method CSV#each with option +headers+ to read a source \String one
|
|
83
83
|
CSV.new(string, headers: true).each do |row|
|
84
84
|
p row
|
85
85
|
end
|
86
|
-
|
86
|
+
Output:
|
87
87
|
#<CSV::Row "Name":"foo" "Value":"0">
|
88
88
|
#<CSV::Row "Name":"bar" "Value":"1">
|
89
89
|
#<CSV::Row "Name":"baz" "Value":"2">
|
data/lib/csv/fields_converter.rb
CHANGED
@@ -16,7 +16,7 @@ class CSV
|
|
16
16
|
@empty_value = options[:empty_value]
|
17
17
|
@empty_value_is_empty_string = (@empty_value == "")
|
18
18
|
@accept_nil = options[:accept_nil]
|
19
|
-
@
|
19
|
+
@builtin_converters_name = options[:builtin_converters_name]
|
20
20
|
@need_static_convert = need_static_convert?
|
21
21
|
end
|
22
22
|
|
@@ -24,7 +24,7 @@ class CSV
|
|
24
24
|
if name.nil? # custom converter
|
25
25
|
@converters << converter
|
26
26
|
else # named converter
|
27
|
-
combo =
|
27
|
+
combo = builtin_converters[name]
|
28
28
|
case combo
|
29
29
|
when Array # combo converter
|
30
30
|
combo.each do |sub_name|
|
@@ -80,5 +80,9 @@ class CSV
|
|
80
80
|
@need_static_convert or
|
81
81
|
(not @converters.empty?)
|
82
82
|
end
|
83
|
+
|
84
|
+
def builtin_converters
|
85
|
+
@builtin_converters ||= ::CSV.const_get(@builtin_converters_name)
|
86
|
+
end
|
83
87
|
end
|
84
88
|
end
|
data/lib/csv/parser.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require "strscan"
|
4
4
|
|
5
5
|
require_relative "delete_suffix"
|
6
|
+
require_relative "input_record_separator"
|
6
7
|
require_relative "match_p"
|
7
8
|
require_relative "row"
|
8
9
|
require_relative "table"
|
@@ -26,6 +27,10 @@ class CSV
|
|
26
27
|
class InvalidEncoding < StandardError
|
27
28
|
end
|
28
29
|
|
30
|
+
# Raised when unexpected case is happen.
|
31
|
+
class UnexpectedError < StandardError
|
32
|
+
end
|
33
|
+
|
29
34
|
#
|
30
35
|
# CSV::Scanner receives a CSV output, scans it and return the content.
|
31
36
|
# It also controls the life cycle of the object with its methods +keep_start+,
|
@@ -77,16 +82,17 @@ class CSV
|
|
77
82
|
# +keep_end+, +keep_back+, +keep_drop+.
|
78
83
|
#
|
79
84
|
# CSV::InputsScanner.scan() tries to match with pattern at the current position.
|
80
|
-
# If there's a match, the scanner advances the
|
85
|
+
# If there's a match, the scanner advances the "scan pointer" and returns the matched string.
|
81
86
|
# Otherwise, the scanner returns nil.
|
82
87
|
#
|
83
|
-
# CSV::InputsScanner.rest() returns the
|
88
|
+
# CSV::InputsScanner.rest() returns the "rest" of the string (i.e. everything after the scan pointer).
|
84
89
|
# If there is no more data (eos? = true), it returns "".
|
85
90
|
#
|
86
91
|
class InputsScanner
|
87
|
-
def initialize(inputs, encoding, chunk_size: 8192)
|
92
|
+
def initialize(inputs, encoding, row_separator, chunk_size: 8192)
|
88
93
|
@inputs = inputs.dup
|
89
94
|
@encoding = encoding
|
95
|
+
@row_separator = row_separator
|
90
96
|
@chunk_size = chunk_size
|
91
97
|
@last_scanner = @inputs.empty?
|
92
98
|
@keeps = []
|
@@ -94,11 +100,13 @@ class CSV
|
|
94
100
|
end
|
95
101
|
|
96
102
|
def each_line(row_separator)
|
103
|
+
return enum_for(__method__, row_separator) unless block_given?
|
97
104
|
buffer = nil
|
98
105
|
input = @scanner.rest
|
99
106
|
position = @scanner.pos
|
100
107
|
offset = 0
|
101
108
|
n_row_separator_chars = row_separator.size
|
109
|
+
# trace(__method__, :start, line, input)
|
102
110
|
while true
|
103
111
|
input.each_line(row_separator) do |line|
|
104
112
|
@scanner.pos += line.bytesize
|
@@ -138,25 +146,28 @@ class CSV
|
|
138
146
|
end
|
139
147
|
|
140
148
|
def scan(pattern)
|
149
|
+
# trace(__method__, pattern, :start)
|
141
150
|
value = @scanner.scan(pattern)
|
151
|
+
# trace(__method__, pattern, :done, :last, value) if @last_scanner
|
142
152
|
return value if @last_scanner
|
143
153
|
|
144
|
-
if value
|
145
|
-
|
146
|
-
|
147
|
-
else
|
148
|
-
nil
|
149
|
-
end
|
154
|
+
read_chunk if value and @scanner.eos?
|
155
|
+
# trace(__method__, pattern, :done, value)
|
156
|
+
value
|
150
157
|
end
|
151
158
|
|
152
159
|
def scan_all(pattern)
|
160
|
+
# trace(__method__, pattern, :start)
|
153
161
|
value = @scanner.scan(pattern)
|
162
|
+
# trace(__method__, pattern, :done, :last, value) if @last_scanner
|
154
163
|
return value if @last_scanner
|
155
164
|
|
156
165
|
return nil if value.nil?
|
157
166
|
while @scanner.eos? and read_chunk and (sub_value = @scanner.scan(pattern))
|
167
|
+
# trace(__method__, pattern, :sub, sub_value)
|
158
168
|
value << sub_value
|
159
169
|
end
|
170
|
+
# trace(__method__, pattern, :done, value)
|
160
171
|
value
|
161
172
|
end
|
162
173
|
|
@@ -165,76 +176,135 @@ class CSV
|
|
165
176
|
end
|
166
177
|
|
167
178
|
def keep_start
|
168
|
-
|
179
|
+
# trace(__method__, :start)
|
180
|
+
adjust_last_keep
|
181
|
+
@keeps.push([@scanner, @scanner.pos, nil])
|
182
|
+
# trace(__method__, :done)
|
169
183
|
end
|
170
184
|
|
171
185
|
def keep_end
|
172
|
-
|
173
|
-
|
186
|
+
# trace(__method__, :start)
|
187
|
+
scanner, start, buffer = @keeps.pop
|
188
|
+
if scanner == @scanner
|
189
|
+
keep = @scanner.string.byteslice(start, @scanner.pos - start)
|
190
|
+
else
|
191
|
+
keep = @scanner.string.byteslice(0, @scanner.pos)
|
192
|
+
end
|
174
193
|
if buffer
|
175
194
|
buffer << keep
|
176
195
|
keep = buffer
|
177
196
|
end
|
197
|
+
# trace(__method__, :done, keep)
|
178
198
|
keep
|
179
199
|
end
|
180
200
|
|
181
201
|
def keep_back
|
182
|
-
|
202
|
+
# trace(__method__, :start)
|
203
|
+
scanner, start, buffer = @keeps.pop
|
183
204
|
if buffer
|
205
|
+
# trace(__method__, :rescan, start, buffer)
|
184
206
|
string = @scanner.string
|
185
|
-
|
207
|
+
if scanner == @scanner
|
208
|
+
keep = string.byteslice(start, string.bytesize - start)
|
209
|
+
else
|
210
|
+
keep = string
|
211
|
+
end
|
186
212
|
if keep and not keep.empty?
|
187
213
|
@inputs.unshift(StringIO.new(keep))
|
188
214
|
@last_scanner = false
|
189
215
|
end
|
190
216
|
@scanner = StringScanner.new(buffer)
|
191
217
|
else
|
218
|
+
if @scanner != scanner
|
219
|
+
message = "scanners are different but no buffer: "
|
220
|
+
message += "#{@scanner.inspect}(#{@scanner.object_id}): "
|
221
|
+
message += "#{scanner.inspect}(#{scanner.object_id})"
|
222
|
+
raise UnexpectedError, message
|
223
|
+
end
|
224
|
+
# trace(__method__, :repos, start, buffer)
|
192
225
|
@scanner.pos = start
|
193
226
|
end
|
194
227
|
read_chunk if @scanner.eos?
|
195
228
|
end
|
196
229
|
|
197
230
|
def keep_drop
|
198
|
-
@keeps.pop
|
231
|
+
_, _, buffer = @keeps.pop
|
232
|
+
# trace(__method__, :done, :empty) unless buffer
|
233
|
+
return unless buffer
|
234
|
+
|
235
|
+
last_keep = @keeps.last
|
236
|
+
# trace(__method__, :done, :no_last_keep) unless last_keep
|
237
|
+
return unless last_keep
|
238
|
+
|
239
|
+
if last_keep[2]
|
240
|
+
last_keep[2] << buffer
|
241
|
+
else
|
242
|
+
last_keep[2] = buffer
|
243
|
+
end
|
244
|
+
# trace(__method__, :done)
|
199
245
|
end
|
200
246
|
|
201
247
|
def rest
|
202
248
|
@scanner.rest
|
203
249
|
end
|
204
250
|
|
251
|
+
def check(pattern)
|
252
|
+
@scanner.check(pattern)
|
253
|
+
end
|
254
|
+
|
205
255
|
private
|
206
|
-
def
|
207
|
-
|
256
|
+
def trace(*args)
|
257
|
+
pp([*args, @scanner, @scanner&.string, @scanner&.pos, @keeps])
|
258
|
+
end
|
208
259
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
260
|
+
def adjust_last_keep
|
261
|
+
# trace(__method__, :start)
|
262
|
+
|
263
|
+
keep = @keeps.last
|
264
|
+
# trace(__method__, :done, :empty) if keep.nil?
|
265
|
+
return if keep.nil?
|
266
|
+
|
267
|
+
scanner, start, buffer = keep
|
268
|
+
string = @scanner.string
|
269
|
+
if @scanner != scanner
|
270
|
+
start = 0
|
271
|
+
end
|
272
|
+
if start == 0 and @scanner.eos?
|
273
|
+
keep_data = string
|
274
|
+
else
|
275
|
+
keep_data = string.byteslice(start, @scanner.pos - start)
|
276
|
+
end
|
277
|
+
if keep_data
|
278
|
+
if buffer
|
279
|
+
buffer << keep_data
|
280
|
+
else
|
281
|
+
keep[2] = keep_data.dup
|
221
282
|
end
|
222
|
-
keep[0] = 0
|
223
283
|
end
|
224
284
|
|
285
|
+
# trace(__method__, :done)
|
286
|
+
end
|
287
|
+
|
288
|
+
def read_chunk
|
289
|
+
return false if @last_scanner
|
290
|
+
|
291
|
+
adjust_last_keep
|
292
|
+
|
225
293
|
input = @inputs.first
|
226
294
|
case input
|
227
295
|
when StringIO
|
228
296
|
string = input.read
|
229
297
|
raise InvalidEncoding unless string.valid_encoding?
|
298
|
+
# trace(__method__, :stringio, string)
|
230
299
|
@scanner = StringScanner.new(string)
|
231
300
|
@inputs.shift
|
232
301
|
@last_scanner = @inputs.empty?
|
233
302
|
true
|
234
303
|
else
|
235
|
-
chunk = input.gets(
|
304
|
+
chunk = input.gets(@row_separator, @chunk_size)
|
236
305
|
if chunk
|
237
306
|
raise InvalidEncoding unless chunk.valid_encoding?
|
307
|
+
# trace(__method__, :chunk, chunk)
|
238
308
|
@scanner = StringScanner.new(chunk)
|
239
309
|
if input.respond_to?(:eof?) and input.eof?
|
240
310
|
@inputs.shift
|
@@ -242,6 +312,7 @@ class CSV
|
|
242
312
|
end
|
243
313
|
true
|
244
314
|
else
|
315
|
+
# trace(__method__, :no_chunk)
|
245
316
|
@scanner = StringScanner.new("".encode(@encoding))
|
246
317
|
@inputs.shift
|
247
318
|
@last_scanner = @inputs.empty?
|
@@ -276,7 +347,11 @@ class CSV
|
|
276
347
|
end
|
277
348
|
|
278
349
|
def field_size_limit
|
279
|
-
@
|
350
|
+
@max_field_size&.succ
|
351
|
+
end
|
352
|
+
|
353
|
+
def max_field_size
|
354
|
+
@max_field_size
|
280
355
|
end
|
281
356
|
|
282
357
|
def skip_lines
|
@@ -344,6 +419,16 @@ class CSV
|
|
344
419
|
end
|
345
420
|
message = "Invalid byte sequence in #{@encoding}"
|
346
421
|
raise MalformedCSVError.new(message, lineno)
|
422
|
+
rescue UnexpectedError => error
|
423
|
+
if @scanner
|
424
|
+
ignore_broken_line
|
425
|
+
lineno = @lineno
|
426
|
+
else
|
427
|
+
lineno = @lineno + 1
|
428
|
+
end
|
429
|
+
message = "This should not be happen: #{error.message}: "
|
430
|
+
message += "Please report this to https://github.com/ruby/csv/issues"
|
431
|
+
raise MalformedCSVError.new(message, lineno)
|
347
432
|
end
|
348
433
|
end
|
349
434
|
|
@@ -360,6 +445,7 @@ class CSV
|
|
360
445
|
prepare_skip_lines
|
361
446
|
prepare_strip
|
362
447
|
prepare_separators
|
448
|
+
validate_strip_and_col_sep_options
|
363
449
|
prepare_quoted
|
364
450
|
prepare_unquoted
|
365
451
|
prepare_line
|
@@ -387,7 +473,7 @@ class CSV
|
|
387
473
|
@backslash_quote = false
|
388
474
|
end
|
389
475
|
@unconverted_fields = @options[:unconverted_fields]
|
390
|
-
@
|
476
|
+
@max_field_size = @options[:max_field_size]
|
391
477
|
@skip_blanks = @options[:skip_blanks]
|
392
478
|
@fields_converter = @options[:fields_converter]
|
393
479
|
@header_fields_converter = @options[:header_fields_converter]
|
@@ -479,9 +565,9 @@ class CSV
|
|
479
565
|
begin
|
480
566
|
StringScanner.new("x").scan("x")
|
481
567
|
rescue TypeError
|
482
|
-
|
568
|
+
STRING_SCANNER_SCAN_ACCEPT_STRING = false
|
483
569
|
else
|
484
|
-
|
570
|
+
STRING_SCANNER_SCAN_ACCEPT_STRING = true
|
485
571
|
end
|
486
572
|
|
487
573
|
def prepare_separators
|
@@ -505,7 +591,7 @@ class CSV
|
|
505
591
|
@first_column_separators = Regexp.new(@escaped_first_column_separator +
|
506
592
|
"+".encode(@encoding))
|
507
593
|
else
|
508
|
-
if
|
594
|
+
if STRING_SCANNER_SCAN_ACCEPT_STRING
|
509
595
|
@column_end = @column_separator
|
510
596
|
else
|
511
597
|
@column_end = Regexp.new(@escaped_column_separator)
|
@@ -526,10 +612,32 @@ class CSV
|
|
526
612
|
|
527
613
|
@cr = "\r".encode(@encoding)
|
528
614
|
@lf = "\n".encode(@encoding)
|
529
|
-
@
|
615
|
+
@line_end = Regexp.new("\r\n|\n|\r".encode(@encoding))
|
530
616
|
@not_line_end = Regexp.new("[^\r\n]+".encode(@encoding))
|
531
617
|
end
|
532
618
|
|
619
|
+
# This method verifies that there are no (obvious) ambiguities with the
|
620
|
+
# provided +col_sep+ and +strip+ parsing options. For example, if +col_sep+
|
621
|
+
# and +strip+ were both equal to +\t+, then there would be no clear way to
|
622
|
+
# parse the input.
|
623
|
+
def validate_strip_and_col_sep_options
|
624
|
+
return unless @strip
|
625
|
+
|
626
|
+
if @strip.is_a?(String)
|
627
|
+
if @column_separator.start_with?(@strip) || @column_separator.end_with?(@strip)
|
628
|
+
raise ArgumentError,
|
629
|
+
"The provided strip (#{@escaped_strip}) and " \
|
630
|
+
"col_sep (#{@escaped_column_separator}) options are incompatible."
|
631
|
+
end
|
632
|
+
else
|
633
|
+
if Regexp.new("\\A[#{@escaped_strip}]|[#{@escaped_strip}]\\z").match?(@column_separator)
|
634
|
+
raise ArgumentError,
|
635
|
+
"The provided strip (true) and " \
|
636
|
+
"col_sep (#{@escaped_column_separator}) options are incompatible."
|
637
|
+
end
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
533
641
|
def prepare_quoted
|
534
642
|
if @quote_character
|
535
643
|
@quotes = Regexp.new(@escaped_quote_character +
|
@@ -605,7 +713,7 @@ class CSV
|
|
605
713
|
# do nothing: ensure will set default
|
606
714
|
end
|
607
715
|
end
|
608
|
-
separator =
|
716
|
+
separator = InputRecordSeparator.value if separator == :auto
|
609
717
|
end
|
610
718
|
separator.to_s.encode(@encoding)
|
611
719
|
end
|
@@ -704,26 +812,28 @@ class CSV
|
|
704
812
|
sample[0, 128].index(@quote_character)
|
705
813
|
end
|
706
814
|
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
@io = StringIO.new(string, "rb:#{string.encoding}")
|
712
|
-
end
|
815
|
+
class UnoptimizedStringIO # :nodoc:
|
816
|
+
def initialize(string)
|
817
|
+
@io = StringIO.new(string, "rb:#{string.encoding}")
|
818
|
+
end
|
713
819
|
|
714
|
-
|
715
|
-
|
716
|
-
|
820
|
+
def gets(*args)
|
821
|
+
@io.gets(*args)
|
822
|
+
end
|
717
823
|
|
718
|
-
|
719
|
-
|
720
|
-
|
824
|
+
def each_line(*args, &block)
|
825
|
+
@io.each_line(*args, &block)
|
826
|
+
end
|
721
827
|
|
722
|
-
|
723
|
-
|
724
|
-
end
|
828
|
+
def eof?
|
829
|
+
@io.eof?
|
725
830
|
end
|
831
|
+
end
|
726
832
|
|
833
|
+
SCANNER_TEST = (ENV["CSV_PARSER_SCANNER_TEST"] == "yes")
|
834
|
+
if SCANNER_TEST
|
835
|
+
SCANNER_TEST_CHUNK_SIZE_NAME = "CSV_PARSER_SCANNER_TEST_CHUNK_SIZE"
|
836
|
+
SCANNER_TEST_CHUNK_SIZE_VALUE = ENV[SCANNER_TEST_CHUNK_SIZE_NAME]
|
727
837
|
def build_scanner
|
728
838
|
inputs = @samples.collect do |sample|
|
729
839
|
UnoptimizedStringIO.new(sample)
|
@@ -733,17 +843,27 @@ class CSV
|
|
733
843
|
else
|
734
844
|
inputs << @input
|
735
845
|
end
|
736
|
-
|
846
|
+
begin
|
847
|
+
chunk_size_value = ENV[SCANNER_TEST_CHUNK_SIZE_NAME]
|
848
|
+
rescue # Ractor::IsolationError
|
849
|
+
# Ractor on Ruby 3.0 can't read ENV value.
|
850
|
+
chunk_size_value = SCANNER_TEST_CHUNK_SIZE_VALUE
|
851
|
+
end
|
852
|
+
chunk_size = Integer((chunk_size_value || "1"), 10)
|
737
853
|
InputsScanner.new(inputs,
|
738
854
|
@encoding,
|
739
|
-
|
855
|
+
@row_separator,
|
856
|
+
chunk_size: chunk_size)
|
740
857
|
end
|
741
858
|
else
|
742
859
|
def build_scanner
|
743
860
|
string = nil
|
744
861
|
if @samples.empty? and @input.is_a?(StringIO)
|
745
862
|
string = @input.read
|
746
|
-
elsif @samples.size == 1 and
|
863
|
+
elsif @samples.size == 1 and
|
864
|
+
@input != ARGF and
|
865
|
+
@input.respond_to?(:eof?) and
|
866
|
+
@input.eof?
|
747
867
|
string = @samples[0]
|
748
868
|
end
|
749
869
|
if string
|
@@ -762,7 +882,7 @@ class CSV
|
|
762
882
|
StringIO.new(sample)
|
763
883
|
end
|
764
884
|
inputs << @input
|
765
|
-
InputsScanner.new(inputs, @encoding)
|
885
|
+
InputsScanner.new(inputs, @encoding, @row_separator)
|
766
886
|
end
|
767
887
|
end
|
768
888
|
end
|
@@ -796,6 +916,14 @@ class CSV
|
|
796
916
|
end
|
797
917
|
end
|
798
918
|
|
919
|
+
def validate_field_size(field)
|
920
|
+
return unless @max_field_size
|
921
|
+
return if field.size <= @max_field_size
|
922
|
+
ignore_broken_line
|
923
|
+
message = "Field size exceeded: #{field.size} > #{@max_field_size}"
|
924
|
+
raise MalformedCSVError.new(message, @lineno)
|
925
|
+
end
|
926
|
+
|
799
927
|
def parse_no_quote(&block)
|
800
928
|
@scanner.each_line(@row_separator) do |line|
|
801
929
|
next if @skip_lines and skip_line?(line)
|
@@ -808,6 +936,11 @@ class CSV
|
|
808
936
|
else
|
809
937
|
line = strip_value(line)
|
810
938
|
row = line.split(@split_column_separator, -1)
|
939
|
+
if @max_field_size
|
940
|
+
row.each do |column|
|
941
|
+
validate_field_size(column)
|
942
|
+
end
|
943
|
+
end
|
811
944
|
n_columns = row.size
|
812
945
|
i = 0
|
813
946
|
while i < n_columns
|
@@ -863,6 +996,7 @@ class CSV
|
|
863
996
|
@need_robust_parsing = true
|
864
997
|
return parse_quotable_robust(&block)
|
865
998
|
end
|
999
|
+
validate_field_size(row[i])
|
866
1000
|
end
|
867
1001
|
i += 1
|
868
1002
|
end
|
@@ -886,10 +1020,7 @@ class CSV
|
|
886
1020
|
value = parse_column_value
|
887
1021
|
if value
|
888
1022
|
@scanner.scan_all(@strip_value) if @strip_value
|
889
|
-
|
890
|
-
ignore_broken_line
|
891
|
-
raise MalformedCSVError.new("Field size exceeded", @lineno)
|
892
|
-
end
|
1023
|
+
validate_field_size(value)
|
893
1024
|
end
|
894
1025
|
if parse_column_end
|
895
1026
|
row << value
|
@@ -910,11 +1041,17 @@ class CSV
|
|
910
1041
|
break
|
911
1042
|
else
|
912
1043
|
if @quoted_column_value
|
1044
|
+
if liberal_parsing? and (new_line = @scanner.check(@line_end))
|
1045
|
+
message =
|
1046
|
+
"Illegal end-of-line sequence outside of a quoted field " +
|
1047
|
+
"<#{new_line.inspect}>"
|
1048
|
+
else
|
1049
|
+
message = "Any value after quoted field isn't allowed"
|
1050
|
+
end
|
913
1051
|
ignore_broken_line
|
914
|
-
message = "Any value after quoted field isn't allowed"
|
915
1052
|
raise MalformedCSVError.new(message, @lineno)
|
916
1053
|
elsif @unquoted_column_value and
|
917
|
-
(new_line = @scanner.scan(@
|
1054
|
+
(new_line = @scanner.scan(@line_end))
|
918
1055
|
ignore_broken_line
|
919
1056
|
message = "Unquoted fields do not allow new line " +
|
920
1057
|
"<#{new_line.inspect}>"
|
@@ -923,7 +1060,7 @@ class CSV
|
|
923
1060
|
ignore_broken_line
|
924
1061
|
message = "Illegal quoting"
|
925
1062
|
raise MalformedCSVError.new(message, @lineno)
|
926
|
-
elsif (new_line = @scanner.scan(@
|
1063
|
+
elsif (new_line = @scanner.scan(@line_end))
|
927
1064
|
ignore_broken_line
|
928
1065
|
message = "New line must be <#{@row_separator.inspect}> " +
|
929
1066
|
"not <#{new_line.inspect}>"
|
@@ -1089,7 +1226,7 @@ class CSV
|
|
1089
1226
|
|
1090
1227
|
def ignore_broken_line
|
1091
1228
|
@scanner.scan_all(@not_line_end)
|
1092
|
-
@scanner.scan_all(@
|
1229
|
+
@scanner.scan_all(@line_end)
|
1093
1230
|
@lineno += 1
|
1094
1231
|
end
|
1095
1232
|
|
data/lib/csv/table.rb
CHANGED
@@ -999,9 +999,15 @@ class CSV
|
|
999
999
|
# Omits the headers if option +write_headers+ is given as +false+
|
1000
1000
|
# (see {Option +write_headers+}[../CSV.html#class-CSV-label-Option+write_headers]):
|
1001
1001
|
# table.to_csv(write_headers: false) # => "foo,0\nbar,1\nbaz,2\n"
|
1002
|
-
|
1002
|
+
#
|
1003
|
+
# Limit rows if option +limit+ is given like +2+:
|
1004
|
+
# table.to_csv(limit: 2) # => "Name,Value\nfoo,0\nbar,1\n"
|
1005
|
+
def to_csv(write_headers: true, limit: nil, **options)
|
1003
1006
|
array = write_headers ? [headers.to_csv(**options)] : []
|
1004
|
-
@table.
|
1007
|
+
limit ||= @table.size
|
1008
|
+
limit = @table.size + 1 + limit if limit < 0
|
1009
|
+
limit = 0 if limit < 0
|
1010
|
+
@table.first(limit).each do |row|
|
1005
1011
|
array.push(row.fields.to_csv(**options)) unless row.header_row?
|
1006
1012
|
end
|
1007
1013
|
|
@@ -1038,9 +1044,13 @@ class CSV
|
|
1038
1044
|
# Example:
|
1039
1045
|
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
|
1040
1046
|
# table = CSV.parse(source, headers: true)
|
1041
|
-
# table.inspect # => "#<CSV::Table mode:col_or_row row_count:4
|
1047
|
+
# table.inspect # => "#<CSV::Table mode:col_or_row row_count:4>\nName,Value\nfoo,0\nbar,1\nbaz,2\n"
|
1048
|
+
#
|
1042
1049
|
def inspect
|
1043
|
-
"#<#{self.class} mode:#{@mode} row_count:#{to_a.size}>"
|
1050
|
+
inspected = +"#<#{self.class} mode:#{@mode} row_count:#{to_a.size}>"
|
1051
|
+
summary = to_csv(limit: 5)
|
1052
|
+
inspected << "\n" << summary if summary.encoding.ascii_compatible?
|
1053
|
+
inspected
|
1044
1054
|
end
|
1045
1055
|
end
|
1046
1056
|
end
|
data/lib/csv/version.rb
CHANGED
data/lib/csv/writer.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "input_record_separator"
|
3
4
|
require_relative "match_p"
|
4
5
|
require_relative "row"
|
5
6
|
|
@@ -133,7 +134,7 @@ class CSV
|
|
133
134
|
@column_separator = @options[:column_separator].to_s.encode(@encoding)
|
134
135
|
row_separator = @options[:row_separator]
|
135
136
|
if row_separator == :auto
|
136
|
-
@row_separator =
|
137
|
+
@row_separator = InputRecordSeparator.value.encode(@encoding)
|
137
138
|
else
|
138
139
|
@row_separator = row_separator.to_s.encode(@encoding)
|
139
140
|
end
|
data/lib/csv.rb
CHANGED
@@ -90,11 +90,11 @@
|
|
90
90
|
# with any questions.
|
91
91
|
|
92
92
|
require "forwardable"
|
93
|
-
require "English"
|
94
93
|
require "date"
|
95
94
|
require "stringio"
|
96
95
|
|
97
96
|
require_relative "csv/fields_converter"
|
97
|
+
require_relative "csv/input_record_separator"
|
98
98
|
require_relative "csv/match_p"
|
99
99
|
require_relative "csv/parser"
|
100
100
|
require_relative "csv/row"
|
@@ -341,6 +341,7 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
|
|
341
341
|
# liberal_parsing: false,
|
342
342
|
# nil_value: nil,
|
343
343
|
# empty_value: "",
|
344
|
+
# strip: false,
|
344
345
|
# # For generating.
|
345
346
|
# write_headers: nil,
|
346
347
|
# quote_empty: true,
|
@@ -348,7 +349,6 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
|
|
348
349
|
# write_converters: nil,
|
349
350
|
# write_nil_value: nil,
|
350
351
|
# write_empty_value: "",
|
351
|
-
# strip: false,
|
352
352
|
# }
|
353
353
|
#
|
354
354
|
# ==== Options for Parsing
|
@@ -357,7 +357,9 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
|
|
357
357
|
# - +row_sep+: Specifies the row separator; used to delimit rows.
|
358
358
|
# - +col_sep+: Specifies the column separator; used to delimit fields.
|
359
359
|
# - +quote_char+: Specifies the quote character; used to quote fields.
|
360
|
-
# - +field_size_limit+: Specifies the maximum field size allowed.
|
360
|
+
# - +field_size_limit+: Specifies the maximum field size + 1 allowed.
|
361
|
+
# Deprecated since 3.2.3. Use +max_field_size+ instead.
|
362
|
+
# - +max_field_size+: Specifies the maximum field size allowed.
|
361
363
|
# - +converters+: Specifies the field converters to be used.
|
362
364
|
# - +unconverted_fields+: Specifies whether unconverted fields are to be available.
|
363
365
|
# - +headers+: Specifies whether data contains headers,
|
@@ -366,8 +368,9 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
|
|
366
368
|
# - +header_converters+: Specifies the header converters to be used.
|
367
369
|
# - +skip_blanks+: Specifies whether blanks lines are to be ignored.
|
368
370
|
# - +skip_lines+: Specifies how comments lines are to be recognized.
|
369
|
-
# - +strip+: Specifies whether leading and trailing whitespace are
|
370
|
-
#
|
371
|
+
# - +strip+: Specifies whether leading and trailing whitespace are to be
|
372
|
+
# stripped from fields. This must be compatible with +col_sep+; if it is not,
|
373
|
+
# then an +ArgumentError+ exception will be raised.
|
371
374
|
# - +liberal_parsing+: Specifies whether \CSV should attempt to parse
|
372
375
|
# non-compliant data.
|
373
376
|
# - +nil_value+: Specifies the object that is to be substituted for each null (no-text) field.
|
@@ -513,7 +516,7 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
|
|
513
516
|
# [" 1 ", #<struct CSV::FieldInfo index=1, line=2, header=nil>]
|
514
517
|
# [" baz ", #<struct CSV::FieldInfo index=0, line=3, header=nil>]
|
515
518
|
# [" 2 ", #<struct CSV::FieldInfo index=1, line=3, header=nil>]
|
516
|
-
# Each CSV::
|
519
|
+
# Each CSV::FieldInfo object shows:
|
517
520
|
# - The 0-based field index.
|
518
521
|
# - The 1-based line index.
|
519
522
|
# - The field header, if any.
|
@@ -547,6 +550,14 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
|
|
547
550
|
#
|
548
551
|
# There is no such storage structure for write headers.
|
549
552
|
#
|
553
|
+
# In order for the parsing methods to access stored converters in non-main-Ractors, the
|
554
|
+
# storage structure must be made shareable first.
|
555
|
+
# Therefore, <tt>Ractor.make_shareable(CSV::Converters)</tt> and
|
556
|
+
# <tt>Ractor.make_shareable(CSV::HeaderConverters)</tt> must be called before the creation
|
557
|
+
# of Ractors that use the converters stored in these structures. (Since making the storage
|
558
|
+
# structures shareable involves freezing them, any custom converters that are to be used
|
559
|
+
# must be added first.)
|
560
|
+
#
|
550
561
|
# ===== Converter Lists
|
551
562
|
#
|
552
563
|
# A _converter_ _list_ is an \Array that may include any assortment of:
|
@@ -917,8 +928,10 @@ class CSV
|
|
917
928
|
symbol: lambda { |h|
|
918
929
|
h.encode(ConverterEncoding).downcase.gsub(/[^\s\w]+/, "").strip.
|
919
930
|
gsub(/\s+/, "_").to_sym
|
920
|
-
}
|
931
|
+
},
|
932
|
+
symbol_raw: lambda { |h| h.encode(ConverterEncoding).to_sym }
|
921
933
|
}
|
934
|
+
|
922
935
|
# Default values for method options.
|
923
936
|
DEFAULT_OPTIONS = {
|
924
937
|
# For both parsing and generating.
|
@@ -927,6 +940,7 @@ class CSV
|
|
927
940
|
quote_char: '"',
|
928
941
|
# For parsing.
|
929
942
|
field_size_limit: nil,
|
943
|
+
max_field_size: nil,
|
930
944
|
converters: nil,
|
931
945
|
unconverted_fields: nil,
|
932
946
|
headers: false,
|
@@ -937,6 +951,7 @@ class CSV
|
|
937
951
|
liberal_parsing: false,
|
938
952
|
nil_value: nil,
|
939
953
|
empty_value: "",
|
954
|
+
strip: false,
|
940
955
|
# For generating.
|
941
956
|
write_headers: nil,
|
942
957
|
quote_empty: true,
|
@@ -944,7 +959,6 @@ class CSV
|
|
944
959
|
write_converters: nil,
|
945
960
|
write_nil_value: nil,
|
946
961
|
write_empty_value: "",
|
947
|
-
strip: false,
|
948
962
|
}.freeze
|
949
963
|
|
950
964
|
class << self
|
@@ -957,6 +971,8 @@ class CSV
|
|
957
971
|
# Creates or retrieves cached \CSV objects.
|
958
972
|
# For arguments and options, see CSV.new.
|
959
973
|
#
|
974
|
+
# This API is not Ractor-safe.
|
975
|
+
#
|
960
976
|
# ---
|
961
977
|
#
|
962
978
|
# With no block given, returns a \CSV object.
|
@@ -1187,7 +1203,7 @@ class CSV
|
|
1187
1203
|
# See {Options for Parsing}[#class-CSV-label-Options+for+Parsing].
|
1188
1204
|
def filter(input=nil, output=nil, **options)
|
1189
1205
|
# parse options for input, output, or both
|
1190
|
-
in_options, out_options = Hash.new, {row_sep:
|
1206
|
+
in_options, out_options = Hash.new, {row_sep: InputRecordSeparator.value}
|
1191
1207
|
options.each do |key, value|
|
1192
1208
|
case key.to_s
|
1193
1209
|
when /\Ain(?:put)?_(.+)\Z/
|
@@ -1407,8 +1423,8 @@ class CSV
|
|
1407
1423
|
# Argument +ary+ must be an \Array.
|
1408
1424
|
#
|
1409
1425
|
# Special options:
|
1410
|
-
# * Option <tt>:row_sep</tt> defaults to <tt
|
1411
|
-
# (<tt>$/</tt>).:
|
1426
|
+
# * Option <tt>:row_sep</tt> defaults to <tt>"\n"> on Ruby 3.0 or later
|
1427
|
+
# and <tt>$INPUT_RECORD_SEPARATOR</tt> (<tt>$/</tt>) otherwise.:
|
1412
1428
|
# $INPUT_RECORD_SEPARATOR # => "\n"
|
1413
1429
|
# * This method accepts an additional option, <tt>:encoding</tt>, which sets the base
|
1414
1430
|
# Encoding for the output. This method will try to guess your Encoding from
|
@@ -1430,7 +1446,7 @@ class CSV
|
|
1430
1446
|
# CSV.generate_line(:foo)
|
1431
1447
|
#
|
1432
1448
|
def generate_line(row, **options)
|
1433
|
-
options = {row_sep:
|
1449
|
+
options = {row_sep: InputRecordSeparator.value}.merge(options)
|
1434
1450
|
str = +""
|
1435
1451
|
if options[:encoding]
|
1436
1452
|
str.force_encoding(options[:encoding])
|
@@ -1853,6 +1869,7 @@ class CSV
|
|
1853
1869
|
row_sep: :auto,
|
1854
1870
|
quote_char: '"',
|
1855
1871
|
field_size_limit: nil,
|
1872
|
+
max_field_size: nil,
|
1856
1873
|
converters: nil,
|
1857
1874
|
unconverted_fields: nil,
|
1858
1875
|
headers: false,
|
@@ -1868,11 +1885,11 @@ class CSV
|
|
1868
1885
|
encoding: nil,
|
1869
1886
|
nil_value: nil,
|
1870
1887
|
empty_value: "",
|
1888
|
+
strip: false,
|
1871
1889
|
quote_empty: true,
|
1872
1890
|
write_converters: nil,
|
1873
1891
|
write_nil_value: nil,
|
1874
|
-
write_empty_value: ""
|
1875
|
-
strip: false)
|
1892
|
+
write_empty_value: "")
|
1876
1893
|
raise ArgumentError.new("Cannot parse nil as CSV") if data.nil?
|
1877
1894
|
|
1878
1895
|
if data.is_a?(String)
|
@@ -1895,11 +1912,14 @@ class CSV
|
|
1895
1912
|
@initial_header_converters = header_converters
|
1896
1913
|
@initial_write_converters = write_converters
|
1897
1914
|
|
1915
|
+
if max_field_size.nil? and field_size_limit
|
1916
|
+
max_field_size = field_size_limit - 1
|
1917
|
+
end
|
1898
1918
|
@parser_options = {
|
1899
1919
|
column_separator: col_sep,
|
1900
1920
|
row_separator: row_sep,
|
1901
1921
|
quote_character: quote_char,
|
1902
|
-
|
1922
|
+
max_field_size: max_field_size,
|
1903
1923
|
unconverted_fields: unconverted_fields,
|
1904
1924
|
headers: headers,
|
1905
1925
|
return_headers: return_headers,
|
@@ -1967,10 +1987,24 @@ class CSV
|
|
1967
1987
|
# Returns the limit for field size; used for parsing;
|
1968
1988
|
# see {Option +field_size_limit+}[#class-CSV-label-Option+field_size_limit]:
|
1969
1989
|
# CSV.new('').field_size_limit # => nil
|
1990
|
+
#
|
1991
|
+
# Deprecated since 3.2.3. Use +max_field_size+ instead.
|
1970
1992
|
def field_size_limit
|
1971
1993
|
parser.field_size_limit
|
1972
1994
|
end
|
1973
1995
|
|
1996
|
+
# :call-seq:
|
1997
|
+
# csv.max_field_size -> integer or nil
|
1998
|
+
#
|
1999
|
+
# Returns the limit for field size; used for parsing;
|
2000
|
+
# see {Option +max_field_size+}[#class-CSV-label-Option+max_field_size]:
|
2001
|
+
# CSV.new('').max_field_size # => nil
|
2002
|
+
#
|
2003
|
+
# Since 3.2.3.
|
2004
|
+
def max_field_size
|
2005
|
+
parser.max_field_size
|
2006
|
+
end
|
2007
|
+
|
1974
2008
|
# :call-seq:
|
1975
2009
|
# csv.skip_lines -> regexp or nil
|
1976
2010
|
#
|
@@ -1992,6 +2026,10 @@ class CSV
|
|
1992
2026
|
# csv.converters # => [:integer]
|
1993
2027
|
# csv.convert(proc {|x| x.to_s })
|
1994
2028
|
# csv.converters
|
2029
|
+
#
|
2030
|
+
# Notes that you need to call
|
2031
|
+
# +Ractor.make_shareable(CSV::Converters)+ on the main Ractor to use
|
2032
|
+
# this method.
|
1995
2033
|
def converters
|
1996
2034
|
parser_fields_converter.map do |converter|
|
1997
2035
|
name = Converters.rassoc(converter)
|
@@ -2054,6 +2092,10 @@ class CSV
|
|
2054
2092
|
# Returns an \Array containing header converters; used for parsing;
|
2055
2093
|
# see {Header Converters}[#class-CSV-label-Header+Converters]:
|
2056
2094
|
# CSV.new('').header_converters # => []
|
2095
|
+
#
|
2096
|
+
# Notes that you need to call
|
2097
|
+
# +Ractor.make_shareable(CSV::HeaderConverters)+ on the main Ractor
|
2098
|
+
# to use this method.
|
2057
2099
|
def header_converters
|
2058
2100
|
header_fields_converter.map do |converter|
|
2059
2101
|
name = HeaderConverters.rassoc(converter)
|
@@ -2694,7 +2736,7 @@ class CSV
|
|
2694
2736
|
|
2695
2737
|
def build_parser_fields_converter
|
2696
2738
|
specific_options = {
|
2697
|
-
|
2739
|
+
builtin_converters_name: :Converters,
|
2698
2740
|
}
|
2699
2741
|
options = @base_fields_converter_options.merge(specific_options)
|
2700
2742
|
build_fields_converter(@initial_converters, options)
|
@@ -2706,7 +2748,7 @@ class CSV
|
|
2706
2748
|
|
2707
2749
|
def build_header_fields_converter
|
2708
2750
|
specific_options = {
|
2709
|
-
|
2751
|
+
builtin_converters_name: :HeaderConverters,
|
2710
2752
|
accept_nil: true,
|
2711
2753
|
}
|
2712
2754
|
options = @base_fields_converter_options.merge(specific_options)
|
@@ -2774,6 +2816,8 @@ end
|
|
2774
2816
|
# io = StringIO.new
|
2775
2817
|
# CSV(io, col_sep: ";") { |csv| csv << ["a", "b", "c"] }
|
2776
2818
|
#
|
2819
|
+
# This API is not Ractor-safe.
|
2820
|
+
#
|
2777
2821
|
def CSV(*args, **options, &block)
|
2778
2822
|
CSV.instance(*args, **options, &block)
|
2779
2823
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: csv
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.2.
|
4
|
+
version: 3.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Edward Gray II
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2022-04-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -59,14 +59,14 @@ dependencies:
|
|
59
59
|
requirements:
|
60
60
|
- - ">="
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: 3.4.
|
62
|
+
version: 3.4.8
|
63
63
|
type: :development
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
67
|
- - ">="
|
68
68
|
- !ruby/object:Gem::Version
|
69
|
-
version: 3.4.
|
69
|
+
version: 3.4.8
|
70
70
|
description: The CSV library provides a complete interface to CSV files and data.
|
71
71
|
It offers tools to enable you to read and write to and from Strings or IO objects,
|
72
72
|
as needed.
|
@@ -118,6 +118,7 @@ files:
|
|
118
118
|
- lib/csv/core_ext/string.rb
|
119
119
|
- lib/csv/delete_suffix.rb
|
120
120
|
- lib/csv/fields_converter.rb
|
121
|
+
- lib/csv/input_record_separator.rb
|
121
122
|
- lib/csv/match_p.rb
|
122
123
|
- lib/csv/parser.rb
|
123
124
|
- lib/csv/row.rb
|
@@ -146,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
146
147
|
- !ruby/object:Gem::Version
|
147
148
|
version: '0'
|
148
149
|
requirements: []
|
149
|
-
rubygems_version: 3.
|
150
|
+
rubygems_version: 3.4.0.dev
|
150
151
|
signing_key:
|
151
152
|
specification_version: 4
|
152
153
|
summary: CSV Reading and Writing
|