ruport 1.6.3 → 1.7.1

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.
@@ -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