csv 1.0.2 → 3.2.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +868 -0
  3. data/README.md +6 -3
  4. data/doc/csv/arguments/io.rdoc +5 -0
  5. data/doc/csv/options/common/col_sep.rdoc +57 -0
  6. data/doc/csv/options/common/quote_char.rdoc +42 -0
  7. data/doc/csv/options/common/row_sep.rdoc +91 -0
  8. data/doc/csv/options/generating/force_quotes.rdoc +17 -0
  9. data/doc/csv/options/generating/quote_empty.rdoc +12 -0
  10. data/doc/csv/options/generating/write_converters.rdoc +25 -0
  11. data/doc/csv/options/generating/write_empty_value.rdoc +15 -0
  12. data/doc/csv/options/generating/write_headers.rdoc +29 -0
  13. data/doc/csv/options/generating/write_nil_value.rdoc +14 -0
  14. data/doc/csv/options/parsing/converters.rdoc +46 -0
  15. data/doc/csv/options/parsing/empty_value.rdoc +13 -0
  16. data/doc/csv/options/parsing/field_size_limit.rdoc +39 -0
  17. data/doc/csv/options/parsing/header_converters.rdoc +43 -0
  18. data/doc/csv/options/parsing/headers.rdoc +63 -0
  19. data/doc/csv/options/parsing/liberal_parsing.rdoc +38 -0
  20. data/doc/csv/options/parsing/nil_value.rdoc +12 -0
  21. data/doc/csv/options/parsing/return_headers.rdoc +22 -0
  22. data/doc/csv/options/parsing/skip_blanks.rdoc +31 -0
  23. data/doc/csv/options/parsing/skip_lines.rdoc +37 -0
  24. data/doc/csv/options/parsing/strip.rdoc +15 -0
  25. data/doc/csv/options/parsing/unconverted_fields.rdoc +27 -0
  26. data/doc/csv/recipes/filtering.rdoc +158 -0
  27. data/doc/csv/recipes/generating.rdoc +298 -0
  28. data/doc/csv/recipes/parsing.rdoc +545 -0
  29. data/doc/csv/recipes/recipes.rdoc +6 -0
  30. data/lib/csv/core_ext/array.rb +1 -1
  31. data/lib/csv/core_ext/string.rb +1 -1
  32. data/lib/csv/fields_converter.rb +89 -0
  33. data/lib/csv/input_record_separator.rb +18 -0
  34. data/lib/csv/parser.rb +1290 -0
  35. data/lib/csv/row.rb +505 -136
  36. data/lib/csv/table.rb +791 -114
  37. data/lib/csv/version.rb +1 -1
  38. data/lib/csv/writer.rb +210 -0
  39. data/lib/csv.rb +2432 -1329
  40. metadata +66 -13
  41. data/news.md +0 -112
data/lib/csv/table.rb CHANGED
@@ -3,28 +3,210 @@
3
3
  require "forwardable"
4
4
 
5
5
  class CSV
6
+ # = \CSV::Table
7
+ # A \CSV::Table instance represents \CSV data.
8
+ # (see {class CSV}[../CSV.html]).
6
9
  #
7
- # A CSV::Table is a two-dimensional data structure for representing CSV
8
- # documents. Tables allow you to work with the data by row or column,
9
- # manipulate the data, and even convert the results back to CSV, if needed.
10
+ # The instance may have:
11
+ # - Rows: each is a Table::Row object.
12
+ # - Headers: names for the columns.
10
13
  #
11
- # All tables returned by CSV will be constructed from this class, if header
12
- # row processing is activated.
14
+ # === Instance Methods
13
15
  #
16
+ # \CSV::Table 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::Table Instance
25
+ #
26
+ # Commonly, a new \CSV::Table instance is created by parsing \CSV source
27
+ # using headers:
28
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
29
+ # table = CSV.parse(source, headers: true)
30
+ # table.class # => CSV::Table
31
+ #
32
+ # You can also create an instance directly. See ::new.
33
+ #
34
+ # == Headers
35
+ #
36
+ # If a table has headers, the headers serve as labels for the columns of data.
37
+ # Each header serves as the label for its column.
38
+ #
39
+ # The headers for a \CSV::Table object are stored as an \Array of Strings.
40
+ #
41
+ # Commonly, headers are defined in the first row of \CSV source:
42
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
43
+ # table = CSV.parse(source, headers: true)
44
+ # table.headers # => ["Name", "Value"]
45
+ #
46
+ # If no headers are defined, the \Array is empty:
47
+ # table = CSV::Table.new([])
48
+ # table.headers # => []
49
+ #
50
+ # == Access Modes
51
+ #
52
+ # \CSV::Table provides three modes for accessing table data:
53
+ # - \Row mode.
54
+ # - Column mode.
55
+ # - Mixed mode (the default for a new table).
56
+ #
57
+ # The access mode for a\CSV::Table instance affects the behavior
58
+ # of some of its instance methods:
59
+ # - #[]
60
+ # - #[]=
61
+ # - #delete
62
+ # - #delete_if
63
+ # - #each
64
+ # - #values_at
65
+ #
66
+ # === \Row Mode
67
+ #
68
+ # Set a table to row mode with method #by_row!:
69
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
70
+ # table = CSV.parse(source, headers: true)
71
+ # table.by_row! # => #<CSV::Table mode:row row_count:4>
72
+ #
73
+ # Specify a single row by an \Integer index:
74
+ # # Get a row.
75
+ # table[1] # => #<CSV::Row "Name":"bar" "Value":"1">
76
+ # # Set a row, then get it.
77
+ # table[1] = CSV::Row.new(['Name', 'Value'], ['bam', 3])
78
+ # table[1] # => #<CSV::Row "Name":"bam" "Value":3>
79
+ #
80
+ # Specify a sequence of rows by a \Range:
81
+ # # Get rows.
82
+ # table[1..2] # => [#<CSV::Row "Name":"bam" "Value":3>, #<CSV::Row "Name":"baz" "Value":"2">]
83
+ # # Set rows, then get them.
84
+ # table[1..2] = [
85
+ # CSV::Row.new(['Name', 'Value'], ['bat', 4]),
86
+ # CSV::Row.new(['Name', 'Value'], ['bad', 5]),
87
+ # ]
88
+ # table[1..2] # => [["Name", #<CSV::Row "Name":"bat" "Value":4>], ["Value", #<CSV::Row "Name":"bad" "Value":5>]]
89
+ #
90
+ # === Column Mode
91
+ #
92
+ # Set a table to column mode with method #by_col!:
93
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
94
+ # table = CSV.parse(source, headers: true)
95
+ # table.by_col! # => #<CSV::Table mode:col row_count:4>
96
+ #
97
+ # Specify a column by an \Integer index:
98
+ # # Get a column.
99
+ # table[0]
100
+ # # Set a column, then get it.
101
+ # table[0] = ['FOO', 'BAR', 'BAZ']
102
+ # table[0] # => ["FOO", "BAR", "BAZ"]
103
+ #
104
+ # Specify a column by its \String header:
105
+ # # Get a column.
106
+ # table['Name'] # => ["FOO", "BAR", "BAZ"]
107
+ # # Set a column, then get it.
108
+ # table['Name'] = ['Foo', 'Bar', 'Baz']
109
+ # table['Name'] # => ["Foo", "Bar", "Baz"]
110
+ #
111
+ # === Mixed Mode
112
+ #
113
+ # In mixed mode, you can refer to either rows or columns:
114
+ # - An \Integer index refers to a row.
115
+ # - A \Range index refers to multiple rows.
116
+ # - A \String index refers to a column.
117
+ #
118
+ # Set a table to mixed mode with method #by_col_or_row!:
119
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
120
+ # table = CSV.parse(source, headers: true)
121
+ # table.by_col_or_row! # => #<CSV::Table mode:col_or_row row_count:4>
122
+ #
123
+ # Specify a single row by an \Integer index:
124
+ # # Get a row.
125
+ # table[1] # => #<CSV::Row "Name":"bar" "Value":"1">
126
+ # # Set a row, then get it.
127
+ # table[1] = CSV::Row.new(['Name', 'Value'], ['bam', 3])
128
+ # table[1] # => #<CSV::Row "Name":"bam" "Value":3>
129
+ #
130
+ # Specify a sequence of rows by a \Range:
131
+ # # Get rows.
132
+ # table[1..2] # => [#<CSV::Row "Name":"bam" "Value":3>, #<CSV::Row "Name":"baz" "Value":"2">]
133
+ # # Set rows, then get them.
134
+ # table[1] = CSV::Row.new(['Name', 'Value'], ['bat', 4])
135
+ # table[2] = CSV::Row.new(['Name', 'Value'], ['bad', 5])
136
+ # table[1..2] # => [["Name", #<CSV::Row "Name":"bat" "Value":4>], ["Value", #<CSV::Row "Name":"bad" "Value":5>]]
137
+ #
138
+ # Specify a column by its \String header:
139
+ # # Get a column.
140
+ # table['Name'] # => ["foo", "bat", "bad"]
141
+ # # Set a column, then get it.
142
+ # table['Name'] = ['Foo', 'Bar', 'Baz']
143
+ # table['Name'] # => ["Foo", "Bar", "Baz"]
14
144
  class Table
