ruport 0.4.99 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/AUTHORS CHANGED
@@ -2,9 +2,17 @@
2
2
 
3
3
  - {Gregory Brown}[mailto:gregory.t.brown@gmail.com]
4
4
  - {Dudley Flanders}[mailto:dudley@misnomer.us]
5
+ - {James Healy}[mailto:jimmy@deefa.com]
6
+ - Dinko Mehinovic
5
7
 
6
8
  = Contributors / People we've (legally) stolen from:
7
9
 
10
+ Iain Broadfoot:
11
+ - RuportDay 2006 Participant
12
+
13
+ Eric Pugh:
14
+ - RuportDay 2006 Participant
15
+
8
16
  James Edward Gray II:
9
17
  - Original inspiration via query.rb
10
18
  - system_extensions.rb
@@ -13,17 +21,4 @@ Francis Hwang:
13
21
  - SQLSplit
14
22
 
15
23
  Simon Claret:
16
- - PDF table support
17
-
18
- Dinko Mehinovic:
19
- - util/release/raa.rb
20
- - util/release/freshmeat.rb
21
-
22
- James Healy:
23
- - original inspiration for Invoice engine
24
- - SVG::Graph integration
25
-
26
- The SVG Graph support is made possible by SVG::Graph, which we have vendored
27
- because a gem is not available.
28
- See: http://www.germane-software.com/software/SVG/SVG::Graph/
29
-
24
+ - Initial PDF table support (now deprecated)
data/CHANGELOG CHANGED
@@ -1,4 +1,16 @@
1
- The current version of Ruby Reports is 0.4.99
1
+ Tnghe current version of Ruby Reports is 0.5.0
2
+
3
+ changes since 0.4.99
4
+
5
+ - SVG::Graph has been dropped in favor of Scruffy for graphing support.
6
+
7
+ - You can now define helpers for specific engines.
8
+
9
+ - All known obsolete documentation has been removed
10
+
11
+ - Collection#as now takes a block (Making it work like DataSet#as used to)
12
+
13
+ - Some cleanup and fixes to examples/
2
14
 
3
15
  changes since 0.4.23
4
16
 
data/README CHANGED
@@ -42,6 +42,10 @@
42
42
  # (available via rubygems)
43
43
  #
44
44
  # MailFactory: For email support.
45
+ # (available via rubygems)
46
+ #
47
+ # Scruffy: For Graph Support
48
+ # (available via rubygems)
45
49
  #
46
50
  # Note that by installing any of the dependencies, either via gems or manually,
47
51
  # their functionality will automatically be enabled.
@@ -95,28 +99,6 @@
95
99
  # For further reading and specific examples, please explore the examples/
96
100
  # directory in the source tree and read the Ruport Cookbook.
97
101
  #
98
- # = Caveats
99
- #
100
- # Ruport is experimental software. It's not completely tested and the API is
101
- # changing rapidly from version to version. Test suites are becoming
102
- # increasingly robust, but have not identified all possible edge cases. If
103
- # Ruport goes wild on you, it's because it hasn't been tamed yet.
104
- #
105
- # The functionality is also not complete yet. There is a lot left to be added
106
- # and there is a lot to think about. If you find yourself wondering why
107
- # feature foo is in Ruport, chances are it just hasn't been written yet.
108
- #
109
- # Documentation so far is something that is a struggle to keep up with. As of
110
- # this release, there is at least partial documentation for the API. This
111
- # will continue to get better as time goes on.
112
- #
113
- # Platform independence is a priority, but I don't absolutely always have
114
- # access to every OS / DBMS combination, so if something breaks on your
115
- # system, please feel free to yell loud at the mailing list.
116
- #
117
- # That having been said, I do use ruport in my daily work. That means that it
118
- # will probably have at least something you will find useful. Or so I hope.
119
- #
120
102
  # = Resources
121
103
  #
122
104
  # The best way to get help and make suggestions is the Ruport mailing list.
