csv 3.0.0 → 3.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +882 -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 +1288 -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 +2433 -1329
  40. metadata +66 -13
  41. data/news.md +0 -123
data/lib/csv/row.rb CHANGED
@@ -3,30 +3,105 @@
3
3
  require "forwardable"
4
4
 
5
5
  class CSV
6
+ # = \CSV::Row
7
+ # A \CSV::Row instance represents a \CSV table row.
8
+ # (see {class CSV}[../CSV.html]).
6
9
  #
7
- # A CSV::Row is part Array and part Hash. It retains an order for the fields
8
- # and allows duplicates just as an Array would, but also allows you to access
9
- # fields by name just as you could if they were in a Hash.
10
+ # The instance may have:
11
+ # - Fields: each is an object, not necessarily a \String.
12
+ # - Headers: each serves a key, and also need not be a \String.
10
13
  #
11
- # All rows returned by CSV will be constructed from this class, if header row
12
- # processing is activated.
14
+ # === Instance Methods
15
+ #
16
+ # \CSV::Row has three groups of instance methods:
17
+ # - Its own internally defined instance methods.
18
+ # - Methods included by module Enumerable.
19
+ # - Methods delegated to class Array.:
20
+ # * Array#empty?
21
+ # * Array#length
22
+ # * Array#size
23
+ #
24
+ # == Creating a \CSV::Row Instance
25
+ #
26
+ # Commonly, a new \CSV::Row instance is created by parsing \CSV source
27
+ # that has headers:
28
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
29
+ # table = CSV.parse(source, headers: true)
30
+ # table.each {|row| p row }
31
+ # Output:
32
+ # #<CSV::Row "Name":"foo" "Value":"0">
33
+ # #<CSV::Row "Name":"bar" "Value":"1">
34
+ # #<CSV::Row "Name":"baz" "Value":"2">
35
+ #
36
+ # You can also create a row directly. See ::new.
37
+ #
38
+ # == Headers
39
+ #
40
+ # Like a \CSV::Table, a \CSV::Row has headers.
41
+ #
42
+ # A \CSV::Row that was created by parsing \CSV source
43
+ # inherits its headers from the table:
44
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
45
+ # table = CSV.parse(source, headers: true)
46
+ # row = table.first
47
+ # row.headers # => ["Name", "Value"]
48
+ #
49
+ # You can also create a new row with headers;
50
+ # like the keys in a \Hash, the headers need not be Strings:
51
+ # row = CSV::Row.new([:name, :value], ['foo', 0])
52
+ # row.headers # => [:name, :value]
53
+ #
54
+ # The new row retains its headers even if added to a table
55
+ # that has headers:
56
+ # table << row # => #<CSV::Table mode:col_or_row row_count:5>
57
+ # row.headers # => [:name, :value]
58
+ # row[:name] # => "foo"
59
+ # row['Name'] # => nil
60
+ #
61
+ #
62
+ #
63
+ # == Accessing Fields
64
+ #
65
+ # You may access a field in a \CSV::Row with either its \Integer index
66
+ # (\Array-style) or its header (\Hash-style).
67
+ #
68
+ # Fetch a field using method #[]:
69
+ # row = CSV::Row.new(['Name', 'Value'], ['foo', 0])
70
+ # row[1] # => 0
71
+ # row['Value'] # => 0
72
+ #
73
+ # Set a field using method #[]=:
74
+ # row = CSV::Row.new(['Name', 'Value'], ['foo', 0])
75
+ # row # => #<CSV::Row "Name":"foo" "Value":0>
76
+ # row[0] = 'bar'
77
+ # row['Value'] = 1
78
+ # row # => #<CSV::Row "Name":"bar" "Value":1>
13
79
  #
14
80
  class Row
