ruport 1.6.3 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,39 @@
1
+
2
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
3
+ require "ruport"
4
+ require "ruby-debug"
5
+
6
+ class Document < Ruport::Controller
7
+ stage :title, :body
8
+
9
+ def setup
10
+ table = Ruport::Data::Table.load('data/wine.csv')
11
+ grouping = Grouping(table, :by => 'Type')
12
+ self.data = grouping
13
+ end
14
+ end
15
+
16
+ class DocumentFormatter < Ruport::Formatter::PrawnPDF
17
+ renders :prawn_pdf, :for => Document
18
+
19
+ def build_title
20
+ pad(10) do
21
+ text 'WINES', :style => :bold, :size => 20
22
+ horizontal_rule
23
+ end
24
+ end
25
+
26
+ def build_body
27
+ render_grouping(data, :formatter => pdf) # It's Nasty!!!
28
+ end
29
+ end
30
+
31
+ class DocumentTextFormatter < Ruport::Formatter::Text
32
+ renders :text, :for => Document
33
+
34
+ def build_body
35
+ output << data.to_text
36
+ end
37
+ end
38
+
39
+ Document.render(:prawn_pdf, :file => 'pdf_grouping.pdf')
@@ -0,0 +1,28 @@
1
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
2
+ require "ruport"
3
+
4
+ # Example using prawn 0.9.0 pdf generator
5
+ class Document < Ruport::Controller
6
+ stage :body
7
+
8
+ def setup
9
+ self.data = Ruport::Data::Table.new(
10
+ :column_names => %w(Make Model Year),
11
+ :data => [
12
+ %w(Nissan Skyline 1989),
13
+ %w(Mercedes-Benz 500SL 2005),
14
+ %w(Kia Sinatra 2008)
15
+ ])
16
+ end
17
+ end
18
+
19
+ class DocumentFormatter < Ruport::Formatter::PrawnPDF
20
+ renders :prawn_pdf, :for => Document
21
+
22
+ def build_body
23
+ draw_table(data)
24
+ end
25
+ end
26
+
27
+ Document.render(:prawn_pdf, :file => 'pdf_table.pdf')
28
+
@@ -0,0 +1,26 @@
1
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
2
+ require "ruport"
3
+
4
+ class Document < Ruport::Controller
5
+ stage :title, :body
6
+
7
+ def setup
8
+ self.data = Ruport::Data::Table.load('data/wine.csv')
9
+ end
10
+ end
11
+
12
+ class DocumentFormatter < Ruport::Formatter::PrawnPDF
13
+ renders :prawn_pdf, :for => Document
14
+
15
+ def build_title
16
+ pad(10) do
17
+ text 'Wine', :style => :bold, :size => 20
18
+ end
19
+ end
20
+
21
+ def build_body
22
+ draw_table(data)
23
+ end
24
+ end
25
+
26
+ Document.render(:prawn_pdf, :file => 'pdf_table_from_csv.pdf')
@@ -0,0 +1,30 @@
1
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
2
+
3
+ require "ruport"
4
+
5
+ table = Ruport::Data::Table.new(
6
+ :column_names => %w(Make Model Year Class),
7
+ :data => [
8
+ %w(Nissan Skyline 1989 B),
9
+ %w(Mercedes-Benz 500SL 2005 A),
10
+ %w(Kia Sinatra 2008 C)
11
+ ])
12
+
13
+ pdf_options = { :pdf_format => {
14
+ :page_layout => :portrait,
15
+ :page_size => "LETTER",
16
+ },
17
+ :table_format => {
18
+ :cell_style => { :size => 8},
19
+ :row_colors => ["FFFFFF","F0F0F0"],
20
+ :column_widths => {
21
+ 0 => 100,
22
+ 1 => 100,
23
+ 2 => 50,
24
+ 3 => 40
25
+ }
26
+ },
27
+ :file => 'pdf_table_prawn.pdf'
28
+ }
29
+
30
+ table.to_prawn_pdf(pdf_options)
@@ -0,0 +1,13 @@
1
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
2
+ require "ruport"
3
+
4
+ # Quick and simple example using prawn 0.9.0 with to_prawn_pdf.
5
+ table = Ruport::Data::Table.new(
6
+ :column_names => %w(Make Model Year),
7
+ :data => [
8
+ %w(Nissan Skyline 1989),
9
+ %w(Mercedes-Benz 500SL 2005),
10
+ %w(Kia Sinatra 2008)
11
+ ])
12
+
13
+ table.to_prawn_pdf(:file => 'pdf_table_simple.pdf')
@@ -10,18 +10,6 @@
10
10
  # See LICENSE and COPYING for details
11
11
  #
12
12
 
13
-
14
- if RUBY_VERSION > "1.9"
15
- require "csv"
16
- unless defined? FCSV
17
- class Object
18
- FCSV = CSV
19
- alias_method :FCSV, :CSV
20
- end
21
- end
22
- end
23
-
24
-
25
13
  module Ruport #:nodoc:#
26
14
  class FormatterError < RuntimeError #:nodoc:
27
15
  end
@@ -165,19 +165,15 @@ class Ruport::Controller
165
165
  # Example:
166
166
  #
167
167
  # table.as(:csv, :show_table_headers => false)
168
- def as(format,options={})
168
+ def as(format, options = {})
169
169
  raise ControllerNotSetError unless self.class.controller
170
- unless self.class.controller.formats.include?(format)
171
- raise UnknownFormatError
170
+ raise UnknownFormatError unless self.class.controller.formats.include?(format)
171
+ self.class.controller.render(format, self.class.rendering_options.merge(options)) do |rend|
172
+ rend.data = respond_to?(:renderable_data) ? renderable_data(format) : self
173
+ yield(rend) if block_given?
172
174
  end
173
- self.class.controller.render(format,
174
- self.class.rendering_options.merge(options)) do |rend|
175
- rend.data =
176
- respond_to?(:renderable_data) ? renderable_data(format) : self
177
- yield(rend) if block_given?
178
- end
179
- end
180
-
175
+ end
176
+
181
177
  def save_as(file,options={})
182
178
  file =~ /.*\.(.*)/
183
179
  format = $1
@@ -221,6 +217,7 @@ class Ruport::Controller
221
217
  { :html => Ruport::Formatter::HTML,
222
218
  :csv => Ruport::Formatter::CSV,
223
219
  :pdf => Ruport::Formatter::PDF,
220
+ :prawn_pdf => Ruport::Formatter::PrawnPDF,
224
221
  :text => Ruport::Formatter::Text }
225
222
  end
226
223
 
@@ -389,16 +386,16 @@ class Ruport::Controller
389
386
  # # other details omitted
390
387
  # end
391
388
  def required_option(*opts)
392
- self.required_options ||= []
393
- opts.each do |opt|
394
- self.required_options << opt
389
+ (self.required_options ||= []).concat(opts)
395
390
 
396
- o = opt
397
- unless instance_methods(false).include?(o.to_s)
398
- define_method(o) { options.send(o.to_s) }
391
+ opts.each do |opt|
392
+ unless method_defined?(opt)
393
+ define_method(opt) { options.send(opt) }
394
+ end
395
+ setter = "#{opt}="
396
+ unless method_defined?(setter)
397
+ define_method(setter) {|t| options.send(setter, t) }
399
398
  end
400
- opt = "#{opt}="
401
- define_method(opt) {|t| options.send(opt, t) }
402
399
  end
403
400
  end
404
401
 
@@ -573,7 +570,6 @@ class Ruport::Controller
573
570
  else
574
571
  execute_stages
575
572
  end
576
-
577
573
  finalize self.class.final_stage if self.class.final_stage
578
574
  maybe :finalize
579
575
  end
@@ -14,7 +14,7 @@
14
14
  #
15
15
  # Table Example:
16
16
  #
17
- # t = Table(%w[a b c]) do |feeder|
17
+ # t = Ruport::Data::Table(%w[a b c]) do |feeder|
18
18
  # feeder.filter { |r| r.a < 5 }
19
19
  # feeder.transform { |r| r.b = "B: #{r.b}"}
20
20
  # feeder << [1,2,3]
@@ -108,4 +108,4 @@ class Ruport::Data::Feeder
108
108
  @constraints << [:transform,block]
109
109
  end
110
110
 
111
- end
111
+ end
@@ -285,7 +285,7 @@ module Ruport::Data
285
285
  else
286
286
  cols = procs.keys + [field]
287
287
  end
288
- expected = Table(cols) { |t|
288
+ expected = Table.new(:column_names => cols) { |t|
289
289
  each do |name,group|
290
290
  t << procs.inject({field => name}) do |s,r|
291
291
  s.merge(r[0] => r[1].call(group))
@@ -379,7 +379,7 @@ module Kernel
379
379
  #
380
380
  # Example:
381
381
  #
382
- # a = Table(%w[a b c], :data => [[1,2,3],[4,5,6]])
382
+ # a = Ruport::Data::Table.new(%w[a b c], :data => [[1,2,3],[4,5,6]])
383
383
  # b = Grouping(a, :by => "a") #=> creates a new grouping on column "a"
384
384
  #
385
385
  def Grouping(*args)
@@ -1,21 +1,24 @@
1
1
  # Ruport : Extensible Reporting System
2
2
  #
3
3
  # data/table.rb provides a table data structure for Ruport.
4
- #
4
+ #
5
5
  # Created by Gregory Brown / Dudley Flanders, 2006
6
- # Copyright (C) 2006 Gregory Brown / Dudley Flanders, All Rights Reserved.
6
+ # Copyright (C) 2006 Gregory Brown / Dudley Flanders, All Rights Reserved.
7
7
  #
8
8
  # This is free software distributed under the same terms as Ruby 1.8
9
- # See LICENSE and COPYING for details.
9
+ # See LICENSE and COPYING for details.
10
10
  #
11
+
12
+ require "csv"
13
+
11
14
  module Ruport::Data
12
-
15
+
13
16
  # === Overview
14
17
  #
15
- # This class is one of the core classes for building and working with data
16
- # in Ruport. The idea is to get your data into a standard form, regardless
18
+ # This class is one of the core classes for building and working with data
19
+ # in Ruport. The idea is to get your data into a standard form, regardless
17
20
  # of its source (a database, manual arrays, ActiveRecord, CSVs, etc.).
18
- #
21
+ #
19
22
  # Table is intended to be used as the data store for structured, tabular
20
23
  # data.
21
24
  #
@@ -32,9 +35,57 @@ module Ruport::Data
32
35
  @pivot_column = pivot_col
33
36
  @summary_column = summary_col
34
37
  @pivot_order = options[:pivot_order]
38
+ @operation = options[:operation] || :first
39
+ unless Operations.respond_to?(@operation, true)
40
+ raise ArgumentError, "Unknown pivot operation '#{@operation}'"
41
+ end
42
+ end
43
+
44
+ # Row is the first row in the pivoted table (without the group column)
45
+ def row
46
+ return @row if defined?(@row)
47
+
48
+ pivot_column_grouping = Grouping(@table, :by => @pivot_column)
49
+
50
+ ordering = self.class.row_order_to_group_order(@pivot_order)
51
+ pivot_column_grouping.sort_grouping_by!(ordering) if ordering
52
+
53
+ @row = pivot_column_grouping.map { |name,grouping| name }
54
+ end
55
+
56
+ # Column in the first column in the pivoted table (without the group column)
57
+ def column
58
+ @column ||= @table.map { |row| row[@group_column] }.uniq
59
+ end
60
+
61
+ def to_table
62
+ table = Table.new
63
+ create_header(table)
64
+
65
+ column.each do |column_entry|
66
+ row_values = row.map { |row_entry| values[column_entry][row_entry] }
67
+ table << [column_entry] + row_values
68
+ end
69
+
70
+ table
71
+ end
72
+
73
+ def values
74
+ @values ||= Hash.new do |values, column_entry|
75
+ rows_group = rows_groups[column_entry]
76
+
77
+ values[column_entry] =
78
+ row.inject({}) do |values, row_entry|
79
+ matching_rows = rows_group.rows_with(@pivot_column => row_entry)
80
+ values[row_entry] = perform_operation(matching_rows)
81
+ values
82
+ end
83
+ end
35
84
  end
36
85
 
37
- def convert_row_order_to_group_order(row_order_spec)
86
+ private
87
+
88
+ def self.row_order_to_group_order(row_order_spec)
38
89
  case row_order_spec
39
90
  when Array
40
91
  proc {|group|
@@ -50,45 +101,55 @@ module Ruport::Data
50
101
  }
51
102
  when NilClass
52
103
  nil
104
+ when :name # Pass through :name as it's a special instruction to Grouping
105
+ :name
53
106
  else
54
107
  proc {|group| group[0][row_order_spec].to_s }
55
108
  end
56
109
  end
57
110
 
58
- def columns_from_pivot
59
- ordering = convert_row_order_to_group_order(@pivot_order)
60
- pivot_column_grouping = Grouping(@table, :by => @pivot_column)
61
- pivot_column_grouping.each {|n,g| g.add_column(n) { n }}
62
- pivot_column_grouping.sort_grouping_by!(ordering) if ordering
63
- result = []
64
- pivot_column_grouping.each {|name,_| result << name }
65
- result
111
+ def create_header(table)
112
+ table.add_column(@group_column)
113
+ row.each { |name| table.add_column(name) }
66
114
  end
67
115
 
68
- def group_column_entries
69
- @table.map {|row| row[@group_column]}.uniq
116
+ def perform_operation(rows)
117
+ Operations.send @operation, rows, @summary_column
70
118
  end
71
119
 
72
- def to_table
73
- result = Table()
74
- result.add_column(@group_column)
75
- pivoted_columns = columns_from_pivot
76
- pivoted_columns.each { |name| result.add_column(name) }
77
- outer_grouping = Grouping(@table, :by => @group_column)
78
- group_column_entries.each {|outer_group_name|
79
- outer_group = outer_grouping[outer_group_name]
80
- pivot_values = pivoted_columns.inject({}) do |hsh, e|
81
- matching_rows = outer_group.rows_with(@pivot_column => e)
82
- hsh[e] = matching_rows.first && matching_rows.first[@summary_column]
83
- hsh
84
- end
85
- result << [outer_group_name] + pivoted_columns.map {|e|
86
- pivot_values[e]
87
- }
88
- }
89
- result
120
+ def rows_groups
121
+ @rows_groups ||= Grouping(@table, :by => @group_column)
90
122
  end
91
123
 
124
+ module Operations
125
+ extend self
126
+
127
+ def first(rows, summary_column)
128
+ rows.first && rows.first[summary_column]
129
+ end
130
+
131
+ def sum(rows, summary_column)
132
+ rows && rows.inject(0) { |sum,row| sum+row[summary_column] }
133
+ end
134
+
135
+ def count(rows, summary_column)
136
+ rows && rows.length
137
+ end
138
+
139
+ def mean(rows, summary_column)
140
+ return if rows.length == 0
141
+ sum = rows && rows.inject(0) { |sum,row| sum+row[summary_column] }
142
+ sum / rows.length
143
+ end
144
+
145
+ def min(rows, summary_column)
146
+ rows && (rows.collect { |r| r[summary_column] }).min
147
+ end
148
+
149
+ def max(rows, summary_column)
150
+ rows && (rows.collect { |r| r[summary_column] }).max
151
+ end
152
+ end
92
153
  end
93
154
 
94
155
  # Creates a new table with values from the specified pivot column
@@ -113,6 +174,14 @@ module Ruport::Data
113
174
  # first argument. This wart will likely
114
175
  # be fixed in a future version.
115
176
  #
177
+ # <b><tt>:operation</tt></b>:: The operation to perform on
178
+ # <tt>:values</tt> column. Supported
179
+ # operations are <tt>:first</tt>,
180
+ # <tt>:sum</tt>, <tt>:count</tt>,
181
+ # <tt>:mean</tt>, <tt>:min</tt>, and
182
+ # <tt>:max</tt>. If not specified, the
183
+ # default is <tt>:first</tt>.
184
+ #
116
185
  # Example:
117
186
  #
118
187
  # Given a table <em>my_table</em>:
@@ -139,9 +208,9 @@ module Ruport::Data
139
208
  # +---------------+
140
209
  #
141
210
  def pivot(pivot_column, options = {})
142
- group_column = options[:group_by] ||
211
+ group_column = options[:group_by] ||
143
212
  raise(ArgumentError, ":group_by option required")
144
- value_column = options[:values] ||
213
+ value_column = options[:values] ||
145
214
  raise(ArgumentError, ":values option required")
146
215
  Pivot.new(
147
216
  self, group_column, pivot_column, value_column, options
@@ -153,10 +222,10 @@ module Ruport::Data
153
222
  # This module provides facilities for creating tables from csv data.
154
223
  #
155
224
  module FromCSV
156
- # Loads a CSV file directly into a Table using the FasterCSV library.
225
+ # Loads a CSV file directly into a Table.
157
226
  #
158
227
  # Example:
159
- #
228
+ #
160
229
  # # treat first row as column_names
161
230
  # table = Table.load('mydata.csv')
162
231
  #
@@ -169,8 +238,8 @@ module Ruport::Data
169
238
  def load(csv_file, options={},&block)
170
239
  get_table_from_csv(:foreach, csv_file, options,&block)
171
240
  end
172
-
173
- # Creates a Table from a CSV string using FasterCSV. See Table.load for
241
+
242
+ # Creates a Table from a CSV string. See Table.load for
174
243
  # additional examples.
175
244
  #
176
245
  # table = Table.parse("a,b,c\n1,2,3\n4,5,6\n")
@@ -180,32 +249,31 @@ module Ruport::Data
180
249
  end
181
250
 
182
251
  private
183
-
184
- def get_table_from_csv(msg,param,options={},&block) #:nodoc:
185
- require "fastercsv"
186
252
 
253
+ def get_table_from_csv(msg,param,options={},&block) #:nodoc:
187
254
  options = {:has_names => true,
188
255
  :csv_options => {} }.merge(options)
189
-
256
+
190
257
  adjust_options_for_fcsv_headers(options)
191
258
 
192
259
  table = self.new(options) do |feeder|
193
260
  first_line = true
194
- FasterCSV.send(msg,param,options[:csv_options]) do |row|
261
+
262
+ ::CSV.send(msg,param,options[:csv_options]) do |row|
195
263
  if first_line
196
264
  adjust_for_headers(feeder.data,row,options)
197
265
  first_line = false
198
266
  next if options[:has_names]
199
267
  end
200
-
268
+
201
269
  if block
202
270
  handle_csv_row_proc(feeder,row,options,block)
203
271
  else
204
272
  feeder << row
205
- end
273
+ end
206
274
  end
207
275
  end
208
-
276
+
209
277
  return table
210
278
  end
211
279
 
@@ -214,10 +282,10 @@ module Ruport::Data
214
282
  rc = options[:record_class] || Record
215
283
  row = rc.new(row, :attributes => feeder.data.column_names)
216
284
  end
217
-
285
+
218
286
  block[feeder,row]
219
287
  end
220
-
288
+
221
289
  def adjust_options_for_fcsv_headers(options)
222
290
  options[:has_names] = false if options[:csv_options][:headers]
223
291
  end
@@ -231,7 +299,7 @@ module Ruport::Data
231
299
  end
232
300
  end
233
301
 
234
- include Enumerable
302
+ include Enumerable
235
303
  extend FromCSV
236
304
 
237
305
  include Ruport::Controller::Hooks
@@ -240,13 +308,13 @@ module Ruport::Data
240
308
  def self.inherited(base) #:nodoc:
241
309
  base.renders_as_table
242
310
  end
243
-
311
+
244
312
  # Creates a new table based on the supplied options.
245
313
  #
246
314
  # Valid options:
247
- # <b><tt>:data</tt></b>:: An Array of Arrays representing the
315
+ # <b><tt>:data</tt></b>:: An Array of Arrays representing the
248
316
  # records in this Table.
249
- # <b><tt>:column_names</tt></b>:: An Array containing the column names
317
+ # <b><tt>:column_names</tt></b>:: An Array containing the column names
250
318
  # for this Table.
251
319
  # <b><tt>:filters</tt></b>:: A proc or array of procs that set up
252
320
  # conditions to filter the data being
@@ -259,55 +327,55 @@ module Ruport::Data
259
327
  #
260
328
  # Example:
261
329
  #
262
- # table = Table.new :data => [[1,2,3], [3,4,5]],
330
+ # table = Table.new :data => [[1,2,3], [3,4,5]],
263
331
  # :column_names => %w[a b c]
264
332
  #
265
333
  def initialize(options={})
266
334
  @column_names = options[:column_names] ? options[:column_names].dup : []
267
335
  @record_class = options[:record_class] &&
268
336
  options[:record_class].name || "Ruport::Data::Record"
269
- @data = []
270
-
337
+ @data = []
338
+
271
339
  feeder = Feeder.new(self)
272
-
340
+
273
341
  Array(options[:filters]).each { |f| feeder.filter(&f) }
274
342
  Array(options[:transforms]).each { |t| feeder.transform(&t) }
275
-
343
+
276
344
  if options[:data]
277
345
  options[:data].each do |e|
278
346
  if e.kind_of?(Record)
279
- e = if @column_names.empty? or
347
+ e = if @column_names.empty? or
280
348
  e.attributes.all? { |a| a.kind_of?(Numeric) }
281
349
  e.to_a
282
350
  else
283
- e.to_hash.values_at(*@column_names)
351
+ e.to_hash.values_at(*@column_names)
284
352
  end
285
353
  end
286
354
  r = recordize(e)
287
-
355
+
288
356
  feeder << r
289
- end
290
- end
291
-
292
- yield(feeder) if block_given?
357
+ end
358
+ end
359
+
360
+ yield(feeder) if block_given?
293
361
  end
294
362
 
295
363
  # This Table's column names
296
364
  attr_reader :column_names
297
-
365
+
298
366
  # This Table's data
299
- attr_reader :data
300
-
367
+ attr_reader :data
368
+
301
369
  require "forwardable"
302
370
  extend Forwardable
303
371
  def_delegators :@data, :each, :length, :size, :empty?, :[]
304
-
305
- # Sets the column names for this table. <tt>new_column_names</tt> should
372
+
373
+ # Sets the column names for this table. <tt>new_column_names</tt> should
306
374
  # be an array listing the names of the columns.
307
375
  #
308
376
  # Example:
309
- #
310
- # table = Table.new :data => [[1,2,3], [3,4,5]],
377
+ #
378
+ # table = Table.new :data => [[1,2,3], [3,4,5]],
311
379
  # :column_names => %w[a b c]
312
380
  #
313
381
  # table.column_names = %w[e f g]
@@ -334,27 +402,27 @@ module Ruport::Data
334
402
  #
335
403
  # Example:
336
404
  #
337
- # one = Table.new :data => [[1,2], [3,4]],
405
+ # one = Table.new :data => [[1,2], [3,4]],
338
406
  # :column_names => %w[a b]
339
407
  #
340
- # two = Table.new :data => [[1,2], [3,4]],
408
+ # two = Table.new :data => [[1,2], [3,4]],
341
409
  # :column_names => %w[a b]
342
410
  #
343
411
  # one.eql?(two) #=> true
344
412
  #
345
413
  def eql?(other)
346
- data.eql?(other.data) && column_names.eql?(other.column_names)
414
+ data.eql?(other.data) && column_names.eql?(other.column_names)
347
415
  end
348
416
 
349
417
  alias_method :==, :eql?
350
418
 
351
- # Used to add extra data to the Table. <tt>row</tt> can be an Array,
419
+ # Used to add extra data to the Table. <tt>row</tt> can be an Array,
352
420
  # Hash or Record. It also can be anything that implements a meaningful
353
421
  # to_hash or to_ary.
354
422
  #
355
423
  # Example:
356
424
  #
357
- # data = Table.new :data => [[1,2], [3,4]],
425
+ # data = Table.new :data => [[1,2], [3,4]],
358
426
  # :column_names => %w[a b]
359
427
  # data << [8,9]
360
428
  # data << { :a => 4, :b => 5}
@@ -362,23 +430,33 @@ module Ruport::Data
362
430
  #
363
431
  def <<(row)
364
432
  @data << recordize(row)
365
- return self
366
- end
367
-
433
+ return self
434
+ end
435
+
436
+ # Add a row to a certain location within the existing table.
437
+ #
438
+ # data.add_row([8,9], :position => 0)
439
+ #
440
+ #
441
+ def add_row(row_data, options={})
442
+ @data.insert(options[:position] || @data.length, recordize(row_data))
443
+ return self
444
+ end
445
+
368
446
  # Returns the record class constant being used by the table.
369
447
  def record_class
370
448
  @record_class.split("::").inject(Class) { |c,el| c.send(:const_get,el) }
371
449
  end
372
-
450
+
373
451
  # Used to merge two Tables by rows.
374
452
  # Raises an ArgumentError if the Tables don't have identical columns.
375
453
  #
376
454
  # Example:
377
455
  #
378
- # inky = Table.new :data => [[1,2], [3,4]],
456
+ # inky = Table.new :data => [[1,2], [3,4]],
379
457
  # :column_names => %w[a b]
380
458
  #
381
- # blinky = Table.new :data => [[5,6]],
459
+ # blinky = Table.new :data => [[5,6]],
382
460
  # :column_names => %w[a b]
383
461
  #
384
462
  # sue = inky + blinky
@@ -386,11 +464,11 @@ module Ruport::Data
386
464
  #
387
465
  def +(other)
388
466
  raise ArgumentError unless other.column_names == @column_names
389
- self.class.new( :column_names => @column_names,
467
+ self.class.new( :column_names => @column_names,
390
468
  :data => @data + other.data,
391
469
  :record_class => record_class )
392
470
  end
393
-
471
+
394
472
  # Allows you to change the order of, or reduce the number of columns in a
395
473
  # Table.
396
474
  #
@@ -411,40 +489,40 @@ module Ruport::Data
411
489
  def reorder(*indices)
412
490
  raise(ArgumentError,"Can't reorder without column names set!") if
413
491
  @column_names.empty?
414
-
492
+
415
493
  indices = indices[0] if indices[0].kind_of? Array
416
-
417
- if indices.all? { |i| i.kind_of? Integer }
418
- indices.map! { |i| @column_names[i] }
494
+
495
+ if indices.all? { |i| i.kind_of? Integer }
496
+ indices.map! { |i| @column_names[i] }
419
497
  end
420
-
498
+
421
499
  reduce(indices)
422
500
  end
423
-
501
+
424
502
  # Adds an extra column to the Table.
425
503
  #
426
504
  # Available Options:
427
- # <b><tt>:default</tt></b>:: The default value to use for the column in
505
+ # <b><tt>:default</tt></b>:: The default value to use for the column in
428
506
  # existing rows. Set to nil if not specified.
429
- #
507
+ #
430
508
  # <b><tt>:position</tt></b>:: Inserts the column at the indicated position
431
509
  # number.
432
510
  #
433
- # <b><tt>:before</tt></b>:: Inserts the new column before the column
511
+ # <b><tt>:before</tt></b>:: Inserts the new column before the column
434
512
  # indicated (by name).
435
513
  #
436
514
  # <b><tt>:after</tt></b>:: Inserts the new column after the column
437
515
  # indicated (by name).
438
516
  #
439
517
  # If a block is provided, it will be used to build up the column.
440
- #
518
+ #
441
519
  # Example:
442
520
  #
443
- # data = Table("a","b") { |t| t << [1,2] << [3,4] }
444
- #
521
+ # data = Table.new("a","b") { |t| t << [1,2] << [3,4] }
522
+ #
445
523
  # # basic usage, column full of 1's
446
524
  # data.add_column 'new_column', :default => 1
447
- #
525
+ #
448
526
  # # new empty column before new_column
449
527
  # data.add_column 'new_col2', :before => 'new_column'
450
528
  #
@@ -453,43 +531,43 @@ module Ruport::Data
453
531
  #
454
532
  # # new column built via a block, added at the end of the table
455
533
  # data.add_column("new_col4") { |r| r.a + r.b }
456
- #
534
+ #
457
535
  def add_column(name,options={})
458
536
  if pos = options[:position]
459
- column_names.insert(pos,name)
537
+ column_names.insert(pos,name)
460
538
  elsif pos = options[:after]
461
- column_names.insert(column_names.index(pos)+1,name)
539
+ column_names.insert(column_names.index(pos)+1,name)
462
540
  elsif pos = options[:before]
463
541
  column_names.insert(column_names.index(pos),name)
464
542
  else
465
543
  column_names << name
466
- end
544
+ end
467
545
 
468
546
  if block_given?
469
547
  each { |r| r[name] = yield(r) || options[:default] }
470
548
  else
471
549
  each { |r| r[name] = options[:default] }
472
550
  end; self
473
- end
474
-
551
+ end
552
+
475
553
  # Add multiple extra columns to the Table. See <tt>add_column</tt> for
476
554
  # a list of available options.
477
- #
555
+ #
478
556
  # Example:
479
557
  #
480
- # data = Table("a","b") { |t| t << [1,2] << [3,4] }
481
- #
558
+ # data = Table.new("a","b") { |t| t << [1,2] << [3,4] }
559
+ #
482
560
  # data.add_columns ['new_column_1','new_column_2'], :default => 1
483
561
  #
484
- def add_columns(names,options={})
562
+ def add_columns(names,options={})
485
563
  raise "Greg isn't smart enough to figure this out.\n"+
486
564
  "Send ideas in at http://list.rubyreports.org" if block_given?
487
565
  need_reverse = !!(options[:after] || options[:position])
488
566
  names = names.reverse if need_reverse
489
- names.each { |n| add_column(n,options) }
567
+ names.each { |n| add_column(n,options) }
490
568
  self
491
569
  end
492
-
570
+
493
571
  # Removes the given column from the table. May use name or position.
494
572
  #
495
573
  # Example:
@@ -502,7 +580,7 @@ module Ruport::Data
502
580
  column_names.delete(col)
503
581
  each { |r| r.send(:delete,col) }
504
582
  end
505
-
583
+
506
584
  # Removes multiple columns from the table. May use name or position
507
585
  # Will autosplat arrays.
508
586
  #
@@ -514,9 +592,9 @@ module Ruport::Data
514
592
  cols = cols[0] if cols[0].kind_of? Array
515
593
  cols.each { |col| remove_column(col) }
516
594
  end
517
-
595
+
518
596
  # Renames a column. Will update Record attributes as well.
519
- #
597
+ #
520
598
  # Example:
521
599
  #
522
600
  # old_values = table.map { |r| r.a }
@@ -528,12 +606,12 @@ module Ruport::Data
528
606
  def rename_column(old_name,new_name)
529
607
  index = column_names.index(old_name) or return
530
608
  self.column_names[index] = new_name
531
- each { |r| r.rename_attribute(old_name,new_name,false)}
609
+ each { |r| r.rename_attribute(old_name,new_name,false)}
532
610
  end
533
611
 
534
612
  # Renames multiple columns. Takes either a hash of "old" => "new"
535
613
  # names or two arrays of names %w[old names],%w[new names].
536
- #
614
+ #
537
615
  # Example:
538
616
  #
539
617
  # table.column_names #=> ["a", "b"]
@@ -553,7 +631,7 @@ module Ruport::Data
553
631
  end
554
632
  return
555
633
  end
556
-
634
+
557
635
  raise ArgumentError unless old_cols
558
636
 
559
637
  if new_cols
@@ -565,12 +643,12 @@ module Ruport::Data
565
643
  end
566
644
  h.each {|old,new| rename_column(old,new) }
567
645
  end
568
-
646
+
569
647
  # Exchanges one column with another.
570
648
  #
571
- # Example:
649
+ # Example:
572
650
  #
573
- # >> a = Table(%w[a b c]) { |t| t << [1,2,3] << [4,5,6] }
651
+ # >> a = Table.new(%w[a b c]) { |t| t << [1,2,3] << [4,5,6] }
574
652
  # >> puts a
575
653
  # +-----------+
576
654
  # | a | b | c |
@@ -587,24 +665,24 @@ module Ruport::Data
587
665
  # | 6 | 5 | 4 |
588
666
  # +-----------+
589
667
  #
590
- def swap_column(a,b)
668
+ def swap_column(a,b)
591
669
  if [a,b].all? { |r| r.kind_of? Fixnum }
592
670
  col_a,col_b = column_names[a],column_names[b]
593
671
  column_names[a] = col_b
594
672
  column_names[b] = col_a
595
673
  else
596
- a_ind, b_ind = [column_names.index(a), column_names.index(b)]
674
+ a_ind, b_ind = [column_names.index(a), column_names.index(b)]
597
675
  column_names[b_ind] = a
598
676
  column_names[a_ind] = b
599
677
  end
600
678
  end
601
-
679
+
602
680
  # Allows you to specify a new column to replace an existing column
603
681
  # in your table via a block.
604
682
  #
605
683
  # Example:
606
684
  #
607
- # >> a = Table(%w[a b c]) { |t| t << [1,2,3] << [4,5,6] }
685
+ # >> a = Table.new(%w[a b c]) { |t| t << [1,2,3] << [4,5,6] }
608
686
  # >> a.replace_column("c","c2") { |r| r.c * 2 + r.a }
609
687
  #
610
688
  # >> puts a
@@ -622,12 +700,12 @@ module Ruport::Data
622
700
  else
623
701
  each { |r| r[old_col] = yield(r) }
624
702
  end
625
- end
626
-
703
+ end
704
+
627
705
  # Generates a sub table
628
- #
706
+ #
629
707
  # Examples:
630
- #
708
+ #
631
709
  # table = [[1,2,3,4],[5,6,7,8],[9,10,11,12]].to_table(%w[a b c d])
632
710
  #
633
711
  # Using column_names and a range:
@@ -641,16 +719,16 @@ module Ruport::Data
641
719
  # sub_table == [[1,4],[5,8],[9,12]].to_table(%w[a d]) #=> true
642
720
  #
643
721
  # Using column_names and a block:
644
- #
645
- # sub_table = table.sub_table(%w[d b]) { |r| r.a < 6 }
646
- # sub_table == [[4,2],[8,6]].to_table(%w[d b]) #=> true
722
+ #
723
+ # sub_table = table.sub_table(%w[d b]) { |r| r.a < 6 }
724
+ # sub_table == [[4,2],[8,6]].to_table(%w[d b]) #=> true
647
725
  #
648
726
  # Using a range for row reduction:
649
727
  # sub_table = table.sub_table(1..-1)
650
728
  # sub_table == [[5,6,7,8],[9,10,11,12]].to_table(%w[a b c d]) #=> true
651
729
  #
652
730
  # Using just a block:
653
- #
731
+ #
654
732
  # sub_table = table.sub_table { |r| r.c > 10 }
655
733
  # sub_table == [[9,10,11,12]].to_table(%w[a b c d]) #=> true
656
734
  #
@@ -662,8 +740,8 @@ module Ruport::Data
662
740
  elsif block
663
741
  self.class.new( :column_names => cor, :data => data.select(&block))
664
742
  else
665
- self.class.new( :column_names => cor, :data => data)
666
- end
743
+ self.class.new( :column_names => cor, :data => data)
744
+ end
667
745
  end
668
746
 
669
747
  # Generates a sub table in place, modifying the receiver. See documentation
@@ -677,7 +755,7 @@ module Ruport::Data
677
755
  end
678
756
 
679
757
  alias_method :sub_table!, :reduce
680
-
758
+
681
759
  # Returns an array of values for the given column name.
682
760
  #
683
761
  # Example:
@@ -689,20 +767,20 @@ module Ruport::Data
689
767
  case(name)
690
768
  when Integer
691
769
  unless column_names.empty?
692
- raise ArgumentError if name > column_names.length
770
+ raise ArgumentError if name > column_names.length
693
771
  end
694
772
  else
695
773
  raise ArgumentError unless column_names.include?(name)
696
774
  end
697
-
775
+
698
776
  map { |r| r[name] }
699
777
  end
700
-
778
+
701
779
  # Calculates sums. If a column name or index is given, it will try to
702
- # convert each element of that column to an integer or float
780
+ # convert each element of that column to an integer or float
703
781
  # and add them together.
704
782
  #
705
- # If a block is given, it yields each Record so that you can do your own
783
+ # If a block is given, it yields each Record so that you can do your own
706
784
  # calculation.
707
785
  #
708
786
  # Example:
@@ -714,7 +792,7 @@ module Ruport::Data
714
792
  # table.sigma { |r| r.col2 + 1 } #=> 15
715
793
  #
716
794
  def sigma(column=nil)
717
- inject(0) { |s,r|
795
+ inject(0) { |s,r|
718
796
  if column
719
797
  s + if r.get(column).kind_of? Numeric
720
798
  r.get(column)
@@ -724,12 +802,12 @@ module Ruport::Data
724
802
  else
725
803
  s + yield(r)
726
804
  end
727
- }
805
+ }
728
806
  end
729
807
 
730
808
  alias_method :sum, :sigma
731
809
 
732
- # Returns a sorted table. If col_names is specified,
810
+ # Returns a sorted table. If col_names is specified,
733
811
  # the block is ignored and the table is sorted by the named columns.
734
812
  #
735
813
  # The second argument specifies sorting options. Currently only
@@ -759,54 +837,54 @@ module Ruport::Data
759
837
  # table.sort_rows_by(["col1", "col2"], :order => descending)
760
838
  #
761
839
  def sort_rows_by(col_names=nil, options={}, &block)
762
- # stabilizer is needed because of
840
+ # stabilizer is needed because of
763
841
  # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/170565
764
842
  stabilizer = 0
765
-
766
- nil_rows, sortable = partition do |r|
767
- Array(col_names).any? { |c| r[c].nil? }
843
+
844
+ nil_rows, sortable = partition do |r|
845
+ Array(col_names).any? { |c| r[c].nil? }
768
846
  end
769
847
 
770
848
  data_array =
771
849
  if col_names
772
- sortable.sort_by do |r|
850
+ sortable.sort_by do |r|
773
851
  stabilizer += 1
774
- [Array(col_names).map {|col| r[col]}, stabilizer]
852
+ [Array(col_names).map {|col| r[col]}, stabilizer]
775
853
  end
776
854
  else
777
855
  sortable.sort_by(&block)
778
- end
779
-
856
+ end
857
+
780
858
  data_array += nil_rows
781
- data_array.reverse! if options[:order] == :descending
859
+ data_array.reverse! if options[:order] == :descending
782
860
 
783
- table = self.class.new( :data => data_array,
861
+ table = self.class.new( :data => data_array,
784
862
  :column_names => @column_names,
785
863
  :record_class => record_class )
786
864
 
787
865
  return table
788
- end
789
-
866
+ end
867
+
790
868
  # Same as Table#sort_rows_by, but self modifying.
791
869
  # See <tt>sort_rows_by</tt> for documentation.
792
870
  #
793
871
  def sort_rows_by!(col_names=nil,options={},&block)
794
- table = sort_rows_by(col_names,options,&block)
872
+ table = sort_rows_by(col_names,options,&block)
795
873
  @data = table.data
796
874
  end
797
-
875
+
798
876
  # Get an array of records from the Table limited by the criteria specified.
799
877
  #
800
878
  # Example:
801
879
  #
802
- # table = Table.new :data => [[1,2,3], [1,4,6], [4,5,6]],
880
+ # table = Table.new :data => [[1,2,3], [1,4,6], [4,5,6]],
803
881
  # :column_names => %w[a b c]
804
882
  # table.rows_with(:a => 1) #=> [[1,2,3], [1,4,6]]
805
883
  # table.rows_with(:a => 1, :b => 4) #=> [[1,4,6]]
806
884
  # table.rows_with_a(1) #=> [[1,2,3], [1,4,6]]
807
885
  # table.rows_with(%w[a b]) {|a,b| [a,b] == [1,4] } #=> [[1,4,6]]
808
886
  #
809
- def rows_with(columns,&block)
887
+ def rows_with(columns,&block)
810
888
  select { |r|
811
889
  if block
812
890
  block[*(columns.map { |c| r.get(c) })]
@@ -816,11 +894,48 @@ module Ruport::Data
816
894
  }
817
895
  end
818
896
 
897
+ # Search row for a string and return the position
898
+ #
899
+ # Example:
900
+ #
901
+ # table = Table.new :data => [["Mow Lawn","50"], ["Sew","40"], ["Clean dishes","5"]],
902
+ # :column_names => %w[task cost]
903
+ # table.row_search("Sew", :column => 0) #=> [[1,2,3], [1,4,6]]
904
+ #
905
+ # Search for a number in column 0 greater than 999.
906
+ # result = table.row_search(999, :column => 0, :greater_than => true)
907
+ #
908
+ #
909
+ def row_search(search, options={})
910
+ position = 0
911
+
912
+ if column = options[:column]
913
+ self.each do |row|
914
+
915
+ if gt=options[:greater_than]
916
+ return position if row[column] > search
917
+ end
918
+
919
+ if lt=options[:less_than]
920
+ return position if row[column] < search
921
+ end
922
+
923
+ unless gt or lt
924
+ if row[column] =~ /#{search}/ # Search for part of or whole search text.
925
+ return position
926
+ end
927
+ end
928
+
929
+ position += 1
930
+ end
931
+ end
932
+ end
933
+
819
934
  # Create a copy of the Table. Records will be copied as well.
820
935
  #
821
936
  # Example:
822
937
  #
823
- # one = Table.new :data => [[1,2], [3,4]],
938
+ # one = Table.new :data => [[1,2], [3,4]],
824
939
  # :column_names => %w[a b]
825
940
  # two = one.dup
826
941
  #
@@ -830,32 +945,32 @@ module Ruport::Data
830
945
  @data = []
831
946
  from.data.each { |r| self << r.dup }
832
947
  end
833
-
948
+
834
949
  # Uses Ruport's built-in text formatter to render this Table into a String.
835
- #
950
+ #
836
951
  # Example:
837
- #
838
- # data = Table.new :data => [[1,2], [3,4]],
952
+ #
953
+ # data = Table.new :data => [[1,2], [3,4]],
839
954
  # :column_names => %w[a b]
840
955
  # puts data.to_s
841
- #
956
+ #
842
957
  def to_s
843
958
  as(:text)
844
- end
959
+ end
845
960
 
846
961
  # Convert the Table into a Group using the supplied group name.
847
962
  #
848
- # data = Table.new :data => [[1,2], [3,4]],
963
+ # data = Table.new :data => [[1,2], [3,4]],
849
964
  # :column_names => %w[a b]
850
965
  # group = data.to_group("my_group")
851
966
  #
852
967
  def to_group(name=nil)
853
- Group.new( :data => data,
968
+ Group.new( :data => data,
854
969
  :column_names => column_names,
855
970
  :name => name,
856
971
  :record_class => record_class )
857
972
  end
858
-
973
+
859
974
  # NOTE: does not respect tainted status
860
975
  alias_method :clone, :dup
861
976
 
@@ -866,50 +981,50 @@ module Ruport::Data
866
981
  # <tt>rows_with(:columnname => args[0])</tt>.
867
982
  #
868
983
  def method_missing(id,*args,&block)
869
- return as($1.to_sym,*args,&block) if id.to_s =~ /^to_(.*)/
984
+ return as($1.to_sym,*args,&block) if id.to_s =~ /^to_(.*)/
870
985
  return rows_with($1.to_sym => args[0]) if id.to_s =~ /^rows_with_(.*)/
871
986
  super
872
987
  end
873
-
988
+
874
989
  def feed_element(row)
875
990
  recordize(row)
876
991
  end
877
-
878
- private
879
-
992
+
993
+ private
994
+
880
995
  def recordize(row)
881
996
  case row
882
997
  when Array
883
998
  normalize_array(row)
884
999
  when Hash
885
- normalize_hash(row)
886
- when record_class
1000
+ normalize_hash(row)
1001
+ when record_class
887
1002
  recordize(normalize_record(row))
888
1003
  else
889
1004
  normalize_hash(row) rescue normalize_array(row)
890
- end
891
- end
892
-
1005
+ end
1006
+ end
1007
+
893
1008
  def normalize_hash(hash_obj)
894
- hash_obj = hash_obj.to_hash
1009
+ hash_obj = hash_obj.to_hash
895
1010
  raise ArgumentError unless @column_names
896
1011
  record_class.new(hash_obj, :attributes => @column_names)
897
- end
898
-
1012
+ end
1013
+
899
1014
  def normalize_record(record)
900
1015
  record.send(column_names.empty? ? :to_a : :to_hash)
901
- end
902
-
1016
+ end
1017
+
903
1018
  def normalize_array(array)
904
- attributes = @column_names.empty? ? nil : @column_names
905
- record_class.new(array.to_ary, :attributes => attributes)
906
- end
1019
+ attributes = @column_names.empty? ? nil : @column_names
1020
+ record_class.new(array.to_ary, :attributes => attributes)
1021
+ end
907
1022
  end
908
1023
  end
909
1024
 
910
1025
 
911
- module Kernel
912
-
1026
+ module Ruport
1027
+
913
1028
  # Shortcut interface for creating Data::Tables
914
1029
  #
915
1030
  # Examples:
@@ -918,33 +1033,30 @@ module Kernel
918
1033
  # t = Table("a","b","c") #=> creates a new empty table w. cols a,b,c
919
1034
  #
920
1035
  # # allows building table inside of block, returns table object
921
- # t = Table(%w[a b c]) { |t| t << [1,2,3] }
1036
+ # t = Table(%w[a b c]) { |t| t << [1,2,3] }
922
1037
  #
923
1038
  # # allows loading table from CSV
924
1039
  # # accepts all Data::Table.load options, including block (yields table,row)
925
1040
  #
926
1041
  # t = Table("foo.csv")
927
1042
  # t = Table("bar.csv", :has_names => false)
928
- def Table(*args,&block)
929
- table=
1043
+ def self.Table(*args,&block)
930
1044
  case(args[0])
931
1045
  when Array
932
1046
  opts = args[1] || {}
933
1047
  Ruport::Data::Table.new(f={:column_names => args[0]}.merge(opts),&block)
934
1048
  when /\.csv/
935
- return Ruport::Data::Table.load(*args,&block)
1049
+ Ruport::Data::Table.load(*args,&block)
936
1050
  when Hash
937
1051
  if file = args[0].delete(:file)
938
- return Ruport::Data::Table.load(file,args[0],&block)
1052
+ Ruport::Data::Table.load(file,args[0],&block)
939
1053
  elsif string = args[0].delete(:string)
940
- return Ruport::Data::Table.parse(string,args[0],&block)
1054
+ Ruport::Data::Table.parse(string,args[0],&block)
941
1055
  else
942
- return Ruport::Data::Table.new(args[0],&block)
1056
+ Ruport::Data::Table.new(args[0],&block)
943
1057
  end
944
1058
  else
945
1059
  Ruport::Data::Table.new(:data => [], :column_names => args,&block)
946
- end
947
-
948
- return table
1060
+ end
949
1061
  end
950
- end
1062
+ end