ruport 0.4.23 → 0.4.99

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/AUTHORS +16 -8
  2. data/CHANGELOG +30 -1
  3. data/README +144 -114
  4. data/Rakefile +12 -4
  5. data/TODO +4 -7
  6. data/bin/rope +21 -28
  7. data/examples/line_graph.rb +36 -0
  8. data/examples/sample_invoice_report.rb +1 -1
  9. data/examples/simple_graph.rb +8 -0
  10. data/lib/SVG/Graph/Bar.rb +137 -0
  11. data/lib/SVG/Graph/BarBase.rb +140 -0
  12. data/lib/SVG/Graph/BarHorizontal.rb +136 -0
  13. data/lib/SVG/Graph/Graph.rb +977 -0
  14. data/lib/SVG/Graph/Line.rb +444 -0
  15. data/lib/SVG/Graph/Pie.rb +394 -0
  16. data/lib/SVG/Graph/Plot.rb +494 -0
  17. data/lib/SVG/Graph/Schedule.rb +373 -0
  18. data/lib/SVG/Graph/TimeSeries.rb +241 -0
  19. data/lib/ruport.rb +2 -2
  20. data/lib/ruport/config.rb +47 -3
  21. data/lib/ruport/data/collection.rb +17 -1
  22. data/lib/ruport/data/record.rb +101 -8
  23. data/lib/ruport/data/set.rb +81 -2
  24. data/lib/ruport/data/set.rb.rej +147 -0
  25. data/lib/ruport/data/set.rb~ +73 -0
  26. data/lib/ruport/data/table.rb +127 -2
  27. data/lib/ruport/data/taggable.rb +21 -2
  28. data/lib/ruport/format.rb +36 -44
  29. data/lib/ruport/format/engine.rb +21 -1
  30. data/lib/ruport/format/plugin.rb +64 -1
  31. data/lib/ruport/mailer.rb +70 -36
  32. data/lib/ruport/meta_tools.rb +15 -6
  33. data/lib/ruport/query.rb +1 -1
  34. data/lib/ruport/rails/reportable.rb +23 -1
  35. data/lib/ruport/report.rb +11 -11
  36. data/lib/ruport/report/invoice.rb +16 -0
  37. data/lib/ruport/system_extensions.rb +3 -55
  38. data/test/{tc_database.rb → _test_database.rb} +0 -0
  39. data/test/{tc_config.rb → test_config.rb} +0 -0
  40. data/test/{tc_format.rb → test_format.rb} +1 -0
  41. data/test/{tc_format_engine.rb → test_format_engine.rb} +14 -2
  42. data/test/test_graph.rb +101 -0
  43. data/test/{tc_invoice.rb → test_invoice.rb} +7 -1
  44. data/test/test_mailer.rb +108 -0
  45. data/test/test_meta_tools.rb +14 -0
  46. data/test/{tc_plugin.rb → test_plugin.rb} +12 -1
  47. data/test/{tc_query.rb → test_query.rb} +0 -0
  48. data/test/{tc_record.rb → test_record.rb} +9 -0
  49. data/test/{tc_report.rb → test_report.rb} +2 -1
  50. data/test/{tc_ruport.rb → test_ruport.rb} +0 -0
  51. data/test/test_set.rb +118 -0
  52. data/test/test_set.rb.rej +16 -0
  53. data/test/{tc_set.rb → test_set.rb~} +17 -0
  54. data/test/{tc_sql_split.rb → test_sql_split.rb} +0 -0
  55. data/test/{tc_table.rb → test_table.rb} +15 -0
  56. data/test/{tc_taggable.rb → test_taggable.rb} +0 -0
  57. data/test/unit.log +361 -0
  58. metadata +52 -30
  59. data/examples/bar.pdf +0 -193
  60. data/examples/f.log +0 -5
  61. data/examples/foo.pdf +0 -193
  62. data/lib/ruport/format/document.rb +0 -78
  63. data/lib/ruport/format/open_node.rb +0 -38
  64. data/test/tc_data_row.rb +0 -132
  65. data/test/tc_data_set.rb +0 -386
  66. data/test/tc_document.rb +0 -42
  67. data/test/tc_element.rb +0 -18
  68. data/test/tc_page.rb +0 -42
  69. data/test/tc_section.rb +0 -45
  70. data/test/ts_all.rb +0 -12
  71. data/test/ts_format.rb +0 -7
@@ -9,49 +9,41 @@
9
9
  #
10
10
  # See LICENSE and COPYING for details
11
11
  module Ruport
