ruport 1.0.1 → 1.0.2
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/Rakefile +1 -1
- data/examples/trac_ticket_status.rb +1 -1
- data/lib/ruport/data/table.rb +2 -2
- data/lib/ruport/formatter.rb +3 -1
- data/lib/ruport/renderer.rb +2 -2
- data/lib/ruport/renderer.rb.orig +542 -0
- data/lib/ruport.rb +9 -4
- data/test/renderer_test.rb +5 -0
- data/test/renderer_test.rb.orig +512 -0
- data/test/table_test.rb +8 -0
- metadata +71 -69
data/Rakefile
CHANGED
@@ -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|
|
35
|
+
next unless title =~ /Ticket.*(created|closed|reopened)/
|
36
36
|
table << { :title => title,
|
37
37
|
:date => Date.parse((r/"pubdate").innerHTML) }
|
38
38
|
end
|
data/lib/ruport/data/table.rb
CHANGED
@@ -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',:
|
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
|
data/lib/ruport/formatter.rb
CHANGED
@@ -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,
|
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
|
data/lib/ruport/renderer.rb
CHANGED
@@ -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.
|
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 =
|
74
|
-
|
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
|
data/test/renderer_test.rb
CHANGED
@@ -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.
|
7
|
-
date: 2007-
|
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/
|
44
|
-
- examples/
|
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/
|
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
|
54
|
-
- lib/
|
55
|
-
- lib/ruport/
|
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
|
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/
|
72
|
-
- lib/ruport/
|
73
|
-
- lib/
|
74
|
-
- lib/ruport.rb
|
75
|
-
-
|
76
|
-
-
|
77
|
-
-
|
78
|
-
-
|
79
|
-
-
|
80
|
-
-
|
81
|
-
-
|
82
|
-
- test/
|
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/
|
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/
|
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/
|
95
|
-
- test/
|
96
|
-
- test/
|
97
|
-
- test/
|
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/
|
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/
|
111
|
-
- util/bench/
|
112
|
-
- util/bench/
|
113
|
-
- util/bench/
|
114
|
-
- util/bench/
|
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/
|
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/
|
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
|