ruport 0.2.0 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,29 @@
1
+ The current version of Ruby Reports is 0.2.2
2
+
3
+ changes since Ruport 0.2.0:
4
+
5
+ - Report becomes Engine
6
+ - MockDB becomes FakeDB
7
+ - MockReport becomes MockEngine
8
+
9
+ - Modules are added so now,
10
+
11
+ Ruport::Report::Engine,
12
+ Ruport::Report::DataSet is the full name of these classes.
13
+
14
+ - 'require ruportlib' runs include Ruport,
15
+ so you can type Report::Engine.new instead of Ruport::Report::Engine.new
16
+
17
+ - The assert that was supposed to be an assert_equal that
18
+ David Black caught was fixed.
19
+
20
+ - Examples are updated and now on the RubyForge FRS in many formats.
21
+ (With .DS_Store and other garbage files removed! ;) )
22
+
23
+ - Formatting of code was cleaned up and improved.
24
+
25
+ - Generates a test.rb that does not use deprecated functions now.
26
+
1
27
  changes since Ruport 0.1.0:
2
28
 
3
29
  CLEANUP / ORGANIZATION:
data/README CHANGED
@@ -7,7 +7,7 @@ of databases via DBI. It also provides helper methods and utilities to generate
7
7
  It happens to be alpha software, so if it offends your mother, burns out the innards of your computer, or threatens you in any other way, you may want to wait until further releases to play around with it.
8
8
 
9
9
  There is a set of examples and a brief walkthrough for this application
10
- available at http://ruport.rubyforge.org/examples.zip which I strongly
10
+ available at http://ruport.rubyforge.org/example.zip which I strongly
11
11
  recommend going through
12
12
 
13
13
  Otherwise, here is a brief set of installation and setup instructions which will
data/Rakefile CHANGED
@@ -14,7 +14,7 @@ end
14
14
 
15
15
  spec = Gem::Specification.new do |spec|
16
16
  spec.name = "ruport"
17
- spec.version = "0.2.0"
17
+ spec.version = "0.2.2"
18
18
  spec.platform = Gem::Platform::RUBY
19
19
  spec.summary = "A generalized Ruby report generation and templating engine."
20
20
 
data/TODO CHANGED
@@ -1,5 +1,11 @@
1
1
  TODO:
2
2
 
3
+ Bugfixes:
4
+ - Fix potential failure in Ruport::Report::DataSet#eql?
5
+ - Is there an OS X packaging problem with the gems?
6
+ - Fix the manual that is in the example package so it it reads FakeDB instead
7
+ of MockDB
8
+
3
9
  Improvement:
4
10
 
5
11
  - Mail system
@@ -104,9 +110,8 @@ Other:
104
110
 
105
111
  Ruport needs to be modified to support as many databases as possibly
106
112
  internally. The system was written using MySQL on Gentoo Linux
107
- and Mac OS X.3 / OS X.4, Windows 2000 / XP
108
-
109
- and Microsoft SQL Server on Windows 2000 (via ODBC),
113
+ and Mac OS X.3 / OS X.4, Windows 2000 / XP and Microsoft SQL Server
114
+ on Windows 2000 (via ODBC),
110
115
 
111
116
  so the support for these platforms are best. An effort will be given to
112
117
  make Ruport less hostile in other environments.
data/bin/ruport CHANGED
@@ -61,11 +61,11 @@ if ARGV[0].eql?("generate")
61
61
 
62
62
  File.open("#{ARGV[1]}/config/ruport.yaml", "w") { |f| f.puts(conf) }
63
63
  File.open("#{ARGV[1]}/templates/test.rb", "w") { |f|
64
- f.puts 'select "database()" do |r| puts r end'
64
+ f.puts('query ("show tables;") do |data| @report << data.to_csv end ')
65
65
  }
66
66
  exit
67
67
  elsif ARGV[0].eql?("-v")
68
- puts "Ruport Version 0.2.0 \nA ruby report generation system by Gregory " +
68
+ puts "Ruport Version 0.2.2 \nA ruby report generation system by Gregory " +
69
69
  "Brown.\nThis application is Free Software under the GPL/Ruby License. " +
70
70
  "\nAll praise and/or criticism can be directed to "+
71
71
  "gregory.t.brown@gmail.com"
@@ -79,17 +79,17 @@ config = YAML.load(File.open("config/ruport.yaml"))
79
79
  mailer = nil
80
80
 
81
81
  if config.has_key?(:mail_host)