12
-
13
-
14
- # Ruport's Format model is meant to help get your data in a suitable format for
15
- # output. Rather than make too many assumptions about how you will want your
16
- # data to look, a number of tools have been built so that you can quickly define
17
- # those things yourself.
18
- #
19
- # There are three main sets of functionality the Ruport::Format model provides.
20
- # * Structured printable document support ( Format::Document and friends)
21
- # * Text filter support ( Report#render and the Format class)
22
- #
23
- # The support for structured printable documents is currently geared towards PDF
24
- # support and needs some additional work to be truly useful. Suggestions would
25
- # be much appreciated.
26
- #
27
- # The filters implemented in the Format class are meant to process strings or
28
- # entire templates. The Format class will soon automatically build a
29
- # Ruport::Parser for any string input. By default, filters are provided to
30
- # process erb, pure ruby, and redcloth. It is trivial to extend this
31
- # functionality though.
32
- #
33
- # This is best shown by a simple example:
34
- #
35
- # a = Ruport::Report.new
36
- # Ruport::Format.register_filter :reverser do
37
- # content.reverse
38
- # end
39
- # a.render "somestring", :filters => [:reverser]
40
- #
41
- # Output: "gnirtsemos"
42
- #
43
- # Filters can be combined, and you can run them in different orders to obtain
44
- # different results.
45
- #
46
- # See the source for the built in filters for ideas.
47
- #
48
- # Also, see Report#render for how to bind Format objects to your own classes.
49
- #
50
- # When combined, filters, data set output templates, and structured printable
51
- # document facilities create a complete Formatting system.
52
- #
53
- # This part of Ruport is under active development. Please do feel free to
54
- # submit feature requests or suggestions.
12
+
13
+ # Ruport makes heavy use of ruby's advanced meta programming features in
14
+ # this Class.
15
+ #
16
+ # All subclasses of Ruport::Format::Engine and Ruport::Format::Plugin
17
+ # (both Ruports' internal ones and any custom ones outside the Ruport
18
+ # library) should dynamically register themselves with this class.
19
+ #
20
+ # All report generation is then done via Format, not with the engines
21
+ # and plugins directly.
22
+ #
23
+ # For each engine that is registered with Format, 2 methods are created:
24
+ # - <enginename>; and
25
+ # - <enginename>_object
26
+ #
27
+ # Either one of these methods can be used to create your report, depending
28
+ # on your requirments.
29
+ #
30
+ # = Format.enginename
31
+ #
32
+ # A brief example of creating a simple report with the table engine
33
+ #
34
+ # data = [[1,2],[5,3],[3,10]].to_table(%w[a b])
35
+ # File.open("myreport.pdf","w") { |f| f.puts Ruport::Format.table(:plugin => :pdf, :data => data)}
36
+ #
37
+ # = Format.enginename_object
38
+ #
39
+ # A slightly different way to create a simple report with the table engine.
40
+ # This technique gives you a chance to modify some of the engines settings
41
+ # before calling render manually.
42
+ #
43
+ # data = [[1,2],[5,3],[3,10]].to_table(%w[a b])
44
+ # myreport = Ruport::Format.table_object :plugin => :pdf, :data => data
45
+ # File.open("myreport.pdf","w") { |f| f.puts myreport.render }
46
+ #
55
47
  class Format
56
48
 
57
49
  # Builds a simple interface to a formatting engine.
@@ -83,7 +75,7 @@ module Ruport
83
75
  options[:auto_render] = false; simple_interface(engine,options) })
84
76
  end
85
77
 
