ruport 0.4.19 → 0.4.21

Sign up to get free protection for your applications and to get access to all the features.
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: ruport
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.4.19
7
- date: 2006-07-30 00:00:00 -04:00
6
+ version: 0.4.21
7
+ date: 2006-08-07 00:00:00 -04:00
8
8
  summary: A generalized Ruby report generation and templating engine.
9
9
  require_paths:
10
10
  - lib
@@ -44,8 +44,6 @@ files:
44
44
  - lib/ruport/format
45
45
  - lib/ruport/rails
46
46
  - lib/ruport/data
47
- - lib/ruport/data_row.rb
48
- - lib/ruport/data_set.rb
49
47
  - lib/ruport/system_extensions.rb
50
48
  - lib/ruport/config.rb
51
49
  - lib/ruport/query.rb
@@ -65,6 +63,7 @@ files:
65
63
  - lib/ruport/data/taggable.rb
66
64
  - lib/ruport/data/record.rb
67
65
  - lib/ruport/data/collection.rb
66
+ - lib/ruport/data/set.rb
68
67
  - test/samples
69
68
  - test/tc_element.rb
70
69
  - test/tc_format.rb
@@ -87,6 +86,7 @@ files:
87
86
  - test/tc_taggable.rb
88
87
  - test/tc_record.rb
89
88
  - test/tc_table.rb
89
+ - test/tc_set.rb
90
90
  - test/samples/ruport_test.sql
91
91
  - test/samples/test.yaml
92
92
  - test/samples/data.csv