15
- #
16
- # Construct a new CSV::Row from +headers+ and +fields+, which are expected
17
- # to be Arrays. If one Array is shorter than the other, it will be padded
18
- # with +nil+ objects.
19
- #
20
- # The optional +header_row+ parameter can be set to +true+ to indicate, via
21
- # CSV::Row.header_row?() and CSV::Row.field_row?(), that this is a header
22
- # row. Otherwise, the row is assumes to be a field row.
23
- #
24
- # A CSV::Row object supports the following Array methods through delegation:
25
- #
26
- # * empty?()
27
- # * length()
28
- # * size()
29
- #
81
+ # :call-seq:
82
+ # CSV::Row.new(headers, fields, header_row = false) -> csv_row
83
+ #
84
+ # Returns the new \CSV::Row instance constructed from
85
+ # arguments +headers+ and +fields+; both should be Arrays;
86
+ # note that the fields need not be Strings:
87
+ # row = CSV::Row.new(['Name', 'Value'], ['foo', 0])
88
+ # row # => #<CSV::Row "Name":"foo" "Value":0>
89
+ #
90
+ # If the \Array lengths are different, the shorter is +nil+-filled:
91
+ # row = CSV::Row.new(['Name', 'Value', 'Date', 'Size'], ['foo', 0])
92
+ # row # => #<CSV::Row "Name":"foo" "Value":0 "Date":nil "Size":nil>
93
+ #
94
+ # Each \CSV::Row object is either a <i>field row</i> or a <i>header row</i>;
95
+ # by default, a new row is a field row; for the row created above:
96
+ # row.field_row? # => true
97
+ # row.header_row? # => false
98
+ #
99
+ # If the optional argument +header_row+ is given as +true+,
100
+ # the created row is a header row:
101
+ # row = CSV::Row.new(['Name', 'Value'], ['foo', 0], header_row = true)
102
+ # row # => #<CSV::Row "Name":"foo" "Value":0>
103
+ # row.field_row? # => false
104
+ # row.header_row? # => true
30
105
  def initialize(headers, fields, header_row = false)
31
106
  @header_row = header_row
32
107
  headers.each { |h| h.freeze if h.is_a? String }
@@ -48,38 +123,87 @@ class CSV
48
123
  extend Forwardable
49
124
  def_delegators :@row, :empty?, :length, :size
50
125
 
51
- # Returns +true+ if this is a header row.
126
+ # :call-seq:
127
+ # row.initialize_copy(other_row) -> self
128
+ #
129
+ # Calls superclass method.
130
+ def initialize_copy(other)
131
+ super_return_value = super
132
+ @row = @row.collect(&:dup)
133
+ super_return_value
134
+ end
135
+
136
+ # :call-seq:
137
+ # row.header_row? -> true or false
138
+ #
139
+ # Returns +true+ if this is a header row, +false+ otherwise.
52
140
  def header_row?
53
141
  @header_row
54
142
  end
55
143
 
56
- # Returns +true+ if this is a field row.
144
+ # :call-seq:
145
+ # row.field_row? -> true or false
146
+ #
147
+ # Returns +true+ if this is a field row, +false+ otherwise.
57
148
  def field_row?
58
149
  not header_row?
59
150
  end
60
151
 
61
- # Returns the headers of this row.
152
+ # :call-seq:
153
+ # row.headers -> array_of_headers
154
+ #
155
+ # Returns the headers for this row:
156
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
157
+ # table = CSV.parse(source, headers: true)
158
+ # row = table.first
159
+ # row.headers # => ["Name", "Value"]
62
160
  def headers
63
161
  @row.map(&:first)
64
162
  end
65
163
 
66
- #
67
164
  # :call-seq:
68
- # field( header )
69
- # field( header, offset )
70
- # field( index )
165
+ # field(index) -> value
166
+ # field(header) -> value
167
+ # field(header, offset) -> value
168
+ #
169
+ # Returns the field value for the given +index+ or +header+.
170
+ #
171
+ # ---
172
+ #
173
+ # Fetch field value by \Integer index:
174
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
175
+ # table = CSV.parse(source, headers: true)
176
+ # row = table[0]
177
+ # row.field(0) # => "foo"
178
+ # row.field(1) # => "bar"
179
+ #
180
+ # Counts backward from the last column if +index+ is negative:
181
+ # row.field(-1) # => "0"
182
+ # row.field(-2) # => "foo"
71
183
  #
