ruport 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -3,7 +3,7 @@ require "rake/testtask"
3
3
  require "rake/gempackagetask"
4
4
 
5
5
 
6
- RUPORT_VERSION = "1.0.1"
6
+ RUPORT_VERSION = "1.0.2"
7
7
 
8
8
  begin
9
9
  require "rubygems"
@@ -32,7 +32,7 @@ class TracSummaryReport
32
32
  table = Table([:title, :date], :record_class => TicketStatus) do |table|
33
33
  (feed/"item").each do |r|
34
34
  title = (r/"title").innerHTML
35
- next unless title =~ /Ticket.*(created|closed|reopene)/
35
+ next unless title =~ /Ticket.*(created|closed|reopened)/
36
36
  table << { :title => title,
37
37
  :date => Date.parse((r/"pubdate").innerHTML) }
38
38
  end
@@ -40,7 +40,7 @@ module Ruport::Data
40
40
  # table = Table.load('mydata.csv',:has_names => false)
41
41
  #
42
42
  # # pass in FasterCSV options, such as column separators
43
- # table = Table.load('mydata.csv',:csv_opts => { :col_sep => "\t" })
43
+ # table = Table.load('mydata.csv',:csv_options => { :col_sep => "\t" })
44
44
  #
45
45
  def load(csv_file, options={},&block)
46
46
  get_table_from_csv(:foreach, csv_file, options,&block)
@@ -144,7 +144,7 @@ module Ruport::Data
144
144
  r.attributes.all? { |a| a.kind_of?(Numeric) }
145
145
  r.to_a
146
146
  else
147
- r.to_hash
147
+ r.to_hash.values_at(*@column_names)
148
148
  end
149
149
  }
150
150
  end
@@ -122,7 +122,9 @@ module Ruport
122
122
  private
123
123
 
124
124
  def render_helper(rend_klass, source_data,options={},&block)
125
- options = {:data => source_data, :io => output}.merge(options)
125
+ options = {:data => source_data,
126
+ :io => output,
127
+ :layout => false }.merge(options)
126
128
  rend_klass.render(format,options) do |rend|
127
129
  block[rend] if block
128
130
  end
@@ -191,7 +191,7 @@ class Ruport::Renderer
191
191
 
192
192
  prepare self.class.first_stage if self.class.first_stage
193
193
 
194
- if formatter.respond_to?(:layout)
194
+ if formatter.respond_to?(:layout) && options.layout != false
195
195
  formatter.layout do execute_stages end
196
196
  else
197
197
  execute_stages
@@ -382,7 +382,7 @@ class Ruport::Renderer
382
382
  rend = build(*args) { |r|
383
383
  yield(r) if block_given?
384
384
  r.setup if r.respond_to? :setup
385
- }
385
+ }
386
386
  if rend.respond_to? :run
387
387
  rend.run
388
388
  else
