ruport 0.4.99 → 0.5.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.
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
  }