ruport 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/AUTHORS +7 -3
  2. data/Rakefile +8 -9
  3. data/TODO +16 -0
  4. data/examples/RWEmerson.jpg +0 -0
  5. data/examples/centered_pdf_text_box.rb +66 -0
  6. data/examples/invoice.rb +35 -25
  7. data/examples/invoice_report.rb +1 -1
  8. data/examples/line_plotter.rb +1 -1
  9. data/examples/pdf_table_with_title.rb +42 -0
  10. data/lib/ruport.rb +5 -7
  11. data/lib/ruport.rb.rej +41 -0
  12. data/lib/ruport.rb~ +85 -0
  13. data/lib/ruport/attempt.rb +59 -59
  14. data/lib/ruport/config.rb +15 -4
  15. data/lib/ruport/data.rb +0 -2
  16. data/lib/ruport/data/groupable.rb +25 -16
  17. data/lib/ruport/data/record.rb +128 -102
  18. data/lib/ruport/data/table.rb +352 -199
  19. data/lib/ruport/data/taggable.rb +18 -7
  20. data/lib/ruport/format/html.rb +3 -1
  21. data/lib/ruport/format/latex.rb +1 -1
  22. data/lib/ruport/format/latex.rb.rej +26 -0
  23. data/lib/ruport/format/latex.rb~ +47 -0
  24. data/lib/ruport/format/pdf.rb +111 -28
  25. data/lib/ruport/format/pdf.rb.rej +168 -0
  26. data/lib/ruport/format/pdf.rb~ +189 -0
  27. data/lib/ruport/format/plugin.rb +0 -5
  28. data/lib/ruport/format/svg.rb +4 -4
  29. data/lib/ruport/format/xml.rb +3 -3
  30. data/lib/ruport/generator.rb +66 -27
  31. data/lib/ruport/mailer.rb +4 -1
  32. data/lib/ruport/query.rb +13 -1
  33. data/lib/ruport/renderer.rb +89 -17
  34. data/lib/ruport/renderer/graph.rb +5 -5
  35. data/lib/ruport/renderer/table.rb +8 -9
  36. data/lib/ruport/report.rb +2 -6
  37. data/test/test_config.rb +88 -76
  38. data/test/{test_text_table.rb → test_format_text.rb} +4 -2
  39. data/test/test_groupable.rb +15 -13
  40. data/test/test_query.rb +6 -3
  41. data/test/test_record.rb +57 -33
  42. data/test/test_renderer.rb +77 -0
  43. data/test/test_report.rb +188 -181
  44. data/test/test_ruport.rb +5 -6
  45. data/test/test_table.rb +290 -190
  46. data/test/test_table_renderer.rb +56 -8
  47. data/test/test_taggable.rb +7 -8
  48. data/test/unit.log +259 -7
  49. metadata +22 -19
  50. data/lib/ruport/data/collection.rb +0 -65
  51. data/lib/ruport/data/set.rb +0 -148
  52. data/test/test_collection.rb +0 -30
  53. data/test/test_set.rb +0 -118
@@ -4,9 +4,42 @@
4
4
  # This is Free Software. For details, see LICENSE and COPYING
5
5
  # Copyright 2006 by respective content owners, all rights reserved.
6
6
 
7
+ module Ruport::Data
8
+
9
+ # === Overview
10
+ #
11
+ # This class implements some base features for Ruport::Data::Table,
12
+ # and may be used to make interaction with Data::Table like classes
13
+ # easier
14
+ #
15
+ module Collection
16
+ include Enumerable
17
+ include Taggable
7
18
 
8
- module Ruport::Data
19
+ # A simple formatting tool which allows you to quickly generate a formatted
20
+ # table from a <tt>Collection</tt> object.
21
+ #
22
+ # If a block is given, the Renderer::Table object will be yielded
23
+ #
24
+ # Example:
25
+ # my_collection.as(:csv) #=> "1,2,3\n4,5,6"
26
+ #
27
+ # my_collection.as(:csv) { |e| e.layout.show_table_headers = false }
28
+ #
29
+ def as(type)
30
+ Ruport::Renderer::Table.render(type) do |rend|
31
+ rend.data = self
32
+ yield(rend) if block_given?
33
+ end
34
+ end
35
+
36
+ # Converts a <tt>Collection</tt> object to a <tt>Data::Table</tt>.
37
+ def to_table(options={})
38
+ Table.new({:data => data.map { |r| r.to_a }}.merge(options))
39
+ end
9
40
 
