ruport 0.4.11 → 0.4.13
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 +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
|
|