145
+ # :call-seq:
146
+ # CSV::Table.new(array_of_rows, headers = nil) -> csv_table
147
+ #
148
+ # Returns a new \CSV::Table object.
149
+ #
150
+ # - Argument +array_of_rows+ must be an \Array of CSV::Row objects.
151
+ # - Argument +headers+, if given, may be an \Array of Strings.
152
+ #
153
+ # ---
154
+ #
155
+ # Create an empty \CSV::Table object:
156
+ # table = CSV::Table.new([])
157
+ # table # => #<CSV::Table mode:col_or_row row_count:1>
158
+ #
159
+ # Create a non-empty \CSV::Table object:
160
+ # rows = [
161
+ # CSV::Row.new([], []),
162
+ # CSV::Row.new([], []),
163
+ # CSV::Row.new([], []),
164
+ # ]
165
+ # table = CSV::Table.new(rows)
166
+ # table # => #<CSV::Table mode:col_or_row row_count:4>
167
+ #
168
+ # ---
15
169
  #
16
- # Construct a new CSV::Table from +array_of_rows+, which are expected
17
- # to be CSV::Row objects. All rows are assumed to have the same headers.
170
+ # If argument +headers+ is an \Array of Strings,
171
+ # those Strings become the table's headers:
172
+ # table = CSV::Table.new([], headers: ['Name', 'Age'])
173
+ # table.headers # => ["Name", "Age"]
18
174
  #
19
- # A CSV::Table object supports the following Array methods through
20
- # delegation:
175
+ # If argument +headers+ is not given and the table has rows,
176
+ # the headers are taken from the first row:
177
+ # rows = [
178
+ # CSV::Row.new(['Foo', 'Bar'], []),
179
+ # CSV::Row.new(['foo', 'bar'], []),
180
+ # CSV::Row.new(['FOO', 'BAR'], []),
181
+ # ]
182
+ # table = CSV::Table.new(rows)
183
+ # table.headers # => ["Foo", "Bar"]
21
184
  #
22
- # * empty?()
23
- # * length()
24
- # * size()
185
+ # If argument +headers+ is not given and the table is empty (has no rows),
186
+ # the headers are also empty:
187
+ # table = CSV::Table.new([])
188
+ # table.headers # => []
25
189
  #
26
- def initialize(array_of_rows)
190
+ # ---
191
+ #
192
+ # Raises an exception if argument +array_of_rows+ is not an \Array object:
193
+ # # Raises NoMethodError (undefined method `first' for :foo:Symbol):
194
+ # CSV::Table.new(:foo)
195
+ #
196
+ # Raises an exception if an element of +array_of_rows+ is not a \CSV::Table object:
197
+ # # Raises NoMethodError (undefined method `headers' for :foo:Symbol):
198
+ # CSV::Table.new([:foo])
199
+ def initialize(array_of_rows, headers: nil)
27
200
  @table = array_of_rows
201
+ @headers = headers
202
+ unless @headers
203
+ if @table.empty?
204
+ @headers = []
205
+ else
206
+ @headers = @table.first.headers
207
+ end
208
+ end
209
+
28
210
  @mode = :col_or_row
29
211
  end
30
212
 
@@ -40,103 +222,295 @@ class CSV
40
222
  extend Forwardable
41
223
  def_delegators :@table, :empty?, :length, :size
42
224
 
225
+ # :call-seq:
226
+ # table.by_col -> table_dup
43
227
  #
44
- # Returns a duplicate table object, in column mode. This is handy for
45
- # chaining in a single call without changing the table mode, but be aware
46
- # that this method can consume a fair amount of memory for bigger data sets.
228
+ # Returns a duplicate of +self+, in column mode
229
+ # (see {Column Mode}[#class-CSV::Table-label-Column+Mode]):
230
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
231
+ # table = CSV.parse(source, headers: true)
232
+ # table.mode # => :col_or_row
233
+ # dup_table = table.by_col
234
+ # dup_table.mode # => :col
235
+ # dup_table.equal?(table) # => false # It's a dup
47
236
  #
48
- # This method returns the duplicate table for chaining. Don't chain
49
- # destructive methods (like []=()) this way though, since you are working
50
- # with a duplicate.
237
+ # This may be used to chain method calls without changing the mode
238
+ # (but also will affect performance and memory usage):
239
+ # dup_table.by_col['Name']
51
240
  #
