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 +22 -2
- data/Rakefile +4 -1
- data/lib/ruport.rb +17 -7
- data/lib/ruport/config.rb +39 -44
- data/lib/ruport/data/record.rb +7 -8
- data/lib/ruport/data/table.rb +39 -5
- data/lib/ruport/data_set.rb +13 -14
- data/lib/ruport/format.rb +10 -10
- data/lib/ruport/format/engine.rb +19 -16
- data/lib/ruport/format/open_node.rb +4 -4
- data/lib/ruport/format/plugin.rb +20 -17
- data/lib/ruport/meta_tools.rb +27 -4
- data/lib/ruport/query.rb +5 -5
- data/lib/ruport/rails/reportable.rb +7 -0
- data/lib/ruport/report.rb +16 -4
- data/test/tc_config.rb +0 -13
- data/test/tc_data_set.rb +3 -0
- data/test/tc_format.rb +1 -0
- data/test/tc_format_engine.rb +1 -0
- data/test/tc_plugin.rb +9 -0
- data/test/tc_ruport.rb +1 -1
- data/test/tc_table.rb +52 -0
- data/test/unit.log +1331 -0
- metadata +2 -2
data/CHANGELOG
CHANGED
@@ -1,4 +1,24 @@
|
|
1
|
-
The current version of Ruby Reports is 0.4.
|
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
|
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.
|
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 = "
|
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,
|
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
|
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
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
61
|
-
|
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
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
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
|
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
|
data/lib/ruport/data/record.rb
CHANGED
@@ -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 &&
|
36
|
-
|
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[
|
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
|
data/lib/ruport/data/table.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
54
|
-
end
|
88
|
+
end
|
55
89
|
|
56
90
|
end
|
57
91
|
end
|
data/lib/ruport/data_set.rb
CHANGED
@@ -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
|
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
|
252
|
+
def as(format)
|
260
253
|
t = Format.table_object(:data => self, :plugin => format)
|
261
|
-
|
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
|
-
|
270
|
-
|
271
|
-
|
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::
|
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
|
85
|
-
|
84
|
+
def self.build_interface_for(engine,name)
|
85
|
+
singleton_class.send(:define_method, name,
|
86
86
|
lambda { |options| simple_interface(engine, options) })
|
87
|
-
|
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(
|
104
|
-
@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
|
-
:
|
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
|
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
|
151
|
+
def self.simple_interface(engine, options={})
|
152
152
|
my_engine = engine.dup
|
153
153
|
|
154
154
|
my_engine.send(:plugin=,options[:plugin])
|