ruport 0.5.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +7 -1
- data/Rakefile +1 -1
- data/lib/ruport.rb +1 -1
- data/lib/ruport/format/engine.rb +2 -109
- data/lib/ruport/format/engine/document.rb +28 -0
- data/lib/ruport/format/engine/graph.rb +18 -0
- data/lib/ruport/format/engine/invoice.rb +23 -0
- data/lib/ruport/format/engine/table.rb +53 -0
- data/lib/ruport/format/plugin.rb +7 -287
- data/lib/ruport/format/plugin/csv_plugin.rb +22 -0
- data/lib/ruport/format/plugin/html_plugin.rb +27 -0
- data/lib/ruport/format/plugin/pdf_plugin.rb +126 -0
- data/lib/ruport/format/plugin/svg_plugin.rb +61 -0
- data/lib/ruport/format/plugin/text_plugin.rb +71 -0
- data/lib/ruport/system_extensions.rb +8 -8
- data/lib/ruport/system_extensions.rb.rej +32 -0
- data/test/test_collection.rb +5 -0
- data/test/unit.log +12 -0
- metadata +14 -2
data/CHANGELOG
CHANGED
@@ -1,4 +1,10 @@
|
|
1
|
-
Tnghe current version of Ruby Reports is 0.5.
|
1
|
+
Tnghe current version of Ruby Reports is 0.5.1
|
2
|
+
|
3
|
+
changes since 0.5.0
|
4
|
+
|
5
|
+
- Broke plugins and engines out into their own files
|
6
|
+
|
7
|
+
- Fixed a bug which prevented TextPlugin from working right in Windows
|
2
8
|
|
3
9
|
changes since 0.4.99
|
4
10
|
|
data/Rakefile
CHANGED
@@ -23,7 +23,7 @@ end
|
|
23
23
|
|
24
24
|
spec = Gem::Specification.new do |spec|
|
25
25
|
spec.name = LEAN ? "lean-ruport" : "ruport"
|
26
|
-
spec.version = "0.5.
|
26
|
+
spec.version = "0.5.1"
|
27
27
|
spec.platform = Gem::Platform::RUBY
|
28
28
|
spec.summary = "A generalized Ruby report generation and templating engine."
|
29
29
|
spec.files = Dir.glob("{examples,lib,test,bin}/**/**/*") +
|
data/lib/ruport.rb
CHANGED
data/lib/ruport/format/engine.rb
CHANGED
@@ -101,114 +101,7 @@ module Ruport
|
|
101
101
|
end
|
102
102
|
|
103
103
|
private_class_method :new
|
104
|
+
engines = %w[graph invoice table document]
|
105
|
+
engines.each { |e| require "lib/ruport/format/engine/#{e}" }
|
104
106
|
end
|
105
|
-
|
106
|
-
class Format::Engine::Graph < Ruport::Format::Engine
|
107
|
-
|
108
|
-
attributes [:width, :height, :style, :title]
|
109
|
-
|
110
|
-
renderer do
|
111
|
-
super
|
112
|
-
active_plugin.render_graph
|
113
|
-
end
|
114
|
-
|
115
|
-
alias_engine Graph, :graph_engine
|
116
|
-
Ruport::Format.build_interface_for Graph, :graph
|
117
|
-
|
118
|
-
end
|
119
|
-
|
120
|
-
class Format::Engine::Invoice < Ruport::Format::Engine
|
121
|
-
|
122
|
-
# order meta data
|
123
|
-
attributes [ :customer_info, :company_info,
|
124
|
-
:comments, :order_info, :title]
|
125
|
-
|
126
|
-
renderer do
|
127
|
-
super
|
128
|
-
build_headers
|
129
|
-
build_body
|
130
|
-
build_footer
|
131
|
-
active_plugin.render_invoice
|
132
|
-
end
|
133
|
-
|
134
|
-
alias_engine Invoice, :invoice_engine
|
135
|
-
Ruport::Format.build_interface_for Invoice, :invoice
|
136
|
-
|
137
|
-
end
|
138
|
-
|
139
|
-
class Format::Engine::Table < Format::Engine
|
140
|
-
|
141
|
-
renderer do
|
142
|
-
super
|
143
|
-
active_plugin.rendered_field_names = ""
|
144
|
-
build_field_names if (data.respond_to?(:column_names) &&
|
145
|
-
data.column_names && show_field_names)
|
146
|
-
a = active_plugin.render_table
|
147
|
-
end
|
148
|
-
|
149
|
-
class << self
|
150
|
-
|
151
|
-
def rewrite_column(key,&block)
|
152
|
-
data.each { |r| r[key] = block[r] }
|
153
|
-
end
|
154
|
-
|
155
|
-
def num_cols
|
156
|
-
data[0].to_a.length
|
157
|
-
end
|
158
|
-
|
159
|
-
def prune(limit=data[0].length)
|
160
|
-
limit.times do |field|
|
161
|
-
last = ""
|
162
|
-
data.each_cons(2) { |l,e|
|
163
|
-
next if field.nonzero? && e[field-1]
|
164
|
-
last = l[field] if l[field]
|
165
|
-
e[field] = nil if e[field] == last
|
166
|
-
}
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
attr_accessor :show_field_names
|
171
|
-
|
172
|
-
private
|
173
|
-
|
174
|
-
def build_field_names
|
175
|
-
if active_plugin.respond_to?(:build_field_names)
|
176
|
-
active_plugin.rendered_field_names =
|
177
|
-
active_plugin.build_field_names
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
end
|
182
|
-
|
183
|
-
alias_engine Table, :table_engine
|
184
|
-
Format.build_interface_for Table, :table
|
185
|
-
self.show_field_names = true
|
186
|
-
end
|
187
|
-
|
188
|
-
class Format::Engine::Document < Format::Engine
|
189
|
-
|
190
|
-
renderer do
|
191
|
-
super
|
192
|
-
apply_erb if erb_enabled
|
193
|
-
apply_red_cloth if red_cloth_enabled
|
194
|
-
active_plugin.render_document
|
195
|
-
end
|
196
|
-
|
197
|
-
class << self
|
198
|
-
|
199
|
-
attr_accessor :red_cloth_enabled
|
200
|
-
attr_accessor :erb_enabled
|
201
|
-
|
202
|
-
def apply_red_cloth
|
203
|
-
require "redcloth"
|
204
|
-
active_plugin.data = RedCloth.new(active_plugin.data).to_html
|
205
|
-
end
|
206
|
-
|
207
|
-
end
|
208
|
-
|
209
|
-
alias_engine Document, :document_engine
|
210
|
-
Format.build_interface_for Document, :document
|
211
|
-
end
|
212
|
-
|
213
|
-
|
214
107
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Ruport
|
2
|
+
class Format::Engine
|
3
|
+
class Document < Format::Engine
|
4
|
+
renderer do
|
5
|
+
super
|
6
|
+
apply_erb if erb_enabled
|
7
|
+
apply_red_cloth if red_cloth_enabled
|
8
|
+
active_plugin.render_document
|
9
|
+
end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
attr_accessor :red_cloth_enabled
|
14
|
+
attr_accessor :erb_enabled
|
15
|
+
|
16
|
+
def apply_red_cloth
|
17
|
+
require "redcloth"
|
18
|
+
active_plugin.data = RedCloth.new(active_plugin.data).to_html
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_engine Document, :document_engine
|
24
|
+
Format.build_interface_for Document, :document
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Ruport
|
2
|
+
class Format::Engine
|
3
|
+
class Graph < Format::Engine
|
4
|
+
|
5
|
+
attributes [:width, :height, :style, :title]
|
6
|
+
|
7
|
+
renderer do
|
8
|
+
super
|
9
|
+
active_plugin.render_graph
|
10
|
+
end
|
11
|
+
|
12
|
+
alias_engine Graph, :graph_engine
|
13
|
+
Ruport::Format.build_interface_for Graph, :graph
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Ruport
|
2
|
+
class Format::Engine
|
3
|
+
class Format::Engine::Invoice < Ruport::Format::Engine
|
4
|
+
|
5
|
+
# order meta data
|
6
|
+
attributes [ :customer_info, :company_info,
|
7
|
+
:comments, :order_info, :title]
|
8
|
+
|
9
|
+
renderer do
|
10
|
+
super
|
11
|
+
build_headers
|
12
|
+
build_body
|
13
|
+
build_footer
|
14
|
+
active_plugin.render_invoice
|
15
|
+
end
|
16
|
+
|
17
|
+
alias_engine Invoice, :invoice_engine
|
18
|
+
Ruport::Format.build_interface_for Invoice, :invoice
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Ruport
|
2
|
+
class Format::Engine
|
3
|
+
class Table < Format::Engine
|
4
|
+
|
5
|
+
renderer do
|
6
|
+
super
|
7
|
+
active_plugin.rendered_field_names = ""
|
8
|
+
build_field_names if (data.respond_to?(:column_names) &&
|
9
|
+
data.column_names && show_field_names)
|
10
|
+
a = active_plugin.render_table
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def rewrite_column(key,&block)
|
16
|
+
data.each { |r| r[key] = block[r] }
|
17
|
+
end
|
18
|
+
|
19
|
+
def num_cols
|
20
|
+
data[0].to_a.length
|
21
|
+
end
|
22
|
+
|
23
|
+
def prune(limit=data[0].length)
|
24
|
+
limit.times do |field|
|
25
|
+
last = ""
|
26
|
+
data.each_cons(2) { |l,e|
|
27
|
+
next if field.nonzero? && e[field-1]
|
28
|
+
last = l[field] if l[field]
|
29
|
+
e[field] = nil if e[field] == last
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_accessor :show_field_names
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def build_field_names
|
39
|
+
if active_plugin.respond_to?(:build_field_names)
|
40
|
+
active_plugin.rendered_field_names =
|
41
|
+
active_plugin.build_field_names
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
alias_engine Table, :table_engine
|
48
|
+
Format.build_interface_for Table, :table
|
49
|
+
self.show_field_names = true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
data/lib/ruport/format/plugin.rb
CHANGED
@@ -2,10 +2,13 @@ class InvalidGraphDataError < RuntimeError; end
|
|
2
2
|
class InvalidGraphOptionError < RuntimeError; end
|
3
3
|
|
4
4
|
require 'bigdecimal'
|
5
|
+
require 'tempfile'
|
6
|
+
|
7
|
+
class RenderingError < RuntimeError; end
|
5
8
|
|
6
9
|
module Ruport
|
7
10
|
class Format::Plugin
|
8
|
-
|
11
|
+
|
9
12
|
class << self
|
10
13
|
|
11
14
|
attr_accessor :data
|
@@ -62,292 +65,9 @@ module Ruport
|
|
62
65
|
attr_accessor :rendered_field_names
|
63
66
|
attr_accessor :pre, :post
|
64
67
|
attr_accessor :header, :footer
|
65
|
-
end
|
66
|
-
|
67
|
-
|
68
|
-
class CSVPlugin < Format::Plugin
|
69
|
-
|
70
|
-
helper(:init_plugin) { |eng| require "fastercsv" }
|
71
|
-
|
72
|
-
format_field_names do
|
73
|
-
FasterCSV.generate { |csv| csv << data.column_names }
|
74
|
-
end
|
75
|
-
|
76
|
-
renderer :table do
|
77
|
-
rendered_field_names +
|
78
|
-
FasterCSV.generate { |csv| data.each { |r| csv << r } }
|
79
|
-
end
|
80
|
-
|
81
|
-
plugin_name :csv
|
82
|
-
register_on :table_engine
|
83
|
-
end
|
84
|
-
|
85
|
-
class SVGPlugin < Format::Plugin
|
86
|
-
|
87
|
-
helper(:init_plugin) { |eng|
|
88
|
-
# check the supplied data can be used for graphing
|
89
|
-
data.each { |r|
|
90
|
-
if data.column_names.size != r.data.size
|
91
|
-
raise InvalidGraphDataError, "Column names and data do not match"
|
92
|
-
end
|
93
|
-
r.data.each { |c|
|
94
|
-
begin
|
95
|
-
c = BigDecimal.new(c) unless c.kind_of?(Float) ||
|
96
|
-
c.kind_of?(Fixnum) || c.kind_of?(BigDecimal)
|
97
|
-
rescue
|
98
|
-
raise InvalidGraphDataError, "Unable to convert #{c.to_s} into a number"
|
99
|
-
end
|
100
|
-
}
|
101
|
-
}
|
102
|
-
|
103
|
-
raise InvalidGraphOptionError, 'You must provide a width before rendering a graph' if eng.width.nil?
|
104
|
-
raise InvalidGraphOptionError, 'You must provide a height before rendering a graph' if eng.height.nil?
|
105
|
-
raise InvalidGraphOptionError, 'You must provide a style before rendering a graph' if eng.style.nil?
|
106
|
-
if eng.style != :area && eng.style != :bar &&
|
107
|
-
eng.style != :line &&
|
108
|
-
eng.style != :smiles &&
|
109
|
-
eng.style != :stacked
|
110
|
-
raise InvalidGraphOptionError, 'Invalid graph style'
|
111
|
-
end
|
112
|
-
|
113
|
-
require 'scruffy'
|
114
|
-
@graph = Scruffy::Graph.new
|
115
|
-
@graph.title = eng.title unless eng.title.nil?
|
116
|
-
@graph.theme = Scruffy::Themes::Mephisto.new
|
117
|
-
@graph.point_markers = @data.column_names
|
118
|
-
@graph_style = eng.style
|
119
|
-
@graph_width = eng.width
|
120
|
-
@graph_height = eng.height
|
121
|
-
}
|
122
|
-
|
123
|
-
renderer :graph do
|
124
|
-
|
125
|
-
data.each_with_index { |r,i|
|
126
|
-
@graph.add(@graph_style,
|
127
|
-
r.tags[0] || 'series ' + (i+1).to_s,
|
128
|
-
r.data)
|
129
|
-
}
|
130
|
-
|
131
|
-
# return the rendered graph
|
132
|
-
@graph.render(:size => [@graph_width, @graph_height])
|
133
|
-
end
|
134
|
-
|
135
|
-
plugin_name :svg
|
136
|
-
register_on :graph_engine
|
137
|
-
end
|
138
|
-
|
139
|
-
class TextPlugin < Format::Plugin
|
140
|
-
rendering_options :erb_enabled => true, :red_cloth_enabled => false
|
141
|
-
|
142
|
-
renderer :document
|
143
|
-
|
144
|
-
renderer :table do
|
145
|
-
require "ruport/system_extensions"
|
146
|
-
|
147
|
-
th = "#{rendered_field_names}#{hr}"
|
148
|
-
|
149
|
-
data.each { |r|
|
150
|
-
r.each_with_index { |f,i|
|
151
|
-
r[i] = f.to_s.center(max_col_width(i))
|
152
|
-
}
|
153
|
-
}
|
154
|
-
|
155
|
-
a = data.inject(th){ |s,r|
|
156
|
-
s + "| #{r.to_a.join(' | ')} |\n"
|
157
|
-
} << hr
|
158
|
-
|
159
|
-
width = self.right_margin || SystemExtensions.terminal_width
|
160
|
-
|
161
|
-
a.to_a.each { |r|
|
162
|
-
r.gsub!(/\A.{#{width+1},}/) { |m| m[0,width-2] + ">>" }
|
163
|
-
}.join
|
164
|
-
end
|
165
|
-
|
166
|
-
format_field_names do
|
167
|
-
data.column_names.each_with_index { |f,i|
|
168
|
-
data.column_names[i] = f.to_s.center(max_col_width(i))
|
169
|
-
}
|
170
|
-
"#{hr}| #{data.column_names.to_a.join(' | ')} |\n"
|
171
|
-
end
|
172
|
-
|
173
|
-
action :max_col_width do |index|
|
174
|
-
f = data.column_names if data.respond_to? :column_names
|
175
|
-
d = Data::Table.new :column_names => f, :data => data
|
176
|
-
|
177
|
-
cw = d.map { |r| r[index].to_s.length }.max
|
178
|
-
|
179
|
-
return cw unless d.column_names
|
180
|
-
|
181
|
-
nw = (index.kind_of?(Integer) ? d.column_names[index] : index ).to_s.length
|
182
|
-
|
183
|
-
[cw,nw].max
|
184
|
-
end
|
185
|
-
|
186
|
-
action :table_width do
|
187
|
-
f = data.column_names if data.respond_to? :column_names
|
188
|
-
d = Data::Table.new:column_names => f, :data => data
|
189
|
-
|
190
|
-
f = d[0].attributes || (0...d[0].length)
|
191
|
-
|
192
|
-
f.inject(0) { |s,e| s + max_col_width(e) }
|
193
|
-
end
|
194
|
-
|
195
|
-
action :hr do
|
196
|
-
len = data[0].to_a.length * 3 + table_width + 1
|
197
|
-
"+" + "-"*(len-2) + "+\n"
|
198
|
-
end
|
199
|
-
|
200
|
-
attribute :right_margin
|
201
|
-
plugin_name :text
|
202
|
-
register_on :table_engine
|
203
|
-
register_on :document_engine
|
204
|
-
end
|
205
|
-
|
206
|
-
class PDFPlugin < Format::Plugin
|
207
|
-
attribute :pdf
|
208
|
-
attribute :paper
|
209
|
-
|
210
|
-
helper(:init_plugin) {
|
211
|
-
require "pdf/writer"
|
212
|
-
require "pdf/simpletable"
|
213
|
-
self.pdf = PDF::Writer.new( :paper => paper || "LETTER" )
|
214
|
-
}
|
215
|
-
|
216
|
-
renderer :table do
|
217
|
-
pre[pdf] if pre
|
218
|
-
PDF::SimpleTable.new do |table|
|
219
|
-
table.maximum_width = 500
|
220
|
-
table.orientation = :center
|
221
|
-
table.data = data
|
222
|
-
m = "Sorry, cant build PDFs from array like things (yet)"
|
223
|
-
raise m if self.rendered_field_names.empty?
|
224
|
-
table.column_order = self.rendered_field_names
|
225
|
-
table.render_on(pdf)
|
226
|
-
end
|
227
|
-
post[pdf] if post
|
228
|
-
pdf.render
|
229
|
-
end
|
230
|
-
|
231
|
-
format_field_names { data.column_names }
|
232
|
-
|
233
|
-
renderer(:invoice) { pdf.render }
|
234
|
-
|
235
|
-
# Company Information in top lefthand corner
|
236
|
-
helper(:build_company_header, :engine => :invoice_engine) { |eng|
|
237
|
-
@tod = pdf.y
|
238
|
-
text_box(eng.company_info)
|
239
|
-
}
|
240
|
-
|
241
|
-
helper(:build_headers, :engine => :invoice_engine) { |eng|
|
242
|
-
build_company_header_helper(eng)
|
243
|
-
build_customer_header_helper(eng)
|
244
|
-
build_title_helper(eng)
|
245
|
-
build_order_header_helper(eng)
|
246
|
-
}
|
247
|
-
|
248
|
-
helper(:build_order_header, :engine => :invoice_engine) { |eng|
|
249
|
-
if eng.order_info
|
250
|
-
text_box(eng.order_info, :position => 350)
|
251
|
-
end
|
252
|
-
}
|
253
|
-
|
254
|
-
helper(:build_title, :engine => :invoice_engine) { |eng|
|
255
|
-
pdf.y = @tod
|
256
|
-
if eng.title
|
257
|
-
pdf.text eng.title, :left => 350, :font_size => 14
|
258
|
-
pdf.y -= 10
|
259
|
-
end
|
260
|
-
}
|
261
|
-
|
262
|
-
helper(:build_footer, :engine => :invoice_engine) { |eng|
|
263
|
-
# footer
|
264
|
-
pdf.open_object do |footer|
|
265
|
-
pdf.save_state
|
266
|
-
pdf.stroke_color! Color::RGB::Black
|
267
|
-
pdf.stroke_style! PDF::Writer::StrokeStyle::DEFAULT
|
268
|
-
if eng.comments
|
269
|
-
pdf.y -= 20
|
270
|
-
text_box eng.comments, :position => 110, :width => 400,
|
271
|
-
:font_size => 14
|
272
|
-
end
|
273
|
-
pdf.add_text_wrap( 50, 20, 200, "Printed at " +
|
274
|
-
Time.now.strftime("%H:%M %d/%m/%Y"), 8)
|
275
|
-
|
276
|
-
pdf.restore_state
|
277
|
-
pdf.close_object
|
278
|
-
pdf.add_object(footer, :all_pages)
|
279
|
-
end
|
280
|
-
pdf.stop_page_numbering(true, :current)
|
281
|
-
}
|
282
|
-
|
283
|
-
helper(:build_body, :engine => :invoice_engine) do
|
284
|
-
pdf.start_page_numbering(500, 20, 8, :right)
|
285
|
-
|
286
|
-
# order contents
|
287
|
-
pdf.y = 620
|
288
|
-
|
289
|
-
PDF::SimpleTable.new do |table|
|
290
|
-
table.width = 450
|
291
|
-
table.orientation = :center
|
292
|
-
table.data = data
|
293
|
-
table.show_lines = :outer
|
294
|
-
table.column_order = data.column_names
|
295
|
-
table.render_on(pdf)
|
296
|
-
table.font_size = 12
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
|
-
# Order details
|
301
|
-
helper(:build_customer_header, :engine => :invoice_engine) { |eng|
|
302
|
-
pdf.y -= 10
|
303
|
-
text_box(eng.customer_info)
|
304
|
-
}
|
305
|
-
|
306
|
-
def self.text_box(content,options={})
|
307
|
-
PDF::SimpleTable.new do |table|
|
308
|
-
table.data = content.to_a.inject([]) do |s,line|
|
309
|
-
s << { "value" => line }
|
310
|
-
end
|
311
|
-
table.column_order = "value"
|
312
|
-
table.show_headings = false
|
313
|
-
table.show_lines = :outer
|
314
|
-
table.shade_rows = :none
|
315
|
-
table.width = options[:width] || 200
|
316
|
-
table.orientation = options[:orientation] || :right
|
317
|
-
table.position = options[:position] || :left
|
318
|
-
table.font_size = options[:font_size] || 10
|
319
|
-
table.render_on(pdf)
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
plugin_name :pdf
|
324
|
-
register_on :table_engine
|
325
|
-
register_on :invoice_engine
|
326
|
-
end
|
327
|
-
|
328
|
-
class HTMLPlugin < Format::Plugin
|
329
|
-
|
330
|
-
rendering_options :red_cloth_enabled => true, :erb_enabled => true
|
331
|
-
|
332
|
-
renderer :document
|
333
|
-
|
334
|
-
renderer :table do
|
335
|
-
rc = data.inject(rendered_field_names) { |s,r|
|
336
|
-
row = r.map { |e| e.to_s.empty? ? " " : e }
|
337
|
-
s + "|#{row.to_a.join('|')}|\n"
|
338
|
-
}
|
339
|
-
Format.document :data => rc, :plugin => :html
|
340
|
-
end
|
341
|
-
|
342
|
-
format_field_names do
|
343
|
-
s = "|_." + data.column_names.join(" |_.") + "|\n"
|
344
|
-
end
|
345
|
-
|
346
|
-
plugin_name :html
|
347
|
-
register_on :table_engine
|
348
|
-
register_on :document_engine
|
349
68
|
|
69
|
+
plugins = %w[text csv pdf svg html]
|
70
|
+
plugins.each { |p| require "ruport/format/plugin/#{p}_plugin" }
|
350
71
|
end
|
351
|
-
|
352
|
-
end
|
72
|
+
end
|
353
73
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Ruport
|
2
|
+
class Format::Plugin
|
3
|
+
class CSVPlugin < Format::Plugin
|
4
|
+
|
5
|
+
helper(:init_plugin) { |eng| require "fastercsv" }
|
6
|
+
|
7
|
+
format_field_names do
|
8
|
+
FasterCSV.generate { |csv| csv << data.column_names }
|
9
|
+
end
|
10
|
+
|
11
|
+
renderer :table do
|
12
|
+
rendered_field_names +
|
13
|
+
FasterCSV.generate { |csv| data.each { |r| csv << r } }
|
14
|
+
end
|
15
|
+
|
16
|
+
plugin_name :csv
|
17
|
+
register_on :table_engine
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Ruport
|
2
|
+
class Format::Plugin
|
3
|
+
class HTMLPlugin < Format::Plugin
|
4
|
+
|
5
|
+
rendering_options :red_cloth_enabled => true, :erb_enabled => true
|
6
|
+
|
7
|
+
renderer :document
|
8
|
+
|
9
|
+
renderer :table do
|
10
|
+
rc = data.inject(rendered_field_names) { |s,r|
|
11
|
+
row = r.map { |e| e.to_s.empty? ? " " : e }
|
12
|
+
s + "|#{row.to_a.join('|')}|\n"
|
13
|
+
}
|
14
|
+
Format.document :data => rc, :plugin => :html
|
15
|
+
end
|
16
|
+
|
17
|
+
format_field_names do
|
18
|
+
s = "|_." + data.column_names.join(" |_.") + "|\n"
|
19
|
+
end
|
20
|
+
|
21
|
+
plugin_name :html
|
22
|
+
register_on :table_engine
|
23
|
+
register_on :document_engine
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module Ruport
|
2
|
+
class Format::Plugin
|
3
|
+
class PDFPlugin < Format::Plugin
|
4
|
+
attribute :pdf
|
5
|
+
attribute :paper
|
6
|
+
|
7
|
+
helper(:init_plugin) {
|
8
|
+
require "pdf/writer"
|
9
|
+
require "pdf/simpletable"
|
10
|
+
self.pdf = PDF::Writer.new( :paper => paper || "LETTER" )
|
11
|
+
}
|
12
|
+
|
13
|
+
renderer :table do
|
14
|
+
pre[pdf] if pre
|
15
|
+
PDF::SimpleTable.new do |table|
|
16
|
+
table.maximum_width = 500
|
17
|
+
table.orientation = :center
|
18
|
+
table.data = data
|
19
|
+
m = "Sorry, cant build PDFs from array like things (yet)"
|
20
|
+
raise m if self.rendered_field_names.empty?
|
21
|
+
table.column_order = self.rendered_field_names
|
22
|
+
table.render_on(pdf)
|
23
|
+
end
|
24
|
+
post[pdf] if post
|
25
|
+
pdf.render
|
26
|
+
end
|
27
|
+
|
28
|
+
format_field_names { data.column_names }
|
29
|
+
|
30
|
+
renderer(:invoice) { pdf.render }
|
31
|
+
|
32
|
+
# Company Information in top lefthand corner
|
33
|
+
helper(:build_company_header, :engine => :invoice_engine) { |eng|
|
34
|
+
@tod = pdf.y
|
35
|
+
text_box(eng.company_info)
|
36
|
+
}
|
37
|
+
|
38
|
+
helper(:build_headers, :engine => :invoice_engine) { |eng|
|
39
|
+
build_company_header_helper(eng)
|
40
|
+
build_customer_header_helper(eng)
|
41
|
+
build_title_helper(eng)
|
42
|
+
build_order_header_helper(eng)
|
43
|
+
}
|
44
|
+
|
45
|
+
helper(:build_order_header, :engine => :invoice_engine) { |eng|
|
46
|
+
if eng.order_info
|
47
|
+
text_box(eng.order_info, :position => 350)
|
48
|
+
end
|
49
|
+
}
|
50
|
+
|
51
|
+
helper(:build_title, :engine => :invoice_engine) { |eng|
|
52
|
+
pdf.y = @tod
|
53
|
+
if eng.title
|
54
|
+
pdf.text eng.title, :left => 350, :font_size => 14
|
55
|
+
pdf.y -= 10
|
56
|
+
end
|
57
|
+
}
|
58
|
+
|
59
|
+
helper(:build_footer, :engine => :invoice_engine) { |eng|
|
60
|
+
# footer
|
61
|
+
pdf.open_object do |footer|
|
62
|
+
pdf.save_state
|
63
|
+
pdf.stroke_color! Color::RGB::Black
|
64
|
+
pdf.stroke_style! PDF::Writer::StrokeStyle::DEFAULT
|
65
|
+
if eng.comments
|
66
|
+
pdf.y -= 20
|
67
|
+
text_box eng.comments, :position => 110, :width => 400,
|
68
|
+
:font_size => 14
|
69
|
+
end
|
70
|
+
pdf.add_text_wrap( 50, 20, 200, "Printed at " +
|
71
|
+
Time.now.strftime("%H:%M %d/%m/%Y"), 8)
|
72
|
+
|
73
|
+
pdf.restore_state
|
74
|
+
pdf.close_object
|
75
|
+
pdf.add_object(footer, :all_pages)
|
76
|
+
end
|
77
|
+
pdf.stop_page_numbering(true, :current)
|
78
|
+
}
|
79
|
+
|
80
|
+
helper(:build_body, :engine => :invoice_engine) do
|
81
|
+
pdf.start_page_numbering(500, 20, 8, :right)
|
82
|
+
|
83
|
+
# order contents
|
84
|
+
pdf.y = 620
|
85
|
+
|
86
|
+
PDF::SimpleTable.new do |table|
|
87
|
+
table.width = 450
|
88
|
+
table.orientation = :center
|
89
|
+
table.data = data
|
90
|
+
table.show_lines = :outer
|
91
|
+
table.column_order = data.column_names
|
92
|
+
table.render_on(pdf)
|
93
|
+
table.font_size = 12
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Order details
|
98
|
+
helper(:build_customer_header, :engine => :invoice_engine) { |eng|
|
99
|
+
pdf.y -= 10
|
100
|
+
text_box(eng.customer_info)
|
101
|
+
}
|
102
|
+
|
103
|
+
def self.text_box(content,options={})
|
104
|
+
PDF::SimpleTable.new do |table|
|
105
|
+
table.data = content.to_a.inject([]) do |s,line|
|
106
|
+
s << { "value" => line }
|
107
|
+
end
|
108
|
+
table.column_order = "value"
|
109
|
+
table.show_headings = false
|
110
|
+
table.show_lines = :outer
|
111
|
+
table.shade_rows = :none
|
112
|
+
table.width = options[:width] || 200
|
113
|
+
table.orientation = options[:orientation] || :right
|
114
|
+
table.position = options[:position] || :left
|
115
|
+
table.font_size = options[:font_size] || 10
|
116
|
+
table.render_on(pdf)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
plugin_name :pdf
|
121
|
+
register_on :table_engine
|
122
|
+
register_on :invoice_engine
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Ruport
|
2
|
+
class Format::Plugin
|
3
|
+
class SVGPlugin < Format::Plugin
|
4
|
+
helper(:init_plugin) { |eng|
|
5
|
+
# check the supplied data can be used for graphing
|
6
|
+
data.each { |r|
|
7
|
+
if data.column_names.size != r.data.size
|
8
|
+
raise InvalidGraphDataError,
|
9
|
+
"Column names and data do not match"
|
10
|
+
end
|
11
|
+
r.data.each { |c|
|
12
|
+
begin
|
13
|
+
c = BigDecimal.new(c) unless c.kind_of?(Float) ||
|
14
|
+
c.kind_of?(Fixnum) || c.kind_of?(BigDecimal)
|
15
|
+
rescue
|
16
|
+
raise InvalidGraphDataError,
|
17
|
+
"Unable to convert #{c.to_s} into a number"
|
18
|
+
end
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
raise InvalidGraphOptionError,
|
23
|
+
'You must provide a width before rendering a graph' if eng.width.nil?
|
24
|
+
raise InvalidGraphOptionError,
|
25
|
+
'You must provide a height before rendering a graph' if eng.height.nil?
|
26
|
+
raise InvalidGraphOptionError,
|
27
|
+
'You must provide a style before rendering a graph' if eng.style.nil?
|
28
|
+
if eng.style != :area && eng.style != :bar &&
|
29
|
+
eng.style != :line &&
|
30
|
+
eng.style != :smiles &&
|
31
|
+
eng.style != :stacked
|
32
|
+
raise InvalidGraphOptionError, 'Invalid graph style'
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'scruffy'
|
36
|
+
@graph = Scruffy::Graph.new
|
37
|
+
@graph.title = eng.title unless eng.title.nil?
|
38
|
+
@graph.theme = Scruffy::Themes::Mephisto.new
|
39
|
+
@graph.point_markers = @data.column_names
|
40
|
+
@graph_style = eng.style
|
41
|
+
@graph_width = eng.width
|
42
|
+
@graph_height = eng.height
|
43
|
+
}
|
44
|
+
|
45
|
+
renderer :graph do
|
46
|
+
|
47
|
+
data.each_with_index { |r,i|
|
48
|
+
@graph.add(@graph_style,
|
49
|
+
r.tags[0] || 'series ' + (i+1).to_s,
|
50
|
+
r.data)
|
51
|
+
}
|
52
|
+
|
53
|
+
# return the rendered graph
|
54
|
+
@graph.render(:size => [@graph_width, @graph_height])
|
55
|
+
end
|
56
|
+
|
57
|
+
plugin_name :svg
|
58
|
+
register_on :graph_engine
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Ruport
|
2
|
+
class Format::Plugin
|
3
|
+
class TextPlugin < Format::Plugin
|
4
|
+
rendering_options :erb_enabled => true, :red_cloth_enabled => false
|
5
|
+
|
6
|
+
renderer :document
|
7
|
+
|
8
|
+
renderer :table do
|
9
|
+
require "ruport/system_extensions"
|
10
|
+
|
11
|
+
th = "#{rendered_field_names}#{hr}"
|
12
|
+
|
13
|
+
data.each { |r|
|
14
|
+
r.each_with_index { |f,i|
|
15
|
+
r[i] = f.to_s.center(max_col_width(i))
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
a = data.inject(th){ |s,r|
|
20
|
+
s + "| #{r.to_a.join(' | ')} |\n"
|
21
|
+
} << hr
|
22
|
+
|
23
|
+
width = self.right_margin || SystemExtensions.terminal_width
|
24
|
+
|
25
|
+
a.to_a.each { |r|
|
26
|
+
r.gsub!(/\A.{#{width+1},}/) { |m| m[0,width-2] + ">>" }
|
27
|
+
}.join
|
28
|
+
end
|
29
|
+
|
30
|
+
format_field_names do
|
31
|
+
data.column_names.each_with_index { |f,i|
|
32
|
+
data.column_names[i] = f.to_s.center(max_col_width(i))
|
33
|
+
}
|
34
|
+
"#{hr}| #{data.column_names.to_a.join(' | ')} |\n"
|
35
|
+
end
|
36
|
+
|
37
|
+
action :max_col_width do |index|
|
38
|
+
f = data.column_names if data.respond_to? :column_names
|
39
|
+
d = Data::Table.new :column_names => f, :data => data
|
40
|
+
|
41
|
+
cw = d.map { |r| r[index].to_s.length }.max
|
42
|
+
|
43
|
+
return cw unless d.column_names
|
44
|
+
|
45
|
+
nw = (index.kind_of?(Integer) ? d.column_names[index] : index ).to_s.length
|
46
|
+
|
47
|
+
[cw,nw].max
|
48
|
+
end
|
49
|
+
|
50
|
+
action :table_width do
|
51
|
+
f = data.column_names if data.respond_to? :column_names
|
52
|
+
d = Data::Table.new:column_names => f, :data => data
|
53
|
+
|
54
|
+
f = d[0].attributes || (0...d[0].length)
|
55
|
+
|
56
|
+
f.inject(0) { |s,e| s + max_col_width(e) }
|
57
|
+
end
|
58
|
+
|
59
|
+
action :hr do
|
60
|
+
len = data[0].to_a.length * 3 + table_width + 1
|
61
|
+
"+" + "-"*(len-2) + "+\n"
|
62
|
+
end
|
63
|
+
|
64
|
+
attribute :right_margin
|
65
|
+
plugin_name :text
|
66
|
+
register_on :table_engine
|
67
|
+
register_on :document_engine
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
@@ -58,16 +58,16 @@ module Ruport
|
|
58
58
|
# A Unix savvy method to fetch the console columns, and rows.
|
59
59
|
def terminal_size
|
60
60
|
`stty size`.split.map { |x| x.to_i }.reverse
|
61
|
-
end
|
61
|
+
end
|
62
|
+
end
|
62
63
|
|
63
|
-
|
64
|
-
|
65
|
-
|
64
|
+
def terminal_width
|
65
|
+
terminal_size.first
|
66
|
+
end
|
66
67
|
|
67
|
-
|
68
|
-
|
69
|
-
end
|
70
|
-
|
68
|
+
def terminal_height
|
69
|
+
terminal_size.last
|
71
70
|
end
|
71
|
+
|
72
72
|
end
|
73
73
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
***************
|
2
|
+
*** 60,74 ****
|
3
|
+
`stty size`.split.map { |x| x.to_i }.reverse
|
4
|
+
end
|
5
|
+
|
6
|
+
- end
|
7
|
+
-
|
8
|
+
- def terminal_width
|
9
|
+
- terminal_size.first
|
10
|
+
- end
|
11
|
+
-
|
12
|
+
- def terminal_height
|
13
|
+
- terminal_size.last
|
14
|
+
- end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
--- 60,73 ----
|
19
|
+
`stty size`.split.map { |x| x.to_i }.reverse
|
20
|
+
end
|
21
|
+
|
22
|
+
+ def terminal_width
|
23
|
+
+ terminal_size.first
|
24
|
+
+ end
|
25
|
+
|
26
|
+
+ def terminal_height
|
27
|
+
+ terminal_size.last
|
28
|
+
+ end
|
29
|
+
+
|
30
|
+
+ end
|
31
|
+
end
|
32
|
+
end
|
data/test/test_collection.rb
CHANGED
@@ -36,4 +36,9 @@ class TestCollection < Test::Unit::TestCase
|
|
36
36
|
assert_equal @ghost_collection.to_csv, @ghost_collection.as(:csv)
|
37
37
|
end
|
38
38
|
|
39
|
+
def test_invalid_plugin_throws_an_error
|
40
|
+
assert_raise(InvalidPluginError) { @ghost_collection.to_papyrus_scroll }
|
41
|
+
assert_raise(InvalidPluginError) { @ghost_collection.as(:stone_tablet)}
|
42
|
+
end
|
43
|
+
|
39
44
|
end
|
data/test/unit.log
CHANGED
@@ -4718,3 +4718,15 @@ F, [2006-08-28T14:56:51.239122 #2797] FATAL -- : no block given!
|
|
4718
4718
|
F, [2006-08-28T15:03:28.339719 #2839] FATAL -- : Missing host for mailer bar
|
4719
4719
|
F, [2006-08-28T15:03:28.340989 #2839] FATAL -- : Missing DSN for source foo!
|
4720
4720
|
F, [2006-08-28T15:03:29.731996 #2839] FATAL -- : no block given!
|
4721
|
+
F, [2006-08-30T16:45:01.507510 #2290] FATAL -- : Missing host for mailer bar
|
4722
|
+
F, [2006-08-30T16:45:01.509091 #2290] FATAL -- : Missing DSN for source foo!
|
4723
|
+
F, [2006-08-30T16:45:02.931966 #2290] FATAL -- : no block given!
|
4724
|
+
F, [2006-08-31T18:50:43.741551 #2594] FATAL -- : Missing host for mailer bar
|
4725
|
+
F, [2006-08-31T18:50:43.823591 #2594] FATAL -- : Missing DSN for source foo!
|
4726
|
+
F, [2006-08-31T18:50:45.287417 #2594] FATAL -- : no block given!
|
4727
|
+
F, [2006-09-02T14:35:05.490338 #2499] FATAL -- : Missing host for mailer bar
|
4728
|
+
F, [2006-09-02T14:35:05.524813 #2499] FATAL -- : Missing DSN for source foo!
|
4729
|
+
F, [2006-09-02T14:35:07.162616 #2499] FATAL -- : no block given!
|
4730
|
+
F, [2006-09-02T14:41:36.925689 #2530] FATAL -- : Missing host for mailer bar
|
4731
|
+
F, [2006-09-02T14:41:36.927265 #2530] FATAL -- : Missing DSN for source foo!
|
4732
|
+
F, [2006-09-02T14:41:38.446094 #2530] FATAL -- : no block given!
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: ruport
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.5.
|
7
|
-
date: 2006-
|
6
|
+
version: 0.5.1
|
7
|
+
date: 2006-09-02 00:00:00 -04:00
|
8
8
|
summary: A generalized Ruby report generation and templating engine.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -47,6 +47,7 @@ files:
|
|
47
47
|
- lib/ruport/rails
|
48
48
|
- lib/ruport/data
|
49
49
|
- lib/ruport/report
|
50
|
+
- lib/ruport/system_extensions.rb.rej
|
50
51
|
- lib/ruport/system_extensions.rb
|
51
52
|
- lib/ruport/config.rb
|
52
53
|
- lib/ruport/query.rb
|
@@ -59,6 +60,17 @@ files:
|
|
59
60
|
- lib/ruport/query/sql_split.rb
|
60
61
|
- lib/ruport/format/plugin.rb
|
61
62
|
- lib/ruport/format/engine.rb
|
63
|
+
- lib/ruport/format/engine
|
64
|
+
- lib/ruport/format/plugin
|
65
|
+
- lib/ruport/format/engine/document.rb
|
66
|
+
- lib/ruport/format/engine/graph.rb
|
67
|
+
- lib/ruport/format/engine/invoice.rb
|
68
|
+
- lib/ruport/format/engine/table.rb
|
69
|
+
- lib/ruport/format/plugin/csv_plugin.rb
|
70
|
+
- lib/ruport/format/plugin/html_plugin.rb
|
71
|
+
- lib/ruport/format/plugin/pdf_plugin.rb
|
72
|
+
- lib/ruport/format/plugin/svg_plugin.rb
|
73
|
+
- lib/ruport/format/plugin/text_plugin.rb
|
62
74
|
- lib/ruport/rails/reportable.rb
|
63
75
|
- lib/ruport/data/taggable.rb
|
64
76
|
- lib/ruport/data/table.rb
|