csv 3.1.7 → 3.1.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.
@@ -0,0 +1,6 @@
1
+ == Recipes for \CSV
2
+
3
+ The recipes are specific code examples for specific tasks. See:
4
+ - {Recipes for Parsing CSV}[./parsing_rdoc.html]
5
+ - {Recipes for Generating CSV}[./generating_rdoc.html]
6
+ - {Recipes for Filtering CSV}[./filtering_rdoc.html]
data/lib/csv.rb CHANGED
@@ -104,6 +104,17 @@ require_relative "csv/writer"
104
104
  using CSV::MatchP if CSV.const_defined?(:MatchP)
105
105
 
106
106
  # == \CSV
107
+ #
108
+ # === In a Hurry?
109
+ #
110
+ # If you are familiar with \CSV data and have a particular task in mind,
111
+ # you may want to go directly to the:
112
+ # - {Recipes for CSV}[doc/csv/recipes/recipes_rdoc.html].
113
+ #
114
+ # Otherwise, read on here, about the API: classes, methods, and constants.
115
+ #
116
+ # === \CSV Data
117
+ #
107
118
  # \CSV (comma-separated values) data is a text representation of a table:
108
119
  # - A _row_ _separator_ delimits table rows.
109
120
  # A common row separator is the newline character <tt>"\n"</tt>.
@@ -117,6 +128,11 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
117
128
  #
118
129
  # Despite the name \CSV, a \CSV representation can use different separators.
119
130
  #
131
+ # For more about tables, see the Wikipedia article
132
+ # "{Table (information)}[https://en.wikipedia.org/wiki/Table_(information)]",
133
+ # especially its section
134
+ # "{Simple table}[https://en.wikipedia.org/wiki/Table_(information)#Simple_table]"
135
+ #
120
136
  # == \Class \CSV
121
137
  #
122
138
  # Class \CSV provides methods for:
@@ -674,12 +690,15 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
674
690
  #
675
691
  # You can define a custom field converter:
676
692
  # strip_converter = proc {|field| field.strip }
677
- # Add it to the \Converters \Hash:
678
- # CSV::Converters[:strip] = strip_converter
679
- # Use it by name:
680
693
  # string = " foo , 0 \n bar , 1 \n baz , 2 \n"
681
694
  # array = CSV.parse(string, converters: strip_converter)
682
695
  # array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
696
+ # You can register the converter in \Converters \Hash,
697
+ # which allows you to refer to it by name:
698
+ # CSV::Converters[:strip] = strip_converter
699
+ # string = " foo , 0 \n bar , 1 \n baz , 2 \n"
700
+ # array = CSV.parse(string, converters: :strip)
701
+ # array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
683
702
  #
684
703
  # ==== Header \Converters
685
704
  #
@@ -737,13 +756,16 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
737
756
  #
738
757
  # You can define a custom header converter:
739
758
  # upcase_converter = proc {|header| header.upcase }
740
- # Add it to the \HeaderConverters \Hash:
741
- # CSV::HeaderConverters[:upcase] = upcase_converter
742
- # Use it by name:
743
759
  # string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
744
- # table = CSV.parse(string, headers: true, converters: upcase_converter)
760
+ # table = CSV.parse(string, headers: true, header_converters: upcase_converter)
761
+ # table # => #<CSV::Table mode:col_or_row row_count:4>
762
+ # table.headers # => ["NAME", "VALUE"]
763
+ # You can register the converter in \HeaderConverters \Hash,
764
+ # which allows you to refer to it by name:
765
+ # CSV::HeaderConverters[:upcase] = upcase_converter
766
+ # table = CSV.parse(string, headers: true, header_converters: :upcase)
745
767
  # table # => #<CSV::Table mode:col_or_row row_count:4>
746
- # table.headers # => ["Name", "Value"]
768
+ # table.headers # => ["NAME", "VALUE"]
747
769
  #
748
770
  # ===== Write \Converters
749
771
  #
@@ -752,23 +774,23 @@ using CSV::MatchP if CSV.const_defined?(:MatchP)
752
774
  # its return value becomes the new value for the field.
753
775
  # A converter might, for example, strip whitespace from a field.
754
776
  #
755
- # - Using no write converter (all fields unmodified):
756
- # output_string = CSV.generate do |csv|
757
- # csv << [' foo ', 0]
758
- # csv << [' bar ', 1]
759
- # csv << [' baz ', 2]
760
- # end
761
- # output_string # => " foo ,0\n bar ,1\n baz ,2\n"
762
- # - Using option +write_converters+:
763
- # strip_converter = proc {|field| field.respond_to?(:strip) ? field.strip : field }
764
- # upcase_converter = proc {|field| field.respond_to?(:upcase) ? field.upcase : field }
765
- # converters = [strip_converter, upcase_converter]
766
- # output_string = CSV.generate(write_converters: converters) do |csv|
767
- # csv << [' foo ', 0]
768
- # csv << [' bar ', 1]
769
- # csv << [' baz ', 2]
770
- # end
771
- # output_string # => "FOO,0\nBAR,1\nBAZ,2\n"
777
+ # Using no write converter (all fields unmodified):
778
+ # output_string = CSV.generate do |csv|
779
+ # csv << [' foo ', 0]
780
+ # csv << [' bar ', 1]
781
+ # csv << [' baz ', 2]
782
+ # end
783
+ # output_string # => " foo ,0\n bar ,1\n baz ,2\n"
784
+ # Using option +write_converters+ with two custom write converters:
785
+ # strip_converter = proc {|field| field.respond_to?(:strip) ? field.strip : field }
786
+ # upcase_converter = proc {|field| field.respond_to?(:upcase) ? field.upcase : field }
787
+ # write_converters = [strip_converter, upcase_converter]
788
+ # output_string = CSV.generate(write_converters: write_converters) do |csv|
789
+ # csv << [' foo ', 0]
790
+ # csv << [' bar ', 1]
791
+ # csv << [' baz ', 2]
792
+ # end
793
+ # output_string # => "FOO,0\nBAR,1\nBAZ,2\n"
772
794
  #
773
795
  # === Character Encodings (M17n or Multilingualization)
774
796
  #
@@ -1052,10 +1074,29 @@ class CSV
1052
1074
  out_options[key] = value
1053
1075
  end
1054
1076
  end
1077
+
1055
1078
  # build input and output wrappers
1056
- input = new(input || ARGF, **in_options)
1079
+ input = new(input || ARGF, **in_options)
1057
1080
  output = new(output || $stdout, **out_options)
1058
1081
 
1082
+ # process headers
1083
+ need_manual_header_output =
1084
+ (in_options[:headers] and
1085
+ out_options[:headers] == true and
1086
+ out_options[:write_headers])
1087
+ if need_manual_header_output
1088
+ first_row = input.shift
1089
+ if first_row
1090
+ if first_row.is_a?(Row)
1091
+ headers = first_row.headers
1092
+ yield headers
1093
+ output << headers
1094
+ end
1095
+ yield first_row
1096
+ output << first_row
1097
+ end
1098
+ end
1099
+
1059
1100
  # read, yield, write
1060
1101
  input.each do |row|
1061
1102
  yield row
@@ -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,39 +123,83 @@ 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
+ # ---
76
172
  #
77
- # This method will return the field value by +header+ or +index+. If a field
78
- # is not found, +nil+ is returned.
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"
79
179
  #
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.
180
+ # Counts backward from the last column if +index+ is negative:
181
+ # row.field(-1) # => "0"
182
+ # row.field(-2) # => "foo"
83
183
  #
184
+ # Returns +nil+ if +index+ is out of range:
185
+ # row.field(2) # => nil
186
+ # row.field(-3) # => nil
187
+ #
188
+ # ---
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.
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
@@ -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|
@@ -339,20 +661,35 @@ class CSV
339
661
 
340
662
  alias_method :to_ary, :to_a
341
663
 
664
+ # :call-seq:
665
+ # row.to_csv -> csv_string
342
666
  #
343
- # Returns the row as a CSV String. Headers are not used. Equivalent to:
344
- #
345
- # csv_row.fields.to_csv( options )
346
- #
667
+ # Returns the row as a \CSV String. Headers are not included:
668
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
669
+ # table = CSV.parse(source, headers: true)
670
+ # row = table[0]
671
+ # row.to_csv # => "foo,0\n"
347
672
  def to_csv(**options)
348
673
  fields.to_csv(**options)
349
674
  end
350
675
  alias_method :to_s, :to_csv
351
676
 
677
+ # :call-seq:
678
+ # row.dig(index_or_header, *identifiers) -> object
679
+ #
680
+ # Finds and returns the object in nested object that is specified
681
+ # by +index_or_header+ and +specifiers+.
352
682
  #
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.
683
+ # The nested objects may be instances of various classes.
684
+ # See {Dig Methods}[https://docs.ruby-lang.org/en/master/doc/dig_methods_rdoc.html].
355
685
  #
686
+ # Examples:
687
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
688
+ # table = CSV.parse(source, headers: true)
689
+ # row = table[0]
690
+ # row.dig(1) # => "0"
691
+ # row.dig('Value') # => "0"
692
+ # row.dig(5) # => nil
356
693
  def dig(index_or_header, *indexes)
357
694
  value = field(index_or_header)
358
695
  if value.nil?
@@ -367,9 +704,17 @@ class CSV
367
704
  end
368
705
  end
369
706
 
370
- #
371
- # A summary of fields, by header, in an ASCII compatible String.
372
- #
707
+ # :call-seq:
708
+ # row.inspect -> string
709
+ #
710
+ # Returns an ASCII-compatible \String showing:
711
+ # - Class \CSV::Row.
712
+ # - Header-value pairs.
713
+ # Example:
714
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
715
+ # table = CSV.parse(source, headers: true)
716
+ # row = table[0]
717
+ # row.inspect # => "#<CSV::Row \"Name\":\"foo\" \"Value\":\"0\">"
373
718
  def inspect
374
719
  str = ["#<", self.class.to_s]
375
720
  each do |header, field|