ruport 0.7.2 → 0.8.0

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