csv 1.0.2 → 3.2.7

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.
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