ruport 0.5.4 → 0.6.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.
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