241
+ # Also note that changes to the duplicate table will not affect the original.
52
242
  def by_col
53
243
  self.class.new(@table.dup).by_col!
54
244
  end
55
245
 
246
+ # :call-seq:
247
+ # table.by_col! -> self
56
248
  #
57
- # Switches the mode of this table to column mode. All calls to indexing and
58
- # iteration methods will work with columns until the mode is changed again.
59
- #
60
- # This method returns the table and is safe to chain.
61
- #
249
+ # Sets the mode for +self+ to column mode
250
+ # (see {Column Mode}[#class-CSV::Table-label-Column+Mode]); returns +self+:
251
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
252
+ # table = CSV.parse(source, headers: true)
253
+ # table.mode # => :col_or_row
254
+ # table1 = table.by_col!
255
+ # table.mode # => :col
256
+ # table1.equal?(table) # => true # Returned self
62
257
  def by_col!
63
258
  @mode = :col
64
259
 
65
260
  self
66
261
  end
67
262
 
263
+ # :call-seq:
264
+ # table.by_col_or_row -> table_dup
68
265
  #
69
- # Returns a duplicate table object, in mixed mode. This is handy for
70
- # chaining in a single call without changing the table mode, but be aware
71
- # that this method can consume a fair amount of memory for bigger data sets.
266
+ # Returns a duplicate of +self+, in mixed mode
267
+ # (see {Mixed Mode}[#class-CSV::Table-label-Mixed+Mode]):
268
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
269
+ # table = CSV.parse(source, headers: true).by_col!
270
+ # table.mode # => :col
271
+ # dup_table = table.by_col_or_row
272
+ # dup_table.mode # => :col_or_row
273
+ # dup_table.equal?(table) # => false # It's a dup
72
274
  #
73
- # This method returns the duplicate table for chaining. Don't chain
74
- # destructive methods (like []=()) this way though, since you are working
75
- # with a duplicate.
275
+ # This may be used to chain method calls without changing the mode
276
+ # (but also will affect performance and memory usage):
277
+ # dup_table.by_col_or_row['Name']
76
278
  #
279
+ # Also note that changes to the duplicate table will not affect the original.
77
280
  def by_col_or_row
78
281
  self.class.new(@table.dup).by_col_or_row!
79
282
  end
80
283
 
284
+ # :call-seq:
285
+ # table.by_col_or_row! -> self
81
286
  #
82
- # Switches the mode of this table to mixed mode. All calls to indexing and
83
- # iteration methods will use the default intelligent indexing system until
84
- # the mode is changed again. In mixed mode an index is assumed to be a row
85
- # reference while anything else is assumed to be column access by headers.
86
- #
87
- # This method returns the table and is safe to chain.
88
- #
287
+ # Sets the mode for +self+ to mixed mode
288
+ # (see {Mixed Mode}[#class-CSV::Table-label-Mixed+Mode]); returns +self+:
289
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
290
+ # table = CSV.parse(source, headers: true).by_col!
291
+ # table.mode # => :col
292
+ # table1 = table.by_col_or_row!
293
+ # table.mode # => :col_or_row
294
+ # table1.equal?(table) # => true # Returned self
89
295
  def by_col_or_row!
90
296
  @mode = :col_or_row
91
297
 
92
298
  self
93
299
  end
94
300
 
301
+ # :call-seq:
302
+ # table.by_row -> table_dup
95
303
  #
96
- # Returns a duplicate table object, in row mode. This is handy for chaining
97
- # in a single call without changing the table mode, but be aware that this
98
- # method can consume a fair amount of memory for bigger data sets.
304
+ # Returns a duplicate of +self+, in row mode
305
+ # (see {Row Mode}[#class-CSV::Table-label-Row+Mode]):
306
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
307
+ # table = CSV.parse(source, headers: true)
308
+ # table.mode # => :col_or_row
309
+ # dup_table = table.by_row
310
+ # dup_table.mode # => :row
311
+ # dup_table.equal?(table) # => false # It's a dup
99
312
  #
100
- # This method returns the duplicate table for chaining. Don't chain
101
- # destructive methods (like []=()) this way though, since you are working
102
- # with a duplicate.
313
+ # This may be used to chain method calls without changing the mode
314
+ # (but also will affect performance and memory usage):
315
+ # dup_table.by_row[1]
103
316
  #
317
+ # Also note that changes to the duplicate table will not affect the original.
104
318
  def by_row
105
319
  self.class.new(@table.dup).by_row!
106
320
  end
107
321
 
322
+ # :call-seq:
323
+ # table.by_row! -> self
108
324
  #
109
- # Switches the mode of this table to row mode. All calls to indexing and
110
- # iteration methods will work with rows until the mode is changed again.
111
- #
112
- # This method returns the table and is safe to chain.
113
- #
325
+ # Sets the mode for +self+ to row mode
326
+ # (see {Row Mode}[#class-CSV::Table-label-Row+Mode]); returns +self+:
327
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
328
+ # table = CSV.parse(source, headers: true)
329
+ # table.mode # => :col_or_row
330
+ # table1 = table.by_row!
331
+ # table.mode # => :row
332
+ # table1.equal?(table) # => true # Returned self
114
333
  def by_row!
115
334
  @mode = :row
116
335
 
117
336
  self
118
337
  end
119
338
 
339
+ # :call-seq:
340
+ # table.headers -> array_of_headers
120
341
  #
121
- # Returns the headers for the first row of this table (assumed to match all
122
- # other rows). An empty Array is returned for empty tables.
342
+ # Returns a new \Array containing the \String headers for the table.
123
343
  #
344
+ # If the table is not empty, returns the headers from the first row:
345
+ # rows = [
346
+ # CSV::Row.new(['Foo', 'Bar'], []),
347
+ # CSV::Row.new(['FOO', 'BAR'], []),
348
+ # CSV::Row.new(['foo', 'bar'], []),
349
+ # ]
350
+ # table = CSV::Table.new(rows)
351
+ # table.headers # => ["Foo", "Bar"]
352
+ # table.delete(0)
353
+ # table.headers # => ["FOO", "BAR"]
354
+ # table.delete(0)
355
+ # table.headers # => ["foo", "bar"]
356
+ #
357
+ # If the table is empty, returns a copy of the headers in the table itself:
358
+ # table.delete(0)
359
+ # table.headers # => ["Foo", "Bar"]
124
360
  def headers
125
361
  if @table.empty?
126
- Array.new
362
+ @headers.dup
127
363
  else
128
364
  @table.first.headers
129
365
  end
130
366
  end
131
367
 
