ruport 0.4.11 → 0.4.13
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +29 -1
- data/README +16 -26
- data/Rakefile +2 -1
- data/TODO +2 -16
- data/examples/report.rb +35 -0
- data/examples/template.rb +15 -0
- data/examples/text_processors.rb +20 -0
- data/lib/ruport.rb +9 -12
- data/lib/ruport/config.rb +2 -2
- data/lib/ruport/data_set.rb +10 -7
- data/lib/ruport/format.rb +58 -36
- data/lib/ruport/format/engine.rb +1 -1
- data/lib/ruport/format/plugin.rb +4 -3
- data/lib/ruport/mailer.rb +10 -9
- data/lib/ruport/query.rb +7 -14
- data/lib/ruport/report.rb +233 -32
- data/test/tc_config.rb +2 -2
- data/test/tc_data_set.rb +0 -1
- data/test/tc_query.rb +5 -0
- data/test/tc_report.rb +36 -1
- data/test/tc_ruport.rb +1 -1
- data/test/unit.log +111 -2506
- metadata +17 -9
- data/examples/create.sql +0 -2
- data/examples/pdf.rb +0 -36
- data/examples/sql_query.rb +0 -19
- data/test/tc_reading.rb +0 -60
data/CHANGELOG
CHANGED
@@ -1,4 +1,32 @@
|
|
1
|
-
The current version of Ruby Reports is 0.4.
|
1
|
+
The current version of Ruby Reports is 0.4.13
|
2
|
+
|
3
|
+
changes since 0.4.11
|
4
|
+
|
5
|
+
- Added high level hook Report#text_processor which essentially works like
|
6
|
+
Format.register_filter
|
7
|
+
|
8
|
+
- Report can now run single or multiple reports and do things such as send them
|
9
|
+
via email or write/append them to files.
|
10
|
+
|
11
|
+
- Report has been overhauled to have a simple DSL
|
12
|
+
|
13
|
+
- Mailfactory is now a gem dependency. If you install via setup.rb or building
|
14
|
+
the gem yourself, you only need it if you plan to use Ruport::Mailer
|
15
|
+
|
16
|
+
- Report#query now has an 'as' method that allows direct translation of a query
|
17
|
+
to a particular format, e.g.
|
18
|
+
|
19
|
+
query "select * from ruport", :as => :pdf
|
20
|
+
|
21
|
+
- Lots of bug fixes and cleanup
|
22
|
+
|
23
|
+
- Ruport is now on Trac.
|
24
|
+
http://stonecode.svnrepository.com/ruport/
|
25
|
+
|
26
|
+
- I apparently never knew how to use inject.
|
27
|
+
Injects are all functional now.
|
28
|
+
|
29
|
+
- Fixed a bug in DataSet that made fields not get duped properly
|
2
30
|
|
3
31
|
changes since 0.4.9
|
4
32
|
|
data/README
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# ------------------------------------------------------------------------
|
2
2
|
# -------------------------------------------------------------------------
|
3
|
-
#
|
4
|
-
# WARNING: THIS DOCUMENT IS FREQUENTLY OUT OF DATE.
|
5
3
|
#
|
4
|
+
# NOTE:
|
5
|
+
#
|
6
6
|
# The most up to date information can be found at:
|
7
7
|
# http://reporting.stonecode.org
|
8
8
|
#
|
@@ -24,9 +24,9 @@
|
|
24
24
|
#
|
25
25
|
# - Installation
|
26
26
|
#
|
27
|
-
#
|
27
|
+
# Dependencies:
|
28
28
|
#
|
29
|
-
# Ruport has a number of dependencies
|
29
|
+
# Ruport has a number of dependencies.
|
30
30
|
#
|
31
31
|
# Ruby/DBI and appropriate dbds: Makes Query useable
|
32
32
|
# (must be installed manually)
|
@@ -37,9 +37,11 @@
|
|
37
37
|
# RedCloth: Enables textile/markdown filtering
|
38
38
|
# (available via rubygems)
|
39
39
|
#
|
40
|
-
# PDF::Writer: Enables printable documents
|
40
|
+
# PDF::Writer: Enables printable documents
|
41
41
|
# (available via rubygems)
|
42
42
|
#
|
43
|
+
# MailFactory: For email support.
|
44
|
+
#
|
43
45
|
# Note that by installing any of the dependencies, either via gems or manually,
|
44
46
|
# their functionality will automatically be enabled.
|
45
47
|
#
|
@@ -64,7 +66,7 @@
|
|
64
66
|
#
|
65
67
|
# - Caveats
|
66
68
|
#
|
67
|
-
# Ruport is
|
69
|
+
# Ruport is experimental software. It's not completely tested and the API is
|
68
70
|
# changing rapidly from version to version. Test suites are becoming
|
69
71
|
# increasingly robust, but have not identified all possible edge cases. If
|
70
72
|
# Ruport goes wild on you, it's because it hasn't been tamed yet.
|
@@ -105,9 +107,9 @@
|
|
105
107
|
# http://rubyforge.org/projects/ruport
|
106
108
|
#
|
107
109
|
# - The latest stable API documentation is available at:
|
108
|
-
# http://
|
110
|
+
# http://reporting.stonecode.org/docs
|
109
111
|
#
|
110
|
-
# There also will be some tutorials on
|
112
|
+
# There also will be some tutorials on ruport.infogami.com
|
111
113
|
#
|
112
114
|
# If you are interested in developing Ruport, please *never* study code in
|
113
115
|
# official releases. As this software is in it's early stages, it's essential
|
@@ -121,7 +123,7 @@
|
|
121
123
|
#
|
122
124
|
# Those who would like to become regular contributors will be given write
|
123
125
|
# access. Also, anyone interested in the internal design and project
|
124
|
-
# management aspects of Ruport can request to be added to our
|
126
|
+
# management aspects of Ruport can request to be added to our Trac
|
125
127
|
# account. This is primarily intended for people who are working on the
|
126
128
|
# project actively, though.
|
127
129
|
#
|
@@ -150,25 +152,13 @@
|
|
150
152
|
# Format provides support for building filters and specialized formatting
|
151
153
|
# systems.
|
152
154
|
#
|
153
|
-
# Query currently provides a high level interface to DBI.
|
154
|
-
#
|
155
|
-
#
|
156
|
-
#
|
157
|
-
# you DBI:Rows, depending on what you need.
|
158
|
-
#
|
159
|
-
# There is also a Config class which allows you to set things such as data
|
160
|
-
# sources, mailer information, and logging. Ruport#complain provides a robust
|
161
|
-
# way to handle error logging and warnings.
|
162
|
-
#
|
163
|
-
# Finally, Ruport provides a powerful yet oh-so-scary parsing tool. It is
|
164
|
-
# essentially James Edward Gray II's Parse::Input library within the Ruport
|
165
|
-
# library. (And will soon be integrated nicely too)
|
166
|
-
#
|
167
|
-
# Read the source of ruport/parser.rb if you have some gnarly data you need to
|
168
|
-
# munge.
|
155
|
+
# Query currently provides a high level interface to DBI.
|
156
|
+
# If you would like to query a database or load a sql dump,
|
157
|
+
# this class can help you do that. It can generate DataSets on the fly,
|
158
|
+
# or feed you DBI:Rows, depending on what you need.
|
169
159
|
#
|
170
160
|
# Finally, Please consult the API documentation and/or source code for more
|
171
|
-
# information. (http://
|
161
|
+
# information. (http://reporting.stonecode.org/docs). Not all classes have been
|
172
162
|
# documented but the ones that have may be easier to understand when their docs
|
173
163
|
# have been read. Also, feel free to contribute documentation.
|
174
164
|
#
|
data/Rakefile
CHANGED
@@ -21,7 +21,7 @@ end
|
|
21
21
|
|
22
22
|
spec = Gem::Specification.new do |spec|
|
23
23
|
spec.name = LEAN ? "lean-ruport" : "ruport"
|
24
|
-
spec.version = "0.4.
|
24
|
+
spec.version = "0.4.13"
|
25
25
|
spec.platform = Gem::Platform::RUBY
|
26
26
|
spec.summary = "A generalized Ruby report generation and templating engine."
|
27
27
|
spec.files = Dir.glob("{examples,lib,test}/**/**/*") +
|
@@ -39,6 +39,7 @@ spec = Gem::Specification.new do |spec|
|
|
39
39
|
spec.add_dependency('fastercsv', '>= 0.1.0')
|
40
40
|
spec.add_dependency('RedCloth', '>= 3.0.0')
|
41
41
|
spec.add_dependency('pdf-writer', '>= 1.1.3')
|
42
|
+
spec.add_dependency("mailfactory", ">= 1.2.2")
|
42
43
|
end
|
43
44
|
spec.author = "Gregory Brown"
|
44
45
|
spec.email = " gregory.t.brown@gmail.com"
|
data/TODO
CHANGED
@@ -4,6 +4,8 @@ Immediate Goals:
|
|
4
4
|
|
5
5
|
Features:
|
6
6
|
|
7
|
+
- PDF document support + example
|
8
|
+
|
7
9
|
- event system
|
8
10
|
|
9
11
|
- Composite key selection
|
@@ -14,24 +16,8 @@ Immediate Goals:
|
|
14
16
|
|
15
17
|
- Column based default values
|
16
18
|
|
17
|
-
- Integrate Ruport::Parser into Report#parse and Format#parser
|
18
|
-
|
19
19
|
- Document the inner classes of Format
|
20
20
|
|
21
21
|
- make the Fetchable module to abstract data acquisition
|
22
22
|
|
23
23
|
- Calculated fields
|
24
|
-
|
25
|
-
Other High Priority Goals:
|
26
|
-
|
27
|
-
- Offer charting support in Format
|
28
|
-
|
29
|
-
Community Requests:
|
30
|
-
|
31
|
-
- Integration with Rails (ActiveRecord)
|
32
|
-
|
33
|
-
Other (Unordered) Goals:
|
34
|
-
|
35
|
-
- Support KirbyBase
|
36
|
-
|
37
|
-
- More Secure passwords for SQL
|
data/examples/report.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require "ruport"
|
2
|
+
require "fileutils"
|
3
|
+
class MyReport < Ruport::Report
|
4
|
+
prepare do
|
5
|
+
log_file "f.log"
|
6
|
+
log "preparing report", :status => :info
|
7
|
+
source :default,
|
8
|
+
:dsn => "dbi:mysql:foo",
|
9
|
+
:user => "root"
|
10
|
+
mailer :default,
|
11
|
+
:host => "mail.adelphia.net",
|
12
|
+
:address => "gregory.t.brown@gmail.com"
|
13
|
+
end
|
14
|
+
|
15
|
+
generate do
|
16
|
+
log "generated csv from query", :status => :info
|
17
|
+
query "select * from bar", :as => :csv
|
18
|
+
end
|
19
|
+
|
20
|
+
cleanup do
|
21
|
+
log "removing foo.csv", :status => :info
|
22
|
+
FileUtils.rm("foo.csv")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
MyReport.run { |res|
|
27
|
+
res.write "foo.csv";
|
28
|
+
res.send_to("greg7224@gmail.com") do |mail|
|
29
|
+
mail.subject = "Sample report"
|
30
|
+
mail.attach "foo.csv"
|
31
|
+
mail.text = <<-EOS
|
32
|
+
this is a sample of sending an emailed report from within Ruport.
|
33
|
+
EOS
|
34
|
+
end
|
35
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "ruport"
|
2
|
+
|
3
|
+
TEMPLATE = <<-EOS
|
4
|
+
|
5
|
+
My HTML Table:
|
6
|
+
<%= query "select * from bar", :as => :html %>
|
7
|
+
|
8
|
+
EOS
|
9
|
+
|
10
|
+
class MyReport < Ruport::Report
|
11
|
+
prepare { source :default, :dsn => "dbi:mysql:foo", :user => "root" }
|
12
|
+
generate { eval_template TEMPLATE }
|
13
|
+
end
|
14
|
+
|
15
|
+
MyReport.run
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "ruport"
|
2
|
+
|
3
|
+
|
4
|
+
class MyReport < Ruport::Report
|
5
|
+
prepare {
|
6
|
+
self.results = "Foo Bar Baz"
|
7
|
+
text_processor(:replace_foo) { results.gsub!(/Foo/,"Ruport") }
|
8
|
+
text_processor(:replace_bar) { results.gsub!(/Bar/,"Is") }
|
9
|
+
text_processor(:replace_baz) { results.gsub!(/Baz/, "Cool!") }
|
10
|
+
}
|
11
|
+
generate {
|
12
|
+
process_text results, :filters => [:replace_foo,:replace_bar,:replace_baz]
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
MyReport.run { |e| puts e.results }
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
data/lib/ruport.rb
CHANGED
@@ -14,7 +14,7 @@ module Ruport
|
|
14
14
|
|
15
15
|
begin; require 'rubygems'; rescue LoadError; nil end
|
16
16
|
|
17
|
-
VERSION = "Ruby Reports Version 0.4.
|
17
|
+
VERSION = "Ruby Reports Version 0.4.13"
|
18
18
|
|
19
19
|
# Ruports logging and error interface.
|
20
20
|
# Can generate warnings or raise fatal errors
|
@@ -39,24 +39,21 @@ 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.
|
43
|
-
options
|
44
|
-
options[:output] ||= $stderr
|
42
|
+
def Ruport.log(message,options={})
|
43
|
+
options = {:status => :warn, :output => $stderr}.merge(options)
|
45
44
|
options[:output].puts "[!!] #{message}" unless
|
46
45
|
options[:level].eql?(:log_only) and not Ruport::Config.paranoid?
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
when :fatal
|
51
|
-
Ruport::Config::logger.fatal(message) if Ruport::Config.logger
|
52
|
-
raise options[:exception] || RuntimeError, message
|
46
|
+
Ruport::Config::logger.send(options[:status],message) if Config.logger
|
47
|
+
if options[:status].eql? :fatal
|
48
|
+
raise(options[:exception] || RuntimeError, message)
|
53
49
|
end
|
54
50
|
end
|
55
|
-
|
51
|
+
|
52
|
+
def Ruport.complain(*args); Ruport.log(*args) end
|
53
|
+
|
56
54
|
def Ruport.configure(&block)
|
57
55
|
block.call(Ruport::Config)
|
58
56
|
end
|
59
|
-
|
60
57
|
end
|
61
58
|
|
62
59
|
|
data/lib/ruport/config.rb
CHANGED
@@ -59,7 +59,7 @@ module Ruport
|
|
59
59
|
#
|
60
60
|
class Config
|
61
61
|
include Singleton
|
62
|
-
|
62
|
+
|
63
63
|
def Config.method_missing(method_id,*args)
|
64
64
|
case(method_id)
|
65
65
|
when :source
|
@@ -69,7 +69,7 @@ module Ruport
|
|
69
69
|
when :mailer
|
70
70
|
@@mailers[args.first] = OpenStruct.new(*args[1..-1])
|
71
71
|
check_mailer(@@mailers[args.first],args.first)
|
72
|
-
when :log_file
|
72
|
+
when :log_file,:log_file=
|
73
73
|
@@logger = Logger.new(args.first)
|
74
74
|
when :default_source
|
75
75
|
@@sources[:default]
|
data/lib/ruport/data_set.rb
CHANGED
@@ -39,7 +39,7 @@ module Ruport
|
|
39
39
|
# length, empty?, delete_at, first, last, pop
|
40
40
|
#
|
41
41
|
def initialize(fields=nil, options={})
|
42
|
-
@fields = fields
|
42
|
+
@fields = fields.dup if fields
|
43
43
|
@default = options[:default] || default
|
44
44
|
@data = []
|
45
45
|
options[:data].each { |r| self << r } if options[:data]
|
@@ -97,6 +97,8 @@ module Ruport
|
|
97
97
|
#
|
98
98
|
# data << [ 1, 2, 3 ]
|
99
99
|
# data << { :some_field_name => 3, :other => 2, :another => 1 }
|
100
|
+
#
|
101
|
+
# FIXME: Appending a datarow is wonky.
|
100
102
|
def << ( stuff, filler=@default )
|
101
103
|
if stuff.kind_of?(DataRow)
|
102
104
|
@data << stuff.clone
|
@@ -184,9 +186,10 @@ module Ruport
|
|
184
186
|
lambda { |r| loaded_data << r }
|
185
187
|
end
|
186
188
|
|
187
|
-
|
188
|
-
|
189
|
-
|
189
|
+
if options[:has_names]
|
190
|
+
loaded_data.fields = input[0] ; input = input[1..-1]
|
191
|
+
end
|
192
|
+
|
190
193
|
loaded_data.default = options[:default]
|
191
194
|
input.each { |row| action[row] }
|
192
195
|
return loaded_data
|
@@ -199,7 +202,7 @@ module Ruport
|
|
199
202
|
# Returns a new DataSet composed of the fields specified.
|
200
203
|
def select_columns(*fields)
|
201
204
|
fields = get_field_names(fields)
|
202
|
-
rows = fields.inject([]) { |s,e| s
|
205
|
+
rows = fields.inject([]) { |s,e| s + [map { |row| row[e] }] }.transpose
|
203
206
|
my_data = DataSet.new(fields, :data => rows)
|
204
207
|
end
|
205
208
|
|
@@ -257,7 +260,7 @@ module Ruport
|
|
257
260
|
# Only works with blocks resulting in numeric values.
|
258
261
|
def sigma
|
259
262
|
inject(0) do |s,r|
|
260
|
-
s
|
263
|
+
s + (yield(r) || 0)
|
261
264
|
end
|
262
265
|
end
|
263
266
|
|
@@ -281,7 +284,7 @@ module Ruport
|
|
281
284
|
|
282
285
|
def get_field_names(f)
|
283
286
|
f.all? { |e| e.kind_of? Integer } &&
|
284
|
-
f.inject([]) { |s,e| s
|
287
|
+
f.inject([]) { |s,e| s + [@fields[e]] } || f
|
285
288
|
end
|
286
289
|
|
287
290
|
end
|
data/lib/ruport/format.rb
CHANGED
@@ -59,41 +59,40 @@ module Ruport
|
|
59
59
|
# This part of Ruport is under active development. Please do feel free to
|
60
60
|
# submit feature requests or suggestions.
|
61
61
|
class Format
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
62
|
+
|
63
|
+
# Builds a simple interface to a formatting engine.
|
64
|
+
# Two of these interfaces are built into Ruport:
|
65
|
+
# Format.document and Format.table
|
66
|
+
#
|
67
|
+
# These interfaces pass a hash of keywords to the associative engine.
|
68
|
+
# Here is a simple example:
|
69
|
+
#
|
70
|
+
# Format.build_interface_for Format::Engine::Yable, "table"
|
71
|
+
#
|
72
|
+
# This will allow the following code to work:
|
73
|
+
#
|
74
|
+
# Format.table :data => [[1,2],[3,4]], :plugin => :csv
|
75
|
+
#
|
76
|
+
# So, if you want to create a standard interface to a
|
77
|
+
# custom built engine, you could simply do something like:
|
78
|
+
#
|
79
|
+
# Format.build_interface_for MyCustomEngine, "my_name"
|
80
|
+
#
|
81
|
+
# which would be accessible via
|
82
|
+
#
|
83
|
+
# Format.my_name ...
|
84
|
+
def Format.build_interface_for(engine,name)
|
85
|
+
(class << self; self; end).send(:define_method, name,
|
86
|
+
lambda { |options| simple_interface(engine, options) })
|
87
|
+
(class << self; self; end).send(:define_method, "#{name}_object",
|
88
|
+
lambda { |options|
|
89
|
+
options[:auto_render] = false; simple_interface(engine,options) })
|
90
|
+
end
|
85
91
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
my_engine.send("#{k}=",v) if my_engine.respond_to? k
|
90
|
-
end
|
91
|
-
|
92
|
-
options[:auto_render] ? my_engine.render : my_engine.dup
|
93
|
-
end
|
92
|
+
%w[open_node document engine plugin].each { |lib|
|
93
|
+
require "ruport/format/#{lib}"
|
94
|
+
}
|
94
95
|
|
95
|
-
end
|
96
|
-
|
97
96
|
@@filters = Hash.new
|
98
97
|
|
99
98
|
# To hook up a Format object to your current class, you need to pass it a
|
@@ -101,7 +100,7 @@ module Ruport
|
|
101
100
|
# evaluated in the context of the object they are being called from, rather
|
102
101
|
# than within an instance of Format.
|
103
102
|
#
|
104
|
-
def initialize(klass_binding)
|
103
|
+
def initialize(klass_binding=binding)
|
105
104
|
@binding = klass_binding
|
106
105
|
end
|
107
106
|
|
@@ -143,9 +142,32 @@ module Ruport
|
|
143
142
|
end
|
144
143
|
|
145
144
|
def method_missing(m)
|
146
|
-
@@filters[m] ? @@filters[m]
|
145
|
+
@@filters[m] ? @@filters[m][@content] : super
|
147
146
|
end
|
148
|
-
|
147
|
+
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def Format.simple_interface(engine, options={})
|
152
|
+
my_engine = engine.dup
|
153
|
+
|
154
|
+
my_engine.send(:plugin=,options[:plugin])
|
155
|
+
options = my_engine.active_plugin.rendering_options.merge(options)
|
156
|
+
|
157
|
+
options[:auto_render] = true unless options.has_key? :auto_render
|
158
|
+
|
159
|
+
|
160
|
+
options[:data] = options[:data].dup
|
161
|
+
|
162
|
+
options.each do |k,v|
|
163
|
+
my_engine.send("#{k}=",v) if my_engine.respond_to? k
|
164
|
+
end
|
165
|
+
|
166
|
+
options[:auto_render] ? my_engine.render : my_engine.dup
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
|
149
171
|
end
|
150
172
|
end
|
151
173
|
|