ruport 0.8.14 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/README +42 -107
  2. data/Rakefile +29 -32
  3. data/examples/centered_pdf_text_box.rb +13 -19
  4. data/examples/example.csv +3 -0
  5. data/examples/line_plotter.rb +15 -15
  6. data/examples/pdf_complex_report.rb +10 -23
  7. data/examples/pdf_table_with_title.rb +12 -12
  8. data/examples/rope_examples/itunes/Rakefile +22 -1
  9. data/examples/rope_examples/itunes/config/environment.rb +4 -0
  10. data/examples/rope_examples/itunes/lib/init.rb +32 -2
  11. data/examples/rope_examples/itunes/util/build +50 -16
  12. data/examples/rope_examples/sales_report/README +1 -1
  13. data/examples/rope_examples/sales_report/Rakefile +22 -1
  14. data/examples/rope_examples/sales_report/config/environment.rb +4 -0
  15. data/examples/rope_examples/sales_report/lib/init.rb +32 -2
  16. data/examples/rope_examples/sales_report/lib/reports/sales.rb +10 -16
  17. data/examples/rope_examples/sales_report/util/build +50 -16
  18. data/examples/row_renderer.rb +39 -0
  19. data/examples/ruport_list/png_embed.rb +61 -0
  20. data/examples/ruport_list/roadmap.png +0 -0
  21. data/examples/sample.rb +16 -0
  22. data/examples/simple_pdf_lines.rb +24 -0
  23. data/lib/ruport.rb +143 -57
  24. data/lib/ruport/acts_as_reportable.rb +246 -0
  25. data/lib/ruport/data.rb +1 -2
  26. data/lib/ruport/data/grouping.rb +311 -0
  27. data/lib/ruport/data/record.rb +113 -84
  28. data/lib/ruport/data/table.rb +275 -174
  29. data/lib/ruport/formatter.rb +149 -0
  30. data/lib/ruport/formatter/csv.rb +87 -0
  31. data/lib/ruport/formatter/html.rb +89 -0
  32. data/lib/ruport/formatter/pdf.rb +357 -0
  33. data/lib/ruport/formatter/text.rb +151 -0
  34. data/lib/ruport/generator.rb +127 -30
  35. data/lib/ruport/query.rb +46 -99
  36. data/lib/ruport/renderer.rb +238 -194
  37. data/lib/ruport/renderer/grouping.rb +67 -0
  38. data/lib/ruport/renderer/table.rb +25 -98
  39. data/lib/ruport/report.rb +45 -96
  40. data/test/acts_as_reportable_test.rb +229 -0
  41. data/test/csv_formatter_test.rb +97 -0
  42. data/test/{_test_database.rb → database_test_.rb} +0 -0
  43. data/test/grouping_test.rb +305 -0
  44. data/test/html_formatter_test.rb +104 -0
  45. data/test/pdf_formatter_test.rb +25 -0
  46. data/test/{test_query.rb → query_test.rb} +32 -121
  47. data/test/{test_record.rb → record_test.rb} +40 -23
  48. data/test/renderer_test.rb +344 -0
  49. data/test/{test_report.rb → report_test.rb} +74 -44
  50. data/test/samples/ticket_count.csv +124 -0
  51. data/test/{test_sql_split.rb → sql_split_test.rb} +0 -0
  52. data/test/{test_table.rb → table_test.rb} +255 -44
  53. data/test/text_formatter_test.rb +144 -0
  54. data/util/bench/data/record/bench_as_vs_to.rb +17 -0
  55. data/util/bench/data/record/bench_constructor.rb +46 -0
  56. data/util/bench/data/record/bench_indexing.rb +65 -0
  57. data/util/bench/data/record/bench_reorder.rb +35 -0
  58. data/util/bench/data/record/bench_to_a.rb +19 -0
  59. data/util/bench/data/table/bench_column_manip.rb +103 -0
  60. data/util/bench/data/table/bench_dup.rb +24 -0
  61. data/util/bench/data/table/bench_init.rb +67 -0
  62. data/util/bench/data/table/bench_manip.rb +125 -0
  63. data/util/bench/formatter/bench_csv.rb +14 -0
  64. data/util/bench/formatter/bench_html.rb +14 -0
  65. data/util/bench/formatter/bench_pdf.rb +14 -0
  66. data/util/bench/formatter/bench_text.rb +14 -0
  67. data/util/bench/samples/tattle.csv +1237 -0
  68. metadata +121 -143
  69. data/TODO +0 -21
  70. data/examples/invoice.rb +0 -142
  71. data/examples/invoice_report.rb +0 -29
  72. data/examples/line_graph.rb +0 -38
  73. data/examples/rope_examples/itunes/config/ruport_config.rb +0 -8
  74. data/examples/rope_examples/sales_report/config/ruport_config.rb +0 -8
  75. data/lib/ruport/attempt.rb +0 -63
  76. data/lib/ruport/config.rb +0 -204
  77. data/lib/ruport/data/groupable.rb +0 -93
  78. data/lib/ruport/data/taggable.rb +0 -80
  79. data/lib/ruport/format.rb +0 -1
  80. data/lib/ruport/format/csv.rb +0 -29
  81. data/lib/ruport/format/html.rb +0 -42
  82. data/lib/ruport/format/latex.rb +0 -47
  83. data/lib/ruport/format/pdf.rb +0 -233
  84. data/lib/ruport/format/plugin.rb +0 -31
  85. data/lib/ruport/format/svg.rb +0 -60
  86. data/lib/ruport/format/text.rb +0 -103
  87. data/lib/ruport/format/xml.rb +0 -32
  88. data/lib/ruport/layout.rb +0 -1
  89. data/lib/ruport/layout/component.rb +0 -7
  90. data/lib/ruport/mailer.rb +0 -99
  91. data/lib/ruport/renderer/graph.rb +0 -46
  92. data/lib/ruport/report/graph.rb +0 -14
  93. data/lib/ruport/system_extensions.rb +0 -71
  94. data/test/test_config.rb +0 -88
  95. data/test/test_format_text.rb +0 -63
  96. data/test/test_graph_renderer.rb +0 -97
  97. data/test/test_groupable.rb +0 -56
  98. data/test/test_mailer.rb +0 -170
  99. data/test/test_renderer.rb +0 -151
  100. data/test/test_ruport.rb +0 -58
  101. data/test/test_table_renderer.rb +0 -141
  102. data/test/test_taggable.rb +0 -52