72
- # This method will return the field value by +header+ or +index+. If a field
73
- # is not found, +nil+ is returned.
184
+ # Returns +nil+ if +index+ is out of range:
185
+ # row.field(2) # => nil
186
+ # row.field(-3) # => nil
74
187
  #
75
- # When provided, +offset+ ensures that a header match occurs on or later
76
- # than the +offset+ index. You can use this to find duplicate headers,
77
- # without resorting to hard-coding exact indices.
188
+ # ---
78
189
  #
190
+ # Fetch field value by header (first found):
191
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
192
+ # table = CSV.parse(source, headers: true)
193
+ # row = table[0]
194
+ # row.field('Name') # => "Foo"
195
+ #
196
+ # Fetch field value by header, ignoring +offset+ leading fields:
197
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
198
+ # table = CSV.parse(source, headers: true)
199
+ # row = table[0]
200
+ # row.field('Name', 2) # => "Baz"
201
+ #
202
+ # Returns +nil+ if the header does not exist.
79
203
  def field(header_or_index, minimum_index = 0)
80
204
  # locate the pair
81
205
  finder = (header_or_index.is_a?(Integer) || header_or_index.is_a?(Range)) ? :[] : :assoc
82
- pair = @row[minimum_index..-1].send(finder, header_or_index)
206
+ pair = @row[minimum_index..-1].public_send(finder, header_or_index)
83
207
 
84
208
  # return the field if we have a pair
85
209
  if pair.nil?
@@ -92,16 +216,45 @@ class CSV
92
216
 
93
217
  #
94
218
  # :call-seq:
95
- # fetch( header )
96
- # fetch( header ) { |row| ... }
97
- # fetch( header, default )
98
- #
99
- # This method will fetch the field value by +header+. It has the same
100
- # behavior as Hash#fetch: if there is a field with the given +header+, its
101
- # value is returned. Otherwise, if a block is given, it is yielded the
102
- # +header+ and its result is returned; if a +default+ is given as the
103
- # second argument, it is returned; otherwise a KeyError is raised.
104
- #
219
+ # fetch(header) -> value
220
+ # fetch(header, default) -> value
221
+ # fetch(header) {|row| ... } -> value
222
+ #
223
+ # Returns the field value as specified by +header+.
224
+ #
225
+ # ---
226
+ #
227
+ # With the single argument +header+, returns the field value
228
+ # for that header (first found):
229
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
230
+ # table = CSV.parse(source, headers: true)
231
+ # row = table[0]
232
+ # row.fetch('Name') # => "Foo"
233
+ #
234
+ # Raises exception +KeyError+ if the header does not exist.
235
+ #
236
+ # ---
237
+ #
238
+ # With arguments +header+ and +default+ given,
239
+ # returns the field value for the header (first found)
240
+ # if the header exists, otherwise returns +default+:
241
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
242
+ # table = CSV.parse(source, headers: true)
243
+ # row = table[0]
244
+ # row.fetch('Name', '') # => "Foo"
245
+ # row.fetch(:nosuch, '') # => ""
246
+ #
247
+ # ---
248
+ #
249
+ # With argument +header+ and a block given,
250
+ # returns the field value for the header (first found)
251
+ # if the header exists; otherwise calls the block
252
+ # and returns its return value:
253
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
254
+ # table = CSV.parse(source, headers: true)
255
+ # row = table[0]
256
+ # row.fetch('Name') {|header| fail 'Cannot happen' } # => "Foo"
257
+ # row.fetch(:nosuch) {|header| "Header '#{header} not found'" } # => "Header 'nosuch not found'"
105
258
  def fetch(header, *varargs)
106
259
  raise ArgumentError, "Too many arguments" if varargs.length > 1
107
260
  pair = @row.assoc(header)
@@ -118,27 +271,71 @@ class CSV
118
271
  end
119
272
  end
120
273
 
