csv 3.1.7 → 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
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"
@@ -104,6 +104,17 @@ require_relative "csv/writer"
104
104
  using CSV::MatchP if CSV.const_defined?(:MatchP)
105
105
 
106
106
  # == \CSV
107
+ #
108
+ # === In a Hurry?
109
+ #
110
+ # If you are familiar with \CSV data and have a particular task in mind,
111
+ # you may want to go directly to the:
112
+ # - {Recipes for CSV}[doc/csv/recipes/recipes_rdoc.html].
113
+ #
114
+ # Otherwise, read on here, about the API: classes, methods, and constants.
115
+ #
116
+ # === \CSV Data
117
+ #
107
118
  # \CSV (comma-separated values) data is a text representation of a table:
108
119
  # - A _row_ _separator_ delimits table rows.
109
120
  # A common row separator is the newline character <tt>"\n"</tt>.
@@ -117,6 +128,11 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
117
128
  #
118
129
  # Despite the name \CSV, a \CSV representation can use different separators.
119
130
  #
131
+ # For more about tables, see the Wikipedia article
132
+ # "{Table (information)}[https://en.wikipedia.org/wiki/Table_(information)]",
133
+ # especially its section
134
+ # "{Simple table}[https://en.wikipedia.org/wiki/Table_(information)#Simple_table]"
135
+ #
120
136
  # == \Class \CSV
121
137
  #
122
138
  # Class \CSV provides methods for:
@@ -497,7 +513,7 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
497
513
  # [" 1 ", #<struct CSV::FieldInfo index=1, line=2, header=nil>]
498
514
  # [" baz ", #<struct CSV::FieldInfo index=0, line=3, header=nil>]
499
515
  # [" 2 ", #<struct CSV::FieldInfo index=1, line=3, header=nil>]
500
- # Each CSV::Info object shows:
516
+ # Each CSV::FieldInfo object shows:
501
517
  # - The 0-based field index.
502
518
  # - The 1-based line index.
503
519
  # - The field header, if any.
@@ -531,6 +547,14 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
531
547
  #
532
548
  # There is no such storage structure for write headers.
533
549
  #
550
+ # In order for the parsing methods to access stored converters in non-main-Ractors, the
551
+ # storage structure must be made shareable first.
552
+ # Therefore, <tt>Ractor.make_shareable(CSV::Converters)</tt> and
553
+ # <tt>Ractor.make_shareable(CSV::HeaderConverters)</tt> must be called before the creation
554
+ # of Ractors that use the converters stored in these structures. (Since making the storage
555
+ # structures shareable involves freezing them, any custom converters that are to be used
556
+ # must be added first.)
557
+ #
534
558
  # ===== Converter Lists
535
559
  #
536
560
  # A _converter_ _list_ is an \Array that may include any assortment of:
@@ -674,19 +698,22 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
674
698
  #
675
699
  # You can define a custom field converter:
676
700
  # strip_converter = proc {|field| field.strip }
677
- # Add it to the \Converters \Hash:
678
- # CSV::Converters[:strip] = strip_converter
679
- # Use it by name:
680
701
  # string = " foo , 0 \n bar , 1 \n baz , 2 \n"
681
702
  # array = CSV.parse(string, converters: strip_converter)
682
703
  # array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
704
+ # You can register the converter in \Converters \Hash,
705
+ # which allows you to refer to it by name:
706
+ # CSV::Converters[:strip] = strip_converter
707
+ # string = " foo , 0 \n bar , 1 \n baz , 2 \n"
708
+ # array = CSV.parse(string, converters: :strip)
709
+ # array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
683
710
  #
684
711
  # ==== Header \Converters
685
712
  #
686
713
  # Header converters operate only on headers (and not on other rows).
687
714
  #
688
715
  # There are three ways to use header \converters;
689
- # these examples use built-in header converter +:dowhcase+,
716
+ # these examples use built-in header converter +:downcase+,
690
717
  # which downcases each parsed header.
691
718
  #
692
719
  # - Option +header_converters+ with a singleton parsing method:
@@ -737,13 +764,16 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
737
764
  #
738
765
  # You can define a custom header converter:
739
766
  # upcase_converter = proc {|header| header.upcase }
740
- # Add it to the \HeaderConverters \Hash:
741
- # CSV::HeaderConverters[:upcase] = upcase_converter
742
- # Use it by name:
743
767
  # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
