ruport 0.5.4 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/AUTHORS +13 -4
  2. data/CHANGELOG +15 -1
  3. data/Rakefile +1 -1
  4. data/bin/rope +5 -1
  5. data/examples/basic_grouping.rb +13 -3
  6. data/examples/latex_table.rb +17 -0
  7. data/examples/line_graph_report.rb +23 -0
  8. data/examples/line_graph_report.rb.rej +15 -0
  9. data/examples/line_graph_report.rb~ +23 -0
  10. data/examples/line_plotter.rb +1 -0
  11. data/examples/sql_erb.rb +20 -0
  12. data/examples/template.rb +1 -1
  13. data/examples/text_processors.rb +3 -9
  14. data/lib/ruport.rb +2 -3
  15. data/lib/ruport.rb~ +69 -0
  16. data/lib/ruport/attempt.rb +63 -0
  17. data/lib/ruport/data.rb +1 -1
  18. data/lib/ruport/data.rb.rej +5 -0
  19. data/lib/ruport/data.rb~ +1 -0
  20. data/lib/ruport/data/groupable.rb +20 -0
  21. data/lib/ruport/data/record.rb +18 -13
  22. data/lib/ruport/data/table.rb +92 -26
  23. data/lib/ruport/data/table.rb~ +329 -0
  24. data/lib/ruport/format.rb +7 -1
  25. data/lib/ruport/format/engine/table.rb +2 -1
  26. data/lib/ruport/format/plugin.rb +8 -8
  27. data/lib/ruport/format/plugin/csv_plugin.rb +5 -1
  28. data/lib/ruport/format/plugin/html_plugin.rb +12 -8
  29. data/lib/ruport/format/plugin/latex_plugin.rb +50 -0
  30. data/lib/ruport/format/plugin/text_plugin.rb +38 -33
  31. data/lib/ruport/meta_tools.rb +3 -3
  32. data/lib/ruport/query.rb +21 -9
  33. data/lib/ruport/report.rb +35 -20
  34. data/lib/ruport/report/graph.rb +14 -0
  35. data/lib/ruport/system_extensions.rb +9 -8
  36. data/test/_test_groupable.rb +0 -0
  37. data/test/samples/data.tsv +3 -0
  38. data/test/samples/erb_test.sql +1 -0
  39. data/test/samples/query_test.sql +1 -0
  40. data/test/test_collection.rb +1 -1
  41. data/test/test_format.rb +1 -1
  42. data/test/test_format_engine.rb +17 -0
  43. data/test/test_groupable.rb +41 -0
  44. data/test/test_invoice.rb +1 -1
  45. data/test/test_latex.rb +20 -0
  46. data/test/test_plugin.rb +59 -29
  47. data/test/test_query.rb +12 -6
  48. data/test/test_record.rb +23 -4
  49. data/test/test_record.rb.rej +46 -0
  50. data/test/test_report.rb +32 -7
  51. data/test/test_table.rb +152 -4
  52. data/test/ts_all.rb +21 -0
  53. data/test/unit.log +61 -154
  54. metadata +32 -12
  55. data/lib/ruport/rails.rb +0 -2
  56. data/lib/ruport/rails/reportable.rb +0 -58
data/lib/ruport/format.rb CHANGED
@@ -75,6 +75,10 @@ module Ruport
75
75
  options[:auto_render] = false; simple_interface(engine,options) })
76
76
  end
77
77
 
78
+ %w[engine plugin].each { |lib|
79
+ require "ruport/format/#{lib}"
80
+ }
81
+
78
82
  @@filters = Hash.new
79
83
 
80
84
  # To hook up a Format object to your current class, you need to pass it a
@@ -147,7 +151,9 @@ module Ruport
147
151
 
148
152
  options[:auto_render] ? my_engine.render : my_engine.dup
149
153
  end
154
+
155
+
156
+
150
157
  end
151
158
  end
152
159
 
153
- %w[engine plugin].each { |lib| require "ruport/format/#{lib}" }
@@ -5,7 +5,8 @@ module Ruport
5
5
  renderer do
6
6
  super
7
7
  active_plugin.rendered_field_names = ""
