ruport 0.4.19 → 0.4.21

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