ruport 1.6.3 → 1.7.1
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.
- checksums.yaml +7 -0
- data/AUTHORS +11 -0
- data/README.rdoc +105 -0
- data/Rakefile +13 -44
- data/examples/add_row_table.rb +46 -0
- data/examples/data/wine.csv +255 -0
- data/examples/pdf_grouping.rb +39 -0
- data/examples/pdf_table.rb +28 -0
- data/examples/pdf_table_from_csv.rb +26 -0
- data/examples/pdf_table_prawn.rb +30 -0
- data/examples/pdf_table_simple.rb +13 -0
- data/lib/ruport.rb +0 -12
- data/lib/ruport/controller.rb +16 -20
- data/lib/ruport/data/feeder.rb +2 -2
- data/lib/ruport/data/grouping.rb +2 -2
- data/lib/ruport/data/table.rb +314 -202
- data/lib/ruport/formatter.rb +53 -52
- data/lib/ruport/formatter/csv.rb +6 -7
- data/lib/ruport/formatter/html.rb +13 -11
- data/lib/ruport/formatter/pdf.rb +73 -75
- data/lib/ruport/formatter/prawn_pdf.rb +72 -0
- data/lib/ruport/formatter/template.rb +1 -1
- data/lib/ruport/version.rb +1 -1
- data/test/controller_test.rb +100 -122
- data/test/csv_formatter_test.rb +15 -15
- data/test/data_feeder_test.rb +26 -26
- data/test/grouping_test.rb +30 -29
- data/test/helpers.rb +18 -10
- data/test/html_formatter_test.rb +24 -24
- data/test/record_test.rb +14 -14
- data/test/samples/sales.csv +21 -0
- data/test/table_pivot_test.rb +68 -24
- data/test/table_test.rb +365 -336
- data/test/template_test.rb +1 -1
- data/test/text_formatter_test.rb +19 -19
- data/util/bench/data/table/bench_init.rb +1 -1
- metadata +123 -75
- data/README +0 -114
- data/test/pdf_formatter_test.rb +0 -354
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
3
|
+
require "ruport"
|
4
|
+
require "ruby-debug"
|
5
|
+
|
6
|
+
class Document < Ruport::Controller
|
7
|
+
stage :title, :body
|
8
|
+
|
9
|
+
def setup
|
10
|
+
table = Ruport::Data::Table.load('data/wine.csv')
|
11
|
+
grouping = Grouping(table, :by => 'Type')
|
12
|
+
self.data = grouping
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class DocumentFormatter < Ruport::Formatter::PrawnPDF
|
17
|
+
renders :prawn_pdf, :for => Document
|
18
|
+
|
19
|
+
def build_title
|
20
|
+
pad(10) do
|
21
|
+
text 'WINES', :style => :bold, :size => 20
|
22
|
+
horizontal_rule
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def build_body
|
27
|
+
render_grouping(data, :formatter => pdf) # It's Nasty!!!
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class DocumentTextFormatter < Ruport::Formatter::Text
|
32
|
+
renders :text, :for => Document
|
33
|
+
|
34
|
+
def build_body
|
35
|
+
output << data.to_text
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Document.render(:prawn_pdf, :file => 'pdf_grouping.pdf')
|
@@ -0,0 +1,28 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
2
|
+
require "ruport"
|
3
|
+
|
4
|
+
# Example using prawn 0.9.0 pdf generator
|
5
|
+
class Document < Ruport::Controller
|
6
|
+
stage :body
|
7
|
+
|
8
|
+
def setup
|
9
|
+
self.data = Ruport::Data::Table.new(
|
10
|
+
:column_names => %w(Make Model Year),
|
11
|
+
:data => [
|
12
|
+
%w(Nissan Skyline 1989),
|
13
|
+
%w(Mercedes-Benz 500SL 2005),
|
14
|
+
%w(Kia Sinatra 2008)
|
15
|
+
])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class DocumentFormatter < Ruport::Formatter::PrawnPDF
|
20
|
+
renders :prawn_pdf, :for => Document
|
21
|
+
|
22
|
+
def build_body
|
23
|
+
draw_table(data)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Document.render(:prawn_pdf, :file => 'pdf_table.pdf')
|
28
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
2
|
+
require "ruport"
|
3
|
+
|
4
|
+
class Document < Ruport::Controller
|
5
|
+
stage :title, :body
|
6
|
+
|
7
|
+
def setup
|
8
|
+
self.data = Ruport::Data::Table.load('data/wine.csv')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class DocumentFormatter < Ruport::Formatter::PrawnPDF
|
13
|
+
renders :prawn_pdf, :for => Document
|
14
|
+
|
15
|
+
def build_title
|
16
|
+
pad(10) do
|
17
|
+
text 'Wine', :style => :bold, :size => 20
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def build_body
|
22
|
+
draw_table(data)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Document.render(:prawn_pdf, :file => 'pdf_table_from_csv.pdf')
|
@@ -0,0 +1,30 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
2
|
+
|
3
|
+
require "ruport"
|
4
|
+
|
5
|
+
table = Ruport::Data::Table.new(
|
6
|
+
:column_names => %w(Make Model Year Class),
|
7
|
+
:data => [
|
8
|
+
%w(Nissan Skyline 1989 B),
|
9
|
+
%w(Mercedes-Benz 500SL 2005 A),
|
10
|
+
%w(Kia Sinatra 2008 C)
|
11
|
+
])
|
12
|
+
|
13
|
+
pdf_options = { :pdf_format => {
|
14
|
+
:page_layout => :portrait,
|
15
|
+
:page_size => "LETTER",
|
16
|
+
},
|
17
|
+
:table_format => {
|
18
|
+
:cell_style => { :size => 8},
|
19
|
+
:row_colors => ["FFFFFF","F0F0F0"],
|
20
|
+
:column_widths => {
|
21
|
+
0 => 100,
|
22
|
+
1 => 100,
|
23
|
+
2 => 50,
|
24
|
+
3 => 40
|
25
|
+
}
|
26
|
+
},
|
27
|
+
:file => 'pdf_table_prawn.pdf'
|
28
|
+
}
|
29
|
+
|
30
|
+
table.to_prawn_pdf(pdf_options)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
2
|
+
require "ruport"
|
3
|
+
|
4
|
+
# Quick and simple example using prawn 0.9.0 with to_prawn_pdf.
|
5
|
+
table = Ruport::Data::Table.new(
|
6
|
+
:column_names => %w(Make Model Year),
|
7
|
+
:data => [
|
8
|
+
%w(Nissan Skyline 1989),
|
9
|
+
%w(Mercedes-Benz 500SL 2005),
|
10
|
+
%w(Kia Sinatra 2008)
|
11
|
+
])
|
12
|
+
|
13
|
+
table.to_prawn_pdf(:file => 'pdf_table_simple.pdf')
|
data/lib/ruport.rb
CHANGED
@@ -10,18 +10,6 @@
|
|
10
10
|
# See LICENSE and COPYING for details
|
11
11
|
#
|
12
12
|
|
13
|
-
|
14
|
-
if RUBY_VERSION > "1.9"
|
15
|
-
require "csv"
|
16
|
-
unless defined? FCSV
|
17
|
-
class Object
|
18
|
-
FCSV = CSV
|
19
|
-
alias_method :FCSV, :CSV
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
|
25
13
|
module Ruport #:nodoc:#
|
26
14
|
class FormatterError < RuntimeError #:nodoc:
|
27
15
|
end
|
data/lib/ruport/controller.rb
CHANGED
@@ -165,19 +165,15 @@ class Ruport::Controller
|
|
165
165
|
# Example:
|
166
166
|
#
|
167
167
|
# table.as(:csv, :show_table_headers => false)
|
168
|
-
def as(format,options={})
|
168
|
+
def as(format, options = {})
|
169
169
|
raise ControllerNotSetError unless self.class.controller
|
170
|
-
unless self.class.controller.formats.include?(format)
|
171
|
-
|
170
|
+
raise UnknownFormatError unless self.class.controller.formats.include?(format)
|
171
|
+
self.class.controller.render(format, self.class.rendering_options.merge(options)) do |rend|
|
172
|
+
rend.data = respond_to?(:renderable_data) ? renderable_data(format) : self
|
173
|
+
yield(rend) if block_given?
|
172
174
|
end
|
173
|
-
|
174
|
-
|
175
|
-
rend.data =
|
176
|
-
respond_to?(:renderable_data) ? renderable_data(format) : self
|
177
|
-
yield(rend) if block_given?
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
175
|
+
end
|
176
|
+
|
181
177
|
def save_as(file,options={})
|
182
178
|
file =~ /.*\.(.*)/
|
183
179
|
format = $1
|
@@ -221,6 +217,7 @@ class Ruport::Controller
|
|
221
217
|
{ :html => Ruport::Formatter::HTML,
|
222
218
|
:csv => Ruport::Formatter::CSV,
|
223
219
|
:pdf => Ruport::Formatter::PDF,
|
220
|
+
:prawn_pdf => Ruport::Formatter::PrawnPDF,
|
224
221
|
:text => Ruport::Formatter::Text }
|
225
222
|
end
|
226
223
|
|
@@ -389,16 +386,16 @@ class Ruport::Controller
|
|
389
386
|
# # other details omitted
|
390
387
|
# end
|
391
388
|
def required_option(*opts)
|
392
|
-
self.required_options ||= []
|
393
|
-
opts.each do |opt|
|
394
|
-
self.required_options << opt
|
389
|
+
(self.required_options ||= []).concat(opts)
|
395
390
|
|
396
|
-
|
397
|
-
unless
|
398
|
-
define_method(
|
391
|
+
opts.each do |opt|
|
392
|
+
unless method_defined?(opt)
|
393
|
+
define_method(opt) { options.send(opt) }
|
394
|
+
end
|
395
|
+
setter = "#{opt}="
|
396
|
+
unless method_defined?(setter)
|
397
|
+
define_method(setter) {|t| options.send(setter, t) }
|
399
398
|
end
|
400
|
-
opt = "#{opt}="
|
401
|
-
define_method(opt) {|t| options.send(opt, t) }
|
402
399
|
end
|
403
400
|
end
|
404
401
|
|
@@ -573,7 +570,6 @@ class Ruport::Controller
|
|
573
570
|
else
|
574
571
|
execute_stages
|
575
572
|
end
|
576
|
-
|
577
573
|
finalize self.class.final_stage if self.class.final_stage
|
578
574
|
maybe :finalize
|
579
575
|
end
|
data/lib/ruport/data/feeder.rb
CHANGED
@@ -14,7 +14,7 @@
|
|
14
14
|
#
|
15
15
|
# Table Example:
|
16
16
|
#
|
17
|
-
# t = Table(%w[a b c]) do |feeder|
|
17
|
+
# t = Ruport::Data::Table(%w[a b c]) do |feeder|
|
18
18
|
# feeder.filter { |r| r.a < 5 }
|
19
19
|
# feeder.transform { |r| r.b = "B: #{r.b}"}
|
20
20
|
# feeder << [1,2,3]
|
@@ -108,4 +108,4 @@ class Ruport::Data::Feeder
|
|
108
108
|
@constraints << [:transform,block]
|
109
109
|
end
|
110
110
|
|
111
|
-
end
|
111
|
+
end
|
data/lib/ruport/data/grouping.rb
CHANGED
@@ -285,7 +285,7 @@ module Ruport::Data
|
|
285
285
|
else
|
286
286
|
cols = procs.keys + [field]
|
287
287
|
end
|
288
|
-
expected = Table(cols) { |t|
|
288
|
+
expected = Table.new(:column_names => cols) { |t|
|
289
289
|
each do |name,group|
|
290
290
|
t << procs.inject({field => name}) do |s,r|
|
291
291
|
s.merge(r[0] => r[1].call(group))
|
@@ -379,7 +379,7 @@ module Kernel
|
|
379
379
|
#
|
380
380
|
# Example:
|
381
381
|
#
|
382
|
-
# a = Table(%w[a b c], :data => [[1,2,3],[4,5,6]])
|
382
|
+
# a = Ruport::Data::Table.new(%w[a b c], :data => [[1,2,3],[4,5,6]])
|
383
383
|
# b = Grouping(a, :by => "a") #=> creates a new grouping on column "a"
|
384
384
|
#
|
385
385
|
def Grouping(*args)
|
data/lib/ruport/data/table.rb
CHANGED
@@ -1,21 +1,24 @@
|
|
1
1
|
# Ruport : Extensible Reporting System
|
2
2
|
#
|
3
3
|
# data/table.rb provides a table data structure for Ruport.
|
4
|
-
#
|
4
|
+
#
|
5
5
|
# Created by Gregory Brown / Dudley Flanders, 2006
|
6
|
-
# Copyright (C) 2006 Gregory Brown / Dudley Flanders, All Rights Reserved.
|
6
|
+
# Copyright (C) 2006 Gregory Brown / Dudley Flanders, All Rights Reserved.
|
7
7
|
#
|
8
8
|
# This is free software distributed under the same terms as Ruby 1.8
|
9
|
-
# See LICENSE and COPYING for details.
|
9
|
+
# See LICENSE and COPYING for details.
|
10
10
|
#
|
11
|
+
|
12
|
+
require "csv"
|
13
|
+
|
11
14
|
module Ruport::Data
|
12
|
-
|
15
|
+
|
13
16
|
# === Overview
|
14
17
|
#
|
15
|
-
# This class is one of the core classes for building and working with data
|
16
|
-
# in Ruport. The idea is to get your data into a standard form, regardless
|
18
|
+
# This class is one of the core classes for building and working with data
|
19
|
+
# in Ruport. The idea is to get your data into a standard form, regardless
|
17
20
|
# of its source (a database, manual arrays, ActiveRecord, CSVs, etc.).
|
18
|
-
#
|
21
|
+
#
|
19
22
|
# Table is intended to be used as the data store for structured, tabular
|
20
23
|
# data.
|
21
24
|
#
|
@@ -32,9 +35,57 @@ module Ruport::Data
|
|
32
35
|
@pivot_column = pivot_col
|
33
36
|
@summary_column = summary_col
|
34
37
|
@pivot_order = options[:pivot_order]
|
38
|
+
@operation = options[:operation] || :first
|
39
|
+
unless Operations.respond_to?(@operation, true)
|
40
|
+
raise ArgumentError, "Unknown pivot operation '#{@operation}'"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Row is the first row in the pivoted table (without the group column)
|
45
|
+
def row
|
46
|
+
return @row if defined?(@row)
|
47
|
+
|
48
|
+
pivot_column_grouping = Grouping(@table, :by => @pivot_column)
|
49
|
+
|
50
|
+
ordering = self.class.row_order_to_group_order(@pivot_order)
|
51
|
+
pivot_column_grouping.sort_grouping_by!(ordering) if ordering
|
52
|
+
|
53
|
+
@row = pivot_column_grouping.map { |name,grouping| name }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Column in the first column in the pivoted table (without the group column)
|
57
|
+
def column
|
58
|
+
@column ||= @table.map { |row| row[@group_column] }.uniq
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_table
|
62
|
+
table = Table.new
|
63
|
+
create_header(table)
|
64
|
+
|
65
|
+
column.each do |column_entry|
|
66
|
+
row_values = row.map { |row_entry| values[column_entry][row_entry] }
|
67
|
+
table << [column_entry] + row_values
|
68
|
+
end
|
69
|
+
|
70
|
+
table
|
71
|
+
end
|
72
|
+
|
73
|
+
def values
|
74
|
+
@values ||= Hash.new do |values, column_entry|
|
75
|
+
rows_group = rows_groups[column_entry]
|
76
|
+
|
77
|
+
values[column_entry] =
|
78
|
+
row.inject({}) do |values, row_entry|
|
79
|
+
matching_rows = rows_group.rows_with(@pivot_column => row_entry)
|
80
|
+
values[row_entry] = perform_operation(matching_rows)
|
81
|
+
values
|
82
|
+
end
|
83
|
+
end
|
35
84
|
end
|
36
85
|
|
37
|
-
|
86
|
+
private
|
87
|
+
|
88
|
+
def self.row_order_to_group_order(row_order_spec)
|
38
89
|
case row_order_spec
|
39
90
|
when Array
|
40
91
|
proc {|group|
|
@@ -50,45 +101,55 @@ module Ruport::Data
|
|
50
101
|
}
|
51
102
|
when NilClass
|
52
103
|
nil
|
104
|
+
when :name # Pass through :name as it's a special instruction to Grouping
|
105
|
+
:name
|
53
106
|
else
|
54
107
|
proc {|group| group[0][row_order_spec].to_s }
|
55
108
|
end
|
56
109
|
end
|
57
110
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
pivot_column_grouping.each {|n,g| g.add_column(n) { n }}
|
62
|
-
pivot_column_grouping.sort_grouping_by!(ordering) if ordering
|
63
|
-
result = []
|
64
|
-
pivot_column_grouping.each {|name,_| result << name }
|
65
|
-
result
|
111
|
+
def create_header(table)
|
112
|
+
table.add_column(@group_column)
|
113
|
+
row.each { |name| table.add_column(name) }
|
66
114
|
end
|
67
115
|
|
68
|
-
def
|
69
|
-
|
116
|
+
def perform_operation(rows)
|
117
|
+
Operations.send @operation, rows, @summary_column
|
70
118
|
end
|
71
119
|
|
72
|
-
def
|
73
|
-
|
74
|
-
result.add_column(@group_column)
|
75
|
-
pivoted_columns = columns_from_pivot
|
76
|
-
pivoted_columns.each { |name| result.add_column(name) }
|
77
|
-
outer_grouping = Grouping(@table, :by => @group_column)
|
78
|
-
group_column_entries.each {|outer_group_name|
|
79
|
-
outer_group = outer_grouping[outer_group_name]
|
80
|
-
pivot_values = pivoted_columns.inject({}) do |hsh, e|
|
81
|
-
matching_rows = outer_group.rows_with(@pivot_column => e)
|
82
|
-
hsh[e] = matching_rows.first && matching_rows.first[@summary_column]
|
83
|
-
hsh
|
84
|
-
end
|
85
|
-
result << [outer_group_name] + pivoted_columns.map {|e|
|
86
|
-
pivot_values[e]
|
87
|
-
}
|
88
|
-
}
|
89
|
-
result
|
120
|
+
def rows_groups
|
121
|
+
@rows_groups ||= Grouping(@table, :by => @group_column)
|
90
122
|
end
|
91
123
|
|
124
|
+
module Operations
|
125
|
+
extend self
|
126
|
+
|
127
|
+
def first(rows, summary_column)
|
128
|
+
rows.first && rows.first[summary_column]
|
129
|
+
end
|
130
|
+
|
131
|
+
def sum(rows, summary_column)
|
132
|
+
rows && rows.inject(0) { |sum,row| sum+row[summary_column] }
|
133
|
+
end
|
134
|
+
|
135
|
+
def count(rows, summary_column)
|
136
|
+
rows && rows.length
|
137
|
+
end
|
138
|
+
|
139
|
+
def mean(rows, summary_column)
|
140
|
+
return if rows.length == 0
|
141
|
+
sum = rows && rows.inject(0) { |sum,row| sum+row[summary_column] }
|
142
|
+
sum / rows.length
|
143
|
+
end
|
144
|
+
|
145
|
+
def min(rows, summary_column)
|
146
|
+
rows && (rows.collect { |r| r[summary_column] }).min
|
147
|
+
end
|
148
|
+
|
149
|
+
def max(rows, summary_column)
|
150
|
+
rows && (rows.collect { |r| r[summary_column] }).max
|
151
|
+
end
|
152
|
+
end
|
92
153
|
end
|
93
154
|
|
94
155
|
# Creates a new table with values from the specified pivot column
|
@@ -113,6 +174,14 @@ module Ruport::Data
|
|
113
174
|
# first argument. This wart will likely
|
114
175
|
# be fixed in a future version.
|
115
176
|
#
|
177
|
+
# <b><tt>:operation</tt></b>:: The operation to perform on
|
178
|
+
# <tt>:values</tt> column. Supported
|
179
|
+
# operations are <tt>:first</tt>,
|
180
|
+
# <tt>:sum</tt>, <tt>:count</tt>,
|
181
|
+
# <tt>:mean</tt>, <tt>:min</tt>, and
|
182
|
+
# <tt>:max</tt>. If not specified, the
|
183
|
+
# default is <tt>:first</tt>.
|
184
|
+
#
|
116
185
|
# Example:
|
117
186
|
#
|
118
187
|
# Given a table <em>my_table</em>:
|
@@ -139,9 +208,9 @@ module Ruport::Data
|
|
139
208
|
# +---------------+
|
140
209
|
#
|
141
210
|
def pivot(pivot_column, options = {})
|
142
|
-
group_column = options[:group_by] ||
|
211
|
+
group_column = options[:group_by] ||
|
143
212
|
raise(ArgumentError, ":group_by option required")
|
144
|
-
value_column = options[:values] ||
|
213
|
+
value_column = options[:values] ||
|
145
214
|
raise(ArgumentError, ":values option required")
|
146
215
|
Pivot.new(
|
147
216
|
self, group_column, pivot_column, value_column, options
|
@@ -153,10 +222,10 @@ module Ruport::Data
|
|
153
222
|
# This module provides facilities for creating tables from csv data.
|
154
223
|
#
|
155
224
|
module FromCSV
|
156
|
-
# Loads a CSV file directly into a Table
|
225
|
+
# Loads a CSV file directly into a Table.
|
157
226
|
#
|
158
227
|
# Example:
|
159
|
-
#
|
228
|
+
#
|
160
229
|
# # treat first row as column_names
|
161
230
|
# table = Table.load('mydata.csv')
|
162
231
|
#
|
@@ -169,8 +238,8 @@ module Ruport::Data
|
|
169
238
|
def load(csv_file, options={},&block)
|
170
239
|
get_table_from_csv(:foreach, csv_file, options,&block)
|
171
240
|
end
|
172
|
-
|
173
|
-
# Creates a Table from a CSV string
|
241
|
+
|
242
|
+
# Creates a Table from a CSV string. See Table.load for
|
174
243
|
# additional examples.
|
175
244
|
#
|
176
245
|
# table = Table.parse("a,b,c\n1,2,3\n4,5,6\n")
|
@@ -180,32 +249,31 @@ module Ruport::Data
|
|
180
249
|
end
|
181
250
|
|
182
251
|
private
|
183
|
-
|
184
|
-
def get_table_from_csv(msg,param,options={},&block) #:nodoc:
|
185
|
-
require "fastercsv"
|
186
252
|
|
253
|
+
def get_table_from_csv(msg,param,options={},&block) #:nodoc:
|
187
254
|
options = {:has_names => true,
|
188
255
|
:csv_options => {} }.merge(options)
|
189
|
-
|
256
|
+
|
190
257
|
adjust_options_for_fcsv_headers(options)
|
191
258
|
|
192
259
|
table = self.new(options) do |feeder|
|
193
260
|
first_line = true
|
194
|
-
|
261
|
+
|
262
|
+
::CSV.send(msg,param,options[:csv_options]) do |row|
|
195
263
|
if first_line
|
196
264
|
adjust_for_headers(feeder.data,row,options)
|
197
265
|
first_line = false
|
198
266
|
next if options[:has_names]
|
199
267
|
end
|
200
|
-
|
268
|
+
|
201
269
|
if block
|
202
270
|
handle_csv_row_proc(feeder,row,options,block)
|
203
271
|
else
|
204
272
|
feeder << row
|
205
|
-
end
|
273
|
+
end
|
206
274
|
end
|
207
275
|
end
|
208
|
-
|
276
|
+
|
209
277
|
return table
|
210
278
|
end
|
211
279
|
|
@@ -214,10 +282,10 @@ module Ruport::Data
|
|
214
282
|
rc = options[:record_class] || Record
|
215
283
|
row = rc.new(row, :attributes => feeder.data.column_names)
|
216
284
|
end
|
217
|
-
|
285
|
+
|
218
286
|
block[feeder,row]
|
219
287
|
end
|
220
|
-
|
288
|
+
|
221
289
|
def adjust_options_for_fcsv_headers(options)
|
222
290
|
options[:has_names] = false if options[:csv_options][:headers]
|
223
291
|
end
|
@@ -231,7 +299,7 @@ module Ruport::Data
|
|
231
299
|
end
|
232
300
|
end
|
233
301
|
|
234
|
-
include Enumerable
|
302
|
+
include Enumerable
|
235
303
|
extend FromCSV
|
236
304
|
|
237
305
|
include Ruport::Controller::Hooks
|
@@ -240,13 +308,13 @@ module Ruport::Data
|
|
240
308
|
def self.inherited(base) #:nodoc:
|
241
309
|
base.renders_as_table
|
242
310
|
end
|
243
|
-
|
311
|
+
|
244
312
|
# Creates a new table based on the supplied options.
|
245
313
|
#
|
246
314
|
# Valid options:
|
247
|
-
# <b><tt>:data</tt></b>:: An Array of Arrays representing the
|
315
|
+
# <b><tt>:data</tt></b>:: An Array of Arrays representing the
|
248
316
|
# records in this Table.
|
249
|
-
# <b><tt>:column_names</tt></b>:: An Array containing the column names
|
317
|
+
# <b><tt>:column_names</tt></b>:: An Array containing the column names
|
250
318
|
# for this Table.
|
251
319
|
# <b><tt>:filters</tt></b>:: A proc or array of procs that set up
|
252
320
|
# conditions to filter the data being
|
@@ -259,55 +327,55 @@ module Ruport::Data
|
|
259
327
|
#
|
260
328
|
# Example:
|
261
329
|
#
|
262
|
-
# table = Table.new :data => [[1,2,3], [3,4,5]],
|
330
|
+
# table = Table.new :data => [[1,2,3], [3,4,5]],
|
263
331
|
# :column_names => %w[a b c]
|
264
332
|
#
|
265
333
|
def initialize(options={})
|
266
334
|
@column_names = options[:column_names] ? options[:column_names].dup : []
|
267
335
|
@record_class = options[:record_class] &&
|
268
336
|
options[:record_class].name || "Ruport::Data::Record"
|
269
|
-
@data = []
|
270
|
-
|
337
|
+
@data = []
|
338
|
+
|
271
339
|
feeder = Feeder.new(self)
|
272
|
-
|
340
|
+
|
273
341
|
Array(options[:filters]).each { |f| feeder.filter(&f) }
|
274
342
|
Array(options[:transforms]).each { |t| feeder.transform(&t) }
|
275
|
-
|
343
|
+
|
276
344
|
if options[:data]
|
277
345
|
options[:data].each do |e|
|
278
346
|
if e.kind_of?(Record)
|
279
|
-
e = if @column_names.empty? or
|
347
|
+
e = if @column_names.empty? or
|
280
348
|
e.attributes.all? { |a| a.kind_of?(Numeric) }
|
281
349
|
e.to_a
|
282
350
|
else
|
283
|
-
e.to_hash.values_at(*@column_names)
|
351
|
+
e.to_hash.values_at(*@column_names)
|
284
352
|
end
|
285
353
|
end
|
286
354
|
r = recordize(e)
|
287
|
-
|
355
|
+
|
288
356
|
feeder << r
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
yield(feeder) if block_given?
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
yield(feeder) if block_given?
|
293
361
|
end
|
294
362
|
|
295
363
|
# This Table's column names
|
296
364
|
attr_reader :column_names
|
297
|
-
|
365
|
+
|
298
366
|
# This Table's data
|
299
|
-
attr_reader :data
|
300
|
-
|
367
|
+
attr_reader :data
|
368
|
+
|
301
369
|
require "forwardable"
|
302
370
|
extend Forwardable
|
303
371
|
def_delegators :@data, :each, :length, :size, :empty?, :[]
|
304
|
-
|
305
|
-
# Sets the column names for this table. <tt>new_column_names</tt> should
|
372
|
+
|
373
|
+
# Sets the column names for this table. <tt>new_column_names</tt> should
|
306
374
|
# be an array listing the names of the columns.
|
307
375
|
#
|
308
376
|
# Example:
|
309
|
-
#
|
310
|
-
# table = Table.new :data => [[1,2,3], [3,4,5]],
|
377
|
+
#
|
378
|
+
# table = Table.new :data => [[1,2,3], [3,4,5]],
|
311
379
|
# :column_names => %w[a b c]
|
312
380
|
#
|
313
381
|
# table.column_names = %w[e f g]
|
@@ -334,27 +402,27 @@ module Ruport::Data
|
|
334
402
|
#
|
335
403
|
# Example:
|
336
404
|
#
|
337
|
-
# one = Table.new :data => [[1,2], [3,4]],
|
405
|
+
# one = Table.new :data => [[1,2], [3,4]],
|
338
406
|
# :column_names => %w[a b]
|
339
407
|
#
|
340
|
-
# two = Table.new :data => [[1,2], [3,4]],
|
408
|
+
# two = Table.new :data => [[1,2], [3,4]],
|
341
409
|
# :column_names => %w[a b]
|
342
410
|
#
|
343
411
|
# one.eql?(two) #=> true
|
344
412
|
#
|
345
413
|
def eql?(other)
|
346
|
-
data.eql?(other.data) && column_names.eql?(other.column_names)
|
414
|
+
data.eql?(other.data) && column_names.eql?(other.column_names)
|
347
415
|
end
|
348
416
|
|
349
417
|
alias_method :==, :eql?
|
350
418
|
|
351
|
-
# Used to add extra data to the Table. <tt>row</tt> can be an Array,
|
419
|
+
# Used to add extra data to the Table. <tt>row</tt> can be an Array,
|
352
420
|
# Hash or Record. It also can be anything that implements a meaningful
|
353
421
|
# to_hash or to_ary.
|
354
422
|
#
|
355
423
|
# Example:
|
356
424
|
#
|
357
|
-
# data = Table.new :data => [[1,2], [3,4]],
|
425
|
+
# data = Table.new :data => [[1,2], [3,4]],
|
358
426
|
# :column_names => %w[a b]
|
359
427
|
# data << [8,9]
|
360
428
|
# data << { :a => 4, :b => 5}
|
@@ -362,23 +430,33 @@ module Ruport::Data
|
|
362
430
|
#
|
363
431
|
def <<(row)
|
364
432
|
@data << recordize(row)
|
365
|
-
return self
|
366
|
-
end
|
367
|
-
|
433
|
+
return self
|
434
|
+
end
|
435
|
+
|
436
|
+
# Add a row to a certain location within the existing table.
|
437
|
+
#
|
438
|
+
# data.add_row([8,9], :position => 0)
|
439
|
+
#
|
440
|
+
#
|
441
|
+
def add_row(row_data, options={})
|
442
|
+
@data.insert(options[:position] || @data.length, recordize(row_data))
|
443
|
+
return self
|
444
|
+
end
|
445
|
+
|
368
446
|
# Returns the record class constant being used by the table.
|
369
447
|
def record_class
|
370
448
|
@record_class.split("::").inject(Class) { |c,el| c.send(:const_get,el) }
|
371
449
|
end
|
372
|
-
|
450
|
+
|
373
451
|
# Used to merge two Tables by rows.
|
374
452
|
# Raises an ArgumentError if the Tables don't have identical columns.
|
375
453
|
#
|
376
454
|
# Example:
|
377
455
|
#
|
378
|
-
# inky = Table.new :data => [[1,2], [3,4]],
|
456
|
+
# inky = Table.new :data => [[1,2], [3,4]],
|
379
457
|
# :column_names => %w[a b]
|
380
458
|
#
|
381
|
-
# blinky = Table.new :data => [[5,6]],
|
459
|
+
# blinky = Table.new :data => [[5,6]],
|
382
460
|
# :column_names => %w[a b]
|
383
461
|
#
|
384
462
|
# sue = inky + blinky
|
@@ -386,11 +464,11 @@ module Ruport::Data
|
|
386
464
|
#
|
387
465
|
def +(other)
|
388
466
|
raise ArgumentError unless other.column_names == @column_names
|
389
|
-
self.class.new( :column_names => @column_names,
|
467
|
+
self.class.new( :column_names => @column_names,
|
390
468
|
:data => @data + other.data,
|
391
469
|
:record_class => record_class )
|
392
470
|
end
|
393
|
-
|
471
|
+
|
394
472
|
# Allows you to change the order of, or reduce the number of columns in a
|
395
473
|
# Table.
|
396
474
|
#
|
@@ -411,40 +489,40 @@ module Ruport::Data
|
|
411
489
|
def reorder(*indices)
|
412
490
|
raise(ArgumentError,"Can't reorder without column names set!") if
|
413
491
|
@column_names.empty?
|
414
|
-
|
492
|
+
|
415
493
|
indices = indices[0] if indices[0].kind_of? Array
|
416
|
-
|
417
|
-
if indices.all? { |i| i.kind_of? Integer }
|
418
|
-
indices.map! { |i| @column_names[i] }
|
494
|
+
|
495
|
+
if indices.all? { |i| i.kind_of? Integer }
|
496
|
+
indices.map! { |i| @column_names[i] }
|
419
497
|
end
|
420
|
-
|
498
|
+
|
421
499
|
reduce(indices)
|
422
500
|
end
|
423
|
-
|
501
|
+
|
424
502
|
# Adds an extra column to the Table.
|
425
503
|
#
|
426
504
|
# Available Options:
|
427
|
-
# <b><tt>:default</tt></b>:: The default value to use for the column in
|
505
|
+
# <b><tt>:default</tt></b>:: The default value to use for the column in
|
428
506
|
# existing rows. Set to nil if not specified.
|
429
|
-
#
|
507
|
+
#
|
430
508
|
# <b><tt>:position</tt></b>:: Inserts the column at the indicated position
|
431
509
|
# number.
|
432
510
|
#
|
433
|
-
# <b><tt>:before</tt></b>:: Inserts the new column before the column
|
511
|
+
# <b><tt>:before</tt></b>:: Inserts the new column before the column
|
434
512
|
# indicated (by name).
|
435
513
|
#
|
436
514
|
# <b><tt>:after</tt></b>:: Inserts the new column after the column
|
437
515
|
# indicated (by name).
|
438
516
|
#
|
439
517
|
# If a block is provided, it will be used to build up the column.
|
440
|
-
#
|
518
|
+
#
|
441
519
|
# Example:
|
442
520
|
#
|
443
|
-
# data = Table("a","b") { |t| t << [1,2] << [3,4] }
|
444
|
-
#
|
521
|
+
# data = Table.new("a","b") { |t| t << [1,2] << [3,4] }
|
522
|
+
#
|
445
523
|
# # basic usage, column full of 1's
|
446
524
|
# data.add_column 'new_column', :default => 1
|
447
|
-
#
|
525
|
+
#
|
448
526
|
# # new empty column before new_column
|
449
527
|
# data.add_column 'new_col2', :before => 'new_column'
|
450
528
|
#
|
@@ -453,43 +531,43 @@ module Ruport::Data
|
|
453
531
|
#
|
454
532
|
# # new column built via a block, added at the end of the table
|
455
533
|
# data.add_column("new_col4") { |r| r.a + r.b }
|
456
|
-
#
|
534
|
+
#
|
457
535
|
def add_column(name,options={})
|
458
536
|
if pos = options[:position]
|
459
|
-
column_names.insert(pos,name)
|
537
|
+
column_names.insert(pos,name)
|
460
538
|
elsif pos = options[:after]
|
461
|
-
column_names.insert(column_names.index(pos)+1,name)
|
539
|
+
column_names.insert(column_names.index(pos)+1,name)
|
462
540
|
elsif pos = options[:before]
|
463
541
|
column_names.insert(column_names.index(pos),name)
|
464
542
|
else
|
465
543
|
column_names << name
|
466
|
-
end
|
544
|
+
end
|
467
545
|
|
468
546
|
if block_given?
|
469
547
|
each { |r| r[name] = yield(r) || options[:default] }
|
470
548
|
else
|
471
549
|
each { |r| r[name] = options[:default] }
|
472
550
|
end; self
|
473
|
-
end
|
474
|
-
|
551
|
+
end
|
552
|
+
|
475
553
|
# Add multiple extra columns to the Table. See <tt>add_column</tt> for
|
476
554
|
# a list of available options.
|
477
|
-
#
|
555
|
+
#
|
478
556
|
# Example:
|
479
557
|
#
|
480
|
-
# data = Table("a","b") { |t| t << [1,2] << [3,4] }
|
481
|
-
#
|
558
|
+
# data = Table.new("a","b") { |t| t << [1,2] << [3,4] }
|
559
|
+
#
|
482
560
|
# data.add_columns ['new_column_1','new_column_2'], :default => 1
|
483
561
|
#
|
484
|
-
def add_columns(names,options={})
|
562
|
+
def add_columns(names,options={})
|
485
563
|
raise "Greg isn't smart enough to figure this out.\n"+
|
486
564
|
"Send ideas in at http://list.rubyreports.org" if block_given?
|
487
565
|
need_reverse = !!(options[:after] || options[:position])
|
488
566
|
names = names.reverse if need_reverse
|
489
|
-
names.each { |n| add_column(n,options) }
|
567
|
+
names.each { |n| add_column(n,options) }
|
490
568
|
self
|
491
569
|
end
|
492
|
-
|
570
|
+
|
493
571
|
# Removes the given column from the table. May use name or position.
|
494
572
|
#
|
495
573
|
# Example:
|
@@ -502,7 +580,7 @@ module Ruport::Data
|
|
502
580
|
column_names.delete(col)
|
503
581
|
each { |r| r.send(:delete,col) }
|
504
582
|
end
|
505
|
-
|
583
|
+
|
506
584
|
# Removes multiple columns from the table. May use name or position
|
507
585
|
# Will autosplat arrays.
|
508
586
|
#
|
@@ -514,9 +592,9 @@ module Ruport::Data
|
|
514
592
|
cols = cols[0] if cols[0].kind_of? Array
|
515
593
|
cols.each { |col| remove_column(col) }
|
516
594
|
end
|
517
|
-
|
595
|
+
|
518
596
|
# Renames a column. Will update Record attributes as well.
|
519
|
-
#
|
597
|
+
#
|
520
598
|
# Example:
|
521
599
|
#
|
522
600
|
# old_values = table.map { |r| r.a }
|
@@ -528,12 +606,12 @@ module Ruport::Data
|
|
528
606
|
def rename_column(old_name,new_name)
|
529
607
|
index = column_names.index(old_name) or return
|
530
608
|
self.column_names[index] = new_name
|
531
|
-
each { |r| r.rename_attribute(old_name,new_name,false)}
|
609
|
+
each { |r| r.rename_attribute(old_name,new_name,false)}
|
532
610
|
end
|
533
611
|
|
534
612
|
# Renames multiple columns. Takes either a hash of "old" => "new"
|
535
613
|
# names or two arrays of names %w[old names],%w[new names].
|
536
|
-
#
|
614
|
+
#
|
537
615
|
# Example:
|
538
616
|
#
|
539
617
|
# table.column_names #=> ["a", "b"]
|
@@ -553,7 +631,7 @@ module Ruport::Data
|
|
553
631
|
end
|
554
632
|
return
|
555
633
|
end
|
556
|
-
|
634
|
+
|
557
635
|
raise ArgumentError unless old_cols
|
558
636
|
|
559
637
|
if new_cols
|
@@ -565,12 +643,12 @@ module Ruport::Data
|
|
565
643
|
end
|
566
644
|
h.each {|old,new| rename_column(old,new) }
|
567
645
|
end
|
568
|
-
|
646
|
+
|
569
647
|
# Exchanges one column with another.
|
570
648
|
#
|
571
|
-
# Example:
|
649
|
+
# Example:
|
572
650
|
#
|
573
|
-
# >> a = Table(%w[a b c]) { |t| t << [1,2,3] << [4,5,6] }
|
651
|
+
# >> a = Table.new(%w[a b c]) { |t| t << [1,2,3] << [4,5,6] }
|
574
652
|
# >> puts a
|
575
653
|
# +-----------+
|
576
654
|
# | a | b | c |
|
@@ -587,24 +665,24 @@ module Ruport::Data
|
|
587
665
|
# | 6 | 5 | 4 |
|
588
666
|
# +-----------+
|
589
667
|
#
|
590
|
-
def swap_column(a,b)
|
668
|
+
def swap_column(a,b)
|
591
669
|
if [a,b].all? { |r| r.kind_of? Fixnum }
|
592
670
|
col_a,col_b = column_names[a],column_names[b]
|
593
671
|
column_names[a] = col_b
|
594
672
|
column_names[b] = col_a
|
595
673
|
else
|
596
|
-
a_ind, b_ind = [column_names.index(a), column_names.index(b)]
|
674
|
+
a_ind, b_ind = [column_names.index(a), column_names.index(b)]
|
597
675
|
column_names[b_ind] = a
|
598
676
|
column_names[a_ind] = b
|
599
677
|
end
|
600
678
|
end
|
601
|
-
|
679
|
+
|
602
680
|
# Allows you to specify a new column to replace an existing column
|
603
681
|
# in your table via a block.
|
604
682
|
#
|
605
683
|
# Example:
|
606
684
|
#
|
607
|
-
# >> a = Table(%w[a b c]) { |t| t << [1,2,3] << [4,5,6] }
|
685
|
+
# >> a = Table.new(%w[a b c]) { |t| t << [1,2,3] << [4,5,6] }
|
608
686
|
# >> a.replace_column("c","c2") { |r| r.c * 2 + r.a }
|
609
687
|
#
|
610
688
|
# >> puts a
|
@@ -622,12 +700,12 @@ module Ruport::Data
|
|
622
700
|
else
|
623
701
|
each { |r| r[old_col] = yield(r) }
|
624
702
|
end
|
625
|
-
end
|
626
|
-
|
703
|
+
end
|
704
|
+
|
627
705
|
# Generates a sub table
|
628
|
-
#
|
706
|
+
#
|
629
707
|
# Examples:
|
630
|
-
#
|
708
|
+
#
|
631
709
|
# table = [[1,2,3,4],[5,6,7,8],[9,10,11,12]].to_table(%w[a b c d])
|
632
710
|
#
|
633
711
|
# Using column_names and a range:
|
@@ -641,16 +719,16 @@ module Ruport::Data
|
|
641
719
|
# sub_table == [[1,4],[5,8],[9,12]].to_table(%w[a d]) #=> true
|
642
720
|
#
|
643
721
|
# Using column_names and a block:
|
644
|
-
#
|
645
|
-
# sub_table = table.sub_table(%w[d b]) { |r| r.a < 6 }
|
646
|
-
# sub_table == [[4,2],[8,6]].to_table(%w[d b]) #=> true
|
722
|
+
#
|
723
|
+
# sub_table = table.sub_table(%w[d b]) { |r| r.a < 6 }
|
724
|
+
# sub_table == [[4,2],[8,6]].to_table(%w[d b]) #=> true
|
647
725
|
#
|
648
726
|
# Using a range for row reduction:
|
649
727
|
# sub_table = table.sub_table(1..-1)
|
650
728
|
# sub_table == [[5,6,7,8],[9,10,11,12]].to_table(%w[a b c d]) #=> true
|
651
729
|
#
|
652
730
|
# Using just a block:
|
653
|
-
#
|
731
|
+
#
|
654
732
|
# sub_table = table.sub_table { |r| r.c > 10 }
|
655
733
|
# sub_table == [[9,10,11,12]].to_table(%w[a b c d]) #=> true
|
656
734
|
#
|
@@ -662,8 +740,8 @@ module Ruport::Data
|
|
662
740
|
elsif block
|
663
741
|
self.class.new( :column_names => cor, :data => data.select(&block))
|
664
742
|
else
|
665
|
-
self.class.new( :column_names => cor, :data => data)
|
666
|
-
end
|
743
|
+
self.class.new( :column_names => cor, :data => data)
|
744
|
+
end
|
667
745
|
end
|
668
746
|
|
669
747
|
# Generates a sub table in place, modifying the receiver. See documentation
|
@@ -677,7 +755,7 @@ module Ruport::Data
|
|
677
755
|
end
|
678
756
|
|
679
757
|
alias_method :sub_table!, :reduce
|
680
|
-
|
758
|
+
|
681
759
|
# Returns an array of values for the given column name.
|
682
760
|
#
|
683
761
|
# Example:
|
@@ -689,20 +767,20 @@ module Ruport::Data
|
|
689
767
|
case(name)
|
690
768
|
when Integer
|
691
769
|
unless column_names.empty?
|
692
|
-
raise ArgumentError if name > column_names.length
|
770
|
+
raise ArgumentError if name > column_names.length
|
693
771
|
end
|
694
772
|
else
|
695
773
|
raise ArgumentError unless column_names.include?(name)
|
696
774
|
end
|
697
|
-
|
775
|
+
|
698
776
|
map { |r| r[name] }
|
699
777
|
end
|
700
|
-
|
778
|
+
|
701
779
|
# Calculates sums. If a column name or index is given, it will try to
|
702
|
-
# convert each element of that column to an integer or float
|
780
|
+
# convert each element of that column to an integer or float
|
703
781
|
# and add them together.
|
704
782
|
#
|
705
|
-
# If a block is given, it yields each Record so that you can do your own
|
783
|
+
# If a block is given, it yields each Record so that you can do your own
|
706
784
|
# calculation.
|
707
785
|
#
|
708
786
|
# Example:
|
@@ -714,7 +792,7 @@ module Ruport::Data
|
|
714
792
|
# table.sigma { |r| r.col2 + 1 } #=> 15
|
715
793
|
#
|
716
794
|
def sigma(column=nil)
|
717
|
-
inject(0) { |s,r|
|
795
|
+
inject(0) { |s,r|
|
718
796
|
if column
|
719
797
|
s + if r.get(column).kind_of? Numeric
|
720
798
|
r.get(column)
|
@@ -724,12 +802,12 @@ module Ruport::Data
|
|
724
802
|
else
|
725
803
|
s + yield(r)
|
726
804
|
end
|
727
|
-
}
|
805
|
+
}
|
728
806
|
end
|
729
807
|
|
730
808
|
alias_method :sum, :sigma
|
731
809
|
|
732
|
-
# Returns a sorted table. If col_names is specified,
|
810
|
+
# Returns a sorted table. If col_names is specified,
|
733
811
|
# the block is ignored and the table is sorted by the named columns.
|
734
812
|
#
|
735
813
|
# The second argument specifies sorting options. Currently only
|
@@ -759,54 +837,54 @@ module Ruport::Data
|
|
759
837
|
# table.sort_rows_by(["col1", "col2"], :order => descending)
|
760
838
|
#
|
761
839
|
def sort_rows_by(col_names=nil, options={}, &block)
|
762
|
-
# stabilizer is needed because of
|
840
|
+
# stabilizer is needed because of
|
763
841
|
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/170565
|
764
842
|
stabilizer = 0
|
765
|
-
|
766
|
-
nil_rows, sortable = partition do |r|
|
767
|
-
Array(col_names).any? { |c| r[c].nil? }
|
843
|
+
|
844
|
+
nil_rows, sortable = partition do |r|
|
845
|
+
Array(col_names).any? { |c| r[c].nil? }
|
768
846
|
end
|
769
847
|
|
770
848
|
data_array =
|
771
849
|
if col_names
|
772
|
-
sortable.sort_by do |r|
|
850
|
+
sortable.sort_by do |r|
|
773
851
|
stabilizer += 1
|
774
|
-
[Array(col_names).map {|col| r[col]}, stabilizer]
|
852
|
+
[Array(col_names).map {|col| r[col]}, stabilizer]
|
775
853
|
end
|
776
854
|
else
|
777
855
|
sortable.sort_by(&block)
|
778
|
-
end
|
779
|
-
|
856
|
+
end
|
857
|
+
|
780
858
|
data_array += nil_rows
|
781
|
-
data_array.reverse! if options[:order] == :descending
|
859
|
+
data_array.reverse! if options[:order] == :descending
|
782
860
|
|
783
|
-
table = self.class.new( :data => data_array,
|
861
|
+
table = self.class.new( :data => data_array,
|
784
862
|
:column_names => @column_names,
|
785
863
|
:record_class => record_class )
|
786
864
|
|
787
865
|
return table
|
788
|
-
end
|
789
|
-
|
866
|
+
end
|
867
|
+
|
790
868
|
# Same as Table#sort_rows_by, but self modifying.
|
791
869
|
# See <tt>sort_rows_by</tt> for documentation.
|
792
870
|
#
|
793
871
|
def sort_rows_by!(col_names=nil,options={},&block)
|
794
|
-
table = sort_rows_by(col_names,options,&block)
|
872
|
+
table = sort_rows_by(col_names,options,&block)
|
795
873
|
@data = table.data
|
796
874
|
end
|
797
|
-
|
875
|
+
|
798
876
|
# Get an array of records from the Table limited by the criteria specified.
|
799
877
|
#
|
800
878
|
# Example:
|
801
879
|
#
|
802
|
-
# table = Table.new :data => [[1,2,3], [1,4,6], [4,5,6]],
|
880
|
+
# table = Table.new :data => [[1,2,3], [1,4,6], [4,5,6]],
|
803
881
|
# :column_names => %w[a b c]
|
804
882
|
# table.rows_with(:a => 1) #=> [[1,2,3], [1,4,6]]
|
805
883
|
# table.rows_with(:a => 1, :b => 4) #=> [[1,4,6]]
|
806
884
|
# table.rows_with_a(1) #=> [[1,2,3], [1,4,6]]
|
807
885
|
# table.rows_with(%w[a b]) {|a,b| [a,b] == [1,4] } #=> [[1,4,6]]
|
808
886
|
#
|
809
|
-
def rows_with(columns,&block)
|
887
|
+
def rows_with(columns,&block)
|
810
888
|
select { |r|
|
811
889
|
if block
|
812
890
|
block[*(columns.map { |c| r.get(c) })]
|
@@ -816,11 +894,48 @@ module Ruport::Data
|
|
816
894
|
}
|
817
895
|
end
|
818
896
|
|
897
|
+
# Search row for a string and return the position
|
898
|
+
#
|
899
|
+
# Example:
|
900
|
+
#
|
901
|
+
# table = Table.new :data => [["Mow Lawn","50"], ["Sew","40"], ["Clean dishes","5"]],
|
902
|
+
# :column_names => %w[task cost]
|
903
|
+
# table.row_search("Sew", :column => 0) #=> [[1,2,3], [1,4,6]]
|
904
|
+
#
|
905
|
+
# Search for a number in column 0 greater than 999.
|
906
|
+
# result = table.row_search(999, :column => 0, :greater_than => true)
|
907
|
+
#
|
908
|
+
#
|
909
|
+
def row_search(search, options={})
|
910
|
+
position = 0
|
911
|
+
|
912
|
+
if column = options[:column]
|
913
|
+
self.each do |row|
|
914
|
+
|
915
|
+
if gt=options[:greater_than]
|
916
|
+
return position if row[column] > search
|
917
|
+
end
|
918
|
+
|
919
|
+
if lt=options[:less_than]
|
920
|
+
return position if row[column] < search
|
921
|
+
end
|
922
|
+
|
923
|
+
unless gt or lt
|
924
|
+
if row[column] =~ /#{search}/ # Search for part of or whole search text.
|
925
|
+
return position
|
926
|
+
end
|
927
|
+
end
|
928
|
+
|
929
|
+
position += 1
|
930
|
+
end
|
931
|
+
end
|
932
|
+
end
|
933
|
+
|
819
934
|
# Create a copy of the Table. Records will be copied as well.
|
820
935
|
#
|
821
936
|
# Example:
|
822
937
|
#
|
823
|
-
# one = Table.new :data => [[1,2], [3,4]],
|
938
|
+
# one = Table.new :data => [[1,2], [3,4]],
|
824
939
|
# :column_names => %w[a b]
|
825
940
|
# two = one.dup
|
826
941
|
#
|
@@ -830,32 +945,32 @@ module Ruport::Data
|
|
830
945
|
@data = []
|
831
946
|
from.data.each { |r| self << r.dup }
|
832
947
|
end
|
833
|
-
|
948
|
+
|
834
949
|
# Uses Ruport's built-in text formatter to render this Table into a String.
|
835
|
-
#
|
950
|
+
#
|
836
951
|
# Example:
|
837
|
-
#
|
838
|
-
# data = Table.new :data => [[1,2], [3,4]],
|
952
|
+
#
|
953
|
+
# data = Table.new :data => [[1,2], [3,4]],
|
839
954
|
# :column_names => %w[a b]
|
840
955
|
# puts data.to_s
|
841
|
-
#
|
956
|
+
#
|
842
957
|
def to_s
|
843
958
|
as(:text)
|
844
|
-
end
|
959
|
+
end
|
845
960
|
|
846
961
|
# Convert the Table into a Group using the supplied group name.
|
847
962
|
#
|
848
|
-
# data = Table.new :data => [[1,2], [3,4]],
|
963
|
+
# data = Table.new :data => [[1,2], [3,4]],
|
849
964
|
# :column_names => %w[a b]
|
850
965
|
# group = data.to_group("my_group")
|
851
966
|
#
|
852
967
|
def to_group(name=nil)
|
853
|
-
Group.new( :data => data,
|
968
|
+
Group.new( :data => data,
|
854
969
|
:column_names => column_names,
|
855
970
|
:name => name,
|
856
971
|
:record_class => record_class )
|
857
972
|
end
|
858
|
-
|
973
|
+
|
859
974
|
# NOTE: does not respect tainted status
|
860
975
|
alias_method :clone, :dup
|
861
976
|
|
@@ -866,50 +981,50 @@ module Ruport::Data
|
|
866
981
|
# <tt>rows_with(:columnname => args[0])</tt>.
|
867
982
|
#
|
868
983
|
def method_missing(id,*args,&block)
|
869
|
-
return as($1.to_sym,*args,&block) if id.to_s =~ /^to_(.*)/
|
984
|
+
return as($1.to_sym,*args,&block) if id.to_s =~ /^to_(.*)/
|
870
985
|
return rows_with($1.to_sym => args[0]) if id.to_s =~ /^rows_with_(.*)/
|
871
986
|
super
|
872
987
|
end
|
873
|
-
|
988
|
+
|
874
989
|
def feed_element(row)
|
875
990
|
recordize(row)
|
876
991
|
end
|
877
|
-
|
878
|
-
private
|
879
|
-
|
992
|
+
|
993
|
+
private
|
994
|
+
|
880
995
|
def recordize(row)
|
881
996
|
case row
|
882
997
|
when Array
|
883
998
|
normalize_array(row)
|
884
999
|
when Hash
|
885
|
-
normalize_hash(row)
|
886
|
-
when record_class
|
1000
|
+
normalize_hash(row)
|
1001
|
+
when record_class
|
887
1002
|
recordize(normalize_record(row))
|
888
1003
|
else
|
889
1004
|
normalize_hash(row) rescue normalize_array(row)
|
890
|
-
end
|
891
|
-
end
|
892
|
-
|
1005
|
+
end
|
1006
|
+
end
|
1007
|
+
|
893
1008
|
def normalize_hash(hash_obj)
|
894
|
-
hash_obj = hash_obj.to_hash
|
1009
|
+
hash_obj = hash_obj.to_hash
|
895
1010
|
raise ArgumentError unless @column_names
|
896
1011
|
record_class.new(hash_obj, :attributes => @column_names)
|
897
|
-
end
|
898
|
-
|
1012
|
+
end
|
1013
|
+
|
899
1014
|
def normalize_record(record)
|
900
1015
|
record.send(column_names.empty? ? :to_a : :to_hash)
|
901
|
-
end
|
902
|
-
|
1016
|
+
end
|
1017
|
+
|
903
1018
|
def normalize_array(array)
|
904
|
-
attributes = @column_names.empty? ? nil : @column_names
|
905
|
-
record_class.new(array.to_ary, :attributes => attributes)
|
906
|
-
end
|
1019
|
+
attributes = @column_names.empty? ? nil : @column_names
|
1020
|
+
record_class.new(array.to_ary, :attributes => attributes)
|
1021
|
+
end
|
907
1022
|
end
|
908
1023
|
end
|
909
1024
|
|
910
1025
|
|
911
|
-
module
|
912
|
-
|
1026
|
+
module Ruport
|
1027
|
+
|
913
1028
|
# Shortcut interface for creating Data::Tables
|
914
1029
|
#
|
915
1030
|
# Examples:
|
@@ -918,33 +1033,30 @@ module Kernel
|
|
918
1033
|
# t = Table("a","b","c") #=> creates a new empty table w. cols a,b,c
|
919
1034
|
#
|
920
1035
|
# # allows building table inside of block, returns table object
|
921
|
-
# t = Table(%w[a b c]) { |t| t << [1,2,3] }
|
1036
|
+
# t = Table(%w[a b c]) { |t| t << [1,2,3] }
|
922
1037
|
#
|
923
1038
|
# # allows loading table from CSV
|
924
1039
|
# # accepts all Data::Table.load options, including block (yields table,row)
|
925
1040
|
#
|
926
1041
|
# t = Table("foo.csv")
|
927
1042
|
# t = Table("bar.csv", :has_names => false)
|
928
|
-
def Table(*args,&block)
|
929
|
-
table=
|
1043
|
+
def self.Table(*args,&block)
|
930
1044
|
case(args[0])
|
931
1045
|
when Array
|
932
1046
|
opts = args[1] || {}
|
933
1047
|
Ruport::Data::Table.new(f={:column_names => args[0]}.merge(opts),&block)
|
934
1048
|
when /\.csv/
|
935
|
-
|
1049
|
+
Ruport::Data::Table.load(*args,&block)
|
936
1050
|
when Hash
|
937
1051
|
if file = args[0].delete(:file)
|
938
|
-
|
1052
|
+
Ruport::Data::Table.load(file,args[0],&block)
|
939
1053
|
elsif string = args[0].delete(:string)
|
940
|
-
|
1054
|
+
Ruport::Data::Table.parse(string,args[0],&block)
|
941
1055
|
else
|
942
|
-
|
1056
|
+
Ruport::Data::Table.new(args[0],&block)
|
943
1057
|
end
|
944
1058
|
else
|
945
1059
|
Ruport::Data::Table.new(:data => [], :column_names => args,&block)
|
946
|
-
end
|
947
|
-
|
948
|
-
return table
|
1060
|
+
end
|
949
1061
|
end
|
950
|
-
end
|
1062
|
+
end
|