41
+ end
42
+
10
43
  # === Overview
11
44
  #
12
45
  # This class is one of the core classes for building and working with data
@@ -20,9 +53,13 @@ module Ruport::Data
20
53
  # Once your data is in a Ruport::Data::Table object, it can be manipulated
21
54
  # to suit your needs, then used to build a report.
22
55
  #
23
- class Table < Collection
24
- include Groupable
56
+ class Table
57
+ include Collection
58
+ include Groupable
25
59
 
60
+ require "forwardable"
61
+ extend Forwardable
62
+
26
63
  # Creates a new table based on the supplied options.
27
64
  # Valid options:
28
65
  # <b><tt>:data</tt></b>:: An Array of Arrays representing the
@@ -36,6 +73,8 @@ module Ruport::Data
36
73
  #
37
74
  def initialize(options={})
38
75
  @column_names = options[:column_names] ? options[:column_names].dup : []
76
+ @record_class = options[:record_class] &&
77
+ options[:record_class].name || "Ruport::Data::Record"
39
78
  @data = []
40
79
  if options[:data]
41
80
  if options[:data].all? { |r| r.kind_of? Record }
@@ -49,22 +88,39 @@ module Ruport::Data
49
88
 
50
89
  # This Table's column names.
51
90
  attr_reader :column_names
52
- def_delegator :@data, :[]
91
+
92
+ attr_reader :data
93
+ def_delegators :@data, :each, :length, :size, :empty?, :[]
53
94
 
95
+
54
96
  # Sets the column names for this table. <tt>new_column_names</tt> should
55
97
  # be an array listing the names of the columns.
56
98
  #
57
99
  # Example:
58
- #
100
+ #
59
101
  # table = Table.new :data => [1,2,3], [3,4,5],
60
102
  # :column_names => %w[a b c]
61
103
  #
62
104
  # table.column_names = %w[e f g]
63
105
  #
64
106
  def column_names=(new_column_names)
107
+ columns = new_column_names.zip(@column_names)
65
108
  @column_names.replace(new_column_names.dup)
109
+ unless @data.empty?
110
+ each { |r|
111
+ columns.each_with_index { |x,i|
112
+ if x[1].nil?
113
+ r.rename_attribute(i,x[0])
114
+ elsif x[1] != x[0]
115
+ r.rename_attribute(x[1],x[0],false)
116
+ end
117
+ }
118
+ r.send(:reindex, @column_names)
119
+ }
120
+ end
66
121
  end
67
122
 
123
+
68
124
  # Compares this Table to another Table and returns <tt>true</tt> if
69
125
  # both the <tt>data</tt> and <tt>column_names</tt> are equal.
70
126
  #
@@ -84,18 +140,6 @@ module Ruport::Data
84
140
 
85
141
  alias_method :==, :eql?
86
142
 
87
- # Uses Ruport's built-in text plugin to render this Table into a String
88
- #
89
- # Example:
90
- #
91
- # data = Table.new :data => [1,2], [3,4],
92
- # :column_names => %w[a b]
93
- # puts data.to_s
94
- #
95
- def to_s
96
- as(:text)
97
- end
98
-
99
143
  # Used to add extra data to the Table. <tt>other</tt> can be an Array,
100
144
  # Hash or Record.
101
145
  #
@@ -110,23 +154,28 @@ module Ruport::Data
110
154
  def <<(other)
111
155
  case other
112
156
  when Array
113
- @data << Record.new(other, :attributes => @column_names)
157
+ attributes = @column_names.empty? ? nil : @column_names
158
+ @data << record_class.new(other, :attributes => attributes)
114
159
  when Hash
115
160
  raise ArgumentError unless @column_names
116
161
  arr = @column_names.map { |k| other[k] }
