csv 3.1.8 → 3.2.2
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 +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
|
-
[](https://travis-ci.org/ruby/csv)
|
4
|
-
[](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
|