@@ -140,7 +122,7 @@
140
122
  # - The latest stable API documentation is available at:
141
123
  # http://reporting.stonecode.org/docs
142
124
  #
143
- # There also will be some tutorials on ruport.infogami.com
125
+ # There also will be some tutorials on reporting.stonecode.org
144
126
  #
145
127
  # If you are interested in developing Ruport, please *never* study code in
146
128
  # official releases. As this software is in it's early stages, it's essential
@@ -152,47 +134,16 @@
152
134
  #
153
135
  # svn co svn://rubyforge.org//var/svn/ruport/trunk/
154
136
  #
137
+ # - Or if you are interested in the latest updates to the stable branch
138
+ #
139
+ # svn co svn://rubyforge.org//var/svn/ruport/branches/stable
140
+ #
155
141
  # Those who would like to become regular contributors will be given write
156
142
  # access. Also, anyone interested in the internal design and project
157
143
  # management aspects of Ruport can request to be added to our Trac
158
144
  # account. This is primarily intended for people who are working on the
159
- # project actively, though.
160
- #
161
- # = Background / Summary
162
- #
163
- # Ruport aims to help you fetch data from various sources, perform
164
- # manipulations on them as needed, and then output them in a variety
165
- # of formats easily. The powerful ERb templating engine is integrated
166
- # to let you embed Ruport code into your formatted data. Also, Ruport
167
- # provides a high level interface to databases, to make getting
168
- # your data easy.
169
- #
170
- # The core of Ruport is it's datastructures. Data::Record, Data::Table, and
171
- # Data::Set provide tools that help with manipulation and preperation of data
172
- # for reporting.
173
- #
174
- # The rest of the code is organized into three main models, Report, Query,
175
- # and Format. Each is meant to be a high level interface to Ruport.
176
- # The inner classes can be used where a decent level of customization
177
- # is needed.
178
- #
179
- # Report is in some sense the 'controller' of your application, and provides
180
- # methods to help you write a Reporting application
181
- #
182
- # Format provides support for building filters and specialized formatting
183
- # systems.
184
- #
185
- # Query currently provides a high level interface to DBI.
186
- # If you would like to query a database or load a sql dump,
187
- # this class can help you do that. It can generate Data::Table objects on
188
- # the fly, or feed you DBI:Rows, depending on what you need.
189
145
  #
190
- # Finally, Please consult the API documentation and/or source code for more
191
- # information. (http://reporting.stonecode.org/docs). Not all classes have been
192
- # documented but the ones that have may be easier to understand when their docs
193
- # have been read. Also, feel free to contribute documentation.
194
146
  #
195
- # If you have any questions or concerns, hop on the mailing list and fire away!
147
+ # Thanks for checking out Ruport!
196
148
  #
197
- # Thanks for downloading my software and I hope you enjoy it!
198
- # -Greg
149
+ # If you have any questions or concerns, hop on the mailing list and fire away!
data/Rakefile CHANGED
@@ -23,7 +23,7 @@ end
23
23
 
24
24
  spec = Gem::Specification.new do |spec|
25
25
  spec.name = LEAN ? "lean-ruport" : "ruport"
26
- spec.version = "0.4.99"
26
+ spec.version = "0.5.0"
27
27
  spec.platform = Gem::Platform::RUBY
28
28
  spec.summary = "A generalized Ruby report generation and templating engine."
29
29
  spec.files = Dir.glob("{examples,lib,test,bin}/**/**/*") +
@@ -43,6 +43,7 @@ spec = Gem::Specification.new do |spec|
43
43
  spec.add_dependency('RedCloth', '>= 3.0.0')
44
44
  spec.add_dependency('pdf-writer', '>= 1.1.3')
45
45
  spec.add_dependency("mailfactory", ">= 1.2.2")
46
+ spec.add_dependency("scruffy", ">= 0.2.2")
46
47
  end
47
48
  spec.author = "Gregory Brown"
48
49
  spec.email = " gregory.t.brown@gmail.com"
