csv 1.0.2 → 3.2.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +868 -0
  3. data/README.md +6 -3
  4. data/doc/csv/arguments/io.rdoc +5 -0
  5. data/doc/csv/options/common/col_sep.rdoc +57 -0
  6. data/doc/csv/options/common/quote_char.rdoc +42 -0
  7. data/doc/csv/options/common/row_sep.rdoc +91 -0
  8. data/doc/csv/options/generating/force_quotes.rdoc +17 -0
  9. data/doc/csv/options/generating/quote_empty.rdoc +12 -0
  10. data/doc/csv/options/generating/write_converters.rdoc +25 -0
  11. data/doc/csv/options/generating/write_empty_value.rdoc +15 -0
  12. data/doc/csv/options/generating/write_headers.rdoc +29 -0
  13. data/doc/csv/options/generating/write_nil_value.rdoc +14 -0
  14. data/doc/csv/options/parsing/converters.rdoc +46 -0
  15. data/doc/csv/options/parsing/empty_value.rdoc +13 -0
  16. data/doc/csv/options/parsing/field_size_limit.rdoc +39 -0
  17. data/doc/csv/options/parsing/header_converters.rdoc +43 -0
  18. data/doc/csv/options/parsing/headers.rdoc +63 -0
  19. data/doc/csv/options/parsing/liberal_parsing.rdoc +38 -0
  20. data/doc/csv/options/parsing/nil_value.rdoc +12 -0
  21. data/doc/csv/options/parsing/return_headers.rdoc +22 -0
  22. data/doc/csv/options/parsing/skip_blanks.rdoc +31 -0
  23. data/doc/csv/options/parsing/skip_lines.rdoc +37 -0
  24. data/doc/csv/options/parsing/strip.rdoc +15 -0
  25. data/doc/csv/options/parsing/unconverted_fields.rdoc +27 -0
  26. data/doc/csv/recipes/filtering.rdoc +158 -0
  27. data/doc/csv/recipes/generating.rdoc +298 -0
  28. data/doc/csv/recipes/parsing.rdoc +545 -0
  29. data/doc/csv/recipes/recipes.rdoc +6 -0
  30. data/lib/csv/core_ext/array.rb +1 -1
  31. data/lib/csv/core_ext/string.rb +1 -1
  32. data/lib/csv/fields_converter.rb +89 -0
  33. data/lib/csv/input_record_separator.rb +18 -0
  34. data/lib/csv/parser.rb +1290 -0
  35. data/lib/csv/row.rb +505 -136
  36. data/lib/csv/table.rb +791 -114
  37. data/lib/csv/version.rb +1 -1
  38. data/lib/csv/writer.rb +210 -0
  39. data/lib/csv.rb +2432 -1329
  40. metadata +66 -13
  41. data/news.md +0 -112
data/lib/csv.rb CHANGED
@@ -10,18 +10,18 @@
10
10
  #
11
11
  # Welcome to the new and improved CSV.
12
12
  #
13
- # This version of the CSV library began its life as FasterCSV. FasterCSV was
14
- # intended as a replacement to Ruby's then standard CSV library. It was
13
+ # This version of the CSV library began its life as FasterCSV. FasterCSV was
14
+ # intended as a replacement to Ruby's then standard CSV library. It was
15
15
  # designed to address concerns users of that library had and it had three
16
16
  # primary goals:
17
17
  #
18
18
  # 1. Be significantly faster than CSV while remaining a pure Ruby library.
19
- # 2. Use a smaller and easier to maintain code base. (FasterCSV eventually
20
- # grew larger, was also but considerably richer in features. The parsing
19
+ # 2. Use a smaller and easier to maintain code base. (FasterCSV eventually
20
+ # grew larger, was also but considerably richer in features. The parsing
21
21
  # core remains quite small.)
22
22
  # 3. Improve on the CSV interface.
23
23
  #
24
- # Obviously, the last one is subjective. I did try to defer to the original
24
+ # Obviously, the last one is subjective. I did try to defer to the original
25
25
  # interface whenever I didn't have a compelling reason to change it though, so
26
26
  # hopefully this won't be too radically different.
27
27
  #
@@ -29,26 +29,26 @@
29
29
  # the original library as of Ruby 1.9. If you are migrating code from 1.8 or
30
30
  # earlier, you may have to change your code to comply with the new interface.
31
31
  #
32
- # == What's Different From the Old CSV?
32
+ # == What's the Different From the Old CSV?
33
33
  #
34
34
  # I'm sure I'll miss something, but I'll try to mention most of the major
35
35
  # differences I am aware of, to help others quickly get up to speed:
36
36
  #
37
- # === CSV Parsing
37
+ # === \CSV Parsing
38
38
  #
39
- # * This parser is m17n aware. See CSV for full details.
39
+ # * This parser is m17n aware. See CSV for full details.
40
40
  # * This library has a stricter parser and will throw MalformedCSVErrors on
41
41
  # problematic data.
42
- # * This library has a less liberal idea of a line ending than CSV. What you
43
- # set as the <tt>:row_sep</tt> is law. It can auto-detect your line endings
42
+ # * This library has a less liberal idea of a line ending than CSV. What you
43
+ # set as the <tt>:row_sep</tt> is law. It can auto-detect your line endings
44
44
  # though.
45
- # * The old library returned empty lines as <tt>[nil]</tt>. This library calls
45
+ # * The old library returned empty lines as <tt>[nil]</tt>. This library calls
46
46
  # them <tt>[]</tt>.
47
47
  # * This library has a much faster parser.
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().
@@ -56,9 +56,9 @@
56
56
  # * CSV now has a new() method used to wrap objects like String and IO for
57
57
  # reading and writing.
58
58
  # * CSV::generate() is different from the old method.
59
- # * CSV no longer supports partial reads. It works line-by-line.
59
+ # * CSV no longer supports partial reads. It works line-by-line.
60
60
  # * CSV no longer allows the instance methods to override the separators for
61
- # performance reasons. They must be set in the constructor.
61
+ # performance reasons. They must be set in the constructor.
62
62
  #
63
63
  # If you use this library and find yourself missing any functionality I have
64
64
  # trimmed, please {let me know}[mailto:james@grayproductions.net].
@@ -70,16 +70,16 @@
70
70
  # == What is CSV, really?
71
71
  #
72
72
  # CSV maintains a pretty strict definition of CSV taken directly from
73
- # {the RFC}[http://www.ietf.org/rfc/rfc4180.txt]. I relax the rules in only one
74
- # place and that is to make using this library easier. CSV will parse all valid
73
+ # {the RFC}[https://www.ietf.org/rfc/rfc4180.txt]. I relax the rules in only one
74
+ # place and that is to make using this library easier. CSV will parse all valid
75
75
  # CSV.
76
76
  #
77
- # What you don't want to do is feed CSV invalid data. Because of the way the
77
+ # What you don't want to do is to feed CSV invalid data. Because of the way the
78
78
  # CSV format works, it's common for a parser to need to read until the end of
79
- # the file to be sure a field is invalid. This eats a lot of time and memory.
79
+ # the file to be sure a field is invalid. This consumes a lot of time and memory.
80
80
  #
81
81
  # Luckily, when working with invalid CSV, Ruby's built-in methods will almost
82
- # always be superior in every way. For example, parsing non-quoted fields is as
82
+ # always be superior in every way. For example, parsing non-quoted fields is as
83
83
  # easy as:
84
84
  #
85
85
  # data.split(",")
@@ -90,189 +90,755 @@
90
90
  # with any questions.
91
91
 
92
92
  require "forwardable"
93
- require "English"
94
93
  require "date"
95
94
  require "stringio"
96
- require_relative "csv/table"
97
- require_relative "csv/row"
98
-
99
- # This provides String#match? and Regexp#match? for Ruby 2.3.
100
- unless String.method_defined?(:match?)
101
- class CSV
102
- module MatchP
103
- refine String do
104
- def match?(pattern)
105
- self =~ pattern
106
- end
107
- end
108
95
 
109
- refine Regexp do
110
- def match?(string)
111
- self =~ string
112
- end
113
- end
114
- end
115
- end
116
-
117
- using CSV::MatchP
118
- end
96
+ require_relative "csv/fields_converter"
97
+ require_relative "csv/input_record_separator"
98
+ require_relative "csv/parser"
99
+ require_relative "csv/row"
100
+ require_relative "csv/table"
101
+ require_relative "csv/writer"
119
102
 
103
+ # == \CSV
120
104
  #
121
- # This class provides a complete interface to CSV files and data. It offers
122
- # tools to enable you to read and write to and from Strings or IO objects, as
123
- # needed.
105
+ # === In a Hurry?
124
106
  #
125
- # The most generic interface of a class is:
107
+ # If you are familiar with \CSV data and have a particular task in mind,
108
+ # you may want to go directly to the:
109
+ # - {Recipes for CSV}[doc/csv/recipes/recipes_rdoc.html].
126
110
  #
127
- # csv = CSV.new(string_or_io, **options)
111
+ # Otherwise, read on here, about the API: classes, methods, and constants.
128
112
  #
129
- # # Reading: IO object should be open for read
130
- # csv.read # => array of rows
131
- # # or
132
- # csv.each do |row|
133
- # # ...
134
- # end
135
- # # or
136
- # row = csv.shift
113
+ # === \CSV Data
137
114
  #
138
- # # Writing: IO object should be open for write
139
- # csv << row
115
+ # \CSV (comma-separated values) data is a text representation of a table:
116
+ # - A _row_ _separator_ delimits table rows.
117
+ # A common row separator is the newline character <tt>"\n"</tt>.
118
+ # - A _column_ _separator_ delimits fields in a row.
119
+ # A common column separator is the comma character <tt>","</tt>.
140
120
  #
141
- # There are several specialized class methods for one-statement reading or writing,
142
- # described in the Specialized Methods section.
121
+ # This \CSV \String, with row separator <tt>"\n"</tt>
122
+ # and column separator <tt>","</tt>,
123
+ # has three rows and two columns:
124
+ # "foo,0\nbar,1\nbaz,2\n"
143
125
  #
144
- # If a String passed into ::new, it is internally wrapped into a StringIO object.
126
+ # Despite the name \CSV, a \CSV representation can use different separators.
145
127
  #
146
- # +options+ can be used for specifying the particular CSV flavor (column
147
- # separators, row separators, value quoting and so on), and for data conversion,
148
- # see Data Conversion section for the description of the latter.
128
+ # For more about tables, see the Wikipedia article
129
+ # "{Table (information)}[https://en.wikipedia.org/wiki/Table_(information)]",
130
+ # especially its section
131
+ # "{Simple table}[https://en.wikipedia.org/wiki/Table_(information)#Simple_table]"
149
132
  #
150
- # == Specialized Methods
133
+ # == \Class \CSV
151
134
  #
152
- # === Reading
135
+ # Class \CSV provides methods for:
136
+ # - Parsing \CSV data from a \String object, a \File (via its file path), or an \IO object.
137
+ # - Generating \CSV data to a \String object.
153
138
  #
154
- # # From a file: all at once
155
- # arr_of_rows = CSV.read("path/to/file.csv", **options)
156
- # # iterator-style:
157
- # CSV.foreach("path/to/file.csv", **options) do |row|
158
- # # ...
159
- # end
139
+ # To make \CSV available:
140
+ # require 'csv'
160
141
  #
161
- # # From a string
162
- # arr_of_rows = CSV.parse("CSV,data,String", **options)
163
- # # or
164
- # CSV.parse("CSV,data,String", **options) do |row|
165
- # # ...
166
- # end
142
+ # All examples here assume that this has been done.
143
+ #
144
+ # == Keeping It Simple
145
+ #
146
+ # A \CSV object has dozens of instance methods that offer fine-grained control
147
+ # of parsing and generating \CSV data.
148
+ # For many needs, though, simpler approaches will do.
149
+ #
150
+ # This section summarizes the singleton methods in \CSV
151
+ # that allow you to parse and generate without explicitly
152
+ # creating \CSV objects.
153
+ # For details, follow the links.
154
+ #
155
+ # === Simple Parsing
156
+ #
157
+ # Parsing methods commonly return either of:
158
+ # - An \Array of Arrays of Strings:
159
+ # - The outer \Array is the entire "table".
160
+ # - Each inner \Array is a row.
161
+ # - Each \String is a field.
162
+ # - A CSV::Table object. For details, see
163
+ # {\CSV with Headers}[#class-CSV-label-CSV+with+Headers].
164
+ #
165
+ # ==== Parsing a \String
166
+ #
167
+ # The input to be parsed can be a string:
168
+ # string = "foo,0\nbar,1\nbaz,2\n"
167
169
  #
168
- # === Writing
170
+ # \Method CSV.parse returns the entire \CSV data:
171
+ # CSV.parse(string) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
169
172
  #
170
- # # To a file
171
- # CSV.open("path/to/file.csv", "wb") do |csv|
172
- # csv << ["row", "of", "CSV", "data"]
173
- # csv << ["another", "row"]
174
- # # ...
173
+ # \Method CSV.parse_line returns only the first row:
174
+ # CSV.parse_line(string) # => ["foo", "0"]
175
+ #
176
+ # \CSV extends class \String with instance method String#parse_csv,
177
+ # which also returns only the first row:
178
+ # string.parse_csv # => ["foo", "0"]
179
+ #
180
+ # ==== Parsing Via a \File Path
181
+ #
182
+ # The input to be parsed can be in a file:
183
+ # string = "foo,0\nbar,1\nbaz,2\n"
184
+ # path = 't.csv'
185
+ # File.write(path, string)
186
+ #
187
+ # \Method CSV.read returns the entire \CSV data:
188
+ # CSV.read(path) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
189
+ #
190
+ # \Method CSV.foreach iterates, passing each row to the given block:
191
+ # CSV.foreach(path) do |row|
192
+ # p row
193
+ # end
194
+ # Output:
195
+ # ["foo", "0"]
196
+ # ["bar", "1"]
197
+ # ["baz", "2"]
198
+ #
199
+ # \Method CSV.table returns the entire \CSV data as a CSV::Table object:
200
+ # CSV.table(path) # => #<CSV::Table mode:col_or_row row_count:3>
201
+ #
202
+ # ==== Parsing from an Open \IO Stream
203
+ #
204
+ # The input to be parsed can be in an open \IO stream:
205
+ #
206
+ # \Method CSV.read returns the entire \CSV data:
207
+ # File.open(path) do |file|
208
+ # CSV.read(file)
209
+ # end # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
210
+ #
211
+ # As does method CSV.parse:
212
+ # File.open(path) do |file|
213
+ # CSV.parse(file)
214
+ # end # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
215
+ #
216
+ # \Method CSV.parse_line returns only the first row:
217
+ # File.open(path) do |file|
218
+ # CSV.parse_line(file)
219
+ # end # => ["foo", "0"]
220
+ #
221
+ # \Method CSV.foreach iterates, passing each row to the given block:
222
+ # File.open(path) do |file|
223
+ # CSV.foreach(file) do |row|
224
+ # p row
225
+ # end
226
+ # end
227
+ # Output:
228
+ # ["foo", "0"]
229
+ # ["bar", "1"]
230
+ # ["baz", "2"]
231
+ #
232
+ # \Method CSV.table returns the entire \CSV data as a CSV::Table object:
233
+ # File.open(path) do |file|
234
+ # CSV.table(file)
235
+ # end # => #<CSV::Table mode:col_or_row row_count:3>
236
+ #
237
+ # === Simple Generating
238
+ #
239
+ # \Method CSV.generate returns a \String;
240
+ # this example uses method CSV#<< to append the rows
241
+ # that are to be generated:
242
+ # output_string = CSV.generate do |csv|
243
+ # csv << ['foo', 0]
244
+ # csv << ['bar', 1]
245
+ # csv << ['baz', 2]
175
246
  # end
247
+ # output_string # => "foo,0\nbar,1\nbaz,2\n"
248
+ #
249
+ # \Method CSV.generate_line returns a \String containing the single row
250
+ # constructed from an \Array:
251
+ # CSV.generate_line(['foo', '0']) # => "foo,0\n"
176
252
  #
177
- # # To a String
178
- # csv_string = CSV.generate do |csv|
179
- # csv << ["row", "of", "CSV", "data"]
180
- # csv << ["another", "row"]
181
- # # ...
253
+ # \CSV extends class \Array with instance method <tt>Array#to_csv</tt>,
254
+ # which forms an \Array into a \String:
255
+ # ['foo', '0'].to_csv # => "foo,0\n"
256
+ #
257
+ # === "Filtering" \CSV
258
+ #
259
+ # \Method CSV.filter provides a Unix-style filter for \CSV data.
260
+ # The input data is processed to form the output data:
261
+ # in_string = "foo,0\nbar,1\nbaz,2\n"
262
+ # out_string = ''
263
+ # CSV.filter(in_string, out_string) do |row|
264
+ # row[0] = row[0].upcase
265
+ # row[1] *= 4
182
266
  # end
267
+ # out_string # => "FOO,0000\nBAR,1111\nBAZ,2222\n"
268
+ #
269
+ # == \CSV Objects
270
+ #
271
+ # There are three ways to create a \CSV object:
272
+ # - \Method CSV.new returns a new \CSV object.
273
+ # - \Method CSV.instance returns a new or cached \CSV object.
274
+ # - \Method \CSV() also returns a new or cached \CSV object.
275
+ #
276
+ # === Instance Methods
277
+ #
278
+ # \CSV has three groups of instance methods:
279
+ # - Its own internally defined instance methods.
280
+ # - Methods included by module Enumerable.
281
+ # - Methods delegated to class IO. See below.
282
+ #
283
+ # ==== Delegated Methods
284
+ #
285
+ # For convenience, a CSV object will delegate to many methods in class IO.
286
+ # (A few have wrapper "guard code" in \CSV.) You may call:
287
+ # * IO#binmode
288
+ # * #binmode?
289
+ # * IO#close
290
+ # * IO#close_read
291
+ # * IO#close_write
292
+ # * IO#closed?
293
+ # * #eof
294
+ # * #eof?
295
+ # * IO#external_encoding
296
+ # * IO#fcntl
297
+ # * IO#fileno
298
+ # * #flock
299
+ # * IO#flush
300
+ # * IO#fsync
301
+ # * IO#internal_encoding
302
+ # * #ioctl
303
+ # * IO#isatty
304
+ # * #path
305
+ # * IO#pid
306
+ # * IO#pos
307
+ # * IO#pos=
308
+ # * IO#reopen
309
+ # * #rewind
310
+ # * IO#seek
311
+ # * #stat
312
+ # * IO#string
313
+ # * IO#sync
314
+ # * IO#sync=
315
+ # * IO#tell
316
+ # * #to_i
317
+ # * #to_io
318
+ # * IO#truncate
319
+ # * IO#tty?
320
+ #
321
+ # === Options
322
+ #
323
+ # The default values for options are:
324
+ # DEFAULT_OPTIONS = {
325
+ # # For both parsing and generating.
326
+ # col_sep: ",",
327
+ # row_sep: :auto,
328
+ # quote_char: '"',
329
+ # # For parsing.
330
+ # field_size_limit: nil,
331
+ # converters: nil,
332
+ # unconverted_fields: nil,
333
+ # headers: false,
334
+ # return_headers: false,
335
+ # header_converters: nil,
336
+ # skip_blanks: false,
337
+ # skip_lines: nil,
338
+ # liberal_parsing: false,
339
+ # nil_value: nil,
340
+ # empty_value: "",
341
+ # strip: false,
342
+ # # For generating.
343
+ # write_headers: nil,
344
+ # quote_empty: true,
345
+ # force_quotes: false,
346
+ # write_converters: nil,
347
+ # write_nil_value: nil,
348
+ # write_empty_value: "",
349
+ # }
350
+ #
351
+ # ==== Options for Parsing
352
+ #
353
+ # Options for parsing, described in detail below, include:
354
+ # - +row_sep+: Specifies the row separator; used to delimit rows.
355
+ # - +col_sep+: Specifies the column separator; used to delimit fields.
356
+ # - +quote_char+: Specifies the quote character; used to quote fields.
357
+ # - +field_size_limit+: Specifies the maximum field size + 1 allowed.
358
+ # Deprecated since 3.2.3. Use +max_field_size+ instead.
359
+ # - +max_field_size+: Specifies the maximum field size allowed.
360
+ # - +converters+: Specifies the field converters to be used.
361
+ # - +unconverted_fields+: Specifies whether unconverted fields are to be available.
362
+ # - +headers+: Specifies whether data contains headers,
363
+ # or specifies the headers themselves.
364
+ # - +return_headers+: Specifies whether headers are to be returned.
365
+ # - +header_converters+: Specifies the header converters to be used.
366
+ # - +skip_blanks+: Specifies whether blanks lines are to be ignored.
367
+ # - +skip_lines+: Specifies how comments lines are to be recognized.
368
+ # - +strip+: Specifies whether leading and trailing whitespace are to be
369
+ # stripped from fields. This must be compatible with +col_sep+; if it is not,
370
+ # then an +ArgumentError+ exception will be raised.
371
+ # - +liberal_parsing+: Specifies whether \CSV should attempt to parse
372
+ # non-compliant data.
373
+ # - +nil_value+: Specifies the object that is to be substituted for each null (no-text) field.
374
+ # - +empty_value+: Specifies the object that is to be substituted for each empty field.
375
+ #
376
+ # :include: ../doc/csv/options/common/row_sep.rdoc
377
+ #
378
+ # :include: ../doc/csv/options/common/col_sep.rdoc
379
+ #
380
+ # :include: ../doc/csv/options/common/quote_char.rdoc
381
+ #
382
+ # :include: ../doc/csv/options/parsing/field_size_limit.rdoc
383
+ #
384
+ # :include: ../doc/csv/options/parsing/converters.rdoc
385
+ #
386
+ # :include: ../doc/csv/options/parsing/unconverted_fields.rdoc
387
+ #
388
+ # :include: ../doc/csv/options/parsing/headers.rdoc
389
+ #
390
+ # :include: ../doc/csv/options/parsing/return_headers.rdoc
391
+ #
392
+ # :include: ../doc/csv/options/parsing/header_converters.rdoc
393
+ #
394
+ # :include: ../doc/csv/options/parsing/skip_blanks.rdoc
395
+ #
396
+ # :include: ../doc/csv/options/parsing/skip_lines.rdoc
397
+ #
398
+ # :include: ../doc/csv/options/parsing/strip.rdoc
399
+ #
400
+ # :include: ../doc/csv/options/parsing/liberal_parsing.rdoc
401
+ #
402
+ # :include: ../doc/csv/options/parsing/nil_value.rdoc
403
+ #
404
+ # :include: ../doc/csv/options/parsing/empty_value.rdoc
405
+ #
406
+ # ==== Options for Generating
407
+ #
408
+ # Options for generating, described in detail below, include:
409
+ # - +row_sep+: Specifies the row separator; used to delimit rows.
410
+ # - +col_sep+: Specifies the column separator; used to delimit fields.
411
+ # - +quote_char+: Specifies the quote character; used to quote fields.
412
+ # - +write_headers+: Specifies whether headers are to be written.
413
+ # - +force_quotes+: Specifies whether each output field is to be quoted.
414
+ # - +quote_empty+: Specifies whether each empty output field is to be quoted.
415
+ # - +write_converters+: Specifies the field converters to be used in writing.
416
+ # - +write_nil_value+: Specifies the object that is to be substituted for each +nil+-valued field.
417
+ # - +write_empty_value+: Specifies the object that is to be substituted for each empty field.
418
+ #
419
+ # :include: ../doc/csv/options/common/row_sep.rdoc
183
420
  #
