alexmchale-commerce-bank-client 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
File without changes
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 4
4
+ :patch: 0
@@ -0,0 +1,332 @@
1
+ require 'rubygems'
2
+ require 'net/http'
3
+ require 'net/https'
4
+ require 'hpricot'
5
+ require 'andand'
6
+ require 'cgi'
7
+ require 'yaml'
8
+ require 'ftools'
9
+ require 'time'
10
+ require 'date'
11
+ require 'json'
12
+ require 'htmlentities'
13
+ require 'commercebank/monkey.rb'
14
+ require 'commercebank/appconfig.rb'
15
+ require 'commercebank/gmail.rb'
16
+
17
+ class Array
18
+ def binary
19
+ map {|e| yield(e) ? [e, nil] : [nil, e]}.transpose.map {|a| a.compact}
20
+ end
21
+ end
22
+
23
+ class Object
24
+ def to_cents
25
+ (to_s.gsub(/[^-.0-9]/, '').to_f * 100).to_i
26
+ end
27
+ end
28
+
29
+ class Date
30
+ def days_in_month
31
+ (Date.parse("12/31/#{strftime("%Y")}") << (12 - month)).day
32
+ end
33
+
34
+ def last_sunday
35
+ d = self
36
+ d -= 1 until d.wday == 0
37
+ d
38
+ end
39
+ end
40
+
41
+ class Hash
42
+ def to_url
43
+ map {|key, value| "#{CGI.escape key.to_s}=#{CGI.escape value.to_s}"}.join "&"
44
+ end
45
+
46
+ def to_cookie
47
+ map {|key, value| "#{key}=#{value}"}.join('; ')
48
+ end
49
+ end
50
+
51
+ class WebClient
52
+ attr_reader :fields, :cookies
53
+
54
+ def initialize
55
+ @cookies = Hash.new
56
+ @http = Net::HTTP.new('banking.commercebank.com', 443)
57
+ @http.use_ssl = true
58
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
59
+ end
60
+
61
+ def get(path, form = nil)
62
+ response = @http.get(path, header)
63
+ add_cookies(response)
64
+ @fields = (form && get_form(response.body, form)) || Hash.new
65
+ response
66
+ end
67
+
68
+ def post(path, form = nil)
69
+ response = @http.post(path, @fields.to_url, header)
70
+ add_cookies(response)
71
+ @fields = (form && get_form(response.body, form)) || Hash.new
72
+ response
73
+ end
74
+
75
+ private
76
+
77
+ def header
78
+ { 'Cookie' => @cookies.to_cookie }
79
+ end
80
+
81
+ def get_form(body, name)
82
+ Hpricot.buffer_size = 262144
83
+ doc = Hpricot.parse(body)
84
+ form = (doc/"##{name}").first
85
+ fields = Hash[*((form/"input").map {|e| [ e.attributes['name'], e.attributes['value'] ]}.flatten)]
86
+ fields['TestJavaScript'] = 'OK'
87
+ fields
88
+ end
89
+
90
+ def add_cookies(response)
91
+ CGI::Cookie.parse(response.header['set-cookie']).each do |key, value|
92
+ @cookies[key] = value.first
93
+ end
94
+
95
+ @cookies.delete 'path'
96
+ @cookies.delete 'expires'
97
+
98
+ self
99
+ end
100
+ end
101
+
102
+ class CommerceBank
103
+ attr_reader :register, :current, :available
104
+
105
+ def initialize
106
+ @config = AppConfig.new('~/.commerce.yaml')
107
+
108
+ client = WebClient.new
109
+
110
+ client.get('/')
111
+
112
+ client.get('/CBI/login.aspx', 'MAINFORM')
113
+
114
+ client.fields['txtUserID'] = @config[:username]
115
+ response = client.post('/CBI/login.aspx', 'MAINFORM')
116
+
117
+ # If a question was asked, answer it then get the password page.
118
+ question = response.body.scan(/Your security question:&nbsp;&nbsp;(.*?)<\/td>/i).first.andand.first
119
+ if question
120
+ client.fields['txtChallengeAnswer'] = @config[question]
121
+ client.fields['saveComputer'] = 'rdoBindDeviceNo'
122
+ response = client.post('/CBI/login.aspx', 'MAINFORM')
123
+ end
124
+
125
+ raise "could not reach the password page" unless client.fields['__EVENTTARGET'] == 'btnLogin'
126
+
127
+ client.fields['txtPassword'] = @config[:password]
128
+ response = client.post('/CBI/login.aspx')
129
+
130
+ response = client.get('/CBI/Accounts/CBI/Activity.aspx', 'MAINFORM')
131
+ (@current, @available) = parse_balance(response.body)
132
+ @pending = parse_pending(response.body)
133
+
134
+ client.fields['Anthem_UpdatePage'] = 'true'
135
+ client.fields['txtFilterFromDate:textBox'] = Time.parse('1/1/2000').strftime('%m/%d/%Y')
136
+ client.fields['txtFilterToDate:textBox'] = Time.now.strftime('%m/%d/%Y')
137
+ response = client.post('/CBI/Accounts/CBI/Activity.aspx?Anthem_CallBack=true')
138
+
139
+ raw_data = JSON.parse(response.body)
140
+ @register = parse_register(raw_data['controls']['pnlPosted'])
141
+ end
142
+
143
+ def daily_summary
144
+ today, yesterday, this_week, last_week = [], [], [], []
145
+
146
+ register.each do |entry|
147
+ if entry[:date] == Date.today then today << entry
148
+ elsif entry[:date] == (Date.today - 1) then yesterday << entry
149
+ elsif entry[:date] >= Date.today.last_sunday then this_week << entry
150
+ elsif entry[:date] >= (Date.today.last_sunday - 1).last_sunday then last_week << entry
151
+ end
152
+ end
153
+
154
+ { 'Pending' => @pending,
155
+ 'Today' => today,
156
+ 'Yesterday' => yesterday,
157
+ 'This Week' => this_week,
158
+ 'Last Week' => last_week,
159
+ :order => [ 'Pending', 'Today', 'Yesterday', 'This Week', 'Last Week' ] }
160
+ end
161
+
162
+ def monthly_summary(day_in_month = (Date.today - Date.today.day))
163
+ first_of_month = day_in_month - day_in_month.day + 1
164
+ last_of_month = first_of_month + day_in_month.days_in_month - 1
165
+ entries = register.find_all {|entry| entry[:date] >= first_of_month && entry[:date] <= last_of_month}
166
+ { day_in_month.strftime('%B') => entries }
167
+ end
168
+
169
+ def print_all
170
+ summarize 'All' => register
171
+ end
172
+
173
+ def print_daily_summary
174
+ print(summarize(daily_summary))
175
+ end
176
+
177
+ def gmail_daily_summary
178
+ subject = "Daily Summary"
179
+ summary = summarize_html(daily_summary)
180
+
181
+ username = @config['GMail Username']
182
+ password = @config['GMail Password']
183
+
184
+ GMail.new(username, password).send(username, subject, summary, 'text/html')
185
+ end
186
+
187
+ def gmail_monthly_summary
188
+ last_month = Date.today - Date.today.day
189
+ subject = "#{last_month.strftime('%B')} Summary"
190
+ summary = summarize_html(monthly_summary(last_month))
191
+
192
+ username = @config['GMail Username']
193
+ password = @config['GMail Password']
194
+
195
+ GMail.new(username, password).send(username, subject, summary, 'text/html')
196
+ end
197
+
198
+ private
199
+
200
+ def summarize(entries)
201
+ (entries[:order] || entries.keys).map do |label|
202
+ next if entries[label].length == 0
203
+
204
+ label.to_s + ":\n" + entries[label].map do |e|
205
+ [
206
+ e[:date].strftime('%02m/%02d/%04Y '),
207
+ "%-100s " % e[:destination],
208
+ "%10s " % e[:delta].to_dollars(:show_plus),
209
+ e[:total] && ("%10s " % e[:total].to_dollars),
210
+ "\n"
211
+ ].compact.join
212
+ end.join
213
+ end.compact.join("\n")
214
+ end
215
+
216
+ def summarize_html(entries)
217
+ html = ''
218
+
219
+ (entries[:order] || entries.keys).each do |label|
220
+ next if entries[label].length == 0
221
+
222
+ use_total = entries[label].find {|e| e[:total]}
223
+
224
+ html += '<h2 style="font-family: garamond, georgia, serif">' + label + '</h2>'
225
+
226
+ html += '<table cellspacing="0" cellpadding="5" style="font-size: 12px; border-style: solid; border-width: 2px; border-color: #DDDDDD" width="100%">'
227
+
228
+ html += '<tr style="font-weight: bold; background-color: #DDDDDD">'
229
+ html += '<th style="text-align: left" width="75">Date</th>'
230
+ html += '<th style="text-align: left">Destination</th>'
231
+ html += '<th style="text-align: right" width="75">Amount</th>'
232
+ html += '<th style="text-align: right" width="75">Total</th>' if use_total
233
+ html += '</tr>'
234
+
235
+ even = true
236
+ entries[label].each do |e|
237
+ even = !even
238
+
239
+ delta = "%s%0.2f" % [ (e[:delta] >= 0 ? '+' : '-'), e[:delta].abs/100.0 ]
240
+ total = "%0.2f" % (e[:total].to_i/100.0)
241
+
242
+ row_style = {
243
+ 'font-weight' => 'normal',
244
+ 'background-color' => even ? '#DDDDDD' : '#FFFFFF'
245
+ }.map {|k, v| "#{k}: #{v}"}.join('; ')
246
+
247
+ html += sprintf '<tr style="%s">', row_style
248
+ html += '<td style="text-align: left">' + e[:date].strftime('%m/%d/%Y') + '</td>'
249
+ html += '<td style="text-align: left">' + e[:destination] + '</td>'
250
+ html += '<td style="text-align: right">' + delta + '</td>'
251
+ html += '<td style="text-align: right">' + total + '</td>' if use_total
252
+ html += '</tr>'
253
+ end
254
+
255
+ html += '</table>'
256
+ end
257
+
258
+ html
259
+ end
260
+
261
+ def parse_balance(body)
262
+ Hpricot.buffer_size = 262144
263
+ doc = Hpricot.parse(body)
264
+ summaryRows = doc/"table.summaryTable"/"tr"
265
+ current = (summaryRows[3]/"td")[1].inner_html.to_cents
266
+ available = (summaryRows[4]/"td")[1].inner_html.to_cents
267
+ [current, available]
268
+ end
269
+
270
+ def parse_pending(body)
271
+ Hpricot.buffer_size = 262144
272
+ doc = Hpricot.parse(body)
273
+ coder = HTMLEntities.new
274
+
275
+ (doc/"#grdMemoPosted"/"tr").map do |e|
276
+ next nil unless (e['class'] == 'item' || e['class'] == 'alternatingItem')
277
+
278
+ values = (e/"td").map {|e1| coder.decode(e1.inner_html.strip)}
279
+
280
+ debit = values[2].to_cents
281
+ credit = values[3].to_cents
282
+ delta = credit - debit
283
+
284
+ { :date => Date.parse(values[0]),
285
+ :destination => values[1],
286
+ :delta => delta,
287
+ :debit => debit,
288
+ :credit => credit }
289
+ end.compact
290
+ end
291
+
292
+ def parse_register(body)
293
+ Hpricot.buffer_size = 262144
294
+ doc = Hpricot.parse(body)
295
+ coder = HTMLEntities.new
296
+ (doc/"#grdHistory"/"tr").map do |e|
297
+ next nil unless [ 'item', 'alternatingitem' ].include? e['class'].to_s.downcase
298
+
299
+ anchor = e.at("a")
300
+ values = (e/"td").map {|e1| e1.inner_html}
301
+ date = Date.parse(values[0])
302
+ check = values[1].strip
303
+ debit = values[3].to_cents
304
+ credit = values[4].to_cents
305
+ delta = credit - debit
306
+ total = values[5].to_cents
307
+
308
+ images = (e/"a").find_all do |e1|
309
+ e1['target'].to_s.downcase == 'checkimage'
310
+ end.map do |e1|
311
+ { :url => e1['href'], :title => e1.inner_html.strip }
312
+ end
313
+
314
+ {
315
+ :destination => coder.decode(anchor.inner_html.strip),
316
+ :url => anchor['href'],
317
+ :date => date,
318
+ :check => check,
319
+ :images => images,
320
+ :delta => delta,
321
+ :debit => debit,
322
+ :credit => credit,
323
+ :total => total
324
+ }
325
+ end.compact
326
+ end
327
+ end
328
+
329
+ if $0 == __FILE__
330
+ cb = CommerceBank.new
331
+ cb.gmail_daily_summary
332
+ end
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'pp'
3
+ require 'andand'
4
+ require 'yaml'
5
+
6
+ class AppConfig
7
+ def initialize(path)
8
+ @path = path
9
+ end
10
+
11
+ def [](field)
12
+ field = field.to_s
13
+ path = File.expand_path(@path)
14
+ config = File.exists?(path) ? YAML.load(File.read path) : Hash.new
15
+
16
+ unless config[field]
17
+ print "Please enter the following:\n"
18
+ print field, ": "
19
+
20
+ config[field] = gets.to_s.chomp
21
+
22
+ File.open(path, 'w') {|file| file.write(config.to_yaml)}
23
+ File.chmod(0600, path)
24
+ end
25
+
26
+ config[field]
27
+ end
28
+ end
@@ -0,0 +1,156 @@
1
+ require 'openssl'
2
+ require 'net/smtp'
3
+ require 'base64'
4
+
5
+ class GMail
6
+ def initialize(username, password)
7
+ @username = username
8
+ @password = password
9
+ end
10
+
11
+ def start(to, subject, body, body_type = 'text/plain')
12
+ @to = to.to_s
13
+ @subject = subject.to_s
14
+ @body = body.to_s
15
+ @body_type = body_type
16
+ @attachments = []
17
+
18
+ self
19
+ end
20
+
21
+ def add_data(name, data, type)
22
+ @attachments << { :name => name, :data => data, :type => type }
23
+
24
+ self
25
+ end
26
+
27
+ def add_file(filename, content_type)
28
+ add_data File.basename(File.expand_path(filename)), File.read(filename), content_type
29
+ end
30
+
31
+ def add_jpeg(filename, data = nil)
32
+ name = File.basename filename
33
+ data ||= File.read(filename)
34
+ type = 'image/jpeg'
35
+
36
+ add_data name, data, type
37
+ end
38
+
39
+ def compose
40
+ boundary = rand(2**128).to_s(16)
41
+
42
+ if @attachments.length > 0
43
+ attachments = @attachments.map do |attachment|
44
+ "--#{boundary}\r\n" +
45
+ "Content-Type: #{attachment[:type]}; name=\"#{attachment[:name]}\"\r\n" +
46
+ "Content-Disposition: attachment; filename=\"#{attachment[:name]}\"\r\n" +
47
+ "Content-Transfer-Encoding: base64\r\n" +
48
+ "\r\n" +
49
+ Base64.encode64(attachment[:data])
50
+ end.compact.join
51
+
52
+ "Date: #{Time.now.to_s}\r\n" +
53
+ "From: #{@username}\r\n" +
54
+ "To: #{@to}\r\n" +
55
+ "Subject: #{@subject}\r\n" +
56
+ "MIME-Version: 1.0\r\n" +
57
+ "Content-Type: multipart/mixed; boundary=\"#{boundary}\"\r\n" +
58
+ "\r\n" +
59
+ "--#{boundary}\r\n" +
60
+ "Content-Type: text/plain\r\n" +
61
+ "\r\n" +
62
+ "#{@body}\r\n" +
63
+ "\r\n" +
64
+ attachments +
65
+ "--#{boundary}--\r\n" +
66
+ "\r\n.\r\n"
67
+ else
68
+ "Date: #{Time.now.to_s}\r\n" +
69
+ "From: #{@username}\r\n" +
70
+ "To: #{@to}\r\n" +
71
+ "Subject: #{@subject}\r\n" +
72
+ "Content-Type: text/plain\r\n" +
73
+ "\r\n" +
74
+ "#{@body}\r\n" +
75
+ "\r\n.\r\n"
76
+ end
77
+ end
78
+
79
+ def dispatch
80
+ Net::SMTP.start('smtp.gmail.com', 587, 'gmail.com', @username, @password, :plain) do |smtp|
81
+ smtp.send_message compose, @username, @to
82
+ end
83
+ end
84
+
85
+ def send(to, subject, body, content_type = 'text/plan')
86
+ Net::SMTP.start('smtp.gmail.com', 587, 'gmail.com', @username, @password, :plain) do |smtp|
87
+ msg = "From: #{@username}\r\nTo: #{to}\r\nSubject: #{subject}\r\nContent-Type: #{content_type}\r\n\r\n#{body}"
88
+ smtp.send_message msg, @username, to
89
+ end
90
+ end
91
+ end
92
+
93
+ # Net::SMTP monkeypatching was taken from:
94
+ # http://www.stephenchu.com/2006/06/how-to-use-gmail-smtp-server-to-send.html
95
+ Net::SMTP.class_eval do
96
+ private
97
+ def do_start(helodomain, user, secret, authtype)
98
+ raise IOError, 'SMTP session already started' if @started
99
+ check_auth_args(user, secret) if (user or secret)
100
+
101
+ sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
102
+ @socket = Net::InternetMessageIO.new(sock)
103
+ @socket.read_timeout = 60 #@read_timeout
104
+ @socket.debug_output = STDERR #@debug_output
105
+
106
+ check_response(critical { recv_response() })
107
+ do_helo(helodomain)
108
+
109
+ raise 'openssl library not installed' unless defined?(OpenSSL)
110
+ starttls
111
+ ssl = OpenSSL::SSL::SSLSocket.new(sock)
112
+ ssl.sync_close = true
113
+ ssl.connect
114
+ @socket = Net::InternetMessageIO.new(ssl)
115
+ @socket.read_timeout = 60 #@read_timeout
116
+ @socket.debug_output = STDERR #@debug_output
117
+ do_helo(helodomain)
118
+
119
+ authenticate user, secret, authtype if user
120
+ @started = true
121
+ ensure
122
+ unless @started
123
+ # authentication failed, cancel connection.
124
+ @socket.close if not @started and @socket and not @socket.closed?
125
+ @socket = nil
126
+ end
127
+ end
128
+
129
+ def do_helo(helodomain)
130
+ begin
131
+ if @esmtp
132
+ ehlo helodomain
133
+ else
134
+ helo helodomain
135
+ end
136
+ rescue Net::ProtocolError
137
+ if @esmtp
138
+ @esmtp = false
139
+ @error_occured = false
140
+ retry
141
+ end
142
+ raise
143
+ end
144
+ end
145
+
146
+ def starttls
147
+ getok('STARTTLS')
148
+ end
149
+
150
+ def quit
151
+ begin
152
+ getok('QUIT')
153
+ rescue EOFError
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,98 @@
1
+ require 'rubygems'
2
+ require 'pp'
3
+ require 'andand'
4
+ require 'cgi'
5
+ require 'yaml'
6
+ require 'RMagick'
7
+
8
+ class Array
9
+ def binary
10
+ map {|e| yield(e) ? [e, nil] : [nil, e]}.transpose.map {|a| a.compact}
11
+ end
12
+
13
+ def paramify
14
+ hash = Hash.new
15
+
16
+ hash.merge! pop if last.kind_of? Hash
17
+ each {|e| hash[e] = true}
18
+
19
+ hash
20
+ end
21
+ end
22
+
23
+ class Object
24
+ def to_cents
25
+ (to_s.gsub(/[^-.0-9]/, '').to_f * 100).to_i
26
+ end
27
+
28
+ def to_dollars(*options)
29
+ options = options.paramify
30
+
31
+ plus = options[:show_plus] ? '+' : ''
32
+ minus = options[:hide_minus] ? '' : '-'
33
+ sign = to_i >= 0 ? plus : minus
34
+
35
+ ("%s%0.2f" % [ sign, to_i.abs / 100.0 ]).commify
36
+ end
37
+ end
38
+
39
+ class Date
40
+ def days_in_month
41
+ (Date.parse("12/31/#{strftime("%Y")}") << (12 - month)).day
42
+ end
43
+
44
+ def last_sunday
45
+ d = self
46
+ d -= 1 until d.wday == 0
47
+ d
48
+ end
49
+ end
50
+
51
+ class Hash
52
+ def to_url
53
+ map {|key, value| "#{CGI.escape key.to_s}=#{CGI.escape value.to_s}"}.join "&"
54
+ end
55
+
56
+ def to_cookie
57
+ map {|key, value| "#{key}=#{value}"}.join('; ')
58
+ end
59
+ end
60
+
61
+ class String
62
+ def commify
63
+ reverse.gsub(/(\d\d\d)(?=\d)/, '\1,').reverse
64
+ end
65
+ end
66
+
67
+ module Magick
68
+ class Image
69
+ def autocrop(red = 65535, green = 65535, blue = 65535)
70
+ low_x = 0
71
+ low_y = 0
72
+ high_x = columns
73
+ high_y = rows
74
+
75
+ croppable = Proc.new do |x, y|
76
+ pixel = pixel_color(x, y)
77
+ (pixel.red == red) && (pixel.green == green) && (pixel.blue == blue)
78
+ end
79
+
80
+ # Scan the top horizontal.
81
+ low_y += 1 until (low_y == rows) || (low_x..high_x).find {|x| !croppable.call(x, low_y)}
82
+
83
+ # Scan the bottom horizontal.
84
+ high_y -= 1 until (low_y == high_y) || (low_x..high_x).find {|x| !croppable.call(x, high_y)}
85
+
86
+ # Scan the left vertical.
87
+ low_x += 1 until (low_x == columns) || (low_y..high_y).find {|y| !croppable.call(low_x, y)}
88
+
89
+ # Scan the right vertical.
90
+ high_x -= 1 until (low_x == high_x) || (low_y..high_y).find {|y| !croppable.call(high_x, y)}
91
+
92
+ width = high_x - low_x
93
+ height = high_y - low_y
94
+
95
+ crop low_x, low_y, width, height
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,6 @@
1
+ require 'test_helper'
2
+
3
+ class CommerceBankClientTest < Test::Unit::TestCase
4
+ should "implement tests" do
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ require 'test_helper'
2
+
3
+ class MonkeyPatchTest < Test::Unit::TestCase
4
+ should "convert a hash to a valid url parameter string" do
5
+ h1 = { :foo => 1, :bar => 2, :baz => 3 }
6
+ assert_equal h1.to_url, 'foo=1&bar=2&baz=3'
7
+
8
+ h2 = { :foo => "What's up Doc", :bar => "1" }
9
+ assert_equal h2.to_url, "foo=What%27s+up+Doc&bar=1"
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'lib/commercebank.rb'
8
+
9
+ class Test::Unit::TestCase
10
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: alexmchale-commerce-bank-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - Alex McHale
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-10 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: json
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.1.3
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: andand
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.3.1
34
+ version:
35
+ description:
36
+ email: alexmchale@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.markdown
43
+ files:
44
+ - VERSION.yml
45
+ - README.markdown
46
+ - lib/commercebank.rb
47
+ - lib/commercebank
48
+ - lib/commercebank/monkey.rb
49
+ - lib/commercebank/gmail.rb
50
+ - lib/commercebank/appconfig.rb
51
+ - test/monkeypatch_test.rb
52
+ - test/test_helper.rb
53
+ - test/commerce_bank_client_test.rb
54
+ has_rdoc: true
55
+ homepage: http://github.com/alexmchale/commerce-bank-client
56
+ post_install_message:
57
+ rdoc_options:
58
+ - --inline-source
59
+ - --charset=UTF-8
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.2.0
78
+ signing_key:
79
+ specification_version: 2
80
+ summary: CBC is a client for Commerce Bank's website.
81
+ test_files: []
82
+