8
- build_field_names if (data.respond_to?(:column_names) &&
8
+ build_field_names if (data.respond_to?(:column_names) &&
9
+ !data.column_names.empty? &&
9
10
  data.column_names && show_field_names)
10
11
  a = active_plugin.render_table
11
12
  end
@@ -47,14 +47,14 @@ module Ruport
47
47
  singleton_class.send( :define_method, :build_field_names, &block)
48
48
  end
49
49
 
50
- def register_on(klass)
51
-
52
- if klass.kind_of? Symbol
53
- klass = Format::Engine.engine_classes[klass]
54
- end
55
-
56
- klass.accept_format_plugin(self)
50
+ def register_on(*args)
51
+ args.each { |klass|
52
+ if klass.kind_of? Symbol
53
+ klass = Format::Engine.engine_classes[klass]
54
+ end
57
55
 
56
+ klass.accept_format_plugin(self)
57
+ }
58
58
  rescue NoMethodError
59
59
  p caller
60
60
  end
@@ -72,5 +72,5 @@ module Ruport
72
72
  end
73
73
  end
74
74
  end
75
- plugins = %w[text csv pdf svg html]
75
+ plugins = %w[text csv pdf svg html latex]
76
76
  plugins.each { |p| require "ruport/format/plugin/#{p}_plugin" }
@@ -5,7 +5,11 @@ module Ruport
5
5
  helper(:init_plugin) { |eng| require "fastercsv" }
6
6
 
7
7
  format_field_names do
8
- FasterCSV.generate { |csv| csv << data.column_names }
8
+ if data.column_names.empty?
9
+ ""
10
+ else
11
+ FasterCSV.generate { |csv| csv << data.column_names }
12
+ end
9
13
  end
10
14
 
11
15
  renderer :table do
@@ -2,21 +2,25 @@ module Ruport
2
2
  class Format::Plugin
3
3
  class HTMLPlugin < Format::Plugin
4
4
 
5
- rendering_options :red_cloth_enabled => true,
6
- :erb_enabled => true
7
-
5
+ rendering_options :red_cloth_enabled => true, :erb_enabled => true
6
+
8
7
  renderer :document
9
8
 
10
9
  renderer :table do
11
- rc = data.inject(rendered_field_names) { |s,r|
10
+ data.inject("\t<table>\n" + rendered_field_names) do |s,r|
12
11
  row = r.map { |e| e.to_s.empty? ? "&nbsp;" : e }
13
- s + "|#{row.to_a.join('|')}|\n"
14
- }
15
- Format.document :data => rc, :plugin => :html
12
+ classstr = defined?(r.tags) ?
13
+ r.tags.inject("") {|cs,c| cs + " class='#{c}'" } : ""
14
+ s + "\t\t<tr#{classstr}>\n\t\t\t<td#{classstr}>" +
15
+ row.to_a.join("</td>\n\t\t\t<td#{classstr}>") +
16
+ "</td>\n\t\t</tr>\n"
17
+ end + "\t</table>"
16
18
  end
17
19
 
18
20
  format_field_names do
19
- s = "|_." + data.column_names.join(" |_.") + "|\n"
21
+ "\t\t<tr>\n\t\t\t<th>" +
22
+ data.column_names.join("</th>\n\t\t\t<th>") +
23
+ "</th>\n\t\t</tr>\n"
20
24
  end
21
25
 
22
26
  plugin_name :html
@@ -0,0 +1,50 @@
1
+ module Ruport
2
+ class Format::Plugin
3
+ class LatexPlugin < Format::Plugin
4
+
5
+ helper(:init_plugin) { |eng|
6
+ @report_header = "\\documentclass[11pt]{article}\n"
7
+ @report_header << "\\RequirePackage{lscape,longtable}\n"
8
+ @report_header << "\\begin{document}\n"
9
+
10
+ @report_footer = "\\end{document}\n"
11
+ }
12
+
13
+ renderer :table do
14
+ self.options = {} if self.options.nil?
15
+
16
+ @body = "\\begin{longtable}[c]{ "
17
+ @data.column_names.each do
18
+ @body << " p{2cm} "
19
+ end
20
+ @body << " }\n"
21
+ @body << "\\hline\n"
22
+ counter = 0
23
+ @data.column_names.each do |t|
24
+ @body << " & " unless counter == 0
25
+ @body << "\\textsc{#{t}}"
26
+ counter += 1
27
+ end
28
+ @body << "\\\\\n"
29
+ @body << "\\hline\n"
30
+ @body << "\\endhead\n"
31
+ @body << "\\endfoot\n"
32
+ @body << "\\hline\n"
33
+ @data.each do |r|
34
+ @body << r.data.join(" & ") + "\\\\\n"
35
+ @body << "\\hline\n"
36
+ end
37
+ unless options[:caption].nil?
38
+ @body << "\\caption[#{options[:caption]}]{#{options[:caption]}}\n"
39
+ end
40
+ @body << "\\end{longtable}\n"
41
+
42
+ @report_header + @body + @report_footer
43
+ end
44
+
45
+ plugin_name :latex
46
+ register_on :table_engine
47
+
48
+ end
49
+ end
50
+ end
@@ -9,56 +9,61 @@ module Ruport
9
9
  require "ruport/system_extensions"
10
10
 
11
11
  return "" if data.length == 0;
12
- th = "#{rendered_field_names}#{hr}"
13
-
12
+
13
+ calculate_max_col_widths
14
+
15
+ width = self.right_margin || SystemExtensions.terminal_width
16
+
17
+ s = "#{rendered_field_names}#{hr}"
18
+
14
19
  data.each { |r|
20
+ line = Array.new
15
21
  r.each_with_index { |f,i|
16
- r[i] = f.to_s.center(max_col_width(i))
22
+ line << f.to_s.center(max_col_width[i])
17
23
  }
24
+ s += "| #{line.join(' | ')} |\n"
18
25
  }
19
-
20
- a = data.inject(th){ |s,r|
21
- s + "| #{r.to_a.join(' | ')} |\n"
22
- } << hr
26
+ s += hr
23
27
 
24
- width = self.right_margin || SystemExtensions.terminal_width
25
-
26
- a.to_a.each { |r|
28
+ s.split("\n").each { |r|
27
29
  r.gsub!(/\A.{#{width+1},}/) { |m| m[0,width-2] + ">>" }
28
- }.join
30
+ }.join("\n") + "\n"
31
+
29
32
  end
30
33
 
31
34
  format_field_names do
32
- return "" if data.length == 0;
33
- data.column_names.each_with_index { |f,i|
34
- data.column_names[i] = f.to_s.center(max_col_width(i))
35
+ return "" if data.length == 0
36
+ calculate_max_col_widths
37
+ c=data.column_names.dup
38
+ c.each_with_index { |f,i|
39
+ c[i] = f.to_s.center(max_col_width[i])
35
40
  }
36
- "#{hr}| #{data.column_names.to_a.join(' | ')} |\n"
41
+ "#{hr}| #{c.to_a.join(' | ')} |\n"
37
42
  end
38
43
 
39
- action :max_col_width do |index|
40
- f = data.column_names if data.respond_to? :column_names
41
- d = Data::Table.new :column_names => f, :data => data
42
-
43
- cw = d.map { |r| r[index].to_s.length }.max
44
-
45
- return cw unless d.column_names
46
-
47
- nw = (index.kind_of?(Integer) ? d.column_names[index] : index ).to_s.length
48
-
49
- [cw,nw].max
44
+ action :max_col_width do
45
+ @max_col_width
50
46
  end
51
47
 
52
- action :table_width do
53
- f = data.column_names if data.respond_to? :column_names
54
- d = Data::Table.new:column_names => f, :data => data
55
-
56
- f = d[0].attributes || (0...d[0].length)
57
- f.inject(0) { |s,e| s + max_col_width(e) }
48
+ action :calculate_max_col_widths do
49
+ @max_col_width=Array.new
50
+ if defined?(data.column_names)
51
+ data.column_names.each_index do |i|
52
+ @max_col_width[i] = data.column_names[i].to_s.length
53
+ end
54
+ end
55
+
56
+ data.each {|r|
57
+ r.each_with_index { |f,i|
58
+ if !max_col_width[i] || f.to_s.length > max_col_width[i]
59
+ max_col_width[i] = f.to_s.length
60
+ end
61
+ }
62
+ }
58
63
  end
59
64
 
60
65
  action :hr do
61
- len = data[0].to_a.length * 3 + table_width + 1
66
+ len = max_col_width.inject(data[0].to_a.length * 3) {|s,e| s+e} + 1
62
67
  "+" + "-"*(len-2) + "+\n"
63
68
  end
64
69
 
@@ -5,7 +5,7 @@ module Ruport
5
5
  # formatting system, and might be helpful for other things.
6
6
  #
7
7
  module MetaTools
8
- # allows you to define an attribute accessor on the singleton_class.
8
+ # Allows you to define an attribute accessor on the singleton_class.
9
9
  #
10
10
  # Example:
11
11
  #
@@ -21,14 +21,14 @@ module Ruport
21
21
  self.send("#{sym}=",value)
22
22
  end
23
23
 
24
- # same as attribute, but takes an array of attributes
24
+ # Same as attribute, but takes an array of attributes
25
25
  #
26
26
  # e.g. attributes [:foo,:bar,:baz]
27
27
  def attributes(syms)
28
28
  syms.each { |s| attribute s }
29
29
  end
30
30
 
31
- # allows you to define a method on the singleton_class
31
+ # Allows you to define a method on the singleton_class
32
32
  #
33
33
  # Example:
34
34
  #
data/lib/ruport/query.rb CHANGED
@@ -59,11 +59,18 @@ module Ruport
59
59
  # :user => "greg", :password => "chunky_bacon" )
60
60
  #
61
61
  # # uses a SQL file stored on disk
62
- # Ruport::Query.new("my_query.sql",:origin => :file)
62
+ # Ruport::Query.new("my_query.sql")
63
+ #
64
+ # # explicitly use a file, even if it doesn't end in .sql
65
+ # Ruport::Query.new("foo",:origin => :file)
63
66
  def initialize(sql, options={})
64
67
  options = { :source => :default, :origin => :string }.merge(options)
65
- @sql = sql
66
- @statements = SqlSplit.new(get_query(options[:origin],sql))
68
+ options[:binding] ||= binding
69
+ options[:origin] = :file if sql =~ /.sql$/
70
+ q = Format.document :data => get_query(options[:origin],sql),
71
+ :plugin => :text, :class_binding => options[:binding]
72
+ @statements = SqlSplit.new(q)
73
+ @sql = @statements.join
67
74
 
68
75
  if options[:dsn]
69
76
  Ruport::Config.source :temp, :dsn => options[:dsn],
@@ -76,6 +83,7 @@ module Ruport
76
83
 
77
84
  @raw_data = options[:raw_data]
78
85
  @cache_enabled = options[:cache_enabled]
86
+ @params = options[:params]
79
87
  @cached_data = nil
80
88
  end
81
89
 
@@ -156,18 +164,22 @@ module Ruport
156
164
 
157
165
  private
158
166
 
159
- def query_data( query_text )
167
+ def query_data( query_text, params=@params )
160
168
 
161
169
  require "dbi"
162
170
 
163
171
  data = @raw_data ? [] : Data::Table.new
164
172
  DBI.connect(@dsn, @user, @password) do |dbh|
165
- dbh.execute(query_text) do |sth|
166
- return unless sth.fetchable?
167
- results = sth.fetch_all
168
- data.column_names = sth.column_names unless @raw_data
169
- results.each { |row| data << row.to_a }
173
+ if params
174
+ sth = dbh.execute(query_text,*params)
175
+ else
176
+ sth = dbh.execute(query_text)
170
177
  end
178
+ return unless sth.fetchable?
179
+ results = sth.fetch_all
180
+ data.column_names = sth.column_names unless @raw_data
181
+ results.each { |row| data << row.to_a }
182
+ sth.finish
171
183
  end
172
184
  data
173
185
  rescue NoMethodError; nil
data/lib/ruport/report.rb CHANGED
@@ -7,11 +7,11 @@
7
7
 
8
8
  #load the needed standard libraries.
9
9
  %w[erb yaml date logger fileutils].each { |lib| require lib }
10
-
11
- require "ruport/report/invoice"
10
+ %w[invoice graph].each { |lib| require "ruport/report/"+lib }
12
11
  require "forwardable"
13
12
 
14
13
  module Ruport
14
+
15
15
  # === Overview
16
16
  #
17
17
  # The Ruport::Report class povides a high level interface to most of Ruport's
@@ -72,6 +72,7 @@ module Ruport
72
72
  class Report
73
73
  extend Forwardable
74
74
 
75
+ include Ruport::Data::TableHelper
75
76
  # When initializing a report, you can provide a default mailer and source by
76
77
  # giving a name of a valid source or mailer you've defined via
77
78
  # Ruport::Config
@@ -131,7 +132,7 @@ module Ruport
131
132
  def query(sql, options={})
132
133
  options[:origin] ||= :string
133
134
  options[:source] ||= @source
134
-
135
+ options[:binding] ||= binding
135
136
  q = options[:query_obj] || Query.new(sql, options)
136
137
  if options[:yield_type].eql?(:by_row)
137
138
  q.each { |r| yield(r) }
@@ -178,16 +179,13 @@ module Ruport
178
179
  string
179
180
  end
180
181
 
181
- # preserved for backwards compatibility. please do not use.
182
- alias_method :render, :process_text
183
-
184
182
  # This allows you to create filters to be used by process_text
185
183
  #
186
184
  # The block is evaluated in the context of the instance.
187
185
  #
188
186
  # E.g
189
187
  #
190
- # text_processor(:unix_newlines) { results.gsub!(/\r\n/,"\n") }
188
+ # text_processor(:unix_newlines) { |r| r.gsub(/\r\n/,"\n") }
191
189
  def text_processor(label,&block)
192
190
  Format.register_filter(label, &block)
193
191
  end
@@ -196,8 +194,8 @@ module Ruport
196
194
  # Writes the contents of <tt>results</tt> to file. If a filename is
197
195
  # specified, it will use it. Otherwise, it will try to write to the file
198
196
  # specified by the <tt>file</tt> attribute.
199
- def write(my_file=file)
200
- File.open(my_file,"w") { |f| f << results }
197
+ def write(my_file=file,my_results=results)
198
+ File.open(my_file,"w") { |f| f << my_results }
201
199
  end
202
200
 
203
201
  # Like Report#write, but will append to a file rather than overwrite it if
@@ -235,22 +233,39 @@ module Ruport
235
233
  # (print something out, write to file, email, etc)
236
234
  #
237
235
  # Finally, it tries to call cleanup.
238
- def run(*reports)
239
- reports[0] ||= self.new
240
- reports.each { |rep|
241
- rep.prepare if rep.respond_to? :prepare;
242
- rep.results = rep.generate;
243
- yield(rep) if block_given?
244
- rep.cleanup if rep.respond_to? :cleanup;
245
- }
236
+ def run(options={})
237
+ options[:reports] ||= [self.new]
238
+
239
+ process = lambda do
240
+ options[:reports].each { |rep|
241
+ rep.prepare if rep.respond_to? :prepare
242
+ rep.results = rep.generate
243
+ yield(rep) if block_given?
244
+ rep.cleanup if rep.respond_to? :cleanup
245
+ }
246
+ end
247
+
248
+ if options[:tries] && (options[:interval] || options[:timeout])
249
+ code = Attempt.new { |a|
250
+ a.tries = options[:tries]
251
+ a.interval = options[:interval] if options[:interval]
252
+ a.timeout = options[:timeout] if options[:timeout]
253
+ a.log_level = options[:log_level]
254
+ }
255
+ code.attempt(&process)
256
+ else
257
+ process.call
258
+ end
246
259
  end
260
+
247
261
  end
248
262
 
249
263
  # this method passes <tt>self</tt> to Report.run
250
264
  #
251
265
  # Please see the class method for details.
252
- def run(&block)
253
- self.class.run(self,&block)
266
+ def run(options={},&block)
267
+ options[:reports] ||= [self]
268
+ self.class.run(options,&block)
254
269
  end
255
270
 
256
271
 
@@ -282,7 +297,7 @@ module Ruport
282
297
  m = Mailer.new
283
298
  m.to = adds
284
299
  yield(m)
285
- m.select_mailer @mailer
300
+ m.send(:select_mailer,@mailer)
286
301
  m.deliver :from => m.from, :to => m.to
287
302
  end
288
303