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.
- data/AUTHORS +7 -3
- data/Rakefile +8 -9
- data/TODO +16 -0
- data/examples/RWEmerson.jpg +0 -0
- data/examples/centered_pdf_text_box.rb +66 -0
- data/examples/invoice.rb +35 -25
- data/examples/invoice_report.rb +1 -1
- data/examples/line_plotter.rb +1 -1
- data/examples/pdf_table_with_title.rb +42 -0
- data/lib/ruport.rb +5 -7
- data/lib/ruport.rb.rej +41 -0
- data/lib/ruport.rb~ +85 -0
- data/lib/ruport/attempt.rb +59 -59
- data/lib/ruport/config.rb +15 -4
- data/lib/ruport/data.rb +0 -2
- data/lib/ruport/data/groupable.rb +25 -16
- data/lib/ruport/data/record.rb +128 -102
- data/lib/ruport/data/table.rb +352 -199
- data/lib/ruport/data/taggable.rb +18 -7
- data/lib/ruport/format/html.rb +3 -1
- data/lib/ruport/format/latex.rb +1 -1
- data/lib/ruport/format/latex.rb.rej +26 -0
- data/lib/ruport/format/latex.rb~ +47 -0
- data/lib/ruport/format/pdf.rb +111 -28
- data/lib/ruport/format/pdf.rb.rej +168 -0
- data/lib/ruport/format/pdf.rb~ +189 -0
- data/lib/ruport/format/plugin.rb +0 -5
- data/lib/ruport/format/svg.rb +4 -4
- data/lib/ruport/format/xml.rb +3 -3
- data/lib/ruport/generator.rb +66 -27
- data/lib/ruport/mailer.rb +4 -1
- data/lib/ruport/query.rb +13 -1
- data/lib/ruport/renderer.rb +89 -17
- data/lib/ruport/renderer/graph.rb +5 -5
- data/lib/ruport/renderer/table.rb +8 -9
- data/lib/ruport/report.rb +2 -6
- data/test/test_config.rb +88 -76
- data/test/{test_text_table.rb → test_format_text.rb} +4 -2
- data/test/test_groupable.rb +15 -13
- data/test/test_query.rb +6 -3
- data/test/test_record.rb +57 -33
- data/test/test_renderer.rb +77 -0
- data/test/test_report.rb +188 -181
- data/test/test_ruport.rb +5 -6
- data/test/test_table.rb +290 -190
- data/test/test_table_renderer.rb +56 -8
- data/test/test_taggable.rb +7 -8
- data/test/unit.log +259 -7
- metadata +22 -19
- data/lib/ruport/data/collection.rb +0 -65
- data/lib/ruport/data/set.rb +0 -148
- data/test/test_collection.rb +0 -30
- data/test/test_set.rb +0 -118
data/lib/ruport/attempt.rb
CHANGED
@@ -1,63 +1,63 @@
|
|
1
1
|
require 'timeout'
|
2
2
|
|
3
3
|
class Attempt # :nodoc:
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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.
|
168
|
+
Ruport.log(
|
169
169
|
"Missing DSN for source #{label}!",
|
170
170
|
:status => :fatal, :level => :log_only,
|
171
|
-
:
|
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.
|
179
|
+
Ruport.log(
|
180
180
|
"Missing host for mailer #{label}",
|
181
181
|
:status => :fatal, :level => :log_only,
|
182
|
-
:
|
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
@@ -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
|
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 { |
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
76
|
-
each { |r| block[r] && r.tag(label) }
|
77
|
-
end
|
78
|
-
|
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
|
data/lib/ruport/data/record.rb
CHANGED
@@ -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
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
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
|
-
|
74
|
-
|
75
|
-
@data[index]
|
68
|
+
case(index)
|
69
|
+
when Integer
|
70
|
+
@data[@attributes[index]]
|
76
71
|
else
|
77
|
-
index
|
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,
|
91
|
-
|
92
|
-
|
93
|
-
@data[index] = value
|
84
|
+
def []=(index,value)
|
85
|
+
case(index)
|
86
|
+
when Integer
|
87
|
+
@data[@attributes[index]] = value
|
94
88
|
else
|
95
|
-
index =
|
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
|
-
|
102
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
def to_h
|
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
|
138
|
-
|
139
|
-
|
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
|
-
#
|
154
|
-
#
|
155
|
-
#
|
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
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
-
|
180
|
-
|
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
|
-
|
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(
|
211
|
-
|
212
|
-
if attributes.
|
213
|
-
args
|
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
|
-
|
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
|