368
+ # :call-seq:
369
+ # table[n] -> row or column_data
370
+ # table[range] -> array_of_rows or array_of_column_data
371
+ # table[header] -> array_of_column_data
372
+ #
373
+ # Returns data from the table; does not modify the table.
374
+ #
375
+ # ---
376
+ #
377
+ # Fetch a \Row by Its \Integer Index::
378
+ # - Form: <tt>table[n]</tt>, +n+ an integer.
379
+ # - Access mode: <tt>:row</tt> or <tt>:col_or_row</tt>.
380
+ # - Return value: _nth_ row of the table, if that row exists;
381
+ # otherwise +nil+.
382
+ #
383
+ # Returns the _nth_ row of the table if that row exists:
384
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
385
+ # table = CSV.parse(source, headers: true)
386
+ # table.by_row! # => #<CSV::Table mode:row row_count:4>
387
+ # table[1] # => #<CSV::Row "Name":"bar" "Value":"1">
388
+ # table.by_col_or_row! # => #<CSV::Table mode:col_or_row row_count:4>
389
+ # table[1] # => #<CSV::Row "Name":"bar" "Value":"1">
390
+ #
391
+ # Counts backward from the last row if +n+ is negative:
392
+ # table[-1] # => #<CSV::Row "Name":"baz" "Value":"2">
393
+ #
394
+ # Returns +nil+ if +n+ is too large or too small:
395
+ # table[4] # => nil
396
+ # table[-4] # => nil
397
+ #
398
+ # Raises an exception if the access mode is <tt>:row</tt>
399
+ # and +n+ is not an \Integer:
400
+ # table.by_row! # => #<CSV::Table mode:row row_count:4>
401
+ # # Raises TypeError (no implicit conversion of String into Integer):
402
+ # table['Name']
403
+ #
404
+ # ---
405
+ #
406
+ # Fetch a Column by Its \Integer Index::
407
+ # - Form: <tt>table[n]</tt>, +n+ an \Integer.
408
+ # - Access mode: <tt>:col</tt>.
409
+ # - Return value: _nth_ column of the table, if that column exists;
410
+ # otherwise an \Array of +nil+ fields of length <tt>self.size</tt>.
411
+ #
412
+ # Returns the _nth_ column of the table if that column exists:
413
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
414
+ # table = CSV.parse(source, headers: true)
415
+ # table.by_col! # => #<CSV::Table mode:col row_count:4>
416
+ # table[1] # => ["0", "1", "2"]
417
+ #
418
+ # Counts backward from the last column if +n+ is negative:
419
+ # table[-2] # => ["foo", "bar", "baz"]
420
+ #
421
+ # Returns an \Array of +nil+ fields if +n+ is too large or too small:
422
+ # table[4] # => [nil, nil, nil]
423
+ # table[-4] # => [nil, nil, nil]
424
+ #
425
+ # ---
426
+ #
427
+ # Fetch Rows by \Range::
428
+ # - Form: <tt>table[range]</tt>, +range+ a \Range object.
429
+ # - Access mode: <tt>:row</tt> or <tt>:col_or_row</tt>.
430
+ # - Return value: rows from the table, beginning at row <tt>range.start</tt>,
431
+ # if those rows exists.
432
+ #
433
+ # Returns rows from the table, beginning at row <tt>range.first</tt>,
434
+ # if those rows exist:
435
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
436
+ # table = CSV.parse(source, headers: true)
437
+ # table.by_row! # => #<CSV::Table mode:row row_count:4>
438
+ # rows = table[1..2] # => #<CSV::Row "Name":"bar" "Value":"1">
439
+ # rows # => [#<CSV::Row "Name":"bar" "Value":"1">, #<CSV::Row "Name":"baz" "Value":"2">]
440
+ # table.by_col_or_row! # => #<CSV::Table mode:col_or_row row_count:4>
441
+ # rows = table[1..2] # => #<CSV::Row "Name":"bar" "Value":"1">
442
+ # rows # => [#<CSV::Row "Name":"bar" "Value":"1">, #<CSV::Row "Name":"baz" "Value":"2">]
443
+ #
444
+ # If there are too few rows, returns all from <tt>range.start</tt> to the end:
445
+ # rows = table[1..50] # => #<CSV::Row "Name":"bar" "Value":"1">
446
+ # rows # => [#<CSV::Row "Name":"bar" "Value":"1">, #<CSV::Row "Name":"baz" "Value":"2">]
447
+ #
448
+ # Special case: if <tt>range.start == table.size</tt>, returns an empty \Array:
449
+ # table[table.size..50] # => []
132
450
  #
133
- # In the default mixed mode, this method returns rows for index access and
134
- # columns for header access. You can force the index association by first
135
- # calling by_col!() or by_row!().
451
+ # If <tt>range.end</tt> is negative, calculates the ending index from the end:
452
+ # rows = table[0..-1]
453
+ # rows # => [#<CSV::Row "Name":"foo" "Value":"0">, #<CSV::Row "Name":"bar" "Value":"1">, #<CSV::Row "Name":"baz" "Value":"2">]
136
454
  #
137
- # Columns are returned as an Array of values. Altering that Array has no
138
- # effect on the table.
455
+ # If <tt>range.start</tt> is negative, calculates the starting index from the end:
456
+ # rows = table[-1..2]
457
+ # rows # => [#<CSV::Row "Name":"baz" "Value":"2">]
139
458
  #
459
+ # If <tt>range.start</tt> is larger than <tt>table.size</tt>, returns +nil+:
460
+ # table[4..4] # => nil
461
+ #
462
+ # ---
463
+ #
464
+ # Fetch Columns by \Range::
465
+ # - Form: <tt>table[range]</tt>, +range+ a \Range object.
466
+ # - Access mode: <tt>:col</tt>.
467
+ # - Return value: column data from the table, beginning at column <tt>range.start</tt>,
468
+ # if those columns exist.
469
+ #
470
+ # Returns column values from the table, if the column exists;
471
+ # the values are arranged by row:
472
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
473
+ # table = CSV.parse(source, headers: true)
474
+ # table.by_col!
475
+ # table[0..1] # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
476
+ #
477
+ # Special case: if <tt>range.start == headers.size</tt>,
478
+ # returns an \Array (size: <tt>table.size</tt>) of empty \Arrays:
479
+ # table[table.headers.size..50] # => [[], [], []]
480
+ #
481
+ # If <tt>range.end</tt> is negative, calculates the ending index from the end:
482
+ # table[0..-1] # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
483
+ #
484
+ # If <tt>range.start</tt> is negative, calculates the starting index from the end:
485
+ # table[-2..2] # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
486
+ #
487
+ # If <tt>range.start</tt> is larger than <tt>table.size</tt>,
488
+ # returns an \Array of +nil+ values:
489
+ # table[4..4] # => [nil, nil, nil]
490
+ #
491
+ # ---
492
+ #
493
+ # Fetch a Column by Its \String Header::
494
+ # - Form: <tt>table[header]</tt>, +header+ a \String header.
495
+ # - Access mode: <tt>:col</tt> or <tt>:col_or_row</tt>
496
+ # - Return value: column data from the table, if that +header+ exists.
497
+ #
498
+ # Returns column values from the table, if the column exists:
499
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
500
+ # table = CSV.parse(source, headers: true)
501
+ # table.by_col! # => #<CSV::Table mode:col row_count:4>
502
+ # table['Name'] # => ["foo", "bar", "baz"]
503
+ # table.by_col_or_row! # => #<CSV::Table mode:col_or_row row_count:4>
504
+ # col = table['Name']
505
+ # col # => ["foo", "bar", "baz"]
506
+ #
507
+ # Modifying the returned column values does not modify the table:
508
+ # col[0] = 'bat'
509
+ # col # => ["bat", "bar", "baz"]
510
+ # table['Name'] # => ["foo", "bar", "baz"]
511
+ #
512
+ # Returns an \Array of +nil+ values if there is no such column:
513
+ # table['Nosuch'] # => [nil, nil, nil]
140
514
  def [](index_or_header)
