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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9a9f965a5db6ac6d07f525f513e212f082c137b487d73328373d0afef7bb8312
4
- data.tar.gz: 5184ad5878a6dee8603e3b48fc820b50e3687eafb96cbcec152a8d621ceaffd6
3
+ metadata.gz: 8682072d16c079d3d25b3f22ca9f06cae36210998194ae3f3de627c74c062453
4
+ data.tar.gz: 755bddbed0b08dd681939a76c5f6a80f2c536a0e72edcf6e8c770be860e5fcae
5
5
  SHA512:
6
- metadata.gz: 422a1a8751e4e8c21884848e7f465e39090b946c3febff711bb756c98e61b7e6aebf0e552adee851d653f89e9ef2772984db2b05a243c7fac8060c64def34ae0
7
- data.tar.gz: 9d85fa756cd744e74da2ae1e89650768370566a5fd8ce1830337d528f9875d07422508eb007522ae3e7ec9c20c9ec055983f09c9b951e78441706bb2d33fdd59
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
- - {API}[CSV.html]: all classes, methods, and constants.
37
- - {Recipes}[doc/csv/recipes/recipes_rdoc.html]: specific code for specific tasks.
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 `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
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 +:conveters+:
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 +:header_conveters+:
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]
@@ -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
- @builtin_converters = options[:builtin_converters]
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 = @builtin_converters[name]
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(nil, @chunk_size)
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
- @@string_scanner_scan_accept_string = false
485
+ STRING_SCANNER_SCAN_ACCEPT_STRING = false
483
486
  else
484
- @@string_scanner_scan_accept_string = true
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 @@string_scanner_scan_accept_string
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
- @cr_or_lf = Regexp.new("[\r\n]".encode(@encoding))
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 = $INPUT_RECORD_SEPARATOR if separator == :auto
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
- chunk_size: Integer(chunk_size, 10))
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 @input.respond_to?(:eof?) and @input.eof?
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(@cr_or_lf))
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(@cr_or_lf))
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(@cr_or_lf)
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].send(finder, header_or_index)
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 { |header| yield([header, self[header]]) }
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  class CSV
4
4
  # The version of the installed library.
5
- VERSION = "3.1.8"
5
+ VERSION = "3.2.2"
6
6
  end
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 = $INPUT_RECORD_SEPARATOR.encode(@encoding)
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 Hash-style parameters to set options.
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
- # to be stripped from fields..
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::Info object shows:
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 +:dowhcase+,
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(in_string, **options) {|row| ... }
1011
- # filter(in_io, **options) {|row| ... }
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
- # Arguments:
1027
- # * \CSV source:
1028
- # * Argument +in_string+, if given, should be a \String object;
1029
- # it will be put into a new StringIO object positioned at the beginning.
1030
- # * Argument +in_io+, if given, should be an IO object that is
1031
- # open for reading; on return, the IO object will be closed.
1032
- # * If neither +in_string+ nor +in_io+ is given,
1033
- # the input stream defaults to {ARGF}[https://ruby-doc.org/core/ARGF.html].
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
- # Example:
1056
- # in_string = "foo,0\nbar,1\nbaz,2\n"
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] = row[0].upcase
1060
- # row[1] *= 4
1061
- # end
1062
- # out_string # => "FOO,0000\nBAR,1111\nBAZ,2222\n"
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: $INPUT_RECORD_SEPARATOR}
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(path, mode='r', **options) {|row| ... )
1110
- # foreach(io, mode='r', **options {|row| ... )
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 +path+ or +io+.
1249
+ # Calls the block with each row read from source +path_or_io+.
1117
1250
  #
1118
- # * Argument +path+, if given, must be the path to a file.
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
- # path = 't.csv'
1142
- # File.write(path, string)
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
- # Returns a new \Enumerator if no block given:
1162
- # CSV.foreach(path) # => #<Enumerator: CSV:foreach("t.csv", "r")>
1163
- # CSV.foreach(File.open(path)) # => #<Enumerator: CSV:foreach(#<File:t.csv>, "r")>
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
- # ====== With Option +headers+
1273
+ # <CSV::Row "Name":"foo" "Value":"0">
1274
+ # <CSV::Row "Name":"bar" "Value":"1">
1275
+ # <CSV::Row "Name":"baz" "Value":"2">
1172
1276
  #
1173
- # With {option +headers+}[#class-CSV-label-Option+headers],
1174
- # returns each row as a CSV::Row object.
1277
+ # \IO stream input without headers:
1175
1278
  #
1176
- # These examples assume prior execution of:
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
- # Read rows from a file at +path+:
1182
- # CSV.foreach(path, headers: true) {|row| p row }
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
- # Read rows from an \IO object:
1190
- # File.open(path) do |file|
1191
- # CSV.foreach(file, headers: true) {|row| p row }
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
- # Raises an exception if +path+ is a \String, but not the path to a readable file:
1202
- # # Raises Errno::ENOENT (No such file or directory @ rb_sysopen - nosuch.csv):
1203
- # CSV.foreach('nosuch.csv') {|row| }
1303
+ # <CSV::Row "Name":"foo" "Value":"0">
1304
+ # <CSV::Row "Name":"bar" "Value":"1">
1305
+ # <CSV::Row "Name":"baz" "Value":"2">
1204
1306
  #
1205
- # Raises an exception if +io+ is an \IO object, but not open for reading:
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
- # Raises an exception if +mode+ is invalid:
1211
- # # Raises ArgumentError (invalid access mode nosuch):
1212
- # CSV.foreach(path, 'nosuch') {|row| }
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>$INPUT_RECORD_SEPARATOR</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: $INPUT_RECORD_SEPARATOR}.merge(options)
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
- # hash form:
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 = {universal_newline: false}.merge(options)
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
- # - +converbers+: +:numeric+
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 -> endcoding
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
- builtin_converters: Converters,
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
- builtin_converters: HeaderConverters,
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
- def CSV(*args, &block)
2665
- CSV.instance(*args, &block)
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.1.8
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: 2020-11-17 00:00:00.000000000 Z
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: simplecov
57
+ name: test-unit
58
58
  requirement: !ruby/object:Gem::Requirement
59
59
  requirements:
60
60
  - - ">="
61
61
  - !ruby/object:Gem::Version
62
- version: '0'
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: '0'
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.2.0.rc.2
150
+ rubygems_version: 3.3.0
149
151
  signing_key:
150
152
  specification_version: 4
151
153
  summary: CSV Reading and Writing