117
- @data << Record.new(arr, :attributes => @column_names)
118
- when Record
119
- raise ArgumentError unless column_names.eql? other.attributes
120
- @data << Record.new(other.data, :attributes => @column_names)
162
+ @data << record_class.new(arr, :attributes => @column_names)
163
+ when record_class
164
+ self << other.to_h
121
165
  @data.last.tags = other.tags.dup
122
166
  else
123
167
  raise ArgumentError
124
168
  end
125
169
  self
126
170
  end
171
+
172
+ def record_class
173
+ @record_class.split("::").inject(Class) { |c,el| c.send(:const_get,el) }
174
+ end
127
175
 
128
- # Used to combine two Tables. Throws an ArgumentError if the Tables don't
129
- # have identical columns.
176
+
177
+ # Used to merge two Tables by rows.
178
+ # Throws an ArgumentError if the Tables don't have identical columns.
130
179
  #
131
180
  # Example:
132
181
  #
@@ -144,34 +193,8 @@ module Ruport::Data
144
193
  Table.new(:column_names => @column_names, :data => @data + other.data)
145
194
  end
146
195
 
147
- # Reorders the columns that exist in the Table. Modifies this Table
148
- # in-place.
149
- #
150
- # Example:
151
- #
152
- # data = Table.new :data => [1,2], [3,4],
153
- # :column_names => %w[a b]
154
- #
155
- # data.reorder!([1,0])
156
- #
157
- def reorder!(*indices)
158
- indices = indices[0] if indices[0].kind_of? Array
159
-
160
- if @column_names && !@column_names.empty?
161
- x = if indices.all? { |i| i.kind_of? Integer }
162
- indices.map { |i| @column_names[i] }
163
- else
164
- indices
165
- end
166
- @column_names = x
167
- end
168
- @data.each { |r|
169
- r.reorder_data!(*indices)
170
- r.attributes = @column_names
171
- }; self
172
- end
173
-
174
- # Returns a copy of the Table with its columns in the requested order.
196
+
197
+ # Reorders the table's columns.
175
198
  #
176
199
  # Example:
177
200
  #
@@ -181,177 +204,212 @@ module Ruport::Data
181
204
  # two = one.reorder!([1,0])
182
205
  #
183
206
  def reorder(*indices)
184
- dup.reorder!(*indices)
185
- end
207
+ indices = indices[0] if indices[0].kind_of? Array
208
+
209
+ if indices.all? { |i| i.kind_of? Integer }
210
+ indices.map! { |i| @column_names[i] }
211
+ end
212
+
213
+ @column_names = indices
214
+
215
+ @data.each { |r|
216
+ r.attributes = @column_names
217
+ }; self
218
+ end
186
219
 
220
+
187
221
  # Adds an extra column to the Table. Available Options:
188
222
  #
189
- # <b><tt>:name</tt></b>:: The new column's name (required)
190
223
  # <b><tt>:fill</tt></b>:: The default value to use for the column in
191
224
  # existing rows. Set to nil if not specified.
225
+ #
226
+ # <b><tt>:position</tt></b>:: Inserts the column at the indicated position
227
+ # number.
228
+ #
229
+ # <b><tt>:before</tt></b>:: Inserts the new column before the column
230
+ # indicated. (by name)
231
+ #
232
+ # <b><tt>:after</tt></b>:: Inserts the new column after the column
233
+ # indicated. (by name)
234
+ #
235
+ # If a block is provided, it will be used to build up the column.
236
+ #
192
237
  #
193
238
  # Example:
194
239
  #
195
- # data = Table.new :data => [1,2], [3,4],
196
- # :column_names => %w[a b]
240
+ # data = Table("a","b") { |t| t << [1,2] << [3,4] }
241
+ #
242
+ # #basic usage, column full of 1's
243
+ # data.add_column 'new_column', :default => 1
244
+ #
245
+ # #new empty column before new_column
246
+ # data.add_column 'new_col2', :before => 'new_column'
197
247
  #
198
- # data.append_column :name => 'new_column', :fill => 1
248
+ # # new column placed just after column a
249
+ # data.add_column 'new_col3', :position => 1
199
250
  #
