csv 3.1.7 → 3.2.1
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.
- checksums.yaml +4 -4
- data/NEWS.md +81 -0
- data/README.md +5 -3
- data/doc/csv/options/common/col_sep.rdoc +1 -7
- data/doc/csv/options/common/row_sep.rdoc +0 -9
- data/doc/csv/options/generating/write_converters.rdoc +0 -8
- data/doc/csv/recipes/filtering.rdoc +158 -0
- data/doc/csv/recipes/generating.rdoc +298 -0
- data/doc/csv/recipes/parsing.rdoc +545 -0
- data/doc/csv/recipes/recipes.rdoc +6 -0
- data/lib/csv/fields_converter.rb +6 -2
- data/lib/csv/input_record_separator.rb +31 -0
- data/lib/csv/parser.rb +13 -10
- data/lib/csv/row.rb +499 -132
- data/lib/csv/table.rb +489 -66
- data/lib/csv/version.rb +1 -1
- data/lib/csv/writer.rb +2 -1
- data/lib/csv.rb +344 -169
- metadata +16 -6
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
|
-
#
|
8
|
-
#
|
9
|
-
#
|
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
|
-
#
|
12
|
-
#
|
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
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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(
|
74
|
-
# field(
|
75
|
-
# field(
|
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
|
-
#
|
78
|
-
# is not found, +nil+ is returned.
|
188
|
+
# ---
|
79
189
|
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
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].
|
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(
|
101
|
-
# fetch(
|
102
|
-
# fetch(
|
103
|
-
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
#
|
107
|
-
#
|
108
|
-
#
|
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
|
-
#
|
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
|
-
# []=
|
138
|
-
# [
|
139
|
-
# []=
|
140
|
-
#
|
141
|
-
#
|
142
|
-
#
|
143
|
-
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
#
|
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
|
-
# <<
|
171
|
-
# <<
|
172
|
-
# <<
|
173
|
-
#
|
174
|
-
#
|
175
|
-
#
|
176
|
-
#
|
177
|
-
# a
|
178
|
-
#
|
179
|
-
#
|
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
|
-
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
#
|
198
|
-
#
|
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,
|
210
|
-
# delete(
|
211
|
-
#
|
212
|
-
# Removes a
|
213
|
-
#
|
214
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
245
|
-
#
|
246
|
-
#
|
247
|
-
#
|
248
|
-
#
|
249
|
-
#
|
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(
|
277
|
-
# index(
|
278
|
-
#
|
279
|
-
#
|
280
|
-
#
|
281
|
-
#
|
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
|
-
#
|
292
|
-
#
|
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
|
-
#
|
302
|
-
#
|
303
|
-
#
|
304
|
-
#
|
305
|
-
#
|
306
|
-
#
|
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
|
-
#
|
320
|
-
#
|
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
|
-
#
|
329
|
-
#
|
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
|
344
|
-
#
|
345
|
-
|
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
|
-
#
|
354
|
-
#
|
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
|
-
#
|
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|
|