@@ -1,187 +0,0 @@
1
- # --
2
- # data_row.rb : Ruby Reports row abstraction
3
- #
4
- # Author: Gregory T. Brown (gregory.t.brown at gmail dot com)
5
- #
6
- # Copyright (c) 2006, All Rights Reserved.
7
- #
8
- # This is free software. You may modify and redistribute this freely under
9
- # your choice of the GNU General Public License or the Ruby License.
10
- #
11
- # See LICENSE and COPYING for details
12
- # ++
13
- module Ruport
14
-
15
- # DataRows are Enumerable lists which can be accessed by field name or
16
- # ordinal position.
17
- #
18
- # They feature a tagging system, allowing them to be easily
19
- # compared or recalled.
20
- #
21
- # DataRows form the elements of DataSets
22
- #
23
- class DataRow
24
-
25
- include Enumerable
26
-
27
- # Takes field names as well as some optional parameters and
28
- # constructs a DataRow.
29
- #
30
- # Options:
31
- # <tt>:data</tt>:: can be specified in Hash, Array, or DataRow form
32
- # <tt>:default</tt>:: The default value for empty fields
33
- # <tt>:tags</tt>:: an initial set of tags for the row
34
- #
35
- #
36
- # Examples:
37
- # >> Ruport::DataRow.new [:a,:b,:c,:d,:e], :data => [1,2,3,4,5]
38
- # :tags => %w[cat dog]
39
- # => #<Ruport::DataRow:0xb77e4b04 @fields=[:a, :b, :c, :d, :e],
40
- # @data=[1, 2, 3, 4, 5], @tags=["cat", "dog"]>
41
- #
42
- # >> Ruport::DataRow.new([:a,:b,:c,:d,:e],
43
- # :data => { :a => 'moo', :c => 'caw'} ,
44
- # :tags => %w[cat dog])
45
- # => #<Ruport::DataRow:0xb77c298c @fields=[:a, :b, :c, :d, :e],
46
- # @data=["moo", nil, "caw", nil, nil], @tags=["cat", "dog"]>
47
- #
48
- # >> Ruport::DataRow.new [:a,:b,:c,:d,:e], :data => [1,2,3],
49
- # :tags => %w[cat dog], :default => 0
50
- # => #<Ruport::DataRow:0xb77bb4d4 @fields=[:a, :b, :c, :d, :e],
51
- # @data=[1, 2, 3, 0, 0], @tags=["cat", "dog"]>
52
- #
53
- def initialize(fields=nil, options={})
54
-
55
- #checks to ensure data is convertable
56
- verify options[:data]
57
- data = options[:data].dup
58
-
59
- @fields = fields ? fields.dup : ( 0...data.length ).to_a
60
- @tags = (options[:tags] || {}).dup
61
- @data = []
62
-
63
- nr_action = case(data)
64
- when Array
65
- lambda {|key, index| @data[index] = data.shift || options[:default]}
66
- when DataRow
67
- lambda {|key, index| @data = data.to_a}
68
- else
69
- lambda {|key, index| @data[index] = data[key] || options[:default]}
70
- end
71
- @fields.each_with_index {|key, index| nr_action.call(key,index)}
72
- end
73
-
74
-
75
- attr_accessor :fields, :tags
76
- alias_method :column_names, :fields
77
- alias_method :attributes, :fields
78
- # Returns a new DataRow
79
- def +(other)
80
- DataRow.new @fields + other.fields, :data => (@data + other.to_a)
81
- end
82
-
83
- # Lets you access individual fields
84
- #
85
- # i.e. row["phone"] or row[4]
86
- def [](key)
87
- case(key)
88
- when Fixnum
89
- @data[key]
90
- when Symbol
91
- @data[@fields.index(key.to_s)] rescue nil
92
- else
93
- @data[@fields.index(key)] rescue nil
94
- end
95
- end
96
-
97
- # Lets you set field values
98
- #
99
- # i.e. row["phone"] = '2038291203', row[7] = "allen"
100
- def []=(key,value)
101
- case(key)
102
- when Fixnum
103
- @data[key] = value
104
- when Symbol
105
- @data[@fields.index(key.to_s)] = value
106
- else
107
- @data[@fields.index(key)] = value
108
- end
109
- end
110
-
111
- # Converts the DataRow to a plain old Array
112
- def to_a
113
- @data.clone
114
- end
115
-
116
- def to_h
117
- a = Hash.new
118
- @fields.each { |f| a[f] = self[f] }; a
119
- end
120
-
121
- # Converts the DataRow to a string representation
122
- # for outputting to screen.
123
- def to_s
124
- "[" + @data.join(",") + "]"
125
- end
126
-
127
- # Checks to see row includes the tag given.
128
- #
129
- # Example:
130
- #
131
- # >> row.has_tag? :running_balance
132
- # => true
133
- #
134
- def has_tag?(tag)
135
- @tags.include?(tag)
136
- end
137
-
138
- # Iterates through DataRow elements. Accepts a block.
139
- def each(&action)
140
- @data.each(&action)
141
- end
142
-
143
- # Allows you to add a tag to a row.
144
- #
145
- # Examples:
146
- #
147
- # row.tag_as(:jay_cross) if row["product"].eql?("im_courier")
148
- # row.tag_as(:running_balance) if row.fields.include?("RB")
149
- #
150
- def tag_as(something)
151
- @tags[something] = true
152
- end
153
-
154
- # Compares two DataRow objects. If values and fields are the same
155
- # (and in the correct order) returns true. Otherwise returns false.
156
- def ==(other)
157
- self.to_a.eql?(other.to_a) && @fields.eql?(other.fields)
158
- end
159
-
160
- # Synonym for DataRow#==
161
- def eql?(other)
162
- self == other
163
- end
164
-
165
- def clone
166
- self.class.new @fields, :data => @data, :tags => @tags
167
- end
168
-
169
- alias_method :dup, :clone
170
-
171
- private
172
-
173
- def verify(data)
174
- if data.kind_of? String or
175
- data.kind_of? Integer or not data.respond_to?(:[])
176
- Ruport.complain "Cannot convert data to DataRow",
177
- :status => :fatal, :exception => ArgumentError, :level => :log_only
178
- end
179
- end
180
-
181
- def method_missing(id,*args)
182
- f = id.to_s.gsub(/=$/,'')
183
- return super unless fields.include?(f)
184
- args.empty? ? self[f] : self[f] = args[0]
185
- end
186
- end
187
- end
@@ -1,308 +0,0 @@
1
- # data_set.rb : Ruby Reports core datastructure.
2
- #
3
- # Author: Gregory T. Brown (gregory.t.brown at gmail dot com)
4
- # Copyright (c) 2006, All Rights Reserved.
5
- #
6
- # Pseudo keyword argument support, improved <<, and set operations submitted by
7
- # Dudley Flanders
8
- #
9
- # This is free software. You may modify and redistribute this freely under
10
- # your choice of the GNU General Public License or the Ruby License.
11
- #
12
- # See LICENSE and COPYING for details
13
- module Ruport
14
-
15
- # The DataSet is the core datastructure for Ruport. It provides methods that
16
- # allow you to compare and combine query results, data loaded in from CSVs,
17
- # and user-defined sets of data.
18
- #
19
- # It is tightly integrated with Ruport's formatting and query systems, so if
20
- # you'd like to take advantage of these models, you will probably find
21
- # DataSet useful.
22
- class DataSet
23
- #FIXME: Add logging to this class
24
- include Enumerable
25
- extend Forwardable
26
- # DataSets must be given a set of fields to be defined.
27
- #
28
- # These field names will define the columns for the DataSet and how you
29
- # access them.
30
- #
31
- # data = Ruport::DataSet.new %w[ id name phone ]
32
- #
33
- # Options:
34
- # * <tt>:data</tt> - An Enumerable with the content for this DataSet
35
- # * <tt>:default</tt> - The default value for empty cells
36
- #
37
- #
38
- # DataSet supports the following Array methods through delegators
39
- # length, empty?, delete_at, first, last, pop
40
- #
41
- def initialize(fields=nil, options={})
42
- @fields = fields.dup if fields
43
- @default = options[:default] || default
44
- @data = []
45
- options[:data].each { |r| self << r } if options[:data]
46
- end
47
-
48
- #an array which contains column names
49
- attr_accessor :fields
50
- alias_method :column_names, :fields
51
- alias_method :column_names=, :fields=
52
- #the default value to fill empty cells with
53
- attr_accessor :default
54
-
55
- #data holds the elements of the Row
56
- attr_reader :data
57
-
58
- def_delegators :@data, :length, :[], :empty?,
59
- :delete_at, :first, :last, :pop,
60
- :each, :reverse_each, :at, :clear
61
-
62
- def delete_if(&block)
63
- @data.delete_if &block; self
64
- end
65
-
66
- #provides a deep copy of the DataSet.
67
- def clone
68
- self.class.new(@fields, :data => @data)
69
- end
70
-
71
- alias_method :dup, :clone
72
-
73
- # Creates a new DataSet with the same shape as this one, but empty.
74
- def empty_clone
75
- self.class.new(@fields)
76
- end
77
-
78
- #allows setting of rows
79
- def []=(index,data)
80
- @data[index] = DataRow.new @fields, :data => data
81
- end
82
-
83
- def [](index)
84
- case(index)
85
- when Range
86
- self.class.new @fields, :data => @data[index]
87
- else
88
- @data[index]
89
- end
90
- end
91
-
92
- # Appends a row to the DataSet
93
- # Can be added as an array, an array of DataRows, a DataRow, or a keyed
94
- # hash-like object.
95
- #
96
- # Columns left undefined will be filled with DataSet#default values.
97
- #
98
- # data << [ 1, 2, 3 ]
99
- # data << { :some_field_name => 3, :other => 2, :another => 1 }
100
- #
101
- # FIXME: Appending a datarow is wonky.
102
- def << ( stuff, filler=@default )
103
- if stuff.kind_of?(DataRow)
104
- @data << stuff.clone
105
- elsif stuff.kind_of?(Array) && stuff[0].kind_of?(DataRow)
106
- @data.concat(stuff)
107
- else
108
- @data << DataRow.new(@fields, :data => stuff.clone,:default => filler)
109
- end
110
- return self
111
- end
112
- alias_method :push, :<<
113
-
114
- # checks if one dataset equals another
115
- # FIXME: expand this doc.
116
- def eql?(data2)
117
- return false unless ( @data.length == data2.data.length and
118
- @fields.eql?(data2.fields) )
119
- @data.each_with_index do |row, r_index|
120
- row.each_with_index do |field, f_index|
121
- return false unless field.eql?(data2[r_index][f_index])
122
- end
123
- end
124
-
125
- return true
126
- end
127
-
128
- # checks if one dataset equals another
129
- def ==(data2)
130
- eql?(data2)
131
- end
132
-
133
- # Set union - returns a DataSet with the elements that are contained in
134
- # in either of the two given sets, with no duplicates.
135
- def |(other)
136
- clone << other.reject { |x| self.include? x }
137
- end
138
- alias_method :union, :|
139
-
140
- # Set intersection
141
- def &(other)
142
- empty_clone << select { |x| other.include? x }
143
- end
144
- alias_method :intersection, :&
145
-
146
- # Set difference
147
- def -(other)
148
- empty_clone << reject { |x| other.include? x }
149
- end
150
- alias_method :difference, :-
151
-
152
- # Checks if one DataSet has the same set of fields as another
153
- def shaped_like?(other)
154
- return true if @fields.eql?(other.fields)
155
- end
156
-
157
- # Concatenates one DataSet onto another if they have the same shape
158
- def concat(other)
159
- if other.shaped_like?(self)
160
- @data.concat(other.data)
161
- return self
162
- end
163
- end
164
- alias_method :+, :concat
165
-
166
- # Allows loading of CSV files or YAML dumps. Returns a DataSet
167
- #
168
- # FasterCSV will be used if it is installed.
169
- #
170
- # my_data = Ruport::DataSet.load("foo.csv")
171
- # my_data = Ruport::DataSet.load("foo.yaml")
172
- # my_data = Ruport::DataSet.load("foo.yml")
173
- def self.load ( source, options={}, &block)
174
- options = {:has_names => true}.merge(options)
175
- case source
176
- when /\.(yaml|yml)/
177
- return YAML.load(File.open(source))
178
- when /\.csv/
179
- require "fastercsv"
180
- input = FasterCSV.read(source) if source =~ /\.csv/
181
- loaded_data = self.new
182
-
183
- action = if block
184
- lambda { |r| block[loaded_data,r] }
185
- else
186
- lambda { |r| loaded_data << r }
187
- end
188
-
189
- if options[:has_names]
190
- loaded_data.fields = input[0] ; input = input[1..-1]
191
- end
192
-
193
- loaded_data.default = options[:default]
194
- input.each { |row| action[row] }
195
- return loaded_data
196
- else
197
- raise "Invalid file type"
198
- end
199
- end
200
-
201
-
202
- # Returns a new DataSet composed of the fields specified.
203
- def select_columns(*fields)
204
- fields = get_field_names(fields)
205
- rows = fields.inject([]) { |s,e| s + [map { |row| row[e] }] }.transpose
206
- my_data = DataSet.new(fields, :data => rows)
207
- end
208
-
209
- # Prunes the dataset to contain only the fields specified. (DESTRUCTIVE)
210
- def select_columns!(*fields)
211
- a = select_columns(*fields)
212
- @fields = a.fields; @data = a.data
213
- end
214
-
215
- #Creates a new dataset with additional columns appending to it
216
- def add_columns(*fields)
217
- select_columns *(@fields + fields)
218
- end
219
-
220
- def add_columns!(*fields)
221
- select_columns! *(@fields + fields)
222
- end
223
-
224
- # Returns a new DataSet with the specified fields removed
225
- def remove_columns(*fields)
226
- fields = get_field_names(fields)
227
- select_columns(*(@fields-fields))
228
- end
229
-
230
- # removes the specified fields from this DataSet (DESTRUCTIVE!)
231
- def remove_columns!(*fields)
232
- d = remove_columns(*fields)
233
- @data = d.data
234
- @fields = d.fields
235
- end
236
-
237
- def rename_columns(cols)
238
- if cols.kind_of?(Array)
239
- cols.each_with_index { |c,i| fields[i] = c }
240
- else
241
- cols.map { |k,v| fields[fields.index(k)] = v }
242
- end
243
- @data.each { |r| r.fields = fields }
244
- end
245
- # uses Format::Builder to render DataSets in various ready to output
246
- # formats.
247
- #
248
- # To add new formats to this function, simply re-open Format::Builder
249
- # and add methods like <tt>render_my_format_name</tt>.
250
- #
251
- # This will enable <tt>data.as(:my_format_name)</tt>
252
- def as(format)
253
- t = Format.table_object(:data => self, :plugin => format)
254
- yield(t) if block_given?
255
- t.render
256
- end
257
-
258
- # Will iterate row by row yielding each row
259
- # The result of the block will be added to a running total
260
- #
261
- # Only works with blocks resulting in numeric values.
262
- #
263
- # Also allows summing up a specific column, e.g.
264
- #
265
- # my_set.sigma("col1")
266
- def sigma(f = nil)
267
- if f
268
- inject(0) { |s,r| s + (r[f] || 0) }
269
- else
270
- inject(0) { |s,r| s + (yield(r) || 0) }
271
- end
272
- end
273
-
274
- alias_method :sum, :sigma
275
-
276
- # Converts a DataSet to an array of arrays
277
- def to_a
278
- @data.map {|x| x.to_a }
279
- end
280
-
281
- # Converts a DataSet to CSV
282
- def to_csv; as(:csv) end
283
-
284
- # Converts a Dataset to html
285
- def to_html; as(:html) end
286
-
287
- # Readable string representation of the DataSet
288
- def to_s; as(:text) end
289
-
290
- private
291
-
292
- def get_field_names(f)
293
- f.all? { |e| e.kind_of? Integer } &&
294
- f.inject([]) { |s,e| s + [@fields[e]] } || f
295
- end
296
-
297
-
298
- end
299
- end
300
-
301
- class Array
302
-
303
- # Will convert Arrays of Enumerable objects to DataSets.
304
- # May have dragons.
305
- def to_ds(fields,options={})
306
- Ruport::DataSet.new fields, :data => to_a, :default => options[:default]
307
- end
308
- end