csv 3.1.7 → 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/csv/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
- # Constructs 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 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,43 +123,87 @@ class CSV
48
123
  extend Forwardable
49
124
  def_delegators :@row, :empty?, :length, :size
50
125
 
126
+ # :call-seq:
127
+ # row.initialize_copy(other_row) -> self
128
+ #
129
+ # Calls superclass method.
51
130
  def initialize_copy(other)
52
- super
131
+ super_return_value = super
53
132
  @row = @row.collect(&:dup)
133
+ super_return_value
54
134
  end
55
135
 
56
- # Returns +true+ if this is a header row.
136
+ # :call-seq:
137
+ # row.header_row? -> true or false
138
+ #
139
+ # Returns +true+ if this is a header row, +false+ otherwise.
57
140
  def header_row?
58
141
  @header_row
59
142
  end
60
143
 
61
- # 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.
62
148
  def field_row?
63
149
  not header_row?
64
150
  end
65
151
 
66
- # 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"]
67
160
  def headers
68
161
  @row.map(&:first)
69
162
  end
70
163
 
71
- #
72
164
  # :call-seq:
73
- # field( header )
74
- # field( header, offset )
75
- # 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"
183
+ #
184
+ # Returns +nil+ if +index+ is out of range:
185
+ # row.field(2) # => nil
186
+ # row.field(-3) # => nil
76
187
  #
77
- # This method will return the field value by +header+ or +index+. If a field
78
- # is not found, +nil+ is returned.
188
+ # ---
79
189
  #
80
- # When provided, +offset+ ensures that a header match occurs on or later
81
- # than the +offset+ index. You can use this to find duplicate headers,
82
- # without resorting to hard-coding exact indices.
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"
83
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.
84
203
  def field(header_or_index, minimum_index = 0)
85
204
  # locate the pair
86
205
  finder = (header_or_index.is_a?(Integer) || header_or_index.is_a?(Range)) ? :[] : :assoc
87
- pair = @row[minimum_index..-1].send(finder, header_or_index)
206
+ pair = @row[minimum_index..-1].public_send(finder, header_or_index)
88
207
 
89
208
  # return the field if we have a pair
90
209
  if pair.nil?
@@ -97,16 +216,45 @@ class CSV
97
216
 
98
217
  #
99
218
  # :call-seq:
100
- # fetch( header )
101
- # fetch( header ) { |row| ... }
102
- # fetch( header, default )
103
- #
104
- # This method will fetch the field value by +header+. It has the same
105
- # behavior as Hash#fetch: if there is a field with the given +header+, its
106
- # value is returned. Otherwise, if a block is given, it is yielded the
107
- # +header+ and its result is returned; if a +default+ is given as the
108
- # second argument, it is returned; otherwise a KeyError is raised.
109
- #
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'"
110
258
  def fetch(header, *varargs)
111
259
  raise ArgumentError, "Too many arguments" if varargs.length > 1
112
260
  pair = @row.assoc(header)
@@ -123,7 +271,11 @@ class CSV
123
271
  end
124
272
  end
125
273
 
126
- # 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.
127
279
  def has_key?(header)
128
280
  !!@row.assoc(header)
129
281
  end
@@ -134,17 +286,56 @@ class CSV
134
286
 
135
287
  #
136
288
  # :call-seq:
137
- # []=( header, value )
138
- # []=( header, offset, value )
139
- # []=( index, value )
140
- #
141
- # Looks up the field by the semantics described in CSV::Row.field() and
142
- # assigns the +value+.
143
- #
144
- # Assigning past the end of the row with an index will set all pairs between
145
- # to <tt>[nil, nil]</tt>. Assigning to an unused header appends the new
146
- # pair.
147
- #
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>
148
339
  def []=(*args)
149
340
  value = args.pop
150
341
 
@@ -167,17 +358,34 @@ class CSV
167
358
 
168
359
  #
169
360
  # :call-seq:
170
- # <<( field )
171
- # <<( header_and_field_array )
172
- # <<( header_and_field_hash )
173
- #
174
- # If a two-element Array is provided, it is assumed to be a header and field
175
- # and the pair is appended. A Hash works the same way with the key being
176
- # the header and the value being the field. Anything else is assumed to be
177
- # a lone field which is appended with a +nil+ header.
178
- #
179
- # This method returns the row for chaining.
180
- #
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">
181
389
  def <<(arg)