141
515
  if @mode == :row or # by index
142
516
  (@mode == :col_or_row and (index_or_header.is_a?(Integer) or index_or_header.is_a?(Range)))
@@ -146,22 +520,132 @@ class CSV
146
520
  end
147
521
  end
148
522
 
523
+ # :call-seq:
524
+ # table[n] = row -> row
525
+ # table[n] = field_or_array_of_fields -> field_or_array_of_fields
526
+ # table[header] = field_or_array_of_fields -> field_or_array_of_fields
527
+ #
528
+ # Puts data onto the table.
529
+ #
530
+ # ---
531
+ #
532
+ # Set a \Row by Its \Integer Index::
533
+ # - Form: <tt>table[n] = row</tt>, +n+ an \Integer,
534
+ # +row+ a \CSV::Row instance or an \Array of fields.
535
+ # - Access mode: <tt>:row</tt> or <tt>:col_or_row</tt>.
536
+ # - Return value: +row+.
149
537
  #
150
- # In the default mixed mode, this method assigns rows for index access and
151
- # columns for header access. You can force the index association by first
152
- # calling by_col!() or by_row!().
538
+ # If the row exists, it is replaced:
539
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
540
+ # table = CSV.parse(source, headers: true)
541
+ # new_row = CSV::Row.new(['Name', 'Value'], ['bat', 3])
542
+ # table.by_row! # => #<CSV::Table mode:row row_count:4>
543
+ # return_value = table[0] = new_row
544
+ # return_value.equal?(new_row) # => true # Returned the row
545
+ # table[0].to_h # => {"Name"=>"bat", "Value"=>3}
153
546
  #
154
- # Rows may be set to an Array of values (which will inherit the table's
155
- # headers()) or a CSV::Row.
547
+ # With access mode <tt>:col_or_row</tt>:
548
+ # table.by_col_or_row! # => #<CSV::Table mode:col_or_row row_count:4>
549
+ # table[0] = CSV::Row.new(['Name', 'Value'], ['bam', 4])
550
+ # table[0].to_h # => {"Name"=>"bam", "Value"=>4}
156
551
  #
157
- # Columns may be set to a single value, which is copied to each row of the
158
- # column, or an Array of values. Arrays of values are assigned to rows top
159
- # to bottom in row major order. Excess values are ignored and if the Array
160
- # does not have a value for each row the extra rows will receive a +nil+.
552
+ # With an \Array instead of a \CSV::Row, inherits headers from the table:
553
+ # array = ['bad', 5]
554
+ # return_value = table[0] = array
555
+ # return_value.equal?(array) # => true # Returned the array
556
+ # table[0].to_h # => {"Name"=>"bad", "Value"=>5}
161
557
  #
162
- # Assigning to an existing column or row clobbers the data. Assigning to
163
- # new columns creates them at the right end of the table.
558
+ # If the row does not exist, extends the table by adding rows:
559
+ # assigns rows with +nil+ as needed:
560
+ # table.size # => 3
561
+ # table[5] = ['bag', 6]
562
+ # table.size # => 6
563
+ # table[3] # => nil
564
+ # table[4]# => nil
565
+ # table[5].to_h # => {"Name"=>"bag", "Value"=>6}
164
566
  #
567
+ # Note that the +nil+ rows are actually +nil+, not a row of +nil+ fields.
568
+ #
569
+ # ---
570
+ #
571
+ # Set a Column by Its \Integer Index::
572
+ # - Form: <tt>table[n] = array_of_fields</tt>, +n+ an \Integer,
573
+ # +array_of_fields+ an \Array of \String fields.
574
+ # - Access mode: <tt>:col</tt>.
575
+ # - Return value: +array_of_fields+.
576
+ #
577
+ # If the column exists, it is replaced:
578
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
579
+ # table = CSV.parse(source, headers: true)
580
+ # new_col = [3, 4, 5]
581
+ # table.by_col! # => #<CSV::Table mode:col row_count:4>
582
+ # return_value = table[1] = new_col
583
+ # return_value.equal?(new_col) # => true # Returned the column
584
+ # table[1] # => [3, 4, 5]
585
+ # # The rows, as revised:
586
+ # table.by_row! # => #<CSV::Table mode:row row_count:4>
587
+ # table[0].to_h # => {"Name"=>"foo", "Value"=>3}
588
+ # table[1].to_h # => {"Name"=>"bar", "Value"=>4}
589
+ # table[2].to_h # => {"Name"=>"baz", "Value"=>5}
590
+ # table.by_col! # => #<CSV::Table mode:col row_count:4>
591
+ #
592
+ # If there are too few values, fills with +nil+ values:
593
+ # table[1] = [0]
594
+ # table[1] # => [0, nil, nil]
595
+ #
596
+ # If there are too many values, ignores the extra values:
597
+ # table[1] = [0, 1, 2, 3, 4]
598
+ # table[1] # => [0, 1, 2]
599
+ #
600
+ # If a single value is given, replaces all fields in the column with that value:
601
+ # table[1] = 'bat'
602
+ # table[1] # => ["bat", "bat", "bat"]
603
+ #
604
+ # ---
605
+ #
606
+ # Set a Column by Its \String Header::
607
+ # - Form: <tt>table[header] = field_or_array_of_fields</tt>,
608
+ # +header+ a \String header, +field_or_array_of_fields+ a field value
609
+ # or an \Array of \String fields.
610
+ # - Access mode: <tt>:col</tt> or <tt>:col_or_row</tt>.
611
+ # - Return value: +field_or_array_of_fields+.
612
+ #
613
+ # If the column exists, it is replaced:
614
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
615
+ # table = CSV.parse(source, headers: true)
616
+ # new_col = [3, 4, 5]
617
+ # table.by_col! # => #<CSV::Table mode:col row_count:4>
618
+ # return_value = table['Value'] = new_col
619
+ # return_value.equal?(new_col) # => true # Returned the column
620
+ # table['Value'] # => [3, 4, 5]
621
+ # # The rows, as revised:
622
+ # table.by_row! # => #<CSV::Table mode:row row_count:4>
623
+ # table[0].to_h # => {"Name"=>"foo", "Value"=>3}
624
+ # table[1].to_h # => {"Name"=>"bar", "Value"=>4}
625
+ # table[2].to_h # => {"Name"=>"baz", "Value"=>5}
626
+ # table.by_col! # => #<CSV::Table mode:col row_count:4>
627
+ #
628
+ # If there are too few values, fills with +nil+ values:
629
+ # table['Value'] = [0]
630
+ # table['Value'] # => [0, nil, nil]
631
+ #
632
+ # If there are too many values, ignores the extra values:
633
+ # table['Value'] = [0, 1, 2, 3, 4]
634
+ # table['Value'] # => [0, 1, 2]
635
+ #
636
+ # If the column does not exist, extends the table by adding columns:
637
+ # table['Note'] = ['x', 'y', 'z']
638
+ # table['Note'] # => ["x", "y", "z"]
639
+ # # The rows, as revised:
640
+ # table.by_row!
641
+ # table[0].to_h # => {"Name"=>"foo", "Value"=>0, "Note"=>"x"}
642
+ # table[1].to_h # => {"Name"=>"bar", "Value"=>1, "Note"=>"y"}
643
+ # table[2].to_h # => {"Name"=>"baz", "Value"=>2, "Note"=>"z"}
644
+ # table.by_col!
645
+ #
646
+ # If a single value is given, replaces all fields in the column with that value:
647
+ # table['Value'] = 'bat'
648
+ # table['Value'] # => ["bat", "bat", "bat"]
165
649
  def []=(index_or_header, value)
