ruport 0.4.17 → 0.4.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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])