ruport 0.7.2 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +7 -3
- data/Rakefile +8 -9
- data/TODO +16 -0
- data/examples/RWEmerson.jpg +0 -0
- data/examples/centered_pdf_text_box.rb +66 -0
- data/examples/invoice.rb +35 -25
- data/examples/invoice_report.rb +1 -1
- data/examples/line_plotter.rb +1 -1
- data/examples/pdf_table_with_title.rb +42 -0
- data/lib/ruport.rb +5 -7
- data/lib/ruport.rb.rej +41 -0
- data/lib/ruport.rb~ +85 -0
- data/lib/ruport/attempt.rb +59 -59
- data/lib/ruport/config.rb +15 -4
- data/lib/ruport/data.rb +0 -2
- data/lib/ruport/data/groupable.rb +25 -16
- data/lib/ruport/data/record.rb +128 -102
- data/lib/ruport/data/table.rb +352 -199
- data/lib/ruport/data/taggable.rb +18 -7
- data/lib/ruport/format/html.rb +3 -1
- data/lib/ruport/format/latex.rb +1 -1
- data/lib/ruport/format/latex.rb.rej +26 -0
- data/lib/ruport/format/latex.rb~ +47 -0
- data/lib/ruport/format/pdf.rb +111 -28
- data/lib/ruport/format/pdf.rb.rej +168 -0
- data/lib/ruport/format/pdf.rb~ +189 -0
- data/lib/ruport/format/plugin.rb +0 -5
- data/lib/ruport/format/svg.rb +4 -4
- data/lib/ruport/format/xml.rb +3 -3
- data/lib/ruport/generator.rb +66 -27
- data/lib/ruport/mailer.rb +4 -1
- data/lib/ruport/query.rb +13 -1
- data/lib/ruport/renderer.rb +89 -17
- data/lib/ruport/renderer/graph.rb +5 -5
- data/lib/ruport/renderer/table.rb +8 -9
- data/lib/ruport/report.rb +2 -6
- data/test/test_config.rb +88 -76
- data/test/{test_text_table.rb → test_format_text.rb} +4 -2
- data/test/test_groupable.rb +15 -13
- data/test/test_query.rb +6 -3
- data/test/test_record.rb +57 -33
- data/test/test_renderer.rb +77 -0
- data/test/test_report.rb +188 -181
- data/test/test_ruport.rb +5 -6
- data/test/test_table.rb +290 -190
- data/test/test_table_renderer.rb +56 -8
- data/test/test_taggable.rb +7 -8
- data/test/unit.log +259 -7
- metadata +22 -19
- data/lib/ruport/data/collection.rb +0 -65
- data/lib/ruport/data/set.rb +0 -148
- data/test/test_collection.rb +0 -30
- data/test/test_set.rb +0 -118
@@ -0,0 +1,189 @@
|
|
1
|
+
module Ruport::Format
|
2
|
+
|
3
|
+
# PDF generation plugin
|
4
|
+
#
|
5
|
+
# layout options:
|
6
|
+
# General:
|
7
|
+
# * paper_size #=> "LETTER"
|
8
|
+
# * orientation #=> :center
|
9
|
+
#
|
10
|
+
# Table:
|
11
|
+
# * table_width
|
12
|
+
# * max_table_width #=> 500
|
13
|
+
#
|
14
|
+
class PDF < Plugin
|
15
|
+
attr_writer :pdf_writer
|
16
|
+
attr_accessor :table_header_proc
|
17
|
+
attr_accessor :table_footer_proc
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
require "pdf/writer"
|
21
|
+
require "pdf/simpletable"
|
22
|
+
end
|
23
|
+
|
24
|
+
def pdf_writer
|
25
|
+
@pdf_writer ||=
|
26
|
+
::PDF::Writer.new( :paper => layout.paper_size || "LETTER" )
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_table_header
|
30
|
+
table_header_proc[pdf_writer] if table_header_proc
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_table_body
|
34
|
+
draw_table
|
35
|
+
end
|
36
|
+
|
37
|
+
def build_table_footer
|
38
|
+
table_footer_proc[pdf_writer] if table_footer_proc
|
39
|
+
end
|
40
|
+
|
41
|
+
def finalize_table
|
42
|
+
output << pdf_writer.render
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_text(*args)
|
46
|
+
pdf_writer.text(*args)
|
47
|
+
end
|
48
|
+
|
49
|
+
# - if the image is bigger than the box, it will be scaled down until it fits
|
50
|
+
# - if the image is smaller than the box it's won't be resized
|
51
|
+
#
|
52
|
+
# arguments:
|
53
|
+
# - x: left bound of box
|
54
|
+
# - y: bottom bound of box
|
55
|
+
# - width: width of box
|
56
|
+
# - height: height of box
|
57
|
+
def center_image_in_box(path, x, y, width, height)
|
58
|
+
info = ::PDF::Writer::Graphics::ImageInfo.new(File.read(path))
|
59
|
+
|
60
|
+
# if the image is larger than the requested box, prepare to
|
61
|
+
# scale it down
|
62
|
+
fits = !(info.width > width || info.height > height)
|
63
|
+
|
64
|
+
# setup initial sizes for the image. These will be reduced as necesary
|
65
|
+
img_width = info.width
|
66
|
+
img_height = info.height
|
67
|
+
img_ratio = info.height.to_f / info.width.to_f
|
68
|
+
|
69
|
+
# reduce the size of the image until it fits into the requested box
|
70
|
+
until fits
|
71
|
+
img_width -= 1
|
72
|
+
img_height = img_width * img_ratio
|
73
|
+
fits = true if img_width < width && img_height < height
|
74
|
+
end
|
75
|
+
|
76
|
+
# if the width of the image is less than the requested box, calculate
|
77
|
+
# the white space buffer
|
78
|
+
if img_width < width
|
79
|
+
white_space = width - img_width
|
80
|
+
x = x + (white_space / 2)
|
81
|
+
end
|
82
|
+
|
83
|
+
# if the height of the image is less than the requested box, calculate
|
84
|
+
# the white space buffer
|
85
|
+
if img_height < height
|
86
|
+
white_space = height - img_height
|
87
|
+
y = y + (white_space / 2)
|
88
|
+
end
|
89
|
+
|
90
|
+
pdf_writer.add_image_from_file(path, x, y, img_width, img_height)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Draws some text on the canvas, surrounded by a box with rounded corners
|
94
|
+
#
|
95
|
+
def rounded_text_box(text)
|
96
|
+
opts = OpenStruct.new
|
97
|
+
yield(opts)
|
98
|
+
|
99
|
+
# resize the text automatically to ensure it isn't wider than the box
|
100
|
+
loop do
|
101
|
+
sz = pdf_writer.text_width( text, opts.font_size )
|
102
|
+
opts.x + sz > opts.x + opts.width or break
|
103
|
+
opts.font_size -= 1
|
104
|
+
end
|
105
|
+
|
106
|
+
# save the drawing state (colors, etc) so we can restore it later
|
107
|
+
pdf_writer.save_state
|
108
|
+
|
109
|
+
# draw our box
|
110
|
+
pdf_writer.fill_color(opts.fill_color || Color::RGB::White)
|
111
|
+
pdf_writer.stroke_color(opts.stroke_color || Color::RGB::Black)
|
112
|
+
pdf_writer.rounded_rectangle( opts.x, opts.y,
|
113
|
+
opts.width, opts.height,
|
114
|
+
opts.radius).fill_stroke
|
115
|
+
|
116
|
+
# if a heading for this box has been specified
|
117
|
+
if opts.heading
|
118
|
+
pdf_writer.line( opts.x, opts.y - 20,
|
119
|
+
opts.x + opts.width, opts.y - 20).stroke
|
120
|
+
pdf_writer.fill_color(Color::RGB::Black)
|
121
|
+
move_cursor_to(opts.y - 3)
|
122
|
+
add_text("<b>#{opts.heading}</b>",
|
123
|
+
:absolute_left => opts.x, :absolute_right => opts.x + opts.width,
|
124
|
+
:justification => :center, :font_size => opts.font_size)
|
125
|
+
end
|
126
|
+
|
127
|
+
# restore the original colors
|
128
|
+
pdf_writer.restore_state
|
129
|
+
|
130
|
+
# move our y cursor into position, write the text, then move the cursor
|
131
|
+
# to be just below the box
|
132
|
+
pdf_writer.y = opts.heading ? opts.y - 20 : opts.y
|
133
|
+
|
134
|
+
add_text( text, :absolute_left => opts.x,
|
135
|
+
:absolute_right => opts.x + opts.width,
|
136
|
+
:justification => opts.justification || :center,
|
137
|
+
:font_size => opts.font_size )
|
138
|
+
|
139
|
+
pdf_writer.y = opts.y - opts.height
|
140
|
+
end
|
141
|
+
|
142
|
+
# adds an image to every page. The color and size won't be modified,
|
143
|
+
# but it will be centered.
|
144
|
+
#
|
145
|
+
def watermark(imgpath)
|
146
|
+
x = pdf_writer.absolute_left_margin
|
147
|
+
y = pdf_writer.absolute_bottom_margin
|
148
|
+
width = pdf_writer.absolute_right_margin - x
|
149
|
+
height = pdf_writer.absolute_top_margin - y
|
150
|
+
|
151
|
+
pdf_writer.open_object do |wm|
|
152
|
+
pdf_writer.save_state
|
153
|
+
center_image_in_box(imgpath, x, y, width, height)
|
154
|
+
pdf_writer.restore_state
|
155
|
+
pdf_writer.close_object
|
156
|
+
pdf_writer.add_object(wm, :all_pages)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def move_cursor(n)
|
161
|
+
pdf_writer.y += n
|
162
|
+
end
|
163
|
+
|
164
|
+
def move_cursor_to(n)
|
165
|
+
pdf_writer.y = n
|
166
|
+
end
|
167
|
+
|
168
|
+
def pad(y,&block)
|
169
|
+
move_cursor -y
|
170
|
+
block.call
|
171
|
+
move_cursor -y
|
172
|
+
end
|
173
|
+
|
174
|
+
def draw_table
|
175
|
+
m = "Sorry, cant build PDFs from array like things (yet)"
|
176
|
+
raise m if data.column_names.empty?
|
177
|
+
::PDF::SimpleTable.new do |table|
|
178
|
+
table.maximum_width = layout.max_table_width || 500
|
179
|
+
table.width = layout.table_width if layout.table_width
|
180
|
+
table.orientation = layout.orientation || :center
|
181
|
+
table.data = data
|
182
|
+
table.column_order = data.column_names
|
183
|
+
table.render_on(pdf_writer)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|
data/lib/ruport/format/plugin.rb
CHANGED
@@ -8,10 +8,7 @@ module Ruport
|
|
8
8
|
module Format
|
9
9
|
class Plugin
|
10
10
|
|
11
|
-
# Shared with renderer
|
12
11
|
attr_accessor :layout
|
13
|
-
|
14
|
-
# Shared with renderer
|
15
12
|
attr_accessor :data
|
16
13
|
|
17
14
|
# Stores a string used for outputting formatted data.
|
@@ -20,8 +17,6 @@ module Ruport
|
|
20
17
|
end
|
21
18
|
|
22
19
|
# Provides a generic OpenStruct for storing plugin options
|
23
|
-
#
|
24
|
-
# This is shared with the renderer object
|
25
20
|
def options
|
26
21
|
@options ||= OpenStruct.new
|
27
22
|
end
|
data/lib/ruport/format/svg.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Ruport::Format
|
2
2
|
class SVG < Plugin
|
3
|
-
|
3
|
+
|
4
4
|
# a hash of Scruffy themes.
|
5
5
|
#
|
6
6
|
# You can use these by setting layout.theme like this:
|
@@ -35,18 +35,18 @@ module Ruport::Format
|
|
35
35
|
@graph.title ||= options.title
|
36
36
|
@graph.theme = layout.theme if layout.theme
|
37
37
|
@graph.point_markers ||= data.column_names
|
38
|
-
end
|
39
38
|
|
39
|
+
end
|
40
40
|
|
41
41
|
# Generates an SVG using Scruffy.
|
42
42
|
def build_graph
|
43
43
|
data.each_with_index do |r,i|
|
44
|
-
add_line(r.
|
44
|
+
add_line(r.to_a,r.tags.to_a[0] || "series #{i+1}")
|
45
45
|
end
|
46
46
|
|
47
47
|
output << @graph.render(:size => [layout.width, layout.height])
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
# Uses Scruffy::Graph#add to add a new line to the graph.
|
51
51
|
#
|
52
52
|
# Will use the first tag on a Record as the label if present.
|
data/lib/ruport/format/xml.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module Ruport::Format
|
2
|
-
class XML < Plugin
|
2
|
+
class XML < Plugin
|
3
3
|
|
4
4
|
def prepare_graph
|
5
|
-
|
5
|
+
gem "builder"
|
6
6
|
@builder = Builder::XmlMarkup.new(:indent => 2)
|
7
7
|
end
|
8
8
|
|
@@ -18,7 +18,7 @@ module Ruport::Format
|
|
18
18
|
}
|
19
19
|
|
20
20
|
data.each_with_index { |r,i|
|
21
|
-
label = r.tags[0] || "Region #{i}"
|
21
|
+
label = r.tags.to_a[0] || "Region #{i}"
|
22
22
|
cd.row { |data_row|
|
23
23
|
data_row.string(label)
|
24
24
|
r.each { |e| data_row.number(e) }
|
data/lib/ruport/generator.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Ruport
|
2
|
-
class Generator
|
2
|
+
class Generator
|
3
3
|
extend FileUtils
|
4
4
|
|
5
5
|
begin
|
@@ -14,47 +14,69 @@ module Ruport
|
|
14
14
|
build_directory_structure
|
15
15
|
build_init
|
16
16
|
build_config
|
17
|
-
|
18
|
-
|
17
|
+
build_utils
|
18
|
+
build_rakefile
|
19
|
+
puts "\nSuccessfully generated project: #{proj}"
|
19
20
|
end
|
20
21
|
|
21
22
|
def self.build_init
|
22
|
-
|
23
|
+
m = "#{project}/lib/init.rb"
|
24
|
+
puts " #{m}"
|
25
|
+
File.open(m,"w") { |f| f << INIT }
|
23
26
|
end
|
24
27
|
|
25
28
|
# Generates a trivial rakefile for use with Ruport.
|
26
29
|
def self.build_rakefile
|
27
|
-
|
30
|
+
m = "#{project}/Rakefile"
|
31
|
+
puts " #{m}"
|
32
|
+
File.open(m,"w") { |f| f << RAKEFILE }
|
28
33
|
end
|
29
34
|
|
30
35
|
# Generates the build.rb, sql_exec.rb, and cabinet.rb utilities
|
31
|
-
def self.build_utils
|
32
|
-
|
33
|
-
|
36
|
+
def self.build_utils
|
37
|
+
|
38
|
+
m = "#{project}/util/build"
|
39
|
+
puts " #{m}"
|
40
|
+
File.open(m,"w") { |f| f << BUILD }
|
41
|
+
chmod(0755, m)
|
42
|
+
|
43
|
+
m = "#{project}/util/sql_exec"
|
44
|
+
puts " #{m}"
|
45
|
+
File.open(m,"w") { |f| f << SQL_EXEC }
|
46
|
+
chmod(0755, m)
|
34
47
|
end
|
35
48
|
|
36
49
|
# sets up the basic directory layout for a Ruport application
|
37
50
|
def self.build_directory_structure
|
38
|
-
mkdir project
|
39
|
-
|
51
|
+
mkdir project
|
52
|
+
puts "creating directories.."
|
53
|
+
%w[ test config output data lib lib/reports
|
40
54
|
templates sql log util].each do |d|
|
41
|
-
|
55
|
+
m="#{project}/#{d}"
|
56
|
+
puts " #{m}"
|
57
|
+
mkdir(m)
|
42
58
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
59
|
+
|
60
|
+
puts "creating files.."
|
61
|
+
%w[reports helpers].each { |f|
|
62
|
+
m = "#{project}/lib/#{f}.rb"
|
63
|
+
puts " #{m}"
|
64
|
+
touch(m)
|
65
|
+
}
|
46
66
|
end
|
47
67
|
|
48
68
|
# Builds a file called config/ruport_config.rb which stores a Ruport::Config
|
49
69
|
# skeleton
|
50
70
|
def self.build_config
|
51
|
-
|
71
|
+
m = "#{project}/config/ruport_config.rb"
|
72
|
+
puts " #{m}"
|
73
|
+
File.open(m,"w") { |f| f << CONFIG }
|
52
74
|
end
|
53
75
|
|
54
76
|
# returns the project's name
|
55
77
|
def self.project; @project; end
|
56
78
|
|
57
|
-
RAKEFILE = <<END_RAKEFILE
|
79
|
+
RAKEFILE = <<'END_RAKEFILE'
|
58
80
|
begin; require "rubygems"; rescue LoadError; end
|
59
81
|
require "rake/testtask"
|
60
82
|
|
@@ -65,6 +87,14 @@ Rake::TestTask.new do |test|
|
|
65
87
|
test.pattern = 'test/**/test_*.rb'
|
66
88
|
test.verbose = true
|
67
89
|
end
|
90
|
+
|
91
|
+
task :build do
|
92
|
+
sh "ruby util/build report #{ENV['report']}"
|
93
|
+
end
|
94
|
+
|
95
|
+
task :run do
|
96
|
+
sh "ruby lib/reports/#{ENV['report']}.rb"
|
97
|
+
end
|
68
98
|
END_RAKEFILE
|
69
99
|
|
70
100
|
CONFIG = <<END_CONFIG
|
@@ -79,6 +109,8 @@ Ruport.configure { |c|
|
|
79
109
|
END_CONFIG
|
80
110
|
|
81
111
|
BUILD = <<'END_BUILD'
|
112
|
+
#!/usr/bin/env ruby
|
113
|
+
|
82
114
|
require 'fileutils'
|
83
115
|
include FileUtils
|
84
116
|
|
@@ -87,19 +119,19 @@ def format_class_name(string)
|
|
87
119
|
end
|
88
120
|
|
89
121
|
unless ARGV.length > 1
|
90
|
-
puts "usage build
|
122
|
+
puts "usage: build [command] [options]"
|
91
123
|
exit
|
92
124
|
end
|
93
125
|
|
94
126
|
class_name = format_class_name(ARGV[1])
|
95
127
|
|
96
|
-
exit if File.exist? "
|
128
|
+
exit if File.exist? "lib/reports/#{ARGV[1]}.rb"
|
97
129
|
if ARGV[0].eql? "report"
|
98
|
-
File.open("
|
99
|
-
f.puts("require \"
|
130
|
+
File.open("lib/reports.rb", "a") { |f|
|
131
|
+
f.puts("require \"lib/reports/#{ARGV[1]}\"")
|
100
132
|
}
|
101
133
|
REP = <<EOR
|
102
|
-
require "
|
134
|
+
require "lib/init"
|
103
135
|
class #{class_name} < Ruport::Report
|
104
136
|
|
105
137
|
def prepare
|
@@ -123,7 +155,7 @@ EOR
|
|
123
155
|
|
124
156
|
TEST = <<EOR
|
125
157
|
require "test/unit"
|
126
|
-
require "
|
158
|
+
require "lib/reports/#{ARGV[1]}"
|
127
159
|
|
128
160
|
class Test#{class_name} < Test::Unit::TestCase
|
129
161
|
def test_flunk
|
@@ -131,13 +163,20 @@ class Test#{class_name} < Test::Unit::TestCase
|
|
131
163
|
end
|
132
164
|
end
|
133
165
|
EOR
|
134
|
-
|
135
|
-
File.open("
|
166
|
+
puts "report file: lib/reports/#{ARGV[1]}.rb"
|
167
|
+
File.open("lib/reports/#{ARGV[1]}.rb", "w") { |f| f << REP }
|
168
|
+
puts "test file: test/test_#{ARGV[1]}.rb"
|
169
|
+
puts "class name: #{class_name}"
|
170
|
+
File.open("test/test_#{ARGV[1]}.rb","w") { |f| f << TEST }
|
171
|
+
else
|
172
|
+
puts "Incorrect usage."
|
136
173
|
end
|
137
174
|
END_BUILD
|
138
175
|
|
139
176
|
SQL_EXEC = <<'END_SQL'
|
140
|
-
|
177
|
+
#!/usr/bin/env ruby
|
178
|
+
|
179
|
+
require "lib/init"
|
141
180
|
|
142
181
|
puts Ruport::Query.new(ARGF.read).result
|
143
182
|
END_SQL
|
@@ -145,12 +184,12 @@ END_SQL
|
|
145
184
|
INIT = <<END_INIT
|
146
185
|
begin
|
147
186
|
require "rubygems"
|
148
|
-
|
187
|
+
gem "ruport","=#{Ruport::VERSION}"
|
149
188
|
rescue LoadError
|
150
189
|
nil
|
151
190
|
end
|
152
191
|
require "ruport"
|
153
|
-
require "
|
192
|
+
require "lib/helpers"
|
154
193
|
require "config/ruport_config"
|
155
194
|
END_INIT
|
156
195
|
|
data/lib/ruport/mailer.rb
CHANGED
@@ -9,6 +9,7 @@ require "forwardable"
|
|
9
9
|
|
10
10
|
module Ruport
|
11
11
|
|
12
|
+
#
|
12
13
|
# === Overview
|
13
14
|
#
|
14
15
|
# This class uses SMTP to provide a simple mail sending mechanism.
|
@@ -38,6 +39,7 @@ module Ruport
|
|
38
39
|
class Mailer
|
39
40
|
extend Forwardable
|
40
41
|
|
42
|
+
#
|
41
43
|
# Creates a new Mailer object. Optionally, you can select a mailer
|
42
44
|
# specified by Ruport::Config.
|
43
45
|
#
|
@@ -56,7 +58,8 @@ module Ruport
|
|
56
58
|
def_delegators( :@mail, :to, :to=, :from, :from=,
|
57
59
|
:subject, :subject=, :attach,
|
58
60
|
:text, :text=, :html, :html= )
|
59
|
-
|
61
|
+
|
62
|
+
#
|
60
63
|
# Sends the message.
|
61
64
|
#
|
62
65
|
# Example:
|