ruport-util 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,71 +11,6 @@ require "forwardable"
11
11
 
12
12
  module Ruport
13
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
14
  # === Overview
80
15
  #
81
16
  # The Ruport::Report class provides a high level interface to much of Ruport's
@@ -98,41 +33,19 @@ module Ruport
98
33
  # renders_as_grouping(:style => :inline)
99
34
  #
100
35
  # def generate
101
- # table = load_csv "foo.csv"
36
+ # table = Table("foo.csv")
102
37
  # Grouping(table, :by => "username")
103
38
  # end
104
39
  #
105
40
  # end
106
41
  #
107
- # report = MyReport.new(:pdf)
108
- # report.run { |results| results.write("bar.pdf") }
42
+ # report = MyReport.new
43
+ # report.save_as("bar.pdf")
109
44
  #
110
45
  class Report
111
46
  extend Forwardable
112
47
  include Renderer::Hooks
113
48
 
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
49
  # This is a simplified interface to Ruport::Query.
137
50
  #
138
51
  # You can use it to read SQL statements from file or string:
@@ -168,9 +81,8 @@ module Ruport
168
81
  # See Ruport::Query for details.
169
82
  #
170
83
  def query(sql, options={})
171
- options[:origin] ||= :string
172
- options[:source] ||= @source
173
- q = options[:query_obj] || Query.new(sql, options)
84
+ options[:source] ||= :default
85
+ q = options[:query_obj] || Ruport::Query.new(sql, options)
174
86
  if block_given?
175
87
  q.each { |r| yield(r) }
176
88
  elsif options[:as]
@@ -179,225 +91,47 @@ module Ruport
179
91
  q.result
180
92
  end
181
93
  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 }
94
+
95
+ def renderable_data #:nodoc:
96
+ generate
222
97
  end
98
+
99
+ alias_method :old_as, :as
223
100
 
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 }
101
+ def as(*args,&block)
102
+ prepare if respond_to?(:prepare)
103
+ output = old_as(*args,&block)
104
+ cleanup if respond_to?(:cleanup)
105
+ return output
229
106
  end
230
107
 
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
108
+ def save_as(filename,options={})
109
+ formats = { "csv" => ["w",:csv], "txt" => ["w",:text],
110
+ "html" => ["w", :html], "pdf" => ["wb", :pdf ] }
111
+
112
+ fo = filename =~ /.*\.(.*)/ && formats[$1]
113
+ flags = options.delete(:flags)
114
+ if fo
115
+ File.open(filename,flags || fo[0]) { |f| f << as(fo[1],options) }
273
116
  else
274
- Data::Table.load(file,options)
117
+ File.open(filename,flags || "w") { |f| f << as($1.to_sym,options) }
275
118
  end
276
119
  end
277
120
 
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)
121
+ def method_missing(id,*args,&block)
122
+ if id.to_s =~ /^to_(.*)/
123
+ as($1.to_sym,*args,&block)
293
124
  else
294
- ERB.new(s).result(binding)
125
+ super
295
126
  end
296
127
  end