166
650
  if @mode == :row or # by index
167
651
  (@mode == :col_or_row and index_or_header.is_a? Integer)
@@ -171,6 +655,10 @@ class CSV
171
655
  @table[index_or_header] = value
172
656
  end
173
657
  else # set column
658
+ unless index_or_header.is_a? Integer
659
+ index = @headers.index(index_or_header) || @headers.size
660
+ @headers[index] = index_or_header
661
+ end
174
662
  if value.is_a? Array # multiple values
175
663
  @table.each_with_index do |row, i|
176
664
  if row.header_row?
@@ -191,15 +679,58 @@ class CSV
191
679
  end
192
680
  end
193
681
 
682
+ # :call-seq:
683
+ # table.values_at(*indexes) -> array_of_rows
684
+ # table.values_at(*headers) -> array_of_columns_data
685
+ #
686
+ # If the access mode is <tt>:row</tt> or <tt>:col_or_row</tt>,
687
+ # and each argument is either an \Integer or a \Range,
688
+ # returns rows.
689
+ # Otherwise, returns columns data.
194
690
  #
195
- # The mixed mode default is to treat a list of indices as row access,
196
- # returning the rows indicated. Anything else is considered columnar
197
- # access. For columnar access, the return set has an Array for each row
198
- # with the values indicated by the headers in each Array. You can force
199
- # column or row mode using by_col!() or by_row!().
691
+ # In either case, the returned values are in the order
692
+ # specified by the arguments. Arguments may be repeated.
200
693
  #
201
- # You cannot mix column and row access.
694
+ # ---
202
695
  #
696
+ # Returns rows as an \Array of \CSV::Row objects.
697
+ #
698
+ # No argument:
699
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
700
+ # table = CSV.parse(source, headers: true)
701
+ # table.values_at # => []
702
+ #
703
+ # One index:
704
+ # values = table.values_at(0)
705
+ # values # => [#<CSV::Row "Name":"foo" "Value":"0">]
706
+ #
707
+ # Two indexes:
708
+ # values = table.values_at(2, 0)
709
+ # values # => [#<CSV::Row "Name":"baz" "Value":"2">, #<CSV::Row "Name":"foo" "Value":"0">]
710
+ #
711
+ # One \Range:
712
+ # values = table.values_at(1..2)
713
+ # values # => [#<CSV::Row "Name":"bar" "Value":"1">, #<CSV::Row "Name":"baz" "Value":"2">]
714
+ #
715
+ # \Ranges and indexes:
716
+ # values = table.values_at(0..1, 1..2, 0, 2)
717
+ # pp values
718
+ # Output:
719
+ # [#<CSV::Row "Name":"foo" "Value":"0">,
720
+ # #<CSV::Row "Name":"bar" "Value":"1">,
721
+ # #<CSV::Row "Name":"bar" "Value":"1">,
722
+ # #<CSV::Row "Name":"baz" "Value":"2">,
723
+ # #<CSV::Row "Name":"foo" "Value":"0">,
724
+ # #<CSV::Row "Name":"baz" "Value":"2">]
725
+ #
726
+ # ---
727
+ #
728
+ # Returns columns data as row Arrays,
729
+ # each consisting of the specified columns data for that row:
730
+ # values = table.values_at('Name')
731
+ # values # => [["foo"], ["bar"], ["baz"]]
732
+ # values = table.values_at('Value', 'Name')
733
+ # values # => [["0", "foo"], ["1", "bar"], ["2", "baz"]]
203
734
  def values_at(*indices_or_headers)
204
735
  if @mode == :row or # by indices
