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.
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: