csv 3.1.7 → 3.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/NEWS.md +11 -0
- data/README.md +5 -0
- 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.rb +67 -26
- data/lib/csv/row.rb +477 -132
- data/lib/csv/table.rb +486 -65
- data/lib/csv/version.rb +1 -1
- metadata +11 -3
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,
|
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 # => ["
|
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
|
-
#
|
756
|
-
#
|
757
|
-
#
|
758
|
-
#
|
759
|
-
#
|
760
|
-
#
|
761
|
-
#
|
762
|
-
#
|
763
|
-
#
|
764
|
-
#
|
765
|
-
#
|
766
|
-
#
|
767
|
-
#
|
768
|
-
#
|
769
|
-
#
|
770
|
-
#
|
771
|
-
#
|
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,
|
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
|
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,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
|
-
#
|
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
|
+
# ---
|
76
172
|
#
|
77
|
-
#
|
78
|
-
#
|
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
|
-
#
|
81
|
-
#
|
82
|
-
#
|
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(
|
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|
|
@@ -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
|
344
|
-
#
|
345
|
-
#
|
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
|
-
#
|
354
|
-
#
|
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
|
-
#
|
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|
|