297
128
 
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
- def prepare(&block); define_method(:prepare,&block) end
321
-
322
- # Defines an instance method which will be executed by Report.run.
323
- #
324
- # The return value of this method is assigned to the <tt>results</tt>
325
- # attribute.
326
- #
327
- def generate(&block); define_method(:generate,&block) end
328
-
329
- # Defines an instance method which will be executed after the object is
330
- # yielded in Report.run.
331
- #
332
- def cleanup(&block); define_method(:cleanup,&block) end
333
-
334
- private :prepare, :generate, :cleanup
335
-
336
- # Runs the reports specified. If no reports are specified, then it
337
- # creates a new instance via <tt>self.new</tt>.
338
- #
339
- # Hooks called, in order:
340
- # * Report#prepare
341
- # * Report#generate #=> return value stored in @results
342
- # * yields self to block, if given
343
- # * if a renderer is specified, passes along @results and options
344
- # * Report#cleanup
345
- #
346
- # Options:
347
- # :reports: A list of reports to run, defaults to a single generic
348
- # instance of the current report (self.new).
349
- #
350
- # :tries:, :timeout:, :interval: Wrappers on attempt.rb
351
- #
352
- # all other options will be forwarded to a renderer if one is specified
353
- # via the Renderer::Hooks methods
354
- def run(options={})
355
- options[:reports] ||= [self.new]
356
-
357
- formatting_options = ( options.keys -
358
- [:reports,:tries,:timeout,:interval])
359
-
360
- fopts = formatting_options.inject({}) { |s,k|
361
- s.merge( k => options[k] )
362
- }
363
-
364
-
365
- process = lambda do
366
- options[:reports].each { |rep|
367
- rep.prepare if rep.respond_to? :prepare
368
- rep.results = rep.generate
369
-
370
- if renderer
371
- rep.results =
372
- renderer.render(rep.format,rendering_options.merge(fopts)) { |r|
373
- r.data = rep.results
374
- }
375
- end
376
-
377
- yield(rep) if block_given?
378
- rep.cleanup if rep.respond_to? :cleanup
379
- }
380
- end
381
-
382
- if options[:tries] && (options[:interval] || options[:timeout])
383
- code = Attempt.new { |a|
384
- a.tries = options[:tries]
385
- a.interval = options[:interval] if options[:interval]
386
- a.timeout = options[:timeout] if options[:timeout]
387
- }
388
- code.attempt(&process)
389
- else
390
- process.call
391
- end
129
+ def add_source(*args)
130
+ Ruport::Query.add_source(*args)
131
+ end
392
132
 
393
- outs = options[:reports].map { |r| r.results }
394
- if outs.length == 1
395
- outs.last
396
- else
397
- outs
398
- end
399
-
400
- end
133
+ def self.generate
134
+ yield(new)
401
135
  end
402
136
  end
403
137
  end
@@ -14,8 +14,7 @@ module Ruport
14
14
  #
15
15
  # just kidding, use models#find or reports#find
16
16
  def self.[](name)
17
- reports.find { |r| r.name.eql?(name) } ||
18
- models.find { |m| m.name.eql?(name) }
17
+ (reports + models).find{|n| n.name == name }
19
18
  end
20
19
 
21
20
  def self.models
@@ -46,4 +45,4 @@ class Ruport::Report
46
45
  def self.acts_as_managed_report
47
46
  Ruport::ReportManager.add_report(self)
48
47
  end
49
- end
48
+ end
data/lib/ruport/util.rb CHANGED
@@ -1,8 +1,15 @@
1
1
  module Ruport
2
2
  module Util
3
- VERSION = "0.6.0"
3
+ VERSION = "0.7.0"
4
+
5
+ file = __FILE__
6
+ file = File.readlink(file) if File.symlink?(file)
7
+ dir = File.dirname(file)
8
+ BASEDIR = File.expand_path(File.join(dir, '..', '..'))
9
+ LIBDIR = File.expand_path(File.join(dir, '..'))
4
10
  end
5
11
  end
12
+
6
13
  require "ruport/util/report"
7
14
  require "ruport/util/graph"
8
15
  require "ruport/util/invoice"
@@ -11,3 +18,4 @@ require "ruport/util/mailer"
11
18
  require "ruport/util/bench"
12
19
  require "ruport/util/generator"
13
20
  require "ruport/util/pdf/form"