@@ -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 engine for Ruport's formatting system. It is
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 renderer
11
- # and Renderer::Graph for graphing support.
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 engines, but if
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
- # establish some class instance variables for storing require data
20
- attr_accessor :first_stage,:final_stage,:required_options,:stages
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
- # allow the report designer to specify what method will
23
- # render the report e.g.
24
- # finalize :document
25
- #
26
- def finalize(stage)
27
- raise 'final stage already defined' if final_stage
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
- # allow the report designer to specify a preparation stage for their
32
- # report, e.g.
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
- # allow the report designer to specify options that can be used to build
42
- # the report. These are generally used for defining rendering options or
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
- # allow the report designer to specify a compulsory option
53
- # e.g.
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
- # allow the report designer to specify the stages that will be used to
63
- # build the report
64
- # e.g.
65
- # stage :document_header
66
- # stage :document_body
67
- # stage :document_footer
68
- def stage(stage)
69
- self.stages ||= []
70
- self.stages << stage.to_s
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 ClassMethods
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 run
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 stage
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
- def prepare(name)
105
- maybe "prepare_#{name}"
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 build(names,prefix=nil)
109
- return maybe("build_#{names}") if prefix.nil?
110
- names.each { |n| maybe "build_#{prefix}_#{n}" }
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 finalize(name)
114
- maybe "finalize_#{name}"
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
- private
118
-
119
- def maybe(something)
120
- plugin.send something if plugin.respond_to? something
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
- # reader for formats. Defaults to a hash
142
- def self.formats
143
- @formats ||= {}
144
- end
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
- # same as Renderer#layout, but can be used to specify a default layout object
147
- # for the entire class.
148
- def self.layout
149
- @layout ||= Ruport::Layout::Component.new
150
- yield(@layout) if block_given?
162
+ def formats
163
+ @formats ||= {}
164
+ end
151
165
 