82
- mailer = Mailer.new( config[:mail_host], config[:mail_address],
82
+ mailer = Report::Mailer.new( config[:mail_host], config[:mail_address],
83
83
  config[:mail_account], config[:mail_password],
84
84
  25, config[:mail_authentication] )
85
85
  end
86
86
 
87
- report = Report.new(
87
+ report = Report::Engine.new(
88
88
  "#{config[:driver]}:#{config[:database]}",
89
89
  config[:db_user], config[:db_password], mailer )
90
90
 
91
91
  if config[:driver].eql?("ruport")
92
- report = MockReport.new( "#{config[:driver]}:#{config[:database]}",
92
+ report = Report::FakeEngine.new( "#{config[:driver]}:#{config[:database]}",
93
93
  config[:db_user], config[:db_password], mailer )
94
94
  end
95
95
 
@@ -0,0 +1,67 @@
1
+ #!/usr/local/bin/ruby
2
+ # data_row.rb
3
+ # Created by Gregory Thomas Brown on 2005-08-02
4
+ # Copyright 2005 (Gregory Brown) All rights reserved.
5
+ #
6
+ # This product is free software, you may distribute it as such
7
+ # under your choice of the Ruby license or the GNU GPL
8
+ # See LICENSE for details
9
+
10
+ module Ruport
11
+ module Report
12
+ class DataRow
13
+
14
+ attr_accessor :fields
15
+
16
+ # DataRows are essentially arrays with named ordinal fields and
17
+ # awareness of their oddness and position.
18
+ def initialize( data, fields, oddness=nil, position=nil )
19
+ @data = data
20
+ @fields = fields
21
+ @oddness = oddness
22
+ @position = position
23
+ end
24
+
25
+
26
+ # Lets you access individual fields
27
+ #
28
+ # i.e. row["phone"]
29
+ def [](key)
30
+ key.kind_of?(String) ? @data[@fields.index(key)] : @data[key]
31
+ end
32
+
33
+ # Lets you set field values
34
+ #
35
+ # i.e. row["phone"] = '2038291203'
36
+ def []=(key,value)
37
+ key.kind_of?(String) ? @data[@fields.index(key)] = value :
38
+ @data[key] = value
39
+ end
40
+
41
+ # Converts the DataRow to a plain old Array
42
+ def to_a
43
+ @data
44
+ end
45
+
46
+ # Converts the DataRow to a string representation
47
+ # for outputting to screen.
48
+ def to_s
49
+ "[" + @data.join(",") + "]"
50
+ end
51
+
52
+ # implements
53
+ # DataRow#first? DataRow#last? DataRow#middle?
54
+ # DataRow#odd? DataRow#even? which are all conditional methods.
55
+ def method_missing(method)
56
+ if %[last? first? middle?].include? method.to_s
57
+ return @position.eql?(method.to_s[0..-2].to_sym)
58
+ elsif %[odd? even?].include? method.to_s
59
+ return @oddness.eql?(method.to_s[0..-2].to_sym)
60
+ end
61
+ super
62
+ end
63
+
64
+ attr_accessor :position
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,104 @@
1
+ #!/usr/local/bin/ruby
2
+ # data_set.rb
3
+ # Created by Gregory Thomas Brown on 2005-08-02
4
+ # Copyright 2005 (Gregory Brown) All rights reserved.
5
+ #
6
+ # This product is free software, you may distribute it as such
7
+ # under your choice of the Ruby license or the GNU GPL
8
+ # See LICENSE for details
9
+ module Ruport
10
+ module Report
11
+ class DataSet
12
+ def initialize
13
+ @data = []
14
+ end
15
+
16
+ #fields are the column names, default is the default cell value
17
+ attr_accessor :fields, :default
18
+
19
+ #data is the core Array that holds all the rows
20
+ attr_reader :data
21
+
22
+
23
+ #Allows ordinal access to rows
24
+ def [](index)
25
+ @data[index]
26
+ end
27
+
28
+ #allows setting of rows (providing a DataRow is passed in)
29
+ def []=(index,value)
30
+ throw "Invalid object type" unless value.kind_of?(DataRow)
31
+ @data[index] = value
32
+ end
33
+
34
+ # appends a row to the DataSet
35
+ # can be added as an array or a keyed hash.
36
+ # i.e if our DataSet have @fields = [:a, :b, :c]
37
+ # data << [ 1, 2, 3] and data << { :a => 1, :b => 2, :c => 3 }
38
+ # are equivalent.
39
+ def << ( stuff )
40
+ new_row = Array.new
41
+ fields.each_with_index do |key, index|
42
+ if stuff.kind_of?(Array)
43
+ new_row[index] = stuff.shift || @default
44
+ else
45
+ new_row[index] = stuff[key] || @default
46
+ end
47
+ end
48
+
49
+ oddness = (@data.length % 2 == 0 ? :even : :odd)
50
+ position = (@data.length == 0 ? :first : :last)
51
+ @data[@data.length - 1].position = :middle if @data.length > 1
52
+ @data << DataRow.new(new_row,@fields,oddness,position)
53
+ end
54
+
55
+ # This works in best case scenario. It should return true if
56
+ # both DataSets have the same values. Still working out the kinks here.
57
+ def eql?(data2)
58
+ return false unless (@data.length == data2.data.length)
59
+ 0.upto(@data.length - 1) do |index|
60
+ (@fields + data2.fields).uniq.each do |key|
61
+ return false unless @data[index][key] == data2[index][key]
62
+ end
63
+ end
64
+ return true
65
+ end
66
+
67
+
68
+ # Allows loading of CSV files or YAML dumps. Returns a DataSet
69
+ def self.load ( source, default="")
70
+
71
+ return YAML.load(File.open(source)) if source =~ /\.(yaml|yml)/
72
+
73
+ input = CSV.read(source) if source =~ /\.csv/
74
+ loaded_data = self.new
75
+ loaded_data.fields = input[0]
76
+ loaded_data.default = default
77
+ input[1..-1].each do |row|
78
+ loaded_data << row
79
+ end
80
+ return loaded_data
81
+ end
82
+
83
+ # Converts a DataSet to CSV
84
+ def to_csv
85
+ output = CSV.generate_line(@fields) + "\n"
86
+ @data.each do |row|
87
+ output << CSV.generate_line(@fields.map { |f| row[f] }) + "\n"
88
+ end
89
+ return output
90
+ end
91
+
92
+ # Works like a standard each iterator
93
+ def each(&action)
94
+ @data[0..-1].each(&action)
95
+ end
96
+
97
+ # Works like a standard each_with_index iterator
98
+ def each_with_index(&action)
99
+ @data[0..-1].each_with_index(&action)
100
+ end
101
+
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,214 @@
1
+ #!/usr/local/bin/ruby
2
+ # engine.rb
3
+ # Created by Gregory Thomas Brown on 2005-08-02
4
+ # Copyright 2005 (Gregory Brown) All rights reserved.
5
+ #
6
+ # This product is free software, you may distribute it as such
7
+ # under your choice of the Ruby license or the GNU GPL
8
+ # See LICENSE for details
9
+ #
10
+ # Special thanks and acknowledgement go to James Edward Gray II
11
+ # for providing the original source code for this application
12
+ require "dbi"
13
+ require "rubygems"
14
+ require "erb"
15
+ require "csv"
16
+ require "yaml"
17
+ require "date"
18
+ require "logger"
19
+ require "fileutils"
20
+ module Ruport
21
+ module Report
22
+ class Engine
23
+ def initialize( dsn, user, password, mailer=nil )
24
+ @dsn = dsn
25
+ @user = user
26
+ @password = password
27
+ @report_name = ""
28
+ @mailer = mailer
29
+ File.exists?("log") or FileUtils.mkdir("log")
30
+ @logger = Logger.new("log/ruport.log")
31
+ @generate = true
32
+ @report = ""
33
+ end
34
+
35
+ attr_accessor :query_table, :file
36
+ attr_reader :mailer, :generate, :config, :logger
37
+
38
+ # * DEPRECATED: Use query() *
39
+ #
40
+ # Takes a query, an optional sourcetype, and a block which is passed the
41
+ # results row by row. When passed a query in string form, it adds the
42
+ # SELECT clause to the string and executes the query. When passed a
43
+ # filename and a source :file, it looks in queries/ for the file specified.
44
+ # When given a database query label, it looks in config[query_table] for a
45
+ # query with the label specified. If no source is specified, it uses
46
+ # string by default for the source.
47
+ #
48
+ # Example:
49
+ #
50
+ # select ( "* FROM test" )
51
+ # Passes "SELECT * FROM test" to the database
52
+ #
53
+ # select ( "test.sql", :file )
54
+ # Passes the contents of queries/test.sql to the database
55
+ #
56
+ # select ( "TEST", :db )
57
+ # Calls the query TEST stored in the database and query_table specified in
58
+ # config/ruport.yaml
59
+
60
+ def select( query, source = :string, &action )
61
+ source != :string || query = "SELECT " + query
62
+ execute( query, source ) do |sth|
63
+ @column_names = sth.column_names
64
+ @first_row = true
65
+ sth.fetch do |row|
66
+ action.call(row) if block_given?
67
+ @first_row = false
68
+ end
69
+ end
70
+ end
71
+
72
+ # * DEPRECATED, Use query() *
73
+ #
74
+ # Takes a query and an optional sourcetype and then runs the query
75
+ # against the database. The output is not returned. This is useful for
76
+ # doing construction and destruction actions.
77
+ def execute( query, source = :string )
78
+ query = get_query(source, query)
79
+ DBI.connect(@dsn, @user, @password) do |dbh|
80
+ dbh.prepare(query) do |sth|
81
+ sth.execute()
82
+ yield(sth)
83
+ end
84
+ end
85
+ end
86
+
87
+ # Takes a query and an optional sourcetype and then runs the query
88
+ # against the database. The return from the query is then converted
89
+ # into a DataSet which can then be manipulated.
90
+ #
91
+ # Example:
92
+ # query("SELECT * FROM address_book") do |data|
93
+ # @report << data.to_csv
94
+ # end
95
+ #
96
+ # The above would select all from the _address_book_ table
97
+ # and then convert the DataSet to a csv and then add it to the report
98
+
99
+ def query ( statement, source = :string, &action )
100
+ query_text = get_query(source,statement)
101
+ DBI.connect(@dsn, @user, @password) do |dbh|
102
+ data = DataSet.new
103
+ sth = dbh.execute(query_text)
104
+ if block_given?
105
+ data.fields = sth.column_names
106
+ sth.each { |row| data << row }
107
+ action.call(data)
108
+ end
109
+ end
110
+ end
111
+
112
+ # Evaluates _code_ from _filename_ as pure ruby code for files ending in
113
+ # .rb, and as ERb templates for anything else.
114
+ def eval_report( filename, code )
115
+ if filename =~ /\.rb/
116
+ eval(code)
117
+ else
118
+ ERB.new(code, 0, "%").run(binding)
119
+ end
120
+ end
121
+
122
+ # Loads a yaml file into the @config variable which is accessible in
123
+ # templates. If _interactive_ is set to true, the user will
124
+ # be prompted to enter information for each key.
125
+ def load_yaml(file, interactive=false)
126
+ file = "config/#{file}" unless File.exists? file
127
+ @config = YAML.load(File.open(file))
128
+ if (interactive)
129
+ @config.keys.map { |c| c.to_s }.sort.each do |property|
130
+ $stderr.print "#{property} (#{@config[property.to_sym]}): "
131
+ entered = $stdin.gets.strip
132
+ @config[property] = entered unless entered.eql?("")
133
+ end
134
+ end
135
+ end
136
+
137
+ # Creates an SQL Object. The block is evaluated within the SQL
138
+ # instance so you may use any methods available to the SQL class.
139
+ # The generated query is returned.
140
+ #
141
+ # Example
142
+ # fetch { from :address_book order :name }
143
+ # => "SELECT * FROM address_book ORDER BY name
144
+
145
+ def fetch( &dsl )
146
+ begin
147
+ SQL.new(:SELECT, &dsl).to_s
148
+ rescue Exception
149
+ @logger.fatal "Error generating SQL."
150
+ raise "Error generating SQL."
151
+ end
152
+ end
153
+
154
+ # Returns the query string for queries of type string, file, or db
155
+ def get_query(type,query)
156
+ case (type)
157
+ when :string
158
+ query
159
+ when :file
160
+ load_file( query )
161
+ when :db
162
+ select ( "query FROM #{@query_table} WHERE " +
163
+ "label LIKE '#{query}';" ) do |row| return row["query"] end
164
+ end
165
+ end
166
+
167
+ # Loads a query from file. Accepts absolute paths, relative paths from
168
+ # the toplevel directory, and relative paths from _queries/_.
169
+ # If you've been putting all your database queries in _queries_, you can
170
+ # just call them by name.
171
+ def load_file( query_file )
172
+ begin
173
+ File.read( query_file ).strip
174
+ rescue
175
+ @logger.fatal "Could not open #{query_file}"
176
+ raise "Could not open #{query_file}"; exit
177
+ end
178
+ end
179
+
180
+ # Generates the report. If @pre or @post are defined with lambdas,
181
+ # they will be called before and after the main code. generate_report
182
+ # will automatically be run at the end of the execution
183
+ # of a ruport template unless @generate is set to false.
184
+ #
185
+ # If @file != nil, ruport will print to the
186
+ # file with the specified name. Otherwise, it will print to STDOUT by
187
+ # default. @file can be set implictly by a file argument, i.e.
188
+ # ruport templates/foo.rb reports/foo_out.html => @file =
189
+ # 'reports/foo_out.html' It can also be set explicitly within a template.
190
+ # A good way to define a default value is @file ||= "reports/somefile"
191
+ #
192
+ # Lastly, if you have your mailer configuration set up and
193
+ # you've specified recipients, the contents of @mailer.body will
194
+ # be automatically emailed by this fuction.
195
+ #
196
+ # The source for this function is probably easier to read than this
197
+ # explanation, so you may want to start there.
198
+ def generate_report
199
+ @pre.call if @pre
200
+ if (@file.nil?)
201
+ puts(@report)
202
+ else
203
+ File.open(@file,"w") { |f| f.puts @report }
204
+ end
205
+ unless @mailer.nil? || @mailer.recipients.empty?
206
+ @mailer.body = @report unless not @mailer.body.empty?
207
+ @mailer.send_report(@report_name)
208
+ end
209
+ @post.call if @post
210
+ end
211
+ end
212
+ end
213
+ end
214
+