ruport-util 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +2 -1
- data/bin/rope +12 -0
- data/lib/ruport/util/generator.rb +280 -0
- data/lib/ruport/util/mailer.rb +1 -0
- data/lib/ruport/util/report.rb +405 -0
- data/lib/ruport/util.rb +7 -0
- data/test/samples/foo.rtxt +3 -0
- data/test/test_report.rb +218 -0
- metadata +19 -13
data/Rakefile
CHANGED
@@ -18,7 +18,7 @@ end
|
|
18
18
|
|
19
19
|
spec = Gem::Specification.new do |spec|
|
20
20
|
spec.name = "ruport-util"
|
21
|
-
spec.version = "0.
|
21
|
+
spec.version = "0.4.0"
|
22
22
|
spec.platform = Gem::Platform::RUBY
|
23
23
|
spec.summary = "A set of tools and helper libs for Ruby Reports"
|
24
24
|
spec.files = Dir.glob("{example,lib,test,bin}/**/**/*") +
|
@@ -28,6 +28,7 @@ spec = Gem::Specification.new do |spec|
|
|
28
28
|
|
29
29
|
spec.test_files = Dir[ "test/test_*.rb" ]
|
30
30
|
spec.bindir = "bin"
|
31
|
+
spec.executables = FileList["rope"]
|
31
32
|
spec.has_rdoc = true
|
32
33
|
spec.extra_rdoc_files = %w{}
|
33
34
|
spec.rdoc_options << '--title' << 'Ruport Documentation' #<<
|
data/bin/rope
ADDED
@@ -0,0 +1,280 @@
|
|
1
|
+
module Ruport
|
2
|
+
class Generator
|
3
|
+
extend FileUtils
|
4
|
+
|
5
|
+
module Helpers
|
6
|
+
def format_class_name(string)
|
7
|
+
string.downcase.split("_").map { |s| s.capitalize }.join
|
8
|
+
end
|
9
|
+
|
10
|
+
def check_for_files
|
11
|
+
if File.exist? "lib/reports/#{ARGV[1]}.rb"
|
12
|
+
raise "Report #{ARGV[1]} exists!"
|
13
|
+
end
|
14
|
+
|
15
|
+
if File.exist? "lib/renderers/#{ARGV[1]}.rb"
|
16
|
+
raise "Renderer #{ARGV[1]} exists!"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
begin
|
22
|
+
require "rubygems"
|
23
|
+
rescue LoadError
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
require "ruport"
|
27
|
+
require "ruport/util"
|
28
|
+
|
29
|
+
def self.build(proj)
|
30
|
+
@project = proj
|
31
|
+
build_directory_structure
|
32
|
+
build_init
|
33
|
+
build_config
|
34
|
+
build_utils
|
35
|
+
build_rakefile
|
36
|
+
puts "\nSuccessfully generated project: #{proj}"
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def self.build_init
|
41
|
+
m = "#{project}/lib/init.rb"
|
42
|
+
puts " #{m}"
|
43
|
+
File.open(m,"w") { |f| f << INIT }
|
44
|
+
end
|
45
|
+
|
46
|
+
# Generates a trivial rakefile for use with Ruport.
|
47
|
+
def self.build_rakefile
|
48
|
+
m = "#{project}/Rakefile"
|
49
|
+
puts " #{m}"
|
50
|
+
File.open(m,"w") { |f| f << RAKEFILE }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Generates the build.rb and sql_exec.rb utilities
|
54
|
+
def self.build_utils
|
55
|
+
|
56
|
+
m = "#{project}/util/build"
|
57
|
+
puts " #{m}"
|
58
|
+
File.open(m,"w") { |f| f << BUILD }
|
59
|
+
chmod(0755, m)
|
60
|
+
|
61
|
+
m = "#{project}/util/sql_exec"
|
62
|
+
puts " #{m}"
|
63
|
+
File.open(m,"w") { |f| f << SQL_EXEC }
|
64
|
+
chmod(0755, m)
|
65
|
+
end
|
66
|
+
|
67
|
+
# sets up the basic directory layout for a Ruport application
|
68
|
+
def self.build_directory_structure
|
69
|
+
mkdir project
|
70
|
+
puts "creating directories.."
|
71
|
+
%w[ test config output data lib lib/reports
|
72
|
+
lib/renderers templates sql log util].each do |d|
|
73
|
+
m="#{project}/#{d}"
|
74
|
+
puts " #{m}"
|
75
|
+
mkdir(m)
|
76
|
+
end
|
77
|
+
|
78
|
+
puts "creating files.."
|
79
|
+
%w[reports helpers renderers].each { |f|
|
80
|
+
m = "#{project}/lib/#{f}.rb"
|
81
|
+
puts " #{m}"
|
82
|
+
touch(m)
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.build_config
|
87
|
+
m = "#{project}/config/environment.rb"
|
88
|
+
puts " #{m}"
|
89
|
+
File.open(m,"w") { |f| f << CONFIG }
|
90
|
+
end
|
91
|
+
|
92
|
+
# returns the project's name
|
93
|
+
def self.project; @project; end
|
94
|
+
|
95
|
+
RAKEFILE = <<'END_RAKEFILE'
|
96
|
+
begin; require "rubygems"; rescue LoadError; end
|
97
|
+
require "rake/testtask"
|
98
|
+
|
99
|
+
task :default => [:test]
|
100
|
+
|
101
|
+
Rake::TestTask.new do |test|
|
102
|
+
test.libs << "test"
|
103
|
+
test.pattern = 'test/**/test_*.rb'
|
104
|
+
test.verbose = true
|
105
|
+
end
|
106
|
+
|
107
|
+
task :build do
|
108
|
+
if ENV['report']
|
109
|
+
sh "ruby util/build report #{ENV['report']}"
|
110
|
+
elsif ENV['renderer']
|
111
|
+
sh "ruby util/build renderer #{ENV['renderer']}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
task :run do
|
116
|
+
sh "ruby lib/reports/#{ENV['report']}.rb"
|
117
|
+
end
|
118
|
+
END_RAKEFILE
|
119
|
+
|
120
|
+
CONFIG = <<END_CONFIG
|
121
|
+
require "ruport"
|
122
|
+
|
123
|
+
Ruport::Query.add_source :default, :user => "root",
|
124
|
+
:dsn => "dbi:mysql:mydb"
|
125
|
+
END_CONFIG
|
126
|
+
|
127
|
+
BUILD = <<'END_BUILD'
|
128
|
+
#!/usr/bin/env ruby
|
129
|
+
|
130
|
+
require 'fileutils'
|
131
|
+
require 'lib/init.rb'
|
132
|
+
require "ruport/util"
|
133
|
+
include FileUtils
|
134
|
+
include Ruport::Generator::Helpers
|
135
|
+
|
136
|
+
unless ARGV.length > 1
|
137
|
+
puts "usage: build [command] [options]"
|
138
|
+
exit
|
139
|
+
end
|
140
|
+
|
141
|
+
class_name = format_class_name(ARGV[1])
|
142
|
+
|
143
|
+
if ARGV[0].eql? "report"
|
144
|
+
check_for_files
|
145
|
+
File.open("lib/reports.rb", "a") { |f|
|
146
|
+
f.puts("require \"lib/reports/#{ARGV[1]}\"")
|
147
|
+
}
|
148
|
+
REP = <<EOR
|
149
|
+
require "lib/init"
|
150
|
+
class #{class_name} < Ruport::Report
|
151
|
+
|
152
|
+
def generate
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
if __FILE__ == $0
|
159
|
+
puts #{class_name}.run
|
160
|
+
end
|
161
|
+
EOR
|
162
|
+
|
163
|
+
TEST = <<EOR
|
164
|
+
require "test/unit"
|
165
|
+
require "lib/reports/#{ARGV[1]}"
|
166
|
+
|
167
|
+
class Test#{class_name} < Test::Unit::TestCase
|
168
|
+
def test_flunk
|
169
|
+
flunk "Write your real tests here or in any test/test_* file"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
EOR
|
173
|
+
|
174
|
+
File.open("lib/reports/#{ARGV[1]}.rb", "w") { |f| f << REP }
|
175
|
+
puts "reports file: lib/reports/#{ARGV[1]}.rb"
|
176
|
+
puts "test file: test/test_#{ARGV[1]}.rb"
|
177
|
+
puts "class name: #{class_name}"
|
178
|
+
File.open("test/test_#{ARGV[1]}.rb","w") { |f| f << TEST }
|
179
|
+
|
180
|
+
elsif ARGV[0].eql? "renderer"
|
181
|
+
|
182
|
+
check_for_files
|
183
|
+
File.open("lib/renderers.rb","a") { |f|
|
184
|
+
f.puts("require \"lib/renderers/#{ARGV[1]}\"")
|
185
|
+
}
|
186
|
+
REP = <<EOR
|
187
|
+
require "lib/init"
|
188
|
+
|
189
|
+
class #{class_name} < Ruport::Renderer
|
190
|
+
stage :#{class_name.downcase}
|
191
|
+
end
|
192
|
+
|
193
|
+
class #{class_name}Formatter < Ruport::Formatter
|
194
|
+
|
195
|
+
# change to your format name, or add additional formats
|
196
|
+
renders :my_format, :for => #{class_name}
|
197
|
+
|
198
|
+
def build_#{class_name.downcase}
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
EOR
|
204
|
+
|
205
|
+
TEST = <<EOR
|
206
|
+
require "test/unit"
|
207
|
+
require "lib/renderers/#{ARGV[1]}"
|
208
|
+
|
209
|
+
class Test#{class_name} < Test::Unit::TestCase
|
210
|
+
def test_flunk
|
211
|
+
flunk "Write your real tests here or in any test/test_* file"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
EOR
|
215
|
+
puts "renderer file: lib/renderers/#{ARGV[1]}.rb"
|
216
|
+
File.open("lib/renderers/#{ARGV[1]}.rb", "w") { |f| f << REP }
|
217
|
+
puts "test file: test/test_#{ARGV[1]}.rb"
|
218
|
+
|
219
|
+
puts "class name: #{class_name}"
|
220
|
+
File.open("test/test_#{ARGV[1]}.rb","w") { |f| f << TEST }
|
221
|
+
else
|
222
|
+
puts "Incorrect usage."
|
223
|
+
end
|
224
|
+
END_BUILD
|
225
|
+
|
226
|
+
SQL_EXEC = <<'END_SQL'
|
227
|
+
#!/usr/bin/env ruby
|
228
|
+
|
229
|
+
require "lib/init"
|
230
|
+
|
231
|
+
puts Ruport::Query.new(ARGF.read).result
|
232
|
+
END_SQL
|
233
|
+
|
234
|
+
INIT = <<END_INIT
|
235
|
+
begin
|
236
|
+
require "rubygems"
|
237
|
+
gem "ruport","=#{Ruport::VERSION}"
|
238
|
+
gem "ruport-util","=#{Ruport::Util::VERSION}"
|
239
|
+
rescue LoadError
|
240
|
+
nil
|
241
|
+
end
|
242
|
+
require "ruport"
|
243
|
+
require "ruport/util"
|
244
|
+
require "lib/helpers"
|
245
|
+
require "config/environment"
|
246
|
+
|
247
|
+
class String
|
248
|
+
def /(other)
|
249
|
+
self + "/" + other
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
class Ruport::Report
|
254
|
+
|
255
|
+
def output_dir
|
256
|
+
config.output_dir or dir('output')
|
257
|
+
end
|
258
|
+
|
259
|
+
def data_dir
|
260
|
+
config.data_dir or dir('data')
|
261
|
+
end
|
262
|
+
|
263
|
+
def query_dir
|
264
|
+
config.query_dir or dir('sql')
|
265
|
+
end
|
266
|
+
|
267
|
+
def template_dir
|
268
|
+
config.template_dir or dir('templates')
|
269
|
+
end
|
270
|
+
|
271
|
+
private
|
272
|
+
def dir(name)
|
273
|
+
"#{FileUtils.pwd}/#{ARGV[0]}/\#{name}"
|
274
|
+
end
|
275
|
+
end
|
276
|
+
END_INIT
|
277
|
+
|
278
|
+
|
279
|
+
end
|
280
|
+
end
|
data/lib/ruport/util/mailer.rb
CHANGED
@@ -0,0 +1,405 @@
|
|
1
|
+
# report.rb : High Level Interface to Ruport
|
2
|
+
#
|
3
|
+
# Author: Gregory Brown
|
4
|
+
# Copyright 2006, All Rights Reserved
|
5
|
+
#
|
6
|
+
# This is Free Software. See LICENSE and COPYING files for details.
|
7
|
+
|
8
|
+
#load the needed standard libraries.
|
9
|
+
%w[erb yaml date logger fileutils].each { |lib| require lib }
|
10
|
+
require "forwardable"
|
11
|
+
|
12
|
+
module Ruport
|
13
|
+
|
14
|
+
require 'timeout'
|
15
|
+
|
16
|
+
class Attempt # :nodoc:
|
17
|
+
VERSION = '0.1.0 (vendored)'
|
18
|
+
|
19
|
+
# Number of attempts to make before failing. The default is 3.
|
20
|
+
attr_accessor :tries
|
21
|
+
|
22
|
+
# Number of seconds to wait between attempts. The default is 60.
|
23
|
+
attr_accessor :interval
|
24
|
+
|
25
|
+
# a level which ruport understands.
|
26
|
+
attr_accessor :log_level
|
27
|
+
|
28
|
+
# If set, this increments the interval with each failed attempt by that
|
29
|
+
# number of seconds.
|
30
|
+
attr_accessor :increment
|
31
|
+
|
32
|
+
# If set, the code block is further wrapped in a timeout block.
|
33
|
+
attr_accessor :timeout
|
34
|
+
|
35
|
+
# Determines which exception level to check when looking for errors to
|
36
|
+
# retry. The default is 'Exception' (i.e. all errors).
|
37
|
+
attr_accessor :level
|
38
|
+
|
39
|
+
# :call-seq:
|
40
|
+
# Attempt.new{ |a| ... }
|
41
|
+
#
|
42
|
+
# Creates and returns a new +Attempt+ object. Use a block to set the
|
43
|
+
# accessors.
|
44
|
+
#
|
45
|
+
def initialize
|
46
|
+
@tries = 3 # Reasonable default
|
47
|
+
@interval = 60 # Reasonable default
|
48
|
+
@increment = nil # Should be an int, if provided
|
49
|
+
@timeout = nil # Wrap the code in a timeout block if provided
|
50
|
+
@level = Exception # Level of exception to be caught
|
51
|
+
|
52
|
+
yield self if block_given?
|
53
|
+
end
|
54
|
+
|
55
|
+
def attempt
|
56
|
+
count = 1
|
57
|
+
begin
|
58
|
+
if @timeout
|
59
|
+
Timeout.timeout(@timeout){ yield }
|
60
|
+
else
|
61
|
+
yield
|
62
|
+
end
|
63
|
+
rescue @level => error
|
64
|
+
@tries -= 1
|
65
|
+
if @tries > 0
|
66
|
+
msg = "Error on attempt # #{count}: #{error}; retrying"
|
67
|
+
count += 1
|
68
|
+
@interval += @increment if @increment
|
69
|
+
sleep @interval
|
70
|
+
retry
|
71
|
+
end
|
72
|
+
raise
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
# === Overview
|
80
|
+
#
|
81
|
+
# The Ruport::Report class provides a high level interface to much of Ruport's
|
82
|
+
# functionality. It is designed to allow you to build and run reports easily.
|
83
|
+
# If your needs are complicated, you will probably need to take a look at the
|
84
|
+
# individual classes of the library, but if they are fairly simple, you may be
|
85
|
+
# able to get away using this class alone.
|
86
|
+
#
|
87
|
+
# Ruport::Report is primarily meant to be used with Ruport's code generator,
|
88
|
+
# rope, and is less useful when integrated within another system, such as
|
89
|
+
# Rails or Camping.
|
90
|
+
#
|
91
|
+
# Below is a simple example of loading a report in from a CSV, performing a
|
92
|
+
# grouping operation, and then rendering the resulting PDF to file.
|
93
|
+
#
|
94
|
+
# require "rubygems"
|
95
|
+
# require "ruport"
|
96
|
+
# class MyReport < Ruport::Report
|
97
|
+
#
|
98
|
+
# renders_as_grouping(:style => :inline)
|
99
|
+
#
|
100
|
+
# def generate
|
101
|
+
# table = load_csv "foo.csv"
|
102
|
+
# Grouping(table, :by => "username")
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# report = MyReport.new(:pdf)
|
108
|
+
# report.run { |results| results.write("bar.pdf") }
|
109
|
+
#
|
110
|
+
class Report
|
111
|
+
extend Forwardable
|
112
|
+
include Renderer::Hooks
|
113
|
+
|
114
|
+
# Builds a report instance. If provided a format parameter,
|
115
|
+
# this format will be used by default when rendering the report.
|
116
|
+
#
|
117
|
+
def initialize( format=nil )
|
118
|
+
use_source :default
|
119
|
+
@format = format
|
120
|
+
@report_name = ""
|
121
|
+
@results = ""
|
122
|
+
@file = nil
|
123
|
+
end
|
124
|
+
|
125
|
+
# By default, this file will be used by Report#write.
|
126
|
+
attr_accessor :file
|
127
|
+
|
128
|
+
# This attribute will get the results of Report#generate when the report is
|
129
|
+
# run.
|
130
|
+
#
|
131
|
+
attr_accessor :results
|
132
|
+
|
133
|
+
# This attribute defines which format the Report will render in by default.
|
134
|
+
attr_accessor :format
|
135
|
+
|
136
|
+
# This is a simplified interface to Ruport::Query.
|
137
|
+
#
|
138
|
+
# You can use it to read SQL statements from file or string:
|
139
|
+
#
|
140
|
+
# #from string
|
141
|
+
# result = query "select * from foo"
|
142
|
+
#
|
143
|
+
# #from file
|
144
|
+
# result = query "my_query.sql", :origin => :file
|
145
|
+
#
|
146
|
+
# You can use multistatement SQL:
|
147
|
+
#
|
148
|
+
# # will return the value of the last statement, "select * from foo"
|
149
|
+
# result = query "insert into foo values(1,2); select * from foo"
|
150
|
+
#
|
151
|
+
# You can iterate by row:
|
152
|
+
#
|
153
|
+
# query("select * from foo") { |r|
|
154
|
+
# #do something with the rows here
|
155
|
+
# }
|
156
|
+
#
|
157
|
+
# query() can return raw DBI:Row objects or Ruport's data structures:
|
158
|
+
#
|
159
|
+
# # will return an Array of DBI::Row objects
|
160
|
+
# result = query "select * from foo", :raw_data => true
|
161
|
+
#
|
162
|
+
# You can quickly output in a number of formats:
|
163
|
+
#
|
164
|
+
# result = query "select * from foo", :as => :csv
|
165
|
+
# result = query "select * from foo", :as => :html
|
166
|
+
# result = query "select * from foo", :as => :pdf
|
167
|
+
#
|
168
|
+
# See Ruport::Query for details.
|
169
|
+
#
|
170
|
+
def query(sql, options={})
|
171
|
+
options[:origin] ||= :string
|
172
|
+
options[:source] ||= @source
|
173
|
+
q = options[:query_obj] || Query.new(sql, options)
|
174
|
+
if block_given?
|
175
|
+
q.each { |r| yield(r) }
|
176
|
+
elsif options[:as]
|
177
|
+
q.result.as(options[:as])
|
178
|
+
else
|
179
|
+
q.result
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Sets the active source to the Ruport::Query source requested by
|
184
|
+
# <tt>label</tt>.
|
185
|
+
#
|
186
|
+
# For example, if you have a data source :test, which is defined as such:
|
187
|
+
#
|
188
|
+
# Ruport::Query.add_source(:test, :dsn => "dbi:mysql:test",
|
189
|
+
# :user => "root" )
|
190
|
+
#
|
191
|
+
#
|
192
|
+
# The following report would use that data source rather than the
|
193
|
+
# <tt>:default</tt> source:
|
194
|
+
#
|
195
|
+
# class MyReport < Ruport::Report
|
196
|
+
#
|
197
|
+
# renders_as_table
|
198
|
+
#
|
199
|
+
# def generate
|
200
|
+
# use_source :test
|
201
|
+
# query "select * from foo"
|
202
|
+
# end
|
203
|
+
#
|
204
|
+
# end
|
205
|
+
def use_source(label)
|
206
|
+
@source = label
|
207
|
+
end
|
208
|
+
|
209
|
+
# Writes the contents of Report#results to file.
|
210
|
+
# If given a string as a second argument, writes that to file, instead.
|
211
|
+
#
|
212
|
+
# Examples:
|
213
|
+
#
|
214
|
+
# # write the results of the report to a file
|
215
|
+
# Report.run { |r| r.write("foo.txt") }
|
216
|
+
#
|
217
|
+
# # write the results in reverse
|
218
|
+
# Report.run { |r| r.write("foo.txt",r.results.reverse) }
|
219
|
+
#
|
220
|
+
def write(my_file=file,my_results=results)
|
221
|
+
File.open(my_file,"w") { |f| f << my_results }
|
222
|
+
end
|
223
|
+
|
224
|
+
# Behaves the same way as Report#write, but will append to a file rather
|
225
|
+
# than create a new file if it already exists.
|
226
|
+
#
|
227
|
+
def append(my_file=file,my_results=results)
|
228
|
+
File.open(my_file,"a") { |f| f << my_results }
|
229
|
+
end
|
230
|
+
|
231
|
+
# This method passes <tt>self</tt> to Report.run.
|
232
|
+
#
|
233
|
+
# Please see the class method for details.
|
234
|
+
#
|
235
|
+
def run(options={},&block)
|
236
|
+
options[:reports] ||= [self]
|
237
|
+
self.class.run(options,&block)
|
238
|
+
end
|
239
|
+
|
240
|
+
# Allows you to override the default format.
|
241
|
+
#
|
242
|
+
# Example:
|
243
|
+
#
|
244
|
+
# my_report.as(:csv)
|
245
|
+
#
|
246
|
+
def as(format,*args)
|
247
|
+
self.format,old = format, self.format
|
248
|
+
results = run(*args)
|
249
|
+
self.format = old
|
250
|
+
return results
|
251
|
+
end
|
252
|
+
|
253
|
+
# Provides syntactic sugar, allowing to_foo in place of as(:foo)
|
254
|
+
def method_missing(id,*args)
|
255
|
+
id.to_s =~ /^to_(.*)/
|
256
|
+
$1 ? as($1.to_sym,*args) : super
|
257
|
+
end
|
258
|
+
|
259
|
+
# Loads a CSV in from a file.
|
260
|
+
#
|
261
|
+
# Example:
|
262
|
+
#
|
263
|
+
# my_table = load_csv "foo.csv" #=> Data::Table
|
264
|
+
# my_array = load_csv "foo.csv", :as => :array #=> Array
|
265
|
+
#
|
266
|
+
# See also Ruport::Data::Table.load and Table()
|
267
|
+
#
|
268
|
+
def load_csv(file,options={})
|
269
|
+
case options[:as]
|
270
|
+
when :array
|
271
|
+
a = []
|
272
|
+
Data::Table.load(file,options) { |s,r| a << r } ; a
|
273
|
+
else
|
274
|
+
Data::Table.load(file,options)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# Executes an erb template. If a filename is given which matches the
|
279
|
+
# pattern /\.r\w+$/ (eg foo.rhtml, bar.rtxt, etc),
|
280
|
+
# it will be loaded and evaluated. Otherwise, the string will be processed
|
281
|
+
# directly.
|
282
|
+
#
|
283
|
+
# Examples:
|
284
|
+
#
|
285
|
+
# @foo = 'greg'
|
286
|
+
# erb "My name is <%= @foo %>" #=> "My name is greg"
|
287
|
+
#
|
288
|
+
# erb "foo.rhtml" #=> contents of evaluated text in foo.rhtml
|
289
|
+
#
|
290
|
+
def erb(s)
|
291
|
+
if s =~ /\.r\w+$/
|
292
|
+
ERB.new(File.read(s)).result(binding)
|
293
|
+
else
|
294
|
+
ERB.new(s).result(binding)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
class << self
|
299
|
+
|
300
|
+
# Allows you to override the default format.
|
301
|
+
#
|
302
|
+
# Example:
|
303
|
+
#
|
304
|
+
# my_report.as(:csv)
|
305
|
+
#
|
306
|
+
def as(format,options={})
|
307
|
+
report = new(format)
|
308
|
+
report.run(rendering_options.merge(options))
|
309
|
+
end
|
310
|
+
|
311
|
+
# Provides syntactic sugar, allowing to_foo in place of as(:foo)
|
312
|
+
def method_missing(id,*args)
|
313
|
+
id.to_s =~ /^to_(.*)/
|
314
|
+
$1 ? as($1.to_sym,*args) : super
|
315
|
+
end
|
316
|
+
|
317
|
+
# Defines an instance method which will be run before the
|
318
|
+
# <tt>generate</tt> method when Ruport.run is executed.
|
319
|
+
#
|
320
|
+
# Good for setting config info and perhaps files and/or loggers.
|
321
|
+
#
|
322
|
+
def prepare(&block); define_method(:prepare,&block) end
|
323
|
+
|
324
|
+
# Defines an instance method which will be executed by Report.run.
|
325
|
+
#
|
326
|
+
# The return value of this method is assigned to the <tt>results</tt>
|
327
|
+
# attribute.
|
328
|
+
#
|
329
|
+
def generate(&block); define_method(:generate,&block) end
|
330
|
+
|
331
|
+
# Defines an instance method which will be executed after the object is
|
332
|
+
# yielded in Report.run.
|
333
|
+
#
|
334
|
+
def cleanup(&block); define_method(:cleanup,&block) end
|
335
|
+
|
336
|
+
private :prepare, :generate, :cleanup
|
337
|
+
|
338
|
+
# Runs the reports specified. If no reports are specified, then it
|
339
|
+
# creates a new instance via <tt>self.new</tt>.
|
340
|
+
#
|
341
|
+
# Hooks called, in order:
|
342
|
+
# * Report#prepare
|
343
|
+
# * Report#generate #=> return value stored in @results
|
344
|
+
# * yields self to block, if given
|
345
|
+
# * if a renderer is specified, passes along @results and options
|
346
|
+
# * Report#cleanup
|
347
|
+
#
|
348
|
+
# Options:
|
349
|
+
# :reports: A list of reports to run, defaults to a single generic
|
350
|
+
# instance of the current report (self.new).
|
351
|
+
#
|
352
|
+
# :tries:, :timeout:, :interval: Wrappers on attempt.rb
|
353
|
+
#
|
354
|
+
# all other options will be forwarded to a renderer if one is specified
|
355
|
+
# via the Renderer::Hooks methods
|
356
|
+
def run(options={})
|
357
|
+
options[:reports] ||= [self.new]
|
358
|
+
|
359
|
+
formatting_options = ( options.keys -
|
360
|
+
[:reports,:tries,:timeout,:interval])
|
361
|
+
|
362
|
+
fopts = formatting_options.inject({}) { |s,k|
|
363
|
+
s.merge( k => options[k] )
|
364
|
+
}
|
365
|
+
|
366
|
+
|
367
|
+
process = lambda do
|
368
|
+
options[:reports].each { |rep|
|
369
|
+
rep.prepare if rep.respond_to? :prepare
|
370
|
+
rep.results = rep.generate
|
371
|
+
|
372
|
+
if renderer
|
373
|
+
rep.results =
|
374
|
+
renderer.render(rep.format,rendering_options.merge(fopts)) { |r|
|
375
|
+
r.data = rep.results
|
376
|
+
}
|
377
|
+
end
|
378
|
+
|
379
|
+
yield(rep) if block_given?
|
380
|
+
rep.cleanup if rep.respond_to? :cleanup
|
381
|
+
}
|
382
|
+
end
|
383
|
+
|
384
|
+
if options[:tries] && (options[:interval] || options[:timeout])
|
385
|
+
code = Attempt.new { |a|
|
386
|
+
a.tries = options[:tries]
|
387
|
+
a.interval = options[:interval] if options[:interval]
|
388
|
+
a.timeout = options[:timeout] if options[:timeout]
|
389
|
+
}
|
390
|
+
code.attempt(&process)
|
391
|
+
else
|
392
|
+
process.call
|
393
|
+
end
|
394
|
+
|
395
|
+
outs = options[:reports].map { |r| r.results }
|
396
|
+
if outs.length == 1
|
397
|
+
outs.last
|
398
|
+
else
|
399
|
+
outs
|
400
|
+
end
|
401
|
+
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
data/lib/ruport/util.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
|
+
module Ruport
|
2
|
+
module Util
|
3
|
+
VERSION = "0.4.0"
|
4
|
+
end
|
5
|
+
end
|
6
|
+
require "ruport/util/report"
|
1
7
|
require "ruport/util/graph"
|
2
8
|
require "ruport/util/invoice"
|
3
9
|
require "ruport/util/report_manager"
|
4
10
|
require "ruport/util/mailer"
|
5
11
|
require "ruport/util/bench"
|
12
|
+
require "ruport/util/generator"
|
data/test/test_report.rb
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
#tc_report.rb
|
2
|
+
#
|
3
|
+
# Created by Gregory Thomas Brown on 2005-08-09
|
4
|
+
# Copyright 2005 (Gregory Brown) All rights reserved.
|
5
|
+
|
6
|
+
require "test/unit"
|
7
|
+
begin; require "rubygems"; rescue LoadError; nil; end
|
8
|
+
require "ruport"
|
9
|
+
require "ruport/util/report"
|
10
|
+
|
11
|
+
begin
|
12
|
+
require 'mocha'
|
13
|
+
require 'stubba'
|
14
|
+
require 'net/smtp'
|
15
|
+
rescue LoadError
|
16
|
+
$stderr.puts "Warning: Mocha not found -- skipping some Report tests"
|
17
|
+
end
|
18
|
+
|
19
|
+
class SampleReport < Ruport::Report
|
20
|
+
renders_with Ruport::Renderer::Table
|
21
|
+
|
22
|
+
def generate
|
23
|
+
Table(%w[not abc]) << %w[o r] << %w[one two] << %w[thr ee]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class TestReport < Test::Unit::TestCase
|
28
|
+
include Ruport
|
29
|
+
|
30
|
+
def setup
|
31
|
+
@report = Report.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_renders_with_shortcuts
|
35
|
+
a = SampleReport.new(:csv)
|
36
|
+
assert_equal("not,abc\no,r\none,two\nthr,ee\n",a.run)
|
37
|
+
assert_equal("not,abc\no,r\none,two\nthr,ee\n",SampleReport.as(:csv))
|
38
|
+
assert_equal("not,abc\no,r\none,two\nthr,ee\n",SampleReport.to_csv)
|
39
|
+
assert_equal("not,abc\no,r\none,two\nthr,ee\n",a.to_csv)
|
40
|
+
a = SampleReport.new
|
41
|
+
assert_equal("not,abc\no,r\none,two\nthr,ee\n",a.to_csv)
|
42
|
+
|
43
|
+
assert_nil nil, a.format
|
44
|
+
a.to_text
|
45
|
+
|
46
|
+
assert_nil a.format
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_erb
|
50
|
+
@report = Report.new
|
51
|
+
@report.results = "foo"
|
52
|
+
assert_equal "foo", @report.erb("<%= @results %>")
|
53
|
+
assert_equal "foo\n4\n---\n", @report.erb("test/samples/foo.rtxt")
|
54
|
+
end
|
55
|
+
|
56
|
+
class MyReport < Report; end
|
57
|
+
|
58
|
+
def test_klass_methods
|
59
|
+
rep_klass = MyReport.dup
|
60
|
+
rep_klass.send(:prepare) { self.file = "foo.csv" }
|
61
|
+
rep_klass.send(:generate) { "hello dolly" }
|
62
|
+
rep_klass.send(:cleanup) { @foo = "bar" }
|
63
|
+
report = rep_klass.new
|
64
|
+
report.run { |rep|
|
65
|
+
assert_equal("foo.csv",rep.file)
|
66
|
+
assert_equal("hello dolly",rep.results)
|
67
|
+
assert_equal(nil,rep.instance_eval("@foo"))
|
68
|
+
}
|
69
|
+
assert_equal("bar",report.instance_eval("@foo"))
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_multi_reports
|
73
|
+
rep_klass = MyReport.dup
|
74
|
+
|
75
|
+
report1 = rep_klass.new
|
76
|
+
report2 = rep_klass.new
|
77
|
+
|
78
|
+
report1.file = "foo"
|
79
|
+
report2.file = "bar"
|
80
|
+
|
81
|
+
rep_klass.send(:generate) { file }
|
82
|
+
|
83
|
+
expected = %w[foo bar]
|
84
|
+
|
85
|
+
rep_klass.run :reports => [report1,report2] do |rep|
|
86
|
+
assert_equal expected.shift, rep.results
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
def test_timeout
|
93
|
+
rep_klass = MyReport.dup
|
94
|
+
rep_klass.send(:generate) { raise }
|
95
|
+
|
96
|
+
assert_raises(RuntimeError){
|
97
|
+
rep_klass.run(:tries => 3, :interval => 1)
|
98
|
+
}
|
99
|
+
|
100
|
+
rep_klass.send(:generate) { sleep 1.1 }
|
101
|
+
|
102
|
+
assert_raises(Timeout::Error) {
|
103
|
+
rep_klass.run( :tries => 2,
|
104
|
+
:timeout => 1,
|
105
|
+
:interval => 1,
|
106
|
+
:log_level => :log_only)
|
107
|
+
}
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_return_value
|
112
|
+
rep_klass = MyReport.dup
|
113
|
+
rep_klass.send(:generate) { "hello dolly" }
|
114
|
+
|
115
|
+
# single report
|
116
|
+
assert_equal "hello dolly", rep_klass.run
|
117
|
+
|
118
|
+
# multiple reports
|
119
|
+
assert_equal ["hello dolly", "hello dolly"],
|
120
|
+
rep_klass.run(:reports => [rep_klass.new,rep_klass.new])
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_write_to_file
|
124
|
+
return unless Object.const_defined? :Mocha
|
125
|
+
file = mock("file")
|
126
|
+
|
127
|
+
File.expects(:open).
|
128
|
+
with("foo.csv","w").yields(file).returns(file).at_least_once
|
129
|
+
|
130
|
+
file.expects(:<<).
|
131
|
+
with("results").returns(file).at_least_once
|
132
|
+
|
133
|
+
@report = Report.new
|
134
|
+
assert @report.write("foo.csv", "results")
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_append_to_file
|
138
|
+
return unless Object.const_defined? :Mocha
|
139
|
+
file = mock("file")
|
140
|
+
|
141
|
+
File.expects(:open).
|
142
|
+
with("foo.csv","a").yields(file).returns(file).at_least_once
|
143
|
+
|
144
|
+
file.expects(:<<).
|
145
|
+
with("results").returns(file).at_least_once
|
146
|
+
|
147
|
+
@report = Report.new
|
148
|
+
assert @report.append("foo.csv", "results")
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_load_csv
|
152
|
+
expected = [%w[a b c],['d', nil, 'e']].to_table(%w[col1 col2 col3])
|
153
|
+
|
154
|
+
@report = Report.new
|
155
|
+
table = @report.load_csv("test/samples/data.csv")
|
156
|
+
|
157
|
+
assert_equal expected, table
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_load_csv_as_array
|
161
|
+
expected = [%w[a b c],['d', nil, 'e']]
|
162
|
+
|
163
|
+
@report = Report.new
|
164
|
+
array = @report.load_csv("test/samples/data.csv", :as => :array)
|
165
|
+
|
166
|
+
assert_equal expected, array
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_renders_with
|
170
|
+
klass = MyReport.dup
|
171
|
+
klass.renders_with Ruport::Renderer::Table
|
172
|
+
klass.send(:generate) { [[1,2,3],[4,5,6]].to_table(%w[a b c]) }
|
173
|
+
a = klass.new(:csv)
|
174
|
+
assert_equal "a,b,c\n1,2,3\n4,5,6\n", a.run
|
175
|
+
|
176
|
+
klass.renders_with Ruport::Renderer::Table, :show_table_headers => false
|
177
|
+
a = klass.new(:csv)
|
178
|
+
assert_equal "1,2,3\n4,5,6\n", a.run
|
179
|
+
assert_equal "a,b,c\n1,2,3\n4,5,6\n", a.run(:show_table_headers => true)
|
180
|
+
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_renders_as_table
|
185
|
+
klass = MyReport.dup
|
186
|
+
klass.renders_as_table
|
187
|
+
klass.send(:generate) { [[1,2,3],[4,5,6]].to_table(%w[a b c]) }
|
188
|
+
a = klass.new(:csv)
|
189
|
+
assert_equal "a,b,c\n1,2,3\n4,5,6\n", a.run
|
190
|
+
end
|
191
|
+
|
192
|
+
def test_renders_as_row
|
193
|
+
klass = MyReport.dup
|
194
|
+
klass.renders_as_row
|
195
|
+
klass.send(:generate) { [[1,2,3]].to_table(%w[a b c])[0] }
|
196
|
+
a = klass.new(:csv)
|
197
|
+
assert_equal "1,2,3\n", a.run
|
198
|
+
end
|
199
|
+
|
200
|
+
def test_renders_as_group
|
201
|
+
klass = MyReport.dup
|
202
|
+
klass.renders_as_group
|
203
|
+
klass.send(:generate) { [[1,2,3]].to_table(%w[a b c]).to_group("foo") }
|
204
|
+
a = klass.new(:csv)
|
205
|
+
assert_equal "foo\n\na,b,c\n1,2,3\n", a.run
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_renders_as_grouping
|
209
|
+
klass = MyReport.dup
|
210
|
+
klass.renders_as_grouping
|
211
|
+
klass.send(:generate) {
|
212
|
+
Grouping([[1,2,3],[4,5,6]].to_table(%w[a b c]),:by => "a")
|
213
|
+
}
|
214
|
+
a = klass.new(:csv)
|
215
|
+
assert_equal "1\n\nb,c\n2,3\n\n4\n\nb,c\n5,6\n\n", a.run
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
|
|
3
3
|
specification_version: 1
|
4
4
|
name: ruport-util
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2007-
|
6
|
+
version: 0.4.0
|
7
|
+
date: 2007-05-01 00:00:00 -04:00
|
8
8
|
summary: A set of tools and helper libs for Ruby Reports
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -29,38 +29,44 @@ post_install_message:
|
|
29
29
|
authors:
|
30
30
|
- Gregory Brown
|
31
31
|
files:
|
32
|
+
- example/graph_report.rb
|
32
33
|
- example/invoice_report.rb
|
33
|
-
- example/managed_report.rb
|
34
34
|
- example/mailer.rb
|
35
|
-
- example/
|
35
|
+
- example/managed_report.rb
|
36
36
|
- lib/ruport
|
37
37
|
- lib/ruport/util
|
38
38
|
- lib/ruport/util.rb
|
39
|
-
- lib/ruport/util/mailer.rb
|
40
39
|
- lib/ruport/util/bench.rb
|
40
|
+
- lib/ruport/util/generator.rb
|
41
41
|
- lib/ruport/util/graph.rb
|
42
|
-
- lib/ruport/util/report_manager.rb
|
43
42
|
- lib/ruport/util/invoice.rb
|
44
|
-
-
|
43
|
+
- lib/ruport/util/mailer.rb
|
44
|
+
- lib/ruport/util/report.rb
|
45
|
+
- lib/ruport/util/report_manager.rb
|
45
46
|
- test/init.rb
|
46
|
-
- test/
|
47
|
+
- test/samples
|
47
48
|
- test/test_graph_renderer.rb
|
48
|
-
- test/test_mailer.rb
|
49
49
|
- test/test_invoice.rb
|
50
|
+
- test/test_mailer.rb
|
51
|
+
- test/test_report.rb
|
52
|
+
- test/test_report_manager.rb
|
50
53
|
- test/samples/data.csv
|
54
|
+
- test/samples/foo.rtxt
|
55
|
+
- bin/rope
|
51
56
|
- Rakefile
|
52
57
|
test_files:
|
53
|
-
- test/test_report_manager.rb
|
54
58
|
- test/test_graph_renderer.rb
|
55
|
-
- test/test_mailer.rb
|
56
59
|
- test/test_invoice.rb
|
60
|
+
- test/test_mailer.rb
|
61
|
+
- test/test_report.rb
|
62
|
+
- test/test_report_manager.rb
|
57
63
|
rdoc_options:
|
58
64
|
- --title
|
59
65
|
- Ruport Documentation
|
60
66
|
extra_rdoc_files: []
|
61
67
|
|
62
|
-
executables:
|
63
|
-
|
68
|
+
executables:
|
69
|
+
- rope
|
64
70
|
extensions: []
|
65
71
|
|
66
72
|
requirements: []
|