200
- def append_column(options={})
201
- self.column_names += [options[:name]] if options[:name]
251
+ # # new column built via a block, added at the end of the table
252
+ # data.add_column("new_col4") { |r| r.a + r.b }
253
+ #
254
+ def add_column(name,options={})
255
+ if pos = options[:position]
256
+ column_names.insert(pos,name)
257
+ elsif pos = options[:after]
258
+ column_names.insert(column_names.index(pos)+1,name)
259
+ elsif pos = options[:before]
260
+ column_names.insert(column_names.index(pos),name)
261
+ else
262
+ column_names << name
263
+ end
264
+
202
265
  if block_given?
203
- each { |r| r.data << yield(r) || options[:fill] }
266
+ each { |r| r[name] = yield(r) || options[:default] }
204
267
  else
205
- each { |r| r.data << options[:fill] }
268
+ each { |r| r[name] = options[:default] }
206
269
  end; self
207
270
  end
208
-
209
- # Removes a column from the Table. Any values in the specified column are
210
- # lost.
271
+
272
+ # Removes the given column from the table. May use name or position.
211
273
  #
212
274
  # Example:
213
275
  #
214
- # data = Table.new :data => [[1,2], [3,4]], :column_names => %w[a b]
215
- # data.append_column :name => 'new_column', :fill => 1
216
- # data.remove_column :name => 'new_column'
217
- # data == Table.new :data => [[1,2], [3,4]],
218
- # :column_names => %w[a b] #=> true
219
- # data = [[1,2],[3,4]].to_table
220
- # data.remove_column(1)
221
- # data.eql? [[1],[3]].to_table %w[a] #=> true
222
- #
223
- def remove_column(options={})
224
- if options.kind_of? Integer
225
- return reorder!((0...data[0].length).to_a - [options])
226
- elsif options.kind_of? Hash
227
- name = options[:name]
228
- else
229
- name = options
230
- end
231
-
232
- raise ArgumentError unless column_names.include? name
233
- reorder! column_names - [name]
276
+ # table.remove_column(0) #=> removes the first column
277
+ # table.remove_column("apple") #=> removes column named apple
278
+ def remove_column(col)
279
+ col = column_names[col] if col.kind_of? Fixnum
280
+ column_names.delete(col)
281
+ each { |r| r.send(:delete,col) }
234
282
  end
235
-
236
- # Create a copy of the Table: records will be copied as well.
283
+
284
+ # Removes multiple columns from the table. May use name or position
285
+ # Will autosplat arrays.
237
286
  #
238
287
  # Example:
239
- #
240
- # one = Table.new :data => [1,2], [3,4],
241
- # :column_names => %w[a b]
242
- # two = one.dup
243
- #
244
- def dup
245
- a = self.class.new(:data => @data, :column_names => @column_names)
246
- a.tags = tags.dup
247
- return a
288
+ # table.remove_columns('a','b','c')
289
+ # table.remove_columns([0,1])
290
+ def remove_columns(*cols)
291
+ cols = cols[0] if cols[0].kind_of? Array
292
+ cols.each { |col| remove_column(col) }
248
293
  end
249
-
250
- # Loads a CSV file directly into a Table using the FasterCSV library.
251
- #
294
+
295
+ # Renames a column. Will update Record attributes as well
296
+ #
252
297
  # Example:
253
- #
254
- # # treat first row as column_names
255
- # table = Table.load('mydata.csv')
256
298
  #
257
- # # do not assume the data has column_names
258
- # table = Table.load('mydata.csv',:has_names => false)
259
- #
260
- # # pass in FasterCSV options, such as column separators
261
- # table = Table.load('mydata.csv',:csv_opts => { :col_sep => "\t" })
262
- #
263
- def self.load(csv_file, options={},&block)
264
- get_table_from_csv(:foreach, csv_file, options,&block)
299
+ # old_values = table.map { |r| r.a }
300
+ # table.rename_column("a","zanzibar")
301
+ # new_values = table.map { |r| r.zanzibar }
302
+ # old_values == new_values #=> true
303
+ # table.column_names.include?("a") #=> false
304
+ def rename_column(old_name,new_name)
305
+ self.column_names[column_names.index(old_name)] = new_name
306
+ each { |r| r.rename_attribute(old_name,new_name,false)}
265
307
  end