121
- # Returns +true+ if there is a field with the given +header+.
274
+ # :call-seq:
275
+ # row.has_key?(header) -> true or false
276
+ #
277
+ # Returns +true+ if there is a field with the given +header+,
278
+ # +false+ otherwise.
122
279
  def has_key?(header)
123
280
  !!@row.assoc(header)
124
281
  end
125
282
  alias_method :include?, :has_key?
126
283
  alias_method :key?, :has_key?
127
284
  alias_method :member?, :has_key?
285
+ alias_method :header?, :has_key?
128
286
 
129
287
  #
130
288
  # :call-seq:
131
- # []=( header, value )
132
- # []=( header, offset, value )
133
- # []=( index, value )
134
- #
135
- # Looks up the field by the semantics described in CSV::Row.field() and
136
- # assigns the +value+.
137
- #
138
- # Assigning past the end of the row with an index will set all pairs between
139
- # to <tt>[nil, nil]</tt>. Assigning to an unused header appends the new
140
- # pair.
141
- #
289
+ # row[index] = value -> value
290
+ # row[header, offset] = value -> value
291
+ # row[header] = value -> value
292
+ #
293
+ # Assigns the field value for the given +index+ or +header+;
294
+ # returns +value+.
295
+ #
296
+ # ---
297
+ #
298
+ # Assign field value by \Integer index:
299
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
300
+ # table = CSV.parse(source, headers: true)
301
+ # row = table[0]
302
+ # row[0] = 'Bat'
303
+ # row[1] = 3
304
+ # row # => #<CSV::Row "Name":"Bat" "Value":3>
305
+ #
306
+ # Counts backward from the last column if +index+ is negative:
307
+ # row[-1] = 4
308
+ # row[-2] = 'Bam'
309
+ # row # => #<CSV::Row "Name":"Bam" "Value":4>
310
+ #
311
+ # Extends the row with <tt>nil:nil</tt> if positive +index+ is not in the row:
312
+ # row[4] = 5
313
+ # row # => #<CSV::Row "Name":"bad" "Value":4 nil:nil nil:nil nil:5>
314
+ #
315
+ # Raises IndexError if negative +index+ is too small (too far from zero).
316
+ #
317
+ # ---
318
+ #
319
+ # Assign field value by header (first found):
320
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
321
+ # table = CSV.parse(source, headers: true)
322
+ # row = table[0]
323
+ # row['Name'] = 'Bat'
324
+ # row # => #<CSV::Row "Name":"Bat" "Name":"Bar" "Name":"Baz">
325
+ #
326
+ # Assign field value by header, ignoring +offset+ leading fields:
327
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
328
+ # table = CSV.parse(source, headers: true)
329
+ # row = table[0]
330
+ # row['Name', 2] = 4
331
+ # row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":4>
332
+ #
333
+ # Append new field by (new) header:
334
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
335
+ # table = CSV.parse(source, headers: true)
336
+ # row = table[0]
337
+ # row['New'] = 6
338
+ # row# => #<CSV::Row "Name":"foo" "Value":"0" "New":6>
142
339
  def []=(*args)
143
340
  value = args.pop
144
341
 
@@ -161,17 +358,34 @@ class CSV
161
358
 
162
359
  #
163
360
  # :call-seq:
164
- # <<( field )
165
- # <<( header_and_field_array )
166
- # <<( header_and_field_hash )
167
- #
168
- # If a two-element Array is provided, it is assumed to be a header and field
169
- # and the pair is appended. A Hash works the same way with the key being
170
- # the header and the value being the field. Anything else is assumed to be
171
- # a lone field which is appended with a +nil+ header.
172
- #
173
- # This method returns the row for chaining.
174
- #
361
+ # row << [header, value] -> self
362
+ # row << hash -> self
363
+ # row << value -> self
364
+ #
365
+ # Adds a field to +self+; returns +self+:
366
+ #
367
+ # If the argument is a 2-element \Array <tt>[header, value]</tt>,
368
+ # a field is added with the given +header+ and +value+:
369
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
370
+ # table = CSV.parse(source, headers: true)
371
+ # row = table[0]
372
+ # row << ['NAME', 'Bat']
373
+ # row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz" "NAME":"Bat">
374
+ #
375
+ # If the argument is a \Hash, each <tt>key-value</tt> pair is added
376
+ # as a field with header +key+ and value +value+.
377
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
378
+ # table = CSV.parse(source, headers: true)
379
+ # row = table[0]
380
+ # row << {NAME: 'Bat', name: 'Bam'}
381
+ # row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz" NAME:"Bat" name:"Bam">
382
+ #
383
+ # Otherwise, the given +value+ is added as a field with no header.
384
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
385
+ # table = CSV.parse(source, headers: true)
386
+ # row = table[0]
387
+ # row << 'Bag'
388
+ # row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz" nil:"Bag">
175
389
  def <<(arg)