744
- # table = CSV.parse(string, headers: true, converters: upcase_converter)
768
+ # table = CSV.parse(string, headers: true, header_converters: upcase_converter)
745
769
  # table # => #<CSV::Table mode:col_or_row row_count:4>
746
- # table.headers # => ["Name", "Value"]
770
+ # table.headers # => ["NAME", "VALUE"]
771
+ # You can register the converter in \HeaderConverters \Hash,
772
+ # which allows you to refer to it by name:
773
+ # CSV::HeaderConverters[:upcase] = upcase_converter
774
+ # table = CSV.parse(string, headers: true, header_converters: :upcase)
775
+ # table # => #<CSV::Table mode:col_or_row row_count:4>
776
+ # table.headers # => ["NAME", "VALUE"]
747
777
  #
748
778
  # ===== Write \Converters
749
779
  #
@@ -752,23 +782,23 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
752
782
  # its return value becomes the new value for the field.
753
783
  # A converter might, for example, strip whitespace from a field.
754
784
  #
755
- # - Using no write converter (all fields unmodified):
756
- # output_string = CSV.generate do |csv|
757
- # csv << [' foo ', 0]
758
- # csv << [' bar ', 1]
759
- # csv << [' baz ', 2]
760
- # end
761
- # output_string # => " foo ,0\n bar ,1\n baz ,2\n"
762
- # - Using option +write_converters+:
763
- # strip_converter = proc {|field| field.respond_to?(:strip) ? field.strip : field }
764
- # upcase_converter = proc {|field| field.respond_to?(:upcase) ? field.upcase : field }
765
- # converters = [strip_converter, upcase_converter]
766
- # output_string = CSV.generate(write_converters: converters) do |csv|
767
- # csv << [' foo ', 0]
768
- # csv << [' bar ', 1]
769
- # csv << [' baz ', 2]
770
- # end
771
- # output_string # => "FOO,0\nBAR,1\nBAZ,2\n"
785
+ # Using no write converter (all fields unmodified):
786
+ # output_string = CSV.generate do |csv|
787
+ # csv << [' foo ', 0]
788
+ # csv << [' bar ', 1]
789
+ # csv << [' baz ', 2]
790
+ # end
791
+ # output_string # => " foo ,0\n bar ,1\n baz ,2\n"
792
+ # Using option +write_converters+ with two custom write converters:
793
+ # strip_converter = proc {|field| field.respond_to?(:strip) ? field.strip : field }
794
+ # upcase_converter = proc {|field| field.respond_to?(:upcase) ? field.upcase : field }
795
+ # write_converters = [strip_converter, upcase_converter]
796
+ # output_string = CSV.generate(write_converters: write_converters) do |csv|
797
+ # csv << [' foo ', 0]
798
+ # csv << [' bar ', 1]
799
+ # csv << [' baz ', 2]
800
+ # end
801
+ # output_string # => "FOO,0\nBAR,1\nBAZ,2\n"
772
802
  #
773
803
  # === Character Encodings (M17n or Multilingualization)
774
804
  #
@@ -897,6 +927,7 @@ class CSV
897
927
  gsub(/\s+/, "_").to_sym
898
928
  }
899
929
  }
930
+
900
931
  # Default values for method options.
