ruport 0.4.23 → 0.4.99

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 (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