@@ -0,0 +1,542 @@
1
+ # renderer.rb : General purpose formatted data renderer for Ruby Reports
2
+ #
3
+ # Copyright December 2006, Gregory Brown. All Rights Reserved.
4
+ #
5
+ # This is free software. Please see the LICENSE and COPYING files for details.
6
+
7
+
8
+ # This class implements the core renderer for Ruport's formatting system. It is
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
+ # renderer.
12
+ #
13
+ class Ruport::Renderer
14
+
15
+ class RequiredOptionNotSet < RuntimeError #:nodoc:
16
+ end
17
+ class UnknownFormatError < RuntimeError #:nodoc:
18
+ end
19
+ class StageAlreadyDefinedError < RuntimeError #:nodoc:
20
+ end
21
+ class RendererNotSetError < RuntimeError #:nodoc:
22
+ end
23
+
24
+ require "ostruct"
25
+
26
+ # Structure for holding renderer options.
27
+ # Simplified version of HashWithIndifferentAccess
28
+ class Options < OpenStruct
29
+ # returns a Hash object. Use this if you need methods other than []
30
+ def to_hash
31
+ @table
32
+ end
33
+ # indifferent lookup of an attribute, e.g.
34
+ #
35
+ # options[:foo] == options["foo"]
36
+ def [](key)
37
+ send(key)
38
+ end
39
+
40
+ # Sets an attribute, with indifferent access.
41
+ #
42
+ # options[:foo] = "bar"
43
+ #
44
+ # options[:foo] == options["foo"] #=> true
45
+ # options["foo"] == options.foo #=> true
46
+ # options.foo #=> "bar"
47
+ def []=(key,value)
48
+ send("#{key}=",value)
49
+ end
50
+ end
51
+
52
+ # This module provides hooks into Ruport's formatting system.
53
+ # It is used to implement the as() method for all of Ruport's datastructures,
54
+ # as well as the renders_with and renders_as_* helpers.
55
+ #
56
+ # You can actually use this with any data structure, it will look for a
57
+ # renderable_data method to pass to the <tt>renderer</tt> you specify,
58
+ # but if that is not defined, it will pass <tt>self</tt>
59
+ #
60
+ # Examples:
61
+ #
62
+ # # Render Arrays with Ruport's Row Renderer
63
+ # class Array
64
+ # include Ruport::Renderer::Hooks
65
+ # renders_as_row
66
+ # end
67
+ #
68
+ # # >> [1,2,3].as(:csv)
69
+ # # => "1,2,3\n"
70
+ #
71
+ # # Render Hashes with Ruport's Row Renderer
72
+ # class Hash
73
+ # include Ruport::Renderer::Hooks
74
+ # renders_as_row
75
+ # attr_accessor :column_order
76
+ # def renderable_data
77
+ # column_order.map { |c| self[c] }
78
+ # end
79
+ # end
80
+ #
81
+ # # >> a = { :a => 1, :b => 2, :c => 3 }
82
+ # # >> a.column_order = [:b,:a,:c]
83
+ # # >> a.as(:csv)
84
+ # # => "2,1,3\n"
85
+ module Hooks
86
+ module ClassMethods
87
+
88
+ # Tells the class which renderer as() will forward to.
89
+ #
90
+ # usage:
91
+ #
92
+ # class MyStructure
93
+ # include Renderer::Hooks
94
+ # renders_with CustomRenderer
95
+ # end
96
+ #
97
+ # You can also specify default rendering options, which will be used
98
+ # if they are not overriden by the options passed to as()
99
+ #
100
+ # class MyStructure
101
+ # include Renderer::Hooks
102
+ # renders_with CustomRenderer, :font_size => 14
103
+ # end
104
+ def renders_with(renderer,opts={})
105
+ @renderer = renderer.name
106
+ @rendering_options=opts
107
+ end
108
+
109
+ # The default rendering options for a class, stored as a hash.
110
+ def rendering_options
111
+ @rendering_options
112
+ end
113
+
114
+ # Shortcut for renders_with(Ruport::Renderer::Table), you
115
+ # may wish to override this if you build a custom table renderer.
116
+ def renders_as_table(options={})
117
+ renders_with Ruport::Renderer::Table,options
118
+ end
119
+
120
+ # Shortcut for renders_with(Ruport::Renderer::Row), you
121
+ # may wish to override this if you build a custom row renderer.
122
+ def renders_as_row(options={})
123
+ renders_with Ruport::Renderer::Row, options
124
+ end
125
+
126
+ # Shortcut for renders_with(Ruport::Renderer::Group), you
127
+ # may wish to override this if you build a custom group renderer.
128
+ def renders_as_group(options={})
129
+ renders_with Ruport::Renderer::Group,options
130
+ end
131
+
132
+ # Shortcut for renders_with(Ruport::Renderer::Grouping), you
133
+ # may wish to override this if you build a custom grouping renderer.
134
+ def renders_as_grouping(options={})
135
+ renders_with Ruport::Renderer::Grouping,options
136
+ end
137
+
138
+ # The class of the renderer object for the base class.
139
+ #
140
+ # Example
141
+ #
142
+ # >> Ruport::Data::Table.renderer
143
+ # => Ruport::Renderer::Table
144
+ def renderer
145
+ return unless @renderer
146
+ @renderer.split("::").inject(Class) { |c,el| c.const_get(el) }
147
+ end
148
+ end
149
+
150
+ def self.included(base) #:nodoc:
151
+ base.extend(ClassMethods)
152
+ end
153
+
154
+ # Uses the Renderer specified by renders_with to generate formatted
155
+ # output. Passes the return value of the <tt>renderable_data</tt> method if
156
+ # the method is defined, otherwise passes <tt>self</tt> as :data
157
+ #
158
+ # The remaining options are converted to a Renderer::Options object and
159
+ # accessible in both the renderer and formatter.
160
+ #
161
+ # Example:
162
+ #
163
+ # table.as(:csv, :show_table_headers => false)
164
+ def as(format,options={})
165
+ raise RendererNotSetError unless self.class.renderer
166
+ unless self.class.renderer.formats.include?(format)
167
+ raise UnknownFormatError
168
+ end
169
+ self.class.renderer.render(format,
170
+ self.class.rendering_options.merge(options)) do |rend|
171
+ rend.data = respond_to?(:renderable_data) ? renderable_data : self
172
+ yield(rend) if block_given?
173
+ end
174
+ end
175
+ end
176
+
177
+
178
+ module AutoRunner #:nodoc:
179
+ # called automagically when the report is rendered. Uses the
180
+ # data collected from the earlier methods.
181
+ def _run_
182
+
183
+ # ensure all the required options have been set
184
+ unless self.class.required_options.nil?
185
+ self.class.required_options.each do |opt|
186
+ if options.__send__(opt).nil?
187
+ raise RequiredOptionNotSet, "Required option #{opt} not set"
188
+ end
189
+ end
190
+ end
191
+
192
+ prepare self.class.first_stage if self.class.first_stage
193
+
194
+ if formatter.respond_to?(:layout)
195
+ formatter.layout do execute_stages end
196
+ else
197
+ execute_stages
198
+ end
199
+
200
+ finalize self.class.final_stage if self.class.final_stage
201
+
202
+ end
203
+
204
+ def execute_stages
205
+ # call each stage to build the report
206
+ unless self.class.stages.nil?
207
+ self.class.stages.each do |stage|
208
+ self.send(:build,stage)
209
+ end
210
+ end
211
+ end
212
+ end
213
+
214
+ class << self
215
+
216
+ attr_accessor :first_stage,:final_stage,:required_options,:stages #:nodoc:
217
+
218
+ # Registers a hook to look for in the Formatter object when the render()
219
+ # method is called.
220
+ #
221
+ # Usage:
222
+ #
223
+ # class MyRenderer < Ruport::Renderer
224
+ # # other details omitted...
225
+ # finalize :apple
226
+ # end
227
+ #
228
+ # class MyFormatter < Ruport::Formatter
229
+ # renders :example, :for => MyRenderer
230
+ #
231
+ # # other details omitted...
232
+ #
233
+ # def finalize_apple
234
+ # # this method will be called when MyRenderer tries to render
235
+ # # the :example format
236
+ # end
237
+ # end
238
+ #
239
+ # If a formatter does not implement this hook, it is simply ignored.
240
+ def finalize(stage)
241
+ if final_stage
242
+ raise StageAlreadyDefinedError, 'final stage already defined'
243
+ end
244
+ self.final_stage = stage
245
+ end
246
+
247
+ # Registers a hook to look for in the Formatter object when the render()
248
+ # method is called.
249
+ #
250
+ # Usage:
251
+ #
252
+ # class MyRenderer < Ruport::Renderer
253
+ # # other details omitted...
254
+ # prepare :apple
255
+ # end
256
+ #
257
+ # class MyFormatter < Ruport::Formatter
258
+ # renders :example, :for => MyRenderer
259
+ #
260
+ # def prepare_apple
261
+ # # this method will be called when MyRenderer tries to render
262
+ # # the :example format
263
+ # end
264
+ #
265
+ # # other details omitted...
266
+ # end
267
+ #
268
+ # If a formatter does not implement this hook, it is simply ignored.
269
+ def prepare(stage)
270
+ if first_stage
271
+ raise StageAlreadyDefinedError, "prepare stage already defined"
272
+ end
273
+ self.first_stage = stage
274
+ end
275
+
276
+ # Registers hooks to look for in the Formatter object when the render()
277
+ # method is called.
278
+ #
279
+ # Usage:
280
+ #
281
+ # class MyRenderer < Ruport::Renderer
282
+ # # other details omitted...
283
+ # stage :apple,:banana
284
+ # end
285
+ #
286
+ # class MyFormatter < Ruport::Formatter
287
+ # renders :example, :for => MyRenderer
288
+ #
289
+ # def build_apple
290
+ # # this method will be called when MyRenderer tries to render
291
+ # # the :example format
292
+ # end
293
+ #
294
+ # def build_banana
295
+ # # this method will be called when MyRenderer tries to render
296
+ # # the :example format
297
+ # end
298
+ #
299
+ # # other details omitted...
300
+ # end
301
+ #
302
+ # If a formatter does not implement these hooks, they are simply ignored.
303
+ def stage(*stage_list)
304
+ self.stages ||= []
305
+ stage_list.each { |stage|
306
+ self.stages << stage.to_s
307
+ }
308
+ end
309
+
310
+ # Defines attribute writers for the Renderer::Options object shared
311
+ # between Renderer and Formatter
312
+ #
313
+ # usage:
314
+ #
315
+ # class MyRenderer < Ruport::Renderer
316
+ # option :font_size, :font_style
317
+ # # other details omitted
318
+ # end
319
+ def option(*opts)
320
+ opts.each do |opt|
321
+ o = opt
322
+ unless instance_methods(false).include?(o.to_s)
323
+ define_method(o) {
324
+ options.send(o.to_s)
325
+ }
326
+ end
327
+ opt = "#{opt}="
328
+ define_method(opt) {|t| options.send(opt, t) }
329
+ end
330
+ end
331
+
332
+ # Defines attribute writers for the Renderer::Options object shared
333
+ # between Renderer and Formatter. Will throw an error if the user does
334
+ # not provide values for these options upon rendering.
335
+ #
336
+ # usage:
337
+ #
338
+ # class MyRenderer < Ruport::Renderer
339
+ # required_option :employee_name, :address
340
+ # # other details omitted
341
+ # end
342
+ def required_option(*opts)
343
+ self.required_options ||= []
344
+ opts.each do |opt|
345
+ self.required_options << opt
346
+ option opt
347
+ end
348
+ end
349
+
350
+
351
+ # Lists the formatters that are currently registered on a renderer,
352
+ # as a hash keyed by format name.
353
+ #
354
+ # Example:
355
+ #
356
+ # >> Ruport::Renderer::Table.formats
357
+ # => {:html=>Ruport::Formatter::HTML,
358
+ # ?> :csv=>Ruport::Formatter::CSV,
359
+ # ?> :text=>Ruport::Formatter::Text,
360
+ # ?> :pdf=>Ruport::Formatter::PDF}
361
+ def formats
362
+ @formats ||= {}
363
+ end
364
+
365
+ # Builds up a renderer object, looks up the appropriate formatter,
366
+ # sets the data and options, and then does the following process:
367
+ #
368
+ # * If the renderer contains a module Helpers, mix it in to the instance.
369
+ # * If a block is given, yield the Renderer instance
370
+ # * If a setup() method is defined on the Renderer, call it
371
+ # * If the renderer has defined a run() method, call it, otherwise,
372
+ # include Renderer::AutoRunner. (you usually won't need a run() method )
373
+ # * call _run_ if it exists (This is provided by default, by AutoRunner)
374
+ # * return the results of formatter.output
375
+ #
376
+ # Note that the only time you will need a run() method is if you can't
377
+ # do what you need to via a helpers module or via setup()
378
+ #
379
+ # Please see the examples/ directory for custom renderer examples, because
380
+ # this is not nearly as complicated as it sounds in most cases.
381
+ def render(*args)
382
+ rend = build(*args) { |r|
383
+ yield(r) if block_given?
384
+ r.setup if r.respond_to? :setup
385
+ }
386
+ if rend.respond_to? :run
387
+ rend.run
388
+ else
389
+ include AutoRunner
390
+ end
391
+ rend._run_ if rend.respond_to? :_run_
392
+ return rend.formatter.output
393
+ end
394
+
395
+ # Allows you to set class_wide default options
396
+ #
397
+ # Example:
398
+ #
399
+ # options { |o| o.style = :justified }
400
+ #
401
+ def options
402
+ @options ||= Ruport::Renderer::Options.new
403
+ yield(@options) if block_given?
404
+
405
+ return @options
406
+ end
407
+
408
+ # Creates a new instance of the renderer and sets it to use the specified
409
+ # formatter (by name). If a block is given, the renderer instance is
410
+ # yielded.
411
+ #
412
+ # Returns the renderer instance.
413
+ #
414
+ def build(*args)
415
+ rend = self.new
416
+
417
+ rend.send(:use_formatter,args[0])
418
+ rend.send(:options=, options.dup)
419
+ if rend.class.const_defined? :Helpers
420
+ rend.formatter.extend(rend.class.const_get(:Helpers))
421
+ end
422
+ if args[1].kind_of?(Hash)
423
+ d = args[1].delete(:data)
424
+ rend.data = d if d
425
+ args[1].each {|k,v| rend.options.send("#{k}=",v) }
426
+ end
427
+
428
+ yield(rend) if block_given?
429
+ return rend
430
+ end
431
+
432
+ private
433
+
434
+ # Allows you to register a format with the renderer.
435
+ #
436
+ # example:
437
+ #
438
+ # class MyFormatter < Ruport::Formatter
439
+ # # formatter code ...
440
+ # SomeRenderer.add_format self, :my_formatter
441
+ # end
442
+ #
443
+ def add_format(format,name=nil)
444
+ formats[name] = format
445
+ end
446
+
447
+ end
448
+
449
+ # The name of format being used
450
+ attr_accessor :format
451
+
452
+ # The formatter object being used
453
+ attr_writer :formatter
454
+
455
+ # The +data+ that has been passed to the active formatter.
456
+ def data
457
+ formatter.data
458
+ end
459
+
460
+ # Sets +data+ attribute on the active formatter.
461
+ def data=(val)
462
+ formatter.data = val.dup
463
+ end
464
+
465
+ # Renderer::Options object which is shared with the current formatter.
466
+ def options
467
+ yield(formatter.options) if block_given?
468
+ formatter.options
469
+ end
470
+
471
+ # If an IO object is given, Formatter#output will use it instead of
472
+ # the default String. For Ruport's core renderers, we technically
473
+ # can use any object that supports the << method, but it's meant
474
+ # for IO objects such as File or STDOUT
475
+ #
476
+ def io=(obj)
477
+ options.io=obj
478
+ end
479
+
480
+ # When no block is given, returns active formatter.
481
+ #
482
+ # When a block is given with a block variable, sets the block variable to the
483
+ # formatter.
484
+ #
485
+ # When a block is given without block variables, instance_evals the block
486
+ # within the context of the formatter.
487
+ #
488
+ def formatter(&block)
489
+ if block.nil?
490
+ return @formatter
491
+ elsif block.arity > 0
492
+ yield(@formatter)
493
+ else
494
+ @formatter.instance_eval(&block)
495
+ end
496
+ return @formatter
497
+ end
498
+
499
+ # Provides a shortcut to render() to allow
500
+ # render(:csv) to become render_csv
501
+ #
502
+ def self.method_missing(id,*args,&block)
503
+ id.to_s =~ /^render_(.*)/
504
+ unless args[0].kind_of? Hash
505
+ args = [ (args[1] || {}).merge(:data => args[0]) ]
506
+ end
507
+ $1 ? render($1.to_sym,*args,&block) : super
508
+ end
509
+
510
+ private
511
+
512
+ def prepare(name)
513
+ maybe "prepare_#{name}"
514
+ end
515
+
516
+ def build(names,prefix=nil)
517
+ return maybe("build_#{names}") if prefix.nil?
518
+ names.each { |n| maybe "build_#{prefix}_#{n}" }
519
+ end
520
+
521
+ def finalize(name)
522
+ maybe "finalize_#{name}"
523
+ end
524
+
525
+ def maybe(something)
526
+ formatter.send something if formatter.respond_to? something
527
+ end
528
+
529
+ def options=(o)
530
+ formatter.options = o
531
+ end
532
+
533
+ # selects a formatter for use by format name
534
+ def use_formatter(format)
535
+ self.formatter = self.class.formats[format].new
536
+ self.formatter.format = format
537
+ end
538
+
539
+ end
540
+
541
+ require "ruport/renderer/table"
542
+ require "ruport/renderer/grouping"
data/lib/ruport.rb CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  module Ruport #:nodoc:#
14
14
 
15
- VERSION = "1.0.1"
15
+ VERSION = "1.0.2"
16
16
 
17
17
  class FormatterError < RuntimeError #:nodoc:
18
18
  end
@@ -70,10 +70,15 @@ module Ruport #:nodoc:#
70
70
  rescue LoadError # If we're not on Windows try...
71
71
  # A Unix savvy method to fetch the console columns, and rows.
72
72
  def terminal_size
73
- size = `stty size 2>/dev/null`.split.map { |x| x.to_i }.reverse
74
- return $? == 0 ? size : [80,24]
73
+ size = if /solaris/ =~ RUBY_PLATFORM
74
+ output = `stty`
75
+ [output.match('columns = (\d+)')[1].to_i,
76
+ output.match('rows = (\d+)')[1].to_i]
77
+ else
78
+ `stty size`.split.map { |x| x.to_i }.reverse
79
+ end
80
+ return $? == 0 ? size : [80,24]
75
81
  end
76
-
77
82
  end
78
83
 
79
84
  def terminal_width
@@ -147,6 +147,11 @@ class TestFormatterWithLayout < Test::Unit::TestCase
147
147
  assert_equal "---\nheader\nbody\nfooter\n---\n",
148
148
  VanillaRenderer.render_text_with_layout
149
149
  end
