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.
@@ -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