ruport 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/AUTHORS +7 -3
  2. data/Rakefile +8 -9
  3. data/TODO +16 -0
  4. data/examples/RWEmerson.jpg +0 -0
  5. data/examples/centered_pdf_text_box.rb +66 -0
  6. data/examples/invoice.rb +35 -25
  7. data/examples/invoice_report.rb +1 -1
  8. data/examples/line_plotter.rb +1 -1
  9. data/examples/pdf_table_with_title.rb +42 -0
  10. data/lib/ruport.rb +5 -7
  11. data/lib/ruport.rb.rej +41 -0
  12. data/lib/ruport.rb~ +85 -0
  13. data/lib/ruport/attempt.rb +59 -59
  14. data/lib/ruport/config.rb +15 -4
  15. data/lib/ruport/data.rb +0 -2
  16. data/lib/ruport/data/groupable.rb +25 -16
  17. data/lib/ruport/data/record.rb +128 -102
  18. data/lib/ruport/data/table.rb +352 -199
  19. data/lib/ruport/data/taggable.rb +18 -7
  20. data/lib/ruport/format/html.rb +3 -1
  21. data/lib/ruport/format/latex.rb +1 -1
  22. data/lib/ruport/format/latex.rb.rej +26 -0
  23. data/lib/ruport/format/latex.rb~ +47 -0
  24. data/lib/ruport/format/pdf.rb +111 -28
  25. data/lib/ruport/format/pdf.rb.rej +168 -0
  26. data/lib/ruport/format/pdf.rb~ +189 -0
  27. data/lib/ruport/format/plugin.rb +0 -5
  28. data/lib/ruport/format/svg.rb +4 -4
  29. data/lib/ruport/format/xml.rb +3 -3
  30. data/lib/ruport/generator.rb +66 -27
  31. data/lib/ruport/mailer.rb +4 -1
  32. data/lib/ruport/query.rb +13 -1
  33. data/lib/ruport/renderer.rb +89 -17
  34. data/lib/ruport/renderer/graph.rb +5 -5
  35. data/lib/ruport/renderer/table.rb +8 -9
  36. data/lib/ruport/report.rb +2 -6
  37. data/test/test_config.rb +88 -76
  38. data/test/{test_text_table.rb → test_format_text.rb} +4 -2
  39. data/test/test_groupable.rb +15 -13
  40. data/test/test_query.rb +6 -3
  41. data/test/test_record.rb +57 -33
  42. data/test/test_renderer.rb +77 -0
  43. data/test/test_report.rb +188 -181
  44. data/test/test_ruport.rb +5 -6
  45. data/test/test_table.rb +290 -190
  46. data/test/test_table_renderer.rb +56 -8
  47. data/test/test_taggable.rb +7 -8
  48. data/test/unit.log +259 -7
  49. metadata +22 -19
  50. data/lib/ruport/data/collection.rb +0 -65
  51. data/lib/ruport/data/set.rb +0 -148
  52. data/test/test_collection.rb +0 -30
  53. data/test/test_set.rb +0 -118
@@ -1,63 +1,63 @@
1
1
  require 'timeout'
2
2
 
3
3
  class Attempt # :nodoc:
4
- VERSION = '0.1.0'
5
-
6
- # Number of attempts to make before failing. The default is 3.
7
- attr_accessor :tries
8
-
9
- # Number of seconds to wait between attempts. The default is 60.
10
- attr_accessor :interval
11
-
12
- # a level which ruport understands.
13
- attr_accessor :log_level
14
-
15
- # If set, this increments the interval with each failed attempt by that
16
- # number of seconds.
17
- attr_accessor :increment
18
-
19
- # If set, the code block is further wrapped in a timeout block.
20
- attr_accessor :timeout
21
-
22
- # Determines which exception level to check when looking for errors to
23
- # retry. The default is 'Exception' (i.e. all errors).
24
- attr_accessor :level
25
-
26
- # :call-seq:
27
- # Attempt.new{ |a| ... }
28
- #
29
- # Creates and returns a new +Attempt+ object. Use a block to set the
30
- # accessors.
31
- #
32
- def initialize
33
- @tries = 3 # Reasonable default
34
- @interval = 60 # Reasonable default
35
- @increment = nil # Should be an int, if provided
36
- @timeout = nil # Wrap the code in a timeout block if provided
37
- @level = Exception # Level of exception to be caught
38
-
39
- yield self if block_given?
40
- end
41
-
42
- def attempt
43
- count = 1
44
- begin
45
- if @timeout
46
- Timeout.timeout(@timeout){ yield }
47
- else
48
- yield
49
- end
50
- rescue @level => error
51
- @tries -= 1
52
- if @tries > 0
53
- msg = "Error on attempt # #{count}: #{error}; retrying"
54
- count += 1
55
- Ruport.log(msg, :level => log_level)
56
- @interval += @increment if @increment
57
- sleep @interval
58
- retry
59
- end
60
- raise
61
- end
62
- end
4
+ VERSION = '0.1.0'
5
+
6
+ # Number of attempts to make before failing. The default is 3.
7
+ attr_accessor :tries
8
+
9
+ # Number of seconds to wait between attempts. The default is 60.
10
+ attr_accessor :interval
11
+
12
+ # a level which ruport understands.
13
+ attr_accessor :log_level
14
+
15
+ # If set, this increments the interval with each failed attempt by that
16
+ # number of seconds.
17
+ attr_accessor :increment
18
+
19
+ # If set, the code block is further wrapped in a timeout block.
20
+ attr_accessor :timeout
21
+
22
+ # Determines which exception level to check when looking for errors to
23
+ # retry. The default is 'Exception' (i.e. all errors).
24
+ attr_accessor :level
25
+
26
+ # :call-seq:
27
+ # Attempt.new{ |a| ... }
28
+ #
29
+ # Creates and returns a new +Attempt+ object. Use a block to set the
30
+ # accessors.
31
+ #
32
+ def initialize
33
+ @tries = 3 # Reasonable default
34
+ @interval = 60 # Reasonable default
35
+ @increment = nil # Should be an int, if provided
36
+ @timeout = nil # Wrap the code in a timeout block if provided
37
+ @level = Exception # Level of exception to be caught
38
+
39
+ yield self if block_given?
40
+ end
41
+
42
+ def attempt
43
+ count = 1
44
+ begin
45
+ if @timeout
46
+ Timeout.timeout(@timeout){ yield }
47
+ else
48
+ yield
49
+ end
50
+ rescue @level => error
51
+ @tries -= 1
52
+ if @tries > 0
53
+ msg = "Error on attempt # #{count}: #{error}; retrying"
54
+ count += 1
55
+ Ruport.log(msg, :level => log_level)
56
+ @interval += @increment if @increment
57
+ sleep @interval
58
+ retry
59
+ end
60
+ raise
61
+ end
62
+ end
63
63
  end
data/lib/ruport/config.rb CHANGED
@@ -165,10 +165,10 @@ module Ruport
165
165
  # Verifies that you have provided a DSN for your source.
166
166
  def check_source(settings,label) # :nodoc:
167
167
  unless settings.dsn