@@ -1,6 +1,8 @@
1
1
  require "ruport"
2
2
  include Ruport
3
3
 
4
+ # this shows how you can create your own plugin with some default rendering
5
+ # options as a shortcut.
4
6
 
5
7
  class Format::Plugin::FieldlessCSVPlugin < Format::Plugin::CSVPlugin
6
8
  rendering_options :show_field_names => false
@@ -8,3 +10,4 @@ class Format::Plugin::FieldlessCSVPlugin < Format::Plugin::CSVPlugin
8
10
  register_on :table_engine
9
11
  end
10
12
 
13
+ puts [[1,2,3]].to_table(%w[a b c]).to_fieldless_csv
@@ -4,32 +4,18 @@ require "ruport"
4
4
  # Start with a Ruport::Table object. This could easily come from
5
5
  # activerecord or any of the other ways to build a Table. See the ruport
6
6
  # recipes book for some ideas
7
- data = [[14.2, 14.4, 14.56, 14.87, 15.23, 15.58, 15.79]].to_table(%w[45000 35000 20000 15000 5000 400 17])
7
+ data = [[5, 7, 9, 12, 14, 16, 18]].to_table(%w[jan feb mar apr may jun jul])
8
8
 
9
9
  # initialize the graph with our table object
10
10
  graph = Ruport::Format.graph_object :plugin => :svg, :data => data
11
11
 
12
- # The SVG:Graph library accepts a wide range of options to style the resulting graph.
13
- # These are set using a simple hash. The ones used below are approximately 1/3 of the available
14
- # options.
15
- options = {
16
- :graph_style => :line,
17
- :height => 500,
18
- :width => 600,
19
- :graph_title => "Global Average Temperature vs. Number of Pirates",
20
- :show_graph_title => true,
21
- :x_title => "Number of Pirates (approx.)",
22
- :show_x_title => true,
23
- :y_title => "Global Average Temperature (C)",
24
- :show_y_title => true,
25
- :key => false,
26
- :min_scale_value => 13,
27
- :scale_integers => true,
28
- :no_css => true
29
- }
30
-
31
- # apply the options to the graph
32
- graph.options = options
12
+ # there are currently only a handful of options for customising the
13
+ # appearance of the graph. The ones listed here are all of them at
14
+ # the current time.
15
+ graph.width = 700
16
+ graph.height = 500
17
+ graph.title = "A Simple Line Graph"
18
+ graph.style = :line # other options: bar, smiles, area, stacked
33
19
 
34
20
  # render the graph and print it to stdout. To save the output to a file, try:
35
21
  # ruby line_graph.rb > pirates.svg
@@ -1,3 +1,4 @@
1
+ #this demonstrates how to create a complete engine / plugin setup.
1
2
  require "ruport"
2
3
 
3
4
  include Ruport
@@ -1,9 +1,11 @@
1
1
  require "rubygems"
2
2
  require "ruport"
3
3
 
4
+ # this shows how you can alter an existing plugin's rendering.
5
+
4
6
  include Ruport
5
7
 
6
- class Format::Plugin::Text < Ruport::Format::Plugin
8
+ class Text < Ruport::Format::Plugin
7
9
  renderer :table do