901
932
  DEFAULT_OPTIONS = {
902
933
  # For both parsing and generating.
@@ -935,6 +966,8 @@ class CSV
935
966
  # Creates or retrieves cached \CSV objects.
936
967
  # For arguments and options, see CSV.new.
937
968
  #
969
+ # This API is not Ractor-safe.
970
+ #
938
971
  # ---
939
972
  #
940
973
  # With no block given, returns a \CSV object.
@@ -984,63 +1017,188 @@ class CSV
984
1017
  end
985
1018
 
986
1019
  # :call-seq:
987
- # filter(**options) {|row| ... }
988
- # filter(in_string, **options) {|row| ... }
989
- # filter(in_io, **options) {|row| ... }
990
- # filter(in_string, out_string, **options) {|row| ... }
991
- # filter(in_string, out_io, **options) {|row| ... }
992
- # filter(in_io, out_string, **options) {|row| ... }
993
- # filter(in_io, out_io, **options) {|row| ... }
994
- #
995
- # Reads \CSV input and writes \CSV output.
996
- #
997
- # For each input row:
998
- # - Forms the data into:
999
- # - A CSV::Row object, if headers are in use.
1000
- # - An \Array of Arrays, otherwise.
1001
- # - Calls the block with that object.
1002
- # - Appends the block's return value to the output.
1020
+ # filter(in_string_or_io, **options) {|row| ... } -> array_of_arrays or csv_table
1021
+ # filter(in_string_or_io, out_string_or_io, **options) {|row| ... } -> array_of_arrays or csv_table
1022
+ # filter(**options) {|row| ... } -> array_of_arrays or csv_table
1003
1023
  #
1004
- # Arguments:
1005
- # * \CSV source:
1006
- # * Argument +in_string+, if given, should be a \String object;
1007
- # it will be put into a new StringIO object positioned at the beginning.
1008
- # * Argument +in_io+, if given, should be an IO object that is
1009
- # open for reading; on return, the IO object will be closed.
1010
- # * If neither +in_string+ nor +in_io+ is given,
1011
- # the input stream defaults to {ARGF}[https://ruby-doc.org/core/ARGF.html].
1012
- # * \CSV output:
1013
- # * Argument +out_string+, if given, should be a \String object;
1014
- # it will be put into a new StringIO object positioned at the beginning.
1015
- # * Argument +out_io+, if given, should be an IO object that is
1016
- # ppen for writing; on return, the IO object will be closed.
1017
- # * If neither +out_string+ nor +out_io+ is given,
1018
- # the output stream defaults to <tt>$stdout</tt>.
1019
- # * Argument +options+ should be keyword arguments.
1020
- # - Each argument name that is prefixed with +in_+ or +input_+
1021
- # is stripped of its prefix and is treated as an option
1022
- # for parsing the input.
1023
- # Option +input_row_sep+ defaults to <tt>$INPUT_RECORD_SEPARATOR</tt>.
1024
- # - Each argument name that is prefixed with +out_+ or +output_+
1025
- # is stripped of its prefix and is treated as an option
1026
- # for generating the output.
1027
- # Option +output_row_sep+ defaults to <tt>$INPUT_RECORD_SEPARATOR</tt>.
1028
- # - Each argument not prefixed as above is treated as an option
1029
- # both for parsing the input and for generating the output.
1030
- # - See {Options for Parsing}[#class-CSV-label-Options+for+Parsing]
1031
- # and {Options for Generating}[#class-CSV-label-Options+for+Generating].
1024
+ # - Parses \CSV from a source (\String, \IO stream, or ARGF).
1025
+ # - Calls the given block with each parsed row:
1026
+ # - Without headers, each row is an \Array.
1027
+ # - With headers, each row is a CSV::Row.
1028
+ # - Generates \CSV to an output (\String, \IO stream, or STDOUT).
1029
+ # - Returns the parsed source:
1030
+ # - Without headers, an \Array of \Arrays.
1031
+ # - With headers, a CSV::Table.
1032
1032
  #
1033
- # Example:
1034
- # in_string = "foo,0\nbar,1\nbaz,2\n"
1033
+ # When +in_string_or_io+ is given, but not +out_string_or_io+,
1034
+ # parses from the given +in_string_or_io+
1035
+ # and generates to STDOUT.
1036
+ #
1037
+ # \String input without headers:
1038
+ #
1039
+ # in_string = "foo,0\nbar,1\nbaz,2"
1040
+ # CSV.filter(in_string) do |row|
1041
+ # row[0].upcase!
1042
+ # row[1] = - row[1].to_i
1043
+ # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
1044
+ #
1045
+ # Output (to STDOUT):
1046
+ #
1047
+ # FOO,0
1048
+ # BAR,-1
1049
+ # BAZ,-2
1050
+ #
1051
+ # \String input with headers:
1052
+ #
1053
+ # in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
1054
+ # CSV.filter(in_string, headers: true) do |row|
1055
+ # row[0].upcase!
1056
+ # row[1] = - row[1].to_i
1057
+ # end # => #<CSV::Table mode:col_or_row row_count:4>
1058
+ #
1059
+ # Output (to STDOUT):
1060
+ #
1061
+ # Name,Value
1062
+ # FOO,0
1063
+ # BAR,-1
1064
+ # BAZ,-2
1065
+ #
1066
+ # \IO stream input without headers:
1067
+ #
1068
+ # File.write('t.csv', "foo,0\nbar,1\nbaz,2")
1069
+ # File.open('t.csv') do |in_io|
1070
+ # CSV.filter(in_io) do |row|
1071
+ # row[0].upcase!
1072
+ # row[1] = - row[1].to_i
1073
+ # end
1074
+ # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
1075
+ #
1076
+ # Output (to STDOUT):
1077
+ #
1078
+ # FOO,0
1079
+ # BAR,-1
1080
+ # BAZ,-2
1081
+ #
1082
+ # \IO stream input with headers:
1083
+ #
1084
+ # File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2")
1085
+ # File.open('t.csv') do |in_io|
1086
+ # CSV.filter(in_io, headers: true) do |row|
1087
+ # row[0].upcase!
1088
+ # row[1] = - row[1].to_i
1089
+ # end
1090
+ # end # => #<CSV::Table mode:col_or_row row_count:4>
1091
+ #
1092
+ # Output (to STDOUT):
1093
+ #
1094
+ # Name,Value
1095
+ # FOO,0
1096
+ # BAR,-1
1097
+ # BAZ,-2
1098
+ #
1099
+ # When both +in_string_or_io+ and +out_string_or_io+ are given,
1100
+ # parses from +in_string_or_io+ and generates to +out_string_or_io+.
1101
+ #
1102
+ # \String output without headers:
1103
+ #
1104
+ # in_string = "foo,0\nbar,1\nbaz,2"
1035
1105
  # out_string = ''
1036
1106
  # CSV.filter(in_string, out_string) do |row|
1037
- # row[0] = row[0].upcase
1038
- # row[1] *= 4
1039
- # end
1040
- # out_string # => "FOO,0000\nBAR,1111\nBAZ,2222\n"
1107
+ # row[0].upcase!
1108
+ # row[1] = - row[1].to_i
1109
+ # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
1110
+ # out_string # => "FOO,0\nBAR,-1\nBAZ,-2\n"
1111
+ #
1112
+ # \String output with headers:
1113
+ #
1114
+ # in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
1115
+ # out_string = ''
1116
+ # CSV.filter(in_string, out_string, headers: true) do |row|
1117
+ # row[0].upcase!
1118
+ # row[1] = - row[1].to_i
1119
+ # end # => #<CSV::Table mode:col_or_row row_count:4>
1120
+ # out_string # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n"
1121
+ #
1122
+ # \IO stream output without headers:
1123
+ #
1124
+ # in_string = "foo,0\nbar,1\nbaz,2"
1125
+ # File.open('t.csv', 'w') do |out_io|
1126
+ # CSV.filter(in_string, out_io) do |row|
1127
+ # row[0].upcase!
1128
+ # row[1] = - row[1].to_i
1129
+ # end
1130
+ # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
1131
+ # File.read('t.csv') # => "FOO,0\nBAR,-1\nBAZ,-2\n"
1132
+ #
1133
+ # \IO stream output with headers:
1134
+ #
1135
+ # in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
1136
+ # File.open('t.csv', 'w') do |out_io|
1137
+ # CSV.filter(in_string, out_io, headers: true) do |row|
1138
+ # row[0].upcase!
1139
+ # row[1] = - row[1].to_i
1140
+ # end
1141
+ # end # => #<CSV::Table mode:col_or_row row_count:4>
1142
+ # File.read('t.csv') # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n"
1143
+ #
1144
+ # When neither +in_string_or_io+ nor +out_string_or_io+ given,
1145
+ # parses from {ARGF}[https://docs.ruby-lang.org/en/master/ARGF.html]
1146
+ # and generates to STDOUT.
1147
+ #
1148
+ # Without headers:
1149
+ #
1150
+ # # Put Ruby code into a file.
1151
+ # ruby = <<-EOT
1152
+ # require 'csv'
1153
+ # CSV.filter do |row|
1154
+ # row[0].upcase!
1155
+ # row[1] = - row[1].to_i
1156
+ # end
1157
+ # EOT
1158
+ # File.write('t.rb', ruby)
1159
+ # # Put some CSV into a file.
1160
+ # File.write('t.csv', "foo,0\nbar,1\nbaz,2")
1161
+ # # Run the Ruby code with CSV filename as argument.
1162
+ # system(Gem.ruby, "t.rb", "t.csv")
1163
+ #
1164
+ # Output (to STDOUT):
1165
+ #
1166
+ # FOO,0
1167
+ # BAR,-1
1168
+ # BAZ,-2
1169
+ #
1170
+ # With headers:
1171
+ #
1172
+ # # Put Ruby code into a file.
1173
+ # ruby = <<-EOT
1174
+ # require 'csv'
1175
+ # CSV.filter(headers: true) do |row|
1176
+ # row[0].upcase!
1177
+ # row[1] = - row[1].to_i
1178
+ # end
1179
+ # EOT
1180
+ # File.write('t.rb', ruby)
1181
+ # # Put some CSV into a file.
1182
+ # File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2")
1183
+ # # Run the Ruby code with CSV filename as argument.
1184
+ # system(Gem.ruby, "t.rb", "t.csv")
1185
+ #
1186
+ # Output (to STDOUT):
1187
+ #
1188
+ # Name,Value
1189
+ # FOO,0
1190
+ # BAR,-1
1191
+ # BAZ,-2
1192
+ #
1193
+ # Arguments:
1194
+ #
1195
+ # * Argument +in_string_or_io+ must be a \String or an \IO stream.
1196
+ # * Argument +out_string_or_io+ must be a \String or an \IO stream.
1197
+ # * Arguments <tt>**options</tt> must be keyword options.
1198
+ # See {Options for Parsing}[#class-CSV-label-Options+for+Parsing].
1041
1199
  def filter(input=nil, output=nil, **options)
1042
1200
  # parse options for input, output, or both
1043
- in_options, out_options = Hash.new, {row_sep: $INPUT_RECORD_SEPARATOR}
1201
+ in_options, out_options = Hash.new, {row_sep: InputRecordSeparator.value}
1044
1202
  options.each do |key, value|
1045
1203
  case key.to_s
1046
1204
  when /\Ain(?:put)?_(.+)\Z/
@@ -1052,10 +1210,29 @@ class CSV
1052
1210
  out_options[key] = value
1053
1211
  end
1054
1212
  end
1213
+
1055
1214
  # build input and output wrappers
1056
- input = new(input || ARGF, **in_options)
1215
+ input = new(input || ARGF, **in_options)
1057
1216
  output = new(output || $stdout, **out_options)
1058
1217
 
1218
+ # process headers
1219
+ need_manual_header_output =
1220
+ (in_options[:headers] and
1221
+ out_options[:headers] == true and
1222
+ out_options[:write_headers])
1223
+ if need_manual_header_output
1224
+ first_row = input.shift
1225
+ if first_row
1226
+ if first_row.is_a?(Row)
1227
+ headers = first_row.headers
1228
+ yield headers
1229
+ output << headers
1230
+ end
1231
+ yield first_row
1232
+ output << first_row
1233
+ end
1234
+ end
1235
+
1059
1236
  # read, yield, write
1060
1237
  input.each do |row|
1061
1238
  yield row
@@ -1065,111 +1242,90 @@ class CSV
1065
1242
 
1066
1243
  #
1067
1244
  # :call-seq:
1068
- # foreach(path, mode='r', **options) {|row| ... )
1069
- # foreach(io, mode='r', **options {|row| ... )
1070
- # foreach(path, mode='r', headers: ..., **options) {|row| ... )
1071
- # foreach(io, mode='r', headers: ..., **options {|row| ... )
1072
- # foreach(path, mode='r', **options) -> new_enumerator
1073
- # foreach(io, mode='r', **options -> new_enumerator
1245
+ # foreach(path_or_io, mode='r', **options) {|row| ... )
1246
+ # foreach(path_or_io, mode='r', **options) -> new_enumerator
1074
1247
  #
1075
- # Calls the block with each row read from source +path+ or +io+.
1248
+ # Calls the block with each row read from source +path_or_io+.
1076
1249
  #
1077
- # * Argument +path+, if given, must be the path to a file.
1078
- # :include: ../doc/csv/arguments/io.rdoc
1079
- # * Argument +mode+, if given, must be a \File mode
1080
- # See {Open Mode}[IO.html#method-c-new-label-Open+Mode].
1081
- # * Arguments <tt>**options</tt> must be keyword options.
1082
- # See {Options for Parsing}[#class-CSV-label-Options+for+Parsing].
1083
- # * This method optionally accepts an additional <tt>:encoding</tt> option
1084
- # that you can use to specify the Encoding of the data read from +path+ or +io+.
1085
- # You must provide this unless your data is in the encoding
1086
- # given by <tt>Encoding::default_external</tt>.
1087
- # Parsing will use this to determine how to parse the data.
1088
- # You may provide a second Encoding to
1089
- # have the data transcoded as it is read. For example,
1090
- # encoding: 'UTF-32BE:UTF-8'
1091
- # would read +UTF-32BE+ data from the file
1092
- # but transcode it to +UTF-8+ before parsing.
1250
+ # \Path input without headers:
1093
1251
  #
1094
- # ====== Without Option +headers+
1095
- #
1096
- # Without option +headers+, returns each row as an \Array object.
1097
- #
1098
- # These examples assume prior execution of:
1099
1252
  # string = "foo,0\nbar,1\nbaz,2\n"
1100
- # path = 't.csv'
1101
- # File.write(path, string)
1253
+ # in_path = 't.csv'
1254
+ # File.write(in_path, string)
1255
+ # CSV.foreach(in_path) {|row| p row }
1102
1256
  #
1103
- # Read rows from a file at +path+:
1104
- # CSV.foreach(path) {|row| p row }
1105
1257
  # Output:
1106
- # ["foo", "0"]
1107
- # ["bar", "1"]
1108
- # ["baz", "2"]
1109
1258
  #
1110
- # Read rows from an \IO object:
1111
- # File.open(path) do |file|
1112
- # CSV.foreach(file) {|row| p row }
1113
- # end
1114
- #
1115
- # Output:
1116
1259
  # ["foo", "0"]
1117
1260
  # ["bar", "1"]
1118
1261
  # ["baz", "2"]
1119
1262
  #
1120
- # Returns a new \Enumerator if no block given:
1121
- # CSV.foreach(path) # => #<Enumerator: CSV:foreach("t.csv", "r")>
1122
- # CSV.foreach(File.open(path)) # => #<Enumerator: CSV:foreach(#<File:t.csv>, "r")>
1263
+ # \Path input with headers:
1264
+ #
1265
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
1266
+ # in_path = 't.csv'
1267
+ # File.write(in_path, string)
1268
+ # CSV.foreach(in_path, headers: true) {|row| p row }
1123
1269
  #
1124
- # Issues a warning if an encoding is unsupported:
1125
- # CSV.foreach(File.open(path), encoding: 'foo:bar') {|row| }
1126
1270
  # Output:
1127
- # warning: Unsupported encoding foo ignored
1128
- # warning: Unsupported encoding bar ignored
1129
1271
  #
1130
- # ====== With Option +headers+
1272
+ # <CSV::Row "Name":"foo" "Value":"0">
1273
+ # <CSV::Row "Name":"bar" "Value":"1">
1274
+ # <CSV::Row "Name":"baz" "Value":"2">
1131
1275
  #
1132
- # With {option +headers+}[#class-CSV-label-Option+headers],
1133
- # returns each row as a CSV::Row object.
1276
+ # \IO stream input without headers:
1134
1277
  #
1135
- # These examples assume prior execution of:
1136
- # string = "Name,Count\nfoo,0\nbar,1\nbaz,2\n"
1278
+ # string = "foo,0\nbar,1\nbaz,2\n"
1137
1279
  # path = 't.csv'
1138
1280
  # File.write(path, string)
1139
- #
1140
- # Read rows from a file at +path+:
1141
- # CSV.foreach(path, headers: true) {|row| p row }
1281
+ # File.open('t.csv') do |in_io|
1282
+ # CSV.foreach(in_io) {|row| p row }
1283
+ # end
1142
1284
  #
1143
1285
  # Output:
1144
- # #<CSV::Row "Name":"foo" "Count":"0">
1145
- # #<CSV::Row "Name":"bar" "Count":"1">
1146
- # #<CSV::Row "Name":"baz" "Count":"2">
1147
1286
  #
1148
- # Read rows from an \IO object:
1149
- # File.open(path) do |file|
1150
- # CSV.foreach(file, headers: true) {|row| p row }
1287
+ # ["foo", "0"]
1288
+ # ["bar", "1"]
1289
+ # ["baz", "2"]
1290
+ #
1291
+ # \IO stream input with headers:
1292
+ #
1293
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
1294
+ # path = 't.csv'
1295
+ # File.write(path, string)
1296
+ # File.open('t.csv') do |in_io|
1297
+ # CSV.foreach(in_io, headers: true) {|row| p row }
1151
1298
  # end
1152
1299
  #
1153
1300
  # Output:
1154
- # #<CSV::Row "Name":"foo" "Count":"0">
1155
- # #<CSV::Row "Name":"bar" "Count":"1">
1156
- # #<CSV::Row "Name":"baz" "Count":"2">
1157
- #
1158
- # ---
1159
1301
  #
1160
- # Raises an exception if +path+ is a \String, but not the path to a readable file:
1161
- # # Raises Errno::ENOENT (No such file or directory @ rb_sysopen - nosuch.csv):
1162
- # CSV.foreach('nosuch.csv') {|row| }
1302
+ # <CSV::Row "Name":"foo" "Value":"0">
1303
+ # <CSV::Row "Name":"bar" "Value":"1">
1304
+ # <CSV::Row "Name":"baz" "Value":"2">
1163
1305
  #
1164
- # Raises an exception if +io+ is an \IO object, but not open for reading:
1165
- # io = File.open(path, 'w') {|row| }
1166
- # # Raises TypeError (no implicit conversion of nil into String):
1167
- # CSV.foreach(io) {|row| }
1306
+ # With no block given, returns an \Enumerator:
1168
1307
  #
1169
- # Raises an exception if +mode+ is invalid:
1170
- # # Raises ArgumentError (invalid access mode nosuch):
1171
- # CSV.foreach(path, 'nosuch') {|row| }
1308
+ # string = "foo,0\nbar,1\nbaz,2\n"
1309
+ # path = 't.csv'
1310
+ # File.write(path, string)
1311
+ # CSV.foreach(path) # => #<Enumerator: CSV:foreach("t.csv", "r")>
1172
1312
  #
1313
+ # Arguments:
1314
+ # * Argument +path_or_io+ must be a file path or an \IO stream.
1315
+ # * Argument +mode+, if given, must be a \File mode
1316
+ # See {Open Mode}[https://ruby-doc.org/core/IO.html#method-c-new-label-Open+Mode].
1317
+ # * Arguments <tt>**options</tt> must be keyword options.
1318
+ # See {Options for Parsing}[#class-CSV-label-Options+for+Parsing].
1319
+ # * This method optionally accepts an additional <tt>:encoding</tt> option
1320
+ # that you can use to specify the Encoding of the data read from +path+ or +io+.
1321
+ # You must provide this unless your data is in the encoding
1322
+ # given by <tt>Encoding::default_external</tt>.
1323
+ # Parsing will use this to determine how to parse the data.
1324
+ # You may provide a second Encoding to
1325
+ # have the data transcoded as it is read. For example,
1326
+ # encoding: 'UTF-32BE:UTF-8'
1327
+ # would read +UTF-32BE+ data from the file
1328
+ # but transcode it to +UTF-8+ before parsing.
1173
1329
  def foreach(path, mode="r", **options, &block)
1174
1330
  return to_enum(__method__, path, mode, **options) unless block_given?
1175
1331
  open(path, mode, **options) do |csv|
@@ -1262,8 +1418,8 @@ class CSV
1262
1418
  # Argument +ary+ must be an \Array.
1263
1419
  #
1264
1420
  # Special options:
1265
- # * Option <tt>:row_sep</tt> defaults to <tt>$INPUT_RECORD_SEPARATOR</tt>
1266
- # (<tt>$/</tt>).:
1421
+ # * Option <tt>:row_sep</tt> defaults to <tt>"\n"> on Ruby 3.0 or later
1422
+ # and <tt>$INPUT_RECORD_SEPARATOR</tt> (<tt>$/</tt>) otherwise.:
1267
1423
  # $INPUT_RECORD_SEPARATOR # => "\n"
1268
1424
  # * This method accepts an additional option, <tt>:encoding</tt>, which sets the base
1269
1425
  # Encoding for the output. This method will try to guess your Encoding from
@@ -1285,7 +1441,7 @@ class CSV
1285
1441
  # CSV.generate_line(:foo)
1286
1442
  #
1287
1443
  def generate_line(row, **options)
1288
- options = {row_sep: $INPUT_RECORD_SEPARATOR}.merge(options)
1444
+ options = {row_sep: InputRecordSeparator.value}.merge(options)
1289
1445
  str = +""
1290
1446
  if options[:encoding]
1291
1447
  str.force_encoding(options[:encoding])
@@ -1315,7 +1471,7 @@ class CSV
1315
1471
  # open(io, mode = "rb", **options ) { |csv| ... } -> object
1316
1472
  #
1317
1473
  # possible options elements:
1318
- # hash form:
1474
+ # keyword form:
1319
1475
  # :invalid => nil # raise error on invalid byte sequence (default)
1320
1476
  # :invalid => :replace # replace invalid byte sequence
1321
1477
  # :undef => :replace # replace undefined conversion
@@ -1382,10 +1538,14 @@ class CSV
1382
1538
  def open(filename, mode="r", **options)
1383
1539
  # wrap a File opened with the remaining +args+ with no newline
1384
1540
  # decorator
1385
- file_opts = {universal_newline: false}.merge(options)
1541
+ file_opts = options.dup
1542
+ unless file_opts.key?(:newline)
1543
+ file_opts[:universal_newline] ||= false
1544
+ end
1386
1545
  options.delete(:invalid)
1387
1546
  options.delete(:undef)
1388
1547
  options.delete(:replace)
1548
+ options.delete_if {|k, _| /newline\z/.match?(k)}
1389
1549
 
1390
1550
  begin
1391
1551
  f = File.open(filename, mode, **file_opts)
@@ -1640,7 +1800,7 @@ class CSV
1640
1800
  #
1641
1801
  # Calls CSV.read with +source+, +options+, and certain default options:
1642
1802
  # - +headers+: +true+
1643
- # - +converbers+: +:numeric+
1803
+ # - +converters+: +:numeric+
1644
1804
  # - +header_converters+: +:symbol+
1645
1805
  #
1646
1806
  # Returns a CSV::Table object.
@@ -1843,6 +2003,10 @@ class CSV
1843
2003
  # csv.converters # => [:integer]
1844
2004
  # csv.convert(proc {|x| x.to_s })
1845
2005
  # csv.converters
2006
+ #
2007
+ # Notes that you need to call
2008
+ # +Ractor.make_shareable(CSV::Converters)+ on the main Ractor to use
2009
+ # this method.
1846
2010
  def converters
1847
2011
  parser_fields_converter.map do |converter|
1848
2012
  name = Converters.rassoc(converter)
@@ -1905,6 +2069,10 @@ class CSV
1905
2069
  # Returns an \Array containing header converters; used for parsing;
1906
2070
  # see {Header Converters}[#class-CSV-label-Header+Converters]:
1907
2071
  # CSV.new('').header_converters # => []
2072
+ #
2073
+ # Notes that you need to call
2074
+ # +Ractor.make_shareable(CSV::HeaderConverters)+ on the main Ractor
2075
+ # to use this method.
1908
2076
  def header_converters
1909
2077
  header_fields_converter.map do |converter|
1910
2078
  name = HeaderConverters.rassoc(converter)
@@ -1944,7 +2112,7 @@ class CSV
1944
2112
  end
1945
2113
 
1946
2114
  # :call-seq:
1947
- # csv.encoding -> endcoding
2115
+ # csv.encoding -> encoding
1948
2116
  #
1949
2117
  # Returns the encoding used for parsing and generating;
1950
2118
  # see {Character Encodings (M17n or Multilingualization)}[#class-CSV-label-Character+Encodings+-28M17n+or+Multilingualization-29]:
@@ -2545,7 +2713,7 @@ class CSV
2545
2713
 
2546
2714
  def build_parser_fields_converter
2547
2715
  specific_options = {
2548
- builtin_converters: Converters,
2716
+ builtin_converters_name: :Converters,
2549
2717
  }
2550
2718
  options = @base_fields_converter_options.merge(specific_options)
2551
2719
  build_fields_converter(@initial_converters, options)
@@ -2557,7 +2725,7 @@ class CSV
2557
2725
 
2558
2726
  def build_header_fields_converter
2559
2727
  specific_options = {
2560
- builtin_converters: HeaderConverters,
2728
+ builtin_converters_name: :HeaderConverters,
2561
2729
  accept_nil: true,
2562
2730
  }
2563
2731
  options = @base_fields_converter_options.merge(specific_options)
@@ -2620,8 +2788,15 @@ end
2620
2788
  # c.read.any? { |a| a.include?("zombies") }
2621
2789
  # } #=> false
2622
2790
  #
2623
- def CSV(*args, &block)
2624
- CSV.instance(*args, &block)
2791
+ # CSV options may also be given.
2792
+ #
2793
+ # io = StringIO.new
2794
+ # CSV(io, col_sep: ";") { |csv| csv << ["a", "b", "c"] }
2795
+ #
2796
+ # This API is not Ractor-safe.
2797
+ #
2798
+ def CSV(*args, **options, &block)
2799
+ CSV.instance(*args, **options, &block)
2625
2800
  end
2626
2801
 
2627
2802
  require_relative "csv/version"