182
390
  if arg.is_a?(Array) and arg.size == 2 # appending a header and name
183
391
  @row << arg
@@ -190,13 +398,15 @@ class CSV
190
398
  self # for chaining
191
399
  end
192
400
 
193
- #
194
- # A shortcut for appending multiple fields. Equivalent to:
195
- #
196
- # args.each { |arg| csv_row << arg }
197
- #
198
- # This method returns the row for chaining.
199
- #
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">
200
410
  def push(*args)
201
411
  args.each { |arg| self << arg }
202
412
 
@@ -205,14 +415,39 @@ class CSV
205
415
 
206
416
  #
207
417
  # :call-seq:
208
- # delete( header )
209
- # delete( header, offset )
210
- # delete( index )
211
- #
212
- # Removes a pair from the row by +header+ or +index+. The pair is
213
- # located as described in CSV::Row.field(). The deleted pair is returned,
214
- # or +nil+ if a pair could not be found.
215
- #
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) # => []
216
451
  def delete(header_or_index, minimum_index = 0)
217
452
  if header_or_index.is_a? Integer # by index
218
453
  @row.delete_at(header_or_index)
@@ -223,15 +458,21 @@ class CSV
223
458
  end
224
459
  end
225
460
 
461
+ # :call-seq:
462
+ # row.delete_if {|header, value| ... } -> self
226
463
  #
227
- # The provided +block+ is passed a header and field for each pair in the row
228
- # and expected to return +true+ or +false+, depending on whether the pair
229
- # should be deleted.
230
- #
231
- # This method returns the row for chaining.
464
+ # Removes fields from +self+ as selected by the block; returns +self+.
232
465
  #
233
- # 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
234
473
  #
474
+ # If no block is given, returns a new Enumerator:
475
+ # row.delete_if # => #<Enumerator: #<CSV::Row "Name":"Foo">:delete_if>
235
476
  def delete_if(&block)
236
477
  return enum_for(__method__) { size } unless block_given?
237
478
 
@@ -240,14 +481,52 @@ class CSV
240
481
  self # for chaining
241
482
  end
242
483
 
243
- #
244
- # This method accepts any number of arguments which can be headers, indices,
245
- # Ranges of either, or two-element Arrays containing a header and offset.
246
- # Each argument will be replaced with a field lookup as described in
247
- # CSV::Row.field().
248
- #
249
- # If called with no arguments, all fields are returned.
250
- #
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"]
251
530
  def fields(*headers_and_or_indices)
252
531
  if headers_and_or_indices.empty? # return all fields--no arguments
253
532
  @row.map(&:last)
@@ -271,15 +550,26 @@ class CSV
271
550
  end
272
551
  alias_method :values_at, :fields
273
552
 
274
- #
275
553
  # :call-seq:
276
- # index( header )
277
- # index( header, offset )
278
- #
279
- # This method will return the index of a field with the provided +header+.
280
- # The +offset+ can be used to locate duplicate header names, as described in
281
- # CSV::Row.field().
282
- #
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
283
573
  def index(header, minimum_index = 0)
284
574
  # find the pair
285
575
  index = headers[minimum_index..-1].index(header)
@@ -287,24 +577,36 @@ class CSV
287
577
  index.nil? ? nil : index + minimum_index
288
578
  end
289
579
 
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
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
701
+ #
702
+ # Finds and returns the object in nested object that is specified
703
+ # by +index_or_header+ and +specifiers+.
352
704
  #
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.
705
+ # The nested objects may be instances of various classes.
706
+ # See {Dig Methods}[https://docs.ruby-lang.org/en/master/doc/dig_methods_rdoc.html].
355
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,9 +726,17 @@ class CSV
367
726
  end
368
727
  end
369
728
 
370
- #
371
- # A summary of fields, by header, in an ASCII compatible String.
372
- #
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\">"
373
740
  def inspect
374
741
  str = ["#<", self.class.to_s]
375
742
  each do |header, field|