266
308
 
267
- # Creates a Table from a CSV string using FasterCSV. See Table.load for
268
- # additional examples.
269
- #
270
- # table = Table.parse("a,b,c\n1,2,3\n4,5,6\n")
271
- #
272
- def self.parse(string, options={},&block)
273
- get_table_from_csv(:parse,string,options,&block)
274
- end
275
309
 
276
- def self.get_table_from_csv(msg,param,options={},&block) #:nodoc:
277
- options = {:has_names => true,
278
- :csv_options => {} }.merge(options)
279
- require "fastercsv"
280
- loaded_data = self.new
281
-
282
- first_line = true
283
- FasterCSV.send(msg,param,options[:csv_options]) do |row|
284
- if first_line && options[:has_names]
285
- loaded_data.column_names = row
286
- first_line = false
287
- elsif !block
288
- loaded_data << row
289
- else
290
- block[loaded_data,row]
291
- end
292
- end ; loaded_data
310
+ # Exchanges one column with another.
311
+ #
312
+ # Example:
313
+ #
314
+ # >> a = Table(%w[a b c]) { |t| t << [1,2,3] << [4,5,6] }
315
+ # >> puts a
316
+ # +-----------+
317
+ # | a | b | c |
318
+ # +-----------+
319
+ # | 1 | 2 | 3 |
320
+ # | 4 | 5 | 6 |
321
+ # +-----------+
322
+ # >> a.swap_column("a","c")
323
+ # >> puts a
324
+ # +-----------+
325
+ # | c | b | a |
326
+ # +-----------+
327
+ # | 3 | 2 | 1 |
328
+ # | 6 | 5 | 4 |
329
+ # +-----------+
330
+ def swap_column(a,b)
331
+ if [a,b].all? { |r| r.kind_of? Fixnum }
332
+ col_a,col_b = column_names[a],column_names[b]
333
+ column_names[a] = col_b
334
+ column_names[b] = col_a
335
+ else
336
+ a_ind, b_ind = [column_names.index(a), column_names.index(b)]
337
+ column_names[b_ind] = a
338
+ column_names[a_ind] = b
339
+ end
293
340
  end
294
-
295
- # Allows you to split Tables into multiple Tables for grouping.
341
+
342
+ # Allows you to specify a new column to replace an existing column
343
+ # in your table via a block.
344
+ #
345
+ # Example:
346
+ #
347
+ # >> a = Table(%w[a b c]) { |t| t << [1,2,3] << [4,5,6] }
348
+ # >> a.replace_column("c","c2") { |r| r.c * 2 + r.a }
349
+ #
350
+ # >> puts a
351
+ # +------------+
352
+ # | a | b | c2 |
353
+ # +------------+
354
+ # | 1 | 2 | 7 |
355
+ # | 4 | 5 | 16 |
356
+ # +------------+
357
+ def replace_column(old_col,new_col,&block)
358
+ add_column(new_col,:after => old_col,&block)
359
+ remove_column(old_col)
360
+ end
361
+
362
+ # Generates a sub table
363
+ #
364
+ # Examples:
365
+ #
366
+ # table = [[1,2,3,4],[5,6,7,8],[9,10,11,12]].to_table(%w[a b c d])
296
367
  #
297
- # Example:
368
+ # Using column_names and a range:
298
369
  #
299
- # a = Table.new(:column_name => %w[name a b c])
300
- # a << ["greg",1,2,3]
301
- # a << ["joe", 2,3,4]
302
- # a << ["greg",7,8,9]
303
- # a << ["joe", 1,2,3]
370
+ # sub_table = table.sub_table(%w[a b],1..-2)
371
+ # sub_table == [[1,2],[3,4]].to_table(%w[a b]) #=> true
304
372
  #
305
- # b = a.split :group => "name"
373
+ # Using just column_names:
306
374
  #
307
- # b.greg.eql? [[1,2,3],[7,8,9]].to_table(%w[a b c]) #=> true
308
- # b["joe"].eql? [[2,3,4],[1,2,3]].to_table(%w[a b c]) #=> true
375
+ # sub_table = table.sub_table(%w[a d])
376
+ # sub_table == [[1,4],[5,8],[9,12]].to_table(%w[a d]) #=> true
309
377
  #
