ruport 0.8.14 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +42 -107
- data/Rakefile +29 -32
- data/examples/centered_pdf_text_box.rb +13 -19
- data/examples/example.csv +3 -0
- data/examples/line_plotter.rb +15 -15
- data/examples/pdf_complex_report.rb +10 -23
- data/examples/pdf_table_with_title.rb +12 -12
- data/examples/rope_examples/itunes/Rakefile +22 -1
- data/examples/rope_examples/itunes/config/environment.rb +4 -0
- data/examples/rope_examples/itunes/lib/init.rb +32 -2
- data/examples/rope_examples/itunes/util/build +50 -16
- data/examples/rope_examples/sales_report/README +1 -1
- data/examples/rope_examples/sales_report/Rakefile +22 -1
- data/examples/rope_examples/sales_report/config/environment.rb +4 -0
- data/examples/rope_examples/sales_report/lib/init.rb +32 -2
- data/examples/rope_examples/sales_report/lib/reports/sales.rb +10 -16
- data/examples/rope_examples/sales_report/util/build +50 -16
- data/examples/row_renderer.rb +39 -0
- data/examples/ruport_list/png_embed.rb +61 -0
- data/examples/ruport_list/roadmap.png +0 -0
- data/examples/sample.rb +16 -0
- data/examples/simple_pdf_lines.rb +24 -0
- data/lib/ruport.rb +143 -57
- data/lib/ruport/acts_as_reportable.rb +246 -0
- data/lib/ruport/data.rb +1 -2
- data/lib/ruport/data/grouping.rb +311 -0
- data/lib/ruport/data/record.rb +113 -84
- data/lib/ruport/data/table.rb +275 -174
- data/lib/ruport/formatter.rb +149 -0
- data/lib/ruport/formatter/csv.rb +87 -0
- data/lib/ruport/formatter/html.rb +89 -0
- data/lib/ruport/formatter/pdf.rb +357 -0
- data/lib/ruport/formatter/text.rb +151 -0
- data/lib/ruport/generator.rb +127 -30
- data/lib/ruport/query.rb +46 -99
- data/lib/ruport/renderer.rb +238 -194
- data/lib/ruport/renderer/grouping.rb +67 -0
- data/lib/ruport/renderer/table.rb +25 -98
- data/lib/ruport/report.rb +45 -96
- data/test/acts_as_reportable_test.rb +229 -0
- data/test/csv_formatter_test.rb +97 -0
- data/test/{_test_database.rb → database_test_.rb} +0 -0
- data/test/grouping_test.rb +305 -0
- data/test/html_formatter_test.rb +104 -0
- data/test/pdf_formatter_test.rb +25 -0
- data/test/{test_query.rb → query_test.rb} +32 -121
- data/test/{test_record.rb → record_test.rb} +40 -23
- data/test/renderer_test.rb +344 -0
- data/test/{test_report.rb → report_test.rb} +74 -44
- data/test/samples/ticket_count.csv +124 -0
- data/test/{test_sql_split.rb → sql_split_test.rb} +0 -0
- data/test/{test_table.rb → table_test.rb} +255 -44
- data/test/text_formatter_test.rb +144 -0
- data/util/bench/data/record/bench_as_vs_to.rb +17 -0
- data/util/bench/data/record/bench_constructor.rb +46 -0
- data/util/bench/data/record/bench_indexing.rb +65 -0
- data/util/bench/data/record/bench_reorder.rb +35 -0
- data/util/bench/data/record/bench_to_a.rb +19 -0
- data/util/bench/data/table/bench_column_manip.rb +103 -0
- data/util/bench/data/table/bench_dup.rb +24 -0
- data/util/bench/data/table/bench_init.rb +67 -0
- data/util/bench/data/table/bench_manip.rb +125 -0
- data/util/bench/formatter/bench_csv.rb +14 -0
- data/util/bench/formatter/bench_html.rb +14 -0
- data/util/bench/formatter/bench_pdf.rb +14 -0
- data/util/bench/formatter/bench_text.rb +14 -0
- data/util/bench/samples/tattle.csv +1237 -0
- metadata +121 -143
- data/TODO +0 -21
- data/examples/invoice.rb +0 -142
- data/examples/invoice_report.rb +0 -29
- data/examples/line_graph.rb +0 -38
- data/examples/rope_examples/itunes/config/ruport_config.rb +0 -8
- data/examples/rope_examples/sales_report/config/ruport_config.rb +0 -8
- data/lib/ruport/attempt.rb +0 -63
- data/lib/ruport/config.rb +0 -204
- data/lib/ruport/data/groupable.rb +0 -93
- data/lib/ruport/data/taggable.rb +0 -80
- data/lib/ruport/format.rb +0 -1
- data/lib/ruport/format/csv.rb +0 -29
- data/lib/ruport/format/html.rb +0 -42
- data/lib/ruport/format/latex.rb +0 -47
- data/lib/ruport/format/pdf.rb +0 -233
- data/lib/ruport/format/plugin.rb +0 -31
- data/lib/ruport/format/svg.rb +0 -60
- data/lib/ruport/format/text.rb +0 -103
- data/lib/ruport/format/xml.rb +0 -32
- data/lib/ruport/layout.rb +0 -1
- data/lib/ruport/layout/component.rb +0 -7
- data/lib/ruport/mailer.rb +0 -99
- data/lib/ruport/renderer/graph.rb +0 -46
- data/lib/ruport/report/graph.rb +0 -14
- data/lib/ruport/system_extensions.rb +0 -71
- data/test/test_config.rb +0 -88
- data/test/test_format_text.rb +0 -63
- data/test/test_graph_renderer.rb +0 -97
- data/test/test_groupable.rb +0 -56
- data/test/test_mailer.rb +0 -170
- data/test/test_renderer.rb +0 -151
- data/test/test_ruport.rb +0 -58
- data/test/test_table_renderer.rb +0 -141
- data/test/test_taggable.rb +0 -52
data/lib/ruport/data/table.rb
CHANGED
@@ -5,40 +5,7 @@
|
|
5
5
|
# Copyright 2006 by respective content owners, all rights reserved.
|
6
6
|
|
7
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
|
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
|
40
|
-
|
41
|
-
end
|
42
9
|
|
43
10
|
# === Overview
|
44
11
|
#
|
@@ -53,12 +20,100 @@ module Ruport::Data
|
|
53
20
|
# to suit your needs, then used to build a report.
|
54
21
|
#
|
55
22
|
class Table
|
56
|
-
include Collection
|
57
|
-
include Groupable
|
58
|
-
|
59
|
-
require "forwardable"
|
60
|
-
extend Forwardable
|
61
23
|
|
24
|
+
module FromCSV
|
25
|
+
# Loads a CSV file directly into a Table using the FasterCSV library.
|
26
|
+
#
|
27
|
+
# Example:
|
28
|
+
#
|
29
|
+
# # treat first row as column_names
|
30
|
+
# table = Table.load('mydata.csv')
|
31
|
+
#
|
32
|
+
# # do not assume the data has column_names
|
33
|
+
# table = Table.load('mydata.csv',:has_names => false)
|
34
|
+
#
|
35
|
+
# # pass in FasterCSV options, such as column separators
|
36
|
+
# table = Table.load('mydata.csv',:csv_opts => { :col_sep => "\t" })
|
37
|
+
#
|
38
|
+
def load(csv_file, options={},&block)
|
39
|
+
get_table_from_csv(:foreach, csv_file, options,&block)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Creates a Table from a CSV string using FasterCSV. See Table.load for
|
43
|
+
# additional examples.
|
44
|
+
#
|
45
|
+
# table = Table.parse("a,b,c\n1,2,3\n4,5,6\n")
|
46
|
+
#
|
47
|
+
def parse(string, options={},&block)
|
48
|
+
get_table_from_csv(:parse,string,options,&block)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def get_table_from_csv(msg,param,options={},&block) #:nodoc:
|
54
|
+
require "fastercsv"
|
55
|
+
|
56
|
+
options = {:has_names => true,
|
57
|
+
:csv_options => {} }.merge(options)
|
58
|
+
|
59
|
+
# if people want to use FCSV's header support, let them
|
60
|
+
adjust_options_for_fcsv_headers(options)
|
61
|
+
|
62
|
+
loaded_data = self.new(options)
|
63
|
+
|
64
|
+
first_line = true
|
65
|
+
FasterCSV.send(msg,param,options[:csv_options]) do |row|
|
66
|
+
|
67
|
+
if first_line
|
68
|
+
adjust_for_headers(loaded_data,row,options)
|
69
|
+
first_line = false
|
70
|
+
next if options[:has_names]
|
71
|
+
end
|
72
|
+
|
73
|
+
if block
|
74
|
+
handle_csv_row_proc(loaded_data,row,options,block)
|
75
|
+
else
|
76
|
+
loaded_data << row
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
return loaded_data
|
82
|
+
end
|
83
|
+
|
84
|
+
def handle_csv_row_proc(data,row,options,block)
|
85
|
+
if options[:records]
|
86
|
+
row = Record.new(row, :attributes => data.column_names)
|
87
|
+
end
|
88
|
+
|
89
|
+
block[data,row]
|
90
|
+
end
|
91
|
+
|
92
|
+
def adjust_options_for_fcsv_headers(options)
|
93
|
+
options[:has_names] = false if options[:csv_options][:headers]
|
94
|
+
end
|
95
|
+
|
96
|
+
def adjust_for_headers(loaded,row,options)
|
97
|
+
if options[:has_names]
|
98
|
+
loaded.column_names = row
|
99
|
+
elsif options[:csv_options][:headers]
|
100
|
+
loaded.column_names = row.headers
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
include Enumerable
|
108
|
+
extend FromCSV
|
109
|
+
|
110
|
+
include Ruport::Renderer::Hooks
|
111
|
+
renders_as_table
|
112
|
+
|
113
|
+
def self.inherited(base)
|
114
|
+
base.renders_as_table
|
115
|
+
end
|
116
|
+
|
62
117
|
# Creates a new table based on the supplied options.
|
63
118
|
# Valid options:
|
64
119
|
# <b><tt>:data</tt></b>:: An Array of Arrays representing the
|
@@ -76,22 +131,28 @@ module Ruport::Data
|
|
76
131
|
options[:record_class].name || "Ruport::Data::Record"
|
77
132
|
@data = []
|
78
133
|
if options[:data]
|
79
|
-
if options[:data].all? { |r| r.kind_of? Record }
|
80
|
-
|
81
|
-
|
134
|
+
if options[:data].all? { |r| r.kind_of? Record }
|
135
|
+
options[:data] = options[:data].map { |r|
|
136
|
+
if @column_names.empty? or
|
137
|
+
r.attributes.all? { |a| a.kind_of?(Numeric) }
|
138
|
+
r.to_a
|
139
|
+
else
|
140
|
+
r.to_hash
|
141
|
+
end
|
142
|
+
}
|
82
143
|
end
|
83
144
|
options[:data].each { |e| self << e }
|
84
|
-
each { |r| r.tags = record_tags.shift } if record_tags
|
85
145
|
end
|
86
146
|
end
|
87
147
|
|
88
148
|
# This Table's column names.
|
89
149
|
attr_reader :column_names
|
90
|
-
|
91
|
-
|
150
|
+
attr_reader :data
|
151
|
+
|
152
|
+
require "forwardable"
|
153
|
+
extend Forwardable
|
92
154
|
def_delegators :@data, :each, :length, :size, :empty?, :[]
|
93
155
|
|
94
|
-
|
95
156
|
# Sets the column names for this table. <tt>new_column_names</tt> should
|
96
157
|
# be an array listing the names of the columns.
|
97
158
|
#
|
@@ -115,11 +176,10 @@ module Ruport::Data
|
|
115
176
|
end
|
116
177
|
}
|
117
178
|
r.send(:reindex, @column_names)
|
118
|
-
|
119
|
-
|
179
|
+
}
|
180
|
+
end
|
120
181
|
end
|
121
182
|
|
122
|
-
|
123
183
|
# Compares this Table to another Table and returns <tt>true</tt> if
|
124
184
|
# both the <tt>data</tt> and <tt>column_names</tt> are equal.
|
125
185
|
#
|
@@ -140,7 +200,8 @@ module Ruport::Data
|
|
140
200
|
alias_method :==, :eql?
|
141
201
|
|
142
202
|
# Used to add extra data to the Table. <tt>other</tt> can be an Array,
|
143
|
-
# Hash or Record.
|
203
|
+
# Hash or Record. It also can be anything that implements a meaningful
|
204
|
+
# to_hash or to_ary
|
144
205
|
#
|
145
206
|
# Example:
|
146
207
|
#
|
@@ -150,28 +211,21 @@ module Ruport::Data
|
|
150
211
|
# data << { :a => 4, :b => 5}
|
151
212
|
# data << Record.new [5,6], :attributes => %w[a b]
|
152
213
|
#
|
153
|
-
def <<(
|
154
|
-
case
|
214
|
+
def <<(row)
|
215
|
+
case row
|
155
216
|
when Array
|
156
|
-
|
157
|
-
@data << record_class.new(other, :attributes => attributes)
|
217
|
+
append_array(row)
|
158
218
|
when Hash
|
159
|
-
|
160
|
-
arr = @column_names.map { |k| other[k] }
|
161
|
-
@data << record_class.new(arr, :attributes => @column_names)
|
219
|
+
append_hash(row)
|
162
220
|
when record_class
|
163
|
-
|
164
|
-
self << other.to_a
|
165
|
-
else
|
166
|
-
self << other.to_h
|
167
|
-
end
|
168
|
-
@data.last.tags = other.tags.dup
|
221
|
+
append_record(row)
|
169
222
|
else
|
170
|
-
|
171
|
-
end
|
172
|
-
self
|
173
|
-
end
|
223
|
+
append_hash(row) rescue append_array(row)
|
224
|
+
end
|
225
|
+
return self
|
226
|
+
end
|
174
227
|
|
228
|
+
# returns the record class constant being used by the table
|
175
229
|
def record_class
|
176
230
|
@record_class.split("::").inject(Class) { |c,el| c.send(:const_get,el) }
|
177
231
|
end
|
@@ -199,34 +253,24 @@ module Ruport::Data
|
|
199
253
|
end
|
200
254
|
|
201
255
|
|
202
|
-
# Reorders the table's columns.
|
203
|
-
#
|
204
|
-
# Example:
|
205
|
-
#
|
206
|
-
# one = Table.new :data => [[1,2], [3,4]],
|
207
|
-
# :column_names => %w[a b]
|
208
|
-
#
|
209
|
-
# two = one.reorder([1,0])
|
210
|
-
#
|
211
256
|
def reorder(*indices)
|
257
|
+
raise(ArgumentError,"Can't reorder without column names set!") if
|
258
|
+
@column_names.empty?
|
259
|
+
|
212
260
|
indices = indices[0] if indices[0].kind_of? Array
|
213
261
|
|
214
262
|
if indices.all? { |i| i.kind_of? Integer }
|
215
263
|
indices.map! { |i| @column_names[i] }
|
216
|
-
end
|
264
|
+
end
|
217
265
|
|
218
|
-
|
219
|
-
|
220
|
-
@data.each { |r|
|
221
|
-
r.attributes = @column_names
|
222
|
-
}; self
|
266
|
+
reduce(indices)
|
223
267
|
end
|
224
268
|
|
225
269
|
|
226
270
|
# Adds an extra column to the Table. Available Options:
|
227
271
|
#
|
228
|
-
# <b><tt>:
|
229
|
-
#
|
272
|
+
# <b><tt>:default</tt></b>:: The default value to use for the column in
|
273
|
+
# existing rows. Set to nil if not specified.
|
230
274
|
#
|
231
275
|
# <b><tt>:position</tt></b>:: Inserts the column at the indicated position
|
232
276
|
# number.
|
@@ -272,6 +316,15 @@ module Ruport::Data
|
|
272
316
|
else
|
273
317
|
each { |r| r[name] = options[:default] }
|
274
318
|
end; self
|
319
|
+
end
|
320
|
+
|
321
|
+
def add_columns(names,options={})
|
322
|
+
raise "Greg isn't smart enough to figure this out.\n"+
|
323
|
+
"Send ideas in at http://list.rubyreports.org" if block_given?
|
324
|
+
need_reverse = !!(options[:after] || options[:position])
|
325
|
+
names = names.reverse if need_reverse
|
326
|
+
names.each { |n| add_column(n,options) }
|
327
|
+
self
|
275
328
|
end
|
276
329
|
|
277
330
|
# Removes the given column from the table. May use name or position.
|
@@ -280,6 +333,7 @@ module Ruport::Data
|
|
280
333
|
#
|
281
334
|
# table.remove_column(0) #=> removes the first column
|
282
335
|
# table.remove_column("apple") #=> removes column named apple
|
336
|
+
#
|
283
337
|
def remove_column(col)
|
284
338
|
col = column_names[col] if col.kind_of? Fixnum
|
285
339
|
column_names.delete(col)
|
@@ -292,6 +346,7 @@ module Ruport::Data
|
|
292
346
|
# Example:
|
293
347
|
# table.remove_columns('a','b','c')
|
294
348
|
# table.remove_columns([0,1])
|
349
|
+
#
|
295
350
|
def remove_columns(*cols)
|
296
351
|
cols = cols[0] if cols[0].kind_of? Array
|
297
352
|
cols.each { |col| remove_column(col) }
|
@@ -306,11 +361,46 @@ module Ruport::Data
|
|
306
361
|
# new_values = table.map { |r| r.zanzibar }
|
307
362
|
# old_values == new_values #=> true
|
308
363
|
# table.column_names.include?("a") #=> false
|
364
|
+
#
|
309
365
|
def rename_column(old_name,new_name)
|
310
366
|
self.column_names[column_names.index(old_name)] = new_name
|
311
367
|
each { |r| r.rename_attribute(old_name,new_name,false)}
|
312
368
|
end
|
313
|
-
|
369
|
+
|
370
|
+
# Renames multiple columns. Takes either a hash of "old" => "new"
|
371
|
+
# names or two arrays of names %w[old names],%w[new names].
|
372
|
+
#
|
373
|
+
# Example:
|
374
|
+
#
|
375
|
+
# table.column_names #=> ["a", "b"]
|
376
|
+
# table.rename_columns ["a", "b"], ["c", "d"]
|
377
|
+
# table.column_names #=> ["c", "d"]
|
378
|
+
#
|
379
|
+
# table.column_names #=> ["a", "b"]
|
380
|
+
# table.rename_columns {"a" => "c", "b" => "d"}
|
381
|
+
# table.column_names #=> ["c", "d"]
|
382
|
+
#
|
383
|
+
def rename_columns(old_cols=nil,new_cols=nil)
|
384
|
+
if block_given?
|
385
|
+
if old_cols
|
386
|
+
old_cols.each { |c| rename_column(c,yield(c)) }
|
387
|
+
else
|
388
|
+
column_names.each { |c| rename_column(c,yield(c)) }
|
389
|
+
end
|
390
|
+
return
|
391
|
+
end
|
392
|
+
|
393
|
+
raise ArgumentError unless old_cols
|
394
|
+
|
395
|
+
if new_cols
|
396
|
+
raise ArgumentError,
|
397
|
+
"odd number of arguments" unless old_cols.size == new_cols.size
|
398
|
+
h = Hash[*old_cols.zip(new_cols).flatten]
|
399
|
+
else
|
400
|
+
h = old_cols
|
401
|
+
end
|
402
|
+
h.each {|old,new| rename_column(old,new) }
|
403
|
+
end
|
314
404
|
|
315
405
|
# Exchanges one column with another.
|
316
406
|
#
|
@@ -332,6 +422,7 @@ module Ruport::Data
|
|
332
422
|
# | 3 | 2 | 1 |
|
333
423
|
# | 6 | 5 | 4 |
|
334
424
|
# +-----------+
|
425
|
+
#
|
335
426
|
def swap_column(a,b)
|
336
427
|
if [a,b].all? { |r| r.kind_of? Fixnum }
|
337
428
|
col_a,col_b = column_names[a],column_names[b]
|
@@ -359,9 +450,14 @@ module Ruport::Data
|
|
359
450
|
# | 1 | 2 | 7 |
|
360
451
|
# | 4 | 5 | 16 |
|
361
452
|
# +------------+
|
362
|
-
|
363
|
-
|
364
|
-
|
453
|
+
#
|
454
|
+
def replace_column(old_col,new_col=nil,&block)
|
455
|
+
if new_col
|
456
|
+
add_column(new_col,:after => old_col,&block)
|
457
|
+
remove_column(old_col)
|
458
|
+
else
|
459
|
+
each { |r| r[old_col] = yield(r) }
|
460
|
+
end
|
365
461
|
end
|
366
462
|
|
367
463
|
# Generates a sub table
|
@@ -383,39 +479,54 @@ module Ruport::Data
|
|
383
479
|
# Using column_names and a block:
|
384
480
|
#
|
385
481
|
# sub_table = table.sub_table(%w[d b]) { |r| r.a < 6 }
|
386
|
-
# sub_table == [[4,2],[8,6]].to_table(%w[d b]) #=> true
|
482
|
+
# sub_table == [[4,2],[8,6]].to_table(%w[d b]) #=> true
|
483
|
+
#
|
484
|
+
# Using a range for row reduction:
|
485
|
+
# sub_table = table.sub_table(1..-1)
|
486
|
+
# sub_table == [[5,6,7,8],[9,10,11,12]].to_table(%w[a b c d]) #=> true
|
387
487
|
#
|
388
488
|
# Using just a block:
|
389
489
|
#
|
390
490
|
# sub_table = table.sub_table { |r| r.c > 10 }
|
391
491
|
# sub_table == [[9,10,11,12]].to_table(%w[a b c d]) #=> true
|
392
492
|
#
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
493
|
+
def sub_table(cor=column_names,range=nil,&block)
|
494
|
+
if range
|
495
|
+
self.class.new(:column_names => cor,:data => data[range])
|
496
|
+
elsif cor.kind_of?(Range)
|
497
|
+
self.class.new(:column_names => column_names,:data => data[cor])
|
498
|
+
elsif block
|
499
|
+
self.class.new( :column_names => cor, :data => data.select(&block))
|
500
|
+
else
|
501
|
+
self.class.new( :column_names => cor, :data => data)
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
# Generates a sub table in place, modifying the receiver. See documentation
|
506
|
+
# for <tt>sub_table</tt>.
|
507
|
+
#
|
508
|
+
def reduce(columns=column_names,range=nil,&block)
|
509
|
+
t = sub_table(columns,range,&block)
|
510
|
+
@data = t.data
|
511
|
+
@column_names = t.column_names
|
512
|
+
self
|
405
513
|
end
|
514
|
+
|
515
|
+
alias_method :sub_table!, :reduce
|
406
516
|
|
407
|
-
#
|
517
|
+
# Returns an array of values for the given column name.
|
518
|
+
#
|
408
519
|
def column(name)
|
409
520
|
case(name)
|
410
521
|
when Integer
|
411
|
-
|
522
|
+
unless column_names.empty?
|
412
523
|
raise ArgumentError if name > column_names.length
|
413
524
|
end
|
414
525
|
else
|
415
526
|
raise ArgumentError unless column_names.include?(name)
|
416
527
|
end
|
417
528
|
|
418
|
-
|
529
|
+
map { |r| r[name] }
|
419
530
|
end
|
420
531
|
|
421
532
|
# Calculates sums. If a column name or index is given, it will try to
|
@@ -436,10 +547,10 @@ module Ruport::Data
|
|
436
547
|
def sigma(column=nil)
|
437
548
|
inject(0) { |s,r|
|
438
549
|
if column
|
439
|
-
s + if r
|
440
|
-
r
|
550
|
+
s + if r.get(column).kind_of? Numeric
|
551
|
+
r.get(column)
|
441
552
|
else
|
442
|
-
r
|
553
|
+
r.get(column) =~ /\./ ? r.get(column).to_f : r.get(column).to_i
|
443
554
|
end
|
444
555
|
else
|
445
556
|
s + yield(r)
|
@@ -449,7 +560,6 @@ module Ruport::Data
|
|
449
560
|
|
450
561
|
alias_method :sum, :sigma
|
451
562
|
|
452
|
-
#
|
453
563
|
# Returns a sorted table. If col_names is specified,
|
454
564
|
# the block is ignored and the table is sorted by the named columns. All
|
455
565
|
# options are used in constructing the new Table (see Array#to_table
|
@@ -477,7 +587,7 @@ module Ruport::Data
|
|
477
587
|
if col_names
|
478
588
|
sort_by do |r|
|
479
589
|
stabilizer += 1
|
480
|
-
[col_names.map {|col| r[col]}, stabilizer]
|
590
|
+
[Array(col_names).map {|col| r[col]}, stabilizer]
|
481
591
|
end
|
482
592
|
else
|
483
593
|
sort_by(&block)
|
@@ -485,12 +595,17 @@ module Ruport::Data
|
|
485
595
|
|
486
596
|
table = self.class.new( :data => data_array,
|
487
597
|
:column_names => @column_names,
|
488
|
-
:record_class => record_class )
|
598
|
+
:record_class => record_class )
|
489
599
|
|
490
|
-
table.tags = self.tags
|
491
600
|
return table
|
601
|
+
end
|
602
|
+
|
603
|
+
# same as Table#sort_rows_by, but self modifiying
|
604
|
+
def sort_rows_by!(col_names=nil,&block)
|
605
|
+
table = sort_rows_by(col_names,&block)
|
606
|
+
@data = table.data
|
492
607
|
end
|
493
|
-
|
608
|
+
|
494
609
|
def rows_with(columns,&block)
|
495
610
|
select { |r|
|
496
611
|
if block
|
@@ -509,19 +624,14 @@ module Ruport::Data
|
|
509
624
|
# :column_names => %w[a b]
|
510
625
|
# two = one.dup
|
511
626
|
#
|
512
|
-
def
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
end
|
519
|
-
|
520
|
-
# NOTE: does not respect tainted status
|
521
|
-
alias_method :clone, :dup
|
627
|
+
def initialize_copy(from)
|
628
|
+
@record_class = from.record_class.name
|
629
|
+
@column_names = from.column_names.dup
|
630
|
+
@data = []
|
631
|
+
from.data.each { |r| self << r.dup }
|
632
|
+
end
|
522
633
|
|
523
|
-
#
|
524
|
-
# Uses Ruport's built-in text plugin to render this Table into a String
|
634
|
+
# Uses Ruport's built-in text formatter to render this Table into a String
|
525
635
|
#
|
526
636
|
# Example:
|
527
637
|
#
|
@@ -533,61 +643,45 @@ module Ruport::Data
|
|
533
643
|
as(:text)
|
534
644
|
end
|
535
645
|
|
646
|
+
# Convert the Table into a Group using the supplied group name.
|
647
|
+
#
|
648
|
+
# data = Table.new :data => [[1,2], [3,4]],
|
649
|
+
# :column_names => %w[a b]
|
650
|
+
# group = data.to_group("my_group")
|
651
|
+
#
|
652
|
+
def to_group(name=nil)
|
653
|
+
Group.new( :data => data,
|
654
|
+
:column_names => column_names,
|
655
|
+
:name => name,
|
656
|
+
:record_class => record_class )
|
657
|
+
end
|
658
|
+
|
659
|
+
# NOTE: does not respect tainted status
|
660
|
+
alias_method :clone, :dup
|
661
|
+
|
536
662
|
# Provides a shortcut for the <tt>as()</tt> method by converting a call to
|
537
663
|
# <tt>as(:format_name)</tt> into a call to <tt>to_format_name</tt>
|
538
|
-
def method_missing(id,*args)
|
539
|
-
return as($1.to_sym) if id.to_s =~ /^to_(.*)/
|
664
|
+
def method_missing(id,*args,&block)
|
665
|
+
return as($1.to_sym,*args,&block) if id.to_s =~ /^to_(.*)/
|
540
666
|
return rows_with($1.to_sym => args[0]) if id.to_s =~ /^rows_with_(.*)/
|
541
667
|
super
|
542
668
|
end
|
543
669
|
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
# # treat first row as column_names
|
549
|
-
# table = Table.load('mydata.csv')
|
550
|
-
#
|
551
|
-
# # do not assume the data has column_names
|
552
|
-
# table = Table.load('mydata.csv',:has_names => false)
|
553
|
-
#
|
554
|
-
# # pass in FasterCSV options, such as column separators
|
555
|
-
# table = Table.load('mydata.csv',:csv_opts => { :col_sep => "\t" })
|
556
|
-
#
|
557
|
-
def self.load(csv_file, options={},&block)
|
558
|
-
get_table_from_csv(:foreach, csv_file, options,&block)
|
559
|
-
end
|
670
|
+
def append_array(array)
|
671
|
+
attributes = @column_names.empty? ? nil : @column_names
|
672
|
+
@data << record_class.new(array.to_ary, :attributes => attributes)
|
673
|
+
end
|
560
674
|
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
# table = Table.parse("a,b,c\n1,2,3\n4,5,6\n")
|
566
|
-
#
|
567
|
-
def self.parse(string, options={},&block)
|
568
|
-
get_table_from_csv(:parse,string,options,&block)
|
675
|
+
def append_hash(hash_obj)
|
676
|
+
hash_obj = hash_obj.to_hash
|
677
|
+
raise ArgumentError unless @column_names
|
678
|
+
@data << record_class.new(hash_obj, :attributes => @column_names)
|
569
679
|
end
|
570
|
-
|
571
|
-
private
|
572
680
|
|
573
|
-
def
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
loaded_data = self.new
|
578
|
-
|
579
|
-
first_line = true
|
580
|
-
FasterCSV.send(msg,param,options[:csv_options]) do |row|
|
581
|
-
if first_line && options[:has_names]
|
582
|
-
loaded_data.column_names = row
|
583
|
-
first_line = false
|
584
|
-
elsif !block
|
585
|
-
loaded_data << row
|
586
|
-
else
|
587
|
-
block[loaded_data,row]
|
588
|
-
end
|
589
|
-
end ; loaded_data
|
590
|
-
end
|
681
|
+
def append_record(record)
|
682
|
+
self << record.send(column_names.empty? ? :to_a : :to_hash)
|
683
|
+
end
|
684
|
+
|
591
685
|
end
|
592
686
|
end
|
593
687
|
|
@@ -605,8 +699,7 @@ module Kernel
|
|
605
699
|
# t = Table(%w[a b c]) { |t| t << [1,2,3] }
|
606
700
|
#
|
607
701
|
# # allows loading table from CSV
|
608
|
-
# # accepts all Data::Table.load options,
|
609
|
-
# # not row!
|
702
|
+
# # accepts all Data::Table.load options, including block (yields table,row)
|
610
703
|
#
|
611
704
|
# t = Table("foo.csv")
|
612
705
|
# t = Table("bar.csv", :has_names => false)
|
@@ -617,9 +710,17 @@ module Kernel
|
|
617
710
|
opts = args[1] || {}
|
618
711
|
Ruport::Data::Table.new(f={:column_names => args[0]}.merge(opts))
|
619
712
|
when /\.csv/
|
620
|
-
Ruport::Data::Table.load(*args)
|
713
|
+
return Ruport::Data::Table.load(*args,&block)
|
714
|
+
when Hash
|
715
|
+
if file = args[0].delete(:file)
|
716
|
+
return Ruport::Data::Table.load(file,args[0],&block)
|
717
|
+
elsif string = args[0].delete(:string)
|
718
|
+
return Ruport::Data::Table.parse(string,args[0],&block)
|
719
|
+
else
|
720
|
+
return Ruport::Data::Table.new(args[0])
|
721
|
+
end
|
621
722
|
else
|
622
|
-
Ruport::Data::Table.new(:data => []
|
723
|
+
Ruport::Data::Table.new(:data => [], :column_names => args)
|
623
724
|
end
|
624
725
|
|
625
726
|
block[table] if block
|
@@ -628,7 +729,7 @@ module Kernel
|
|
628
729
|
end
|
629
730
|
|
630
731
|
class Array
|
631
|
-
|
732
|
+
|
632
733
|
# Converts an array to a Ruport::Data::Table object, ready to
|
633
734
|
# use in your reports.
|
634
735
|
#
|