184
- # === Shortcuts
421
+ # :include: ../doc/csv/options/common/col_sep.rdoc
185
422
  #
186
- # # Core extensions for converting one line
187
- # csv_string = ["CSV", "data"].to_csv # to CSV
188
- # csv_array = "CSV,String".parse_csv # from CSV
423
+ # :include: ../doc/csv/options/common/quote_char.rdoc
189
424
  #
190
- # # CSV() method
191
- # CSV { |csv_out| csv_out << %w{my data here} } # to $stdout
192
- # CSV(csv = "") { |csv_str| csv_str << %w{my data here} } # to a String
193
- # CSV($stderr) { |csv_err| csv_err << %w{my data here} } # to $stderr
194
- # CSV($stdin) { |csv_in| csv_in.each { |row| p row } } # from $stdin
425
+ # :include: ../doc/csv/options/generating/write_headers.rdoc
195
426
  #
196
- # == Data Conversion
427
+ # :include: ../doc/csv/options/generating/force_quotes.rdoc
197
428
  #
198
- # === CSV with headers
429
+ # :include: ../doc/csv/options/generating/quote_empty.rdoc
430
+ #
431
+ # :include: ../doc/csv/options/generating/write_converters.rdoc
432
+ #
433
+ # :include: ../doc/csv/options/generating/write_nil_value.rdoc
434
+ #
435
+ # :include: ../doc/csv/options/generating/write_empty_value.rdoc
436
+ #
437
+ # === \CSV with Headers
199
438
  #
200
439
  # CSV allows to specify column names of CSV file, whether they are in data, or
201
- # provided separately. If headers specified, reading methods return an instance
440
+ # provided separately. If headers are specified, reading methods return an instance
202
441
  # of CSV::Table, consisting of CSV::Row.
203
442
  #
204
443
  # # Headers are part of data
205
444
  # data = CSV.parse(<<~ROWS, headers: true)
206
445
  # Name,Department,Salary
207
- # Bob,Engeneering,1000
446
+ # Bob,Engineering,1000
208
447
  # Jane,Sales,2000
209
448
  # John,Management,5000
210
449
  # ROWS
211
450
  #
212
451
  # data.class #=> CSV::Table
213
- # data.first #=> #<CSV::Row "Name":"Bob" "Department":"Engeneering" "Salary":"1000">
214
- # data.first.to_h #=> {"Name"=>"Bob", "Department"=>"Engeneering", "Salary"=>"1000"}
452
+ # data.first #=> #<CSV::Row "Name":"Bob" "Department":"Engineering" "Salary":"1000">
453
+ # data.first.to_h #=> {"Name"=>"Bob", "Department"=>"Engineering", "Salary"=>"1000"}
215
454
  #
216
455
  # # Headers provided by developer
217
- # data = CSV.parse('Bob,Engeneering,1000', headers: %i[name department salary])
218
- # data.first #=> #<CSV::Row name:"Bob" department:"Engeneering" salary:"1000">
219
- #
220
- # === Typed data reading
221
- #
222
- # CSV allows to provide a set of data _converters_ e.g. transformations to try on input
223
- # data. Converter could be a symbol from CSV::Converters constant's keys, or lambda.
224
- #
225
- # # Without any converters:
226
- # CSV.parse('Bob,2018-03-01,100')
227
- # #=> [["Bob", "2018-03-01", "100"]]
228
- #
229
- # # With built-in converters:
230
- # CSV.parse('Bob,2018-03-01,100', converters: %i[numeric date])
231
- # #=> [["Bob", #<Date: 2018-03-01>, 100]]
232
- #
233
- # # With custom converters:
234
- # CSV.parse('Bob,2018-03-01,100', converters: [->(v) { Time.parse(v) rescue v }])
235
- # #=> [["Bob", 2018-03-01 00:00:00 +0200, "100"]]
456
+ # data = CSV.parse('Bob,Engineering,1000', headers: %i[name department salary])
457
+ # data.first #=> #<CSV::Row name:"Bob" department:"Engineering" salary:"1000">
458
+ #
459
+ # === \Converters
460
+ #
461
+ # By default, each value (field or header) parsed by \CSV is formed into a \String.
462
+ # You can use a _field_ _converter_ or _header_ _converter_
463
+ # to intercept and modify the parsed values:
464
+ # - See {Field Converters}[#class-CSV-label-Field+Converters].
465
+ # - See {Header Converters}[#class-CSV-label-Header+Converters].
466
+ #
467
+ # Also by default, each value to be written during generation is written 'as-is'.
468
+ # You can use a _write_ _converter_ to modify values before writing.
469
+ # - See {Write Converters}[#class-CSV-label-Write+Converters].
470
+ #
471
+ # ==== Specifying \Converters
472
+ #
473
+ # You can specify converters for parsing or generating in the +options+
474
+ # argument to various \CSV methods:
475
+ # - Option +converters+ for converting parsed field values.
476
+ # - Option +header_converters+ for converting parsed header values.
477
+ # - Option +write_converters+ for converting values to be written (generated).
478
+ #
479
+ # There are three forms for specifying converters:
480
+ # - A converter proc: executable code to be used for conversion.
481
+ # - A converter name: the name of a stored converter.
482
+ # - A converter list: an array of converter procs, converter names, and converter lists.
483
+ #
484
+ # ===== Converter Procs
485
+ #
486
+ # This converter proc, +strip_converter+, accepts a value +field+
487
+ # and returns <tt>field.strip</tt>:
488
+ # strip_converter = proc {|field| field.strip }
489
+ # In this call to <tt>CSV.parse</tt>,
490
+ # the keyword argument <tt>converters: string_converter</tt>
491
+ # specifies that:
492
+ # - \Proc +string_converter+ is to be called for each parsed field.
493
+ # - The converter's return value is to replace the +field+ value.
494
+ # Example:
495
+ # string = " foo , 0 \n bar , 1 \n baz , 2 \n"
496
+ # array = CSV.parse(string, converters: strip_converter)
497
+ # array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
498
+ #
499
+ # A converter proc can receive a second argument, +field_info+,
500
+ # that contains details about the field.
501
+ # This modified +strip_converter+ displays its arguments:
502
+ # strip_converter = proc do |field, field_info|
503
+ # p [field, field_info]
504
+ # field.strip
505
+ # end
506
+ # string = " foo , 0 \n bar , 1 \n baz , 2 \n"
507
+ # array = CSV.parse(string, converters: strip_converter)
508
+ # array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
509
+ # Output:
510
+ # [" foo ", #<struct CSV::FieldInfo index=0, line=1, header=nil>]
511
+ # [" 0 ", #<struct CSV::FieldInfo index=1, line=1, header=nil>]
512
+ # [" bar ", #<struct CSV::FieldInfo index=0, line=2, header=nil>]
513
+ # [" 1 ", #<struct CSV::FieldInfo index=1, line=2, header=nil>]
514
+ # [" baz ", #<struct CSV::FieldInfo index=0, line=3, header=nil>]
515
+ # [" 2 ", #<struct CSV::FieldInfo index=1, line=3, header=nil>]
516
+ # Each CSV::FieldInfo object shows:
517
+ # - The 0-based field index.
518
+ # - The 1-based line index.
519
+ # - The field header, if any.
520
+ #
521
+ # ===== Stored \Converters
522
+ #
523
+ # A converter may be given a name and stored in a structure where
524
+ # the parsing methods can find it by name.
525
+ #
526
+ # The storage structure for field converters is the \Hash CSV::Converters.
527
+ # It has several built-in converter procs:
528
+ # - <tt>:integer</tt>: converts each \String-embedded integer into a true \Integer.
529
+ # - <tt>:float</tt>: converts each \String-embedded float into a true \Float.
530
+ # - <tt>:date</tt>: converts each \String-embedded date into a true \Date.
531
+ # - <tt>:date_time</tt>: converts each \String-embedded date-time into a true \DateTime
532
+ # .
533
+ # This example creates a converter proc, then stores it:
534
+ # strip_converter = proc {|field| field.strip }
535
+ # CSV::Converters[:strip] = strip_converter
536
+ # Then the parsing method call can refer to the converter
537
+ # by its name, <tt>:strip</tt>:
538
+ # string = " foo , 0 \n bar , 1 \n baz , 2 \n"
539
+ # array = CSV.parse(string, converters: :strip)
540
+ # array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
541
+ #
542
+ # The storage structure for header converters is the \Hash CSV::HeaderConverters,
543
+ # which works in the same way.
544
+ # It also has built-in converter procs:
545
+ # - <tt>:downcase</tt>: Downcases each header.
546
+ # - <tt>:symbol</tt>: Converts each header to a \Symbol.
547
+ #
548
+ # There is no such storage structure for write headers.
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
+ #
558
+ # ===== Converter Lists
559
+ #
560
+ # A _converter_ _list_ is an \Array that may include any assortment of:
561
+ # - Converter procs.
562
+ # - Names of stored converters.
563
+ # - Nested converter lists.
564
+ #
565
+ # Examples:
566
+ # numeric_converters = [:integer, :float]
567
+ # date_converters = [:date, :date_time]
568
+ # [numeric_converters, strip_converter]
569
+ # [strip_converter, date_converters, :float]
570
+ #
571
+ # Like a converter proc, a converter list may be named and stored in either
572
+ # \CSV::Converters or CSV::HeaderConverters:
573
+ # CSV::Converters[:custom] = [strip_converter, date_converters, :float]
574
+ # CSV::HeaderConverters[:custom] = [:downcase, :symbol]
575
+ #
576
+ # There are two built-in converter lists:
577
+ # CSV::Converters[:numeric] # => [:integer, :float]
578
+ # CSV::Converters[:all] # => [:date_time, :numeric]
579
+ #
580
+ # ==== Field \Converters
581
+ #
582
+ # With no conversion, all parsed fields in all rows become Strings:
583
+ # string = "foo,0\nbar,1\nbaz,2\n"
584
+ # ary = CSV.parse(string)
585
+ # ary # => # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
586
+ #
587
+ # When you specify a field converter, each parsed field is passed to the converter;
588
+ # its return value becomes the stored value for the field.
589
+ # A converter might, for example, convert an integer embedded in a \String
590
+ # into a true \Integer.
591
+ # (In fact, that's what built-in field converter +:integer+ does.)
592
+ #
593
+ # There are three ways to use field \converters.
594
+ #
595
+ # - Using option {converters}[#class-CSV-label-Option+converters] with a parsing method:
596
+ # ary = CSV.parse(string, converters: :integer)
597
+ # ary # => [0, 1, 2] # => [["foo", 0], ["bar", 1], ["baz", 2]]
598
+ # - Using option {converters}[#class-CSV-label-Option+converters] with a new \CSV instance:
599
+ # csv = CSV.new(string, converters: :integer)
600
+ # # Field converters in effect:
601
+ # csv.converters # => [:integer]
602
+ # csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]]
603
+ # - Using method #convert to add a field converter to a \CSV instance:
604
+ # csv = CSV.new(string)
605
+ # # Add a converter.
606
+ # csv.convert(:integer)
607
+ # csv.converters # => [:integer]
608
+ # csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]]
609
+ #
610
+ # Installing a field converter does not affect already-read rows:
611
+ # csv = CSV.new(string)
612
+ # csv.shift # => ["foo", "0"]
613
+ # # Add a converter.
614
+ # csv.convert(:integer)
615
+ # csv.converters # => [:integer]
616
+ # csv.read # => [["bar", 1], ["baz", 2]]
617
+ #
618
+ # There are additional built-in \converters, and custom \converters are also supported.
619
+ #
620
+ # ===== Built-In Field \Converters
621
+ #
622
+ # The built-in field converters are in \Hash CSV::Converters:
623
+ # - Each key is a field converter name.
624
+ # - Each value is one of:
625
+ # - A \Proc field converter.
626
+ # - An \Array of field converter names.
627
+ #
628
+ # Display:
629
+ # CSV::Converters.each_pair do |name, value|
630
+ # if value.kind_of?(Proc)
631
+ # p [name, value.class]
632
+ # else
633
+ # p [name, value]
634
+ # end
635
+ # end
636
+ # Output:
637
+ # [:integer, Proc]
638
+ # [:float, Proc]
639
+ # [:numeric, [:integer, :float]]
640
+ # [:date, Proc]
641
+ # [:date_time, Proc]
642
+ # [:all, [:date_time, :numeric]]
643
+ #
644
+ # Each of these converters transcodes values to UTF-8 before attempting conversion.
645
+ # If a value cannot be transcoded to UTF-8 the conversion will
646
+ # fail and the value will remain unconverted.
647
+ #
648
+ # Converter +:integer+ converts each field that Integer() accepts:
649
+ # data = '0,1,2,x'
650
+ # # Without the converter
651
+ # csv = CSV.parse_line(data)
652
+ # csv # => ["0", "1", "2", "x"]
653
+ # # With the converter
654
+ # csv = CSV.parse_line(data, converters: :integer)
655
+ # csv # => [0, 1, 2, "x"]
656
+ #
657
+ # Converter +:float+ converts each field that Float() accepts:
658
+ # data = '1.0,3.14159,x'
659
+ # # Without the converter
660
+ # csv = CSV.parse_line(data)
661
+ # csv # => ["1.0", "3.14159", "x"]
662
+ # # With the converter
663
+ # csv = CSV.parse_line(data, converters: :float)
664
+ # csv # => [1.0, 3.14159, "x"]
665
+ #
666
+ # Converter +:numeric+ converts with both +:integer+ and +:float+..
667
+ #
668
+ # Converter +:date+ converts each field that Date::parse accepts:
669
+ # data = '2001-02-03,x'
670
+ # # Without the converter
671
+ # csv = CSV.parse_line(data)
672
+ # csv # => ["2001-02-03", "x"]
673
+ # # With the converter
674
+ # csv = CSV.parse_line(data, converters: :date)
675
+ # csv # => [#<Date: 2001-02-03 ((2451944j,0s,0n),+0s,2299161j)>, "x"]
676
+ #
677
+ # Converter +:date_time+ converts each field that DateTime::parse accepts:
678
+ # data = '2020-05-07T14:59:00-05:00,x'
679
+ # # Without the converter
680
+ # csv = CSV.parse_line(data)
681
+ # csv # => ["2020-05-07T14:59:00-05:00", "x"]
682
+ # # With the converter
683
+ # csv = CSV.parse_line(data, converters: :date_time)
684
+ # csv # => [#<DateTime: 2020-05-07T14:59:00-05:00 ((2458977j,71940s,0n),-18000s,2299161j)>, "x"]
685
+ #
686
+ # Converter +:numeric+ converts with both +:date_time+ and +:numeric+..
687
+ #
688
+ # As seen above, method #convert adds \converters to a \CSV instance,
689
+ # and method #converters returns an \Array of the \converters in effect:
690
+ # csv = CSV.new('0,1,2')
691
+ # csv.converters # => []
692
+ # csv.convert(:integer)
693
+ # csv.converters # => [:integer]
694
+ # csv.convert(:date)
695
+ # csv.converters # => [:integer, :date]
696
+ #
697
+ # ===== Custom Field \Converters
698
+ #
699
+ # You can define a custom field converter:
700
+ # strip_converter = proc {|field| field.strip }
701
+ # string = " foo , 0 \n bar , 1 \n baz , 2 \n"
702
+ # array = CSV.parse(string, converters: strip_converter)
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"]]
710
+ #
711
+ # ==== Header \Converters
712
+ #
713
+ # Header converters operate only on headers (and not on other rows).
714
+ #
715
+ # There are three ways to use header \converters;
716
+ # these examples use built-in header converter +:downcase+,
717
+ # which downcases each parsed header.
718
+ #
719
+ # - Option +header_converters+ with a singleton parsing method:
720
+ # string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2"
721
+ # tbl = CSV.parse(string, headers: true, header_converters: :downcase)
722
+ # tbl.class # => CSV::Table
723
+ # tbl.headers # => ["name", "count"]
724
+ #
725
+ # - Option +header_converters+ with a new \CSV instance:
726
+ # csv = CSV.new(string, header_converters: :downcase)
727
+ # # Header converters in effect:
728
+ # csv.header_converters # => [:downcase]
729
+ # tbl = CSV.parse(string, headers: true)
730
+ # tbl.headers # => ["Name", "Count"]
731
+ #
732
+ # - Method #header_convert adds a header converter to a \CSV instance:
733
+ # csv = CSV.new(string)
734
+ # # Add a header converter.
735
+ # csv.header_convert(:downcase)
736
+ # csv.header_converters # => [:downcase]
737
+ # tbl = CSV.parse(string, headers: true)
738
+ # tbl.headers # => ["Name", "Count"]
739
+ #
740
+ # ===== Built-In Header \Converters
741
+ #
742
+ # The built-in header \converters are in \Hash CSV::HeaderConverters.
743
+ # The keys there are the names of the \converters:
744
+ # CSV::HeaderConverters.keys # => [:downcase, :symbol]
745
+ #
746
+ # Converter +:downcase+ converts each header by downcasing it:
747
+ # string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2"
748
+ # tbl = CSV.parse(string, headers: true, header_converters: :downcase)
749
+ # tbl.class # => CSV::Table
750
+ # tbl.headers # => ["name", "count"]
751
+ #
752
+ # Converter +:symbol+ converts each header by making it into a \Symbol:
753
+ # string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2"
754
+ # tbl = CSV.parse(string, headers: true, header_converters: :symbol)
755
+ # tbl.headers # => [:name, :count]
756
+ # Details:
757
+ # - Strips leading and trailing whitespace.
758
+ # - Downcases the header.
759
+ # - Replaces embedded spaces with underscores.
760
+ # - Removes non-word characters.
761
+ # - Makes the string into a \Symbol.
762
+ #
763
+ # ===== Custom Header \Converters
764
+ #
765
+ # You can define a custom header converter:
766
+ # upcase_converter = proc {|header| header.upcase }
767
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
768
+ # table = CSV.parse(string, headers: true, header_converters: upcase_converter)
769
+ # table # => #<CSV::Table mode:col_or_row row_count:4>
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"]
777
+ #
778
+ # ===== Write \Converters
779
+ #
780
+ # When you specify a write converter for generating \CSV,
781
+ # each field to be written is passed to the converter;
782
+ # its return value becomes the new value for the field.
783
+ # A converter might, for example, strip whitespace from a field.
784
+ #
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"
236
802
  #
237
- # == CSV and Character Encodings (M17n or Multilingualization)
803
+ # === Character Encodings (M17n or Multilingualization)
238
804
  #
239
805
  # This new CSV parser is m17n savvy. The parser works in the Encoding of the IO
240
- # or String object being read from or written to. Your data is never transcoded
806
+ # or String object being read from or written to. Your data is never transcoded
241
807
  # (unless you ask Ruby to transcode it for you) and will literally be parsed in
242
- # the Encoding it is in. Thus CSV will return Arrays or Rows of Strings in the
243
- # Encoding of your data. This is accomplished by transcoding the parser itself
808
+ # the Encoding it is in. Thus CSV will return Arrays or Rows of Strings in the
809
+ # Encoding of your data. This is accomplished by transcoding the parser itself
244
810
  # into your Encoding.
245
811
  #
246
812
  # Some transcoding must take place, of course, to accomplish this multiencoding
247
- # support. For example, <tt>:col_sep</tt>, <tt>:row_sep</tt>, and
813
+ # support. For example, <tt>:col_sep</tt>, <tt>:row_sep</tt>, and
248
814
  # <tt>:quote_char</tt> must be transcoded to match your data. Hopefully this
249
815
  # makes the entire process feel transparent, since CSV's defaults should just
250
- # magically work for your data. However, you can set these values manually in
816
+ # magically work for your data. However, you can set these values manually in
251
817
  # the target Encoding to avoid the translation.
252
818
  #
253
819
  # It's also important to note that while all of CSV's core parser is now
254
- # Encoding agnostic, some features are not. For example, the built-in
820
+ # Encoding agnostic, some features are not. For example, the built-in
255
821
  # converters will try to transcode data to UTF-8 before making conversions.
256
822
  # Again, you can provide custom converters that are aware of your Encodings to
257
- # avoid this translation. It's just too hard for me to support native
823
+ # avoid this translation. It's just too hard for me to support native
258
824
  # conversions in all of Ruby's Encodings.
259
825
  #
