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 +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])
|