ruport 0.7.2 → 0.8.0
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.
- 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
|