260
- # Anyway, the practical side of this is simple: make sure IO and String objects
826
+ # Anyway, the practical side of this is simple: make sure IO and String objects
261
827
  # passed into CSV have the proper Encoding set and everything should just work.
262
828
  # CSV methods that allow you to open IO objects (CSV::foreach(), CSV::open(),
263
829
  # CSV::read(), and CSV::readlines()) do allow you to specify the Encoding.
264
830
  #
265
831
  # One minor exception comes when generating CSV into a String with an Encoding
266
- # that is not ASCII compatible. There's no existing data for CSV to use to
832
+ # that is not ASCII compatible. There's no existing data for CSV to use to
267
833
  # prepare itself and thus you will probably need to manually specify the desired
268
- # Encoding for most of those cases. It will try to guess using the fields in a
834
+ # Encoding for most of those cases. It will try to guess using the fields in a
269
835
  # row of output though, when using CSV::generate_line() or Array#to_csv().
270
836
  #
271
837
  # I try to point out any other Encoding issues in the documentation of methods
272
838
  # as they come up.
273
839
  #
274
840
  # This has been tested to the best of my ability with all non-"dummy" Encodings
275
- # Ruby ships with. However, it is brave new code and may have some bugs.
841
+ # Ruby ships with. However, it is brave new code and may have some bugs.
276
842
  # Please feel free to {report}[mailto:james@grayproductions.net] any issues you
277
843
  # find with it.
278
844
  #
@@ -297,8 +863,9 @@ class CSV
297
863
  # <b><tt>index</tt></b>:: The zero-based index of the field in its row.
298
864
  # <b><tt>line</tt></b>:: The line of the data source this row is from.
299
865
  # <b><tt>header</tt></b>:: The header for the column, when available.
866
+ # <b><tt>quoted?</tt></b>:: True or false, whether the original value is quoted or not.
300
867
  #
301
- FieldInfo = Struct.new(:index, :line, :header)
868
+ FieldInfo = Struct.new(:index, :line, :header, :quoted?)
302
869
 
303
870
  # A Regexp used to find and convert some common Date formats.
304
871
  DateMatcher = / \A(?: (\w+,?\s+)?\w+\s+\d{1,2},?\s+\d{2,4} |
@@ -306,39 +873,20 @@ class CSV
306
873
  # A Regexp used to find and convert some common DateTime formats.
307
874
  DateTimeMatcher =
308
875
  / \A(?: (\w+,?\s+)?\w+\s+\d{1,2}\s+\d{1,2}:\d{1,2}:\d{1,2},?\s+\d{2,4} |
309
- \d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2} |
310
- # ISO-8601
876
+ # ISO-8601 and RFC-3339 (space instead of T) recognized by DateTime.parse
311
877
  \d{4}-\d{2}-\d{2}
312
- (?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?(?:[+-]\d{2}(?::\d{2})|Z)?)?)?
878
+ (?:[T\s]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?(?:[+-]\d{2}(?::\d{2})|Z)?)?)?
313
879
  )\z /x
314
880
 
315
881
  # The encoding used by all converters.
316
882
  ConverterEncoding = Encoding.find("UTF-8")
317
883
 
884
+ # A \Hash containing the names and \Procs for the built-in field converters.
885
+ # See {Built-In Field Converters}[#class-CSV-label-Built-In+Field+Converters].
318
886
  #
319
- # This Hash holds the built-in converters of CSV that can be accessed by name.
320
- # You can select Converters with CSV.convert() or through the +options+ Hash
321
- # passed to CSV::new().
322
- #
323
- # <b><tt>:integer</tt></b>:: Converts any field Integer() accepts.
324
- # <b><tt>:float</tt></b>:: Converts any field Float() accepts.
325
- # <b><tt>:numeric</tt></b>:: A combination of <tt>:integer</tt>
326
- # and <tt>:float</tt>.
327
- # <b><tt>:date</tt></b>:: Converts any field Date::parse() accepts.
328
- # <b><tt>:date_time</tt></b>:: Converts any field DateTime::parse() accepts.
329
- # <b><tt>:all</tt></b>:: All built-in converters. A combination of
330
- # <tt>:date_time</tt> and <tt>:numeric</tt>.
331
- #
332
- # All built-in converters transcode field data to UTF-8 before attempting a
333
- # conversion. If your data cannot be transcoded to UTF-8 the conversion will
334
- # fail and the field will remain unchanged.
335
- #
336
- # This Hash is intentionally left unfrozen and users should feel free to add
337
- # values to it that can be accessed by all CSV objects.
338
- #
339
- # To add a combo field, the value should be an Array of names. Combo fields
340
- # can be nested with other combo fields.
341
- #
887
+ # This \Hash is intentionally left unfrozen, and may be extended with
888
+ # custom field converters.
889
+ # See {Custom Field Converters}[#class-CSV-label-Custom+Field+Converters].
342
890
  Converters = {
343
891
  integer: lambda { |f|
344
892
  Integer(f.encode(ConverterEncoding)) rescue f
@@ -366,992 +914,1780 @@ class CSV
366
914
  all: [:date_time, :numeric],
367
915
  }
368
916
 
917
+ # A \Hash containing the names and \Procs for the built-in header converters.
918
+ # See {Built-In Header Converters}[#class-CSV-label-Built-In+Header+Converters].
369
919
  #
370
- # This Hash holds the built-in header converters of CSV that can be accessed
371
- # by name. You can select HeaderConverters with CSV.header_convert() or
372
- # through the +options+ Hash passed to CSV::new().
373
- #
374
- # <b><tt>:downcase</tt></b>:: Calls downcase() on the header String.
375
- # <b><tt>:symbol</tt></b>:: Leading/trailing spaces are dropped, string is
376
- # downcased, remaining spaces are replaced with
377
- # underscores, non-word characters are dropped,
378
- # and finally to_sym() is called.
379
- #
380
- # All built-in header converters transcode header data to UTF-8 before
381
- # attempting a conversion. If your data cannot be transcoded to UTF-8 the
382
- # conversion will fail and the header will remain unchanged.
383
- #
384
- # This Hash is intentionally left unfrozen and users should feel free to add
385
- # values to it that can be accessed by all CSV objects.
386
- #
387
- # To add a combo field, the value should be an Array of names. Combo fields
388
- # can be nested with other combo fields.
389
- #
920
+ # This \Hash is intentionally left unfrozen, and may be extended with
921
+ # custom field converters.
922
+ # See {Custom Header Converters}[#class-CSV-label-Custom+Header+Converters].
390
923
  HeaderConverters = {
391
924
  downcase: lambda { |h| h.encode(ConverterEncoding).downcase },
392
925
  symbol: lambda { |h|
393
926
  h.encode(ConverterEncoding).downcase.gsub(/[^\s\w]+/, "").strip.
394
927
  gsub(/\s+/, "_").to_sym
395
- }
928
+ },
929
+ symbol_raw: lambda { |h| h.encode(ConverterEncoding).to_sym }
396
930
  }
397
931
 
398
- #
399
- # The options used when no overrides are given by calling code. They are:
400
- #
401
- # <b><tt>:col_sep</tt></b>:: <tt>","</tt>
402
- # <b><tt>:row_sep</tt></b>:: <tt>:auto</tt>
403
- # <b><tt>:quote_char</tt></b>:: <tt>'"'</tt>
404
- # <b><tt>:field_size_limit</tt></b>:: +nil+
405
- # <b><tt>:converters</tt></b>:: +nil+
406
- # <b><tt>:unconverted_fields</tt></b>:: +nil+
407
- # <b><tt>:headers</tt></b>:: +false+
408
- # <b><tt>:return_headers</tt></b>:: +false+
409
- # <b><tt>:header_converters</tt></b>:: +nil+
410
- # <b><tt>:skip_blanks</tt></b>:: +false+
411
- # <b><tt>:force_quotes</tt></b>:: +false+
412
- # <b><tt>:skip_lines</tt></b>:: +nil+
413
- # <b><tt>:liberal_parsing</tt></b>:: +false+
414
- #
932
+ # Default values for method options.
415
933
  DEFAULT_OPTIONS = {
934
+ # For both parsing and generating.
416
935
  col_sep: ",",
417
936
  row_sep: :auto,
418
937
  quote_char: '"',
938
+ # For parsing.
419
939
  field_size_limit: nil,
940
+ max_field_size: nil,
420
941
  converters: nil,
421
942
  unconverted_fields: nil,
422
943
  headers: false,
423
944
  return_headers: false,
424
945
  header_converters: nil,
425
946
  skip_blanks: false,
426
- force_quotes: false,
427
947
  skip_lines: nil,
428
948
  liberal_parsing: false,
949
+ nil_value: nil,
950
+ empty_value: "",
951
+ strip: false,
952
+ # For generating.
953
+ write_headers: nil,
954
+ quote_empty: true,
955
+ force_quotes: false,
956
+ write_converters: nil,
957
+ write_nil_value: nil,
958
+ write_empty_value: "",
429
959
  }.freeze
430
960
 
431
- #
432
- # This method will return a CSV instance, just like CSV::new(), but the
433
- # instance will be cached and returned for all future calls to this method for
434
- # the same +data+ object (tested by Object#object_id()) with the same
435
- # +options+.
436
- #
437
- # If a block is given, the instance is passed to the block and the return
438
- # value becomes the return value of the block.
439
- #
440
- def self.instance(data = $stdout, **options)
441
- # create a _signature_ for this method call, data object and options
442
- sig = [data.object_id] +
443
- options.values_at(*DEFAULT_OPTIONS.keys.sort_by { |sym| sym.to_s })
961
+ class << self
962
+ # :call-seq:
963
+ # instance(string, **options)
964
+ # instance(io = $stdout, **options)
965
+ # instance(string, **options) {|csv| ... }
966
+ # instance(io = $stdout, **options) {|csv| ... }
967
+ #
968
+ # Creates or retrieves cached \CSV objects.
969
+ # For arguments and options, see CSV.new.
970
+ #
971
+ # This API is not Ractor-safe.
972
+ #
973
+ # ---
974
+ #
975
+ # With no block given, returns a \CSV object.
976
+ #
977
+ # The first call to +instance+ creates and caches a \CSV object:
978
+ # s0 = 's0'
979
+ # csv0 = CSV.instance(s0)
980
+ # csv0.class # => CSV
981
+ #
982
+ # Subsequent calls to +instance+ with that _same_ +string+ or +io+
983
+ # retrieve that same cached object:
984
+ # csv1 = CSV.instance(s0)
985
+ # csv1.class # => CSV
986
+ # csv1.equal?(csv0) # => true # Same CSV object
987
+ #
988
+ # A subsequent call to +instance+ with a _different_ +string+ or +io+
989
+ # creates and caches a _different_ \CSV object.
990
+ # s1 = 's1'
991
+ # csv2 = CSV.instance(s1)
992
+ # csv2.equal?(csv0) # => false # Different CSV object
993
+ #
994
+ # All the cached objects remains available:
995
+ # csv3 = CSV.instance(s0)
996
+ # csv3.equal?(csv0) # true # Same CSV object
997
+ # csv4 = CSV.instance(s1)
998
+ # csv4.equal?(csv2) # true # Same CSV object
999
+ #
1000
+ # ---
1001
+ #
1002
+ # When a block is given, calls the block with the created or retrieved
1003
+ # \CSV object; returns the block's return value:
1004
+ # CSV.instance(s0) {|csv| :foo } # => :foo
1005
+ def instance(data = $stdout, **options)
1006
+ # create a _signature_ for this method call, data object and options
1007
+ sig = [data.object_id] +
1008
+ options.values_at(*DEFAULT_OPTIONS.keys)
1009
+
1010
+ # fetch or create the instance for this signature
1011
+ @@instances ||= Hash.new
1012
+ instance = (@@instances[sig] ||= new(data, **options))
1013
+
1014
+ if block_given?
1015
+ yield instance # run block, if given, returning result
1016
+ else
1017
+ instance # or return the instance
1018
+ end
1019
+ end
444
1020
 
445
- # fetch or create the instance for this signature
446
- @@instances ||= Hash.new
447
- instance = (@@instances[sig] ||= new(data, options))
1021
+ # :call-seq:
1022
+ # filter(in_string_or_io, **options) {|row| ... } -> array_of_arrays or csv_table
1023
+ # filter(in_string_or_io, out_string_or_io, **options) {|row| ... } -> array_of_arrays or csv_table
1024
+ # filter(**options) {|row| ... } -> array_of_arrays or csv_table
1025
+ #
1026
+ # - Parses \CSV from a source (\String, \IO stream, or ARGF).
1027
+ # - Calls the given block with each parsed row:
1028
+ # - Without headers, each row is an \Array.
1029
+ # - With headers, each row is a CSV::Row.
1030
+ # - Generates \CSV to an output (\String, \IO stream, or STDOUT).
1031
+ # - Returns the parsed source:
1032
+ # - Without headers, an \Array of \Arrays.
1033
+ # - With headers, a CSV::Table.
1034
+ #
1035
+ # When +in_string_or_io+ is given, but not +out_string_or_io+,
1036
+ # parses from the given +in_string_or_io+
1037
+ # and generates to STDOUT.
1038
+ #
1039
+ # \String input without headers:
1040
+ #
1041
+ # in_string = "foo,0\nbar,1\nbaz,2"
1042
+ # CSV.filter(in_string) do |row|
1043
+ # row[0].upcase!
1044
+ # row[1] = - row[1].to_i
1045
+ # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
1046
+ #
1047
+ # Output (to STDOUT):
1048
+ #
1049
+ # FOO,0
1050
+ # BAR,-1
1051
+ # BAZ,-2
1052
+ #
1053
+ # \String input with headers:
1054
+ #
1055
+ # in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
1056
+ # CSV.filter(in_string, headers: true) do |row|
1057
+ # row[0].upcase!
1058
+ # row[1] = - row[1].to_i
1059
+ # end # => #<CSV::Table mode:col_or_row row_count:4>
1060
+ #
1061
+ # Output (to STDOUT):
1062
+ #
1063
+ # Name,Value
1064
+ # FOO,0
1065
+ # BAR,-1
1066
+ # BAZ,-2
1067
+ #
1068
+ # \IO stream input without headers:
1069
+ #
1070
+ # File.write('t.csv', "foo,0\nbar,1\nbaz,2")
1071
+ # File.open('t.csv') do |in_io|
1072
+ # CSV.filter(in_io) do |row|
1073
+ # row[0].upcase!
1074
+ # row[1] = - row[1].to_i
1075
+ # end
1076
+ # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
1077
+ #
1078
+ # Output (to STDOUT):
1079
+ #
1080
+ # FOO,0
1081
+ # BAR,-1
1082
+ # BAZ,-2
1083
+ #
1084
+ # \IO stream input with headers:
1085
+ #
1086
+ # File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2")
1087
+ # File.open('t.csv') do |in_io|
1088
+ # CSV.filter(in_io, headers: true) do |row|
1089
+ # row[0].upcase!
1090
+ # row[1] = - row[1].to_i
1091
+ # end
1092
+ # end # => #<CSV::Table mode:col_or_row row_count:4>
1093
+ #
1094
+ # Output (to STDOUT):
1095
+ #
1096
+ # Name,Value
1097
+ # FOO,0
1098
+ # BAR,-1
1099
+ # BAZ,-2
1100
+ #
1101
+ # When both +in_string_or_io+ and +out_string_or_io+ are given,
1102
+ # parses from +in_string_or_io+ and generates to +out_string_or_io+.
1103
+ #
1104
+ # \String output without headers:
1105
+ #
1106
+ # in_string = "foo,0\nbar,1\nbaz,2"
1107
+ # out_string = ''
1108
+ # CSV.filter(in_string, out_string) do |row|
1109
+ # row[0].upcase!
1110
+ # row[1] = - row[1].to_i
1111
+ # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
1112
+ # out_string # => "FOO,0\nBAR,-1\nBAZ,-2\n"
1113
+ #
1114
+ # \String output with headers:
1115
+ #
1116
+ # in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
1117
+ # out_string = ''
1118
+ # CSV.filter(in_string, out_string, headers: true) do |row|
1119
+ # row[0].upcase!
1120
+ # row[1] = - row[1].to_i
1121
+ # end # => #<CSV::Table mode:col_or_row row_count:4>
1122
+ # out_string # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n"
1123
+ #
1124
+ # \IO stream output without headers:
1125
+ #
1126
+ # in_string = "foo,0\nbar,1\nbaz,2"
1127
+ # File.open('t.csv', 'w') do |out_io|
1128
+ # CSV.filter(in_string, out_io) do |row|
1129
+ # row[0].upcase!
1130
+ # row[1] = - row[1].to_i
1131
+ # end
1132
+ # end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
1133
+ # File.read('t.csv') # => "FOO,0\nBAR,-1\nBAZ,-2\n"
1134
+ #
1135
+ # \IO stream output with headers:
1136
+ #
1137
+ # in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
1138
+ # File.open('t.csv', 'w') do |out_io|
1139
+ # CSV.filter(in_string, out_io, headers: true) do |row|
1140
+ # row[0].upcase!
1141
+ # row[1] = - row[1].to_i
1142
+ # end
1143
+ # end # => #<CSV::Table mode:col_or_row row_count:4>
1144
+ # File.read('t.csv') # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n"
1145
+ #
1146
+ # When neither +in_string_or_io+ nor +out_string_or_io+ given,
1147
+ # parses from {ARGF}[rdoc-ref:ARGF]
1148
+ # and generates to STDOUT.
1149
+ #
1150
+ # Without headers:
1151
+ #
1152
+ # # Put Ruby code into a file.
1153
+ # ruby = <<-EOT
1154
+ # require 'csv'
1155
+ # CSV.filter do |row|
1156
+ # row[0].upcase!
1157
+ # row[1] = - row[1].to_i
1158
+ # end
1159
+ # EOT
1160
+ # File.write('t.rb', ruby)
1161
+ # # Put some CSV into a file.
1162
+ # File.write('t.csv', "foo,0\nbar,1\nbaz,2")
1163
+ # # Run the Ruby code with CSV filename as argument.
1164
+ # system(Gem.ruby, "t.rb", "t.csv")
1165
+ #
1166
+ # Output (to STDOUT):
1167
+ #
1168
+ # FOO,0
1169
+ # BAR,-1
1170
+ # BAZ,-2
1171
+ #
1172
+ # With headers:
1173
+ #
1174
+ # # Put Ruby code into a file.
1175
+ # ruby = <<-EOT
1176
+ # require 'csv'
1177
+ # CSV.filter(headers: true) do |row|
1178
+ # row[0].upcase!
1179
+ # row[1] = - row[1].to_i
1180
+ # end
1181
+ # EOT
1182
+ # File.write('t.rb', ruby)
1183
+ # # Put some CSV into a file.
1184
+ # File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2")
1185
+ # # Run the Ruby code with CSV filename as argument.
1186
+ # system(Gem.ruby, "t.rb", "t.csv")
1187
+ #
1188
+ # Output (to STDOUT):
1189
+ #
1190
+ # Name,Value
1191
+ # FOO,0
1192
+ # BAR,-1
1193
+ # BAZ,-2
1194
+ #
1195
+ # Arguments:
1196
+ #
1197
+ # * Argument +in_string_or_io+ must be a \String or an \IO stream.
1198
+ # * Argument +out_string_or_io+ must be a \String or an \IO stream.
1199
+ # * Arguments <tt>**options</tt> must be keyword options.
1200
+ # See {Options for Parsing}[#class-CSV-label-Options+for+Parsing].
1201
+ def filter(input=nil, output=nil, **options)
1202
+ # parse options for input, output, or both
1203
+ in_options, out_options = Hash.new, {row_sep: InputRecordSeparator.value}
1204
+ options.each do |key, value|
1205
+ case key
1206
+ when /\Ain(?:put)?_(.+)\Z/
1207
+ in_options[$1.to_sym] = value
1208
+ when /\Aout(?:put)?_(.+)\Z/
1209
+ out_options[$1.to_sym] = value
1210
+ else
1211
+ in_options[key] = value
1212
+ out_options[key] = value
1213
+ end
1214
+ end
448
1215
 
449
- if block_given?
450
- yield instance # run block, if given, returning result
451
- else
452
- instance # or return the instance
453
- end
454
- end
1216
+ # build input and output wrappers
1217
+ input = new(input || ARGF, **in_options)
1218
+ output = new(output || $stdout, **out_options)
1219
+
1220
+ # process headers
1221
+ need_manual_header_output =
1222
+ (in_options[:headers] and
1223
+ out_options[:headers] == true and
1224
+ out_options[:write_headers])
1225
+ if need_manual_header_output
1226
+ first_row = input.shift
1227
+ if first_row
1228
+ if first_row.is_a?(Row)
1229
+ headers = first_row.headers
1230
+ yield headers
1231
+ output << headers
1232
+ end
1233
+ yield first_row
1234
+ output << first_row
1235
+ end
1236
+ end
455
1237
 
456
- #
457
- # :call-seq:
458
- # filter( **options ) { |row| ... }
459
- # filter( input, **options ) { |row| ... }
460
- # filter( input, output, **options ) { |row| ... }
461
- #
462
- # This method is a convenience for building Unix-like filters for CSV data.
463
- # Each row is yielded to the provided block which can alter it as needed.
464
- # After the block returns, the row is appended to +output+ altered or not.
465
- #
466
- # The +input+ and +output+ arguments can be anything CSV::new() accepts
467
- # (generally String or IO objects). If not given, they default to
468
- # <tt>ARGF</tt> and <tt>$stdout</tt>.
469
- #
470
- # The +options+ parameter is also filtered down to CSV::new() after some
471
- # clever key parsing. Any key beginning with <tt>:in_</tt> or
472
- # <tt>:input_</tt> will have that leading identifier stripped and will only
473
- # be used in the +options+ Hash for the +input+ object. Keys starting with
474
- # <tt>:out_</tt> or <tt>:output_</tt> affect only +output+. All other keys
475
- # are assigned to both objects.
476
- #
477
- # The <tt>:output_row_sep</tt> +option+ defaults to
478
- # <tt>$INPUT_RECORD_SEPARATOR</tt> (<tt>$/</tt>).
479
- #
480
- def self.filter(input=nil, output=nil, **options)
481
- # parse options for input, output, or both
482
- in_options, out_options = Hash.new, {row_sep: $INPUT_RECORD_SEPARATOR}
483
- options.each do |key, value|
484
- case key.to_s
485
- when /\Ain(?:put)?_(.+)\Z/
486
- in_options[$1.to_sym] = value
487
- when /\Aout(?:put)?_(.+)\Z/
488
- out_options[$1.to_sym] = value
489
- else
490
- in_options[key] = value
491
- out_options[key] = value
1238
+ # read, yield, write
1239
+ input.each do |row|
1240
+ yield row
1241
+ output << row
492
1242
  end