205
736
  ( @mode == :col_or_row and indices_or_headers.all? do |index|
@@ -214,13 +745,20 @@ class CSV
214
745
  end
215
746
  end
216
747
 
748
+ # :call-seq:
749
+ # table << row_or_array -> self
217
750
  #
218
- # Adds a new row to the bottom end of this table. You can provide an Array,
219
- # which will be converted to a CSV::Row (inheriting the table's headers()),
220
- # or a CSV::Row.
221
- #
222
- # This method returns the table for chaining.
751
+ # If +row_or_array+ is a \CSV::Row object,
752
+ # it is appended to the table:
753
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
754
+ # table = CSV.parse(source, headers: true)
755
+ # table << CSV::Row.new(table.headers, ['bat', 3])
756
+ # table[3] # => #<CSV::Row "Name":"bat" "Value":3>
223
757
  #
758
+ # If +row_or_array+ is an \Array, it is used to create a new
759
+ # \CSV::Row object which is then appended to the table:
760
+ # table << ['bam', 4]
761
+ # table[4] # => #<CSV::Row "Name":"bam" "Value":4>
224
762
  def <<(row_or_array)
225
763
  if row_or_array.is_a? Array # append Array
226
764
  @table << Row.new(headers, row_or_array)
@@ -232,23 +770,67 @@ class CSV
232
770
  end
233
771
 
234
772
  #
235
- # A shortcut for appending multiple rows. Equivalent to:
773
+ # :call-seq:
774
+ # table.push(*rows_or_arrays) -> self
236
775
  #
237
- # rows.each { |row| self << row }
238
- #
239
- # This method returns the table for chaining.
776
+ # A shortcut for appending multiple rows. Equivalent to:
777
+ # rows.each {|row| self << row }
240
778
  #
779
+ # Each argument may be either a \CSV::Row object or an \Array:
780
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
781
+ # table = CSV.parse(source, headers: true)
782
+ # rows = [
783
+ # CSV::Row.new(table.headers, ['bat', 3]),
784
+ # ['bam', 4]
785
+ # ]
786
+ # table.push(*rows)
787
+ # table[3..4] # => [#<CSV::Row "Name":"bat" "Value":3>, #<CSV::Row "Name":"bam" "Value":4>]
241
788
  def push(*rows)
242
789
  rows.each { |row| self << row }
243
790
 
244
791
  self # for chaining
245
792
  end
246
793
 
794
+ # :call-seq:
795
+ # table.delete(*indexes) -> deleted_values
796
+ # table.delete(*headers) -> deleted_values
797
+ #
798
+ # If the access mode is <tt>:row</tt> or <tt>:col_or_row</tt>,
799
+ # and each argument is either an \Integer or a \Range,
800
+ # returns deleted rows.
801
+ # Otherwise, returns deleted columns data.
802
+ #
803
+ # In either case, the returned values are in the order
804
+ # specified by the arguments. Arguments may be repeated.
805
+ #
806
+ # ---
807
+ #
808
+ # Returns rows as an \Array of \CSV::Row objects.
809
+ #
810
+ # One index:
811
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
812
+ # table = CSV.parse(source, headers: true)
813
+ # deleted_values = table.delete(0)
814
+ # deleted_values # => [#<CSV::Row "Name":"foo" "Value":"0">]
815
+ #
816
+ # Two indexes:
817
+ # table = CSV.parse(source, headers: true)
818
+ # deleted_values = table.delete(2, 0)
819
+ # deleted_values # => [#<CSV::Row "Name":"baz" "Value":"2">, #<CSV::Row "Name":"foo" "Value":"0">]
247
820
  #
248
- # Removes and returns the indicated columns or rows. In the default mixed
249
- # mode indices refer to rows and everything else is assumed to be a column
250
- # headers. Use by_col!() or by_row!() to force the lookup.
821
+ # ---
251
822
  #
823
+ # Returns columns data as column Arrays.
824
+ #
825
+ # One header:
826
+ # table = CSV.parse(source, headers: true)
827
+ # deleted_values = table.delete('Name')
828
+ # deleted_values # => ["foo", "bar", "baz"]
829
+ #
830
+ # Two headers:
831
+ # table = CSV.parse(source, headers: true)
832
+ # deleted_values = table.delete('Value', 'Name')
833
+ # deleted_values # => [["0", "1", "2"], ["foo", "bar", "baz"]]
252
834
  def delete(*indexes_or_headers)
253
835
  if indexes_or_headers.empty?
254
836
  raise ArgumentError, "wrong number of arguments (given 0, expected 1+)"
@@ -258,6 +840,11 @@ class CSV
258
840
  (@mode == :col_or_row and index_or_header.is_a? Integer)
259
841
  @table.delete_at(index_or_header)
260
842
  else # by header
843
+ if index_or_header.is_a? Integer
844
+ @headers.delete_at(index_or_header)
845
+ else
846
+ @headers.delete(index_or_header)
847
+ end
261
848
  @table.map { |row| row.delete(index_or_header).last }
262
849
  end
263
850
  end
@@ -268,25 +855,43 @@ class CSV
268
855
  end
269
856
  end
270
857
 
858
+ # :call-seq:
859
+ # table.delete_if {|row_or_column| ... } -> self
271
860
  #
272
- # Removes any column or row for which the block returns +true+. In the
273
- # default mixed mode or row mode, iteration is the standard row major
274
- # walking of rows. In column mode, iteration will +yield+ two element
275
- # tuples containing the column name and an Array of values for that column.
861
+ # Removes rows or columns for which the block returns a truthy value;
862
+ # returns +self+.
276
863
  #
277
- # This method returns the table for chaining.
864
+ # Removes rows when the access mode is <tt>:row</tt> or <tt>:col_or_row</tt>;
865
+ # calls the block with each \CSV::Row object:
866
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
867
+ # table = CSV.parse(source, headers: true)
868
+ # table.by_row! # => #<CSV::Table mode:row row_count:4>
869
+ # table.size # => 3
870
+ # table.delete_if {|row| row['Name'].start_with?('b') }
871
+ # table.size # => 1
278
872
  #
279
- # If no block is given, an Enumerator is returned.
873
+ # Removes columns when the access mode is <tt>:col</tt>;
874
+ # calls the block with each column as a 2-element array
875
+ # containing the header and an \Array of column fields:
876
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
877
+ # table = CSV.parse(source, headers: true)
878
+ # table.by_col! # => #<CSV::Table mode:col row_count:4>
879
+ # table.headers.size # => 2
880
+ # table.delete_if {|column_data| column_data[1].include?('2') }
881
+ # table.headers.size # => 1
280
882
  #
883
+ # Returns a new \Enumerator if no block is given:
884
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
885
+ # table = CSV.parse(source, headers: true)
886
+ # table.delete_if # => #<Enumerator: #<CSV::Table mode:col_or_row row_count:4>:delete_if>
281
887
  def delete_if(&block)
282
888
  return enum_for(__method__) { @mode == :row or @mode == :col_or_row ? size : headers.size } unless block_given?
283
889
 
284
890
  if @mode == :row or @mode == :col_or_row # by index
285
891
  @table.delete_if(&block)
286
892
  else # by header
287
- deleted = []
288
893
  headers.each do |header|
289
- deleted << delete(header) if yield([header, self[header]])
894
+ delete(header) if yield([header, self[header]])
290
895
  end
291
896
  end
292
897
 
@@ -295,20 +900,40 @@ class CSV
295
900
 
296
901
  include Enumerable
297
902
 
903
+ # :call-seq:
904
+ # table.each {|row_or_column| ... ) -> self
298
905
  #
299
- # In the default mixed mode or row mode, iteration is the standard row major
300
- # walking of rows. In column mode, iteration will +yield+ two element
301
- # tuples containing the column name and an Array of values for that column.
906
+ # Calls the block with each row or column; returns +self+.
302
907
  #
303
- # This method returns the table for chaining.
908
+ # When the access mode is <tt>:row</tt> or <tt>:col_or_row</tt>,
909
+ # calls the block with each \CSV::Row object:
910
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
911
+ # table = CSV.parse(source, headers: true)
912
+ # table.by_row! # => #<CSV::Table mode:row row_count:4>
913
+ # table.each {|row| p row }
914
+ # Output:
915
+ # #<CSV::Row "Name":"foo" "Value":"0">
916
+ # #<CSV::Row "Name":"bar" "Value":"1">
917
+ # #<CSV::Row "Name":"baz" "Value":"2">
304
918
  #
305
- # If no block is given, an Enumerator is returned.
919
+ # When the access mode is <tt>:col</tt>,
920
+ # calls the block with each column as a 2-element array
921
+ # containing the header and an \Array of column fields:
922
+ # table.by_col! # => #<CSV::Table mode:col row_count:4>
923
+ # table.each {|column_data| p column_data }
924
+ # Output:
925
+ # ["Name", ["foo", "bar", "baz"]]
926
+ # ["Value", ["0", "1", "2"]]
306
927
  #
928
+ # Returns a new \Enumerator if no block is given:
929
+ # table.each # => #<Enumerator: #<CSV::Table mode:col row_count:4>:each>
307
930
  def each(&block)
308
931
  return enum_for(__method__) { @mode == :col ? headers.size : size } unless block_given?
309
932
 
310
933
  if @mode == :col
311
- headers.each { |header| yield([header, self[header]]) }
934
+ headers.each.with_index do |header, i|
935
+ yield([header, @table.map {|row| row[header, i]}])
936
+ end
312
937
  else
313
938
  @table.each(&block)
314
939
  end
@@ -316,16 +941,40 @@ class CSV
316
941
  self # for chaining
317
942
  end
318
943
 
319
- # Returns +true+ if all rows of this table ==() +other+'s rows.
944
+ # :call-seq:
945
+ # table == other_table -> true or false
946
+ #
947
+ # Returns +true+ if all each row of +self+ <tt>==</tt>
948
+ # the corresponding row of +other_table+, otherwise, +false+.
949
+ #
950
+ # The access mode does no affect the result.
951
+ #
952
+ # Equal tables:
953
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
954
+ # table = CSV.parse(source, headers: true)
955
+ # other_table = CSV.parse(source, headers: true)
956
+ # table == other_table # => true
957
+ #
958
+ # Different row count:
959
+ # other_table.delete(2)
960
+ # table == other_table # => false
961
+ #
962
+ # Different last row:
963
+ # other_table << ['bat', 3]
964
+ # table == other_table # => false
320
965
  def ==(other)
321
966
  return @table == other.table if other.is_a? CSV::Table
322
967
  @table == other
323
968
  end
324
969
 
970
+ # :call-seq:
971
+ # table.to_a -> array_of_arrays
325
972
  #
326
- # Returns the table as an Array of Arrays. Headers will be the first row,
327
- # then all of the field rows will follow.
328
- #
973
+ # Returns the table as an \Array of \Arrays;
974
+ # the headers are in the first row:
975
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
976
+ # table = CSV.parse(source, headers: true)
977
+ # table.to_a # => [["Name", "Value"], ["foo", "0"], ["bar", "1"], ["baz", "2"]]
329
978
  def to_a
330
979
  array = [headers]
331
980
  @table.each do |row|
@@ -335,17 +984,30 @@ class CSV
335
984
  array
336
985
  end
337
986
 
987
+ # :call-seq:
988
+ # table.to_csv(**options) -> csv_string
338
989
  #
339
- # Returns the table as a complete CSV String. Headers will be listed first,
340
- # then all of the field rows.
990
+ # Returns the table as \CSV string.
991
+ # See {Options for Generating}[../CSV.html#class-CSV-label-Options+for+Generating].
341
992
  #
342
- # This method assumes you want the Table.headers(), unless you explicitly
343
- # pass <tt>:write_headers => false</tt>.
993
+ # Defaults option +write_headers+ to +true+:
994
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
995
+ # table = CSV.parse(source, headers: true)
996
+ # table.to_csv # => "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
344
997
  #
345
- def to_csv(write_headers: true, **options)
346
- array = write_headers ? [headers.to_csv(options)] : []
347
- @table.each do |row|
348
- array.push(row.fields.to_csv(options)) unless row.header_row?
998
+ # Omits the headers if option +write_headers+ is given as +false+
999
+ # (see {Option +write_headers+}[../CSV.html#class-CSV-label-Option+write_headers]):
1000
+ # table.to_csv(write_headers: false) # => "foo,0\nbar,1\nbaz,2\n"
1001
+ #
1002
+ # Limit rows if option +limit+ is given like +2+:
1003
+ # table.to_csv(limit: 2) # => "Name,Value\nfoo,0\nbar,1\n"
1004
+ def to_csv(write_headers: true, limit: nil, **options)
1005
+ array = write_headers ? [headers.to_csv(**options)] : []
1006
+ limit ||= @table.size
1007
+ limit = @table.size + 1 + limit if limit < 0
1008
+ limit = 0 if limit < 0
1009
+ @table.first(limit).each do |row|
1010
+ array.push(row.fields.to_csv(**options)) unless row.header_row?
349
1011
  end
350
1012
 
351
1013
  array.join("")
@@ -370,9 +1032,24 @@ class CSV
370
1032
  end
371
1033
  end
372
1034
 
373
- # Shows the mode and size of this table in a US-ASCII String.
1035
+ # :call-seq:
1036
+ # table.inspect => string
1037
+ #
1038
+ # Returns a <tt>US-ASCII</tt>-encoded \String showing table:
1039
+ # - Class: <tt>CSV::Table</tt>.
1040
+ # - Access mode: <tt>:row</tt>, <tt>:col</tt>, or <tt>:col_or_row</tt>.
1041
+ # - Size: Row count, including the header row.
1042
+ #
1043
+ # Example:
1044
+ # source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
1045
+ # table = CSV.parse(source, headers: true)
1046
+ # table.inspect # => "#<CSV::Table mode:col_or_row row_count:4>\nName,Value\nfoo,0\nbar,1\nbaz,2\n"
1047
+ #
374
1048
  def inspect
375
- "#<#{self.class} mode:#{@mode} row_count:#{to_a.size}>".encode("US-ASCII")
1049
+ inspected = +"#<#{self.class} mode:#{@mode} row_count:#{to_a.size}>"
1050
+ summary = to_csv(limit: 5)
1051
+ inspected << "\n" << summary if summary.encoding.ascii_compatible?
1052
+ inspected
376
1053
  end
377
1054
  end
378
- end
1055
+ end