21
+ require "ruport/util/ods"
@@ -0,0 +1,55 @@
1
+ require 'test/helper/wrap'
2
+
3
+ class SpecLayout
4
+ attr_accessor :base, :layout, :files
5
+
6
+ def initialize(base, layout)
7
+ @base, @layout = base, layout
8
+ end
9
+
10
+ def run
11
+ SpecWrap.new(@files).run
12
+ end
13
+
14
+ def gather
15
+ @files = gather_files(@base, @layout)
16
+ end
17
+
18
+ def gather_files(base, layout)
19
+ files = Set.new
20
+ base = File.expand_path(base)
21
+
22
+ layout.each do |key, value|
23
+ if value.is_a?(Hash)
24
+ files += gather_files(base/key, value)
25
+ else
26
+ glob = base/key/"#{value}.rb"
27
+ files += Dir[glob].map{|f| File.expand_path(f)}
28
+ end
29
+ end
30
+
31
+ files.reject{|f| File.directory?(f)}
32
+ end
33
+
34
+ def clean
35
+ @files = clean_files(@files, @layout)
36
+ end
37
+
38
+ def clean_files(files, layout)
39
+ layout.each do |key, value|
40
+ if value.is_a?(Hash)
41
+ clean_files(files, value)
42
+ elsif files
43
+ files.dup.each do |file|
44
+ name = File.basename(file, File.extname(file))
45
+ dir = file.gsub(File.extname(file), '')
46
+ if name == key and File.directory?(dir)
47
+ files.delete(file)
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ files
54
+ end
55
+ end
@@ -0,0 +1,190 @@
1
+ require 'pp'
2
+ require 'set'
3
+
4
+ begin
5
+ require 'rubygems'
6
+ rescue LoadError
7
+ end
8
+
9
+ begin
10
+ require 'systemu'
11
+ rescue LoadError
12
+ puts "Please install systemu for better-looking results"
13
+
14
+ # small drop-in replacement for systemu... far from perfect though, so please
15
+ # install the library
16
+
17
+ def systemu command
18
+ stdout = `#{command} 2>&1`
19
+ status, stdout, stderr = $?, stdout, ''
20
+ end
21
+ end
22
+
23
+ class String
24
+ { :red => 31,
25
+ :green => 32,
26
+ :yellow => 33,
27
+ }.each do |key, value|
28
+ define_method key do
29
+ "\e[#{value}m" + self + "\e[0m"
30
+ end
31
+ end
32
+
33
+ def /(str)
34
+ File.join(self, str.to_s)
35
+ end
36
+ end
37
+
38
+ class Array
39
+ def commonize
40
+ snips, rest = map{|s| [s[0,1], s[1..-1]]}.transpose
41
+ unless snips.uniq.size != 1 or rest.any?{|r| File.basename(r) == r}
42
+ rest.commonize
43
+ else
44
+ self.map{|e| e.gsub(/^\//, '')}
45
+ end
46
+ end
47
+
48
+ def namize
49
+ commonize.map do |e|
50
+ dir = File.dirname(e)
51
+ file = File.basename(e, File.extname(e))
52
+ ( dir / file ).gsub(/^\.\//, '')
53
+ end
54
+ end
55
+ end
56
+
57
+ $stdout.sync = true
58
+
59
+ class SpecWrap
60
+ def initialize(*files)
61
+ @files = files.flatten
62
+ @names = @files.namize
63
+ @specs = Hash[*@files.zip(@names).flatten]
64
+ @done = Set.new
65
+ end
66
+
67
+ def run
68
+ @specs.sort_by{|s| s.last}.each do |file, name|
69
+ spec = SpecFile.new(file, name, term_width)
70
+ spec.run
71
+ spec.short_summary
72
+ @done << spec
73
+ end
74
+
75
+ @done.sort_by{|d| d.name}.each do |spec|
76
+ puts(spec.long_summary) if spec.failed?
77
+ end
78
+
79
+ summarize
80
+ end
81
+
82
+ def summarize
83
+ total_passed = @done.inject(0){|s,v| s + v.passed }
84
+ total_failed = @done.inject(0){|s,v| s + v.failed }
85
+ total_specs = total_failed + total_passed
86
+
87
+ puts "#{total_specs} examples, #{total_failed} failures"
88
+ puts
89
+
90
+ if total_failed.nonzero?
91
+ failed = @done.select{|d| d.failed.nonzero? or d.passed.zero?}.map{|f| f.name.red }
92
+ puts "These failed: #{failed.join(', ')}"
93
+ exit 1
94
+ else
95
+ puts("No failing examples, let's add some tests!")
96
+ end
97
+ end
98
+
99
+ def term_width
100
+ @names.sort_by{|s| s.size }.last.size
101
+ end
102
+ end
103
+
104
+ class SpecFile
105
+ attr_reader :file, :name, :passed, :failed, :mark_passed
106
+
107
+ def initialize file, name, term_width
108
+ @file, @name, @term_width = file, name, term_width
109
+ @inc = $:.map{|e| "-I#{e}" }.join(" ")
110
+ end
111
+
112
+ def run
113
+ init
114
+ execute
115
+ parse
116
+ done
117
+ self
118
+ end
119
+
120
+ def init
121
+ print "Running #@name... ".ljust(@term_width + 20)
122
+ end
123
+
124
+ def execute
125
+ @status, @stdout, @stderr = systemu("ruby #@inc #@file")
126
+ end
127
+
128
+ def done
129
+ @ran = true
130
+ end
131
+
132
+ def short_summary
133
+ f = lambda{|n| n.to_s.rjust(3) }
134
+ total = f[@passed + @failed] rescue nil
135
+ failed, passed = f[@failed], f[@passed]
136
+ color = :red
137
+ width = 22
138
+
139
+ if total_failure?
140
+ text = 'total failure'.center(width)
141
+ elsif failed?
142
+ text = "#{total} specs - #{failed} failed".rjust(width)
143
+ if @stdout =~ /Usually you should not worry about this failure, just install the/
144
+ lib = @stdout.scan(/^no such file to load -- (.*?)$/).flatten.first
145
+ text = "needs #{lib}".center(width)
146
+ @mark_passed = true
147
+ end
148
+ elsif (not @mark_passed) and succeeded?
149
+ color = :green
150
+ text = "#{total} specs - all passed".rjust(width)
151
+ end
152
+
153
+ puts "[ #{text.send(color)} ]"
154
+ end
155
+
156
+ def long_summary
157
+ puts "[ #@name ]".center(80, '-'), "ExitStatus:".yellow
158
+ pp @status
159
+ puts "StdOut:".yellow, @stdout, "StdErr:".yellow, @stderr
160
+ end
161
+
162
+ def parse
163
+ @passed = 0
164
+ @failed = 0
165
+ found = false
166
+ @stdout.grep(/(\d+) examples?, (\d+) failures?/)
167
+ @passed, @failed = $1.to_i, $2.to_i
168
+ end
169
+
170
+ def failed?
171
+ not succeeded?
172
+ end
173
+
174
+ def total_failure?
175
+ succeeded? == nil
176
+ end
177
+
178
+ def succeeded?
179
+ run unless @ran
180
+ return @mark_passed unless @mark_passed.nil?
181
+ crits = [
182
+ [@status.exitstatus.zero?, @stderr.empty?],
183
+ [@passed, @failed],
184
+ [@passed.nonzero?, @failed.zero?],
185
+ ]
186
+ crits.all?{|c| c.all? }
187
+ rescue
188
+ nil
189
+ end
190
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,27 @@
1
+ begin
2
+ require "rubygems"
3
+ rescue LoadError
4
+ nil
5
+ end
6
+
7
+ require "spec"
8
+ require 'ruport'
9
+ this = File.dirname(__FILE__)
10
+ lib = File.expand_path(File.join(this, '..', 'lib'))
11
+ $LOAD_PATH.unshift(lib)
12
+ require "ruport/util"
13
+
14
+ # Use this to require optional dependencies where tests are expected to fail if
15
+ # a library is not installed, for example Hpricot or Scruffy.
16
+ # It will be parsed by the wrapper and marked.
17
+
18
+ def testcase_requires(*following)
19
+ following.each do |file|
20
+ require(file.to_s)
21
+ end
22
+ rescue LoadError => ex
23
+ puts ex
24
+ puts "Can't run #{$0}: #{ex}"
25
+ puts "Usually you should not worry about this failure, just install the"
26
+ puts "library and try again (if you want to use that feature later on)"
27
+ end