176
390
  if arg.is_a?(Array) and arg.size == 2 # appending a header and name
177
391
  @row << arg
@@ -184,13 +398,15 @@ class CSV
184
398
  self # for chaining
185
399
  end
186
400
 
187
- #
188
- # A shortcut for appending multiple fields. Equivalent to:
189
- #
190
- # args.each { |arg| csv_row << arg }
191
- #
192
- # This method returns the row for chaining.
193
- #
401
+ # :call-seq:
402
+ # row.push(*values) -> self
403
+ #
404
+ # Appends each of the given +values+ to +self+ as a field; returns +self+:
405
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
406
+ # table = CSV.parse(source, headers: true)
407
+ # row = table[0]
408
+ # row.push('Bat', 'Bam')
409
+ # row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz" nil:"Bat" nil:"Bam">
194
410
  def push(*args)
195
411
  args.each { |arg| self << arg }
196
412
 
@@ -199,14 +415,39 @@ class CSV
199
415
 
200
416
  #
201
417
  # :call-seq:
202
- # delete( header )
203
- # delete( header, offset )
204
- # delete( index )
205
- #
206
- # Used to remove a pair from the row by +header+ or +index+. The pair is
207
- # located as described in CSV::Row.field(). The deleted pair is returned,
208
- # or +nil+ if a pair could not be found.
209
- #
418
+ # delete(index) -> [header, value] or nil
419
+ # delete(header) -> [header, value] or empty_array
420
+ # delete(header, offset) -> [header, value] or empty_array
421
+ #
422
+ # Removes a specified field from +self+; returns the 2-element \Array
423
+ # <tt>[header, value]</tt> if the field exists.
424
+ #
425
+ # If an \Integer argument +index+ is given,
426
+ # removes and returns the field at offset +index+,
427
+ # or returns +nil+ if the field does not exist:
428
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
429
+ # table = CSV.parse(source, headers: true)
430
+ # row = table[0]
431
+ # row.delete(1) # => ["Name", "Bar"]
432
+ # row.delete(50) # => nil
433
+ #
434
+ # Otherwise, if the single argument +header+ is given,
435
+ # removes and returns the first-found field with the given header,
436
+ # of returns a new empty \Array if the field does not exist:
437
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
438
+ # table = CSV.parse(source, headers: true)
439
+ # row = table[0]
440
+ # row.delete('Name') # => ["Name", "Foo"]
441
+ # row.delete('NAME') # => []
442
+ #
443
+ # If argument +header+ and \Integer argument +offset+ are given,
444
+ # removes and returns the first-found field with the given header
445
+ # whose +index+ is at least as large as +offset+:
446
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
447
+ # table = CSV.parse(source, headers: true)
448
+ # row = table[0]
449
+ # row.delete('Name', 1) # => ["Name", "Bar"]
450
+ # row.delete('NAME', 1) # => []
210
451
  def delete(header_or_index, minimum_index = 0)
211
452
  if header_or_index.is_a? Integer # by index
212
453
  @row.delete_at(header_or_index)
@@ -217,15 +458,21 @@ class CSV
217
458
  end
218
459
  end
219
460
 
461
+ # :call-seq:
462
+ # row.delete_if {|header, value| ... } -> self
220
463
  #