493
1243
  end
494
- # build input and output wrappers
495
- input = new(input || ARGF, in_options)
496
- output = new(output || $stdout, out_options)
497
-
498
- # read, yield, write
499
- input.each do |row|
500
- yield row
501
- output << row
502
- end
503
- end
504
1244
 
505
- #
506
- # This method is intended as the primary interface for reading CSV files. You
507
- # pass a +path+ and any +options+ you wish to set for the read. Each row of
508
- # file will be passed to the provided +block+ in turn.
509
- #
510
- # The +options+ parameter can be anything CSV::new() understands. This method
511
- # also understands an additional <tt>:encoding</tt> parameter that you can use
512
- # to specify the Encoding of the data in the file to be read. You must provide
513
- # this unless your data is in Encoding::default_external(). CSV will use this
514
- # to determine how to parse the data. You may provide a second Encoding to
515
- # have the data transcoded as it is read. For example,
516
- # <tt>encoding: "UTF-32BE:UTF-8"</tt> would read UTF-32BE data from the file
517
- # but transcode it to UTF-8 before CSV parses it.
518
- #
519
- def self.foreach(path, **options, &block)
520
- return to_enum(__method__, path, options) unless block_given?
521
- open(path, options) do |csv|
522
- csv.each(&block)
1245
+ #
1246
+ # :call-seq:
1247
+ # foreach(path_or_io, mode='r', **options) {|row| ... )
1248
+ # foreach(path_or_io, mode='r', **options) -> new_enumerator
1249
+ #
1250
+ # Calls the block with each row read from source +path_or_io+.
1251
+ #
1252
+ # \Path input without headers:
1253
+ #
1254
+ # string = "foo,0\nbar,1\nbaz,2\n"
1255
+ # in_path = 't.csv'
1256
+ # File.write(in_path, string)
1257
+ # CSV.foreach(in_path) {|row| p row }
1258
+ #
1259
+ # Output:
1260
+ #
1261
+ # ["foo", "0"]
1262
+ # ["bar", "1"]
1263
+ # ["baz", "2"]
1264
+ #
1265
+ # \Path input with headers:
1266
+ #
1267
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
1268
+ # in_path = 't.csv'
1269
+ # File.write(in_path, string)
1270
+ # CSV.foreach(in_path, headers: true) {|row| p row }
1271
+ #
1272
+ # Output:
1273
+ #
1274
+ # <CSV::Row "Name":"foo" "Value":"0">
1275
+ # <CSV::Row "Name":"bar" "Value":"1">
1276
+ # <CSV::Row "Name":"baz" "Value":"2">
1277
+ #
1278
+ # \IO stream input without headers:
1279
+ #
1280
+ # string = "foo,0\nbar,1\nbaz,2\n"
1281
+ # path = 't.csv'
1282
+ # File.write(path, string)
1283
+ # File.open('t.csv') do |in_io|
1284
+ # CSV.foreach(in_io) {|row| p row }
1285
+ # end
1286
+ #
1287
+ # Output:
1288
+ #
1289
+ # ["foo", "0"]
1290
+ # ["bar", "1"]
1291
+ # ["baz", "2"]
1292
+ #
1293
+ # \IO stream input with headers:
1294
+ #
1295
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
1296
+ # path = 't.csv'
1297
+ # File.write(path, string)
1298
+ # File.open('t.csv') do |in_io|
1299
+ # CSV.foreach(in_io, headers: true) {|row| p row }
1300
+ # end
1301
+ #
1302
+ # Output:
1303
+ #
1304
+ # <CSV::Row "Name":"foo" "Value":"0">
1305
+ # <CSV::Row "Name":"bar" "Value":"1">
1306
+ # <CSV::Row "Name":"baz" "Value":"2">
1307
+ #
1308
+ # With no block given, returns an \Enumerator:
1309
+ #
1310
+ # string = "foo,0\nbar,1\nbaz,2\n"
1311
+ # path = 't.csv'
1312
+ # File.write(path, string)
1313
+ # CSV.foreach(path) # => #<Enumerator: CSV:foreach("t.csv", "r")>
1314
+ #
1315
+ # Arguments:
1316
+ # * Argument +path_or_io+ must be a file path or an \IO stream.
1317
+ # * Argument +mode+, if given, must be a \File mode
1318
+ # See {Open Mode}[https://ruby-doc.org/core/IO.html#method-c-new-label-Open+Mode].
1319
+ # * Arguments <tt>**options</tt> must be keyword options.
1320
+ # See {Options for Parsing}[#class-CSV-label-Options+for+Parsing].
1321
+ # * This method optionally accepts an additional <tt>:encoding</tt> option
1322
+ # that you can use to specify the Encoding of the data read from +path+ or +io+.
1323
+ # You must provide this unless your data is in the encoding
1324
+ # given by <tt>Encoding::default_external</tt>.
1325
+ # Parsing will use this to determine how to parse the data.
1326
+ # You may provide a second Encoding to
1327
+ # have the data transcoded as it is read. For example,
1328
+ # encoding: 'UTF-32BE:UTF-8'
1329
+ # would read +UTF-32BE+ data from the file
1330
+ # but transcode it to +UTF-8+ before parsing.
1331
+ def foreach(path, mode="r", **options, &block)
1332
+ return to_enum(__method__, path, mode, **options) unless block_given?
1333
+ open(path, mode, **options) do |csv|
1334
+ csv.each(&block)
1335
+ end
523
1336
  end
524
- end
525
1337
 
526
- #
527
- # :call-seq:
528
- # generate( str, **options ) { |csv| ... }
529
- # generate( **options ) { |csv| ... }
530
- #
531
- # This method wraps a String you provide, or an empty default String, in a
532
- # CSV object which is passed to the provided block. You can use the block to
533
- # append CSV rows to the String and when the block exits, the final String
534
- # will be returned.
535
- #
536
- # Note that a passed String *is* modified by this method. Call dup() before
537
- # passing if you need a new String.
538
- #
539
- # The +options+ parameter can be anything CSV::new() understands. This method
540
- # understands an additional <tt>:encoding</tt> parameter when not passed a
541
- # String to set the base Encoding for the output. CSV needs this hint if you
542
- # plan to output non-ASCII compatible data.
543
- #
544
- def self.generate(str=nil, **options)
545
- # add a default empty String, if none was given
546
- if str
547
- str = StringIO.new(str)
548
- str.seek(0, IO::SEEK_END)
549
- else
1338
+ #
1339
+ # :call-seq:
1340
+ # generate(csv_string, **options) {|csv| ... }
1341
+ # generate(**options) {|csv| ... }
1342
+ #
1343
+ # * Argument +csv_string+, if given, must be a \String object;
1344
+ # defaults to a new empty \String.
1345
+ # * Arguments +options+, if given, should be generating options.
1346
+ # See {Options for Generating}[#class-CSV-label-Options+for+Generating].
1347
+ #
1348
+ # ---
1349
+ #
1350
+ # Creates a new \CSV object via <tt>CSV.new(csv_string, **options)</tt>;
1351
+ # calls the block with the \CSV object, which the block may modify;
1352
+ # returns the \String generated from the \CSV object.
1353
+ #
1354
+ # Note that a passed \String *is* modified by this method.
1355
+ # Pass <tt>csv_string</tt>.dup if the \String must be preserved.
1356
+ #
1357
+ # This method has one additional option: <tt>:encoding</tt>,
1358
+ # which sets the base Encoding for the output if no no +str+ is specified.
1359
+ # CSV needs this hint if you plan to output non-ASCII compatible data.
1360
+ #
1361
+ # ---
1362
+ #
1363
+ # Add lines:
1364
+ # input_string = "foo,0\nbar,1\nbaz,2\n"
1365
+ # output_string = CSV.generate(input_string) do |csv|
1366
+ # csv << ['bat', 3]
1367
+ # csv << ['bam', 4]
1368
+ # end
1369
+ # output_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n"
1370
+ # input_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n"
1371
+ # output_string.equal?(input_string) # => true # Same string, modified
1372
+ #
1373
+ # Add lines into new string, preserving old string:
1374
+ # input_string = "foo,0\nbar,1\nbaz,2\n"
1375
+ # output_string = CSV.generate(input_string.dup) do |csv|
1376
+ # csv << ['bat', 3]
1377
+ # csv << ['bam', 4]
1378
+ # end
1379
+ # output_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n"
1380
+ # input_string # => "foo,0\nbar,1\nbaz,2\n"
1381
+ # output_string.equal?(input_string) # => false # Different strings
1382
+ #
1383
+ # Create lines from nothing:
1384
+ # output_string = CSV.generate do |csv|
1385
+ # csv << ['foo', 0]
1386
+ # csv << ['bar', 1]
1387
+ # csv << ['baz', 2]
1388
+ # end
1389
+ # output_string # => "foo,0\nbar,1\nbaz,2\n"
1390
+ #
1391
+ # ---
1392
+ #
1393
+ # Raises an exception if +csv_string+ is not a \String object:
1394
+ # # Raises TypeError (no implicit conversion of Integer into String)
1395
+ # CSV.generate(0)
1396
+ #
1397
+ def generate(str=nil, **options)
550
1398
  encoding = options[:encoding]
551
- str = String.new
552
- str.force_encoding(encoding) if encoding
1399
+ # add a default empty String, if none was given
1400
+ if str
1401
+ str = StringIO.new(str)
1402
+ str.seek(0, IO::SEEK_END)
1403
+ str.set_encoding(encoding) if encoding
1404
+ else
1405
+ str = +""
1406
+ str.force_encoding(encoding) if encoding
1407
+ end
1408
+ csv = new(str, **options) # wrap
1409
+ yield csv # yield for appending
1410
+ csv.string # return final String
553
1411
  end
554
- csv = new(str, options) # wrap
555
- yield csv # yield for appending
556
- csv.string # return final String
557
- end
558
1412
 
559
- #
560
- # This method is a shortcut for converting a single row (Array) into a CSV
561
- # String.
562
- #
563
- # The +options+ parameter can be anything CSV::new() understands. This method
564
- # understands an additional <tt>:encoding</tt> parameter to set the base
565
- # Encoding for the output. This method will try to guess your Encoding from
566
- # the first non-+nil+ field in +row+, if possible, but you may need to use
567
- # this parameter as a backup plan.
568
- #
569
- # The <tt>:row_sep</tt> +option+ defaults to <tt>$INPUT_RECORD_SEPARATOR</tt>
570
- # (<tt>$/</tt>) when calling this method.
571
- #
572
- def self.generate_line(row, **options)
573
- options = {row_sep: $INPUT_RECORD_SEPARATOR}.merge(options)
574
- str = String.new
575
- if options[:encoding]
576
- str.force_encoding(options[:encoding])
577
- elsif field = row.find { |f| not f.nil? }
578
- str.force_encoding(String(field).encoding)
1413
+ # :call-seq:
1414
+ # CSV.generate_line(ary)
1415
+ # CSV.generate_line(ary, **options)
1416
+ #
1417
+ # Returns the \String created by generating \CSV from +ary+
1418
+ # using the specified +options+.
1419
+ #
1420
+ # Argument +ary+ must be an \Array.
1421
+ #
1422
+ # Special options:
1423
+ # * Option <tt>:row_sep</tt> defaults to <tt>"\n"> on Ruby 3.0 or later
1424
+ # and <tt>$INPUT_RECORD_SEPARATOR</tt> (<tt>$/</tt>) otherwise.:
1425
+ # $INPUT_RECORD_SEPARATOR # => "\n"
1426
+ # * This method accepts an additional option, <tt>:encoding</tt>, which sets the base
1427
+ # Encoding for the output. This method will try to guess your Encoding from
1428
+ # the first non-+nil+ field in +row+, if possible, but you may need to use
1429
+ # this parameter as a backup plan.
1430
+ #
1431
+ # For other +options+,
1432
+ # see {Options for Generating}[#class-CSV-label-Options+for+Generating].
1433
+ #
1434
+ # ---
1435
+ #
1436
+ # Returns the \String generated from an \Array:
1437
+ # CSV.generate_line(['foo', '0']) # => "foo,0\n"
1438
+ #
1439
+ # ---
1440
+ #
1441
+ # Raises an exception if +ary+ is not an \Array:
1442
+ # # Raises NoMethodError (undefined method `find' for :foo:Symbol)
1443
+ # CSV.generate_line(:foo)
1444
+ #
1445
+ def generate_line(row, **options)
1446
+ options = {row_sep: InputRecordSeparator.value}.merge(options)
1447
+ str = +""
1448
+ if options[:encoding]
1449
+ str.force_encoding(options[:encoding])
1450
+ else
1451
+ fallback_encoding = nil
1452
+ output_encoding = nil
1453
+ row.each do |field|
1454
+ next unless field.is_a?(String)
1455
+ fallback_encoding ||= field.encoding
1456
+ next if field.ascii_only?
1457
+ output_encoding = field.encoding
1458
+ break
1459
+ end
1460
+ output_encoding ||= fallback_encoding
1461
+ if output_encoding
1462
+ str.force_encoding(output_encoding)
1463
+ end
1464
+ end
1465
+ (new(str, **options) << row).string
579
1466
  end
580
- (new(str, options) << row).string
581
- end
582
1467
 
583
- #
584
- # :call-seq:
585
- # open( filename, mode = "rb", **options ) { |faster_csv| ... }
586
- # open( filename, **options ) { |faster_csv| ... }
587
- # open( filename, mode = "rb", **options )
588
- # open( filename, **options )
589
- #
590
- # This method opens an IO object, and wraps that with CSV. This is intended
591
- # as the primary interface for writing a CSV file.
592
- #
593
- # You must pass a +filename+ and may optionally add a +mode+ for Ruby's
594
- # open(). You may also pass an optional Hash containing any +options+
595
- # CSV::new() understands as the final argument.
596
- #
597
- # This method works like Ruby's open() call, in that it will pass a CSV object
598
- # to a provided block and close it when the block terminates, or it will
599
- # return the CSV object when no block is provided. (*Note*: This is different
600
- # from the Ruby 1.8 CSV library which passed rows to the block. Use
601
- # CSV::foreach() for that behavior.)
602
- #
603
- # You must provide a +mode+ with an embedded Encoding designator unless your
604
- # data is in Encoding::default_external(). CSV will check the Encoding of the
605
- # underlying IO object (set by the +mode+ you pass) to determine how to parse
606
- # the data. You may provide a second Encoding to have the data transcoded as
607
- # it is read just as you can with a normal call to IO::open(). For example,
608
- # <tt>"rb:UTF-32BE:UTF-8"</tt> would read UTF-32BE data from the file but
609
- # transcode it to UTF-8 before CSV parses it.
610
- #
611
- # An opened CSV object will delegate to many IO methods for convenience. You
612
- # may call:
613
- #
614
- # * binmode()
615
- # * binmode?()
616
- # * close()
617
- # * close_read()
618
- # * close_write()
619
- # * closed?()
620
- # * eof()
621
- # * eof?()
622
- # * external_encoding()
623
- # * fcntl()
624
- # * fileno()
625
- # * flock()
626
- # * flush()
627
- # * fsync()
628
- # * internal_encoding()
629
- # * ioctl()
630
- # * isatty()
631
- # * path()
632
- # * pid()
633
- # * pos()
634
- # * pos=()
635
- # * reopen()
636
- # * seek()
637
- # * stat()
638
- # * sync()
639
- # * sync=()
640
- # * tell()
641
- # * to_i()
642
- # * to_io()
643
- # * truncate()
644
- # * tty?()
645
- #
646
- def self.open(filename, mode="r", **options)
647
- # wrap a File opened with the remaining +args+ with no newline
648
- # decorator
649
- file_opts = {universal_newline: false}.merge(options)
650
-
651
- begin
652
- f = File.open(filename, mode, file_opts)
653
- rescue ArgumentError => e
654
- raise unless /needs binmode/.match?(e.message) and mode == "r"
655
- mode = "rb"
656
- file_opts = {encoding: Encoding.default_external}.merge(file_opts)
657
- retry
658
- end
659
- begin
660
- csv = new(f, options)
661
- rescue Exception
662
- f.close
663
- raise
1468
+ # :call-seq:
1469
+ # CSV.generate_lines(rows)
1470
+ # CSV.generate_lines(rows, **options)
1471
+ #
1472
+ # Returns the \String created by generating \CSV from
1473
+ # using the specified +options+.
1474
+ #
1475
+ # Argument +rows+ must be an \Array of row. Row is \Array of \String or \CSV::Row.
1476
+ #
1477
+ # Special options:
1478
+ # * Option <tt>:row_sep</tt> defaults to <tt>"\n"</tt> on Ruby 3.0 or later
1479
+ # and <tt>$INPUT_RECORD_SEPARATOR</tt> (<tt>$/</tt>) otherwise.:
1480
+ # $INPUT_RECORD_SEPARATOR # => "\n"
1481
+ # * This method accepts an additional option, <tt>:encoding</tt>, which sets the base
1482
+ # Encoding for the output. This method will try to guess your Encoding from
1483
+ # the first non-+nil+ field in +row+, if possible, but you may need to use
1484
+ # this parameter as a backup plan.
1485
+ #
1486
+ # For other +options+,
1487
+ # see {Options for Generating}[#class-CSV-label-Options+for+Generating].
1488
+ #
1489
+ # ---
1490
+ #
1491
+ # Returns the \String generated from an
1492
+ # CSV.generate_lines([['foo', '0'], ['bar', '1'], ['baz', '2']]) # => "foo,0\nbar,1\nbaz,2\n"
1493
+ #
1494
+ # ---
1495
+ #
1496
+ # Raises an exception
1497
+ # # Raises NoMethodError (undefined method `each' for :foo:Symbol)
1498
+ # CSV.generate_lines(:foo)
1499
+ #
1500
+ def generate_lines(rows, **options)
1501
+ self.generate(**options) do |csv|
1502
+ rows.each do |row|
1503
+ csv << row
1504
+ end
1505
+ end
664
1506
  end
665
1507
 
666
- # handle blocks like Ruby's open(), not like the CSV library
667
- if block_given?
1508
+ #
1509
+ # :call-seq:
1510
+ # open(file_path, mode = "rb", **options ) -> new_csv
1511
+ # open(io, mode = "rb", **options ) -> new_csv
1512
+ # open(file_path, mode = "rb", **options ) { |csv| ... } -> object
1513
+ # open(io, mode = "rb", **options ) { |csv| ... } -> object
1514
+ #
1515
+ # possible options elements:
1516
+ # keyword form:
1517
+ # :invalid => nil # raise error on invalid byte sequence (default)
1518
+ # :invalid => :replace # replace invalid byte sequence
1519
+ # :undef => :replace # replace undefined conversion
1520
+ # :replace => string # replacement string ("?" or "\uFFFD" if not specified)
1521
+ #
1522
+ # * Argument +path+, if given, must be the path to a file.
1523
+ # :include: ../doc/csv/arguments/io.rdoc
1524
+ # * Argument +mode+, if given, must be a \File mode
1525
+ # See {Open Mode}[IO.html#method-c-new-label-Open+Mode].
1526
+ # * Arguments <tt>**options</tt> must be keyword options.
1527
+ # See {Options for Generating}[#class-CSV-label-Options+for+Generating].
1528
+ # * This method optionally accepts an additional <tt>:encoding</tt> option
1529
+ # that you can use to specify the Encoding of the data read from +path+ or +io+.
1530
+ # You must provide this unless your data is in the encoding
1531
+ # given by <tt>Encoding::default_external</tt>.
1532
+ # Parsing will use this to determine how to parse the data.
1533
+ # You may provide a second Encoding to
1534
+ # have the data transcoded as it is read. For example,
1535
+ # encoding: 'UTF-32BE:UTF-8'
1536
+ # would read +UTF-32BE+ data from the file
1537
+ # but transcode it to +UTF-8+ before parsing.
1538
+ #
1539
+ # ---
1540
+ #
1541
+ # These examples assume prior execution of:
1542
+ # string = "foo,0\nbar,1\nbaz,2\n"
1543
+ # path = 't.csv'
1544
+ # File.write(path, string)
1545
+ #
1546
+ # ---
1547
+ #
1548
+ # With no block given, returns a new \CSV object.
1549
+ #
1550
+ # Create a \CSV object using a file path:
1551
+ # csv = CSV.open(path)
1552
+ # csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1553
+ #
1554
+ # Create a \CSV object using an open \File:
1555
+ # csv = CSV.open(File.open(path))
1556
+ # csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1557
+ #
1558
+ # ---
1559
+ #
1560
+ # With a block given, calls the block with the created \CSV object;
1561
+ # returns the block's return value:
1562
+ #
1563
+ # Using a file path:
1564
+ # csv = CSV.open(path) {|csv| p csv}
1565
+ # csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1566
+ # Output:
1567
+ # #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1568
+ #
1569
+ # Using an open \File:
1570
+ # csv = CSV.open(File.open(path)) {|csv| p csv}
1571
+ # csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1572
+ # Output:
1573
+ # #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1574
+ #
1575
+ # ---
1576
+ #
1577
+ # Raises an exception if the argument is not a \String object or \IO object:
1578
+ # # Raises TypeError (no implicit conversion of Symbol into String)
1579
+ # CSV.open(:foo)
1580
+ def open(filename, mode="r", **options)
1581
+ # wrap a File opened with the remaining +args+ with no newline
1582
+ # decorator
1583
+ file_opts = options.dup
1584
+ unless file_opts.key?(:newline)
1585
+ file_opts[:universal_newline] ||= false
1586
+ end
1587
+ options.delete(:invalid)
1588
+ options.delete(:undef)
1589
+ options.delete(:replace)
1590
+ options.delete_if {|k, _| /newline\z/.match?(k)}
1591
+
668
1592
  begin
