ruport 0.7.2 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +7 -3
- data/Rakefile +8 -9
- data/TODO +16 -0
- data/examples/RWEmerson.jpg +0 -0
- data/examples/centered_pdf_text_box.rb +66 -0
- data/examples/invoice.rb +35 -25
- data/examples/invoice_report.rb +1 -1
- data/examples/line_plotter.rb +1 -1
- data/examples/pdf_table_with_title.rb +42 -0
- data/lib/ruport.rb +5 -7
- data/lib/ruport.rb.rej +41 -0
- data/lib/ruport.rb~ +85 -0
- data/lib/ruport/attempt.rb +59 -59
- data/lib/ruport/config.rb +15 -4
- data/lib/ruport/data.rb +0 -2
- data/lib/ruport/data/groupable.rb +25 -16
- data/lib/ruport/data/record.rb +128 -102
- data/lib/ruport/data/table.rb +352 -199
- data/lib/ruport/data/taggable.rb +18 -7
- data/lib/ruport/format/html.rb +3 -1
- data/lib/ruport/format/latex.rb +1 -1
- data/lib/ruport/format/latex.rb.rej +26 -0
- data/lib/ruport/format/latex.rb~ +47 -0
- data/lib/ruport/format/pdf.rb +111 -28
- data/lib/ruport/format/pdf.rb.rej +168 -0
- data/lib/ruport/format/pdf.rb~ +189 -0
- data/lib/ruport/format/plugin.rb +0 -5
- data/lib/ruport/format/svg.rb +4 -4
- data/lib/ruport/format/xml.rb +3 -3
- data/lib/ruport/generator.rb +66 -27
- data/lib/ruport/mailer.rb +4 -1
- data/lib/ruport/query.rb +13 -1
- data/lib/ruport/renderer.rb +89 -17
- data/lib/ruport/renderer/graph.rb +5 -5
- data/lib/ruport/renderer/table.rb +8 -9
- data/lib/ruport/report.rb +2 -6
- data/test/test_config.rb +88 -76
- data/test/{test_text_table.rb → test_format_text.rb} +4 -2
- data/test/test_groupable.rb +15 -13
- data/test/test_query.rb +6 -3
- data/test/test_record.rb +57 -33
- data/test/test_renderer.rb +77 -0
- data/test/test_report.rb +188 -181
- data/test/test_ruport.rb +5 -6
- data/test/test_table.rb +290 -190
- data/test/test_table_renderer.rb +56 -8
- data/test/test_taggable.rb +7 -8
- data/test/unit.log +259 -7
- metadata +22 -19
- data/lib/ruport/data/collection.rb +0 -65
- data/lib/ruport/data/set.rb +0 -148
- data/test/test_collection.rb +0 -30
- data/test/test_set.rb +0 -118
data/lib/ruport/data/table.rb
CHANGED
@@ -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
|
-
|
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
|
24
|
-
include
|
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
|
-
|
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
|
-
|
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 <<
|
118
|
-
when
|
119
|
-
|
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
|
-
|
129
|
-
#
|
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
|
-
|
148
|
-
#
|
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
|
-
|
185
|
-
|
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
|
196
|
-
#
|
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
|
-
#
|
248
|
+
# # new column placed just after column a
|
249
|
+
# data.add_column 'new_col3', :position => 1
|
199
250
|
#
|
200
|
-
|
201
|
-
|
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
|
266
|
+
each { |r| r[name] = yield(r) || options[:default] }
|
204
267
|
else
|
205
|
-
each { |r| r
|
268
|
+
each { |r| r[name] = options[:default] }
|
206
269
|
end; self
|
207
270
|
end
|
208
|
-
|
209
|
-
# Removes
|
210
|
-
# lost.
|
271
|
+
|
272
|
+
# Removes the given column from the table. May use name or position.
|
211
273
|
#
|
212
274
|
# Example:
|
213
275
|
#
|
214
|
-
#
|
215
|
-
#
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
-
#
|
283
|
+
|
284
|
+
# Removes multiple columns from the table. May use name or position
|
285
|
+
# Will autosplat arrays.
|
237
286
|
#
|
238
287
|
# Example:
|
239
|
-
#
|
240
|
-
#
|
241
|
-
|
242
|
-
|
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
|
-
#
|
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
|
-
#
|
258
|
-
# table
|
259
|
-
#
|
260
|
-
#
|
261
|
-
# table
|
262
|
-
|
263
|
-
|
264
|
-
|
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
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
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
|
-
#
|
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
|
-
#
|
368
|
+
# Using column_names and a range:
|
298
369
|
#
|
299
|
-
#
|
300
|
-
#
|
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
|
-
#
|
373
|
+
# Using just column_names:
|
306
374
|
#
|
307
|
-
#
|
308
|
-
#
|
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
|
-
#
|
311
|
-
# attributes in the group will be joined by an underscore.
|
378
|
+
# Using column_names and a block:
|
312
379
|
#
|
313
|
-
#
|
314
|
-
#
|
315
|
-
#
|
316
|
-
#
|
317
|
-
#
|
318
|
-
#
|
319
|
-
#
|
320
|
-
#
|
321
|
-
#
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
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
|
-
|
409
|
+
raise ArgumentError unless column_names.include?(name)
|
351
410
|
end
|
352
|
-
|
353
|
-
|
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
|
-
|
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
|