8
10
  data.inject(rendered_field_names) { |s,r|
9
11
  s << r.to_a.join("()") << "\n"
@@ -14,6 +16,7 @@ class Format::Plugin::Text < Ruport::Format::Plugin
14
16
  data.column_names.join("---") << "\n"
15
17
  end
16
18
 
19
+ plugin_name :text
17
20
  register_on :table_engine
18
21
  end
19
22
 
@@ -1,6 +1,5 @@
1
1
  require "ruport"
2
2
 
3
-
4
3
  class MyReport < Ruport::Report
5
4
  prepare {
6
5
  self.results = "Foo Bar Baz"
data/lib/ruport.rb CHANGED
@@ -12,9 +12,7 @@
12
12
 
13
13
  module Ruport
14
14
 
15
- #begin; require 'rubygems'; rescue LoadError; nil end
16
-
17
- VERSION = "0.4.99"
15
+ VERSION = "0.5.0"
18
16
 
19
17
  # Ruports logging and error interface.
20
18
  # Can generate warnings or raise fatal errors
@@ -22,7 +22,9 @@ module Ruport::Data
22
22
  #
23
23
  # my_collection.as(:csv) #=> "1,2,3\n4,5,6"
24
24
  def as(type)
25
- Ruport::Format.table :data => self, :plugin => type
25
+ eng = Ruport::Format.table_object :data => self, :plugin => type
26
+ yield(eng) if block_given?
27
+ eng.render
26
28
  end
27
29
 
28
30
  # Converts any Collection object to a Data::Set
@@ -43,7 +45,7 @@ module Ruport::Data
43
45
  end
44
46
 
45
47
  attr_reader :data
46
- def_delegators :@data, :each, :length, :[], :empty?
48
+ def_delegators :@data, :each, :length, :size, :[], :empty?
47
49
  end
48
50
  end
49
51
 
@@ -12,8 +12,8 @@ module Ruport::Data
12
12
  # of its source (a database, manual arrays, ActiveRecord, CSVs, etc.).
13
13
  #
14
14
  # Set is intended to be used as the data store for unstructured data -
15
- # Ruport::Data::Table is an alternate intermediary data store intended
16
- # for structured, tabular data.
15
+ # Ruport::Data::Table is an alternate data store intended for structured,
16
+ # tabular data.
17
17
  #
18
18
  # Once your data is in a Ruport::Data::Set object, it can be manipulated
19
19
  # to suit your needs, then used to build a report.
@@ -22,8 +22,8 @@ module Ruport::Data
22
22
  # of its source (a database, manual arrays, ActiveRecord, CSVs, etc.).
23
23
  #
24
24
  # Table is intended to be used as the data store for structured, tabular
25
- # data - Ruport::Data::Set is an alternate intermediary data store intended
26
- # for less structured data.
25
+ # data - Ruport::Data::Set is an alternate data store intended for less
26
+ # structured data.
27
27
  #
28
28
  # Once your data is in a Ruport::Data::Table object, it can be manipulated
29
29
  # to suit your needs, then used to build a report.
@@ -106,10 +106,12 @@ module Ruport::Data
106
106
  # data.reorder!([1,0])
107
107
  def reorder!(*indices)
108
108
  indices = indices[0] if indices[0].kind_of? Array
109
- @column_names = if indices.all? { |i| i.kind_of? Integer }
110
- indices.map { |i| @column_names[i] }
111
- else
112
- indices
109
+ if @column_names
110
+ @column_names = if indices.all? { |i| i.kind_of? Integer }
111
+ indices.map { |i| @column_names[i] }
112
+ else
113
+ indices
114
+ end
113
115
  end
114
116
  @data.each { |r| r.reorder! *indices }; self
115
117
  end
@@ -138,17 +140,25 @@ module Ruport::Data
138
140
  end
139
141
  end
140
142
 
141
- # Removes a column from the table. Any values in the specified column are
142
- # lost.
143
- # data = Table.new({:data => [1,2], [3,4], :column_names => %w[a b]})
144
- # data.append_column({:name => 'new_column', :fill => 1)
145
- # data.remove_column({:name => 'new_column')
146
- # data == Table.new({:data => [1,2], [3,4], :column_names => %w[a b]})
147
- # => true
148
- def remove_column(options={})
149
- raise ArgumentError unless column_names.include? options[:name]
150
- reorder! column_names - [options[:name]]
151
- end
143
+ # Removes a column from the table. Any values in the specified column are
144
+ # lost.
145
+ # data = Table.new({:data => [1,2], [3,4], :column_names => %w[a b]})
146
+ # data.append_column({:name => 'new_column', :fill => 1)
147
+ # data.remove_column({:name => 'new_column')
148
+ # data == Table.new({:data => [1,2], [3,4], :column_names => %w[a b]})
149
+ # #=> true
150
+ #
151
+ # data = [[1,2],[3,4]].to_table
152
+ # data.remove_column(1)
153
+ # data.eql? [[1],[3]].to_table %w[a] #=> true
154
+ def remove_column(options={})
155
+ if options.kind_of? Integer
156
+ reorder!((0...data[0].length).to_a - [options])
157
+ else
158
+ raise ArgumentError unless column_names.include? options[:name]
159
+ reorder! column_names - [options[:name]]
160
+ end
161
+ end
152
162
 
153
163
  # Create a shallow copy of the table: the same data elements are referenced
154
164
  # by both the old and new table.
@@ -25,6 +25,7 @@ module Ruport
25
25
  end
26
26
 
27
27
  def alias_engine(klass,name)
28
+ singleton_class.send(:define_method,:engine_name) { name }
28
29
  Format::Engine.engine_classes ||= {}
29
30
  Format::Engine.engine_classes[name] = klass
30
31
  end
@@ -92,6 +93,7 @@ module Ruport
92
93
  end
93
94
 
94
95
  def method_missing(id,*args)
96
+ active_plugin.extend active_plugin.helpers[engine_name]
95
97
  super unless active_plugin.respond_to?("#{id}_helper")
96
98
  return active_plugin.send("#{id}_helper",self)
97
99
  end
@@ -103,6 +105,8 @@ module Ruport
103
105
 
104
106
  class Format::Engine::Graph < Ruport::Format::Engine
105
107
 
108
+ attributes [:width, :height, :style, :title]
109
+
106
110
  renderer do
107
111
  super
108
112
  active_plugin.render_graph
@@ -1,3 +1,6 @@
1
+ class InvalidGraphDataError < RuntimeError; end
2
+ class InvalidGraphOptionError < RuntimeError; end
3
+
1
4
  require 'bigdecimal'
2
5
 
3
6
  module Ruport
@@ -10,8 +13,21 @@ module Ruport
10
13
 
11
14
  include MetaTools
12
15
 
13
- def helper(name,&block)
14
- singleton_class.send( :define_method, "#{name}_helper", &block )
16
+ def helper(name,options={},&block)
17
+ if options[:engines]
18
+ options[:engines].each { |e|
19
+ helpers[e].send(:define_method, "#{name}_helper", &block)
20
+ }
21
+ elsif options[:engine]
22
+ helpers[options[:engine]].send( :define_method,
23
+ "#{name}_helper", &block)
24
+ else
25
+ singleton_class.send( :define_method, "#{name}_helper", &block )
26
+ end
27
+ end
28
+
29
+ def helpers
30
+ @helpers ||= Hash.new { |h,k| h[k] = Module.new }
15
31
  end
16
32
 
17
33
  private :singleton_class, :attribute, :attributes, :action
@@ -51,7 +67,7 @@ module Ruport
51
67
 
52
68
  class CSVPlugin < Format::Plugin
53
69
 
54
- helper(:init_plugin) { require "fastercsv" }
70
+ helper(:init_plugin) { |eng| require "fastercsv" }
55
71
 
56
72
  format_field_names do
57
73
  FasterCSV.generate { |csv| csv << data.column_names }
@@ -72,54 +88,48 @@ module Ruport
72
88
  # check the supplied data can be used for graphing
73
89
  data.each { |r|
74
90
  if data.column_names.size != r.data.size
75
- raise ArgumentError, "Column names and data do not match"
91
+ raise InvalidGraphDataError, "Column names and data do not match"
76
92
  end
77
93
  r.data.each { |c|
78
94
  begin
79
95
  c = BigDecimal.new(c) unless c.kind_of?(Float) ||
80
96
  c.kind_of?(Fixnum) || c.kind_of?(BigDecimal)
81
97
  rescue
82
- raise ArgumentError, "Unable to convert #{c.to_s} into a number"
98
+ raise InvalidGraphDataError, "Unable to convert #{c.to_s} into a number"
83
99
  end
84
100
  }
85
101
  }
86
102
 
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"
103
+ raise InvalidGraphOptionError, 'You must provide a width before rendering a graph' if eng.width.nil?
104
+ raise InvalidGraphOptionError, 'You must provide a height before rendering a graph' if eng.height.nil?
105
+ raise InvalidGraphOptionError, 'You must provide a style before rendering a graph' if eng.style.nil?
106
+ if eng.style != :area && eng.style != :bar &&
107
+ eng.style != :line &&
108
+ eng.style != :smiles &&
109
+ eng.style != :stacked
110
+ raise InvalidGraphOptionError, 'Invalid graph style'
105
111
  end
106
112
 
107
- # create an instance of the graphing class
108
- options[:fields] = data.column_names
109
- @graph = graphclass.new(options)
113
+ require 'scruffy'
114
+ @graph = Scruffy::Graph.new
115
+ @graph.title = eng.title unless eng.title.nil?
116
+ @graph.theme = Scruffy::Themes::Mephisto.new
117
+ @graph.point_markers = @data.column_names
118
+ @graph_style = eng.style
119
+ @graph_width = eng.width
120
+ @graph_height = eng.height
110
121
  }
111
122
 
112
123
  renderer :graph do
113
124
 
114
125
  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
- })
126
+ @graph.add(@graph_style,
127
+ r.tags[0] || 'series ' + (i+1).to_s,
128
+ r.data)
119
129
  }
120
130
 
121
131
  # return the rendered graph
122
- @graph.burn()
132
+ @graph.render(:size => [@graph_width, @graph_height])
123
133
  end
124
134
 
125
135
  plugin_name :svg
@@ -223,25 +233,25 @@ module Ruport
223
233
  renderer(:invoice) { pdf.render }
224
234
 
225
235
  # Company Information in top lefthand corner
226
- helper(:build_company_header) { |eng|
236
+ helper(:build_company_header, :engine => :invoice_engine) { |eng|
227
237
  @tod = pdf.y
228
238
  text_box(eng.company_info)
229
239
  }
230
240
 
231
- helper(:build_headers) { |eng|
241
+ helper(:build_headers, :engine => :invoice_engine) { |eng|
232
242
  build_company_header_helper(eng)
233
243
  build_customer_header_helper(eng)
234
244
  build_title_helper(eng)
235
245
  build_order_header_helper(eng)
236
246
  }
237
247
 
238
- helper(:build_order_header) { |eng|
248
+ helper(:build_order_header, :engine => :invoice_engine) { |eng|
239
249
  if eng.order_info
240
250
  text_box(eng.order_info, :position => 350)
241
251
  end
242
252
  }
243
253
 
244
- helper(:build_title) { |eng|
254
+ helper(:build_title, :engine => :invoice_engine) { |eng|
245
255
  pdf.y = @tod
246
256
  if eng.title
247
257
  pdf.text eng.title, :left => 350, :font_size => 14
@@ -249,7 +259,7 @@ module Ruport
249
259
  end
250
260
  }
251
261
 
252
- helper(:build_footer) { |eng|
262
+ helper(:build_footer, :engine => :invoice_engine) { |eng|
253
263
  # footer
254
264
  pdf.open_object do |footer|
255
265
  pdf.save_state
@@ -270,7 +280,7 @@ module Ruport
270
280
  pdf.stop_page_numbering(true, :current)
271
281
  }
272
282
 
273
- helper(:build_body) do
283
+ helper(:build_body, :engine => :invoice_engine) do
274
284
  pdf.start_page_numbering(500, 20, 8, :right)
275
285
 
276
286
  # order contents
@@ -288,7 +298,7 @@ module Ruport
288
298
  end
289
299
 
290
300
  # Order details
291
- helper(:build_customer_header) { |eng|
301
+ helper(:build_customer_header, :engine => :invoice_engine) { |eng|
292
302
  pdf.y -= 10
293
303
  text_box(eng.customer_info)
294
304
  }