ruport 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +0,0 @@
1
- ***************
2
- *** 1 ****
3
- - %w[taggable record collection table set groupable ].each { |l| require "ruport/data/#{l}" }
4
- --- 1 ----
5
- + %w[groupable taggable record collection table set].each { |l| require "ruport/data/#{l}" }
data/lib/ruport/data.rb~ DELETED
@@ -1 +0,0 @@
1
- %w[taggable record collection table set groupable].each { |l| require "ruport/data/#{l}" }
@@ -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
@@ -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