152
- return @layout
153
- end
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
- # creates a new instance of the renderer
156
- # then looks up the formatting plugin and creates a new instance of that as
157
- # well. If a block is given, the renderer instance is yielded.
158
- #
159
- # The run() method is then called on the renderer method.
160
- #
161
- # Finally, the value of the plugin's output accessor is returned
162
- def self.render(format,&block)
163
- rend = build format, &block
164
- rend.run
165
- return rend.plugin.output
166
- end
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
- # creates a new instance of the renderer and sets it to use the specified
170
- # formatting plugin (by name). If a block is given, the renderer instance is
171
- # yielded.
172
- #
173
- # Returns the renderer instance.
174
- def self.build(format)
175
- rend = self.new
176
-
177
- rend.send(:use_plugin,format)
178
- rend.send(:layout=, layout.dup)
179
- yield(rend) if block_given?
180
- return rend
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
- attr_reader :data
235
+ attr_writer :formatter
185
236
 
186
- # Generates a layout object and passes it along to the current formatting
187
- # plugin. If the block form is used, the layout object will be yielded for
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
- # sets +data+ attribute on both the renderer and any active plugin
241
+ # Sets +data+ attribute on the active formatter.
198
242
  def data=(val)
199
- @data = val.dup
200
- plugin.data = @data if plugin
243
+ formatter.data = val.dup
201
244
  end
202
245
 
203
- # General purpose openstruct which is shared with the current formatting
204
- # plugin.
246
+ # Renderer::Options object which is shared with the current formatter.
205
247
  def options
206
- yield(plugin.options) if block_given?
207
- plugin.options
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
- # when no block is given, returns active plugin
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
- # when a block is given with a block variable, sets the block variable to the
213
- # plugin.
266
+ # When a block is given without block variables, instance_evals the block
267
+ # within the context of the formatter.
214
268
  #
215
- # when a block is given without block variables, instance_evals the block
216
- # within the context of the plugin
217
- def plugin(&block)
269
+ def formatter(&block)
218
270
  if block.nil?
219
- return @plugin
271
+ return @formatter
220
272
  elsif block.arity > 0
221
- yield(@plugin)
273
+ yield(@formatter)
222
274
  else
223
- @plugin.instance_eval(&block)
275
+ @formatter.instance_eval(&block)
224
276
  end
225
- return @plugin
277
+ return @formatter
226
278
  end
227
279
 
228
- private
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
- attr_writer :plugin
291
+ private
231
292
 
232
- #internal layout accessor.
233
- def layout=(lay)
234
- @layout = lay
235
- plugin.layout = lay
293
+ def prepare(name)
294
+ maybe "prepare_#{name}"
236
295
  end
237
296
 
238
- # tries to autoload and register a format which is part of the Ruport::Format
239
- # module. For internal use only.
240
- def self.add_core_format(format)
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
- # internal shortcut for format registering
250
- def self.add_formats(*formats)
251
- formats.each { |f| add_format f }
252
- end
253
-
254
- # Trys to autoload a given format,
255
- # silently fails
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
- # selects a plugin for use by format name
265
- def use_plugin(format)
266
- self.plugin = self.class.formats[format].new
310
+ def options=(o)
311
+ formatter.options = o
267
312
  end
268
-
269
- # provides a shortcut to render() to allow
270
- # render(:csv) to become render_csv
271
- def self.method_missing(id,*args,&block)
272
- id.to_s =~ /^render_(.*)/
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/graph"
323
+ require "ruport/renderer/grouping"