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 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