221
- # The provided +block+ is passed a header and field for each pair in the row
222
- # and expected to return +true+ or +false+, depending on whether the pair
223
- # should be deleted.
224
- #
225
- # This method returns the row for chaining.
464
+ # Removes fields from +self+ as selected by the block; returns +self+.
226
465
  #
227
- # If no block is given, an Enumerator is returned.
466
+ # Removes each field for which the block returns a truthy value:
467
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
468
+ # table = CSV.parse(source, headers: true)
469
+ # row = table[0]
470
+ # row.delete_if {|header, value| value.start_with?('B') } # => true
471
+ # row # => #<CSV::Row "Name":"Foo">
472
+ # row.delete_if {|header, value| header.start_with?('B') } # => false
228
473
  #
474
+ # If no block is given, returns a new Enumerator:
475
+ # row.delete_if # => #<Enumerator: #<CSV::Row "Name":"Foo">:delete_if>
229
476
  def delete_if(&block)
230
477
  return enum_for(__method__) { size } unless block_given?
231
478
 
@@ -234,14 +481,52 @@ class CSV
234
481
  self # for chaining
235
482
  end
236
483
 
237
- #
238
- # This method accepts any number of arguments which can be headers, indices,
239
- # Ranges of either, or two-element Arrays containing a header and offset.
240
- # Each argument will be replaced with a field lookup as described in
241
- # CSV::Row.field().
242
- #
243
- # If called with no arguments, all fields are returned.
244
- #
484
+ # :call-seq:
485
+ # self.fields(*specifiers) -> array_of_fields
486
+ #
487
+ # Returns field values per the given +specifiers+, which may be any mixture of:
488
+ # - \Integer index.
489
+ # - \Range of \Integer indexes.
490
+ # - 2-element \Array containing a header and offset.
491
+ # - Header.
492
+ # - \Range of headers.
493
+ #
494
+ # For +specifier+ in one of the first four cases above,
495
+ # returns the result of <tt>self.field(specifier)</tt>; see #field.
496
+ #
497
+ # Although there may be any number of +specifiers+,
498
+ # the examples here will illustrate one at a time.
499
+ #
500
+ # When the specifier is an \Integer +index+,
501
+ # returns <tt>self.field(index)</tt>L
502
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
503
+ # table = CSV.parse(source, headers: true)
504
+ # row = table[0]
505
+ # row.fields(1) # => ["Bar"]
506
+ #
507
+ # When the specifier is a \Range of \Integers +range+,
508
+ # returns <tt>self.field(range)</tt>:
509
+ # row.fields(1..2) # => ["Bar", "Baz"]
510
+ #
511
+ # When the specifier is a 2-element \Array +array+,
512
+ # returns <tt>self.field(array)</tt>L
513
+ # row.fields('Name', 1) # => ["Foo", "Bar"]
514
+ #
515
+ # When the specifier is a header +header+,
516
+ # returns <tt>self.field(header)</tt>L
517
+ # row.fields('Name') # => ["Foo"]
518
+ #
519
+ # When the specifier is a \Range of headers +range+,
520
+ # forms a new \Range +new_range+ from the indexes of
521
+ # <tt>range.start</tt> and <tt>range.end</tt>,
522
+ # and returns <tt>self.field(new_range)</tt>:
523
+ # source = "Name,NAME,name\nFoo,Bar,Baz\n"
524
+ # table = CSV.parse(source, headers: true)
525
+ # row = table[0]
526
+ # row.fields('Name'..'NAME') # => ["Foo", "Bar"]
527
+ #
528
+ # Returns all fields if no argument given:
529
+ # row.fields # => ["Foo", "Bar", "Baz"]
245
530
  def fields(*headers_and_or_indices)
246
531
  if headers_and_or_indices.empty? # return all fields--no arguments
247
532
  @row.map(&:last)
@@ -265,15 +550,26 @@ class CSV
265
550
  end
266
551
  alias_method :values_at, :fields
267
552
 
268
- #
269
553
  # :call-seq:
270
- # index( header )
271
- # index( header, offset )
272
- #
273
- # This method will return the index of a field with the provided +header+.
274
- # The +offset+ can be used to locate duplicate header names, as described in
275
- # CSV::Row.field().
276
- #
554
+ # index(header) -> index
555
+ # index(header, offset) -> index
556
+ #
557
+ # Returns the index for the given header, if it exists;
558
+ # otherwise returns +nil+.
559
+ #
560
+ # With the single argument +header+, returns the index
561
+ # of the first-found field with the given +header+:
562
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
563
+ # table = CSV.parse(source, headers: true)
564
+ # row = table[0]
565
+ # row.index('Name') # => 0
566
+ # row.index('NAME') # => nil
567
+ #
568
+ # With arguments +header+ and +offset+,
569
+ # returns the index of the first-found field with given +header+,
570
+ # but ignoring the first +offset+ fields:
571
+ # row.index('Name', 1) # => 1
572
+ # row.index('Name', 3) # => nil
277
573
  def index(header, minimum_index = 0)
278
574
  # find the pair
279
575
  index = headers[minimum_index..-1].index(header)
@@ -281,30 +577,36 @@ class CSV
281
577
  index.nil? ? nil : index + minimum_index
282
578
  end
283
579
 
284
- # Returns +true+ if +name+ is a header for this row, and +false+ otherwise.
285
- def header?(name)
286
- headers.include? name
287
- end
288
- alias_method :include?, :header?
289
-
290
- #
291
- # Returns +true+ if +data+ matches a field in this row, and +false+
292
- # otherwise.
293
- #
580
+ # :call-seq:
581
+ # row.field?(value) -> true or false
582
+ #
583
+ # Returns +true+ if +value+ is a field in this row, +false+ otherwise:
584
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
585
+ # table = CSV.parse(source, headers: true)
586
+ # row = table[0]
587
+ # row.field?('Bar') # => true
588
+ # row.field?('BAR') # => false
294
589
  def field?(data)
295
590
  fields.include? data
296
591
  end
297
592
 
298
593
  include Enumerable
299
594
 
300
- #
301
- # Yields each pair of the row as header and field tuples (much like
302
- # iterating over a Hash). This method returns the row for chaining.
303
- #
304
- # If no block is given, an Enumerator is returned.
305
- #
306
- # Support for Enumerable.
307
- #
595
+ # :call-seq:
596
+ # row.each {|header, value| ... } -> self
597
+ #
598
+ # Calls the block with each header-value pair; returns +self+:
599
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
600
+ # table = CSV.parse(source, headers: true)
601
+ # row = table[0]
602
+ # row.each {|header, value| p [header, value] }
603
+ # Output:
604
+ # ["Name", "Foo"]
605
+ # ["Name", "Bar"]
606
+ # ["Name", "Baz"]
607
+ #
608
+ # If no block is given, returns a new Enumerator:
609
+ # row.each # => #<Enumerator: #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz">:each>
308
610
  def each(&block)
309
611
  return enum_for(__method__) { size } unless block_given?
310
612
 
@@ -315,19 +617,39 @@ class CSV
315
617
 
316
618
  alias_method :each_pair, :each
317
619
 
318
- #
319
- # Returns +true+ if this row contains the same headers and fields in the
320
- # same order as +other+.
321
- #
620
+ # :call-seq:
621
+ # row == other -> true or false
622
+ #
623
+ # Returns +true+ if +other+ is a /CSV::Row that has the same
624
+ # fields (headers and values) in the same order as +self+;
625
+ # otherwise returns +false+:
626
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
627
+ # table = CSV.parse(source, headers: true)
628
+ # row = table[0]
629
+ # other_row = table[0]
630
+ # row == other_row # => true
631
+ # other_row = table[1]
632
+ # row == other_row # => false
322
633
  def ==(other)
323
634
  return @row == other.row if other.is_a? CSV::Row
324
635
  @row == other
325
636
  end
326
637
 