310
- # You can also pass an Array to <tt>:group</tt>, and the resulting
311
- # attributes in the group will be joined by an underscore.
378
+ # Using column_names and a block:
312
379
  #
313
- # Example:
314
- #
315
- # a = Table.new(:column_names => %w[first_name last_name x]
316
- # a << %w[greg brown foo]
317
- # a << %w[greg gibson bar]
318
- # a << %w[greg brown baz]
319
- #
320
- # b = a.split :group => %w[first_name last_name]
321
- # a.greg_brown.length #=> 2
322
- # a["greg_gibson"].length #=> 1
323
- # a.greg_brown[0].x #=> "foo"
324
- #
325
- def split(options={})
326
- if options[:group].kind_of? Array
327
- group = map { |r| options[:group].map { |e| r[e] } }.uniq
328
- data = group.inject([]) { |s,g|
329
- s + [select { |r| options[:group].map { |e| r[e] }.eql?(g) }]
330
- }
331
- c = column_names - options[:group]
332
- else
333
- group = map { |r| r[options[:group]] }.uniq
334
- data = group.inject([]) { |s,g|
335
- s + [select { |r| r[options[:group]].eql?(g) }]
336
- }
337
- c = column_names - [options[:group]]
338
-
339
- end
340
- data.map! { |g|
341
- Ruport::Data::Table.new(
342
- :data => g.map { |x| x.reorder(*c) },
343
- :column_names => c
344
- )
345
- }
346
- rec = if options[:group].kind_of? Array
347
- Ruport::Data::Record.new(data,
348
- :attributes => group.map { |e| e.join("_") } )
380
+ # sub_table = table.sub_table(%w[d b]) { |r| r.a < 6 }
381
+ # sub_table == [[4,2],[8,6]].to_table(%w[b d]) #=> true
382
+ #
383
+ # Using just a block:
384
+ #
385
+ # sub_table = table.sub_table { |r| r.c > 10 }
386
+ # sub_table == [[9,10,11,12]].to_table(%w[a b c d]) #=> true
387
+ #
388
+ # FIXME: loses tags
389
+ def sub_table(columns=column_names,range=nil)
390
+ Table(columns) do |t|
391
+ if range
392
+ data[range].each { |r| t << r }
393
+ elsif block_given?
394
+ data.each { |r| t << r if yield(r) }
395
+ else
396
+ data.each { |r| t << r }
397
+ end
398
+ end
399
+ end
400
+
401
+ # returns an array of values for the given column_name
402
+ def column(name)
403
+ case(name)
404
+ when Integer
405
+ unless column_names.empty?
406
+ raise ArgumentError if name > column_names.length
407
+ end
349
408
  else
350
- Ruport::Data::Record.new data, :attributes => group
409
+ raise ArgumentError unless column_names.include?(name)
351
410
  end
352
- class << rec
353
- def each_group; attributes.each { |a| yield(a) }; end
354
- end; rec
411
+
412
+ map { |r| r[name] }
355
413
  end
356
414
 
357
415
  # Calculates sums. If a column name or index is given, it will try to
@@ -380,11 +438,12 @@ module Ruport::Data
380
438
  else
381
439
  s + yield(r)
382
440
  end
383
- }
441
+ }
384
442
  end
385
443
 
386
444
  alias_method :sum, :sigma
387
-
445
+
446
+ #
388
447
  # Returns a sorted table. If col_names is specified,
389
448
  # the block is ignored and the table is sorted by the named columns. All
390
449
  # options are used in constructing the new Table (see Array#to_table
@@ -424,9 +483,104 @@ module Ruport::Data
424
483
  table.tags = self.tags
425
484
  return table
426
485
  end
427
-
428
- end
429
486
 
