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.
- data/AUTHORS +13 -4
- data/CHANGELOG +15 -1
- data/Rakefile +1 -1
- data/bin/rope +5 -1
- data/examples/basic_grouping.rb +13 -3
- data/examples/latex_table.rb +17 -0
- data/examples/line_graph_report.rb +23 -0
- data/examples/line_graph_report.rb.rej +15 -0
- data/examples/line_graph_report.rb~ +23 -0
- data/examples/line_plotter.rb +1 -0
- data/examples/sql_erb.rb +20 -0
- data/examples/template.rb +1 -1
- data/examples/text_processors.rb +3 -9
- data/lib/ruport.rb +2 -3
- data/lib/ruport.rb~ +69 -0
- data/lib/ruport/attempt.rb +63 -0
- data/lib/ruport/data.rb +1 -1
- data/lib/ruport/data.rb.rej +5 -0
- data/lib/ruport/data.rb~ +1 -0
- data/lib/ruport/data/groupable.rb +20 -0
- data/lib/ruport/data/record.rb +18 -13
- data/lib/ruport/data/table.rb +92 -26
- data/lib/ruport/data/table.rb~ +329 -0
- data/lib/ruport/format.rb +7 -1
- data/lib/ruport/format/engine/table.rb +2 -1
- data/lib/ruport/format/plugin.rb +8 -8
- data/lib/ruport/format/plugin/csv_plugin.rb +5 -1
- data/lib/ruport/format/plugin/html_plugin.rb +12 -8
- data/lib/ruport/format/plugin/latex_plugin.rb +50 -0
- data/lib/ruport/format/plugin/text_plugin.rb +38 -33
- data/lib/ruport/meta_tools.rb +3 -3
- data/lib/ruport/query.rb +21 -9
- data/lib/ruport/report.rb +35 -20
- data/lib/ruport/report/graph.rb +14 -0
- data/lib/ruport/system_extensions.rb +9 -8
- data/test/_test_groupable.rb +0 -0
- data/test/samples/data.tsv +3 -0
- data/test/samples/erb_test.sql +1 -0
- data/test/samples/query_test.sql +1 -0
- data/test/test_collection.rb +1 -1
- data/test/test_format.rb +1 -1
- data/test/test_format_engine.rb +17 -0
- data/test/test_groupable.rb +41 -0
- data/test/test_invoice.rb +1 -1
- data/test/test_latex.rb +20 -0
- data/test/test_plugin.rb +59 -29
- data/test/test_query.rb +12 -6
- data/test/test_record.rb +23 -4
- data/test/test_record.rb.rej +46 -0
- data/test/test_report.rb +32 -7
- data/test/test_table.rb +152 -4
- data/test/ts_all.rb +21 -0
- data/test/unit.log +61 -154
- metadata +32 -12
- data/lib/ruport/rails.rb +0 -2
- 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
|
data/lib/ruport/format/plugin.rb
CHANGED
@@ -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(
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
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
|
-
|
7
|
-
|
5
|
+
rendering_options :red_cloth_enabled => true, :erb_enabled => true
|
6
|
+
|
8
7
|
renderer :document
|
9
8
|
|
10
9
|
renderer :table do
|
11
|
-
|
10
|
+
data.inject("\t<table>\n" + rendered_field_names) do |s,r|
|
12
11
|
row = r.map { |e| e.to_s.empty? ? " " : e }
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
34
|
-
|
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}| #{
|
41
|
+
"#{hr}| #{c.to_a.join(' | ')} |\n"
|
37
42
|
end
|
38
43
|
|
39
|
-
action :max_col_width do
|
40
|
-
|
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 :
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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 +
|
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
|
|
data/lib/ruport/meta_tools.rb
CHANGED
@@ -5,7 +5,7 @@ module Ruport
|
|
5
5
|
# formatting system, and might be helpful for other things.
|
6
6
|
#
|
7
7
|
module MetaTools
|
8
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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"
|
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
|
-
|
66
|
-
|
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
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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) {
|
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 <<
|
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(
|
239
|
-
reports
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
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(
|
253
|
-
self
|
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
|
300
|
+
m.send(:select_mailer,@mailer)
|
286
301
|
m.deliver :from => m.from, :to => m.to
|
287
302
|
end
|
288
303
|
|