csv 3.1.8 → 3.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/NEWS.md +98 -0
- data/README.md +3 -6
- data/doc/csv/recipes/parsing.rdoc +2 -2
- data/lib/csv/fields_converter.rb +6 -2
- data/lib/csv/input_record_separator.rb +31 -0
- data/lib/csv/parser.rb +45 -14
- data/lib/csv/row.rb +23 -1
- data/lib/csv/table.rb +3 -1
- data/lib/csv/version.rb +1 -1
- data/lib/csv/writer.rb +2 -1
- data/lib/csv.rb +284 -149
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8682072d16c079d3d25b3f22ca9f06cae36210998194ae3f3de627c74c062453
|
4
|
+
data.tar.gz: 755bddbed0b08dd681939a76c5f6a80f2c536a0e72edcf6e8c770be860e5fcae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28e191df9cb41c6ca04a9969eace28deac9217de9f2677b7edec8803531c28b4c47e4866d00fd6edbc5ea0b21f10a4f59b376473475a8c96317949b84b53f49a
|
7
|
+
data.tar.gz: 4c747ddbdb78e4e6e8dc725199b2f35aea7949b7eb5b49929c2e818d15cbebfaa07825f57a5ee092a5da7f5c75fc2ea3c035ff204f1a95a12283cea85720e31c
|
data/NEWS.md
CHANGED
@@ -1,5 +1,103 @@
|
|
1
1
|
# News
|
2
2
|
|
3
|
+
## 3.2.2 - 2021-12-24
|
4
|
+
|
5
|
+
### Improvements
|
6
|
+
|
7
|
+
* Added a validation for invalid option combination.
|
8
|
+
[GitHub#225][Patch by adamroyjones]
|
9
|
+
|
10
|
+
* Improved documentation for developers.
|
11
|
+
[GitHub#227][Patch by Eriko Sugiyama]
|
12
|
+
|
13
|
+
### Fixes
|
14
|
+
|
15
|
+
* Fixed a bug that all of `ARGF` contents may not be consumed.
|
16
|
+
[GitHub#228][Reported by Rafael Navaza]
|
17
|
+
|
18
|
+
* Fixed a bug that some texts may be dropped unexpectedly.
|
19
|
+
[Bug #18245][ruby-core:105587][Reported by Hassan Abdul Rehman]
|
20
|
+
|
21
|
+
### Thanks
|
22
|
+
|
23
|
+
* adamroyjones
|
24
|
+
|
25
|
+
* Eriko Sugiyama
|
26
|
+
|
27
|
+
* Rafael Navaza
|
28
|
+
|
29
|
+
* Hassan Abdul Rehman
|
30
|
+
|
31
|
+
## 3.2.1 - 2021-10-23
|
32
|
+
|
33
|
+
### Improvements
|
34
|
+
|
35
|
+
* doc: Fixed wrong class name.
|
36
|
+
[GitHub#217][Patch by Vince]
|
37
|
+
|
38
|
+
* Changed to always use `"\n"` for the default row separator on Ruby
|
39
|
+
3.0 or later because `$INPUT_RECORD_SEPARATOR` was deprecated
|
40
|
+
since Ruby 3.0.
|
41
|
+
|
42
|
+
* Added support for Ractor.
|
43
|
+
[GitHub#218][Patch by rm155]
|
44
|
+
|
45
|
+
* Users who want to use the built-in converters in non-main
|
46
|
+
Ractors need to call `Ractor.make_shareable(CSV::Converters)`
|
47
|
+
and/or `Ractor.make_shareable(CSV::HeaderConverters)` before
|
48
|
+
creating non-main Ractors.
|
49
|
+
|
50
|
+
### Thanks
|
51
|
+
|
52
|
+
* Vince
|
53
|
+
|
54
|
+
* Joakim Antman
|
55
|
+
|
56
|
+
* rm155
|
57
|
+
|
58
|
+
## 3.2.0 - 2021-06-06
|
59
|
+
|
60
|
+
### Improvements
|
61
|
+
|
62
|
+
* `CSV.open`: Added support for `:newline` option.
|
63
|
+
[GitHub#198][Patch by Nobuyoshi Nakada]
|
64
|
+
|
65
|
+
* `CSV::Table#each`: Added support for column mode with duplicated
|
66
|
+
headers.
|
67
|
+
[GitHub#206][Reported by Yaroslav Berezovskiy]
|
68
|
+
|
69
|
+
* `Object#CSV`: Added support for Ruby 3.0.
|
70
|
+
|
71
|
+
* `CSV::Row`: Added support for pattern matching.
|
72
|
+
[GitHub#207][Patch by Kevin Newton]
|
73
|
+
|
74
|
+
### Fixes
|
75
|
+
|
76
|
+
* Fixed typos in documentation.
|
77
|
+
[GitHub#196][GitHub#205][Patch by Sampat Badhe]
|
78
|
+
|
79
|
+
### Thanks
|
80
|
+
|
81
|
+
* Sampat Badhe
|
82
|
+
|
83
|
+
* Nobuyoshi Nakada
|
84
|
+
|
85
|
+
* Yaroslav Berezovskiy
|
86
|
+
|
87
|
+
* Kevin Newton
|
88
|
+
|
89
|
+
## 3.1.9 - 2020-11-23
|
90
|
+
|
91
|
+
### Fixes
|
92
|
+
|
93
|
+
* Fixed a compatibility bug that the line to be processed by
|
94
|
+
`skip_lines:` has a row separator.
|
95
|
+
[GitHub#194][Reported by Josef Šimánek]
|
96
|
+
|
97
|
+
### Thanks
|
98
|
+
|
99
|
+
* Josef Šimánek
|
100
|
+
|
3
101
|
## 3.1.8 - 2020-11-18
|
4
102
|
|
5
103
|
### Improvements
|
data/README.md
CHANGED
@@ -1,8 +1,5 @@
|
|
1
1
|
# CSV
|
2
2
|
|
3
|
-
[![Build Status](https://travis-ci.org/ruby/csv.svg?branch=master)](https://travis-ci.org/ruby/csv)
|
4
|
-
[![Test Coverage](https://api.codeclimate.com/v1/badges/321fa39e510a0abd0369/test_coverage)](https://codeclimate.com/github/ruby/csv/test_coverage)
|
5
|
-
|
6
3
|
This library provides a complete interface to CSV files and data. It offers tools to enable you to read and write to and from Strings or IO objects, as needed.
|
7
4
|
|
8
5
|
## Installation
|
@@ -33,12 +30,12 @@ end
|
|
33
30
|
|
34
31
|
## Documentation
|
35
32
|
|
36
|
-
-
|
37
|
-
-
|
33
|
+
- [API](https://ruby-doc.org/stdlib/libdoc/csv/rdoc/CSV.html): all classes, methods, and constants.
|
34
|
+
- [Recipes](https://ruby-doc.org/core/doc/csv/recipes/recipes_rdoc.html): specific code for specific tasks.
|
38
35
|
|
39
36
|
## Development
|
40
37
|
|
41
|
-
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.
|
42
39
|
|
43
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).
|
44
41
|
|
@@ -431,7 +431,7 @@ You can use multiple field converters in either of these ways:
|
|
431
431
|
|
432
432
|
===== Recipe: Specify Multiple Field Converters in Option +:converters+
|
433
433
|
|
434
|
-
Apply multiple field converters by specifying them in option +:
|
434
|
+
Apply multiple field converters by specifying them in option +:converters+:
|
435
435
|
source = "Name,Value\nfoo,0\nbar,1.0\nbaz,2.0\n"
|
436
436
|
parsed = CSV.parse(source, headers: true, converters: [:integer, :float])
|
437
437
|
parsed['Value'] # => [0, 1.0, 2.0]
|
@@ -500,7 +500,7 @@ You can use multiple header converters in either of these ways:
|
|
500
500
|
|
501
501
|
===== Recipe: Specify Multiple Header Converters in Option :header_converters
|
502
502
|
|
503
|
-
Apply multiple header converters by specifying them in option +:
|
503
|
+
Apply multiple header converters by specifying them in option +:header_converters+:
|
504
504
|
source = "Name,Value\nfoo,0\nbar,1.0\nbaz,2.0\n"
|
505
505
|
parsed = CSV.parse(source, headers: true, header_converters: [:downcase, :symbol])
|
506
506
|
parsed.headers # => [:name, :value]
|
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
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "English"
|
2
|
+
require "stringio"
|
3
|
+
|
4
|
+
class CSV
|
5
|
+
module InputRecordSeparator
|
6
|
+
class << self
|
7
|
+
is_input_record_separator_deprecated = false
|
8
|
+
verbose, $VERBOSE = $VERBOSE, true
|
9
|
+
stderr, $stderr = $stderr, StringIO.new
|
10
|
+
input_record_separator = $INPUT_RECORD_SEPARATOR
|
11
|
+
begin
|
12
|
+
$INPUT_RECORD_SEPARATOR = "\r\n"
|
13
|
+
is_input_record_separator_deprecated = (not $stderr.string.empty?)
|
14
|
+
ensure
|
15
|
+
$INPUT_RECORD_SEPARATOR = input_record_separator
|
16
|
+
$stderr = stderr
|
17
|
+
$VERBOSE = verbose
|
18
|
+
end
|
19
|
+
|
20
|
+
if is_input_record_separator_deprecated
|
21
|
+
def value
|
22
|
+
"\n"
|
23
|
+
end
|
24
|
+
else
|
25
|
+
def value
|
26
|
+
$INPUT_RECORD_SEPARATOR
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
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"
|
@@ -84,9 +85,10 @@ class CSV
|
|
84
85
|
# If there is no more data (eos? = true), it returns "".
|
85
86
|
#
|
86
87
|
class InputsScanner
|
87
|
-
def initialize(inputs, encoding, chunk_size: 8192)
|
88
|
+
def initialize(inputs, encoding, row_separator, chunk_size: 8192)
|
88
89
|
@inputs = inputs.dup
|
89
90
|
@encoding = encoding
|
91
|
+
@row_separator = row_separator
|
90
92
|
@chunk_size = chunk_size
|
91
93
|
@last_scanner = @inputs.empty?
|
92
94
|
@keeps = []
|
@@ -232,7 +234,7 @@ class CSV
|
|
232
234
|
@last_scanner = @inputs.empty?
|
233
235
|
true
|
234
236
|
else
|
235
|
-
chunk = input.gets(
|
237
|
+
chunk = input.gets(@row_separator, @chunk_size)
|
236
238
|
if chunk
|
237
239
|
raise InvalidEncoding unless chunk.valid_encoding?
|
238
240
|
@scanner = StringScanner.new(chunk)
|
@@ -360,6 +362,7 @@ class CSV
|
|
360
362
|
prepare_skip_lines
|
361
363
|
prepare_strip
|
362
364
|
prepare_separators
|
365
|
+
validate_strip_and_col_sep_options
|
363
366
|
prepare_quoted
|
364
367
|
prepare_unquoted
|
365
368
|
prepare_line
|
@@ -479,9 +482,9 @@ class CSV
|
|
479
482
|
begin
|
480
483
|
StringScanner.new("x").scan("x")
|
481
484
|
rescue TypeError
|
482
|
-
|
485
|
+
STRING_SCANNER_SCAN_ACCEPT_STRING = false
|
483
486
|
else
|
484
|
-
|
487
|
+
STRING_SCANNER_SCAN_ACCEPT_STRING = true
|
485
488
|
end
|
486
489
|
|
487
490
|
def prepare_separators
|
@@ -505,7 +508,7 @@ class CSV
|
|
505
508
|
@first_column_separators = Regexp.new(@escaped_first_column_separator +
|
506
509
|
"+".encode(@encoding))
|
507
510
|
else
|
508
|
-
if
|
511
|
+
if STRING_SCANNER_SCAN_ACCEPT_STRING
|
509
512
|
@column_end = @column_separator
|
510
513
|
else
|
511
514
|
@column_end = Regexp.new(@escaped_column_separator)
|
@@ -526,10 +529,32 @@ class CSV
|
|
526
529
|
|
527
530
|
@cr = "\r".encode(@encoding)
|
528
531
|
@lf = "\n".encode(@encoding)
|
529
|
-
@
|
532
|
+
@line_end = Regexp.new("\r\n|\n|\r".encode(@encoding))
|
530
533
|
@not_line_end = Regexp.new("[^\r\n]+".encode(@encoding))
|
531
534
|
end
|
532
535
|
|
536
|
+
# This method verifies that there are no (obvious) ambiguities with the
|
537
|
+
# provided +col_sep+ and +strip+ parsing options. For example, if +col_sep+
|
538
|
+
# and +strip+ were both equal to +\t+, then there would be no clear way to
|
539
|
+
# parse the input.
|
540
|
+
def validate_strip_and_col_sep_options
|
541
|
+
return unless @strip
|
542
|
+
|
543
|
+
if @strip.is_a?(String)
|
544
|
+
if @column_separator.start_with?(@strip) || @column_separator.end_with?(@strip)
|
545
|
+
raise ArgumentError,
|
546
|
+
"The provided strip (#{@escaped_strip}) and " \
|
547
|
+
"col_sep (#{@escaped_column_separator}) options are incompatible."
|
548
|
+
end
|
549
|
+
else
|
550
|
+
if Regexp.new("\\A[#{@escaped_strip}]|[#{@escaped_strip}]\\z").match?(@column_separator)
|
551
|
+
raise ArgumentError,
|
552
|
+
"The provided strip (true) and " \
|
553
|
+
"col_sep (#{@escaped_column_separator}) options are incompatible."
|
554
|
+
end
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
533
558
|
def prepare_quoted
|
534
559
|
if @quote_character
|
535
560
|
@quotes = Regexp.new(@escaped_quote_character +
|
@@ -605,7 +630,7 @@ class CSV
|
|
605
630
|
# do nothing: ensure will set default
|
606
631
|
end
|
607
632
|
end
|
608
|
-
separator =
|
633
|
+
separator = InputRecordSeparator.value if separator == :auto
|
609
634
|
end
|
610
635
|
separator.to_s.encode(@encoding)
|
611
636
|
end
|
@@ -724,6 +749,8 @@ class CSV
|
|
724
749
|
end
|
725
750
|
end
|
726
751
|
|
752
|
+
SCANNER_TEST_CHUNK_SIZE =
|
753
|
+
Integer((ENV["CSV_PARSER_SCANNER_TEST_CHUNK_SIZE"] || "1"), 10)
|
727
754
|
def build_scanner
|
728
755
|
inputs = @samples.collect do |sample|
|
729
756
|
UnoptimizedStringIO.new(sample)
|
@@ -733,17 +760,20 @@ class CSV
|
|
733
760
|
else
|
734
761
|
inputs << @input
|
735
762
|
end
|
736
|
-
chunk_size = ENV["CSV_PARSER_SCANNER_TEST_CHUNK_SIZE"] || "1"
|
737
763
|
InputsScanner.new(inputs,
|
738
764
|
@encoding,
|
739
|
-
|
765
|
+
@row_separator,
|
766
|
+
chunk_size: SCANNER_TEST_CHUNK_SIZE)
|
740
767
|
end
|
741
768
|
else
|
742
769
|
def build_scanner
|
743
770
|
string = nil
|
744
771
|
if @samples.empty? and @input.is_a?(StringIO)
|
745
772
|
string = @input.read
|
746
|
-
elsif @samples.size == 1 and
|
773
|
+
elsif @samples.size == 1 and
|
774
|
+
@input != ARGF and
|
775
|
+
@input.respond_to?(:eof?) and
|
776
|
+
@input.eof?
|
747
777
|
string = @samples[0]
|
748
778
|
end
|
749
779
|
if string
|
@@ -762,7 +792,7 @@ class CSV
|
|
762
792
|
StringIO.new(sample)
|
763
793
|
end
|
764
794
|
inputs << @input
|
765
|
-
InputsScanner.new(inputs, @encoding)
|
795
|
+
InputsScanner.new(inputs, @encoding, @row_separator)
|
766
796
|
end
|
767
797
|
end
|
768
798
|
end
|
@@ -785,6 +815,7 @@ class CSV
|
|
785
815
|
end
|
786
816
|
|
787
817
|
def skip_line?(line)
|
818
|
+
line = line.delete_suffix(@row_separator)
|
788
819
|
case @skip_lines
|
789
820
|
when String
|
790
821
|
line.include?(@skip_lines)
|
@@ -913,7 +944,7 @@ class CSV
|
|
913
944
|
message = "Any value after quoted field isn't allowed"
|
914
945
|
raise MalformedCSVError.new(message, @lineno)
|
915
946
|
elsif @unquoted_column_value and
|
916
|
-
(new_line = @scanner.scan(@
|
947
|
+
(new_line = @scanner.scan(@line_end))
|
917
948
|
ignore_broken_line
|
918
949
|
message = "Unquoted fields do not allow new line " +
|
919
950
|
"<#{new_line.inspect}>"
|
@@ -922,7 +953,7 @@ class CSV
|
|
922
953
|
ignore_broken_line
|
923
954
|
message = "Illegal quoting"
|
924
955
|
raise MalformedCSVError.new(message, @lineno)
|
925
|
-
elsif (new_line = @scanner.scan(@
|
956
|
+
elsif (new_line = @scanner.scan(@line_end))
|
926
957
|
ignore_broken_line
|
927
958
|
message = "New line must be <#{@row_separator.inspect}> " +
|
928
959
|
"not <#{new_line.inspect}>"
|
@@ -1088,7 +1119,7 @@ class CSV
|
|
1088
1119
|
|
1089
1120
|
def ignore_broken_line
|
1090
1121
|
@scanner.scan_all(@not_line_end)
|
1091
|
-
@scanner.scan_all(@
|
1122
|
+
@scanner.scan_all(@line_end)
|
1092
1123
|
@lineno += 1
|
1093
1124
|
end
|
1094
1125
|
|
data/lib/csv/row.rb
CHANGED
@@ -203,7 +203,7 @@ class CSV
|
|
203
203
|
def field(header_or_index, minimum_index = 0)
|
204
204
|
# locate the pair
|
205
205
|
finder = (header_or_index.is_a?(Integer) || header_or_index.is_a?(Range)) ? :[] : :assoc
|
206
|
-
pair = @row[minimum_index..-1].
|
206
|
+
pair = @row[minimum_index..-1].public_send(finder, header_or_index)
|
207
207
|
|
208
208
|
# return the field if we have a pair
|
209
209
|
if pair.nil?
|
@@ -659,8 +659,30 @@ class CSV
|
|
659
659
|
end
|
660
660
|
alias_method :to_hash, :to_h
|
661
661
|
|
662
|
+
# :call-seq:
|
663
|
+
# row.deconstruct_keys(keys) -> hash
|
664
|
+
#
|
665
|
+
# Returns the new \Hash suitable for pattern matching containing only the
|
666
|
+
# keys specified as an argument.
|
667
|
+
def deconstruct_keys(keys)
|
668
|
+
if keys.nil?
|
669
|
+
to_h
|
670
|
+
else
|
671
|
+
keys.to_h { |key| [key, self[key]] }
|
672
|
+
end
|
673
|
+
end
|
674
|
+
|
662
675
|
alias_method :to_ary, :to_a
|
663
676
|
|
677
|
+
# :call-seq:
|
678
|
+
# row.deconstruct -> array
|
679
|
+
#
|
680
|
+
# Returns the new \Array suitable for pattern matching containing the values
|
681
|
+
# of the row.
|
682
|
+
def deconstruct
|
683
|
+
fields
|
684
|
+
end
|
685
|
+
|
664
686
|
# :call-seq:
|
665
687
|
# row.to_csv -> csv_string
|
666
688
|
#
|
data/lib/csv/table.rb
CHANGED
@@ -932,7 +932,9 @@ class CSV
|
|
932
932
|
return enum_for(__method__) { @mode == :col ? headers.size : size } unless block_given?
|
933
933
|
|
934
934
|
if @mode == :col
|
935
|
-
headers.each
|
935
|
+
headers.each.with_index do |header, i|
|
936
|
+
yield([header, @table.map {|row| row[header, i]}])
|
937
|
+
end
|
936
938
|
else
|
937
939
|
@table.each(&block)
|
938
940
|
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
@@ -48,7 +48,7 @@
|
|
48
48
|
#
|
49
49
|
# === Interface
|
50
50
|
#
|
51
|
-
# * CSV now uses
|
51
|
+
# * CSV now uses keyword parameters to set options.
|
52
52
|
# * CSV no longer has generate_row() or parse_row().
|
53
53
|
# * The old CSV's Reader and Writer classes have been dropped.
|
54
54
|
# * CSV::open() is now more like Ruby's open().
|
@@ -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
|
@@ -366,8 +366,9 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
|
|
366
366
|
# - +header_converters+: Specifies the header converters to be used.
|
367
367
|
# - +skip_blanks+: Specifies whether blanks lines are to be ignored.
|
368
368
|
# - +skip_lines+: Specifies how comments lines are to be recognized.
|
369
|
-
# - +strip+: Specifies whether leading and trailing whitespace are
|
370
|
-
#
|
369
|
+
# - +strip+: Specifies whether leading and trailing whitespace are to be
|
370
|
+
# stripped from fields. This must be compatible with +col_sep+; if it is not,
|
371
|
+
# then an +ArgumentError+ exception will be raised.
|
371
372
|
# - +liberal_parsing+: Specifies whether \CSV should attempt to parse
|
372
373
|
# non-compliant data.
|
373
374
|
# - +nil_value+: Specifies the object that is to be substituted for each null (no-text) field.
|
@@ -513,7 +514,7 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
|
|
513
514
|
# [" 1 ", #<struct CSV::FieldInfo index=1, line=2, header=nil>]
|
514
515
|
# [" baz ", #<struct CSV::FieldInfo index=0, line=3, header=nil>]
|
515
516
|
# [" 2 ", #<struct CSV::FieldInfo index=1, line=3, header=nil>]
|
516
|
-
# Each CSV::
|
517
|
+
# Each CSV::FieldInfo object shows:
|
517
518
|
# - The 0-based field index.
|
518
519
|
# - The 1-based line index.
|
519
520
|
# - The field header, if any.
|
@@ -547,6 +548,14 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
|
|
547
548
|
#
|
548
549
|
# There is no such storage structure for write headers.
|
549
550
|
#
|
551
|
+
# In order for the parsing methods to access stored converters in non-main-Ractors, the
|
552
|
+
# storage structure must be made shareable first.
|
553
|
+
# Therefore, <tt>Ractor.make_shareable(CSV::Converters)</tt> and
|
554
|
+
# <tt>Ractor.make_shareable(CSV::HeaderConverters)</tt> must be called before the creation
|
555
|
+
# of Ractors that use the converters stored in these structures. (Since making the storage
|
556
|
+
# structures shareable involves freezing them, any custom converters that are to be used
|
557
|
+
# must be added first.)
|
558
|
+
#
|
550
559
|
# ===== Converter Lists
|
551
560
|
#
|
552
561
|
# A _converter_ _list_ is an \Array that may include any assortment of:
|
@@ -705,7 +714,7 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
|
|
705
714
|
# Header converters operate only on headers (and not on other rows).
|
706
715
|
#
|
707
716
|
# There are three ways to use header \converters;
|
708
|
-
# these examples use built-in header converter +:
|
717
|
+
# these examples use built-in header converter +:downcase+,
|
709
718
|
# which downcases each parsed header.
|
710
719
|
#
|
711
720
|
# - Option +header_converters+ with a singleton parsing method:
|
@@ -919,6 +928,7 @@ class CSV
|
|
919
928
|
gsub(/\s+/, "_").to_sym
|
920
929
|
}
|
921
930
|
}
|
931
|
+
|
922
932
|
# Default values for method options.
|
923
933
|
DEFAULT_OPTIONS = {
|
924
934
|
# For both parsing and generating.
|
@@ -937,6 +947,7 @@ class CSV
|
|
937
947
|
liberal_parsing: false,
|
938
948
|
nil_value: nil,
|
939
949
|
empty_value: "",
|
950
|
+
strip: false,
|
940
951
|
# For generating.
|
941
952
|
write_headers: nil,
|
942
953
|
quote_empty: true,
|
@@ -944,7 +955,6 @@ class CSV
|
|
944
955
|
write_converters: nil,
|
945
956
|
write_nil_value: nil,
|
946
957
|
write_empty_value: "",
|
947
|
-
strip: false,
|
948
958
|
}.freeze
|
949
959
|
|
950
960
|
class << self
|
@@ -957,6 +967,8 @@ class CSV
|
|
957
967
|
# Creates or retrieves cached \CSV objects.
|
958
968
|
# For arguments and options, see CSV.new.
|
959
969
|
#
|
970
|
+
# This API is not Ractor-safe.
|
971
|
+
#
|
960
972
|
# ---
|
961
973
|
#
|
962
974
|
# With no block given, returns a \CSV object.
|
@@ -1006,63 +1018,188 @@ class CSV
|
|
1006
1018
|
end
|
1007
1019
|
|
1008
1020
|
# :call-seq:
|
1009
|
-
# filter(**options) {|row| ... }
|
1010
|
-
# filter(
|
1011
|
-
# filter(
|
1012
|
-
# filter(in_string, out_string, **options) {|row| ... }
|
1013
|
-
# filter(in_string, out_io, **options) {|row| ... }
|
1014
|
-
# filter(in_io, out_string, **options) {|row| ... }
|
1015
|
-
# filter(in_io, out_io, **options) {|row| ... }
|
1016
|
-
#
|
1017
|
-
# Reads \CSV input and writes \CSV output.
|
1018
|
-
#
|
1019
|
-
# For each input row:
|
1020
|
-
# - Forms the data into:
|
1021
|
-
# - A CSV::Row object, if headers are in use.
|
1022
|
-
# - An \Array of Arrays, otherwise.
|
1023
|
-
# - Calls the block with that object.
|
1024
|
-
# - Appends the block's return value to the output.
|
1021
|
+
# filter(in_string_or_io, **options) {|row| ... } -> array_of_arrays or csv_table
|
1022
|
+
# filter(in_string_or_io, out_string_or_io, **options) {|row| ... } -> array_of_arrays or csv_table
|
1023
|
+
# filter(**options) {|row| ... } -> array_of_arrays or csv_table
|
1025
1024
|
#
|
1026
|
-
#
|
1027
|
-
#
|
1028
|
-
#
|
1029
|
-
#
|
1030
|
-
#
|
1031
|
-
#
|
1032
|
-
#
|
1033
|
-
#
|
1034
|
-
# * \CSV output:
|
1035
|
-
# * Argument +out_string+, if given, should be a \String object;
|
1036
|
-
# it will be put into a new StringIO object positioned at the beginning.
|
1037
|
-
# * Argument +out_io+, if given, should be an IO object that is
|
1038
|
-
# ppen for writing; on return, the IO object will be closed.
|
1039
|
-
# * If neither +out_string+ nor +out_io+ is given,
|
1040
|
-
# the output stream defaults to <tt>$stdout</tt>.
|
1041
|
-
# * Argument +options+ should be keyword arguments.
|
1042
|
-
# - Each argument name that is prefixed with +in_+ or +input_+
|
1043
|
-
# is stripped of its prefix and is treated as an option
|
1044
|
-
# for parsing the input.
|
1045
|
-
# Option +input_row_sep+ defaults to <tt>$INPUT_RECORD_SEPARATOR</tt>.
|
1046
|
-
# - Each argument name that is prefixed with +out_+ or +output_+
|
1047
|
-
# is stripped of its prefix and is treated as an option
|
1048
|
-
# for generating the output.
|
1049
|
-
# Option +output_row_sep+ defaults to <tt>$INPUT_RECORD_SEPARATOR</tt>.
|
1050
|
-
# - Each argument not prefixed as above is treated as an option
|
1051
|
-
# both for parsing the input and for generating the output.
|
1052
|
-
# - See {Options for Parsing}[#class-CSV-label-Options+for+Parsing]
|
1053
|
-
# and {Options for Generating}[#class-CSV-label-Options+for+Generating].
|
1025
|
+
# - Parses \CSV from a source (\String, \IO stream, or ARGF).
|
1026
|
+
# - Calls the given block with each parsed row:
|
1027
|
+
# - Without headers, each row is an \Array.
|
1028
|
+
# - With headers, each row is a CSV::Row.
|
1029
|
+
# - Generates \CSV to an output (\String, \IO stream, or STDOUT).
|
1030
|
+
# - Returns the parsed source:
|
1031
|
+
# - Without headers, an \Array of \Arrays.
|
1032
|
+
# - With headers, a CSV::Table.
|
1054
1033
|
#
|
1055
|
-
#
|
1056
|
-
#
|
1034
|
+
# When +in_string_or_io+ is given, but not +out_string_or_io+,
|
1035
|
+
# parses from the given +in_string_or_io+
|
1036
|
+
# and generates to STDOUT.
|
1037
|
+
#
|
1038
|
+
# \String input without headers:
|
1039
|
+
#
|
1040
|
+
# in_string = "foo,0\nbar,1\nbaz,2"
|
1041
|
+
# CSV.filter(in_string) do |row|
|
1042
|
+
# row[0].upcase!
|
1043
|
+
# row[1] = - row[1].to_i
|
1044
|
+
# end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
|
1045
|
+
#
|
1046
|
+
# Output (to STDOUT):
|
1047
|
+
#
|
1048
|
+
# FOO,0
|
1049
|
+
# BAR,-1
|
1050
|
+
# BAZ,-2
|
1051
|
+
#
|
1052
|
+
# \String input with headers:
|
1053
|
+
#
|
1054
|
+
# in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
|
1055
|
+
# CSV.filter(in_string, headers: true) do |row|
|
1056
|
+
# row[0].upcase!
|
1057
|
+
# row[1] = - row[1].to_i
|
1058
|
+
# end # => #<CSV::Table mode:col_or_row row_count:4>
|
1059
|
+
#
|
1060
|
+
# Output (to STDOUT):
|
1061
|
+
#
|
1062
|
+
# Name,Value
|
1063
|
+
# FOO,0
|
1064
|
+
# BAR,-1
|
1065
|
+
# BAZ,-2
|
1066
|
+
#
|
1067
|
+
# \IO stream input without headers:
|
1068
|
+
#
|
1069
|
+
# File.write('t.csv', "foo,0\nbar,1\nbaz,2")
|
1070
|
+
# File.open('t.csv') do |in_io|
|
1071
|
+
# CSV.filter(in_io) do |row|
|
1072
|
+
# row[0].upcase!
|
1073
|
+
# row[1] = - row[1].to_i
|
1074
|
+
# end
|
1075
|
+
# end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
|
1076
|
+
#
|
1077
|
+
# Output (to STDOUT):
|
1078
|
+
#
|
1079
|
+
# FOO,0
|
1080
|
+
# BAR,-1
|
1081
|
+
# BAZ,-2
|
1082
|
+
#
|
1083
|
+
# \IO stream input with headers:
|
1084
|
+
#
|
1085
|
+
# File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2")
|
1086
|
+
# File.open('t.csv') do |in_io|
|
1087
|
+
# CSV.filter(in_io, headers: true) do |row|
|
1088
|
+
# row[0].upcase!
|
1089
|
+
# row[1] = - row[1].to_i
|
1090
|
+
# end
|
1091
|
+
# end # => #<CSV::Table mode:col_or_row row_count:4>
|
1092
|
+
#
|
1093
|
+
# Output (to STDOUT):
|
1094
|
+
#
|
1095
|
+
# Name,Value
|
1096
|
+
# FOO,0
|
1097
|
+
# BAR,-1
|
1098
|
+
# BAZ,-2
|
1099
|
+
#
|
1100
|
+
# When both +in_string_or_io+ and +out_string_or_io+ are given,
|
1101
|
+
# parses from +in_string_or_io+ and generates to +out_string_or_io+.
|
1102
|
+
#
|
1103
|
+
# \String output without headers:
|
1104
|
+
#
|
1105
|
+
# in_string = "foo,0\nbar,1\nbaz,2"
|
1057
1106
|
# out_string = ''
|
1058
1107
|
# CSV.filter(in_string, out_string) do |row|
|
1059
|
-
# row[0]
|
1060
|
-
# row[1]
|
1061
|
-
# end
|
1062
|
-
# out_string # => "FOO,
|
1108
|
+
# row[0].upcase!
|
1109
|
+
# row[1] = - row[1].to_i
|
1110
|
+
# end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
|
1111
|
+
# out_string # => "FOO,0\nBAR,-1\nBAZ,-2\n"
|
1112
|
+
#
|
1113
|
+
# \String output with headers:
|
1114
|
+
#
|
1115
|
+
# in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
|
1116
|
+
# out_string = ''
|
1117
|
+
# CSV.filter(in_string, out_string, headers: true) do |row|
|
1118
|
+
# row[0].upcase!
|
1119
|
+
# row[1] = - row[1].to_i
|
1120
|
+
# end # => #<CSV::Table mode:col_or_row row_count:4>
|
1121
|
+
# out_string # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n"
|
1122
|
+
#
|
1123
|
+
# \IO stream output without headers:
|
1124
|
+
#
|
1125
|
+
# in_string = "foo,0\nbar,1\nbaz,2"
|
1126
|
+
# File.open('t.csv', 'w') do |out_io|
|
1127
|
+
# CSV.filter(in_string, out_io) do |row|
|
1128
|
+
# row[0].upcase!
|
1129
|
+
# row[1] = - row[1].to_i
|
1130
|
+
# end
|
1131
|
+
# end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
|
1132
|
+
# File.read('t.csv') # => "FOO,0\nBAR,-1\nBAZ,-2\n"
|
1133
|
+
#
|
1134
|
+
# \IO stream output with headers:
|
1135
|
+
#
|
1136
|
+
# in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
|
1137
|
+
# File.open('t.csv', 'w') do |out_io|
|
1138
|
+
# CSV.filter(in_string, out_io, headers: true) do |row|
|
1139
|
+
# row[0].upcase!
|
1140
|
+
# row[1] = - row[1].to_i
|
1141
|
+
# end
|
1142
|
+
# end # => #<CSV::Table mode:col_or_row row_count:4>
|
1143
|
+
# File.read('t.csv') # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n"
|
1144
|
+
#
|
1145
|
+
# When neither +in_string_or_io+ nor +out_string_or_io+ given,
|
1146
|
+
# parses from {ARGF}[https://docs.ruby-lang.org/en/master/ARGF.html]
|
1147
|
+
# and generates to STDOUT.
|
1148
|
+
#
|
1149
|
+
# Without headers:
|
1150
|
+
#
|
1151
|
+
# # Put Ruby code into a file.
|
1152
|
+
# ruby = <<-EOT
|
1153
|
+
# require 'csv'
|
1154
|
+
# CSV.filter do |row|
|
1155
|
+
# row[0].upcase!
|
1156
|
+
# row[1] = - row[1].to_i
|
1157
|
+
# end
|
1158
|
+
# EOT
|
1159
|
+
# File.write('t.rb', ruby)
|
1160
|
+
# # Put some CSV into a file.
|
1161
|
+
# File.write('t.csv', "foo,0\nbar,1\nbaz,2")
|
1162
|
+
# # Run the Ruby code with CSV filename as argument.
|
1163
|
+
# system(Gem.ruby, "t.rb", "t.csv")
|
1164
|
+
#
|
1165
|
+
# Output (to STDOUT):
|
1166
|
+
#
|
1167
|
+
# FOO,0
|
1168
|
+
# BAR,-1
|
1169
|
+
# BAZ,-2
|
1170
|
+
#
|
1171
|
+
# With headers:
|
1172
|
+
#
|
1173
|
+
# # Put Ruby code into a file.
|
1174
|
+
# ruby = <<-EOT
|
1175
|
+
# require 'csv'
|
1176
|
+
# CSV.filter(headers: true) do |row|
|
1177
|
+
# row[0].upcase!
|
1178
|
+
# row[1] = - row[1].to_i
|
1179
|
+
# end
|
1180
|
+
# EOT
|
1181
|
+
# File.write('t.rb', ruby)
|
1182
|
+
# # Put some CSV into a file.
|
1183
|
+
# File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2")
|
1184
|
+
# # Run the Ruby code with CSV filename as argument.
|
1185
|
+
# system(Gem.ruby, "t.rb", "t.csv")
|
1186
|
+
#
|
1187
|
+
# Output (to STDOUT):
|
1188
|
+
#
|
1189
|
+
# Name,Value
|
1190
|
+
# FOO,0
|
1191
|
+
# BAR,-1
|
1192
|
+
# BAZ,-2
|
1193
|
+
#
|
1194
|
+
# Arguments:
|
1195
|
+
#
|
1196
|
+
# * Argument +in_string_or_io+ must be a \String or an \IO stream.
|
1197
|
+
# * Argument +out_string_or_io+ must be a \String or an \IO stream.
|
1198
|
+
# * Arguments <tt>**options</tt> must be keyword options.
|
1199
|
+
# See {Options for Parsing}[#class-CSV-label-Options+for+Parsing].
|
1063
1200
|
def filter(input=nil, output=nil, **options)
|
1064
1201
|
# parse options for input, output, or both
|
1065
|
-
in_options, out_options = Hash.new, {row_sep:
|
1202
|
+
in_options, out_options = Hash.new, {row_sep: InputRecordSeparator.value}
|
1066
1203
|
options.each do |key, value|
|
1067
1204
|
case key.to_s
|
1068
1205
|
when /\Ain(?:put)?_(.+)\Z/
|
@@ -1106,111 +1243,90 @@ class CSV
|
|
1106
1243
|
|
1107
1244
|
#
|
1108
1245
|
# :call-seq:
|
1109
|
-
# foreach(
|
1110
|
-
# foreach(
|
1111
|
-
# foreach(path, mode='r', headers: ..., **options) {|row| ... )
|
1112
|
-
# foreach(io, mode='r', headers: ..., **options {|row| ... )
|
1113
|
-
# foreach(path, mode='r', **options) -> new_enumerator
|
1114
|
-
# foreach(io, mode='r', **options -> new_enumerator
|
1246
|
+
# foreach(path_or_io, mode='r', **options) {|row| ... )
|
1247
|
+
# foreach(path_or_io, mode='r', **options) -> new_enumerator
|
1115
1248
|
#
|
1116
|
-
# Calls the block with each row read from source +
|
1249
|
+
# Calls the block with each row read from source +path_or_io+.
|
1117
1250
|
#
|
1118
|
-
#
|
1119
|
-
# :include: ../doc/csv/arguments/io.rdoc
|
1120
|
-
# * Argument +mode+, if given, must be a \File mode
|
1121
|
-
# See {Open Mode}[IO.html#method-c-new-label-Open+Mode].
|
1122
|
-
# * Arguments <tt>**options</tt> must be keyword options.
|
1123
|
-
# See {Options for Parsing}[#class-CSV-label-Options+for+Parsing].
|
1124
|
-
# * This method optionally accepts an additional <tt>:encoding</tt> option
|
1125
|
-
# that you can use to specify the Encoding of the data read from +path+ or +io+.
|
1126
|
-
# You must provide this unless your data is in the encoding
|
1127
|
-
# given by <tt>Encoding::default_external</tt>.
|
1128
|
-
# Parsing will use this to determine how to parse the data.
|
1129
|
-
# You may provide a second Encoding to
|
1130
|
-
# have the data transcoded as it is read. For example,
|
1131
|
-
# encoding: 'UTF-32BE:UTF-8'
|
1132
|
-
# would read +UTF-32BE+ data from the file
|
1133
|
-
# but transcode it to +UTF-8+ before parsing.
|
1251
|
+
# \Path input without headers:
|
1134
1252
|
#
|
1135
|
-
# ====== Without Option +headers+
|
1136
|
-
#
|
1137
|
-
# Without option +headers+, returns each row as an \Array object.
|
1138
|
-
#
|
1139
|
-
# These examples assume prior execution of:
|
1140
1253
|
# string = "foo,0\nbar,1\nbaz,2\n"
|
1141
|
-
#
|
1142
|
-
# File.write(
|
1254
|
+
# in_path = 't.csv'
|
1255
|
+
# File.write(in_path, string)
|
1256
|
+
# CSV.foreach(in_path) {|row| p row }
|
1143
1257
|
#
|
1144
|
-
# Read rows from a file at +path+:
|
1145
|
-
# CSV.foreach(path) {|row| p row }
|
1146
1258
|
# Output:
|
1147
|
-
# ["foo", "0"]
|
1148
|
-
# ["bar", "1"]
|
1149
|
-
# ["baz", "2"]
|
1150
|
-
#
|
1151
|
-
# Read rows from an \IO object:
|
1152
|
-
# File.open(path) do |file|
|
1153
|
-
# CSV.foreach(file) {|row| p row }
|
1154
|
-
# end
|
1155
1259
|
#
|
1156
|
-
# Output:
|
1157
1260
|
# ["foo", "0"]
|
1158
1261
|
# ["bar", "1"]
|
1159
1262
|
# ["baz", "2"]
|
1160
1263
|
#
|
1161
|
-
#
|
1162
|
-
#
|
1163
|
-
#
|
1264
|
+
# \Path input with headers:
|
1265
|
+
#
|
1266
|
+
# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
|
1267
|
+
# in_path = 't.csv'
|
1268
|
+
# File.write(in_path, string)
|
1269
|
+
# CSV.foreach(in_path, headers: true) {|row| p row }
|
1164
1270
|
#
|
1165
|
-
# Issues a warning if an encoding is unsupported:
|
1166
|
-
# CSV.foreach(File.open(path), encoding: 'foo:bar') {|row| }
|
1167
1271
|
# Output:
|
1168
|
-
# warning: Unsupported encoding foo ignored
|
1169
|
-
# warning: Unsupported encoding bar ignored
|
1170
1272
|
#
|
1171
|
-
#
|
1273
|
+
# <CSV::Row "Name":"foo" "Value":"0">
|
1274
|
+
# <CSV::Row "Name":"bar" "Value":"1">
|
1275
|
+
# <CSV::Row "Name":"baz" "Value":"2">
|
1172
1276
|
#
|
1173
|
-
#
|
1174
|
-
# returns each row as a CSV::Row object.
|
1277
|
+
# \IO stream input without headers:
|
1175
1278
|
#
|
1176
|
-
#
|
1177
|
-
# string = "Name,Count\nfoo,0\nbar,1\nbaz,2\n"
|
1279
|
+
# string = "foo,0\nbar,1\nbaz,2\n"
|
1178
1280
|
# path = 't.csv'
|
1179
1281
|
# File.write(path, string)
|
1180
|
-
#
|
1181
|
-
#
|
1182
|
-
#
|
1282
|
+
# File.open('t.csv') do |in_io|
|
1283
|
+
# CSV.foreach(in_io) {|row| p row }
|
1284
|
+
# end
|
1183
1285
|
#
|
1184
1286
|
# Output:
|
1185
|
-
# #<CSV::Row "Name":"foo" "Count":"0">
|
1186
|
-
# #<CSV::Row "Name":"bar" "Count":"1">
|
1187
|
-
# #<CSV::Row "Name":"baz" "Count":"2">
|
1188
1287
|
#
|
1189
|
-
#
|
1190
|
-
#
|
1191
|
-
#
|
1288
|
+
# ["foo", "0"]
|
1289
|
+
# ["bar", "1"]
|
1290
|
+
# ["baz", "2"]
|
1291
|
+
#
|
1292
|
+
# \IO stream input with headers:
|
1293
|
+
#
|
1294
|
+
# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
|
1295
|
+
# path = 't.csv'
|
1296
|
+
# File.write(path, string)
|
1297
|
+
# File.open('t.csv') do |in_io|
|
1298
|
+
# CSV.foreach(in_io, headers: true) {|row| p row }
|
1192
1299
|
# end
|
1193
1300
|
#
|
1194
1301
|
# Output:
|
1195
|
-
# #<CSV::Row "Name":"foo" "Count":"0">
|
1196
|
-
# #<CSV::Row "Name":"bar" "Count":"1">
|
1197
|
-
# #<CSV::Row "Name":"baz" "Count":"2">
|
1198
|
-
#
|
1199
|
-
# ---
|
1200
1302
|
#
|
1201
|
-
#
|
1202
|
-
#
|
1203
|
-
# CSV
|
1303
|
+
# <CSV::Row "Name":"foo" "Value":"0">
|
1304
|
+
# <CSV::Row "Name":"bar" "Value":"1">
|
1305
|
+
# <CSV::Row "Name":"baz" "Value":"2">
|
1204
1306
|
#
|
1205
|
-
#
|
1206
|
-
# io = File.open(path, 'w') {|row| }
|
1207
|
-
# # Raises TypeError (no implicit conversion of nil into String):
|
1208
|
-
# CSV.foreach(io) {|row| }
|
1307
|
+
# With no block given, returns an \Enumerator:
|
1209
1308
|
#
|
1210
|
-
#
|
1211
|
-
#
|
1212
|
-
#
|
1309
|
+
# string = "foo,0\nbar,1\nbaz,2\n"
|
1310
|
+
# path = 't.csv'
|
1311
|
+
# File.write(path, string)
|
1312
|
+
# CSV.foreach(path) # => #<Enumerator: CSV:foreach("t.csv", "r")>
|
1213
1313
|
#
|
1314
|
+
# Arguments:
|
1315
|
+
# * Argument +path_or_io+ must be a file path or an \IO stream.
|
1316
|
+
# * Argument +mode+, if given, must be a \File mode
|
1317
|
+
# See {Open Mode}[https://ruby-doc.org/core/IO.html#method-c-new-label-Open+Mode].
|
1318
|
+
# * Arguments <tt>**options</tt> must be keyword options.
|
1319
|
+
# See {Options for Parsing}[#class-CSV-label-Options+for+Parsing].
|
1320
|
+
# * This method optionally accepts an additional <tt>:encoding</tt> option
|
1321
|
+
# that you can use to specify the Encoding of the data read from +path+ or +io+.
|
1322
|
+
# You must provide this unless your data is in the encoding
|
1323
|
+
# given by <tt>Encoding::default_external</tt>.
|
1324
|
+
# Parsing will use this to determine how to parse the data.
|
1325
|
+
# You may provide a second Encoding to
|
1326
|
+
# have the data transcoded as it is read. For example,
|
1327
|
+
# encoding: 'UTF-32BE:UTF-8'
|
1328
|
+
# would read +UTF-32BE+ data from the file
|
1329
|
+
# but transcode it to +UTF-8+ before parsing.
|
1214
1330
|
def foreach(path, mode="r", **options, &block)
|
1215
1331
|
return to_enum(__method__, path, mode, **options) unless block_given?
|
1216
1332
|
open(path, mode, **options) do |csv|
|
@@ -1303,8 +1419,8 @@ class CSV
|
|
1303
1419
|
# Argument +ary+ must be an \Array.
|
1304
1420
|
#
|
1305
1421
|
# Special options:
|
1306
|
-
# * Option <tt>:row_sep</tt> defaults to <tt
|
1307
|
-
# (<tt>$/</tt>).:
|
1422
|
+
# * Option <tt>:row_sep</tt> defaults to <tt>"\n"> on Ruby 3.0 or later
|
1423
|
+
# and <tt>$INPUT_RECORD_SEPARATOR</tt> (<tt>$/</tt>) otherwise.:
|
1308
1424
|
# $INPUT_RECORD_SEPARATOR # => "\n"
|
1309
1425
|
# * This method accepts an additional option, <tt>:encoding</tt>, which sets the base
|
1310
1426
|
# Encoding for the output. This method will try to guess your Encoding from
|
@@ -1326,7 +1442,7 @@ class CSV
|
|
1326
1442
|
# CSV.generate_line(:foo)
|
1327
1443
|
#
|
1328
1444
|
def generate_line(row, **options)
|
1329
|
-
options = {row_sep:
|
1445
|
+
options = {row_sep: InputRecordSeparator.value}.merge(options)
|
1330
1446
|
str = +""
|
1331
1447
|
if options[:encoding]
|
1332
1448
|
str.force_encoding(options[:encoding])
|
@@ -1356,7 +1472,7 @@ class CSV
|
|
1356
1472
|
# open(io, mode = "rb", **options ) { |csv| ... } -> object
|
1357
1473
|
#
|
1358
1474
|
# possible options elements:
|
1359
|
-
#
|
1475
|
+
# keyword form:
|
1360
1476
|
# :invalid => nil # raise error on invalid byte sequence (default)
|
1361
1477
|
# :invalid => :replace # replace invalid byte sequence
|
1362
1478
|
# :undef => :replace # replace undefined conversion
|
@@ -1423,10 +1539,14 @@ class CSV
|
|
1423
1539
|
def open(filename, mode="r", **options)
|
1424
1540
|
# wrap a File opened with the remaining +args+ with no newline
|
1425
1541
|
# decorator
|
1426
|
-
file_opts =
|
1542
|
+
file_opts = options.dup
|
1543
|
+
unless file_opts.key?(:newline)
|
1544
|
+
file_opts[:universal_newline] ||= false
|
1545
|
+
end
|
1427
1546
|
options.delete(:invalid)
|
1428
1547
|
options.delete(:undef)
|
1429
1548
|
options.delete(:replace)
|
1549
|
+
options.delete_if {|k, _| /newline\z/.match?(k)}
|
1430
1550
|
|
1431
1551
|
begin
|
1432
1552
|
f = File.open(filename, mode, **file_opts)
|
@@ -1681,7 +1801,7 @@ class CSV
|
|
1681
1801
|
#
|
1682
1802
|
# Calls CSV.read with +source+, +options+, and certain default options:
|
1683
1803
|
# - +headers+: +true+
|
1684
|
-
# - +
|
1804
|
+
# - +converters+: +:numeric+
|
1685
1805
|
# - +header_converters+: +:symbol+
|
1686
1806
|
#
|
1687
1807
|
# Returns a CSV::Table object.
|
@@ -1760,11 +1880,11 @@ class CSV
|
|
1760
1880
|
encoding: nil,
|
1761
1881
|
nil_value: nil,
|
1762
1882
|
empty_value: "",
|
1883
|
+
strip: false,
|
1763
1884
|
quote_empty: true,
|
1764
1885
|
write_converters: nil,
|
1765
1886
|
write_nil_value: nil,
|
1766
|
-
write_empty_value: ""
|
1767
|
-
strip: false)
|
1887
|
+
write_empty_value: "")
|
1768
1888
|
raise ArgumentError.new("Cannot parse nil as CSV") if data.nil?
|
1769
1889
|
|
1770
1890
|
if data.is_a?(String)
|
@@ -1884,6 +2004,10 @@ class CSV
|
|
1884
2004
|
# csv.converters # => [:integer]
|
1885
2005
|
# csv.convert(proc {|x| x.to_s })
|
1886
2006
|
# csv.converters
|
2007
|
+
#
|
2008
|
+
# Notes that you need to call
|
2009
|
+
# +Ractor.make_shareable(CSV::Converters)+ on the main Ractor to use
|
2010
|
+
# this method.
|
1887
2011
|
def converters
|
1888
2012
|
parser_fields_converter.map do |converter|
|
1889
2013
|
name = Converters.rassoc(converter)
|
@@ -1946,6 +2070,10 @@ class CSV
|
|
1946
2070
|
# Returns an \Array containing header converters; used for parsing;
|
1947
2071
|
# see {Header Converters}[#class-CSV-label-Header+Converters]:
|
1948
2072
|
# CSV.new('').header_converters # => []
|
2073
|
+
#
|
2074
|
+
# Notes that you need to call
|
2075
|
+
# +Ractor.make_shareable(CSV::HeaderConverters)+ on the main Ractor
|
2076
|
+
# to use this method.
|
1949
2077
|
def header_converters
|
1950
2078
|
header_fields_converter.map do |converter|
|
1951
2079
|
name = HeaderConverters.rassoc(converter)
|
@@ -1985,7 +2113,7 @@ class CSV
|
|
1985
2113
|
end
|
1986
2114
|
|
1987
2115
|
# :call-seq:
|
1988
|
-
# csv.encoding ->
|
2116
|
+
# csv.encoding -> encoding
|
1989
2117
|
#
|
1990
2118
|
# Returns the encoding used for parsing and generating;
|
1991
2119
|
# see {Character Encodings (M17n or Multilingualization)}[#class-CSV-label-Character+Encodings+-28M17n+or+Multilingualization-29]:
|
@@ -2586,7 +2714,7 @@ class CSV
|
|
2586
2714
|
|
2587
2715
|
def build_parser_fields_converter
|
2588
2716
|
specific_options = {
|
2589
|
-
|
2717
|
+
builtin_converters_name: :Converters,
|
2590
2718
|
}
|
2591
2719
|
options = @base_fields_converter_options.merge(specific_options)
|
2592
2720
|
build_fields_converter(@initial_converters, options)
|
@@ -2598,7 +2726,7 @@ class CSV
|
|
2598
2726
|
|
2599
2727
|
def build_header_fields_converter
|
2600
2728
|
specific_options = {
|
2601
|
-
|
2729
|
+
builtin_converters_name: :HeaderConverters,
|
2602
2730
|
accept_nil: true,
|
2603
2731
|
}
|
2604
2732
|
options = @base_fields_converter_options.merge(specific_options)
|
@@ -2661,8 +2789,15 @@ end
|
|
2661
2789
|
# c.read.any? { |a| a.include?("zombies") }
|
2662
2790
|
# } #=> false
|
2663
2791
|
#
|
2664
|
-
|
2665
|
-
|
2792
|
+
# CSV options may also be given.
|
2793
|
+
#
|
2794
|
+
# io = StringIO.new
|
2795
|
+
# CSV(io, col_sep: ";") { |csv| csv << ["a", "b", "c"] }
|
2796
|
+
#
|
2797
|
+
# This API is not Ractor-safe.
|
2798
|
+
#
|
2799
|
+
def CSV(*args, **options, &block)
|
2800
|
+
CSV.instance(*args, **options, &block)
|
2666
2801
|
end
|
2667
2802
|
|
2668
2803
|
require_relative "csv/version"
|
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.
|
4
|
+
version: 3.2.2
|
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: 2021-12-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -54,19 +54,19 @@ dependencies:
|
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: '0'
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
|
-
name:
|
57
|
+
name: test-unit
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
59
59
|
requirements:
|
60
60
|
- - ">="
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version:
|
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:
|
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
|
@@ -126,6 +127,7 @@ files:
|
|
126
127
|
- lib/csv/writer.rb
|
127
128
|
homepage: https://github.com/ruby/csv
|
128
129
|
licenses:
|
130
|
+
- Ruby
|
129
131
|
- BSD-2-Clause
|
130
132
|
metadata: {}
|
131
133
|
post_install_message:
|
@@ -145,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
145
147
|
- !ruby/object:Gem::Version
|
146
148
|
version: '0'
|
147
149
|
requirements: []
|
148
|
-
rubygems_version: 3.
|
150
|
+
rubygems_version: 3.3.0
|
149
151
|
signing_key:
|
150
152
|
specification_version: 4
|
151
153
|
summary: CSV Reading and Writing
|