669
- yield csv
670
- ensure
671
- csv.close
1593
+ f = File.open(filename, mode, **file_opts)
1594
+ rescue ArgumentError => e
1595
+ raise unless /needs binmode/.match?(e.message) and mode == "r"
1596
+ mode = "rb"
1597
+ file_opts = {encoding: Encoding.default_external}.merge(file_opts)
1598
+ retry
1599
+ end
1600
+ begin
1601
+ csv = new(f, **options)
1602
+ rescue Exception
1603
+ f.close
1604
+ raise
1605
+ end
1606
+
1607
+ # handle blocks like Ruby's open(), not like the CSV library
1608
+ if block_given?
1609
+ begin
1610
+ yield csv
1611
+ ensure
1612
+ csv.close
1613
+ end
1614
+ else
1615
+ csv
672
1616
  end
673
- else
674
- csv
675
1617
  end
676
- end
677
1618
 
678
- #
679
- # :call-seq:
680
- # parse( str, **options ) { |row| ... }
681
- # parse( str, **options )
682
- #
683
- # This method can be used to easily parse CSV out of a String. You may either
684
- # provide a +block+ which will be called with each row of the String in turn,
685
- # or just use the returned Array of Arrays (when no +block+ is given).
686
- #
687
- # You pass your +str+ to read from, and an optional +options+ containing
688
- # anything CSV::new() understands.
689
- #
690
- def self.parse(*args, &block)
691
- csv = new(*args)
1619
+ #
1620
+ # :call-seq:
1621
+ # parse(string) -> array_of_arrays
1622
+ # parse(io) -> array_of_arrays
1623
+ # parse(string, headers: ..., **options) -> csv_table
1624
+ # parse(io, headers: ..., **options) -> csv_table
1625
+ # parse(string, **options) {|row| ... }
1626
+ # parse(io, **options) {|row| ... }
1627
+ #
1628
+ # Parses +string+ or +io+ using the specified +options+.
1629
+ #
1630
+ # - Argument +string+ should be a \String object;
1631
+ # it will be put into a new StringIO object positioned at the beginning.
1632
+ # :include: ../doc/csv/arguments/io.rdoc
1633
+ # - Argument +options+: see {Options for Parsing}[#class-CSV-label-Options+for+Parsing]
1634
+ #
1635
+ # ====== Without Option +headers+
1636
+ #
1637
+ # Without {option +headers+}[#class-CSV-label-Option+headers] case.
1638
+ #
1639
+ # These examples assume prior execution of:
1640
+ # string = "foo,0\nbar,1\nbaz,2\n"
1641
+ # path = 't.csv'
1642
+ # File.write(path, string)
1643
+ #
1644
+ # ---
1645
+ #
1646
+ # With no block given, returns an \Array of Arrays formed from the source.
1647
+ #
1648
+ # Parse a \String:
1649
+ # a_of_a = CSV.parse(string)
1650
+ # a_of_a # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
1651
+ #
1652
+ # Parse an open \File:
1653
+ # a_of_a = File.open(path) do |file|
1654
+ # CSV.parse(file)
1655
+ # end
1656
+ # a_of_a # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
1657
+ #
1658
+ # ---
1659
+ #
1660
+ # With a block given, calls the block with each parsed row:
1661
+ #
1662
+ # Parse a \String:
1663
+ # CSV.parse(string) {|row| p row }
1664
+ #
1665
+ # Output:
1666
+ # ["foo", "0"]
1667
+ # ["bar", "1"]
1668
+ # ["baz", "2"]
1669
+ #
1670
+ # Parse an open \File:
1671
+ # File.open(path) do |file|
1672
+ # CSV.parse(file) {|row| p row }
1673
+ # end
1674
+ #
1675
+ # Output:
1676
+ # ["foo", "0"]
1677
+ # ["bar", "1"]
1678
+ # ["baz", "2"]
1679
+ #
1680
+ # ====== With Option +headers+
1681
+ #
1682
+ # With {option +headers+}[#class-CSV-label-Option+headers] case.
1683
+ #
1684
+ # These examples assume prior execution of:
1685
+ # string = "Name,Count\nfoo,0\nbar,1\nbaz,2\n"
1686
+ # path = 't.csv'
1687
+ # File.write(path, string)
1688
+ #
1689
+ # ---
1690
+ #
1691
+ # With no block given, returns a CSV::Table object formed from the source.
1692
+ #
1693
+ # Parse a \String:
1694
+ # csv_table = CSV.parse(string, headers: ['Name', 'Count'])
1695
+ # csv_table # => #<CSV::Table mode:col_or_row row_count:5>
1696
+ #
1697
+ # Parse an open \File:
1698
+ # csv_table = File.open(path) do |file|
1699
+ # CSV.parse(file, headers: ['Name', 'Count'])
1700
+ # end
1701
+ # csv_table # => #<CSV::Table mode:col_or_row row_count:4>
1702
+ #
1703
+ # ---
1704
+ #
1705
+ # With a block given, calls the block with each parsed row,
1706
+ # which has been formed into a CSV::Row object:
1707
+ #
1708
+ # Parse a \String:
1709
+ # CSV.parse(string, headers: ['Name', 'Count']) {|row| p row }
1710
+ #
1711
+ # Output:
1712
+ # # <CSV::Row "Name":"foo" "Count":"0">
1713
+ # # <CSV::Row "Name":"bar" "Count":"1">
1714
+ # # <CSV::Row "Name":"baz" "Count":"2">
1715
+ #
1716
+ # Parse an open \File:
1717
+ # File.open(path) do |file|
1718
+ # CSV.parse(file, headers: ['Name', 'Count']) {|row| p row }
1719
+ # end
1720
+ #
1721
+ # Output:
1722
+ # # <CSV::Row "Name":"foo" "Count":"0">
1723
+ # # <CSV::Row "Name":"bar" "Count":"1">
1724
+ # # <CSV::Row "Name":"baz" "Count":"2">
1725
+ #
1726
+ # ---
1727
+ #
1728
+ # Raises an exception if the argument is not a \String object or \IO object:
1729
+ # # Raises NoMethodError (undefined method `close' for :foo:Symbol)
1730
+ # CSV.parse(:foo)
1731
+ def parse(str, **options, &block)
1732
+ csv = new(str, **options)
692
1733
 
693
- return csv.each(&block) if block_given?
1734
+ return csv.each(&block) if block_given?
694
1735
 
695
- # slurp contents, if no block is given
696
- begin
697
- csv.read
698
- ensure
699
- csv.close
1736
+ # slurp contents, if no block is given
1737
+ begin
1738
+ csv.read
1739
+ ensure
1740
+ csv.close
1741
+ end
700
1742
  end
701
- end
702
1743
 
703
- #
704
- # This method is a shortcut for converting a single line of a CSV String into
705
- # an Array. Note that if +line+ contains multiple rows, anything beyond the
706
- # first row is ignored.
707
- #
708
- # The +options+ parameter can be anything CSV::new() understands.
709
- #
710
- def self.parse_line(line, **options)
711
- new(line, options).shift
712
- end
1744
+ # :call-seq:
1745
+ # CSV.parse_line(string) -> new_array or nil
1746
+ # CSV.parse_line(io) -> new_array or nil
1747
+ # CSV.parse_line(string, **options) -> new_array or nil
1748
+ # CSV.parse_line(io, **options) -> new_array or nil
1749
+ # CSV.parse_line(string, headers: true, **options) -> csv_row or nil
1750
+ # CSV.parse_line(io, headers: true, **options) -> csv_row or nil
1751
+ #
1752
+ # Returns the data created by parsing the first line of +string+ or +io+
1753
+ # using the specified +options+.
1754
+ #
1755
+ # - Argument +string+ should be a \String object;
1756
+ # it will be put into a new StringIO object positioned at the beginning.
1757
+ # :include: ../doc/csv/arguments/io.rdoc
1758
+ # - Argument +options+: see {Options for Parsing}[#class-CSV-label-Options+for+Parsing]
1759
+ #
1760
+ # ====== Without Option +headers+
1761
+ #
1762
+ # Without option +headers+, returns the first row as a new \Array.
1763
+ #
1764
+ # These examples assume prior execution of:
1765
+ # string = "foo,0\nbar,1\nbaz,2\n"
1766
+ # path = 't.csv'
1767
+ # File.write(path, string)
1768
+ #
1769
+ # Parse the first line from a \String object:
1770
+ # CSV.parse_line(string) # => ["foo", "0"]
1771
+ #
1772
+ # Parse the first line from a File object:
1773
+ # File.open(path) do |file|
1774
+ # CSV.parse_line(file) # => ["foo", "0"]
1775
+ # end # => ["foo", "0"]
1776
+ #
1777
+ # Returns +nil+ if the argument is an empty \String:
1778
+ # CSV.parse_line('') # => nil
1779
+ #
1780
+ # ====== With Option +headers+
1781
+ #
1782
+ # With {option +headers+}[#class-CSV-label-Option+headers],
1783
+ # returns the first row as a CSV::Row object.
1784
+ #
1785
+ # These examples assume prior execution of:
1786
+ # string = "Name,Count\nfoo,0\nbar,1\nbaz,2\n"
1787
+ # path = 't.csv'
1788
+ # File.write(path, string)
1789
+ #
1790
+ # Parse the first line from a \String object:
1791
+ # CSV.parse_line(string, headers: true) # => #<CSV::Row "Name":"foo" "Count":"0">
1792
+ #
1793
+ # Parse the first line from a File object:
1794
+ # File.open(path) do |file|
1795
+ # CSV.parse_line(file, headers: true)
1796
+ # end # => #<CSV::Row "Name":"foo" "Count":"0">
1797
+ #
1798
+ # ---
1799
+ #
1800
+ # Raises an exception if the argument is +nil+:
1801
+ # # Raises ArgumentError (Cannot parse nil as CSV):
1802
+ # CSV.parse_line(nil)
1803
+ #
1804
+ def parse_line(line, **options)
1805
+ new(line, **options).each.first
1806
+ end
713
1807
 
714
- #
715
- # Use to slurp a CSV file into an Array of Arrays. Pass the +path+ to the
716
- # file and any +options+ CSV::new() understands. This method also understands
717
- # an additional <tt>:encoding</tt> parameter that you can use to specify the
718
- # Encoding of the data in the file to be read. You must provide this unless
719
- # your data is in Encoding::default_external(). CSV will use this to determine
720
- # how to parse the data. You may provide a second Encoding to have the data
721
- # transcoded as it is read. For example,
722
- # <tt>encoding: "UTF-32BE:UTF-8"</tt> would read UTF-32BE data from the file
723
- # but transcode it to UTF-8 before CSV parses it.
724
- #
725
- def self.read(path, *options)
726
- open(path, *options) { |csv| csv.read }
727
- end
1808
+ #
1809
+ # :call-seq:
1810
+ # read(source, **options) -> array_of_arrays
1811
+ # read(source, headers: true, **options) -> csv_table
1812
+ #
1813
+ # Opens the given +source+ with the given +options+ (see CSV.open),
1814
+ # reads the source (see CSV#read), and returns the result,
1815
+ # which will be either an \Array of Arrays or a CSV::Table.
1816
+ #
1817
+ # Without headers:
1818
+ # string = "foo,0\nbar,1\nbaz,2\n"
1819
+ # path = 't.csv'
1820
+ # File.write(path, string)
1821
+ # CSV.read(path) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
1822
+ #
1823
+ # With headers:
1824
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
1825
+ # path = 't.csv'
1826
+ # File.write(path, string)
1827
+ # CSV.read(path, headers: true) # => #<CSV::Table mode:col_or_row row_count:4>
1828
+ def read(path, **options)
1829
+ open(path, **options) { |csv| csv.read }
1830
+ end
728
1831
 
729
- # Alias for CSV::read().
730
- def self.readlines(*args)
731
- read(*args)
732
- end
1832
+ # :call-seq:
1833
+ # CSV.readlines(source, **options)
1834
+ #
1835
+ # Alias for CSV.read.
1836
+ def readlines(path, **options)
1837
+ read(path, **options)
1838
+ end
733
1839
 
734
- #
735
- # A shortcut for:
736
- #
737
- # CSV.read( path, { headers: true,
738
- # converters: :numeric,
739
- # header_converters: :symbol }.merge(options) )
740
- #
741
- def self.table(path, **options)
742
- read( path, { headers: true,
743
- converters: :numeric,
744
- header_converters: :symbol }.merge(options) )
1840
+ # :call-seq:
1841
+ # CSV.table(source, **options)
1842
+ #
1843
+ # Calls CSV.read with +source+, +options+, and certain default options:
1844
+ # - +headers+: +true+
1845
+ # - +converters+: +:numeric+
1846
+ # - +header_converters+: +:symbol+
1847
+ #
1848
+ # Returns a CSV::Table object.
1849
+ #
1850
+ # Example:
1851
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
1852
+ # path = 't.csv'
1853
+ # File.write(path, string)
1854
+ # CSV.table(path) # => #<CSV::Table mode:col_or_row row_count:4>
1855
+ def table(path, **options)
1856
+ default_options = {
1857
+ headers: true,
1858
+ converters: :numeric,
1859
+ header_converters: :symbol,
1860
+ }
1861
+ options = default_options.merge(options)
1862
+ read(path, **options)
1863
+ end
745
1864
  end
746
1865
 
747
- #
748
- # This constructor will wrap either a String or IO object passed in +data+ for
749
- # reading and/or writing. In addition to the CSV instance methods, several IO
750
- # methods are delegated. (See CSV::open() for a complete list.) If you pass
751
- # a String for +data+, you can later retrieve it (after writing to it, for
752
- # example) with CSV.string().
753
- #
754
- # Note that a wrapped String will be positioned at the beginning (for
755
- # reading). If you want it at the end (for writing), use CSV::generate().
756
- # If you want any other positioning, pass a preset StringIO object instead.
757
- #
758
- # You may set any reading and/or writing preferences in the +options+ Hash.
759
- # Available options are:
760
- #
761
- # <b><tt>:col_sep</tt></b>:: The String placed between each field.
762
- # This String will be transcoded into
763
- # the data's Encoding before parsing.
764
- # <b><tt>:row_sep</tt></b>:: The String appended to the end of each
765
- # row. This can be set to the special
766
- # <tt>:auto</tt> setting, which requests
767
- # that CSV automatically discover this
768
- # from the data. Auto-discovery reads
769
- # ahead in the data looking for the next
770
- # <tt>"\r\n"</tt>, <tt>"\n"</tt>, or
771
- # <tt>"\r"</tt> sequence. A sequence
772
- # will be selected even if it occurs in
773
- # a quoted field, assuming that you
774
- # would have the same line endings
775
- # there. If none of those sequences is
776
- # found, +data+ is <tt>ARGF</tt>,
777
- # <tt>STDIN</tt>, <tt>STDOUT</tt>, or
778
- # <tt>STDERR</tt>, or the stream is only
779
- # available for output, the default
780
- # <tt>$INPUT_RECORD_SEPARATOR</tt>
781
- # (<tt>$/</tt>) is used. Obviously,
782
- # discovery takes a little time. Set
783
- # manually if speed is important. Also
784
- # note that IO objects should be opened
785
- # in binary mode on Windows if this
786
- # feature will be used as the
787
- # line-ending translation can cause
788
- # problems with resetting the document
789
- # position to where it was before the
790
- # read ahead. This String will be
791
- # transcoded into the data's Encoding
792
- # before parsing.
793
- # <b><tt>:quote_char</tt></b>:: The character used to quote fields.
794
- # This has to be a single character
795
- # String. This is useful for
796
- # application that incorrectly use
797
- # <tt>'</tt> as the quote character
798
- # instead of the correct <tt>"</tt>.
799
- # CSV will always consider a double
800
- # sequence of this character to be an
801
- # escaped quote. This String will be
802
- # transcoded into the data's Encoding
803
- # before parsing.
804
- # <b><tt>:field_size_limit</tt></b>:: This is a maximum size CSV will read
805
- # ahead looking for the closing quote
806
- # for a field. (In truth, it reads to
807
- # the first line ending beyond this
808
- # size.) If a quote cannot be found
809
- # within the limit CSV will raise a
810
- # MalformedCSVError, assuming the data
811
- # is faulty. You can use this limit to
812
- # prevent what are effectively DoS
813
- # attacks on the parser. However, this
814
- # limit can cause a legitimate parse to
815
- # fail and thus is set to +nil+, or off,
816
- # by default.
817
- # <b><tt>:converters</tt></b>:: An Array of names from the Converters
818
- # Hash and/or lambdas that handle custom
819
- # conversion. A single converter
820
- # doesn't have to be in an Array. All
821
- # built-in converters try to transcode
822
- # fields to UTF-8 before converting.
823
- # The conversion will fail if the data
824
- # cannot be transcoded, leaving the
825
- # field unchanged.
826
- # <b><tt>:unconverted_fields</tt></b>:: If set to +true+, an
827
- # unconverted_fields() method will be
828
- # added to all returned rows (Array or
829
- # CSV::Row) that will return the fields
830
- # as they were before conversion. Note
831
- # that <tt>:headers</tt> supplied by
832
- # Array or String were not fields of the
833
- # document and thus will have an empty
834
- # Array attached.
835
- # <b><tt>:headers</tt></b>:: If set to <tt>:first_row</tt> or
836
- # +true+, the initial row of the CSV
837
- # file will be treated as a row of
838
- # headers. If set to an Array, the
839
- # contents will be used as the headers.
840
- # If set to a String, the String is run
841
- # through a call of CSV::parse_line()
842
- # with the same <tt>:col_sep</tt>,
843
- # <tt>:row_sep</tt>, and
844
- # <tt>:quote_char</tt> as this instance
845
- # to produce an Array of headers. This
846
- # setting causes CSV#shift() to return
847
- # rows as CSV::Row objects instead of
848
- # Arrays and CSV#read() to return
849
- # CSV::Table objects instead of an Array
850
- # of Arrays.
851
- # <b><tt>:return_headers</tt></b>:: When +false+, header rows are silently
852
- # swallowed. If set to +true+, header
853
- # rows are returned in a CSV::Row object
854
- # with identical headers and
855
- # fields (save that the fields do not go
856
- # through the converters).
857
- # <b><tt>:write_headers</tt></b>:: When +true+ and <tt>:headers</tt> is
858
- # set, a header row will be added to the
859
- # output.
860
- # <b><tt>:header_converters</tt></b>:: Identical in functionality to
861
- # <tt>:converters</tt> save that the
862
- # conversions are only made to header
863
- # rows. All built-in converters try to
864
- # transcode headers to UTF-8 before
865
- # converting. The conversion will fail
866
- # if the data cannot be transcoded,
867
- # leaving the header unchanged.
868
- # <b><tt>:skip_blanks</tt></b>:: When set to a +true+ value, CSV will
869
- # skip over any empty rows. Note that
870
- # this setting will not skip rows that
871
- # contain column separators, even if
872
- # the rows contain no actual data. If
873
- # you want to skip rows that contain
874
- # separators but no content, consider
875
- # using <tt>:skip_lines</tt>, or
876
- # inspecting fields.compact.empty? on
877
- # each row.
878
- # <b><tt>:force_quotes</tt></b>:: When set to a +true+ value, CSV will
879
- # quote all CSV fields it creates.
880
- # <b><tt>:skip_lines</tt></b>:: When set to an object responding to
881
- # <tt>match</tt>, every line matching
882
- # it is considered a comment and ignored
883
- # during parsing. When set to a String,
884
- # it is first converted to a Regexp.
885
- # When set to +nil+ no line is considered
886
- # a comment. If the passed object does
887
- # not respond to <tt>match</tt>,
888
- # <tt>ArgumentError</tt> is thrown.
889
- # <b><tt>:liberal_parsing</tt></b>:: When set to a +true+ value, CSV will
890
- # attempt to parse input not conformant
891
- # with RFC 4180, such as double quotes
892
- # in unquoted fields.
893
- # <b><tt>:nil_value</tt></b>:: TODO: WRITE ME.
894
- # <b><tt>:empty_value</tt></b>:: TODO: WRITE ME.
895
- #
896
- # See CSV::DEFAULT_OPTIONS for the default settings.
897
- #
898
- # Options cannot be overridden in the instance methods for performance reasons,
899
- # so be sure to set what you want here.
900
- #
901
- def initialize(data, col_sep: ",", row_sep: :auto, quote_char: '"', field_size_limit: nil,
902
- converters: nil, unconverted_fields: nil, headers: false, return_headers: false,
903
- write_headers: nil, header_converters: nil, skip_blanks: false, force_quotes: false,
904
- skip_lines: nil, liberal_parsing: false, internal_encoding: nil, external_encoding: nil, encoding: nil,
1866
+ # :call-seq:
1867
+ # CSV.new(string)
1868
+ # CSV.new(io)
1869
+ # CSV.new(string, **options)
1870
+ # CSV.new(io, **options)
1871
+ #
1872
+ # Returns the new \CSV object created using +string+ or +io+
1873
+ # and the specified +options+.
1874
+ #
1875
+ # - Argument +string+ should be a \String object;
1876
+ # it will be put into a new StringIO object positioned at the beginning.
1877
+ # :include: ../doc/csv/arguments/io.rdoc
1878
+ # - Argument +options+: See:
1879
+ # * {Options for Parsing}[#class-CSV-label-Options+for+Parsing]
1880
+ # * {Options for Generating}[#class-CSV-label-Options+for+Generating]
1881
+ # For performance reasons, the options cannot be overridden
1882
+ # in a \CSV object, so those specified here will endure.
1883
+ #
1884
+ # In addition to the \CSV instance methods, several \IO methods are delegated.
1885
+ # See {Delegated Methods}[#class-CSV-label-Delegated+Methods].
1886
+ #
1887
+ # ---
1888
+ #
1889
+ # Create a \CSV object from a \String object:
1890
+ # csv = CSV.new('foo,0')
1891
+ # csv # => #<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1892
+ #
1893
+ # Create a \CSV object from a \File object:
1894
+ # File.write('t.csv', 'foo,0')
1895
+ # csv = CSV.new(File.open('t.csv'))
1896
+ # csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
1897
+ #
1898
+ # ---
1899
+ #
1900
+ # Raises an exception if the argument is +nil+:
1901
+ # # Raises ArgumentError (Cannot parse nil as CSV):
1902
+ # CSV.new(nil)
1903
+ #
1904
+ def initialize(data,
1905
+ col_sep: ",",
1906
+ row_sep: :auto,
1907
+ quote_char: '"',
1908
+ field_size_limit: nil,
1909
+ max_field_size: nil,
1910
+ converters: nil,
1911
+ unconverted_fields: nil,
1912
+ headers: false,
1913
+ return_headers: false,
1914
+ write_headers: nil,
1915
+ header_converters: nil,
1916
+ skip_blanks: false,
1917
+ force_quotes: false,
1918
+ skip_lines: nil,
1919
+ liberal_parsing: false,
1920
+ internal_encoding: nil,
1921
+ external_encoding: nil,
1922
+ encoding: nil,
905
1923
  nil_value: nil,
906
- empty_value: "")
1924
+ empty_value: "",
1925
+ strip: false,
1926
+ quote_empty: true,
1927
+ write_converters: nil,
1928
+ write_nil_value: nil,
1929
+ write_empty_value: "")
907
1930
  raise ArgumentError.new("Cannot parse nil as CSV") if data.nil?