150
+
151
+ def test_layout_disabled
152
+ assert_equal "header\nbody\nfooter\n",
153
+ VanillaRenderer.render_text_with_layout(:layout => false)
154
+ end
150
155
 
151
156
  end
152
157
 
@@ -0,0 +1,512 @@
1
+ require 'test/helpers'
2
+
3
+ ###########################################################################
4
+ #
5
+ # NOTE:
6
+ #
7
+ # As it stands, we haven't found a more clever way to test the formatting
8
+ # system than to just create a bunch of renderers and basic formatters for
9
+ # different concepts we're trying to test. Patches and ideas welcome:
10
+ #
11
+ # list.rubyreports.org
12
+ ############################################################################
13
+
14
+ #============================================================================
15
+ # These two renderers represent the two styles that can be used when defining
16
+ # renderers in Ruport. The OldSchoolRenderer approach has largely been
17
+ # deprecated, but still has uses in edge cases that we need to support.
18
+ #============================================================================
19
+
20
+ class OldSchoolRenderer < Ruport::Renderer
21
+
22
+ def run
23
+ formatter do
24
+ build_header
25
+ build_body
26
+ build_footer
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+ class VanillaRenderer < Ruport::Renderer
33
+ stage :header,:body,:footer
34
+ end
35
+
36
+
37
+ # This formatter implements some junk output so we can be sure
38
+ # that the hooks are being set up right. Perhaps these could
39
+ # be replaced by mock objects in the future.
40
+ class DummyText < Ruport::Formatter
41
+
42
+ renders :text, :for => OldSchoolRenderer
43
+
44
+ def prepare_document
45
+ output << "p"
46
+ end
47
+
48
+ def build_header
49
+ output << "header\n"
50
+ end
51
+
52
+ def build_body
53
+ output << "body\n"
54
+ end
55
+
56
+ def build_footer
57
+ output << "footer\n"
58
+ end
59
+
60
+ def finalize_document
61
+ output << "f"
62
+ end
63
+ end
64
+
65
+
66
+ class TestRenderer < Test::Unit::TestCase
67
+
68
+ def test_trivial
69
+ actual = OldSchoolRenderer.render(:text)
70
+ assert_equal "header\nbody\nfooter\n", actual
71
+ end
72
+
73
+ def test_using_io
74
+ require "stringio"
75
+ out = StringIO.new
76
+ a = OldSchoolRenderer.render(:text) { |r| r.io = out }
77
+ out.rewind
78
+ assert_equal "header\nbody\nfooter\n", out.read
79
+ assert_equal "", out.read
80
+ end
81
+
82
+ def test_formats
83
+ assert_equal( {}, Ruport::Renderer.formats )
84
+ assert_equal( { :text => DummyText },OldSchoolRenderer.formats )
85
+ end
86
+
87
+ def test_method_missing
88
+ actual = OldSchoolRenderer.render_text
89
+ assert_equal "header\nbody\nfooter\n", actual
90
+ end
91
+
92
+ def test_formatter
93
+ # normal instance mode
94
+ rend = OldSchoolRenderer.new
95
+ rend.send(:use_formatter,:text)
96
+
97
+ assert_kind_of Ruport::Formatter, rend.formatter
98
+ assert_kind_of DummyText, rend.formatter
99
+
100
+ # render mode
101
+ OldSchoolRenderer.render_text do |r|
102
+ assert_kind_of Ruport::Formatter, r.formatter
103
+ assert_kind_of DummyText, r.formatter
104
+ end
105
+
106
+ assert_equal "body\n", rend.formatter { build_body }.output
107
+
108
+ rend.formatter.clear_output
109
+ assert_equal "", rend.formatter.output
110
+ end
111
+
112
+ def test_options_act_like_indifferent_hash
113
+ opts = Ruport::Renderer::Options.new
114
+ opts.foo = "bar"
115
+ assert_equal "bar", opts[:foo]
116
+ assert_equal "bar", opts["foo"]
117
+
118
+ opts["f"] = "bar"
119
+ assert_equal "bar", opts[:f]
120
+ assert_equal "bar", opts.f
121
+ assert_equal "bar", opts["f"]
122
+
123
+ opts[:apple] = "banana"
124
+ assert_equal "banana", opts.apple
125
+ assert_equal "banana", opts["apple"]
126
+ assert_equal "banana", opts[:apple]
127
+ end
128
+
129
+ end
130
+
131
+
132
+ class TestFormatterWithLayout < Test::Unit::TestCase
133
+ # This formatter is meant to check out a special case in Ruport's renderer,
134
+ # in which a layout method is called and yielded to when defined
135
+ class WithLayout < DummyText
136
+ renders :text_with_layout, :for => VanillaRenderer
137
+
138
+ def layout
139
+ output << "---\n"
140
+ yield
141
+ output << "---\n"
142
+ end
143
+
144
+ end
145
+
146
+ def test_layout
147
+ assert_equal "---\nheader\nbody\nfooter\n---\n",
148
+ VanillaRenderer.render_text_with_layout
149
+ end
150
+
151
+ end
152
+
153
+
154
+ class TestRendererWithManyHooks < Test::Unit::TestCase
155
+ # This provides a way to check several hooks that Renderer supports
156
+ class RendererWithManyHooks < Ruport::Renderer
157
+
158
+ add_format DummyText, :text
159
+
160
+ prepare :document
161
+
162
+ option :subtitle, :subsubtitle
163
+
164
+ stage :header
165
+ stage :body
166
+ stage :footer
167
+
168
+ finalize :document
169
+
170
+ def setup
171
+ options.apple = true
172
+ end
173
+
174
+ end
175
+
176
+ def test_hash_options_setters
177
+ a = RendererWithManyHooks.render(:text, :subtitle => "foo",
178
+ :subsubtitle => "bar") { |r|
179
+ assert_equal "foo", r.options.subtitle
180
+ assert_equal "bar", r.options.subsubtitle
181
+ }
182
+ end
183
+
184
+ def test_data_accessors
185
+ a = RendererWithManyHooks.render(:text, :data => [1,2,4]) { |r|
186
+ assert_equal [1,2,4], r.data
187
+ }
188
+
189
+ b = RendererWithManyHooks.render_text(%w[a b c]) { |r|
190
+ assert_equal %w[a b c], r.data
191
+ }
192
+
193
+ c = RendererWithManyHooks.render_text(%w[a b f],:snapper => :red) { |r|
194
+ assert_equal %w[a b f], r.data
195
+ assert_equal :red, r.options.snapper
196
+ }
197
+ end
198
+
199
+ def test_stage_helper
200
+ assert RendererWithManyHooks.stages.include?('body')
201
+ end
202
+
203
+ def test_finalize_helper
204
+ assert_equal :document, RendererWithManyHooks.final_stage
205
+ end
206
+
207
+ def test_prepare_helper
208
+ assert_equal :document, RendererWithManyHooks.first_stage
209
+ end
210
+
211
+ def test_finalize_again
212
+ assert_raise(Ruport::Renderer::StageAlreadyDefinedError) {
213
+ RendererWithManyHooks.finalize :report
214
+ }
215
+ end
216
+
217
+ def test_prepare_again
218
+ assert_raise(Ruport::Renderer::StageAlreadyDefinedError) {
219
+ RendererWithManyHooks.prepare :foo
220
+ }
221
+ end
222
+
223
+ def test_renderer_using_helpers
224
+ actual = RendererWithManyHooks.render(:text)
225
+ assert_equal "pheader\nbody\nfooter\nf", actual
226
+
227
+ actual = RendererWithManyHooks.render_text
228
+ assert_equal "pheader\nbody\nfooter\nf", actual
229
+ end
230
+
231
+ def test_option_helper
232
+ RendererWithManyHooks.render_text do |r|
233
+ r.subtitle = "Test Report"
234
+ assert_equal "Test Report", r.options.subtitle
235
+ end
236
+ end
237
+
238
+ def test_required_option_helper
239
+ a = RendererWithManyHooks.dup
240
+ a.required_option :title
241
+
242
+ a.render_text do |r|
243
+ r.title = "Test Report"
244
+ assert_equal "Test Report", r.options.title
245
+ end
246
+
247
+ end
248
+
249
+ def test_without_required_option
250
+ a = RendererWithManyHooks.dup
251
+ a.required_option :title
252
+
253
+ assert_raise(Ruport::Renderer::RequiredOptionNotSet) { a.render(:text) }
254
+ end
255
+
256
+ end
257
+
258
+
259
+ class TestRendererWithRunHook < Test::Unit::TestCase
260
+
261
+ class RendererWithRunHook < Ruport::Renderer
262
+
263
+ include AutoRunner
264
+
265
+ add_format DummyText, :text
266
+
267
+ required_option :foo,:bar
268
+ stage :header
269
+ stage :body
270
+ stage :footer
271
+
272
+ def run
273
+ formatter.output << "|"
274
+ end
275
+
276
+ end
277
+
278
+ def test_renderer_with_run_hooks
279
+ assert_equal "|header\nbody\nfooter\n",
280
+ RendererWithRunHook.render_text(:foo => "bar",:bar => "baz")
281
+ end
282
+
283
+ end
284
+
285
+
286
+ class TestRendererWithHelperModule < Test::Unit::TestCase
287
+
288
+ class RendererWithHelperModule < VanillaRenderer
289
+
290
+ add_format DummyText, :stub
291
+
292
+ module Helpers
293
+ def say_hello
294
+ "Hello Dolly"
295
+ end
296
+ end
297
+ end
298
+
299
+ def test_renderer_helper_module
300
+ RendererWithHelperModule.render_stub do |r|
301
+ assert_equal "Hello Dolly", r.formatter.say_hello
302
+ end
303
+ end
304
+ end
305
+
306
+
307
+ class TestMultiPurposeFormatter < Test::Unit::TestCase
308
+ # This provides a way to check the multi-format hooks for the Renderer
309
+ class MultiPurposeFormatter < Ruport::Formatter
310
+
311
+ renders [:html,:text], :for => VanillaRenderer
312
+
313
+ def build_header
314
+ a = 10
315
+
316
+ text { output << "Foo: #{a}\n" }
317
+ html { output << "<b>Foo: #{a}</b>\n" }
318
+ end
319
+
320
+ def build_body
321
+ html { output << "<pre>\n" }
322
+ output << options.body_text
323
+ html { output << "\n</pre>\n" }
324
+ end
325
+
326
+ end
327
+
328
+ def test_multi_purpose
329
+ text = VanillaRenderer.render_text(:body_text => "foo")
330
+ assert_equal "Foo: 10\nfoo", text
331
+ html = VanillaRenderer.render_html(:body_text => "bar")
332
+ assert_equal "<b>Foo: 10</b>\n<pre>\nbar\n</pre>\n",html
333
+ end
334
+
335
+
336
+ def test_method_missing_hack_formatter
337
+ assert_equal [:html,:text], MultiPurposeFormatter.formats
338
+
339
+ a = MultiPurposeFormatter.new
340
+ a.format = :html
341
+
342
+ visited = false
343
+ a.html { visited = true }
344
+
345
+ assert visited
346
+
347
+ visited = false
348
+ a.text { visited = true }
349
+ assert !visited
350
+
351
+ assert_raises(NoMethodError) do
352
+ a.pdf { 'do nothing' }
353
+ end
354
+ end
355
+
356
+ end
357
+
358
+
359
+ class TestFormatterErbHelper < Test::Unit::TestCase
360
+ class ErbFormatter < Ruport::Formatter
361
+
362
+ renders :terb, :for => VanillaRenderer
363
+
364
+ def build_body
365
+ # demonstrate local binding
366
+ @foo = "bar"
367
+ if options.binding
368
+ output << erb("Binding Override: <%= reverse %>",
369
+ :binding => options.binding)
370
+ else
371
+ output << erb("Default Binding: <%= @foo %>")
372
+ end
373
+ end
374
+
375
+ end
376
+
377
+ #FIXME: need to test file
378
+
379
+ def test_self_bound
380
+ assert_equal "Default Binding: bar", VanillaRenderer.render_terb
381
+ end
382
+
383
+ def test_custom_bound
384
+ a = [1,2,3]
385
+ arr_binding = a.instance_eval { binding }
386
+ assert_equal "Binding Override: 321",
387
+ VanillaRenderer.render_terb(:binding => arr_binding)
388
+ end
389
+ end
390
+
391
+
392
+ class TestOptionReaders < Test::Unit::TestCase
393
+
394
+ class RendererForCheckingOptionReaders < Ruport::Renderer
395
+ option :foo
396
+ end
397
+
398
+ class RendererForCheckingPassivity < Ruport::Renderer
399
+ def foo
400
+ "apples"
401
+ end
402
+ option :foo
403
+ end
404
+
405
+ def setup
406
+ @renderer = RendererForCheckingOptionReaders.new
407
+ @renderer.formatter = Ruport::Formatter.new
408
+
409
+ @passive = RendererForCheckingPassivity.new
410
+ @passive.formatter = Ruport::Formatter.new
411
+ end
412
+
413
+ def test_options_are_readable
414
+ @renderer.foo = 5
415
+ assert_equal 5, @renderer.foo
416
+ end
417
+
418
+ def test_methods_are_not_overridden
419
+ @passive.foo = 5
420
+ assert_equal "apples", @passive.foo
421
+ assert_equal 5, @passive.options.foo
422
+ assert_equal 5, @passive.formatter.options.foo
423
+ end
424
+
425
+ end
426
+
427
+ class TestSetupOrdering < Test::Unit::TestCase
428
+
429
+ class RendererWithSetup < Ruport::Renderer
430
+ option :foo
431
+ stage :bar
432
+ def setup
433
+ foo.capitalize!
434
+ end
435
+ end
436
+
437
+ class BasicFormatter < Ruport::Formatter
438
+ renders :text, :for => RendererWithSetup
439
+
440
+ def build_bar
441
+ output << options.foo
442
+ end
443
+ end
444
+
445
+ def test_render_hash_options_should_be_called_before_setup
446
+ assert_equal "Hello", RendererWithSetup.render_text(:foo => "hello")
447
+ end
448
+
449
+ def test_render_block_should_be_called_before_setup
450
+ assert_equal "Hello",
451
+ RendererWithSetup.render_text { |r| r.foo = "hello" }
452
+ end
453
+
454
+ end
455
+
456
+ class TestRendererHooks < Test::Unit::TestCase
457
+
458
+ context "when renderable_data omitted" do
459
+
460
+ require "mocha"
461
+
462
+ class DummyObject
463
+ include Ruport::Renderer::Hooks
464
+ renders_as_table
465
+ end
466
+
467
+ def specify_should_return_self
468
+ a = DummyObject.new
469
+ rend = mock("renderer")
470
+ rend.expects(:data=).with(a)
471
+ Ruport::Renderer::Table.expects(:render).with(:csv,{}).yields(rend)
472
+ a.as(:csv)
473
+ end
474
+
475
+ end
476
+
477
+ context "when using renderable_data" do
478
+
479
+ class DummyObject2
480
+ include Ruport::Renderer::Hooks
481
+ renders_as_table
482
+
483
+ def renderable_data
484
+ 1
485
+ end
486
+ end
487
+
488
+ def specify_should_return_results_of_renderable_data
489
+ a = DummyObject2.new
490
+ rend = mock("renderer")
491
+ rend.expects(:data=).with(1)
492
+ Ruport::Renderer::Table.expects(:render).with(:csv,{}).yields(rend)
493
+ a.as(:csv)
494
+ end
495
+
496
+ class DummyObject3
497
+ include Ruport::Renderer::Hooks
498
+ renders_as_table
499
+
500
+ def renderable_data
501
+ raise ArgumentError
502
+ end
503
+ end
504
+
505
+ def specify_should_not_mask_errors
506
+ assert_raises(ArgumentError) { DummyObject3.new.as(:csv) }
507
+ end
508
+
509
+
510
+ end
511
+
512
+ end
data/test/table_test.rb CHANGED
@@ -82,6 +82,14 @@ class TestTable < Test::Unit::TestCase
82
82
  table.sub_table(2..-1)
