ruport 1.6.3 → 1.7.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|