908
1931
 
909
- # create the IO object we will read from
910
- @io = data.is_a?(String) ? StringIO.new(data) : data
1932
+ if data.is_a?(String)
1933
+ if encoding
1934
+ if encoding.is_a?(String)
1935
+ data_external_encoding, data_internal_encoding = encoding.split(":", 2)
1936
+ if data_internal_encoding
1937
+ data = data.encode(data_internal_encoding, data_external_encoding)
1938
+ else
1939
+ data = data.dup.force_encoding(data_external_encoding)
1940
+ end
1941
+ else
1942
+ data = data.dup.force_encoding(encoding)
1943
+ end
1944
+ end
1945
+ @io = StringIO.new(data)
1946
+ else
1947
+ @io = data
1948
+ end
911
1949
  @encoding = determine_encoding(encoding, internal_encoding)
912
- #
913
- # prepare for building safe regular expressions in the target encoding,
914
- # if we can transcode the needed characters
915
- #
916
- @re_esc = "\\".encode(@encoding).freeze rescue ""
917
- @re_chars = /#{%"[-\\]\\[\\.^$?*+{}()|# \r\n\t\f\v]".encode(@encoding)}/
918
- @unconverted_fields = unconverted_fields
919
1950
 
920
- # Stores header row settings and loads header converters, if needed.
921
- @use_headers = headers
922
- @return_headers = return_headers
923
- @write_headers = write_headers
924
-
925
- # headers must be delayed until shift(), in case they need a row of content
926
- @headers = nil
927
-
928
- @nil_value = nil_value
929
- @empty_value = empty_value
930
- @empty_value_is_empty_string = (empty_value == "")
931
-
932
- init_separators(col_sep, row_sep, quote_char, force_quotes)
933
- init_parsers(skip_blanks, field_size_limit, liberal_parsing)
934
- init_converters(converters, :@converters, :convert)
935
- init_converters(header_converters, :@header_converters, :header_convert)
936
- init_comments(skip_lines)
1951
+ @base_fields_converter_options = {
1952
+ nil_value: nil_value,
1953
+ empty_value: empty_value,
1954
+ }
1955
+ @write_fields_converter_options = {
1956
+ nil_value: write_nil_value,
1957
+ empty_value: write_empty_value,
1958
+ }
1959
+ @initial_converters = converters
1960
+ @initial_header_converters = header_converters
1961
+ @initial_write_converters = write_converters
937
1962
 
938
- @force_encoding = !!encoding
1963
+ if max_field_size.nil? and field_size_limit
1964
+ max_field_size = field_size_limit - 1
1965
+ end
1966
+ @parser_options = {
1967
+ column_separator: col_sep,
1968
+ row_separator: row_sep,
1969
+ quote_character: quote_char,
1970
+ max_field_size: max_field_size,
1971
+ unconverted_fields: unconverted_fields,
1972
+ headers: headers,
1973
+ return_headers: return_headers,
1974
+ skip_blanks: skip_blanks,
1975
+ skip_lines: skip_lines,
1976
+ liberal_parsing: liberal_parsing,
1977
+ encoding: @encoding,
1978
+ nil_value: nil_value,
1979
+ empty_value: empty_value,
1980
+ strip: strip,
1981
+ }
1982
+ @parser = nil
1983
+ @parser_enumerator = nil
1984
+ @eof_error = nil
1985
+
1986
+ @writer_options = {
1987
+ encoding: @encoding,
1988
+ force_encoding: (not encoding.nil?),
1989
+ force_quotes: force_quotes,
1990
+ headers: headers,
1991
+ write_headers: write_headers,
1992
+ column_separator: col_sep,
1993
+ row_separator: row_sep,
1994
+ quote_character: quote_char,
1995
+ quote_empty: quote_empty,
1996
+ }
939
1997
 
940
- # track our own lineno since IO gets confused about line-ends is CSV fields
941
- @lineno = 0
1998
+ @writer = nil
1999
+ writer if @writer_options[:write_headers]
2000
+ end
942
2001
 
943
- # make sure headers have been assigned
944
- if header_row? and [Array, String].include? @use_headers.class and @write_headers
945
- parse_headers # won't read data for Array or String
946
- self << @headers
947
- end
2002
+ # :call-seq:
2003
+ # csv.col_sep -> string
2004
+ #
2005
+ # Returns the encoded column separator; used for parsing and writing;
2006
+ # see {Option +col_sep+}[#class-CSV-label-Option+col_sep]:
2007
+ # CSV.new('').col_sep # => ","
2008
+ def col_sep
2009
+ parser.column_separator
948
2010
  end
949
2011
 
2012
+ # :call-seq:
2013
+ # csv.row_sep -> string
950
2014
  #
951
- # The encoded <tt>:col_sep</tt> used in parsing and writing. See CSV::new
952
- # for details.
2015
+ # Returns the encoded row separator; used for parsing and writing;
2016
+ # see {Option +row_sep+}[#class-CSV-label-Option+row_sep]:
2017
+ # CSV.new('').row_sep # => "\n"
2018
+ def row_sep
2019
+ parser.row_separator
2020
+ end
2021
+
2022
+ # :call-seq:
2023
+ # csv.quote_char -> character
953
2024
  #
954
- attr_reader :col_sep
2025
+ # Returns the encoded quote character; used for parsing and writing;
2026
+ # see {Option +quote_char+}[#class-CSV-label-Option+quote_char]:
2027
+ # CSV.new('').quote_char # => "\""
2028
+ def quote_char
2029
+ parser.quote_character
2030
+ end
2031
+
2032
+ # :call-seq:
2033
+ # csv.field_size_limit -> integer or nil
955
2034
  #
956
- # The encoded <tt>:row_sep</tt> used in parsing and writing. See CSV::new
957
- # for details.
2035
+ # Returns the limit for field size; used for parsing;
2036
+ # see {Option +field_size_limit+}[#class-CSV-label-Option+field_size_limit]:
2037
+ # CSV.new('').field_size_limit # => nil
958
2038
  #
959
- attr_reader :row_sep
2039
+ # Deprecated since 3.2.3. Use +max_field_size+ instead.
2040
+ def field_size_limit
2041
+ parser.field_size_limit
2042
+ end
2043
+
2044
+ # :call-seq:
2045
+ # csv.max_field_size -> integer or nil
960
2046
  #
961
- # The encoded <tt>:quote_char</tt> used in parsing and writing. See CSV::new
962
- # for details.
2047
+ # Returns the limit for field size; used for parsing;
2048
+ # see {Option +max_field_size+}[#class-CSV-label-Option+max_field_size]:
2049
+ # CSV.new('').max_field_size # => nil
963
2050
  #
964
- attr_reader :quote_char
965
- # The limit for field size, if any. See CSV::new for details.
966
- attr_reader :field_size_limit
967
-
968
- # The regex marking a line as a comment. See CSV::new for details
969
- attr_reader :skip_lines
2051
+ # Since 3.2.3.
2052
+ def max_field_size
2053
+ parser.max_field_size
2054
+ end
970
2055
 
2056
+ # :call-seq:
2057
+ # csv.skip_lines -> regexp or nil
971
2058
  #
972
- # Returns the current list of converters in effect. See CSV::new for details.
973
- # Built-in converters will be returned by name, while others will be returned
974
- # as is.
975
- #
2059
+ # Returns the \Regexp used to identify comment lines; used for parsing;
2060
+ # see {Option +skip_lines+}[#class-CSV-label-Option+skip_lines]:
2061
+ # CSV.new('').skip_lines # => nil
2062
+ def skip_lines
2063
+ parser.skip_lines
2064
+ end
2065
+
2066
+ # :call-seq:
2067
+ # csv.converters -> array
2068
+ #
2069
+ # Returns an \Array containing field converters;
2070
+ # see {Field Converters}[#class-CSV-label-Field+Converters]:
2071
+ # csv = CSV.new('')
2072
+ # csv.converters # => []
2073
+ # csv.convert(:integer)
2074
+ # csv.converters # => [:integer]
2075
+ # csv.convert(proc {|x| x.to_s })
2076
+ # csv.converters
2077
+ #
2078
+ # Notes that you need to call
2079
+ # +Ractor.make_shareable(CSV::Converters)+ on the main Ractor to use
2080
+ # this method.
976
2081
  def converters
977
- @converters.map do |converter|
2082
+ parser_fields_converter.map do |converter|
978
2083
  name = Converters.rassoc(converter)
979
2084
  name ? name.first : converter
980
2085
  end
981
2086
  end
2087
+
2088
+ # :call-seq:
2089
+ # csv.unconverted_fields? -> object
2090
+ #
2091
+ # Returns the value that determines whether unconverted fields are to be
2092
+ # available; used for parsing;
2093
+ # see {Option +unconverted_fields+}[#class-CSV-label-Option+unconverted_fields]:
2094
+ # CSV.new('').unconverted_fields? # => nil
2095
+ def unconverted_fields?
2096
+ parser.unconverted_fields?
2097
+ end
2098
+
2099
+ # :call-seq:
2100
+ # csv.headers -> object
982
2101
  #
983
- # Returns +true+ if unconverted_fields() to parsed results. See CSV::new
984
- # for details.
985
- #
986
- def unconverted_fields?() @unconverted_fields end
987
- #
988
- # Returns +nil+ if headers will not be used, +true+ if they will but have not
989
- # yet been read, or the actual headers after they have been read. See
990
- # CSV::new for details.
991
- #
2102
+ # Returns the value that determines whether headers are used; used for parsing;
2103
+ # see {Option +headers+}[#class-CSV-label-Option+headers]:
2104
+ # CSV.new('').headers # => nil
992
2105
  def headers
993
- @headers || true if @use_headers
2106
+ if @writer
2107
+ @writer.headers
2108
+ else
2109
+ parsed_headers = parser.headers
2110
+ return parsed_headers if parsed_headers
2111
+ raw_headers = @parser_options[:headers]
2112
+ raw_headers = nil if raw_headers == false
2113
+ raw_headers
2114
+ end
994
2115
  end
2116
+
2117
+ # :call-seq:
2118
+ # csv.return_headers? -> true or false
995
2119
  #
996
- # Returns +true+ if headers will be returned as a row of results.
997
- # See CSV::new for details.
2120
+ # Returns the value that determines whether headers are to be returned; used for parsing;
2121
+ # see {Option +return_headers+}[#class-CSV-label-Option+return_headers]:
2122
+ # CSV.new('').return_headers? # => false
2123
+ def return_headers?
2124
+ parser.return_headers?
2125
+ end
2126
+
2127
+ # :call-seq:
2128
+ # csv.write_headers? -> true or false
998
2129
  #
999
- def return_headers?() @return_headers end
1000
- # Returns +true+ if headers are written in output. See CSV::new for details.
1001
- def write_headers?() @write_headers end
2130
+ # Returns the value that determines whether headers are to be written; used for generating;
2131
+ # see {Option +write_headers+}[#class-CSV-label-Option+write_headers]:
2132
+ # CSV.new('').write_headers? # => nil
2133
+ def write_headers?
2134
+ @writer_options[:write_headers]
2135
+ end
2136
+
2137
+ # :call-seq:
2138
+ # csv.header_converters -> array
1002
2139
  #
1003
- # Returns the current list of converters in effect for headers. See CSV::new
1004
- # for details. Built-in converters will be returned by name, while others
1005
- # will be returned as is.
2140
+ # Returns an \Array containing header converters; used for parsing;
2141
+ # see {Header Converters}[#class-CSV-label-Header+Converters]:
2142
+ # CSV.new('').header_converters # => []
1006
2143
  #
2144
+ # Notes that you need to call
2145
+ # +Ractor.make_shareable(CSV::HeaderConverters)+ on the main Ractor
2146
+ # to use this method.
1007
2147
  def header_converters
1008
- @header_converters.map do |converter|
2148
+ header_fields_converter.map do |converter|
1009
2149
  name = HeaderConverters.rassoc(converter)
1010
2150
  name ? name.first : converter
1011
2151
  end
1012
2152
  end
2153
+
2154
+ # :call-seq:
2155
+ # csv.skip_blanks? -> true or false
1013
2156
  #
1014
- # Returns +true+ blank lines are skipped by the parser. See CSV::new
1015
- # for details.
1016
- #
1017
- def skip_blanks?() @skip_blanks end
1018
- # Returns +true+ if all output fields are quoted. See CSV::new for details.
1019
- def force_quotes?() @force_quotes end
1020
- # Returns +true+ if illegal input is handled. See CSV::new for details.
1021
- def liberal_parsing?() @liberal_parsing end
2157
+ # Returns the value that determines whether blank lines are to be ignored; used for parsing;
2158
+ # see {Option +skip_blanks+}[#class-CSV-label-Option+skip_blanks]:
2159
+ # CSV.new('').skip_blanks? # => false
2160
+ def skip_blanks?
2161
+ parser.skip_blanks?
2162
+ end
2163
+
2164
+ # :call-seq:
2165
+ # csv.force_quotes? -> true or false
2166
+ #
2167
+ # Returns the value that determines whether all output fields are to be quoted;
2168
+ # used for generating;
2169
+ # see {Option +force_quotes+}[#class-CSV-label-Option+force_quotes]:
2170
+ # CSV.new('').force_quotes? # => false
2171
+ def force_quotes?
2172
+ @writer_options[:force_quotes]
2173
+ end
1022
2174
 
2175
+ # :call-seq:
2176
+ # csv.liberal_parsing? -> true or false
1023
2177
  #
1024
- # The Encoding CSV is parsing or writing in. This will be the Encoding you
1025
- # receive parsed data in and/or the Encoding data will be written in.
2178
+ # Returns the value that determines whether illegal input is to be handled; used for parsing;
2179
+ # see {Option +liberal_parsing+}[#class-CSV-label-Option+liberal_parsing]:
2180
+ # CSV.new('').liberal_parsing? # => false
2181
+ def liberal_parsing?
2182
+ parser.liberal_parsing?
2183
+ end
2184
+
2185
+ # :call-seq:
2186
+ # csv.encoding -> encoding
1026
2187
  #
2188
+ # Returns the encoding used for parsing and generating;
2189
+ # see {Character Encodings (M17n or Multilingualization)}[#class-CSV-label-Character+Encodings+-28M17n+or+Multilingualization-29]:
2190
+ # CSV.new('').encoding # => #<Encoding:UTF-8>
1027
2191
  attr_reader :encoding
1028
2192
 
1029
- #
1030
- # The line number of the last row read from this file. Fields with nested
1031
- # line-end characters will not affect this count.
1032
- #
1033
- attr_reader :lineno, :line
2193
+ # :call-seq:
2194
+ # csv.line_no -> integer
2195
+ #
2196
+ # Returns the count of the rows parsed or generated.
2197
+ #
2198
+ # Parsing:
2199
+ # string = "foo,0\nbar,1\nbaz,2\n"
2200
+ # path = 't.csv'
2201
+ # File.write(path, string)
2202
+ # CSV.open(path) do |csv|
2203
+ # csv.each do |row|
2204
+ # p [csv.lineno, row]
2205
+ # end
2206
+ # end
2207
+ # Output:
2208
+ # [1, ["foo", "0"]]
2209
+ # [2, ["bar", "1"]]
2210
+ # [3, ["baz", "2"]]
2211
+ #
2212
+ # Generating:
2213
+ # CSV.generate do |csv|
2214
+ # p csv.lineno; csv << ['foo', 0]
2215
+ # p csv.lineno; csv << ['bar', 1]
2216
+ # p csv.lineno; csv << ['baz', 2]
2217
+ # end
2218
+ # Output:
2219
+ # 0
2220
+ # 1
2221
+ # 2
2222
+ def lineno
2223
+ if @writer
2224
+ @writer.lineno
2225
+ else
2226
+ parser.lineno
2227
+ end
2228
+ end
2229
+
2230
+ # :call-seq:
2231
+ # csv.line -> array
2232
+ #
2233
+ # Returns the line most recently read:
2234
+ # string = "foo,0\nbar,1\nbaz,2\n"
2235
+ # path = 't.csv'
2236
+ # File.write(path, string)
2237
+ # CSV.open(path) do |csv|
2238
+ # csv.each do |row|
2239
+ # p [csv.lineno, csv.line]
2240
+ # end
2241
+ # end
2242
+ # Output:
2243
+ # [1, "foo,0\n"]
2244
+ # [2, "bar,1\n"]
2245
+ # [3, "baz,2\n"]
2246
+ def line
2247
+ parser.line
2248
+ end
1034
2249
 
1035
2250
  ### IO and StringIO Delegation ###
1036
2251
 
1037
2252
  extend Forwardable
1038
- def_delegators :@io, :binmode, :binmode?, :close, :close_read, :close_write,
1039
- :closed?, :eof, :eof?, :external_encoding, :fcntl,
1040
- :fileno, :flock, :flush, :fsync, :internal_encoding,
1041
- :ioctl, :isatty, :path, :pid, :pos, :pos=, :reopen,
1042
- :seek, :stat, :string, :sync, :sync=, :tell, :to_i,
1043
- :to_io, :truncate, :tty?
2253
+ def_delegators :@io, :binmode, :close, :close_read, :close_write,
2254
+ :closed?, :external_encoding, :fcntl,
2255
+ :fileno, :flush, :fsync, :internal_encoding,
2256
+ :isatty, :pid, :pos, :pos=, :reopen,
2257
+ :seek, :string, :sync, :sync=, :tell,
2258
+ :truncate, :tty?
2259
+
2260
+ def binmode?
2261
+ if @io.respond_to?(:binmode?)
2262
+ @io.binmode?
2263
+ else
2264
+ false
2265
+ end
2266
+ end
1044
2267
 
1045
- # Rewinds the underlying IO object and resets CSV's lineno() counter.
1046
- def rewind
1047
- @headers = nil
1048
- @lineno = 0
2268
+ def flock(*args)
2269
+ raise NotImplementedError unless @io.respond_to?(:flock)
2270
+ @io.flock(*args)
2271
+ end
1049
2272
 
1050
- @io.rewind
2273
+ def ioctl(*args)
2274
+ raise NotImplementedError unless @io.respond_to?(:ioctl)
2275
+ @io.ioctl(*args)
1051
2276
  end
1052
2277
 
1053
- ### End Delegation ###
2278
+ def path
2279
+ @io.path if @io.respond_to?(:path)
2280
+ end
1054
2281
 
1055
- #
1056
- # The primary write method for wrapped Strings and IOs, +row+ (an Array or
1057
- # CSV::Row) is converted to CSV and appended to the data source. When a
1058
- # CSV::Row is passed, only the row's fields() are appended to the output.
1059
- #
1060
- # The data source must be open for writing.
1061
- #
1062
- def <<(row)
1063
- # make sure headers have been assigned
1064
- if header_row? and [Array, String].include? @use_headers.class and !@write_headers
1065
- parse_headers # won't read data for Array or String
1066
- end
2282
+ def stat(*args)
2283
+ raise NotImplementedError unless @io.respond_to?(:stat)
2284
+ @io.stat(*args)
2285
+ end
1067
2286
 
1068
- # handle CSV::Row objects and Hashes
1069
- row = case row
1070
- when self.class::Row then row.fields
1071
- when Hash then @headers.map { |header| row[header] }
1072
- else row
1073
- end
2287
+ def to_i
2288
+ raise NotImplementedError unless @io.respond_to?(:to_i)
2289
+ @io.to_i
2290
+ end
1074
2291
 
