ruport 0.7.2 → 0.8.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 +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:
|