ruport 0.4.17 → 0.4.19

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,4 +1,24 @@
1
- The current version of Ruby Reports is 0.4.17
1
+ The current version of Ruby Reports is 0.4.19
2
+
3
+ changes since 0.4.1
4
+
5
+ - Made method_missing a lot more friendly all around.
6
+
7
+ - Made minor interface changes.
8
+
9
+ - Data::Table and Data::Record now dup correctly
10
+
11
+ - Fixed some serious issues between Data::Table and TextPlugin
12
+
13
+ - Report#eval_template now returns an ERb string instead of printing to STDOUT
14
+
15
+ - Added Data::Table#split which allows basic grouping operations
16
+
17
+ - Added Report#load_csv convenience method
18
+
19
+ - Tightened up Data::Table#<< to ensure column_names match record attributes
20
+
21
+ - Fixed appending a Record in Data::Table#<<
2
22
 
3
23
  changes since 0.4.15
4
24
 
@@ -24,7 +44,7 @@ changes since 0.4.13
24
44
 
25
45
  - added accessor style methods to DataRow via method_missing
26
46
 
27
- - added prune method for Table formmatting.
47
+ - added prune method for Table formatting.
28
48
 
29
49
  changes since 0.4.11
30
50
 
data/Rakefile CHANGED
@@ -10,6 +10,9 @@ end
10
10
 
11
11
  #Set to true to disable dependency resolution
12
12
  LEAN=false