1075
- @headers = row if header_row?
1076
- @lineno += 1
1077
-
1078
- output = row.map(&@quote).join(@col_sep) + @row_sep # quote and separate
1079
- if @io.is_a?(StringIO) and
1080
- output.encoding != (encoding = raw_encoding)
1081
- if @force_encoding
1082
- output = output.encode(encoding)
1083
- elsif (compatible_encoding = Encoding.compatible?(@io.string, output))
1084
- @io.set_encoding(compatible_encoding)
1085
- @io.seek(0, IO::SEEK_END)
1086
- end
2292
+ def to_io
2293
+ @io.respond_to?(:to_io) ? @io.to_io : @io
2294
+ end
2295
+
2296
+ def eof?
2297
+ return false if @eof_error
2298
+ begin
2299
+ parser_enumerator.peek
2300
+ false
2301
+ rescue MalformedCSVError => error
2302
+ @eof_error = error
2303
+ false
2304
+ rescue StopIteration
2305
+ true
1087
2306
  end
1088
- @io << output
2307
+ end
2308
+ alias_method :eof, :eof?
2309
+
2310
+ # Rewinds the underlying IO object and resets CSV's lineno() counter.
2311
+ def rewind
2312
+ @parser = nil
2313
+ @parser_enumerator = nil
2314
+ @eof_error = nil
2315
+ @writer.rewind if @writer
2316
+ @io.rewind
2317
+ end
1089
2318
 
1090
- self # for chaining
2319
+ ### End Delegation ###
2320
+
2321
+ # :call-seq:
2322
+ # csv << row -> self
2323
+ #
2324
+ # Appends a row to +self+.
2325
+ #
2326
+ # - Argument +row+ must be an \Array object or a CSV::Row object.
2327
+ # - The output stream must be open for writing.
2328
+ #
2329
+ # ---
2330
+ #
2331
+ # Append Arrays:
2332
+ # CSV.generate do |csv|
2333
+ # csv << ['foo', 0]
2334
+ # csv << ['bar', 1]
2335
+ # csv << ['baz', 2]
2336
+ # end # => "foo,0\nbar,1\nbaz,2\n"
2337
+ #
2338
+ # Append CSV::Rows:
2339
+ # headers = []
2340
+ # CSV.generate do |csv|
2341
+ # csv << CSV::Row.new(headers, ['foo', 0])
2342
+ # csv << CSV::Row.new(headers, ['bar', 1])
2343
+ # csv << CSV::Row.new(headers, ['baz', 2])
2344
+ # end # => "foo,0\nbar,1\nbaz,2\n"
2345
+ #
2346
+ # Headers in CSV::Row objects are not appended:
2347
+ # headers = ['Name', 'Count']
2348
+ # CSV.generate do |csv|
2349
+ # csv << CSV::Row.new(headers, ['foo', 0])
2350
+ # csv << CSV::Row.new(headers, ['bar', 1])
2351
+ # csv << CSV::Row.new(headers, ['baz', 2])
2352
+ # end # => "foo,0\nbar,1\nbaz,2\n"
2353
+ #
2354
+ # ---
2355
+ #
2356
+ # Raises an exception if +row+ is not an \Array or \CSV::Row:
2357
+ # CSV.generate do |csv|
2358
+ # # Raises NoMethodError (undefined method `collect' for :foo:Symbol)
2359
+ # csv << :foo
2360
+ # end
2361
+ #
2362
+ # Raises an exception if the output stream is not opened for writing:
2363
+ # path = 't.csv'
2364
+ # File.write(path, '')
2365
+ # File.open(path) do |file|
2366
+ # CSV.open(file) do |csv|
2367
+ # # Raises IOError (not opened for writing)
2368
+ # csv << ['foo', 0]
2369
+ # end
2370
+ # end
2371
+ def <<(row)
2372
+ writer << row
2373
+ self
1091
2374
  end
1092
2375
  alias_method :add_row, :<<
1093
2376
  alias_method :puts, :<<
1094
2377
 
1095
- #
1096
2378
  # :call-seq:
1097
- # convert( name )
1098
- # convert { |field| ... }
1099
- # convert { |field, field_info| ... }
1100
- #
1101
- # You can use this method to install a CSV::Converters built-in, or provide a
1102
- # block that handles a custom conversion.
1103
- #
1104
- # If you provide a block that takes one argument, it will be passed the field
1105
- # and is expected to return the converted value or the field itself. If your
1106
- # block takes two arguments, it will also be passed a CSV::FieldInfo Struct,
1107
- # containing details about the field. Again, the block should return a
1108
- # converted field or the field itself.
1109
- #
2379
+ # convert(converter_name) -> array_of_procs
2380
+ # convert {|field, field_info| ... } -> array_of_procs
2381
+ #
2382
+ # - With no block, installs a field converter (a \Proc).
2383
+ # - With a block, defines and installs a custom field converter.
2384
+ # - Returns the \Array of installed field converters.
2385
+ #
2386
+ # - Argument +converter_name+, if given, should be the name
2387
+ # of an existing field converter.
2388
+ #
2389
+ # See {Field Converters}[#class-CSV-label-Field+Converters].
2390
+ # ---
2391
+ #
2392
+ # With no block, installs a field converter:
2393
+ # csv = CSV.new('')
2394
+ # csv.convert(:integer)
2395
+ # csv.convert(:float)
2396
+ # csv.convert(:date)
2397
+ # csv.converters # => [:integer, :float, :date]
2398
+ #
2399
+ # ---
2400
+ #
2401
+ # The block, if given, is called for each field:
2402
+ # - Argument +field+ is the field value.
2403
+ # - Argument +field_info+ is a CSV::FieldInfo object
2404
+ # containing details about the field.
2405
+ #
2406
+ # The examples here assume the prior execution of:
2407
+ # string = "foo,0\nbar,1\nbaz,2\n"
2408
+ # path = 't.csv'
2409
+ # File.write(path, string)
2410
+ #
2411
+ # Example giving a block:
2412
+ # csv = CSV.open(path)
2413
+ # csv.convert {|field, field_info| p [field, field_info]; field.upcase }
2414
+ # csv.read # => [["FOO", "0"], ["BAR", "1"], ["BAZ", "2"]]
2415
+ #
2416
+ # Output:
2417
+ # ["foo", #<struct CSV::FieldInfo index=0, line=1, header=nil>]
2418
+ # ["0", #<struct CSV::FieldInfo index=1, line=1, header=nil>]
2419
+ # ["bar", #<struct CSV::FieldInfo index=0, line=2, header=nil>]
2420
+ # ["1", #<struct CSV::FieldInfo index=1, line=2, header=nil>]
2421
+ # ["baz", #<struct CSV::FieldInfo index=0, line=3, header=nil>]
2422
+ # ["2", #<struct CSV::FieldInfo index=1, line=3, header=nil>]
2423
+ #
2424
+ # The block need not return a \String object:
2425
+ # csv = CSV.open(path)
2426
+ # csv.convert {|field, field_info| field.to_sym }
2427
+ # csv.read # => [[:foo, :"0"], [:bar, :"1"], [:baz, :"2"]]
2428
+ #
2429
+ # If +converter_name+ is given, the block is not called:
2430
+ # csv = CSV.open(path)
2431
+ # csv.convert(:integer) {|field, field_info| fail 'Cannot happen' }
2432
+ # csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]]
2433
+ #
2434
+ # ---
2435
+ #
2436
+ # Raises a parse-time exception if +converter_name+ is not the name of a built-in
2437
+ # field converter:
2438
+ # csv = CSV.open(path)
2439
+ # csv.convert(:nosuch) => [nil]
2440
+ # # Raises NoMethodError (undefined method `arity' for nil:NilClass)
2441
+ # csv.read
1110
2442
  def convert(name = nil, &converter)
1111
- add_converter(:@converters, self.class::Converters, name, &converter)
2443
+ parser_fields_converter.add_converter(name, &converter)
1112
2444
  end
1113
2445
 
1114
- #
1115
2446
  # :call-seq:
1116
- # header_convert( name )
1117
- # header_convert { |field| ... }
1118
- # header_convert { |field, field_info| ... }
1119
- #
1120
- # Identical to CSV#convert(), but for header rows.
1121
- #
1122
- # Note that this method must be called before header rows are read to have any
1123
- # effect.
1124
- #
2447
+ # header_convert(converter_name) -> array_of_procs
2448
+ # header_convert {|header, field_info| ... } -> array_of_procs
2449
+ #
2450
+ # - With no block, installs a header converter (a \Proc).
2451
+ # - With a block, defines and installs a custom header converter.
2452
+ # - Returns the \Array of installed header converters.
2453
+ #
2454
+ # - Argument +converter_name+, if given, should be the name
2455
+ # of an existing header converter.
2456
+ #
2457
+ # See {Header Converters}[#class-CSV-label-Header+Converters].
2458
+ # ---
2459
+ #
2460
+ # With no block, installs a header converter:
2461
+ # csv = CSV.new('')
2462
+ # csv.header_convert(:symbol)
2463
+ # csv.header_convert(:downcase)
2464
+ # csv.header_converters # => [:symbol, :downcase]
2465
+ #
2466
+ # ---
2467
+ #
2468
+ # The block, if given, is called for each header:
2469
+ # - Argument +header+ is the header value.
2470
+ # - Argument +field_info+ is a CSV::FieldInfo object
2471
+ # containing details about the header.
2472
+ #
2473
+ # The examples here assume the prior execution of:
2474
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
2475
+ # path = 't.csv'
2476
+ # File.write(path, string)
2477
+ #
2478
+ # Example giving a block:
2479
+ # csv = CSV.open(path, headers: true)
2480
+ # csv.header_convert {|header, field_info| p [header, field_info]; header.upcase }
2481
+ # table = csv.read
2482
+ # table # => #<CSV::Table mode:col_or_row row_count:4>
2483
+ # table.headers # => ["NAME", "VALUE"]
2484
+ #
2485
+ # Output:
2486
+ # ["Name", #<struct CSV::FieldInfo index=0, line=1, header=nil>]
2487
+ # ["Value", #<struct CSV::FieldInfo index=1, line=1, header=nil>]
2488
+
2489
+ # The block need not return a \String object:
2490
+ # csv = CSV.open(path, headers: true)
2491
+ # csv.header_convert {|header, field_info| header.to_sym }
2492
+ # table = csv.read
2493
+ # table.headers # => [:Name, :Value]
2494
+ #
2495
+ # If +converter_name+ is given, the block is not called:
2496
+ # csv = CSV.open(path, headers: true)
2497
+ # csv.header_convert(:downcase) {|header, field_info| fail 'Cannot happen' }
2498
+ # table = csv.read
2499
+ # table.headers # => ["name", "value"]
2500
+ # ---
2501
+ #
2502
+ # Raises a parse-time exception if +converter_name+ is not the name of a built-in
2503
+ # field converter:
2504
+ # csv = CSV.open(path, headers: true)
2505
+ # csv.header_convert(:nosuch)
2506
+ # # Raises NoMethodError (undefined method `arity' for nil:NilClass)
2507
+ # csv.read
1125
2508
  def header_convert(name = nil, &converter)
1126
- add_converter( :@header_converters,
1127
- self.class::HeaderConverters,
1128
- name,
1129
- &converter )
2509
+ header_fields_converter.add_converter(name, &converter)
1130
2510
  end
1131
2511
 
1132
2512
  include Enumerable
1133
2513
 
1134
- #
1135
- # Yields each row of the data source in turn.
1136
- #
1137
- # Support for Enumerable.
1138
- #
1139
- # The data source must be open for reading.
1140
- #
1141
- def each
1142
- if block_given?
1143
- while row = shift
1144
- yield row
2514
+ # :call-seq:
2515
+ # csv.each -> enumerator
2516
+ # csv.each {|row| ...}
2517
+ #
2518
+ # Calls the block with each successive row.
2519
+ # The data source must be opened for reading.
2520
+ #
2521
+ # Without headers:
2522
+ # string = "foo,0\nbar,1\nbaz,2\n"
2523
+ # csv = CSV.new(string)
2524
+ # csv.each do |row|
2525
+ # p row
2526
+ # end
2527
+ # Output:
2528
+ # ["foo", "0"]
2529
+ # ["bar", "1"]
2530
+ # ["baz", "2"]
2531
+ #
2532
+ # With headers:
2533
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
2534
+ # csv = CSV.new(string, headers: true)
2535
+ # csv.each do |row|
2536
+ # p row
2537
+ # end
2538
+ # Output:
2539
+ # <CSV::Row "Name":"foo" "Value":"0">
2540
+ # <CSV::Row "Name":"bar" "Value":"1">
2541
+ # <CSV::Row "Name":"baz" "Value":"2">
2542
+ #
2543
+ # ---
2544
+ #
2545
+ # Raises an exception if the source is not opened for reading:
2546
+ # string = "foo,0\nbar,1\nbaz,2\n"
2547
+ # csv = CSV.new(string)
2548
+ # csv.close
2549
+ # # Raises IOError (not opened for reading)
2550
+ # csv.each do |row|
2551
+ # p row
2552
+ # end
2553
+ def each(&block)
2554
+ return to_enum(__method__) unless block_given?
2555
+ begin
2556
+ while true
2557
+ yield(parser_enumerator.next)
1145
2558
  end
1146
- else
1147
- to_enum
2559
+ rescue StopIteration
1148
2560
  end
1149
2561
  end
1150
2562
 
1151
- #
1152
- # Slurps the remaining rows and returns an Array of Arrays.
1153
- #
1154
- # The data source must be open for reading.
1155
- #
2563
+ # :call-seq:
2564
+ # csv.read -> array or csv_table
2565
+ #
2566
+ # Forms the remaining rows from +self+ into:
2567
+ # - A CSV::Table object, if headers are in use.
2568
+ # - An \Array of Arrays, otherwise.
2569
+ #
2570
+ # The data source must be opened for reading.
2571
+ #
2572
+ # Without headers:
2573
+ # string = "foo,0\nbar,1\nbaz,2\n"
2574
+ # path = 't.csv'
2575
+ # File.write(path, string)
2576
+ # csv = CSV.open(path)
2577
+ # csv.read # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
2578
+ #
2579
+ # With headers:
2580
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
2581
+ # path = 't.csv'
2582
+ # File.write(path, string)
2583
+ # csv = CSV.open(path, headers: true)
2584
+ # csv.read # => #<CSV::Table mode:col_or_row row_count:4>
2585
+ #
2586
+ # ---
2587
+ #
2588
+ # Raises an exception if the source is not opened for reading:
2589
+ # string = "foo,0\nbar,1\nbaz,2\n"
2590
+ # csv = CSV.new(string)
2591
+ # csv.close
2592
+ # # Raises IOError (not opened for reading)
2593
+ # csv.read
1156
2594
  def read
1157
2595
  rows = to_a
1158
- if @use_headers
1159
- Table.new(rows)
2596
+ if parser.use_headers?
2597
+ Table.new(rows, headers: parser.headers)
1160
2598
  else
1161
2599
  rows
1162
2600
  end
1163
2601
  end
1164
2602
  alias_method :readlines, :read
1165
2603
 
1166
- # Returns +true+ if the next row read will be a header row.
2604
+ # :call-seq:
2605
+ # csv.header_row? -> true or false
2606
+ #
2607
+ # Returns +true+ if the next row to be read is a header row\;
2608
+ # +false+ otherwise.
2609
+ #
2610
+ # Without headers:
2611
+ # string = "foo,0\nbar,1\nbaz,2\n"
2612
+ # csv = CSV.new(string)
2613
+ # csv.header_row? # => false
2614
+ #
2615
+ # With headers:
2616
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
2617
+ # csv = CSV.new(string, headers: true)
2618
+ # csv.header_row? # => true
2619
+ # csv.shift # => #<CSV::Row "Name":"foo" "Value":"0">
2620
+ # csv.header_row? # => false
2621
+ #
2622
+ # ---
2623
+ #
2624
+ # Raises an exception if the source is not opened for reading:
2625
+ # string = "foo,0\nbar,1\nbaz,2\n"
2626
+ # csv = CSV.new(string)
2627
+ # csv.close
2628
+ # # Raises IOError (not opened for reading)
2629
+ # csv.header_row?
1167
2630
  def header_row?
1168
- @use_headers and @headers.nil?
2631
+ parser.header_row?
1169
2632
  end
1170
2633
 
1171
- #
1172
- # The primary read method for wrapped Strings and IOs, a single row is pulled
1173
- # from the data source, parsed and returned as an Array of fields (if header
1174
- # rows are not used) or a CSV::Row (when header rows are used).
1175
- #
1176
- # The data source must be open for reading.
1177
- #
2634
+ # :call-seq:
2635
+ # csv.shift -> array, csv_row, or nil
2636
+ #
2637
+ # Returns the next row of data as:
2638
+ # - An \Array if no headers are used.
2639
+ # - A CSV::Row object if headers are used.
2640
+ #
2641
+ # The data source must be opened for reading.
2642
+ #
2643
+ # Without headers:
2644
+ # string = "foo,0\nbar,1\nbaz,2\n"
2645
+ # csv = CSV.new(string)
2646
+ # csv.shift # => ["foo", "0"]
2647
+ # csv.shift # => ["bar", "1"]
2648
+ # csv.shift # => ["baz", "2"]
2649
+ # csv.shift # => nil
2650
+ #
2651
+ # With headers:
2652
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
2653
+ # csv = CSV.new(string, headers: true)
2654
+ # csv.shift # => #<CSV::Row "Name":"foo" "Value":"0">
2655
+ # csv.shift # => #<CSV::Row "Name":"bar" "Value":"1">
2656
+ # csv.shift # => #<CSV::Row "Name":"baz" "Value":"2">
2657
+ # csv.shift # => nil
2658
+ #
2659
+ # ---
2660
+ #
2661
+ # Raises an exception if the source is not opened for reading:
2662
+ # string = "foo,0\nbar,1\nbaz,2\n"
2663
+ # csv = CSV.new(string)
2664
+ # csv.close
2665
+ # # Raises IOError (not opened for reading)
2666
+ # csv.shift
1178
2667
  def shift
1179
- #########################################################################
1180
- ### This method is purposefully kept a bit long as simple conditional ###
1181
- ### checks are faster than numerous (expensive) method calls. ###
1182
- #########################################################################
1183
-
1184
- # handle headers not based on document content
1185
- if header_row? and @return_headers and
1186
- [Array, String].include? @use_headers.class
1187
- if @unconverted_fields
1188
- return add_unconverted_fields(parse_headers, Array.new)
1189
- else
1190
- return parse_headers
1191
- end
2668
+ if @eof_error
2669
+ eof_error, @eof_error = @eof_error, nil
2670
+ raise eof_error
1192
2671
  end
1193
-
1194
- #
1195
- # it can take multiple calls to <tt>@io.gets()</tt> to get a full line,
1196
- # because of \r and/or \n characters embedded in quoted fields
1197
- #
1198
- in_extended_col = false
1199
- csv = Array.new
1200
-
1201
- loop do
1202
- # add another read to the line
1203
- unless parse = @io.gets(@row_sep)
1204
- return nil
1205
- end
1206
-
1207
- if in_extended_col
1208
- @line.concat(parse)
1209
- else
1210
- @line = parse.clone
1211
- end
1212
-
1213
- begin
1214
- parse.sub!(@parsers[:line_end], "")
1215
- rescue ArgumentError
1216
- unless parse.valid_encoding?
1217
- message = "Invalid byte sequence in #{parse.encoding}"
1218
- raise MalformedCSVError.new(message, lineno + 1)
1219
- end
1220
- raise
1221
- end
1222
-
1223
- if csv.empty?
1224
- #
1225
- # I believe a blank line should be an <tt>Array.new</tt>, not Ruby 1.8
1226
- # CSV's <tt>[nil]</tt>
1227
- #
1228
- if parse.empty?
1229
- @lineno += 1
1230
- if @skip_blanks
1231
- next
1232
- elsif @unconverted_fields
1233
- return add_unconverted_fields(Array.new, Array.new)
1234
- elsif @use_headers
1235
- return self.class::Row.new(Array.new, Array.new)
1236
- else
1237
- return Array.new
1238
- end
1239
- end
1240
- end
1241
-
1242
- next if @skip_lines and @skip_lines.match parse
1243
-
1244
- parts = parse.split(@col_sep_split_separator, -1)
1245
- if parts.empty?
1246
- if in_extended_col
1247
- csv[-1] << @col_sep # will be replaced with a @row_sep after the parts.each loop
1248
- else
1249
- csv << nil
1250
- end
1251
- end
1252
-
1253
- # This loop is the hot path of csv parsing. Some things may be non-dry
1254
- # for a reason. Make sure to benchmark when refactoring.
1255
- parts.each do |part|
1256
- if in_extended_col
1257
- # If we are continuing a previous column
1258
- if part.end_with?(@quote_char) && part.count(@quote_char) % 2 != 0
1259
- # extended column ends
1260
- csv.last << part[0..-2]
1261
- if csv.last.match?(@parsers[:stray_quote])
1262
- raise MalformedCSVError.new("Missing or stray quote",
1263
- lineno + 1)
1264
- end
1265
- csv.last.gsub!(@double_quote_char, @quote_char)
1266
- in_extended_col = false
1267
- else
1268
- csv.last << part << @col_sep
1269
- end
1270
- elsif part.start_with?(@quote_char)
1271
- # If we are starting a new quoted column
1272
- if part.count(@quote_char) % 2 != 0
1273
- # start an extended column
1274
- csv << (part[1..-1] << @col_sep)
1275
- in_extended_col = true
1276
- elsif part.end_with?(@quote_char)
1277
- # regular quoted column
1278
- csv << part[1..-2]
1279
- if csv.last.match?(@parsers[:stray_quote])
1280
- raise MalformedCSVError.new("Missing or stray quote",
1281
- lineno + 1)
1282
- end
1283
- csv.last.gsub!(@double_quote_char, @quote_char)
1284
- elsif @liberal_parsing
1285
- csv << part
1286
- else
1287
- raise MalformedCSVError.new("Missing or stray quote",
1288
- lineno + 1)
1289
- end
1290
- elsif part.match?(@parsers[:quote_or_nl])
1291
- # Unquoted field with bad characters.
1292
- if part.match?(@parsers[:nl_or_lf])
1293
- message = "Unquoted fields do not allow \\r or \\n"
1294
- raise MalformedCSVError.new(message, lineno + 1)
1295
- else
1296
- if @liberal_parsing
1297
- csv << part
1298
- else
1299
- raise MalformedCSVError.new("Illegal quoting", lineno + 1)
1300
- end
1301
- end
1302
- else
1303
- # Regular ole unquoted field.
1304
- csv << (part.empty? ? nil : part)
1305
- end
1306
- end
1307
-
1308
- # Replace tacked on @col_sep with @row_sep if we are still in an extended
1309
- # column.
1310
- csv[-1][-1] = @row_sep if in_extended_col
1311
-
1312
- if in_extended_col
1313
- # if we're at eof?(), a quoted field wasn't closed...
1314
- if @io.eof?
1315
- raise MalformedCSVError.new("Unclosed quoted field",
1316
- lineno + 1)
1317
- elsif @field_size_limit and csv.last.size >= @field_size_limit
1318
- raise MalformedCSVError.new("Field size exceeded",
1319
- lineno + 1)
1320
- end
1321
- # otherwise, we need to loop and pull some more data to complete the row
1322
- else
1323
- @lineno += 1
1324
-
1325
- # save fields unconverted fields, if needed...
1326
- unconverted = csv.dup if @unconverted_fields
1327
-
1328
- if @use_headers
1329
- # parse out header rows and handle CSV::Row conversions...
1330
- csv = parse_headers(csv)
1331
- else
1332
- # convert fields, if needed...
1333
- csv = convert_fields(csv)
1334
- end
1335
-
1336
- # inject unconverted fields and accessor, if requested...
1337
- if @unconverted_fields and not csv.respond_to? :unconverted_fields
1338
- add_unconverted_fields(csv, unconverted)
1339
- end
1340
-
1341
- # return the results
1342
- break csv
1343
- end
2672
+ begin
2673
+ parser_enumerator.next
2674
+ rescue StopIteration
2675
+ nil
1344
2676
  end
