ruport-util 0.6.0 → 0.7.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.
@@ -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