ruport 0.8.14 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +42 -107
- data/Rakefile +29 -32
- data/examples/centered_pdf_text_box.rb +13 -19
- data/examples/example.csv +3 -0
- data/examples/line_plotter.rb +15 -15
- data/examples/pdf_complex_report.rb +10 -23
- data/examples/pdf_table_with_title.rb +12 -12
- data/examples/rope_examples/itunes/Rakefile +22 -1
- data/examples/rope_examples/itunes/config/environment.rb +4 -0
- data/examples/rope_examples/itunes/lib/init.rb +32 -2
- data/examples/rope_examples/itunes/util/build +50 -16
- data/examples/rope_examples/sales_report/README +1 -1
- data/examples/rope_examples/sales_report/Rakefile +22 -1
- data/examples/rope_examples/sales_report/config/environment.rb +4 -0
- data/examples/rope_examples/sales_report/lib/init.rb +32 -2
- data/examples/rope_examples/sales_report/lib/reports/sales.rb +10 -16
- data/examples/rope_examples/sales_report/util/build +50 -16
- data/examples/row_renderer.rb +39 -0
- data/examples/ruport_list/png_embed.rb +61 -0
- data/examples/ruport_list/roadmap.png +0 -0
- data/examples/sample.rb +16 -0
- data/examples/simple_pdf_lines.rb +24 -0
- data/lib/ruport.rb +143 -57
- data/lib/ruport/acts_as_reportable.rb +246 -0
- data/lib/ruport/data.rb +1 -2
- data/lib/ruport/data/grouping.rb +311 -0
- data/lib/ruport/data/record.rb +113 -84
- data/lib/ruport/data/table.rb +275 -174
- data/lib/ruport/formatter.rb +149 -0
- data/lib/ruport/formatter/csv.rb +87 -0
- data/lib/ruport/formatter/html.rb +89 -0
- data/lib/ruport/formatter/pdf.rb +357 -0
- data/lib/ruport/formatter/text.rb +151 -0
- data/lib/ruport/generator.rb +127 -30
- data/lib/ruport/query.rb +46 -99
- data/lib/ruport/renderer.rb +238 -194
- data/lib/ruport/renderer/grouping.rb +67 -0
- data/lib/ruport/renderer/table.rb +25 -98
- data/lib/ruport/report.rb +45 -96
- data/test/acts_as_reportable_test.rb +229 -0
- data/test/csv_formatter_test.rb +97 -0
- data/test/{_test_database.rb → database_test_.rb} +0 -0
- data/test/grouping_test.rb +305 -0
- data/test/html_formatter_test.rb +104 -0
- data/test/pdf_formatter_test.rb +25 -0
- data/test/{test_query.rb → query_test.rb} +32 -121
- data/test/{test_record.rb → record_test.rb} +40 -23
- data/test/renderer_test.rb +344 -0
- data/test/{test_report.rb → report_test.rb} +74 -44
- data/test/samples/ticket_count.csv +124 -0
- data/test/{test_sql_split.rb → sql_split_test.rb} +0 -0
- data/test/{test_table.rb → table_test.rb} +255 -44
- data/test/text_formatter_test.rb +144 -0
- data/util/bench/data/record/bench_as_vs_to.rb +17 -0
- data/util/bench/data/record/bench_constructor.rb +46 -0
- data/util/bench/data/record/bench_indexing.rb +65 -0
- data/util/bench/data/record/bench_reorder.rb +35 -0
- data/util/bench/data/record/bench_to_a.rb +19 -0
- data/util/bench/data/table/bench_column_manip.rb +103 -0
- data/util/bench/data/table/bench_dup.rb +24 -0
- data/util/bench/data/table/bench_init.rb +67 -0
- data/util/bench/data/table/bench_manip.rb +125 -0
- data/util/bench/formatter/bench_csv.rb +14 -0
- data/util/bench/formatter/bench_html.rb +14 -0
- data/util/bench/formatter/bench_pdf.rb +14 -0
- data/util/bench/formatter/bench_text.rb +14 -0
- data/util/bench/samples/tattle.csv +1237 -0
- metadata +121 -143
- data/TODO +0 -21
- data/examples/invoice.rb +0 -142
- data/examples/invoice_report.rb +0 -29
- data/examples/line_graph.rb +0 -38
- data/examples/rope_examples/itunes/config/ruport_config.rb +0 -8
- data/examples/rope_examples/sales_report/config/ruport_config.rb +0 -8
- data/lib/ruport/attempt.rb +0 -63
- data/lib/ruport/config.rb +0 -204
- data/lib/ruport/data/groupable.rb +0 -93
- data/lib/ruport/data/taggable.rb +0 -80
- data/lib/ruport/format.rb +0 -1
- data/lib/ruport/format/csv.rb +0 -29
- data/lib/ruport/format/html.rb +0 -42
- data/lib/ruport/format/latex.rb +0 -47
- data/lib/ruport/format/pdf.rb +0 -233
- data/lib/ruport/format/plugin.rb +0 -31
- data/lib/ruport/format/svg.rb +0 -60
- data/lib/ruport/format/text.rb +0 -103
- data/lib/ruport/format/xml.rb +0 -32
- data/lib/ruport/layout.rb +0 -1
- data/lib/ruport/layout/component.rb +0 -7
- data/lib/ruport/mailer.rb +0 -99
- data/lib/ruport/renderer/graph.rb +0 -46
- data/lib/ruport/report/graph.rb +0 -14
- data/lib/ruport/system_extensions.rb +0 -71
- data/test/test_config.rb +0 -88
- data/test/test_format_text.rb +0 -63
- data/test/test_graph_renderer.rb +0 -97
- data/test/test_groupable.rb +0 -56
- data/test/test_mailer.rb +0 -170
- data/test/test_renderer.rb +0 -151
- data/test/test_ruport.rb +0 -58
- data/test/test_table_renderer.rb +0 -141
- data/test/test_taggable.rb +0 -52
data/lib/ruport/renderer.rb
CHANGED
@@ -5,275 +5,319 @@
|
|
5
5
|
# This is free software. Please see the LICENSE and COPYING files for details.
|
6
6
|
|
7
7
|
|
8
|
-
# This class implements the core
|
8
|
+
# This class implements the core renderer for Ruport's formatting system. It is
|
9
9
|
# designed to implement the low level tools necessary to build report renderers
|
10
|
-
# for different kinds of tasks. See Renderer::Table for a tabular data
|
11
|
-
#
|
10
|
+
# for different kinds of tasks. See Renderer::Table for a tabular data
|
11
|
+
# renderer.
|
12
12
|
#
|
13
|
-
# This class can easily be extended to build custom formatting
|
14
|
-
# you do not need that, may not be relevant to study for your use of Ruport.
|
13
|
+
# This class can easily be extended to build custom formatting systems, but if
|
14
|
+
# you do not need that, it may not be relevant to study for your use of Ruport.
|
15
15
|
class Ruport::Renderer
|
16
|
-
module Helpers #:nodoc:
|
17
|
-
module ClassMethods
|
18
16
|
|
19
|
-
|
20
|
-
|
17
|
+
class RequiredOptionNotSet < RuntimeError; end
|
18
|
+
class UnknownFormatError < RuntimeError; end
|
19
|
+
class StageAlreadyDefinedError < RuntimeError; end
|
20
|
+
class RendererNotSetError < RuntimeError; end
|
21
|
+
|
22
|
+
require "ostruct"
|
23
|
+
class Options < OpenStruct #:nodoc:
|
24
|
+
def to_hash
|
25
|
+
@table
|
26
|
+
end
|
27
|
+
def [](key)
|
28
|
+
send(key)
|
29
|
+
end
|
30
|
+
def []=(key,value)
|
31
|
+
send("#{key}=",value)
|
32
|
+
end
|
33
|
+
end
|
21
34
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
self.final_stage = stage
|
29
|
-
end
|
35
|
+
module Hooks #:nodoc:
|
36
|
+
module ClassMethods
|
37
|
+
def renders_with(renderer,opts={})
|
38
|
+
@renderer = renderer.name
|
39
|
+
@rendering_options=opts
|
40
|
+
end
|
30
41
|
|
31
|
-
|
32
|
-
|
33
|
-
#
|
34
|
-
# prepare :document
|
35
|
-
#
|
36
|
-
def prepare(stage)
|
37
|
-
raise "prepare stage already defined" if first_stage
|
38
|
-
self.first_stage = stage
|
42
|
+
def rendering_options
|
43
|
+
@rendering_options
|
39
44
|
end
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
# data
|
44
|
-
# e.g.
|
45
|
-
# option :report_title
|
46
|
-
# option :table_width
|
47
|
-
def option(opt)
|
48
|
-
opt = "#{opt.to_s}="
|
49
|
-
define_method(opt) {|t| options.send(opt, t) }
|
45
|
+
|
46
|
+
def renders_as_table(options={})
|
47
|
+
renders_with Ruport::Renderer::Table,options
|
50
48
|
end
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
# required_option :freight
|
55
|
-
# required_option :tax
|
56
|
-
def required_option(opt)
|
57
|
-
self.required_options ||= []
|
58
|
-
self.required_options << opt
|
59
|
-
option opt
|
49
|
+
|
50
|
+
def renders_as_row(options={})
|
51
|
+
renders_with Ruport::Renderer::Row, options
|
60
52
|
end
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
53
|
+
|
54
|
+
def renders_as_group(options={})
|
55
|
+
renders_with Ruport::Renderer::Group,options
|
56
|
+
end
|
57
|
+
|
58
|
+
def renders_as_grouping(options={})
|
59
|
+
renders_with Ruport::Renderer::Grouping,options
|
60
|
+
end
|
61
|
+
|
62
|
+
def renderer
|
63
|
+
return unless @renderer
|
64
|
+
@renderer.split("::").inject(Class) { |c,el| c.const_get(el) }
|
71
65
|
end
|
72
66
|
end
|
73
|
-
|
67
|
+
|
74
68
|
def self.included(base)
|
75
|
-
base.extend
|
76
|
-
end
|
69
|
+
base.extend(ClassMethods)
|
70
|
+
end
|
71
|
+
|
72
|
+
def as(format,options={})
|
73
|
+
raise RendererNotSetError unless self.class.renderer
|
74
|
+
unless self.class.renderer.formats.include?(format)
|
75
|
+
raise UnknownFormatError
|
76
|
+
end
|
77
|
+
self.class.renderer.render(format,
|
78
|
+
self.class.rendering_options.merge(options)) do |rend|
|
79
|
+
rend.data = send(:renderable_data) rescue self
|
80
|
+
yield(rend) if block_given?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
77
84
|
|
85
|
+
|
86
|
+
module AutoRunner #:nodoc:
|
78
87
|
# called automagically when the report is rendered. Uses the
|
79
88
|
# data collected from the earlier methods.
|
80
|
-
def
|
89
|
+
def _run_
|
90
|
+
|
81
91
|
# ensure all the required options have been set
|
82
92
|
unless self.class.required_options.nil?
|
83
93
|
self.class.required_options.each do |opt|
|
84
94
|
if options.__send__(opt).nil?
|
85
|
-
raise "Required option #{opt} not set"
|
95
|
+
raise RequiredOptionNotSet, "Required option #{opt} not set"
|
86
96
|
end
|
87
97
|
end
|
88
98
|
end
|
89
99
|
|
90
100
|
prepare self.class.first_stage if self.class.first_stage
|
101
|
+
|
102
|
+
if formatter.respond_to?(:layout)
|
103
|
+
formatter.layout do execute_stages end
|
104
|
+
else
|
105
|
+
execute_stages
|
106
|
+
end
|
107
|
+
|
108
|
+
finalize self.class.final_stage if self.class.final_stage
|
91
109
|
|
110
|
+
end
|
111
|
+
|
112
|
+
def execute_stages
|
92
113
|
# call each stage to build the report
|
93
114
|
unless self.class.stages.nil?
|
94
115
|
self.class.stages.each do |stage|
|
95
|
-
self.build
|
116
|
+
self.send(:build,stage)
|
96
117
|
end
|
97
118
|
end
|
98
|
-
|
99
|
-
finalize self.class.final_stage if self.class.final_stage
|
100
|
-
|
101
119
|
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class << self
|
123
|
+
|
124
|
+
attr_accessor :first_stage,:final_stage,:required_options,:stages #:nodoc:
|
102
125
|
|
103
|
-
|
104
|
-
|
105
|
-
|
126
|
+
def finalize(stage)
|
127
|
+
if final_stage
|
128
|
+
raise StageAlreadyDefinedError, 'final stage already defined'
|
129
|
+
end
|
130
|
+
self.final_stage = stage
|
106
131
|
end
|
107
132
|
|
108
|
-
def
|
109
|
-
|
110
|
-
|
133
|
+
def prepare(stage)
|
134
|
+
if first_stage
|
135
|
+
raise StageAlreadyDefinedError, "prepare stage already defined"
|
136
|
+
end
|
137
|
+
self.first_stage = stage
|
111
138
|
end
|
112
139
|
|
113
|
-
def
|
114
|
-
|
140
|
+
def option(*opts)
|
141
|
+
opts.each do |opt|
|
142
|
+
opt = "#{opt}="
|
143
|
+
define_method(opt) {|t| options.send(opt, t) }
|
144
|
+
end
|
115
145
|
end
|
116
146
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
147
|
+
def required_option(*opts)
|
148
|
+
self.required_options ||= []
|
149
|
+
opts.each do |opt|
|
150
|
+
self.required_options << opt
|
151
|
+
option opt
|
152
|
+
end
|
121
153
|
end
|
122
|
-
end
|
123
|
-
# allows you to register a format with the renderer.
|
124
|
-
#
|
125
|
-
# example:
|
126
|
-
#
|
127
|
-
# class MyPlugin < Ruport::Format::Plugin
|
128
|
-
#
|
129
|
-
# # plugin code ...
|
130
|
-
#
|
131
|
-
# SomeRenderer.add_format self, :my_plugin
|
132
|
-
#
|
133
|
-
# end
|
134
|
-
def self.add_format(format,name=nil)
|
135
|
-
return formats[name] = format if name
|
136
|
-
|
137
|
-
add_core_format(format)
|
138
|
-
end
|
139
|
-
|
140
154
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
155
|
+
def stage(*stage_list)
|
156
|
+
self.stages ||= []
|
157
|
+
stage_list.each { |stage|
|
158
|
+
self.stages << stage.to_s
|
159
|
+
}
|
160
|
+
end
|
145
161
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
@layout ||= Ruport::Layout::Component.new
|
150
|
-
yield(@layout) if block_given?
|
162
|
+
def formats
|
163
|
+
@formats ||= {}
|
164
|
+
end
|
151
165
|
|
152
|
-
|
153
|
-
|
166
|
+
def render(*args)
|
167
|
+
rend = build(*args) { |r|
|
168
|
+
r.setup if r.respond_to? :setup
|
169
|
+
yield(r) if block_given?
|
170
|
+
}
|
171
|
+
if rend.respond_to? :run
|
172
|
+
rend.run
|
173
|
+
else
|
174
|
+
include AutoRunner
|
175
|
+
end
|
176
|
+
rend._run_ if rend.respond_to? :_run_
|
177
|
+
return rend.formatter.output
|
178
|
+
end
|
154
179
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
180
|
+
# Allows you to set class_wide default options
|
181
|
+
#
|
182
|
+
# Example:
|
183
|
+
#
|
184
|
+
# options { |o| o.style = :justified }
|
185
|
+
#
|
186
|
+
def options
|
187
|
+
@options ||= Ruport::Renderer::Options.new
|
188
|
+
yield(@options) if block_given?
|
189
|
+
|
190
|
+
return @options
|
191
|
+
end
|
167
192
|
|
193
|
+
# Creates a new instance of the renderer and sets it to use the specified
|
194
|
+
# formatter (by name). If a block is given, the renderer instance is
|
195
|
+
# yielded.
|
196
|
+
#
|
197
|
+
# Returns the renderer instance.
|
198
|
+
#
|
199
|
+
def build(*args)
|
200
|
+
rend = self.new
|
201
|
+
|
202
|
+
rend.send(:use_formatter,args[0])
|
203
|
+
rend.send(:options=, options.dup)
|
204
|
+
if rend.class.const_defined? :Helpers
|
205
|
+
rend.formatter.extend(rend.class.const_get(:Helpers))
|
206
|
+
end
|
207
|
+
if args[1].kind_of?(Hash)
|
208
|
+
d = args[1].delete(:data)
|
209
|
+
rend.data = d if d
|
210
|
+
args[1].each {|k,v| rend.options.send("#{k}=",v) }
|
211
|
+
end
|
168
212
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
213
|
+
yield(rend) if block_given?
|
214
|
+
return rend
|
215
|
+
end
|
216
|
+
|
217
|
+
private
|
218
|
+
|
219
|
+
# Allows you to register a format with the renderer.
|
220
|
+
#
|
221
|
+
# example:
|
222
|
+
#
|
223
|
+
# class MyFormatter < Ruport::Formatter
|
224
|
+
# # formatter code ...
|
225
|
+
# SomeRenderer.add_format self, :my_formatter
|
226
|
+
# end
|
227
|
+
#
|
228
|
+
def add_format(format,name=nil)
|
229
|
+
formats[name] = format
|
230
|
+
end
|
231
|
+
|
181
232
|
end
|
182
233
|
|
183
234
|
attr_accessor :format
|
184
|
-
|
235
|
+
attr_writer :formatter
|
185
236
|
|
186
|
-
|
187
|
-
|
188
|
-
# modification.
|
189
|
-
#
|
190
|
-
def layout
|
191
|
-
@layout ||= Ruport::Layout::Component.new
|
192
|
-
yield(@layout) if block_given?
|
193
|
-
|
194
|
-
plugin.layout = @layout
|
237
|
+
def data
|
238
|
+
formatter.data
|
195
239
|
end
|
196
240
|
|
197
|
-
#
|
241
|
+
# Sets +data+ attribute on the active formatter.
|
198
242
|
def data=(val)
|
199
|
-
|
200
|
-
plugin.data = @data if plugin
|
243
|
+
formatter.data = val.dup
|
201
244
|
end
|
202
245
|
|
203
|
-
#
|
204
|
-
# plugin.
|
246
|
+
# Renderer::Options object which is shared with the current formatter.
|
205
247
|
def options
|
206
|
-
yield(
|
207
|
-
|
248
|
+
yield(formatter.options) if block_given?
|
249
|
+
formatter.options
|
250
|
+
end
|
251
|
+
|
252
|
+
# If an IO object is given, Formatter#output will use it instead of
|
253
|
+
# the default String. For Ruport's core renderers, we technically
|
254
|
+
# can use any object that supports the << method, but it's meant
|
255
|
+
# for IO objects such as File or STDOUT
|
256
|
+
#
|
257
|
+
def io=(obj)
|
258
|
+
options.io=obj
|
208
259
|
end
|
209
260
|
|
210
|
-
#
|
261
|
+
# When no block is given, returns active formatter.
|
262
|
+
#
|
263
|
+
# When a block is given with a block variable, sets the block variable to the
|
264
|
+
# formatter.
|
211
265
|
#
|
212
|
-
#
|
213
|
-
#
|
266
|
+
# When a block is given without block variables, instance_evals the block
|
267
|
+
# within the context of the formatter.
|
214
268
|
#
|
215
|
-
|
216
|
-
# within the context of the plugin
|
217
|
-
def plugin(&block)
|
269
|
+
def formatter(&block)
|
218
270
|
if block.nil?
|
219
|
-
return @
|
271
|
+
return @formatter
|
220
272
|
elsif block.arity > 0
|
221
|
-
yield(@
|
273
|
+
yield(@formatter)
|
222
274
|
else
|
223
|
-
@
|
275
|
+
@formatter.instance_eval(&block)
|
224
276
|
end
|
225
|
-
return @
|
277
|
+
return @formatter
|
226
278
|
end
|
227
279
|
|
228
|
-
|
280
|
+
# Provides a shortcut to render() to allow
|
281
|
+
# render(:csv) to become render_csv
|
282
|
+
#
|
283
|
+
def self.method_missing(id,*args,&block)
|
284
|
+
id.to_s =~ /^render_(.*)/
|
285
|
+
unless args[0].kind_of? Hash
|
286
|
+
args = [ (args[1] || {}).merge(:data => args[0]) ]
|
287
|
+
end
|
288
|
+
$1 ? render($1.to_sym,*args,&block) : super
|
289
|
+
end
|
229
290
|
|
230
|
-
|
291
|
+
private
|
231
292
|
|
232
|
-
|
233
|
-
|
234
|
-
@layout = lay
|
235
|
-
plugin.layout = lay
|
293
|
+
def prepare(name)
|
294
|
+
maybe "prepare_#{name}"
|
236
295
|
end
|
237
296
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
try_require(format)
|
242
|
-
|
243
|
-
klass = Ruport::Format.const_get(
|
244
|
-
Ruport::Format.constants.find { |c| c =~ /#{format}/i })
|
245
|
-
|
246
|
-
formats[format] = klass
|
297
|
+
def build(names,prefix=nil)
|
298
|
+
return maybe("build_#{names}") if prefix.nil?
|
299
|
+
names.each { |n| maybe "build_#{prefix}_#{n}" }
|
247
300
|
end
|
248
301
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
def self.try_require(format)
|
257
|
-
begin
|
258
|
-
require "ruport/format/#{format}"
|
259
|
-
rescue LoadError
|
260
|
-
nil
|
261
|
-
end
|
262
|
-
end
|
302
|
+
def finalize(name)
|
303
|
+
maybe "finalize_#{name}"
|
304
|
+
end
|
305
|
+
|
306
|
+
def maybe(something)
|
307
|
+
formatter.send something if formatter.respond_to? something
|
308
|
+
end
|
263
309
|
|
264
|
-
|
265
|
-
|
266
|
-
self.plugin = self.class.formats[format].new
|
310
|
+
def options=(o)
|
311
|
+
formatter.options = o
|
267
312
|
end
|
268
|
-
|
269
|
-
#
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
$1 ? render($1.to_sym,&block) : super
|
313
|
+
|
314
|
+
# selects a formatter for use by format name
|
315
|
+
def use_formatter(format)
|
316
|
+
self.formatter = self.class.formats[format].new
|
317
|
+
self.formatter.format = format
|
274
318
|
end
|
275
319
|
|
276
320
|
end
|
277
321
|
|
278
322
|
require "ruport/renderer/table"
|
279
|
-
require "ruport/renderer/
|
323
|
+
require "ruport/renderer/grouping"
|