ruport 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/AUTHORS +7 -3
  2. data/Rakefile +8 -9
  3. data/TODO +16 -0
  4. data/examples/RWEmerson.jpg +0 -0
  5. data/examples/centered_pdf_text_box.rb +66 -0
  6. data/examples/invoice.rb +35 -25
  7. data/examples/invoice_report.rb +1 -1
  8. data/examples/line_plotter.rb +1 -1
  9. data/examples/pdf_table_with_title.rb +42 -0
  10. data/lib/ruport.rb +5 -7
  11. data/lib/ruport.rb.rej +41 -0
  12. data/lib/ruport.rb~ +85 -0
  13. data/lib/ruport/attempt.rb +59 -59
  14. data/lib/ruport/config.rb +15 -4
  15. data/lib/ruport/data.rb +0 -2
  16. data/lib/ruport/data/groupable.rb +25 -16
  17. data/lib/ruport/data/record.rb +128 -102
  18. data/lib/ruport/data/table.rb +352 -199
  19. data/lib/ruport/data/taggable.rb +18 -7
  20. data/lib/ruport/format/html.rb +3 -1
  21. data/lib/ruport/format/latex.rb +1 -1
  22. data/lib/ruport/format/latex.rb.rej +26 -0
  23. data/lib/ruport/format/latex.rb~ +47 -0
  24. data/lib/ruport/format/pdf.rb +111 -28
  25. data/lib/ruport/format/pdf.rb.rej +168 -0
  26. data/lib/ruport/format/pdf.rb~ +189 -0
  27. data/lib/ruport/format/plugin.rb +0 -5
  28. data/lib/ruport/format/svg.rb +4 -4
  29. data/lib/ruport/format/xml.rb +3 -3
  30. data/lib/ruport/generator.rb +66 -27
  31. data/lib/ruport/mailer.rb +4 -1
  32. data/lib/ruport/query.rb +13 -1
  33. data/lib/ruport/renderer.rb +89 -17
  34. data/lib/ruport/renderer/graph.rb +5 -5
  35. data/lib/ruport/renderer/table.rb +8 -9
  36. data/lib/ruport/report.rb +2 -6
  37. data/test/test_config.rb +88 -76
  38. data/test/{test_text_table.rb → test_format_text.rb} +4 -2
  39. data/test/test_groupable.rb +15 -13
  40. data/test/test_query.rb +6 -3
  41. data/test/test_record.rb +57 -33
  42. data/test/test_renderer.rb +77 -0
  43. data/test/test_report.rb +188 -181
  44. data/test/test_ruport.rb +5 -6
  45. data/test/test_table.rb +290 -190
  46. data/test/test_table_renderer.rb +56 -8
  47. data/test/test_taggable.rb +7 -8
  48. data/test/unit.log +259 -7
  49. metadata +22 -19
  50. data/lib/ruport/data/collection.rb +0 -65
  51. data/lib/ruport/data/set.rb +0 -148
  52. data/test/test_collection.rb +0 -30
  53. 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
@@ -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
@@ -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.data,r.tags[0] || "series #{i+1}")
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.
@@ -1,8 +1,8 @@
1
1
  module Ruport::Format
2
- class XML < Plugin #:nodoc:
2
+ class XML < Plugin
3
3
 
4
4
  def prepare_graph
5
- require_gem "builder"
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) }
@@ -1,5 +1,5 @@
1
1
  module Ruport
2
- class Generator #:nodoc:
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
- build_rakefile
18
- build_utils
17
+ build_utils
18
+ build_rakefile
19
+ puts "\nSuccessfully generated project: #{proj}"
19
20
  end
20
21
 
21
22
  def self.build_init
22
- File.open("#{project}/app/init.rb","w") { |f| f << INIT }
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
- File.open("#{project}/Rakefile","w") { |f| f << RAKEFILE }
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
- File.open("#{project}/util/build.rb","w") { |f| f << BUILD }
33
- File.open("#{project}/util/sql_exec.rb","w") { |f| f << SQL_EXEC }
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
- %w[ test config output data app app/reports
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
- mkdir "#{project}/#{d}"
55
+ m="#{project}/#{d}"
56
+ puts " #{m}"
57
+ mkdir(m)
42
58
  end
43
-
44
- touch("#{project}/app/reports.rb")
45
- touch("#{project}/app/helpers.rb")
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
- File.open("#{project}/config/ruport_config.rb","w") { |f| f << CONFIG }
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.rb [command] [options]"
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? "app/reports/#{ARGV[1]}.rb"
128
+ exit if File.exist? "lib/reports/#{ARGV[1]}.rb"
97
129
  if ARGV[0].eql? "report"
98
- File.open("app/reports.rb", "a") { |f|
99
- f.puts("require \"app/reports/#{ARGV[1]}\"")
130
+ File.open("lib/reports.rb", "a") { |f|
131
+ f.puts("require \"lib/reports/#{ARGV[1]}\"")
100
132
  }
101
133
  REP = <<EOR
102
- require "app/init"
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 "app/reports/#{ARGV[1]}"
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
- File.open("app/reports/#{ARGV[1]}.rb", "w") { |f| f << REP }
135
- File.open("test/test_#{ARGV[1]}.rb","w") { |f| f << TEST }
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
- require "app/init"
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
- require_gem "ruport","=#{Ruport::VERSION}"
187
+ gem "ruport","=#{Ruport::VERSION}"
149
188
  rescue LoadError
150
189
  nil
151
190
  end
152
191
  require "ruport"
153
- require "app/helpers"
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: