ruport 0.1.0 → 0.2.0

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.
@@ -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
+