487
+ def rows_with(columns,&block)
488
+ select { |r|
489
+ if block
490
+ block[*(columns.map { |c| r.get(c) })]
491
+ else
492
+ columns.all? { |k,v| r.get(k) == v }
493
+ end
494
+ }
495
+ end
496
+
497
+ # Create a copy of the Table: records will be copied as well.
498
+ #
499
+ # Example:
500
+ #
501
+ # one = Table.new :data => [1,2], [3,4],
502
+ # :column_names => %w[a b]
503
+ # two = one.dup
504
+ #
505
+ def dup
506
+ a = self.class.new(:data => @data, :column_names => @column_names)
507
+ a.tags = tags.dup
508
+ return a
509
+ end
510
+
511
+ #
512
+ # Uses Ruport's built-in text plugin to render this Table into a String
513
+ #
514
+ # Example:
515
+ #
516
+ # data = Table.new :data => [1,2], [3,4],
517
+ # :column_names => %w[a b]
518
+ # puts data.to_s
519
+ #
520
+ def to_s
521
+ as(:text)
522
+ end
523
+
524
+ # NOTE: does not respect tainted status
525
+ alias_method :clone, :dup
526
+
527
+
528
+ # Provides a shortcut for the <tt>as()</tt> method by converting a call to
529
+ # <tt>as(:format_name)</tt> into a call to <tt>to_format_name</tt>
530
+ def method_missing(id,*args)
531
+ return as($1.to_sym) if id.to_s =~ /^to_(.*)/
532
+ return rows_with($1.to_sym => args[0]) if id.to_s =~ /^rows_with_(.*)/
533
+ super
534
+ end
535
+
536
+ # Loads a CSV file directly into a Table using the FasterCSV library.
537
+ #
538
+ # Example:
539
+ #
540
+ # # treat first row as column_names
541
+ # table = Table.load('mydata.csv')
542
+ #
543
+ # # do not assume the data has column_names
544
+ # table = Table.load('mydata.csv',:has_names => false)
545
+ #
546
+ # # pass in FasterCSV options, such as column separators
547
+ # table = Table.load('mydata.csv',:csv_opts => { :col_sep => "\t" })
548
+ #
549
+ def self.load(csv_file, options={},&block)
550
+ get_table_from_csv(:foreach, csv_file, options,&block)
551
+ end
552
+
553
+ #
554
+ # Creates a Table from a CSV string using FasterCSV. See Table.load for
555
+ # additional examples.
556
+ #
557
+ # table = Table.parse("a,b,c\n1,2,3\n4,5,6\n")
558
+ #
559
+ def self.parse(string, options={},&block)
560
+ get_table_from_csv(:parse,string,options,&block)
561
+ end
562
+
563
+ private
564
+
565
+ def self.get_table_from_csv(msg,param,options={},&block) #:nodoc:
566
+ options = {:has_names => true,
567
+ :csv_options => {} }.merge(options)
568
+ require "fastercsv"
569
+ loaded_data = self.new
570
+
571
+ first_line = true
572
+ FasterCSV.send(msg,param,options[:csv_options]) do |row|
573
+ if first_line && options[:has_names]
574
+ loaded_data.column_names = row
575
+ first_line = false
576
+ elsif !block
577
+ loaded_data << row
578
+ else
579
+ block[loaded_data,row]
580
+ end
581
+ end ; loaded_data
582
+ end
583
+ end
430
584
  end
431
585
 
432
586
 
@@ -452,21 +606,20 @@ module Kernel
452
606
  table=
453
607
  case(args[0])
454
608
  when Array
455
- [].to_table(args[0])
609
+ opts = args[1] || {}
610
+ Ruport::Data::Table.new(f={:column_names => args[0]}.merge(opts))
456
611
  when /\.csv/
457
612
  Ruport::Data::Table.load(*args)
458
613
  else
459
614
  [].to_table(args)
460
- end
461
-
615
+ end
616
+
462
617
  block[table] if block
463
-
464
618
  return table
465
619
  end
466
- end
620
+ end
467
621
 
468
622
  class Array
469
-
470
623
  #
471
624
  # Converts an array to a Ruport::Data::Table object, ready to
472
625
  # use in your reports.
@@ -477,5 +630,5 @@ class Array
477
630
  def to_table(column_names=nil)
478
631
  Ruport::Data::Table.new({:data => self, :column_names => column_names})
479
632
  end
480
- end
481
633
 
634
+ end