ruport-util 0.3.0 → 0.4.0
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 +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: []
|