327
- #
328
- # Collapses the row into a simple Hash. Be warned that this discards field
329
- # order and clobbers duplicate fields.
330
- #
638
+ # :call-seq:
639
+ # row.to_h -> hash
640
+ #
641
+ # Returns the new \Hash formed by adding each header-value pair in +self+
642
+ # as a key-value pair in the \Hash.
643
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
644
+ # table = CSV.parse(source, headers: true)
645
+ # row = table[0]
646
+ # row.to_h # => {"Name"=>"foo", "Value"=>"0"}
647
+ #
648
+ # Header order is preserved, but repeated headers are ignored:
649
+ # source = "Name,Name,Name\nFoo,Bar,Baz\n"
650
+ # table = CSV.parse(source, headers: true)
651
+ # row = table[0]
652
+ # row.to_h # => {"Name"=>"Foo"}
331
653
  def to_h
332
654
  hash = {}
333
655
  each do |key, _value|
@@ -337,22 +659,59 @@ class CSV
337
659
  end
338
660
  alias_method :to_hash, :to_h
339
661
 
662
+ # :call-seq:
663
+ # row.deconstruct_keys(keys) -> hash
664
+ #
665
+ # Returns the new \Hash suitable for pattern matching containing only the
666
+ # keys specified as an argument.
667
+ def deconstruct_keys(keys)
668
+ if keys.nil?
669
+ to_h
670
+ else
671
+ keys.to_h { |key| [key, self[key]] }
672
+ end
673
+ end
674
+
340
675
  alias_method :to_ary, :to_a
341
676
 
677
+ # :call-seq:
678
+ # row.deconstruct -> array
342
679
  #
343
- # Returns the row as a CSV String. Headers are not used. Equivalent to:
344
- #
345
- # csv_row.fields.to_csv( options )
680
+ # Returns the new \Array suitable for pattern matching containing the values
681
+ # of the row.
682
+ def deconstruct
683
+ fields
684
+ end
685
+
686
+ # :call-seq:
687
+ # row.to_csv -> csv_string
346
688
  #
689
+ # Returns the row as a \CSV String. Headers are not included:
690
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
691
+ # table = CSV.parse(source, headers: true)
692
+ # row = table[0]
693
+ # row.to_csv # => "foo,0\n"
347
694
  def to_csv(**options)
348
- fields.to_csv(options)
695
+ fields.to_csv(**options)
349
696
  end
350
697
  alias_method :to_s, :to_csv
351
698
 
699
+ # :call-seq:
700
+ # row.dig(index_or_header, *identifiers) -> object
352
701
  #
353
- # Extracts the nested value specified by the sequence of +index+ or +header+ objects by calling dig at each step,
354
- # returning nil if any intermediate step is nil.
702
+ # Finds and returns the object in nested object that is specified
703
+ # by +index_or_header+ and +specifiers+.
355
704
  #
705
+ # The nested objects may be instances of various classes.
706
+ # See {Dig Methods}[rdoc-ref:dig_methods.rdoc].
707
+ #
708
+ # Examples:
709
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
710
+ # table = CSV.parse(source, headers: true)
711
+ # row = table[0]
712
+ # row.dig(1) # => "0"
713
+ # row.dig('Value') # => "0"
714
+ # row.dig(5) # => nil
356
715
  def dig(index_or_header, *indexes)
357
716
  value = field(index_or_header)
358
717
  if value.nil?
@@ -367,7 +726,17 @@ class CSV
367
726
  end
368
727
  end
369
728
 
370
- # A summary of fields, by header, in an ASCII compatible String.
729
+ # :call-seq:
730
+ # row.inspect -> string
731
+ #
732
+ # Returns an ASCII-compatible \String showing:
733
+ # - Class \CSV::Row.
734
+ # - Header-value pairs.
735
+ # Example:
736
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
737
+ # table = CSV.parse(source, headers: true)
738
+ # row = table[0]
739
+ # row.inspect # => "#<CSV::Row \"Name\":\"foo\" \"Value\":\"0\">"
371
740
  def inspect
372
741
  str = ["#<", self.class.to_s]
373
742
  each do |header, field|