168
- Ruport.complain(
168
+ Ruport.log(
169
169
  "Missing DSN for source #{label}!",
170
170
  :status => :fatal, :level => :log_only,
171
- :exception => ArgumentError
171
+ :raises => ArgumentError
172
172
  )
173
173
  end
174
174
  end
@@ -176,10 +176,10 @@ module Ruport
176
176
  # Verifies that you have provided a host for your mailer.
177
177
  def check_mailer(settings, label) # :nodoc:
178
178
  unless settings.host
179
- Ruport.complain(
179
+ Ruport.log(
180
180
  "Missing host for mailer #{label}",
181
181
  :status => :fatal, :level => :log_only,
182
- :exception => ArgumentError
182
+ :raises => ArgumentError
183
183
  )
184
184
  end
185
185
  end
@@ -189,5 +189,16 @@ module Ruport
189
189
  @debug_mode = !!something
190
190
  end
191
191
 
192
+ # Allows users to set their own accessors on the Config module
193
+ def method_missing(meth, *args)
194
+ @config ||= OpenStruct.new
195
+
196
+ if args.empty? || meth.to_s =~ /.*=/
197
+ @config.send(meth, *args)
198
+ else
199
+ @config.send("#{meth}=".to_sym, *args)
200
+ end
201
+ end
202
+
192
203
  end
193
204
  end
data/lib/ruport/data.rb CHANGED
@@ -1,6 +1,4 @@
1
1
  require "ruport/data/groupable"
2
2
  require "ruport/data/taggable"
3
3
  require "ruport/data/record"
4
- require "ruport/data/collection"
5
4
  require "ruport/data/table"
6
- require "ruport/data/set"
@@ -8,6 +8,7 @@ module Ruport::Data
8
8
  #
9
9
  module Groupable
10
10
 
11
+ #
11
12
  # Creates a <tt>Record</tt> made up of <tt>Table</tt>s containing all the
12
13
  # records in the original table with the same tag.
13
14
  #
@@ -39,20 +40,23 @@ module Ruport::Data
39
40
  # | clyde | 4 |
40
41
  # +----------------+
41
42
  #
42
- def group_by_tag
43
- r_tags = map { |r| r.tags }.flatten.uniq
43
+ def groups
44
+ #r_tags = map { |r| r.tags }.flatten.uniq
45
+ r_tags = group_names_intern
44
46
  tables_hash = Hash.new { |h,k| h[k] = [].to_table(column_names) }
45
- each { |row|
46
- row.tags.each { |t|
47
- tables_hash[t].instance_variable_get(:@data) << row
48
- }
49
- }
50
- r = Record.new tables_hash, :attributes => r_tags
51
- class << r
52
- def each_group; attributes.each { |a| yield(a) }; end
53
- end; r
47
+ r_tags.each { |t|
48
+ tables_hash[t.gsub(/^grp_/,"")] = sub_table { |r| r.tags.include? t }}
49
+ r = Record.new tables_hash, :attributes => group_names
50
+ end
51
+
52
+ def group_names
53
+ group_names_intern.map { |r| r.gsub(/^grp_/,"") }
54
54
  end
55
-
55
+
56
+ def group(tag)
57
+ sub_table { |r| r.tags.include?("grp_#{tag}") }
58
+ end
59
+ #
56
60
  # Tags each row of the <tt>Table</tt> for which the <tt>block</tt> is not
57
61
  # false with <tt>label</tt>.
58
62
  #
@@ -72,10 +76,15 @@ module Ruport::Data
72
76
  # | pinky | 3 |
73
77
  # +----------------+
74
78
  #
75
- def create_tag_group(label,&block)
76
- each { |r| block[r] && r.tag(label) }
77
- end
78
- alias_method :tag_group, :create_tag_group
79
+ def create_group(label,&block)
80
+ each { |r| block[r] && r.tag("grp_#{label}") }
81
+ end
82
+
83
+ private
84
+
85
+ def group_names_intern
86
+ map { |r| r.tags.select { |r| r =~ /^grp_/ } }.flatten.uniq
87
+ end
79
88
 
80
89
  end
81
90
  end
@@ -5,18 +5,19 @@
5
5
  # Copyright 2006 by respective content owners, all rights reserved.
6
6
  module Ruport::Data
7
7
 
8
+ #
8
9
  # === Overview
9
10
  #
10
11
  # Data::Records are the work horse of Ruport's data model. These can behave
11
12
  # as Array-like, Hash-like, or Struct-like objects. They are used as the
12
- # base element in both Tables and Sets.
13
+ # base element for Data::Table
13
14
  #
14
15
  class Record
15
- require "forwardable"
16
- extend Forwardable
17
- include Enumerable
18
- include Taggable
19
16
 
17
+ include Taggable
18
+ include Enumerable
19
+
20
+ #
20
21
  # Creates a new Record object. If the <tt>:attributes</tt>
21
22
  # keyword is specified, Hash-like and Struct-like
22
23
  # access will be enabled. Otherwise, Record elements may be
@@ -44,24 +45,18 @@ module Ruport::Data
44
45
  # b.c #=> 3
45
46
  #
46
47
  def initialize(data,options={})
47
- if data.kind_of?(Hash)
48
- if options[:attributes]
49
- @attributes = options[:attributes]
50
- @data = options[:attributes].map { |k| data[k] }
51
- else
52
- @attributes, @data = data.to_a.transpose
53
- end
54
- else
48
+ data = data.dup
49
+ case(data)
50
+ when Array
51
+ @attributes = options[:attributes] || (0...data.length).to_a
52
+ @data = @attributes.inject({}) { |h,a| h.merge(a => data.shift) }
53
+ when Hash
55
54
  @data = data.dup
56
- @attributes = options[:attributes] || []
55
+ @attributes = options[:attributes] || data.keys
57
56
  end
58
- end
59
-
60
- # The underlying <tt>data</tt> which is being stored in the record.
61
- attr_reader :data
57
+ end
62
58
 
63
- def_delegators :@data,:each, :length
64
-
59
+ #
65
60
  # Allows either Array or Hash-like indexing.
66
61
  #
67
62
  # Examples:
@@ -70,16 +65,15 @@ module Ruport::Data
70
65
  # my_record["foo"]
71
66
  #
72
67
  def [](index)
73
- if index.kind_of? Integer
74
- raise "Invalid index" unless index < @data.length
75
- @data[index]
68
+ case(index)
69
+ when Integer
70
+ @data[@attributes[index]]
76
71
  else
77
- index = index.to_s
78
- raise "Invalid index" unless attributes.index(index)
79
- @data[attributes.index(index)]
72
+ @data[index]
80
73
  end
81
74
  end
82
-
75
+
76
+ #
83
77
  # Allows setting a <tt>value</tt> at an <tt>index</tt>.
84
78
  #
85
79
  # Examples:
@@ -87,28 +81,21 @@ module Ruport::Data
87
81
  # my_record[1] = "foo"
88
82
  # my_record["bar"] = "baz"
89
83
  #
90
- def []=(index, value)
91
- if index.kind_of? Integer
92
- raise "Invalid index" unless index < @data.length
93
- @data[index] = value
84
+ def []=(index,value)
85
+ case(index)
86
+ when Integer
87
+ @data[@attributes[index]] = value
94
88
  else
95
- index = index.to_s
96
- raise "Invalid index" unless @attributes.index(index)
97
- @data[attributes.index(index)] = value
89
+ @data[index] = value
98
90
  end
99
91
  end
100
92
 
101
- # If <tt>attributes</tt> and <tt>data</tt> are equivalent, then
102
- # <tt>==</tt> evaluates to true. Otherwise, <tt>==</tt> returns false.
103
- #
104
- def ==(other)
105
- return false if @attributes && !other.attributes
106
- return false if other.attributes && !@attributes
107
- @attributes == other.attributes && @data == other.data
93
+ def size
94
+ @data.size
108
95
  end
109
-
110
- alias_method :eql?, :==
111
-
96
+ alias_method :length, :size
97
+
98
+ #
112
99
  # Converts a Record into an Array.
113
100
  #
114
101
  # Example:
@@ -116,17 +103,27 @@ module Ruport::Data
116
103
  # a = Data::Record.new([1,2],:attributes => %w[a b])
117
104
  # a.to_a #=> [1,2]
118
105
  #
119
- def to_a; @data.dup; end
106
+ # Note: in earlier versions of Ruport, to_a was aliased to data.
107
+ # From now on to_a only for array representation!
108
+ def to_a
109
+ @attributes.map { |a| @data[a] }
110
+ end
111
+
112
+ attr_reader :data
120
113
 
121
- # Converts a Record into a Hash. This only works if <tt>attributes</tt>
122
- # are specified in the Record.
123
- #
124
- # Example:
125
- #
126
- # a = Data::Record.new([1,2],:attributes => %w[a b])
127
- # a.to_h #=> {"a" => 1, "b" => 2}
128
- def to_h; Hash[*@attributes.zip(data).flatten] end
129
-
114
+ #
115
+ # Converts a Record into a Hash.
116
+ #
117
+ # Example:
118
+ #
119
+ # a = Data::Record.new([1,2],:attributes => %w[a b])
120
+ # a.to_h #=> {"a" => 1, "b" => 2}
121
+ def to_h
122
+ @data.dup
123
+ end
124
+
125
+ #alias_method :data,:to_a
126
+
130
127
  # Returns a copy of the <tt>attributes</tt> from this Record.
131
128
  #
132
129
  # Example:
@@ -134,72 +131,79 @@ module Ruport::Data
134
131
  # a = Data::Record.new([1,2],:attributes => %w[a b])
135
132
  # a.attributes #=> ["a","b"]
136
133
  #
137
- def attributes; @attributes.map { |a| a.to_s }; end
138
-
139
- # Sets the <tt>attribute</tt> list for this Record.
134
+ def attributes
135
+ @attributes.dup
136
+ end
137
+
138
+ #
139
+ # Sets the <tt>attribute</tt> list for this Record.
140
+ # (Dangerous when used within Table objects!)
140
141
  #
141
142
  # Example:
142
143
  #
143
144
  # my_record.attributes = %w[foo bar baz]
144
145
  #
145
146
  attr_writer :attributes
146
-
147
+
148
+ #
149
+ # If <tt>attributes</tt> and <tt>to_a</tt> are equivalent, then
150
+ # <tt>==</tt> evaluates to true. Otherwise, <tt>==</tt> returns false.
151
+ #
152
+ def ==(other)
153
+ @attributes.eql?(other.attributes) &&
154
+ to_a == other.to_a
155
+ end
156
+
157
+ alias_method :eql?, :==
158
+
159
+ # Yields each element of the Record. Does not provide attribute names
160
+ def each
161
+ to_a.each { |e| yield(e) }
162
+ end
163
+
164
+ #
147
165
  # Allows you to change the order of or reduce the number of columns in a
148
166
  # Record.
149
167
  #
150
168
  # Example:
151
169
  #
152
170
  # a = Data::Record.new([1,2,3,4],:attributes => %w[a b c d])
153
- # b = a.reorder("a","d","b")
154
- # b.attributes #=> ["a","d","b"]
155
- # b.data #=> [1,4,2]
156
- #
171
+ # a.reorder("a","d","b")
172
+ # a.attributes #=> ["a","d","b"]
173
+ # a.data #=> [1,4,2]
157
174
  def reorder(*indices)
158
- dup.reorder!(*indices)
159
- end
160
-
161
- # Same as Record#reorder but modifies its reciever in place.
162
- def reorder!(*indices)
163
- indices = reorder_data!(*indices)
164
- if @attributes
165
- if indices.all? { |e| e.kind_of? Integer }
166
- @attributes = indices.map { |i| @attributes[i] }
167
- else
168
- @attributes = indices
169
- end
170
- end; self
171
- end
172
-
173
- def reorder_data!(*indices) # :nodoc:
174
- indices = indices[0] if indices[0].kind_of?(Array)
175
- indices.each do |i|
176
- self[i] rescue raise ArgumentError,
177
- "you may have specified an invalid column"
175
+ indices[0].kind_of?(Array) && indices.flatten!
176
+ if indices.all? { |i| i.kind_of? Integer }
177
+ raise ArgumentError unless indices.all? { |i| @attributes[i] }
178
+ self.attributes = indices.map { |i| @attributes[i] }
179
+ else
180
+ raise ArgumentError unless (indices - @attributes).empty?
181
+ self.attributes = indices
178
182
  end
179
- @data = indices.map { |i| self[i] }
180
- return indices;
183
+ self
184
+ end
185
+
186
+ # Takes an old name and a new name and renames an attribute.
187
+ def rename_attribute(old_name,new_name,update_index=true)
188
+ @attributes[@attributes.index(old_name)] = new_name if update_index
189
+ @data[new_name] = @data.delete(old_name)
181
190
  end
182
-
183
-
184
- # Makes a fresh copy of the Record.
185
- def dup
186
- copy = self.class.new(@data,:attributes => attributes)
187
- copy.tags = self.tags.dup
188
- return copy
189
- end
190
-
191
- #FIXME: This does not take into account frozen / tainted state
192
- alias_method :clone, :dup
193
191
 
192
+ #
194
193
  # Provides a unique hash value. If a Record contains the same data and
195
194
  # attributes as another Record, they will hash to the same value, even if
196
195
  # they are not the same object. This is similar to the way Array works,
197
196
  # but different from Hash and other objects.
198
197
  #
199
198
  def hash
200
- (attributes.to_a + data.to_a).hash
199
+ @attributes.hash + to_a.hash
201
200
  end
202
-
201
+
202
+ # Makes a fresh copy of the Record.
203
+ def dup
204
+ r = Record.new(@data.dup,:attributes => @attributes.dup)
205
+ end
206
+
203
207
  # Provides accessor style methods for attribute access.
204
208
  #
205
209
  # Example:
@@ -207,14 +211,36 @@ module Ruport::Data
207
211
  # my_record.foo = 2
208
212
  # my_record.foo #=> 2
209
213
  #
210
- def method_missing(mname, *args)
211
- id = mname.to_s.gsub(/=$/,"")
212
- if attributes.include?(id)
213
- args.empty? ? self[id] : self[id] = args.first
214
+ def method_missing(id,*args)
215
+ k = id.to_s.gsub(/=$/,"")
216
+ if(key = @attributes.find { |r| r.to_s.eql?(k) })
217
+ args[0] ? @data[key] = args[0] : @data[key]
214
218
  else
215
- super(mname, *args)
219
+ super
220
+ end
221
+ end
222
+
223
+ #indifferentish access that also can call methods
224
+ def get(name)
225
+ case name
226
+ when Symbol
227
+ send(name)
228
+ when String
229
+ self[name]
230
+ else
231
+ raise "Whatchu Talkin' Bout, Willis?"
216
232
  end
217
233
  end
234
+
235
+ private
236
+
237
+ def delete(key)
238
+ @data.delete(key)
239
+ end
218
240
 
241
+
242
+ def reindex(new_attributes)
243
+ @attributes.replace(new_attributes)
244
+ end
219
245
  end
220
246
  end