86
- %w[open_node document engine plugin].each { |lib|
78
+ %w[engine plugin].each { |lib|
87
79
  require "ruport/format/#{lib}"
88
80
  }
89
81
 
@@ -1,3 +1,5 @@
1
+ class InvalidPluginError < RuntimeError; end
2
+
1
3
  module Ruport
2
4
  class Format::Engine
3
5
  require "forwardable"
@@ -44,6 +46,11 @@ module Ruport
44
46
  end
45
47
 
46
48
  def plugin=(p)
49
+ if @format_plugins[p].nil?
50
+ raise(InvalidPluginError,
51
+ 'The requested plugin and engine combination is invalid')
52
+ end
53
+
47
54
  @plugin = p
48
55
  @format_plugins[:current] = @format_plugins[p].dup
49
56
  @format_plugins[:current].data = self.data.dup if self.data
@@ -94,6 +101,18 @@ module Ruport
94
101
  private_class_method :new
95
102
  end
96
103
 
104
+ class Format::Engine::Graph < Ruport::Format::Engine
105
+
106
+ renderer do
107
+ super
108
+ active_plugin.render_graph
109
+ end
110
+
111
+ alias_engine Graph, :graph_engine
112
+ Ruport::Format.build_interface_for Graph, :graph
113
+
114
+ end
115
+
97
116
  class Format::Engine::Invoice < Ruport::Format::Engine
98
117
 
99
118
  # order meta data
@@ -150,7 +169,8 @@ module Ruport
150
169
 
151
170
  def build_field_names
152
171
  if active_plugin.respond_to?(:build_field_names)
153
- active_plugin.rendered_field_names = active_plugin.build_field_names
172
+ active_plugin.rendered_field_names =
173
+ active_plugin.build_field_names
154
174
  end
155
175
  end
156
176
 
@@ -1,3 +1,5 @@
1
+ require 'bigdecimal'
2
+
1
3
  module Ruport
2
4
  class Format::Plugin
3
5
 
@@ -64,6 +66,66 @@ module Ruport
64
66
  register_on :table_engine
65
67
  end
66
68
 
69
+ class SVGPlugin < Format::Plugin
70
+
71
+ helper(:init_plugin) { |eng|
72
+ # check the supplied data can be used for graphing
73
+ data.each { |r|
74
+ if data.column_names.size != r.data.size
75
+ raise ArgumentError, "Column names and data do not match"
76
+ end
77
+ r.data.each { |c|
78
+ begin
79
+ c = BigDecimal.new(c) unless c.kind_of?(Float) ||
80
+ c.kind_of?(Fixnum) || c.kind_of?(BigDecimal)
81
+ rescue
82
+ raise ArgumentError, "Unable to convert #{c.to_s} into a number"
83
+ end
84
+ }
85
+ }
86
+
87
+ raise RuntimeError, 'You must provide an options hash before rendering a graph' if self.options.nil?
88
+
89
+ # load the appropriate SVG::Graph class based on the graph_style option
90
+ case options[:graph_style]
91
+ when :bar
92
+ require "SVG/Graph/Bar"
93
+ graphclass = SVG::Graph::Bar
94
+ when :bar_horizontal
95
+ require "SVG/Graph/BarHorizontal"
96
+ graphclass = SVG::Graph::BarHorizontal
97
+ when :line
98
+ require "SVG/Graph/Line"
99
+ graphclass = SVG::Graph::Line
100
+ when :pie
101
+ require "SVG/Graph/Pie"
102
+ graphclass = SVG::Graph::Pie
103
+ else
104
+ raise "Unsupported graph type requested"
105
+ end
106
+
107
+ # create an instance of the graphing class
108
+ options[:fields] = data.column_names
109
+ @graph = graphclass.new(options)
110
+ }
111
+
112
+ renderer :graph do
113
+
114
+ data.each_with_index { |r,i|
115
+ @graph.add_data({
116
+ :data => r.data,
117
+ :title => r.tags[0] || 'series ' + (i+1).to_s
118
+ })
119
+ }
120
+
121
+ # return the rendered graph
122
+ @graph.burn()
123
+ end
124
+
125
+ plugin_name :svg
126
+ register_on :graph_engine
127
+ end
128
+
67
129
  class TextPlugin < Format::Plugin
68
130
  rendering_options :erb_enabled => true, :red_cloth_enabled => false
69
131
 
@@ -195,7 +257,8 @@ module Ruport
195
257
  pdf.stroke_style! PDF::Writer::StrokeStyle::DEFAULT
196
258
  if eng.comments
197
259
  pdf.y -= 20
198
- text_box eng.comments, :position => 110, :width => 400
260
+ text_box eng.comments, :position => 110, :width => 400,
261
+ :font_size => 14
199
262
  end
200
263
  pdf.add_text_wrap( 50, 20, 200, "Printed at " +
201
264
  Time.now.strftime("%H:%M %d/%m/%Y"), 8)
@@ -7,45 +7,79 @@
7
7
  require "net/smtp"
8
8
  require "forwardable"
9
9
  module Ruport
10
- class Mailer
11
- extend Forwardable
12
-
13
- def initialize( mailer_label=:default )
14
- select_mailer(mailer_label);
15
- mail_object.from = @mailer.address if mail_object.from.to_s.empty?
16
- rescue
17
- raise "you need to specify a mailer to use"
18
- end
19
-
20
- def_delegators( :@mail, :to, :to=, :from, :from=,
21
- :subject, :subject=, :attach,
22
- :text, :text=, :html, :html= )
10
+
11
+ # This class uses SMTP to provide a simple mail sending mechanism.
12
+ # It also uses MailFactory to provide attachment and HTML email support.
13
+ #
14
+ # Here is a simple example of a message which attaches a readme file:
15
+ #
16
+ # require "ruport"
17
+ #
18
+ # Ruport.configure do |conf|
19
+ # conf.mailer :default,
20
+ # :host => "mail.adelphia.net", :address => "gregory.t.brown@gmail.com"
21
+ # end
22
+ #
23
+ # mailer = Ruport::Mailer.new
24
+ #
25
+ # mailer.attach "README"
26
+ #
27
+ # mailer.deliver :to => "gregory.t.brown@gmail.com",
28
+ # :from => "gregory.t.brown@gmail.com",
29
+ # :subject => "Hey there",
30
+ # :text => "This is what you asked for"
31
+ class Mailer
32
+ extend Forwardable
33
+
34
+
35
+ # Creates a new Mailer object. Optionally, can select a mailer specified
36
+ # by Ruport::Config.
37
+ #
38
+ # a = Mailer.new # uses the :default mailer
39
+ # a = Mailer.new :foo # uses :foo mail config from Ruport::Config
40
+ #
41
+ def initialize( mailer_label=:default )
42
+ select_mailer(mailer_label);
43
+ mail_object.from = @mailer.address if mail_object.from.to_s.empty?
44
+ rescue
45
+ raise "you need to specify a mailer to use"
46
+ end
47
+
48
+ def_delegators( :@mail, :to, :to=, :from, :from=,
49
+ :subject, :subject=, :attach,
50
+ :text, :text=, :html, :html= )
51
+
52
+ # sends the message
53
+ #
54
+ # mailer.deliver :from => "gregory.t.brown@gmail.com",
55
+ # :to => "greg7224@gmail.com"
56
+ def deliver(options={})
57
+ options.each { |k,v| send("#{k}=",v) if respond_to? "#{k}=" }
23
58
 
24
- def deliver(options={})
25
- options.each { |k,v| send("#{k}=",v) if respond_to? "#{k}=" }
26
-
27
- Net::SMTP.start(@host,@port,@host,@user,@password,@auth) do |smtp|
28
- smtp.send_message((options[:mail_object] || mail_object).to_s, options[:from], options[:to] )
29
- end
59
+ Net::SMTP.start(@host,@port,@host,@user,@password,@auth) do |smtp|
60
+ smtp.send_message((options[:mail_object] || mail_object).to_s, options[:from], options[:to] )
30
61
  end
62
+ end
31
63
 
32
- def select_mailer(label)
33
- @mailer = Ruport::Config.mailers[label]
34
- @host = @mailer.host
35
- @user = @mailer.user
36
- @password = @mailer.password
37
- @address = @mailer.address
38
- @port = @mailer.port || 25
39
- @auth = @mailer.auth_type || :plain
40
- @mail_klass = @mailer.mail_klass
41
- end
64
+ private
42
65
 
43
- def mail_object
44
- return @mail if @mail
45
- return @mail ||= @mail_klass.new if @mail_klass
46
- require "mailfactory"
47
- @mail ||= MailFactory.new
48
- end
49
-
66
+ def select_mailer(label)
67
+ @mailer = Ruport::Config.mailers[label]
68
+ @host = @mailer.host
69
+ @user = @mailer.user
70
+ @password = @mailer.password
71
+ @address = @mailer.address
72
+ @port = @mailer.port || 25
73
+ @auth = @mailer.auth_type || :plain
74
+ @mail_klass = @mailer.mail_klass
75
+ end
76
+
77
+ def mail_object
78
+ return @mail if @mail
79
+ return @mail ||= @mail_klass.new if @mail_klass
80
+ require "mailfactory"
81
+ @mail ||= MailFactory.new
50
82
  end
83
+
84
+ end
51
85
  end
@@ -1,3 +1,4 @@
1
+ class ActionAlreadyDefinedError < RuntimeError; end
1
2
  module Ruport
2
3
  # This module provides a few tools for doing some manipulations of the
3
4
  # eigenclass on an object. These are used in the implementation of Ruport's
@@ -9,8 +10,7 @@ module Ruport
9
10
  # Example:
10
11
  #
11
12
  # class A
12
- # class << self; include MetaTools; end
13
- #
13
+ # extend Ruport::MetaTools
14
14
  # attribute :foo
15
15
  # end
16
16
  #
@@ -21,22 +21,31 @@ module Ruport
21
21
  self.send("#{sym}=",value)
22
22
  end
23
23
 
24
- # same as accessor, but takes an array of attributes
24
+ # same as attribute, but takes an array of attributes
25
25
  #
26
26
  # e.g. attributes [:foo,:bar,:baz]
27
27
  def attributes(syms)
28
28
  syms.each { |s| attribute s }
29
29
  end
30
-
30
+
31
+ # allows you to define a method on the singleton_class
32
+ #
33
+ # Example:
34
+ #
35
+ # class A
36
+ # extend Ruport::MetaTools
37
+ # action(:bar) { |x| x + 1 }
38
+ # end
39
+ #
40
+ # A.bar(3) #=> 4
31
41
  def action(name,&block)
42
+ raise ActionAlreadyDefinedError if respond_to? name
32
43
  singleton_class.send(:define_method, name, &block)
33
44
  end
34
45
  end
35
46
  end
36
47
 
37
48
  class Module
38
-
39
49
  # provides the singleton_class object
40
50
  def singleton_class; (class << self; self; end); end
41
-
42
51
  end
@@ -107,7 +107,7 @@ module Ruport
107
107
  end
108
108
 
109
109
  # Grabs the result set as a Data::Table or if in raw_data mode, an array of
110
- # DBI:Row objects
110
+ # DBI::Row objects
111
111
  def result; fetch; end
112
112
 
113
113
  # Runs the query without returning its results.
@@ -1,13 +1,33 @@
1
1
  module Ruport
2
-
2
+
3
+ # This module is designed to be mixed in with an ActiveRecord model
4
+ # to add easy conversion to ruport data structures.
5
+ #
6
+ # In the ActiveRecord Model you wish to integrate with report, add the
7
+ # following line just below the class definition:
8
+ #
9
+ # acts_as_reportable
10
+ #
11
+ # This will automatically make all the methods in this module available
12
+ # in the model.
3
13
  module Reportable
4
14
 
15
+ # Converts the models' data into a Ruport::Data::Table, then renders
16
+ # it using the requested plugin. If :find is specified as an option
17
+ # it is passed directly on to ActiveRecords find method
18
+ #
19
+ # User.formatted_table(:pdf, :find => "age > 18")
5
20
  def formatted_table(type,options={})
6
21
  to_table(:find => options[:find],:columns => options[:columns]).as(type){ |e|
7
22
  yield(e) if block_given?
8
23
  }
9
24
  end
10
25
 
26
+ # Converts the models' data into a Ruport::Data::Table object
27
+ # If :find is supplied as an option it is passed directly on to
28
+ # the models find method.
29
+ #
30
+ # data = User.to_table(:find => "age > 18")
11
31
  def to_table(options={})
12
32
  options[:columns] ||= column_names
13
33
  Ruport::Data::Table.new(
@@ -27,6 +47,8 @@ module Ruport
27
47
  end
28
48
  end
29
49
 
50
+ # Extend rails ActiveRecord::Base class to add the option of mixing in
51
+ # the Ruport::Reportable Module in the standard rails way
30
52
  class ActiveRecord::Base
31
53
  def self.acts_as_reportable
32
54
  extend Ruport::Reportable
@@ -95,12 +95,6 @@ module Ruport
95
95
  #run.
96
96
  attr_accessor :results
97
97
 
98
- # Preserved for backwards compatability, please do not use this.
99
- alias_method :report, :results
100
-
101
- # Preserved for backwards compabilitity, please do not use this.
102
- alias_method :report=, :results=
103
-
104
98
  # Simplified interface to Ruport::Query
105
99
  #
106
100
  # === Can read SQL statements from file or string
@@ -259,19 +253,25 @@ module Ruport
259
253
  self.class.run(self,&block)
260
254
  end
261
255
 
256
+
257
+ # loads a CSV in from file.
258
+ #
259
+ # Example
260
+ #
261
+ # my_table = load_csv "foo.csv" #=> Data::Table
262
+ # my_array = load_csv "foo.csv", :as => :array #=> Array
263
+ #
264
+ # See also, Ruport::Data::Table.load
262
265
  def load_csv(file,options={})
263
266
  case options[:as]
264
267
  when :array
265
268
  a = []
266
- Data::Table.load(file) { |s,r| a << r } ; a
269
+ Data::Table.load(file,options) { |s,r| a << r } ; a
267
270
  else
268
- Data::Table.load(file)
271
+ Data::Table.load(file,options)
269
272
  end
270
273
  end
271
274
 
272
- # Preserved for backwards compatibility, please do not use this.
273
- alias_method :generate_report, :run
274
-
275
275
  # Allows logging and other fun stuff. See Ruport.log
276
276
  def log(*args); Ruport.log(*args) end
277
277