13
+ dir = File.dirname(__FILE__)
14
+ lib = File.join(dir, "lib", "ruport.rb")
15
+ version = File.read(lib)[/^\s*VERSION\s*=\s*(['"])(\d+\.\d+\.d+)\1/,2]
13
16
 
14
17
  task :default => [:test]
15
18
 
@@ -21,7 +24,7 @@ end
21
24
 
22
25
  spec = Gem::Specification.new do |spec|
23
26
  spec.name = LEAN ? "lean-ruport" : "ruport"
24
- spec.version = "0.4.17"
27
+ spec.version = "0.4.19"
25
28
  spec.platform = Gem::Platform::RUBY
26
29
  spec.summary = "A generalized Ruby report generation and templating engine."
27
30
  spec.files = Dir.glob("{examples,lib,test}/**/**/*") +
data/lib/ruport.rb CHANGED
@@ -12,9 +12,9 @@
12
12
 
13
13
  module Ruport
14
14
 
15
- begin; require 'rubygems'; rescue LoadError; nil end
15
+ #begin; require 'rubygems'; rescue LoadError; nil end
16
16
 
17
- VERSION = "Ruby Reports Version 0.4.17"
17
+ VERSION = "0.4.19"
18
18
 
19
19
  # Ruports logging and error interface.
20
20
  # Can generate warnings or raise fatal errors
@@ -31,7 +31,7 @@ module Ruport
31
31
  # The status <tt>:warn</tt> will invoke Logger#warn. A status of
32
32
  # <tt>:fatal</tt> will invoke Logger#fatal and raise an exception
33
33
  #
34
- # By default, complain will also print warnings to $stderr
34
+ # By default, <tt>log()</tt> will also print warnings to $stderr
35
35
  # You can redirect this to any I/O object via <tt>:output</tt>
36
36
  #
37
37
  # You can prevent messages from appearing on the secondary output by setting
@@ -39,7 +39,7 @@ module Ruport
39
39
  #
40
40
  # If you want to recover these messages to secondary output for debugging, you
41
41
  # can use Config::enable_paranoia
42
- def Ruport.log(message,options={})
42
+ def self.log(message,options={})
43
43
  options = {:status => :warn, :output => $stderr}.merge(options)
44
44
  options[:output].puts "[!!] #{message}" unless
45
45
  options[:level].eql?(:log_only) and not Ruport::Config.paranoid?
@@ -49,9 +49,19 @@ module Ruport
49
49
  end
50
50
  end
51
51
 
52
- def Ruport.complain(*args); Ruport.log(*args) end
53
-
54
- def Ruport.configure(&block)
52
+ #Alias for Ruport.log
53
+ def self.complain(*args); Ruport.log(*args) end
54
+
55
+ # yields a Ruport::Config object, allowing you to specify configuration
56
+ # options.
57
+ #
58
+ # Example:
59
+ #
60
+ # Ruport.configure do |c|
61
+ # c.source :default, :dsn => "dbi:mysql:foo",
62
+ # :user => "clyde", :password => "pman"
63
+ # end
64
+ def self.configure(&block)
55
65
  block.call(Ruport::Config)
56
66
  end
57
67
  end
data/lib/ruport/config.rb CHANGED
@@ -9,7 +9,6 @@
9
9
  #
10
10
  # See LICENSE and COPYING for details
11
11
  #
12
- require "singleton"
13
12
  require "ostruct"
14
13
  module Ruport
15
14
  # This class serves as the configuration system for Ruport.
@@ -57,51 +56,49 @@ module Ruport
57
56
  # Saving this config information into a file and then requiring it can allow
58
57
  # you share configurations between Ruport applications.
59
58
  #
60
- class Config
61
- include Singleton
59
+ module Config
60
+ module_function
61
+
62
+ def source(*args)
63
+ return sources[args.first] if args.length == 1
64
+ sources[args.first] = OpenStruct.new(*args[1..-1])
65
+ check_source(sources[args.first],args.first)
66
+ end
62
67
 
63
- def Config.method_missing(method_id,*args)
64
- case(method_id)
65
- when :source
66
- return @@sources[args.first] if args.length == 1
67
- @@sources[args.first] = OpenStruct.new(*args[1..-1])
68
- check_source(@@sources[args.first],args.first)
69
- when :mailer
70
- @@mailers[args.first] = OpenStruct.new(*args[1..-1])
71
- check_mailer(@@mailers[args.first],args.first)
72
- when :log_file,:log_file=
73
- @@logger = Logger.new(args.first)
74
- when :default_source
75
- @@sources[:default]
76
- when :default_mailer
77
- @@mailers[:default]
78
- when :sources
79
- @@sources
80
- when :mailers
81
- @@mailers
82
- when :logger
83
- @@logger
84
- when :enable_paranoia
85
- @@paranoid = true
86
- when :disable_paranoia
87
- @@paranoid = false
88
- when :paranoid?
89
- @@paranoid
90
- else
91
- super
92
- end
68
+ def mailer(*args)
69
+ mailers[args.first] = OpenStruct.new(*args[1..-1])
70
+ check_mailer(mailers[args.first],args.first)
71
+ end
72
+
73
+ def log_file(file)
74
+ @logger = Logger.new(file)
93
75
  end
94
76
 
95
- private
96
-
97
- def Config.init!
98
- @@sources = { }
99
- @@mailers = { }
100
- @@logger = nil
101
- @@paranoid = false
77
+ #alias_method :log_file=, :log_file
78
+ def log_file=(file)
79
+ log_file(file)
80
+ end
81
+
82
+ def default_source
83
+ sources[:default]
102
84
  end
103
85
 
104
- def Config.check_source(settings,label)
86
+ def default_mailer
87
+ mailers[:default]
88
+ end
89
+
90
+ def sources; @sources ||= {}; end
91
+
92
+ def mailers; @mailers ||= {}; end
93
+
94
+ def logger; @logger; end
95
+
96
+ def enable_paranoia; @paranoid = true; end
97
+ def disable_paranoia; @paranoid = false; end
98
+ def paranoid=(val); @paranoid = val; end
99
+ def paranoid?; !!@paranoid; end
100
+
101
+ def check_source(settings,label)
105
102
  unless settings.dsn
106
103
  Ruport.complain(
107
104
  "Missing DSN for source #{label}!",
@@ -111,7 +108,7 @@ module Ruport
111
108
  end
112
109
  end
113
110
 
114
- def Config.check_mailer(settings, label)
111
+ def check_mailer(settings, label)
115
112
  unless settings.host
116
113
  Ruport.complain(
117
114
  "Missing host for mailer #{label}",
@@ -120,8 +117,6 @@ module Ruport
120
117
  )
121
118
  end
122
119
  end
123
-
124
- init!
125
120
 
126
121
  end
127
122
  end
@@ -12,7 +12,6 @@ module Ruport::Data
12
12
 
13
13
  attr_reader :data
14
14
  def_delegators :@data,:each, :length
15
-
16
15
 
17
16
  def [](index)
18
17
  if index.kind_of? Integer
@@ -31,16 +30,16 @@ module Ruport::Data
31
30
  end
32
31
 
33
32
  def ==(other)
34
- return false if attributes && !other.attributes
35
- return false if other.attributes && !attributes
36
- (attributes == other.attributes) && (data == other.data)
33
+ return false if @attributes && !other.attributes
34
+ return false if other.attributes && !@attributes
35
+ @attributes == other.attributes && @data == other.data
37
36
  end
38
37
 
39
38
  alias_method :eql?, :==
40
39
 
41
- def to_a; data.dup; end
40
+ def to_a; @data.dup; end
42
41
 
43
- def to_h; Hash[*attributes.zip(data).flatten] end
42
+ def to_h; Hash[*@attributes.zip(data).flatten] end
44
43
 
45
44
  def attributes; @attributes && @attributes.dup; end
46
45
 
@@ -52,7 +51,7 @@ module Ruport::Data
52
51
 
53
52
  def reorder!(*indices)
54
53
  @data = indices.map { |i| self[i] }
55
- if attributes
54
+ if @attributes
56
55
  if indices.all? { |e| e.kind_of? Integer }
57
56
  @attributes = indices.map { |i| @attributes[i] }
58
57
  else
@@ -62,7 +61,7 @@ module Ruport::Data
62
61
  end
63
62
 
64
63
  def dup
65
- self.class.new(data,:attributes => attributes)
64
+ self.class.new(@data,:attributes => attributes)
66
65
  end
67
66
 
68
67
  #FIXME: This does not take into account frozen / tainted state
@@ -7,13 +7,13 @@ module Ruport::Data
7
7
  end
8
8
 
9
9
  attr_reader :column_names
10
-
10
+
11
11
  def column_names=(other)
12
12
  @column_names = other.dup
13
13
  end
14
14
 
15
15
  def <<(other)
16
- case(other)
16
+ case other
17
17
  when Array
18
18
  @data << Record.new(other, :attributes => @column_names)
19
19
  when Hash
@@ -21,8 +21,10 @@ module Ruport::Data
21
21
  arr = @column_names.map { |k| other[k] }
22
22
  @data << Record.new(arr, :attributes => @column_names)
23
23
  when Record
24
- @data << Record.reorder(*column_names)
24
+ raise ArgumentError unless column_names.eql? other.attributes
25
+ @data << Record.new(other.to_a, :attributes => @column_names)
25
26
  end
27
+ self
26
28
  end
27
29
 
28
30
  def reorder!(*indices)
@@ -34,6 +36,10 @@ module Ruport::Data
34
36
  dup.reorder! *indices
35
37
  end
36
38
 
39
+ def dup
40
+ a = self.class.new(:data => @data, :column_names => @column_names)
41
+ end
42
+
37
43
  def self.load(csv_file, options = {})
38
44
  options = {:has_names => true}.merge(options)
39
45
  require "fastercsv"
@@ -49,9 +55,37 @@ module Ruport::Data
49
55
  else
50
56
  yield(loaded_data,row)
51
57
  end
58
+ end ; loaded_data
59
+ end
60
+
61
+ def split(options={})
62
+ if options[:group].kind_of? Array
63
+ group = map { |r| options[:group].map { |e| r[e] } }.uniq
64
+ data = group.inject([]) { |s,g|
65
+ s + [select { |r| options[:group].map { |e| r[e] }.eql?(g) }]
66
+ }
67
+ c = column_names - options[:group]
68
+ else
69
+ group = map { |r| r[options[:group]] }.uniq
70
+ data = group.inject([]) { |s,g|
71
+ s + [select { |r| r[options[:group]].eql?(g) }]
72
+ }
73
+ c = column_names - [options[:group]]
74
+
75
+ end
76
+ data.map! { |g|
77
+ Ruport::Data::Table.new(
78
+ :data => g.map { |x| x.reorder(*c) },
79
+ :column_names => c
80
+ )
81
+ }
82
+ if options[:group].kind_of? Array
83
+ Ruport::Data::Record.new(data,
84
+ :attributes => group.map { |e| e.join("_") } )
85
+ else
86
+ Ruport::Data::Record.new data, :attributes => group
52
87
  end
53
- return loaded_data
54
- end
88
+ end
55
89
 
56
90
  end
57
91
  end
@@ -48,7 +48,7 @@ module Ruport
48
48
  #an array which contains column names
49
49
  attr_accessor :fields
50
50
  alias_method :column_names, :fields
51
-
51
+ alias_method :column_names=, :fields=
52
52
  #the default value to fill empty cells with
53
53
  attr_accessor :default
54
54
 
@@ -180,7 +180,7 @@ module Ruport
180
180
  input = FasterCSV.read(source) if source =~ /\.csv/
181
181
  loaded_data = self.new
182
182
 
183
- action = if block_given?
183
+ action = if block
184
184
  lambda { |r| block[loaded_data,r] }
185
185
  else
186
186
  lambda { |r| loaded_data << r }
@@ -245,20 +245,13 @@ module Ruport
245
245
  # uses Format::Builder to render DataSets in various ready to output
246
246
  # formats.
247
247
  #
248
- # data.as(:html) -> String
249
- #
250
- # data.as(:text) do |builder|
251
- # builder.range = 2..4 -> String
252
- # builder.header = "My Title"
253
- # end
254
- #
255
248
  # To add new formats to this function, simply re-open Format::Builder
256
249
  # and add methods like <tt>render_my_format_name</tt>.
257
250
  #
258
251
  # This will enable <tt>data.as(:my_format_name)</tt>
259
- def as(format,&action)
252
+ def as(format)
260
253
  t = Format.table_object(:data => self, :plugin => format)
261
- action.call(t) if block_given?
254
+ yield(t) if block_given?
262
255
  t.render
263
256
  end
264
257
 
@@ -266,9 +259,15 @@ module Ruport
266
259
  # The result of the block will be added to a running total
267
260
  #
268
261
  # Only works with blocks resulting in numeric values.
269
- def sigma
270
- inject(0) do |s,r|
271
- s + (yield(r) || 0)
262
+ #
263
+ # Also allows summing up a specific column, e.g.
264
+ #
265
+ # my_set.sigma("col1")
266
+ def sigma(f = nil)
267
+ if f
268
+ inject(0) { |s,r| s + (r[f] || 0) }
269
+ else
270
+ inject(0) { |s,r| s + (yield(r) || 0) }
272
271
  end
273
272
  end
274
273
 
data/lib/ruport/format.rb CHANGED
@@ -67,7 +67,7 @@ module Ruport
67
67
  # These interfaces pass a hash of keywords to the associative engine.
68
68
  # Here is a simple example:
69
69
  #
70
- # Format.build_interface_for Format::Engine::Yable, "table"
70
+ # Format.build_interface_for Format::Engine::Table, "table"
71
71
  #
72
72
  # This will allow the following code to work:
73
73
  #
@@ -81,10 +81,10 @@ module Ruport
81
81
  # which would be accessible via
82
82
  #
83
83
  # Format.my_name ...
84
- def Format.build_interface_for(engine,name)
85
- (class << self; self; end).send(:define_method, name,
84
+ def self.build_interface_for(engine,name)
85
+ singleton_class.send(:define_method, name,
86
86
  lambda { |options| simple_interface(engine, options) })
87
- (class << self; self; end).send(:define_method, "#{name}_object",
87
+ singleton_class.send(:define_method, "#{name}_object",
88
88
  lambda { |options|
89
89
  options[:auto_render] = false; simple_interface(engine,options) })
90
90
  end
@@ -100,8 +100,8 @@ module Ruport
100
100
  # evaluated in the context of the object they are being called from, rather
101
101
  # than within an instance of Format.
102
102
  #
103
- def initialize(klass_binding=binding)
104
- @binding = klass_binding
103
+ def initialize(class_binding=binding)
104
+ @binding = class_binding
105
105
  end
106
106
 
107
107
  # This is the text to be processed by the filters
@@ -114,7 +114,7 @@ module Ruport
114
114
  # of the object that Format is bound to.
115
115
  def filter_erb
116
116
  self.class.document :data => @content,
117
- :klass_binding => @binding,
117
+ :class_binding => @binding,
118
118
  :plugin => :text
119
119
  end
120
120
 
@@ -137,18 +137,18 @@ module Ruport
137
137
  # Format.register_filter :no_ohz do
138
138
  # content.gsub(/O/i,"")
139
139
  # end
140
- def Format.register_filter(name,&filter_proc)
140
+ def self.register_filter(name,&filter_proc)
141
141
  @@filters["filter_#{name}".to_sym] = filter_proc
142
142
  end
143
143
 
144
- def method_missing(m)
144
+ def method_missing(m,*args)
145
145
  @@filters[m] ? @@filters[m][@content] : super
146
146
  end
147
147
 
148
148
 
149
149
  private
150
150
 
151
- def Format.simple_interface(engine, options={})
151
+ def self.simple_interface(engine, options={})
152
152
  my_engine = engine.dup
153
153
 
154
154
  my_engine.send(:plugin=,options[:plugin])