ruport 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,64 @@
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
+
11
+ class DataRow
12
+
13
+ attr_accessor :fields
14
+
15
+
16
+ # DataRows are essentially arrays with named ordinal fields and awareness of
17
+ # 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 for outputting to screen.
47
+ def to_s
48
+ "[" + @data.join(",") + "]"
49
+ end
50
+
51
+ # implements
52
+ # DataRow#first? DataRow#last? DataRow#middle? DataRow#odd? DataRow#even?
53
+ # which are all conditional methods.
54
+ def method_missing(method)
55
+ if %[last? first? middle?].include? method.to_s
56
+ return @position.eql?(method.to_s[0..-2].to_sym)
57
+ elsif %[odd? even?].include? method.to_s
58
+ return @oddness.eql?(method.to_s[0..-2].to_sym)
59
+ end
60
+ super
61
+ end
62
+
63
+ attr_accessor :position
64
+ end
@@ -0,0 +1,101 @@
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
+
10
+ class DataSet
11
+ def initialize
12
+ @data = []
13
+ end
14
+
15
+ #fields are the column names, default is the default cell value
16
+ attr_accessor :fields, :default
17
+
18
+ #data is the core Array that holds all the rows
19
+ attr_reader :data
20
+
21
+
22
+ #Allows ordinal access to rows
23
+ def [](index)
24
+ @data[index]
25
+ end
26
+
27
+ #allows setting of rows (providing a DataRow is passed in)
28
+ def []=(index,value)
29
+ throw "Invalid object type" unless value.kind_of?(DataRow)
30
+ @data[index] = value
31
+ end
32
+
33
+ # appends a row to the DataSet
34
+ # can be added as an array or a keyed hash.
35
+ # i.e if our DataSet have @fields = [:a, :b, :c]
36
+ # data << [ 1, 2, 3] and data << { :a => 1, :b => 2, :c => 3 }
37
+ # are equivalent.
38
+ def << ( stuff )
39
+ new_row = Array.new
40
+ fields.each_with_index do |key, index|
41
+ if stuff.kind_of?(Array)
42
+ new_row[index] = stuff.shift || @default
43
+ else
44
+ new_row[index] = stuff[key] || @default
45
+ end
46
+ end
47
+
48
+ oddness = (@data.length % 2 == 0 ? :even : :odd)
49
+ position = (@data.length == 0 ? :first : :last)
50
+ @data[@data.length - 1].position = :middle if @data.length > 1
51
+ @data << DataRow.new(new_row,@fields,oddness,position)
52
+ end
53
+
54
+ # This works in best case scenario. It should return true if both DataSets
55
+ # have the same values. Still working out the kinks here.
56
+ def eql?(data2)
57
+ return false unless (@data.length == data2.data.length)
58
+ 0.upto(@data.length - 1) do |index|
59
+ (@fields + data2.fields).uniq.each do |key|
60
+ return false unless @data[index][key] == data2[index][key]
61
+ end
62
+ end
63
+ return true
64
+ end
65
+
66
+
67
+ # Allows loading of CSV files or YAML dumps. Returns a DataSet
68
+ def self.load ( source, default="")
69
+
70
+ return YAML.load(File.open(source)) if source =~ /\.(yaml|yml)/
71
+
72
+ input = CSV.read(source) if source =~ /\.csv/
73
+ loaded_data = self.new
74
+ loaded_data.fields = input[0]
75
+ loaded_data.default = default
76
+ input[1..-1].each do |row|
77
+ loaded_data << row
78
+ end
79
+ return loaded_data
80
+ end
81
+
82
+ # Converts a DataSet to CSV
83
+ def to_csv
84
+ output = CSV.generate_line(@fields) + "\n"
85
+ @data.each do |row|
86
+ output << CSV.generate_line(@fields.map { |f| row[f] }) + "\n"
87
+ end
88
+ return output
89
+ end
90
+
91
+ # Works like a standard each iterator
92
+ def each(&action)
93
+ @data[0..-1].each(&action)
94
+ end
95
+
96
+ # Works like a standard each_with_index iterator
97
+ def each_with_index(&action)
98
+ @data[0..-1].each_with_index(&action)
99
+ end
100
+
101
+ end
@@ -0,0 +1,42 @@
1
+ # mailer.rb
2
+ # Created by Gregory Brown on 2005-08-16
3
+ # Copyright 2005 (Gregory Brown) All Rights Reserved.
4
+ # This product is free software, you may distribute it as such
5
+ # under your choice of the Ruby license or the GNU GPL
6
+ # See LICENSE for details
7
+
8
+ require "net/smtp"
9
+ class Mailer
10
+
11
+ # Creates a new Mailer object. User must specify their mail host, email
12
+ # address, and account. Password, port, and authentication method are all
13
+ # optional.
14
+ def initialize(host, address, account, password=nil, port=25, authentication=nil)
15
+ @host = host
16
+ @account = account
17
+ @password = password
18
+ @address = address
19
+ @port = port
20
+ @auth = authentication
21
+ @recipients = []
22
+ @body = ""
23
+ end
24
+
25
+ #A list of email addresses to send the message to.
26
+ attr_accessor :recipients
27
+
28
+ #The body of the message to be sent
29
+ attr_accessor :body
30
+
31
+ # This takes _report_name_ as argument and sends the contents of @body to
32
+ # @recipients
33
+ def send_report(report_name="No Subject")
34
+ return if @body.empty?
35
+ Net::SMTP.start(@host,@port,@host,@account,@password,@auth) do |smtp|
36
+ smtp.send_message( "Subject: #{report_name}\n\n#{@body}",
37
+ @address,
38
+ @recipients )
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,49 @@
1
+ #!/usr/local/bin/ruby
2
+ # mock_db.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
+ class MockDB
11
+ # Creates a new MockDB e.g.
12
+ #
13
+ # MockDB.new([ "ruport", "ruport_example",
14
+ # "localhost", "test", "123"] )
15
+ #
16
+ # Which would create a fake database ruport_example at localhost with the
17
+ # username test and the password 123 using the driver 'ruport'
18
+
19
+ def initialize(args)
20
+ @db_driver, @db_name, @db_host, @username, @password = *args
21
+ @data = Hash.new
22
+ end
23
+
24
+
25
+ # Verifies account info and retrieves information
26
+ def process( dsn, user, password, query )
27
+ if ( dsn.eql?("#{@db_driver}:#{@db_name}:#{@db_host}") and
28
+ user.eql?(@username) and password.eql?(@password) )
29
+ @data[query]
30
+ else
31
+ throw "Processing Error"
32
+ end
33
+ end
34
+
35
+
36
+ # Allows query to be looked up in the data
37
+ def [](query)
38
+ @data[query]
39
+ end
40
+
41
+ # Used to setup fake queries E.g:
42
+ #
43
+ # self.fake_db["SELECT * FROM address_book"] =
44
+ # DataSet.load("data/addressbook.csv")
45
+ def []=(query, contents)
46
+ @data[query] = contents
47
+ end
48
+
49
+ end
@@ -0,0 +1,44 @@
1
+ #!/usr/local/bin/ruby
2
+ # mock_report.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
+ class MockReport < Report
11
+
12
+ attr_accessor :fake_db
13
+
14
+ def execute (query, source = :string)
15
+ query = get_query(source, query)
16
+ yield(@fake_db.process(@dsn,@user,@password, query))
17
+ end
18
+
19
+ def select( query, source = :string, &action )
20
+ source != :string || query = "SELECT " + query
21
+ execute( query, source ) do |table|
22
+ if table.kind_of?(DataSet)
23
+ @column_names = table.fields
24
+ else
25
+ @column_names = table[0].keys
26
+ end
27
+ @first_row = true
28
+ table.each do |row|
29
+ row = row.to_hash if table.kind_of?(DataSet)
30
+ action.call(row) if block_given?
31
+ @first_row = false
32
+ end
33
+ end
34
+ end
35
+
36
+ def query ( query, source = :string, &action)
37
+ execute( query, source ) do |table|
38
+ action.call(table)
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+
@@ -0,0 +1,212 @@
1
+ #!/usr/local/bin/ruby
2
+ # report.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
+ class Report
21
+ def initialize( dsn, user, password, mailer=nil )
22
+ @dsn = dsn
23
+ @user = user
24
+ @password = password
25
+ @report_name = ""
26
+ @mailer = mailer
27
+ File.exists?("log") or FileUtils.mkdir("log")
28
+ @logger = Logger.new("log/ruport.log")
29
+ @generate = true
30
+ @report = ""
31
+ end
32
+
33
+ attr_accessor :query_table, :file
34
+ attr_reader :mailer, :generate, :config, :logger
35
+
36
+ # * DEPRECATED: Use query() *
37
+ #
38
+ # Takes a query, an optional sourcetype, and a block which is passed the
39
+ # results row by row. When passed a query in string form, it adds the
40
+ # SELECT clause to the string and executes the query. When passed a
41
+ # filename and a source :file, it looks in queries/ for the file specified.
42
+ # When given a database query label, it looks in config[query_table] for a
43
+ # query with the label specified. If no source is specified, it uses
44
+ # string by default for the source.
45
+ #
46
+ # Example:
47
+ #
48
+ # select ( "* FROM test" )
49
+ # Passes "SELECT * FROM test" to the database
50
+ #
51
+ # select ( "test.sql", :file )
52
+ # Passes the contents of queries/test.sql to the database
53
+ #
54
+ # select ( "TEST", :db )
55
+ # Calls the query TEST stored in the database and query_table specified in
56
+ # config/ruport.yaml
57
+
58
+ def select( query, source = :string, &action )
59
+ source != :string || query = "SELECT " + query
60
+ execute( query, source ) do |sth|
61
+ @column_names = sth.column_names
62
+ @first_row = true
63
+ sth.fetch do |row|
64
+ action.call(row) if block_given?
65
+ @first_row = false
66
+ end
67
+ end
68
+ end
69
+
70
+ # * DEPRECATED, Use query() *
71
+ #
72
+ # Takes a query and an optional sourcetype and then runs the query
73
+ # against the database. The output is not returned. This is useful for
74
+ # doing construction and destruction actions.
75
+ def execute( query, source = :string )
76
+ query = get_query(source, query)
77
+ DBI.connect(@dsn, @user, @password) do |dbh|
78
+ dbh.prepare(query) do |sth|
79
+ sth.execute()
80
+ yield(sth)
81
+ end
82
+ end
83
+ end
84
+
85
+ # Takes a query and an optional sourcetype and then runs the query against the
86
+ # database. The return from the query is then converted into a DataSet which
87
+ # can then be manipulated.
88
+ #
89
+ # Example:
90
+ # query("SELECT * FROM address_book") do |data|
91
+ # @report << data.to_csv
92
+ # end
93
+ #
94
+ # The above would select all from the _address_book_ table and then convert
95
+ # the DataSet to a csv and then add it to the report
96
+
97
+ def query ( statement, source = :string, &action )
98
+ query_text = get_query(source,statement)
99
+ DBI.connect(@dsn, @user, @password) do |dbh|
100
+ data = DataSet.new
101
+ sth = dbh.execute(query_text)
102
+ if block_given?
103
+ data.fields = sth.column_names
104
+ sth.each { |row| data << row }
105
+ action.call(data)
106
+ end
107
+ end
108
+ end
109
+
110
+ # Evaluates _code_ from _filename_ as pure ruby code for files ending in
111
+ # .rb, and as ERb templates for anything else.
112
+ def eval_report( filename, code )
113
+ if filename =~ /\.rb/
114
+ eval(code)
115
+ else
116
+ ERB.new(code, 0, "%").run(binding)
117
+ end
118
+ end
119
+
120
+ # Loads a yaml file into the @config variable which is accessible in
121
+ # templates. If _interactive_ is set to true, the user will be prompted to
122
+ # enter information for each key.
123
+ def load_yaml(file, interactive=false)
124
+ file = "config/#{file}" unless File.exists? file
125
+ @config = YAML.load(File.open(file))
126
+ if (interactive)
127
+ @config.keys.map { |c| c.to_s }.sort.each do |property|
128
+ $stderr.print "#{property} (#{@config[property.to_sym]}): "
129
+ entered = $stdin.gets.strip
130
+ @config[property] = entered unless entered.eql?("")
131
+ end
132
+ end
133
+ end
134
+
135
+ # Creates an SQL Object. The block is evaluated within the SQL instance so
136
+ # you may use any methods available to the SQL class. The generated query is
137
+ # returned.
138
+ #
139
+ # Example
140
+ # fetch { from :address_book order :name }
141
+ # => "SELECT * FROM address_book ORDER BY name
142
+
143
+ def fetch( &dsl )
144
+ begin
145
+ SQL.new(:SELECT, &dsl).to_s
146
+ rescue Exception
147
+ @logger.fatal "Error generating SQL."
148
+ raise "Error generating SQL."
149
+ end
150
+ end
151
+
152
+ # Returns the query string for queries of type string, file, or db
153
+ def get_query(type,query)
154
+ case (type)
155
+ when :string
156
+ query
157
+ when :file
158
+ load_file( query )
159
+ when :db
160
+ select ( "query FROM #{@query_table} WHERE " +
161
+ "label LIKE '#{query}';" ) do |row| return row["query"] end
162
+ end
163
+ end
164
+
165
+ # Loads a query from file. Accepts absolute paths, relative paths from
166
+ # the toplevel directory, and relative paths from _queries/_.
167
+ # If you've been putting all your database queries in _queries_, you can
168
+ # just call them by name.
169
+ def load_file( query_file )
170
+ begin
171
+ File.read( query_file ).strip
172
+ rescue
173
+ @logger.fatal "Could not open #{query_file}"
174
+ raise "Could not open #{query_file}"; exit
175
+ end
176
+ end
177
+
178
+ # Generates the report. If @pre or @post are defined with lambdas, they will
179
+ # be called before and after the main code. generate_report will
180
+ # automatically be run at the end of the execution of a ruport template unless
181
+ # @generate is set to false.
182
+ #
183
+ #If @file != nil, ruport will print to the
184
+ # file with the specified name. Otherwise, it will print to STDOUT by
185
+ # default. @file can be set implictly by a file argument, i.e.
186
+ # ruport templates/foo.rb reports/foo_out.html => @file =
187
+ # 'reports/foo_out.html' It can also be set explicitly within a template. A
188
+ # good way to define a default value is @file ||= "reports/somefile"
189
+ #
190
+ # Lastly, if you have your mailer configuration set up and you've specified
191
+ # recipients, the contents of @mailer.body will be automatically emailed by
192
+ # this fuction.
193
+ #
194
+ # I am aware of the fact that this function is a bear. It will be cleaned up
195
+ # in future releases
196
+
197
+ def generate_report
198
+ @pre.call if @pre
199
+ if (@file.nil?)
200
+ puts(@report)
201
+ else
202
+ File.open(@file,"w") { |f| f.puts @report }
203
+ end
204
+ unless @mailer.nil? || @mailer.recipients.empty?
205
+ @mailer.body = @report unless not @mailer.body.empty?
206
+ @mailer.send_report(@report_name)
207
+ end
208
+ @post.call if @post
209
+ end
210
+
211
+ end
212
+