ruport 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/AUTHORS +4 -2
- data/README +4 -3
- data/Rakefile +1 -1
- data/lib/ruport.rb +1 -4
- data/lib/ruport/config.rb +6 -11
- data/lib/ruport/data/collection.rb +8 -2
- data/lib/ruport/data/groupable.rb +0 -2
- data/lib/ruport/data/record.rb +0 -13
- data/lib/ruport/data/set.rb +0 -8
- data/lib/ruport/data/table.rb +1 -18
- data/lib/ruport/data/taggable.rb +1 -6
- data/lib/ruport/format/latex.rb +5 -8
- data/lib/ruport/format/pdf.rb +56 -0
- data/lib/ruport/format/plugin.rb +5 -0
- data/lib/ruport/format/svg.rb +26 -4
- data/lib/ruport/format/text.rb +25 -2
- data/lib/ruport/format/xml.rb +1 -1
- data/lib/ruport/generator.rb +1 -1
- data/lib/ruport/mailer.rb +1 -4
- data/lib/ruport/query.rb +0 -9
- data/lib/ruport/renderer.rb +15 -1
- data/lib/ruport/report.rb +5 -22
- data/test/test_mailer.rb +86 -24
- data/test/test_report.rb +72 -1
- data/test/test_table.rb +15 -0
- data/test/unit.log +7 -277
- metadata +34 -44
- data/lib/ruport.rb~ +0 -69
- data/lib/ruport/data.rb.rej +0 -5
- data/lib/ruport/data.rb~ +0 -1
- data/lib/ruport/data/record.rb~ +0 -236
- data/lib/ruport/data/table.rb.rej +0 -67
- data/lib/ruport/data/table.rb~ +0 -414
- data/test/test_query.rb.rej +0 -161
- data/test/test_query.rb~ +0 -337
- data/test/test_record.rb.rej +0 -46
- data/test/test_table.rb~ +0 -336
data/lib/ruport/data.rb.rej
DELETED
data/lib/ruport/data.rb~
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
%w[taggable record collection table set groupable].each { |l| require "ruport/data/#{l}" }
|
data/lib/ruport/data/record.rb~
DELETED
@@ -1,236 +0,0 @@
|
|
1
|
-
# The Ruport Data Collections.
|
2
|
-
# Authors: Gregory Brown / Dudley Flanders
|
3
|
-
#
|
4
|
-
# This is Free Software. For details, see LICENSE and COPYING
|
5
|
-
# Copyright 2006 by respective content owners, all rights reserved.
|
6
|
-
module Ruport::Data
|
7
|
-
|
8
|
-
#
|
9
|
-
# === Overview
|
10
|
-
#
|
11
|
-
# Data::Records are the work horse of Ruport's data model. These can behave
|
12
|
-
# as Array-like, Hash-like, or Struct-like objects. They are used as the
|
13
|
-
# base element in both Tables and Sets.
|
14
|
-
#
|
15
|
-
class Record
|
16
|
-
require "forwardable"
|
17
|
-
extend Forwardable
|
18
|
-
include Enumerable
|
19
|
-
include Taggable
|
20
|
-
|
21
|
-
#
|
22
|
-
# Creates a new Record object. If the <tt>:attributes</tt>
|
23
|
-
# keyword is specified, Hash-like and Struct-like
|
24
|
-
# access will be enabled. Otherwise, Record elements may be
|
25
|
-
# accessed ordinally, like an Array.
|
26
|
-
#
|
27
|
-
# A Record can accept either a Hash or an Array as its <tt>data</tt>.
|
28
|
-
#
|
29
|
-
# Examples:
|
30
|
-
# a = Record.new [1,2,3]
|
31
|
-
# a[1] #=> 2
|
32
|
-
#
|
33
|
-
# b = Record.new [1,2,3], :attributes => %w[a b c]
|
34
|
-
# b[1] #=> 2
|
35
|
-
# b['a'] #=> 1
|
36
|
-
# b.c #=> 3
|
37
|
-
#
|
38
|
-
# c = Record.new {"a" => 1, "c" => 3, "b" => 2}, :attributes => %w[a b c]
|
39
|
-
# b[1] #=> 2
|
40
|
-
# b['a'] #=> 1
|
41
|
-
# b.c #=> 3
|
42
|
-
#
|
43
|
-
# c = Record.new { "a" => 1, "c" => 3, "b" => 2 }
|
44
|
-
# b[1] #=> ? (without attributes, you cannot rely on order)
|
45
|
-
# b['a'] #=> 1
|
46
|
-
# b.c #=> 3
|
47
|
-
#
|
48
|
-
def initialize(data,options={})
|
49
|
-
if data.kind_of?(Hash)
|
50
|
-
if options[:attributes]
|
51
|
-
@attributes = options[:attributes]
|
52
|
-
@data = options[:attributes].map { |k| data[k] }
|
53
|
-
else
|
54
|
-
@attributes, @data = data.to_a.transpose
|
55
|
-
end
|
56
|
-
else
|
57
|
-
@data = data.dup
|
58
|
-
@attributes = if options[:attributes]
|
59
|
-
options[:attributes]
|
60
|
-
else
|
61
|
-
[]
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# The underlying <tt>data</tt> which is being stored in the record.
|
67
|
-
attr_reader :data
|
68
|
-
|
69
|
-
def_delegators :@data,:each, :length
|
70
|
-
|
71
|
-
#
|
72
|
-
# Allows either Array or Hash-like indexing.
|
73
|
-
#
|
74
|
-
# Examples:
|
75
|
-
#
|
76
|
-
# my_record[1]
|
77
|
-
# my_record["foo"]
|
78
|
-
#
|
79
|
-
def [](index)
|
80
|
-
if index.kind_of? Integer
|
81
|
-
raise "Invalid index" unless index < @data.length
|
82
|
-
@data[index]
|
83
|
-
else
|
84
|
-
raise "Invalid index" unless attributes.index(index.to_s)
|
85
|
-
@data[attributes.index(index.to_s)]
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
|
90
|
-
#
|
91
|
-
# Allows setting a <tt>value</tt> at an <tt>index</tt>.
|
92
|
-
#
|
93
|
-
# Examples:
|
94
|
-
#
|
95
|
-
# my_record[1] = "foo"
|
96
|
-
# my_record["bar"] = "baz"
|
97
|
-
#
|
98
|
-
def []=(index, value)
|
99
|
-
if index.kind_of? Integer
|
100
|
-
raise "Invalid index" unless index < @data.length
|
101
|
-
@data[index] = value
|
102
|
-
else
|
103
|
-
raise "Invalid index" unless attributes.index(index.to_s)
|
104
|
-
@data[attributes.index(index.to_s)] = value
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
#
|
109
|
-
# If <tt>attributes</tt> and <tt>data</tt> are equivalent, then
|
110
|
-
# <tt>==</tt> evaluates to true. Otherwise, <tt>==</tt> returns false.
|
111
|
-
#
|
112
|
-
def ==(other)
|
113
|
-
return false if attributes && !other.attributes
|
114
|
-
return false if other.attributes && !attributes
|
115
|
-
attributes == other.attributes && @data == other.data
|
116
|
-
end
|
117
|
-
|
118
|
-
alias_method :eql?, :==
|
119
|
-
|
120
|
-
#
|
121
|
-
# Converts a Record into an Array.
|
122
|
-
#
|
123
|
-
# Example:
|
124
|
-
#
|
125
|
-
# a = Data::Record.new([1,2],:attributes => %w[a b])
|
126
|
-
# a.to_a #=> [1,2]
|
127
|
-
#
|
128
|
-
def to_a; @data.dup; end
|
129
|
-
|
130
|
-
#
|
131
|
-
# Converts a Record into a Hash. This only works if <tt>attributes</tt>
|
132
|
-
# are specified in the Record.
|
133
|
-
#
|
134
|
-
# Example:
|
135
|
-
#
|
136
|
-
# a = Data::Record.new([1,2],:attributes => %w[a b])
|
137
|
-
# a.to_h #=> {"a" => 1, "b" => 2}
|
138
|
-
#
|
139
|
-
def to_h; Hash[*attributes.zip(data).flatten] end
|
140
|
-
|
141
|
-
#
|
142
|
-
# Returns a copy of the <tt>attributes</tt> from this Record.
|
143
|
-
#
|
144
|
-
# Example:
|
145
|
-
#
|
146
|
-
# a = Data::Record.new([1,2],:attributes => %w[a b])
|
147
|
-
# a.attributes #=> ["a","b"]
|
148
|
-
#
|
149
|
-
def attributes; @attributes.map { |a| a.to_s }; end
|
150
|
-
|
151
|
-
#
|
152
|
-
# Sets the <tt>attribute</tt> list for this Record.
|
153
|
-
#
|
154
|
-
# Example:
|
155
|
-
#
|
156
|
-
# my_record.attributes = %w[foo bar baz]
|
157
|
-
#
|
158
|
-
attr_writer :attributes
|
159
|
-
|
160
|
-
#
|
161
|
-
# Allows you to change the order of or reduce the number of columns in a
|
162
|
-
# Record.
|
163
|
-
#
|
164
|
-
# Example:
|
165
|
-
#
|
166
|
-
# a = Data::Record.new([1,2,3,4],:attributes => %w[a b c d])
|
167
|
-
# b = a.reorder("a","d","b")
|
168
|
-
# b.attributes #=> ["a","d","b"]
|
169
|
-
# b.data #=> [1,4,2]
|
170
|
-
#
|
171
|
-
def reorder(*indices)
|
172
|
-
dup.reorder!(*indices)
|
173
|
-
end
|
174
|
-
|
175
|
-
# Same as Record#reorder but modifies its reciever in place.
|
176
|
-
def reorder!(*indices)
|
177
|
-
indices = reorder_data!(*indices)
|
178
|
-
if attributes
|
179
|
-
if indices.all? { |e| e.kind_of? Integer }
|
180
|
-
@attributes = indices.map { |i| attributes[i] }
|
181
|
-
else
|
182
|
-
@attributes = indices
|
183
|
-
end
|
184
|
-
end; self
|
185
|
-
end
|
186
|
-
|
187
|
-
def reorder_data!(*indices) # :nodoc:
|
188
|
-
indices = indices[0] if indices[0].kind_of?(Array)
|
189
|
-
indices.each do |i|
|
190
|
-
self[i] rescue raise ArgumentError,
|
191
|
-
"you may have specified an invalid column"
|
192
|
-
end
|
193
|
-
@data = indices.map { |i| self[i] }
|
194
|
-
return indices;
|
195
|
-
end
|
196
|
-
|
197
|
-
|
198
|
-
# Makes a fresh copy of the Record.
|
199
|
-
def dup
|
200
|
-
copy = self.class.new(@data,:attributes => attributes)
|
201
|
-
copy.tags = self.tags.dup
|
202
|
-
return copy
|
203
|
-
end
|
204
|
-
|
205
|
-
#FIXME: This does not take into account frozen / tainted state
|
206
|
-
alias_method :clone, :dup
|
207
|
-
|
208
|
-
#
|
209
|
-
# Provides a unique hash value. If a Record contains the same data and
|
210
|
-
# attributes as another Record, they will hash to the same value, even if
|
211
|
-
# they are not the same object. This is similar to the way Array works,
|
212
|
-
# but different from Hash and other objects.
|
213
|
-
#
|
214
|
-
def hash
|
215
|
-
(attributes.to_a + data.to_a).hash
|
216
|
-
end
|
217
|
-
|
218
|
-
#
|
219
|
-
# Provides accessor style methods for attribute access.
|
220
|
-
#
|
221
|
-
# Example:
|
222
|
-
#
|
223
|
-
# my_record.foo = 2
|
224
|
-
# my_record.foo #=> 2
|
225
|
-
#
|
226
|
-
def method_missing(id,*args)
|
227
|
-
id = id.to_s.gsub(/=$/,"")
|
228
|
-
if attributes.include?(id)
|
229
|
-
args.empty? ? self[id] : self[id] = args.first
|
230
|
-
else
|
231
|
-
super
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
end
|
236
|
-
end
|
@@ -1,67 +0,0 @@
|
|
1
|
-
***************
|
2
|
-
*** 287,294 ****
|
3
|
-
# # pass in FasterCSV options, such as column separators
|
4
|
-
# table = Table.load('mydata.csv',:csv_opts => { :col_sep => "\t" })
|
5
|
-
#
|
6
|
-
- def self.load(csv_file, options={})
|
7
|
-
- get_table_from_csv(:foreach, csv_file, options)
|
8
|
-
end
|
9
|
-
|
10
|
-
#
|
11
|
-
--- 287,294 ----
|
12
|
-
# # pass in FasterCSV options, such as column separators
|
13
|
-
# table = Table.load('mydata.csv',:csv_opts => { :col_sep => "\t" })
|
14
|
-
#
|
15
|
-
+ def self.load(csv_file, options={},&block)
|
16
|
-
+ get_table_from_csv(:foreach, csv_file, options,&block)
|
17
|
-
end
|
18
|
-
|
19
|
-
#
|
20
|
-
***************
|
21
|
-
*** 297,307 ****
|
22
|
-
#
|
23
|
-
# table = Table.parse("a,b,c\n1,2,3\n4,5,6\n")
|
24
|
-
#
|
25
|
-
- def self.parse(string, options={})
|
26
|
-
- get_table_from_csv(:parse,string,options)
|
27
|
-
end
|
28
|
-
|
29
|
-
- def self.get_table_from_csv(msg,param,options={}) #:nodoc:
|
30
|
-
options = {:has_names => true,
|
31
|
-
:csv_options => {} }.merge(options)
|
32
|
-
require "fastercsv"
|
33
|
-
--- 297,307 ----
|
34
|
-
#
|
35
|
-
# table = Table.parse("a,b,c\n1,2,3\n4,5,6\n")
|
36
|
-
#
|
37
|
-
+ def self.parse(string, options={},&block)
|
38
|
-
+ get_table_from_csv(:parse,string,options,&block)
|
39
|
-
end
|
40
|
-
|
41
|
-
+ def self.get_table_from_csv(msg,param,options={},&block) #:nodoc:
|
42
|
-
options = {:has_names => true,
|
43
|
-
:csv_options => {} }.merge(options)
|
44
|
-
require "fastercsv"
|
45
|
-
***************
|
46
|
-
*** 312,321 ****
|
47
|
-
if first_line && options[:has_names]
|
48
|
-
loaded_data.column_names = row
|
49
|
-
first_line = false
|
50
|
-
- elsif !block_given?
|
51
|
-
loaded_data << row
|
52
|
-
else
|
53
|
-
- yield(loaded_data,row)
|
54
|
-
end
|
55
|
-
end ; loaded_data
|
56
|
-
end
|
57
|
-
--- 312,321 ----
|
58
|
-
if first_line && options[:has_names]
|
59
|
-
loaded_data.column_names = row
|
60
|
-
first_line = false
|
61
|
-
+ elsif !block
|
62
|
-
loaded_data << row
|
63
|
-
else
|
64
|
-
+ block[loaded_data,row]
|
65
|
-
end
|
66
|
-
end ; loaded_data
|
67
|
-
end
|
data/lib/ruport/data/table.rb~
DELETED
@@ -1,414 +0,0 @@
|
|
1
|
-
# The Ruport Data Collections.
|
2
|
-
# Authors: Gregory Brown / Dudley Flanders
|
3
|
-
#
|
4
|
-
# This is Free Software. For details, see LICENSE and COPYING
|
5
|
-
# Copyright 2006 by respective content owners, all rights reserved.
|
6
|
-
|
7
|
-
class Array
|
8
|
-
|
9
|
-
#
|
10
|
-
# Converts an array to a Ruport::Data::Table object, ready to
|
11
|
-
# use in your reports.
|
12
|
-
#
|
13
|
-
# Example:
|
14
|
-
# [[1,2],[3,4]].to_table(%w[a b])
|
15
|
-
#
|
16
|
-
def to_table(options={})
|
17
|
-
options = { :column_names => options } if options.kind_of? Array
|
18
|
-
Ruport::Data::Table.new({:data => self}.merge(options))
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
module Ruport::Data
|
23
|
-
|
24
|
-
#
|
25
|
-
# === Overview
|
26
|
-
#
|
27
|
-
# This class is one of the core classes for building and working with data
|
28
|
-
# in Ruport. The idea is to get your data into a standard form, regardless
|
29
|
-
# of its source (a database, manual arrays, ActiveRecord, CSVs, etc.).
|
30
|
-
#
|
31
|
-
# Table is intended to be used as the data store for structured, tabular
|
32
|
-
# data - Ruport::Data::Set is an alternate data store intended for less
|
33
|
-
# structured data.
|
34
|
-
#
|
35
|
-
# Once your data is in a Ruport::Data::Table object, it can be manipulated
|
36
|
-
# to suit your needs, then used to build a report.
|
37
|
-
#
|
38
|
-
class Table < Collection
|
39
|
-
include Groupable
|
40
|
-
|
41
|
-
#
|
42
|
-
# Creates a new table based on the supplied options.
|
43
|
-
# Valid options:
|
44
|
-
# <b><tt>:data</tt></b>:: An Array of Arrays representing the
|
45
|
-
# records in this Table
|
46
|
-
# <b><tt>:column_names</tt></b>:: An Array containing the column names
|
47
|
-
# for this Table.
|
48
|
-
# Example:
|
49
|
-
#
|
50
|
-
# table = Table.new :data => [[1,2,3], [3,4,5]],
|
51
|
-
# :column_names => %w[a b c]
|
52
|
-
#
|
53
|
-
def initialize(options={})
|
54
|
-
@column_names = options[:column_names] ? options[:column_names].dup : []
|
55
|
-
@data = []
|
56
|
-
if options[:data]
|
57
|
-
if options[:data].all? { |r| r.kind_of? Record }
|
58
|
-
record_tags = options[:data].map { |r| r.tags }
|
59
|
-
options[:data] = options[:data].map { |r| r.to_a }
|
60
|
-
end
|
61
|
-
options[:data].each { |e| self << e }
|
62
|
-
each { |r| r.tags = record_tags.shift } if record_tags
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
# This Table's column names.
|
67
|
-
attr_reader :column_names
|
68
|
-
def_delegator :@data, :[]
|
69
|
-
|
70
|
-
#
|
71
|
-
# Sets the column names for this table. <tt>new_column_names</tt> should
|
72
|
-
# be an array listing the names of the columns.
|
73
|
-
#
|
74
|
-
# Example:
|
75
|
-
#
|
76
|
-
# table = Table.new :data => [1,2,3], [3,4,5],
|
77
|
-
# :column_names => %w[a b c]
|
78
|
-
#
|
79
|
-
# table.column_names = %w[e f g]
|
80
|
-
#
|
81
|
-
def column_names=(new_column_names)
|
82
|
-
@column_names.replace(other.dup)
|
83
|
-
end
|
84
|
-
|
85
|
-
#
|
86
|
-
# Compares this Table to another Table and returns <tt>true</tt> if
|
87
|
-
# both the <tt>data</tt> and <tt>column_names</tt> are equal.
|
88
|
-
#
|
89
|
-
# Example:
|
90
|
-
#
|
91
|
-
# one = Table.new :data => [1,2], [3,4],
|
92
|
-
# :column_names => %w[a b]
|
93
|
-
#
|
94
|
-
# two = Table.new :data => [1,2], [3,4],
|
95
|
-
# :column_names => %w[a b]
|
96
|
-
#
|
97
|
-
# one.eql?(two) #=> true
|
98
|
-
#
|
99
|
-
def eql?(other)
|
100
|
-
data.eql?(other.data) && column_names.eql?(other.column_names)
|
101
|
-
end
|
102
|
-
|
103
|
-
alias_method :==, :eql?
|
104
|
-
|
105
|
-
#
|
106
|
-
# Uses Ruport's built-in text plugin to render this Table into a String
|
107
|
-
#
|
108
|
-
# Example:
|
109
|
-
#
|
110
|
-
# data = Table.new :data => [1,2], [3,4],
|
111
|
-
# :column_names => %w[a b]
|
112
|
-
# puts data.to_s
|
113
|
-
#
|
114
|
-
def to_s
|
115
|
-
as(:text)
|
116
|
-
end
|
117
|
-
|
118
|
-
#
|
119
|
-
# Used to add extra data to the Table. <tt>other</tt> can be an Array,
|
120
|
-
# Hash or Record.
|
121
|
-
#
|
122
|
-
# Example:
|
123
|
-
#
|
124
|
-
# data = Table.new :data => [1,2], [3,4],
|
125
|
-
# :column_names => %w[a b]
|
126
|
-
# data << [8,9]
|
127
|
-
# data << { :a => 4, :b => 5}
|
128
|
-
# data << Record.new [5,6], :attributes => %w[a b]
|
129
|
-
#
|
130
|
-
def <<(other)
|
131
|
-
case other
|
132
|
-
when Array
|
133
|
-
@data << Record.new(other, :attributes => @column_names)
|
134
|
-
when Hash
|
135
|
-
raise ArgumentError unless @column_names
|
136
|
-
arr = @column_names.map { |k| other[k] }
|
137
|
-
@data << Record.new(arr, :attributes => @column_names)
|
138
|
-
when Record
|
139
|
-
raise ArgumentError unless column_names.eql? other.attributes
|
140
|
-
@data << Record.new(other.data, :attributes => @column_names)
|
141
|
-
@data.last.tags = other.tags.dup
|
142
|
-
else
|
143
|
-
raise ArgumentError
|
144
|
-
end
|
145
|
-
self
|
146
|
-
end
|
147
|
-
|
148
|
-
#
|
149
|
-
# Used to combine two Tables. Throws an ArgumentError if the Tables don't
|
150
|
-
# have identical columns.
|
151
|
-
#
|
152
|
-
# Example:
|
153
|
-
#
|
154
|
-
# inky = Table.new :data => [[1,2], [3,4]],
|
155
|
-
# :column_names => %w[a b]
|
156
|
-
#
|
157
|
-
# blinky = Table.new :data => [[5,6]],
|
158
|
-
# :column_names => %w[a b]
|
159
|
-
#
|
160
|
-
# sue = inky + blinky
|
161
|
-
# sue.data #=> [[1,2],[3,4],[5,6]]
|
162
|
-
#
|
163
|
-
def +(other)
|
164
|
-
raise ArgumentError unless other.column_names == @column_names
|
165
|
-
Table.new(:column_names => @column_names, :data => @data + other.data)
|
166
|
-
end
|
167
|
-
|
168
|
-
#
|
169
|
-
# Reorders the columns that exist in the Table. Modifies this Table
|
170
|
-
# in-place.
|
171
|
-
#
|
172
|
-
# Example:
|
173
|
-
#
|
174
|
-
# data = Table.new :data => [1,2], [3,4],
|
175
|
-
# :column_names => %w[a b]
|
176
|
-
#
|
177
|
-
# data.reorder!([1,0])
|
178
|
-
#
|
179
|
-
def reorder!(*indices)
|
180
|
-
indices = indices[0] if indices[0].kind_of? Array
|
181
|
-
if @column_names && !@column_names.empty?
|
182
|
-
x = if indices.all? { |i| i.kind_of? Integer }
|
183
|
-
indices.map { |i| @column_names[i] }
|
184
|
-
else
|
185
|
-
indices
|
186
|
-
end
|
187
|
-
@column_names = x
|
188
|
-
end
|
189
|
-
@data.each { |r|
|
190
|
-
r.reorder_data!(*indices)
|
191
|
-
r.attributes = @column_names
|
192
|
-
}; self
|
193
|
-
end
|
194
|
-
|
195
|
-
#
|
196
|
-
# Returns a copy of the Table with its columns in the requested order.
|
197
|
-
#
|
198
|
-
# Example:
|
199
|
-
#
|
200
|
-
# one = Table.new :data => [1,2], [3,4],
|
201
|
-
# :column_names => %w[a b]
|
202
|
-
#
|
203
|
-
# two = one.reorder!([1,0])
|
204
|
-
#
|
205
|
-
def reorder(*indices)
|
206
|
-
dup.reorder!(*indices)
|
207
|
-
end
|
208
|
-
|
209
|
-
#
|
210
|
-
# Adds an extra column to the Table. Required options:
|
211
|
-
#
|
212
|
-
# <b><tt>:name</tt></b>:: The new column's name
|
213
|
-
# <b><tt>:fill</tt></b>:: The default value to use for the column in
|
214
|
-
# existing rows.
|
215
|
-
#
|
216
|
-
# Example:
|
217
|
-
#
|
218
|
-
# data = Table.new :data => [1,2], [3,4],
|
219
|
-
# :column_names => %w[a b]
|
220
|
-
#
|
221
|
-
# data.append_column :name => 'new_column', :fill => 1
|
222
|
-
#
|
223
|
-
def append_column(options={})
|
224
|
-
self.column_names += [options[:name]] if options[:name]
|
225
|
-
if block_given?
|
226
|
-
each { |r| r.data << yield(r) || options[:fill] }
|
227
|
-
else
|
228
|
-
each { |r| r.data << options[:fill] }
|
229
|
-
end; self
|
230
|
-
end
|
231
|
-
|
232
|
-
#
|
233
|
-
# Removes a column from the Table. Any values in the specified column are
|
234
|
-
# lost.
|
235
|
-
#
|
236
|
-
# Example:
|
237
|
-
#
|
238
|
-
# data = Table.new :data => [[1,2], [3,4]], :column_names => %w[a b]
|
239
|
-
# data.append_column :name => 'new_column', :fill => 1
|
240
|
-
# data.remove_column :name => 'new_column'
|
241
|
-
# data == Table.new :data => [[1,2], [3,4]],
|
242
|
-
# :column_names => %w[a b] #=> true
|
243
|
-
# data = [[1,2],[3,4]].to_table
|
244
|
-
# data.remove_column(1)
|
245
|
-
# data.eql? [[1],[3]].to_table %w[a] #=> true
|
246
|
-
#
|
247
|
-
def remove_column(options={})
|
248
|
-
if options.kind_of? Integer
|
249
|
-
return reorder!((0...data[0].length).to_a - [options])
|
250
|
-
elsif options.kind_of? Hash
|
251
|
-
name = options[:name]
|
252
|
-
else
|
253
|
-
name = options
|
254
|
-
end
|
255
|
-
|
256
|
-
raise ArgumentError unless column_names.include? name
|
257
|
-
reorder! column_names - [name]
|
258
|
-
end
|
259
|
-
|
260
|
-
#
|
261
|
-
# Create a shallow copy of the Table: the same data elements are referenced
|
262
|
-
# by both the old and new Table.
|
263
|
-
#
|
264
|
-
# Example:
|
265
|
-
#
|
266
|
-
# one = Table.new :data => [1,2], [3,4],
|
267
|
-
# :column_names => %w[a b]
|
268
|
-
# two = one.dup
|
269
|
-
#
|
270
|
-
def dup
|
271
|
-
a = self.class.new(:data => @data, :column_names => @column_names)
|
272
|
-
a.tags = tags.dup
|
273
|
-
return a
|
274
|
-
end
|
275
|
-
|
276
|
-
#
|
277
|
-
# Loads a CSV file directly into a Table using the FasterCSV library.
|
278
|
-
#
|
279
|
-
# Example:
|
280
|
-
#
|
281
|
-
# table = Table.load('mydata.csv')
|
282
|
-
#
|
283
|
-
def self.load(csv_file, options={})
|
284
|
-
get_table_from_csv(:foreach, csv_file, options)
|
285
|
-
end
|
286
|
-
|
287
|
-
def self.parse(string, options={}) #:nodoc:
|
288
|
-
get_table_from_csv(:parse,string,options)
|
289
|
-
end
|
290
|
-
|
291
|
-
def self.get_table_from_csv(msg,param,options={}) #:nodoc:
|
292
|
-
options = {:has_names => true,
|
293
|
-
:csv_options => {} }.merge(options)
|
294
|
-
require "fastercsv"
|
295
|
-
loaded_data = self.new
|
296
|
-
|
297
|
-
first_line = true
|
298
|
-
FasterCSV.send(msg,param,options[:csv_options]) do |row|
|
299
|
-
if first_line && options[:has_names]
|
300
|
-
loaded_data.column_names = row
|
301
|
-
first_line = false
|
302
|
-
elsif !block_given?
|
303
|
-
loaded_data << row
|
304
|
-
else
|
305
|
-
yield(loaded_data,row)
|
306
|
-
end
|
307
|
-
end ; loaded_data
|
308
|
-
end
|
309
|
-
|
310
|
-
#
|
311
|
-
# Allows you to split Tables into multiple Tables for grouping.
|
312
|
-
#
|
313
|
-
# Example:
|
314
|
-
#
|
315
|
-
# a = Table.new(:column_name => %w[name a b c])
|
316
|
-
# a << ["greg",1,2,3]
|
317
|
-
# a << ["joe", 2,3,4]
|
318
|
-
# a << ["greg",7,8,9]
|
319
|
-
# a << ["joe", 1,2,3]
|
320
|
-
#
|
321
|
-
# b = a.split :group => "name"
|
322
|
-
#
|
323
|
-
# b.greg.eql? [[1,2,3],[7,8,9]].to_table(%w[a b c]) #=> true
|
324
|
-
# b["joe"].eql? [[2,3,4],[1,2,3]].to_table(%w[a b c]) #=> true
|
325
|
-
#
|
326
|
-
# You can also pass an Array to <tt>:group</tt>, and the resulting
|
327
|
-
# attributes in the group will be joined by an underscore.
|
328
|
-
#
|
329
|
-
# Example:
|
330
|
-
#
|
331
|
-
# a = Table.new(:column_names => %w[first_name last_name x]
|
332
|
-
# a << %w[greg brown foo]
|
333
|
-
# a << %w[greg gibson bar]
|
334
|
-
# a << %w[greg brown baz]
|
335
|
-
#
|
336
|
-
# b = a.split :group => %w[first_name last_name]
|
337
|
-
# a.greg_brown.length #=> 2
|
338
|
-
# a["greg_gibson"].length #=> 1
|
339
|
-
# a.greg_brown[0].x #=> "foo"
|
340
|
-
#
|
341
|
-
def split(options={})
|
342
|
-
if options[:group].kind_of? Array
|
343
|
-
group = map { |r| options[:group].map { |e| r[e] } }.uniq
|
344
|
-
data = group.inject([]) { |s,g|
|
345
|
-
s + [select { |r| options[:group].map { |e| r[e] }.eql?(g) }]
|
346
|
-
}
|
347
|
-
c = column_names - options[:group]
|
348
|
-
else
|
349
|
-
group = map { |r| r[options[:group]] }.uniq
|
350
|
-
data = group.inject([]) { |s,g|
|
351
|
-
s + [select { |r| r[options[:group]].eql?(g) }]
|
352
|
-
}
|
353
|
-
c = column_names - [options[:group]]
|
354
|
-
|
355
|
-
end
|
356
|
-
data.map! { |g|
|
357
|
-
Ruport::Data::Table.new(
|
358
|
-
:data => g.map { |x| x.reorder(*c) },
|
359
|
-
:column_names => c
|
360
|
-
)
|
361
|
-
}
|
362
|
-
rec = if options[:group].kind_of? Array
|
363
|
-
Ruport::Data::Record.new(data,
|
364
|
-
:attributes => group.map { |e| e.join("_") } )
|
365
|
-
else
|
366
|
-
Ruport::Data::Record.new data, :attributes => group
|
367
|
-
end
|
368
|
-
class << rec
|
369
|
-
def each_group; attributes.each { |a| yield(a) }; end
|
370
|
-
end; rec
|
371
|
-
end
|
372
|
-
|
373
|
-
#
|
374
|
-
# Calculates sums. If a column name or index is given, it will try to
|
375
|
-
# convert each element of that column to an integer or float
|
376
|
-
# and add them together.
|
377
|
-
#
|
378
|
-
# If a block is given, it yields each Record so that you can do your own
|
379
|
-
# calculation.
|
380
|
-
#
|
381
|
-
# Example:
|
382
|
-
#
|
383
|
-
# table = [[1,2],[3,4],[5,6]].to_table(%w[col1 col2])
|
384
|
-
# table.sigma("col1") #=> 9
|
385
|
-
# table.sigma(0) #=> 9
|
386
|
-
# table.sigma { |r| r.col1 + r.col2 } #=> 21
|
387
|
-
# table.sigma { |r| r.col2 + 1 } #=> 15
|
388
|
-
#
|
389
|
-
def sigma(column=nil)
|
390
|
-
inject(0) { |s,r|
|
391
|
-
if column
|
392
|
-
s + if r[column].kind_of? Numeric
|
393
|
-
r[column]
|
394
|
-
else
|
395
|
-
r[column] =~ /\./ ? r[column].to_f : r[column].to_i
|
396
|
-
end
|
397
|
-
else
|
398
|
-
s + yield(r)
|
399
|
-
end
|
400
|
-
}
|
401
|
-
end
|
402
|
-
|
403
|
-
alias_method :sum, :sigma
|
404
|
-
|
405
|
-
end
|
406
|
-
|
407
|
-
end
|
408
|
-
|
409
|
-
module Ruport::Data::TableHelper #:nodoc:
|
410
|
-
def table(names=[])
|
411
|
-
t = [].to_table(names)
|
412
|
-
yield(t) if block_given?; t
|
413
|
-
end
|
414
|
-
end
|