1345
2677
  end
1346
2678
  alias_method :gets, :shift
1347
2679
  alias_method :readline, :shift
1348
2680
 
2681
+ # :call-seq:
2682
+ # csv.inspect -> string
1349
2683
  #
1350
- # Returns a simplified description of the key CSV attributes in an
1351
- # ASCII compatible String.
1352
- #
2684
+ # Returns a \String showing certain properties of +self+:
2685
+ # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
2686
+ # csv = CSV.new(string, headers: true)
2687
+ # s = csv.inspect
2688
+ # s # => "#<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:\",\" row_sep:\"\\n\" quote_char:\"\\\"\" headers:true>"
1353
2689
  def inspect
1354
- str = ["<#", self.class.to_s, " io_type:"]
2690
+ str = ["#<", self.class.to_s, " io_type:"]
1355
2691
  # show type of wrapped IO
1356
2692
  if @io == $stdout then str << "$stdout"
1357
2693
  elsif @io == $stdin then str << "$stdin"
@@ -1365,15 +2701,18 @@ class CSV
1365
2701
  # show encoding
1366
2702
  str << " encoding:" << @encoding.name
1367
2703
  # show other attributes
1368
- %w[ lineno col_sep row_sep
1369
- quote_char skip_blanks liberal_parsing ].each do |attr_name|
1370
- if a = instance_variable_get("@#{attr_name}")
2704
+ ["lineno", "col_sep", "row_sep", "quote_char"].each do |attr_name|
2705
+ if a = __send__(attr_name)
1371
2706
  str << " " << attr_name << ":" << a.inspect
1372
2707
  end
1373
2708
  end
1374
- if @use_headers
1375
- str << " headers:" << headers.inspect
2709
+ ["skip_blanks", "liberal_parsing"].each do |attr_name|
2710
+ if a = __send__("#{attr_name}?")
2711
+ str << " " << attr_name << ":" << a.inspect
2712
+ end
1376
2713
  end
2714
+ _headers = headers
2715
+ str << " headers:" << _headers.inspect if _headers
1377
2716
  str << ">"
1378
2717
  begin
1379
2718
  str.join('')
@@ -1389,7 +2728,7 @@ class CSV
1389
2728
 
1390
2729
  def determine_encoding(encoding, internal_encoding)
1391
2730
  # honor the IO encoding if we can, otherwise default to ASCII-8BIT
1392
- io_encoding = raw_encoding(nil)
2731
+ io_encoding = raw_encoding
1393
2732
  return io_encoding if io_encoding
1394
2733
 
1395
2734
  return Encoding.find(internal_encoding) if internal_encoding
@@ -1402,354 +2741,111 @@ class CSV
1402
2741
  Encoding.default_internal || Encoding.default_external
1403
2742
  end
1404
2743
 
1405
- #
1406
- # Stores the indicated separators for later use.
1407
- #
1408
- # If auto-discovery was requested for <tt>@row_sep</tt>, this method will read
1409
- # ahead in the <tt>@io</tt> and try to find one. +ARGF+, +STDIN+, +STDOUT+,
1410
- # +STDERR+ and any stream open for output only with a default
1411
- # <tt>@row_sep</tt> of <tt>$INPUT_RECORD_SEPARATOR</tt> (<tt>$/</tt>).
1412
- #
1413
- # This method also establishes the quoting rules used for CSV output.
1414
- #
1415
- def init_separators(col_sep, row_sep, quote_char, force_quotes)
1416
- # store the selected separators
1417
- @col_sep = col_sep.to_s.encode(@encoding)
1418
- if @col_sep == " "
1419
- @col_sep_split_separator = Regexp.new(/#{Regexp.escape(@col_sep)}/)
1420
- else
1421
- @col_sep_split_separator = @col_sep
1422
- end
1423
- @row_sep = row_sep # encode after resolving :auto
1424
- @quote_char = quote_char.to_s.encode(@encoding)
1425
- @double_quote_char = @quote_char * 2
1426
-
1427
- if @quote_char.length != 1
1428
- raise ArgumentError, ":quote_char has to be a single character String"
1429
- end
1430
-
1431
- #
1432
- # automatically discover row separator when requested
1433
- # (not fully encoding safe)
1434
- #
1435
- if @row_sep == :auto
1436
- if [ARGF, STDIN, STDOUT, STDERR].include?(@io) or
1437
- (defined?(Zlib) and @io.class == Zlib::GzipWriter)
1438
- @row_sep = $INPUT_RECORD_SEPARATOR
1439
- else
1440
- begin
1441
- #
1442
- # remember where we were (pos() will raise an exception if @io is pipe
1443
- # or not opened for reading)
1444
- #
1445
- saved_pos = @io.pos
1446
- while @row_sep == :auto
1447
- #
1448
- # if we run out of data, it's probably a single line
1449
- # (ensure will set default value)
1450
- #
1451
- break unless sample = @io.gets(nil, 1024)
1452
-
1453
- cr = encode_str("\r")
1454
- lf = encode_str("\n")
1455
- # extend sample if we're unsure of the line ending
1456
- if sample.end_with?(cr)
1457
- sample << (@io.gets(nil, 1) || "")
1458
- end
1459
-
1460
- # try to find a standard separator
1461
- sample.each_char.each_cons(2) do |char, next_char|
1462
- case char
1463
- when cr
1464
- if next_char == lf
1465
- @row_sep = encode_str("\r\n")
1466
- else
1467
- @row_sep = cr
1468
- end
1469
- break
1470
- when lf
1471
- @row_sep = lf
1472
- break
1473
- end
1474
- end
1475
- end
1476
-
1477
- # tricky seek() clone to work around GzipReader's lack of seek()
1478
- @io.rewind
1479
- # reset back to the remembered position
1480
- while saved_pos > 1024 # avoid loading a lot of data into memory
1481
- @io.read(1024)
1482
- saved_pos -= 1024
1483
- end
1484
- @io.read(saved_pos) if saved_pos.nonzero?
1485
- rescue IOError # not opened for reading
1486
- # do nothing: ensure will set default
1487
- rescue NoMethodError # Zlib::GzipWriter doesn't have some IO methods
1488
- # do nothing: ensure will set default
1489
- rescue SystemCallError # pipe
1490
- # do nothing: ensure will set default
1491
- ensure
1492
- #
1493
- # set default if we failed to detect
1494
- # (stream not opened for reading, a pipe, or a single line of data)
1495
- #
1496
- @row_sep = $INPUT_RECORD_SEPARATOR if @row_sep == :auto
1497
- end
1498
- end
1499
- end
1500
- @row_sep = @row_sep.to_s.encode(@encoding)
1501
-
1502
- # establish quoting rules
1503
- @force_quotes = force_quotes
1504
- do_quote = lambda do |field|
1505
- field = String(field)
1506
- encoded_quote = @quote_char.encode(field.encoding)
1507
- encoded_quote + field.gsub(encoded_quote, encoded_quote * 2) + encoded_quote
2744
+ def normalize_converters(converters)
2745
+ converters ||= []
2746
+ unless converters.is_a?(Array)
2747
+ converters = [converters]
1508
2748
  end
1509
- quotable_chars = encode_str("\r\n", @col_sep, @quote_char)
1510
- @quote = if @force_quotes
1511
- do_quote
1512
- else
1513
- lambda do |field|
1514
- if field.nil? # represent +nil+ fields as empty unquoted fields
1515
- ""
1516
- else
1517
- field = String(field) # Stringify fields
1518
- # represent empty fields as empty quoted fields
1519
- if field.empty? or
1520
- field.count(quotable_chars).nonzero?
1521
- do_quote.call(field)
1522
- else
1523
- field # unquoted field
1524
- end
1525
- end
2749
+ converters.collect do |converter|
2750
+ case converter
2751
+ when Proc # custom code block
2752
+ [nil, converter]
2753
+ else # by name
2754
+ [converter, nil]
1526
2755
  end
1527
2756
  end
1528
2757
  end
1529
2758
 
1530
- # Pre-compiles parsers and stores them by name for access during reads.
1531
- def init_parsers(skip_blanks, field_size_limit, liberal_parsing)
1532
- # store the parser behaviors
1533
- @skip_blanks = skip_blanks
1534
- @field_size_limit = field_size_limit
1535
- @liberal_parsing = liberal_parsing
1536
-
1537
- # prebuild Regexps for faster parsing
1538
- esc_row_sep = escape_re(@row_sep)
1539
- esc_quote = escape_re(@quote_char)
1540
- @parsers = {
1541
- # for detecting parse errors
1542
- quote_or_nl: encode_re("[", esc_quote, "\r\n]"),
1543
- nl_or_lf: encode_re("[\r\n]"),
1544
- stray_quote: encode_re( "[^", esc_quote, "]", esc_quote,
1545
- "[^", esc_quote, "]" ),
1546
- # safer than chomp!()
1547
- line_end: encode_re(esc_row_sep, "\\z"),
1548
- # illegal unquoted characters
1549
- return_newline: encode_str("\r\n")
1550
- }
1551
- end
1552
-
1553
2759
  #
1554
- # Loads any converters requested during construction.
1555
- #
1556
- # If +field_name+ is set <tt>:converters</tt> (the default) field converters
1557
- # are set. When +field_name+ is <tt>:header_converters</tt> header converters
1558
- # are added instead.
1559
- #
1560
- # The <tt>:unconverted_fields</tt> option is also activated for
1561
- # <tt>:converters</tt> calls, if requested.
2760
+ # Processes +fields+ with <tt>@converters</tt>, or <tt>@header_converters</tt>
2761
+ # if +headers+ is passed as +true+, returning the converted field set. Any
2762
+ # converter that changes the field into something other than a String halts
2763
+ # the pipeline of conversion for that field. This is primarily an efficiency
2764
+ # shortcut.
1562
2765
  #
1563
- def init_converters(converters, ivar_name, convert_method)
1564
- converters = case converters
1565
- when nil then []
1566
- when Array then converters
1567
- else [converters]
1568
- end
1569
- instance_variable_set(ivar_name, [])
1570
- convert = method(convert_method)
1571
-
1572
- # load converters
1573
- converters.each do |converter|
1574
- if converter.is_a? Proc # custom code block
1575
- convert.call(&converter)
1576
- else # by name
1577
- convert.call(converter)
1578
- end
2766
+ def convert_fields(fields, headers = false)
2767
+ if headers
2768
+ header_fields_converter.convert(fields, nil, 0)
2769
+ else
2770
+ parser_fields_converter.convert(fields, @headers, lineno)
1579
2771
  end
1580
2772
  end
1581
2773
 
1582
- # Stores the pattern of comments to skip from the provided options.
1583
2774
  #
1584
- # The pattern must respond to +.match+, else ArgumentError is raised.
1585
- # Strings are converted to a Regexp.
2775
+ # Returns the encoding of the internal IO object.
1586
2776
  #
1587
- # See also CSV.new
1588
- def init_comments(skip_lines)
1589
- @skip_lines = skip_lines
1590
- @skip_lines = Regexp.new(Regexp.escape(@skip_lines)) if @skip_lines.is_a? String
1591
- if @skip_lines and not @skip_lines.respond_to?(:match)
1592
- raise ArgumentError, ":skip_lines has to respond to matches"
2777
+ def raw_encoding
2778
+ if @io.respond_to? :internal_encoding
2779
+ @io.internal_encoding || @io.external_encoding
2780
+ elsif @io.respond_to? :encoding
2781
+ @io.encoding
2782
+ else
2783
+ nil
1593
2784
  end
1594
2785
  end
1595
- #
1596
- # The actual work method for adding converters, used by both CSV.convert() and
1597
- # CSV.header_convert().
1598
- #
1599
- # This method requires the +var_name+ of the instance variable to place the
1600
- # converters in, the +const+ Hash to lookup named converters in, and the
1601
- # normal parameters of the CSV.convert() and CSV.header_convert() methods.
1602
- #
1603
- def add_converter(var_name, const, name = nil, &converter)
1604
- if name.nil? # custom converter
1605
- instance_variable_get(var_name) << converter
1606
- else # named converter
1607
- combo = const[name]
1608
- case combo
1609
- when Array # combo converter
1610
- combo.each do |converter_name|
1611
- add_converter(var_name, const, converter_name)
1612
- end
1613
- else # individual named converter
1614
- instance_variable_get(var_name) << combo
1615
- end
1616
- end
2786
+
2787
+ def parser_fields_converter
2788
+ @parser_fields_converter ||= build_parser_fields_converter
1617
2789
  end
1618
2790
 
1619
- #
1620
- # Processes +fields+ with <tt>@converters</tt>, or <tt>@header_converters</tt>
1621
- # if +headers+ is passed as +true+, returning the converted field set. Any
1622
- # converter that changes the field into something other than a String halts
1623
- # the pipeline of conversion for that field. This is primarily an efficiency
1624
- # shortcut.
1625
- #
1626
- def convert_fields(fields, headers = false)
1627
- if headers
1628
- converters = @header_converters
1629
- else
1630
- converters = @converters
1631
- if !@use_headers and
1632
- converters.empty? and
1633
- @nil_value.nil? and
1634
- @empty_value_is_empty_string
1635
- return fields
1636
- end
1637
- end
2791
+ def build_parser_fields_converter
2792
+ specific_options = {
2793
+ builtin_converters_name: :Converters,
2794
+ }
2795
+ options = @base_fields_converter_options.merge(specific_options)
2796
+ build_fields_converter(@initial_converters, options)
2797
+ end
1638
2798
 
1639
- fields.map.with_index do |field, index|
1640
- if field.nil?
1641
- field = @nil_value
1642
- elsif field.empty?
1643
- field = @empty_value unless @empty_value_is_empty_string
1644
- end
1645
- converters.each do |converter|
1646
- break if headers && field.nil?
1647
- field = if converter.arity == 1 # straight field converter
1648
- converter[field]
1649
- else # FieldInfo converter
1650
- header = @use_headers && !headers ? @headers[index] : nil
1651
- converter[field, FieldInfo.new(index, lineno, header)]
1652
- end
1653
- break unless field.is_a? String # short-circuit pipeline for speed
1654
- end
1655
- field # final state of each field, converted or original
1656
- end
2799
+ def header_fields_converter
2800
+ @header_fields_converter ||= build_header_fields_converter
1657
2801
  end
1658
2802
 
1659
- #
1660
- # This method is used to turn a finished +row+ into a CSV::Row. Header rows
1661
- # are also dealt with here, either by returning a CSV::Row with identical
1662
- # headers and fields (save that the fields do not go through the converters)
1663
- # or by reading past them to return a field row. Headers are also saved in
1664
- # <tt>@headers</tt> for use in future rows.
1665
- #
1666
- # When +nil+, +row+ is assumed to be a header row not based on an actual row
1667
- # of the stream.
1668
- #
1669
- def parse_headers(row = nil)
1670
- if @headers.nil? # header row
1671
- @headers = case @use_headers # save headers
1672
- # Array of headers
1673
- when Array then @use_headers
1674
- # CSV header String
1675
- when String
1676
- self.class.parse_line( @use_headers,
1677
- col_sep: @col_sep,
1678
- row_sep: @row_sep,
1679
- quote_char: @quote_char )
1680
- # first row is headers
1681
- else row
1682
- end
1683
-
1684
- # prepare converted and unconverted copies
1685
- row = @headers if row.nil?
1686
- @headers = convert_fields(@headers, true)
1687
- @headers.each { |h| h.freeze if h.is_a? String }
1688
-
1689
- if @return_headers # return headers
1690
- return self.class::Row.new(@headers, row, true)
1691
- elsif not [Array, String].include? @use_headers.class # skip to field row
1692
- return shift
1693
- end
1694
- end
2803
+ def build_header_fields_converter
2804
+ specific_options = {
2805
+ builtin_converters_name: :HeaderConverters,
2806
+ accept_nil: true,
2807
+ }
2808
+ options = @base_fields_converter_options.merge(specific_options)
2809
+ build_fields_converter(@initial_header_converters, options)
2810
+ end
1695
2811
 
1696
- self.class::Row.new(@headers, convert_fields(row)) # field row
2812
+ def writer_fields_converter
2813
+ @writer_fields_converter ||= build_writer_fields_converter
1697
2814
  end
1698
2815
 
1699
- #
1700
- # This method injects an instance variable <tt>unconverted_fields</tt> into
1701
- # +row+ and an accessor method for +row+ called unconverted_fields(). The
1702
- # variable is set to the contents of +fields+.
1703
- #
1704
- def add_unconverted_fields(row, fields)
1705
- class << row
1706
- attr_reader :unconverted_fields
2816
+ def build_writer_fields_converter
2817
+ build_fields_converter(@initial_write_converters,
2818
+ @write_fields_converter_options)
2819
+ end
2820
+
2821
+ def build_fields_converter(initial_converters, options)
2822
+ fields_converter = FieldsConverter.new(options)
2823
+ normalize_converters(initial_converters).each do |name, converter|
2824
+ fields_converter.add_converter(name, &converter)
1707
2825
  end
1708
- row.instance_variable_set(:@unconverted_fields, fields)
1709
- row
2826
+ fields_converter
1710
2827
  end
1711
2828
 
1712
- #
1713
- # This method is an encoding safe version of Regexp::escape(). It will escape
1714
- # any characters that would change the meaning of a regular expression in the
1715
- # encoding of +str+. Regular expression characters that cannot be transcoded
1716
- # to the target encoding will be skipped and no escaping will be performed if
1717
- # a backslash cannot be transcoded.
1718
- #
1719
- def escape_re(str)
1720
- str.gsub(@re_chars) {|c| @re_esc + c}
2829
+ def parser
2830
+ @parser ||= Parser.new(@io, parser_options)
1721
2831
  end
1722
2832
 
1723
- #
1724
- # Builds a regular expression in <tt>@encoding</tt>. All +chunks+ will be
1725
- # transcoded to that encoding.
1726
- #
1727
- def encode_re(*chunks)
1728
- Regexp.new(encode_str(*chunks))
2833
+ def parser_options
2834
+ @parser_options.merge(header_fields_converter: header_fields_converter,
2835
+ fields_converter: parser_fields_converter)
1729
2836
  end
1730
2837
 
1731
- #
1732
- # Builds a String in <tt>@encoding</tt>. All +chunks+ will be transcoded to
1733
- # that encoding.
1734
- #
1735
- def encode_str(*chunks)
1736
- chunks.map { |chunk| chunk.encode(@encoding.name) }.join('')
2838
+ def parser_enumerator
2839
+ @parser_enumerator ||= parser.parse
1737
2840
  end
1738
2841
 
1739
- #
1740
- # Returns the encoding of the internal IO object or the +default+ if the
1741
- # encoding cannot be determined.
1742
- #
1743
- def raw_encoding(default = Encoding::ASCII_8BIT)
1744
- if @io.respond_to? :internal_encoding
1745
- @io.internal_encoding || @io.external_encoding
1746
- elsif @io.is_a? StringIO
1747
- @io.string.encoding
1748
- elsif @io.respond_to? :encoding
1749
- @io.encoding
1750
- else
1751
- default
1752
- end
2842
+ def writer
2843
+ @writer ||= Writer.new(@io, writer_options)
2844
+ end
2845
+
2846
+ def writer_options
2847
+ @writer_options.merge(header_fields_converter: header_fields_converter,
2848
+ fields_converter: writer_fields_converter)
1753
2849
  end
1754
2850
  end
1755
2851
 
@@ -1769,8 +2865,15 @@ end
1769
2865
  # c.read.any? { |a| a.include?("zombies") }
1770
2866
  # } #=> false
1771
2867
  #
1772
- def CSV(*args, &block)
1773
- CSV.instance(*args, &block)
2868
+ # CSV options may also be given.
2869
+ #
2870
+ # io = StringIO.new
2871
+ # CSV(io, col_sep: ";") { |csv| csv << ["a", "b", "c"] }
2872
+ #
2873
+ # This API is not Ractor-safe.
2874
+ #
2875
+ def CSV(*args, **options, &block)
2876
+ CSV.instance(*args, **options, &block)
1774
2877
  end
1775
2878
 
1776
2879
  require_relative "csv/version"