83
83
 
84
84
  end
85
+
86
+ def test_subtable_records_have_correct_data
87
+ table = [ [1,2,3,4],[5,6,7,9],
88
+ [10,11,12,13],[14,15,16,17] ].to_table(%w[a b c d])
89
+ sub = table.sub_table(%w[b c d]) {|r| r.a == 1 }
90
+ assert_equal({"b"=>2, "c"=>3, "d"=>4}, sub[0].data)
91
+ assert_equal(["b", "c", "d"], sub[0].attributes)
92
+ end
85
93
 
86
94
  def test_reduce
87
95
  table = [ [1,2,3,4],[5,6,7,9],
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: ruport
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.0.1
7
- date: 2007-06-12 00:00:00 -04:00
6
+ version: 1.0.2
7
+ date: 2007-08-22 00:00:00 -04:00
8
8
  summary: A generalized Ruby report generation and templating engine.
9
9
  require_paths:
10
10
  - lib
@@ -30,106 +30,108 @@ authors:
30
30
  - Gregory Brown
31
31
  files:
32
32
  - examples/btree
33
- - examples/btree/commaleon
34
- - examples/btree/commaleon/sample_data
35
- - examples/btree/commaleon/sample_data/ticket_count.csv
36
- - examples/btree/commaleon/sample_data/ticket_count2.csv
37
- - examples/btree/commaleon/commaleon.rb
38
- - examples/data
39
- - examples/data/tattle.dump
40
- - examples/row_renderer.rb
41
- - examples/RWEmerson.jpg
42
33
  - examples/centered_pdf_text_box.rb
43
- - examples/tattle_rubygems_version.rb
44
- - examples/trac_ticket_status.rb
34
+ - examples/data
35
+ - examples/example.csv
45
36
  - examples/line_plotter.rb
46
- - examples/png_embed.rb
47
37
  - examples/pdf_report_with_common_base.rb
48
- - examples/example.csv
49
- - examples/tattle_ruby_version.rb
38
+ - examples/png_embed.rb
50
39
  - examples/roadmap.png
40
+ - examples/row_renderer.rb
41
+ - examples/RWEmerson.jpg
51
42
  - examples/simple_pdf_lines.rb
43
+ - examples/tattle_ruby_version.rb
44
+ - examples/tattle_rubygems_version.rb
45
+ - examples/trac_ticket_status.rb
46
+ - examples/btree/commaleon
47
+ - examples/btree/commaleon/commaleon.rb
48
+ - examples/btree/commaleon/sample_data
49
+ - examples/btree/commaleon/sample_data/ticket_count.csv
50
+ - examples/btree/commaleon/sample_data/ticket_count2.csv
51
+ - examples/data/tattle.dump
52
52
  - lib/ruport
53
- - lib/ruport/formatter
54
- - lib/ruport/formatter/html.rb
55
- - lib/ruport/formatter/text.rb
56
- - lib/ruport/formatter/pdf.rb
57
- - lib/ruport/formatter/csv.rb
58
- - lib/ruport/query
59
- - lib/ruport/query/sql_split.rb
60
- - lib/ruport/renderer
61
- - lib/ruport/renderer/table.rb
62
- - lib/ruport/renderer/grouping.rb
53
+ - lib/ruport.rb
54
+ - lib/uport.rb
55
+ - lib/ruport/acts_as_reportable.rb
63
56
  - lib/ruport/data
64
- - lib/ruport/data/record.rb
65
- - lib/ruport/data/table.rb
66
- - lib/ruport/data/grouping.rb
57
+ - lib/ruport/data.rb
67
58
  - lib/ruport/extensions.rb
59
+ - lib/ruport/formatter
68
60
  - lib/ruport/formatter.rb
61
+ - lib/ruport/query
69
62
  - lib/ruport/query.rb
63
+ - lib/ruport/renderer
70
64
  - lib/ruport/renderer.rb
71
- - lib/ruport/data.rb
72
- - lib/ruport/acts_as_reportable.rb
73
- - lib/uport.rb
74
- - lib/ruport.rb
75
- - test/samples
76
- - test/samples/test.yaml
77
- - test/samples/query_test.sql
78
- - test/samples/data.csv
79
- - test/samples/data.tsv
80
- - test/samples/erb_test.sql
81
- - test/samples/ticket_count.csv
82
- - test/samples/ruport_test.sql
83
- - test/samples/addressbook.csv
84
- - test/samples/dates.csv
85
- - test/samples/test.sql
65
+ - lib/ruport/renderer.rb.orig
66
+ - lib/ruport/data/grouping.rb
67
+ - lib/ruport/data/record.rb
68
+ - lib/ruport/data/table.rb
69
+ - lib/ruport/formatter/csv.rb
70
+ - lib/ruport/formatter/html.rb
71
+ - lib/ruport/formatter/pdf.rb
72
+ - lib/ruport/formatter/text.rb
73
+ - lib/ruport/query/sql_split.rb
74
+ - lib/ruport/renderer/grouping.rb
75
+ - lib/ruport/renderer/table.rb
76
+ - test/acts_as_reportable_test.rb
86
77
  - test/csv_formatter_test.rb
87
- - test/record_test.rb
78
+ - test/grouping_test.rb
88
79
  - test/helpers.rb
80
+ - test/html_formatter_test.rb
81
+ - test/pdf_formatter_test.rb
89
82
  - test/query_test.rb
83
+ - test/record_test.rb
90
84
  - test/renderer_test.rb
91
- - test/html_formatter_test.rb
85
+ - test/renderer_test.rb.orig
86
+ - test/samples
87
+ - test/sql_split_test.rb
92
88
  - test/table_test.rb
93
89
  - test/text_formatter_test.rb
94
- - test/grouping_test.rb
95
- - test/sql_split_test.rb
96
- - test/acts_as_reportable_test.rb
97
- - test/pdf_formatter_test.rb
90
+ - test/samples/addressbook.csv
91
+ - test/samples/data.csv
92
+ - test/samples/data.tsv
93
+ - test/samples/dates.csv
94
+ - test/samples/erb_test.sql
95
+ - test/samples/query_test.sql
96
+ - test/samples/ruport_test.sql
97
+ - test/samples/test.sql
98
+ - test/samples/test.yaml
99
+ - test/samples/ticket_count.csv
100
+ - util/bench/data
98
101
  - util/bench/formatter
99
- - util/bench/formatter/bench_html.rb
100
- - util/bench/formatter/bench_text.rb
101
- - util/bench/formatter/bench_pdf.rb
102
- - util/bench/formatter/bench_csv.rb
103
102
  - util/bench/samples
104
- - util/bench/samples/tattle.csv
105
- - util/bench/data
103
+ - util/bench/data/record
106
104
  - util/bench/data/table
105
+ - util/bench/data/record/bench_as_vs_to.rb
106
+ - util/bench/data/record/bench_constructor.rb
107
+ - util/bench/data/record/bench_indexing.rb
108
+ - util/bench/data/record/bench_reorder.rb
109
+ - util/bench/data/record/bench_to_a.rb
107
110
  - util/bench/data/table/bench_column_manip.rb
111
+ - util/bench/data/table/bench_dup.rb
108
112
  - util/bench/data/table/bench_init.rb
109
113
  - util/bench/data/table/bench_manip.rb
110
- - util/bench/data/table/bench_dup.rb
111
- - util/bench/data/record
112
- - util/bench/data/record/bench_reorder.rb
113
- - util/bench/data/record/bench_to_a.rb
114
- - util/bench/data/record/bench_constructor.rb
115
- - util/bench/data/record/bench_indexing.rb
116
- - util/bench/data/record/bench_as_vs_to.rb
114
+ - util/bench/formatter/bench_csv.rb
115
+ - util/bench/formatter/bench_html.rb
116
+ - util/bench/formatter/bench_pdf.rb
117
+ - util/bench/formatter/bench_text.rb
118
+ - util/bench/samples/tattle.csv
117
119
  - Rakefile
118
120
  - README
119
121
  - LICENSE
120
122
  - AUTHORS
121
123
  test_files:
124
+ - test/acts_as_reportable_test.rb
122
125
  - test/csv_formatter_test.rb
123
- - test/record_test.rb
126
+ - test/grouping_test.rb
127
+ - test/html_formatter_test.rb
128
+ - test/pdf_formatter_test.rb
124
129
  - test/query_test.rb
130
+ - test/record_test.rb
125
131
  - test/renderer_test.rb
126
- - test/html_formatter_test.rb
132
+ - test/sql_split_test.rb
127
133
  - test/table_test.rb
128
134
  - test/text_formatter_test.rb
129
- - test/grouping_test.rb
130
- - test/sql_split_test.rb
131
- - test/acts_as_reportable_test.rb
132
- - test/pdf_formatter_test.rb
133
135
  rdoc_options:
134
136
  - --title
135
137
  - Ruport Documentation