wesabe-wesabe 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Wesabe
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,4 @@
1
+ wesabe
2
+ ======
3
+
4
+ Access the Wesabe API. See the examples directory for usage examples.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ task :default => :install
2
+
3
+ desc "install the gem locally"
4
+ task :install do
5
+ `which thor &>/dev/null`
6
+ if $?.exitstatus != 0
7
+ $stderr.puts "This project uses thor (http://github.com/wycats/thor)"
8
+ exit(1)
9
+ end
10
+
11
+ sh %{thor :install}
12
+ end
data/lib/cacert.pem ADDED
@@ -0,0 +1,19 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx
3
+ FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
4
+ VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
5
+ biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm
6
+ MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx
7
+ MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
8
+ DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3
9
+ dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl
10
+ cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3
11
+ DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
12
+ gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91
13
+ yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX
14
+ L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj
15
+ EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG
16
+ 7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e
17
+ QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ
18
+ qdq5snUb9kLy78fyGPmJvKP/iiMucEc=
19
+ -----END CERTIFICATE-----
@@ -0,0 +1,39 @@
1
+ class Wesabe::Account
2
+ # The user-scoped account id, used to identify the account in URLs.
3
+ attr_accessor :id
4
+ # The user-provided account name ("Bank of America - Checking")
5
+ attr_accessor :name
6
+ # This account's balance or +nil+ if the account is a cash account.
7
+ attr_accessor :balance
8
+ # This account's currency.
9
+ attr_accessor :currency
10
+ # The financial institution this account is held at.
11
+ attr_accessor :financial_institution
12
+
13
+ # Initializes a +Wesabe::Account+ and yields itself.
14
+ #
15
+ # @yieldparam [Wesabe::Account] account
16
+ # The newly-created account.
17
+ def initialize
18
+ yield self if block_given?
19
+ end
20
+
21
+ # Returns a +Wesabe::Account+ generated from Wesabe's API XML.
22
+ #
23
+ # @param [Hpricot::Element] xml
24
+ # The <account> element from the API.
25
+ #
26
+ # @return [Wesabe::Account]
27
+ # The newly-created account populated by +xml+.
28
+ def self.from_xml(xml)
29
+ new do |account|
30
+ account.id = xml.at("id").inner_text.to_i
31
+ account.name = xml.at("name").inner_text
32
+ balance = xml.at("current-balance")
33
+ account.balance = balance.inner_text.to_f if balance
34
+ account.currency = Wesabe::Currency.from_xml(xml.at("currency"))
35
+ fi = xml.at("financial-institution")
36
+ account.financial_institution = Wesabe::FinancialInstitution.from_xml(fi) if fi
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,34 @@
1
+ class Wesabe::Credential
2
+ # The id of the credential, used to identify the account in URLs.
3
+ attr_accessor :id
4
+ # The financial institution this credential is for.
5
+ attr_accessor :financial_institution
6
+ # The accounts linked to this credential.
7
+ attr_accessor :accounts
8
+
9
+ # Initializes a +Wesabe::Credential+ and yields itself.
10
+ #
11
+ # @yieldparam [Wesabe::Credential] credential
12
+ # The newly-created credential.
13
+ def initialize
14
+ yield self if block_given?
15
+ end
16
+
17
+ # Returns a +Wesabe::Credential+ generated from Wesabe's API XML.
18
+ #
19
+ # @param [Hpricot::Element] xml
20
+ # The <credential> element from the API.
21
+ #
22
+ # @return [Wesabe::Credential]
23
+ # The newly-created credential populated by +xml+.
24
+ def self.from_xml(xml)
25
+ new do |cred|
26
+ cred.id = xml.at('id').inner_text.to_i
27
+ cred.financial_institution = Wesabe::FinancialInstitution.from_xml(
28
+ xml.children_of_type('financial-institution')[0])
29
+ cred.accounts = xml.search('accounts account').map do |account|
30
+ Wesabe::Account.from_xml(account)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ class Wesabe::Currency
2
+ attr_accessor :decimal_places, :symbol, :separator, :delimiter
3
+
4
+ # Initializes a +Wesabe::Currency+ and yields itself.
5
+ #
6
+ # @yieldparam [Wesabe::Currency] currency
7
+ # The newly-created currency.
8
+ def initialize
9
+ yield self if block_given?
10
+ end
11
+
12
+ alias_method :precision, :decimal_places
13
+ alias_method :precision=, :decimal_places=
14
+
15
+ # Returns a +Wesabe::Currency+ generated from Wesabe's API XML.
16
+ #
17
+ # @param [Hpricot::Element] xml
18
+ # The <currency> element from the API.
19
+ #
20
+ # @return [Wesabe::Currency]
21
+ # The newly-created currency populated by +xml+.
22
+ def self.from_xml(xml)
23
+ new do |currency|
24
+ currency.decimal_places = xml[:decimal_places].to_s.to_i
25
+ currency.symbol = xml[:symbol].to_s
26
+ currency.separator = xml[:separator].to_s
27
+ currency.delimiter = xml[:delimiter].to_s
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,34 @@
1
+ class Wesabe::FinancialInstitution
2
+ # The id of this +FinancialInstitution+, as used in URLs.
3
+ attr_accessor :id
4
+ # The name of this +FinancialInstitution+ ("Bank of America").
5
+ attr_accessor :name
6
+ # The url users of this +FinancialInstitution+ log in to for online banking.
7
+ attr_accessor :login_url
8
+ # The home url of this +FinancialInstitution+.
9
+ attr_accessor :homepage_url
10
+
11
+ # Initializes a +Wesabe::FinancialInstitution+ and yields itself.
12
+ #
13
+ # @yieldparam [Wesabe::FinancialInstitution] financial_institution
14
+ # The newly-created financial institution.
15
+ def initialize
16
+ yield self if block_given?
17
+ end
18
+
19
+ # Returns a +Wesabe::FinancialInstitution+ generated from Wesabe's API XML.
20
+ #
21
+ # @param [Hpricot::Element] xml
22
+ # The <financial-institution> element from the API.
23
+ #
24
+ # @return [Wesabe::FinancialInstitution]
25
+ # The newly-created financial institution populated by +xml+.
26
+ def self.from_xml(xml)
27
+ new do |fi|
28
+ fi.id = (xml.children_of_type("id") + xml.children_of_type("wesabe-id")).first.inner_text
29
+ fi.name = xml.at("name").inner_text
30
+ fi.login_url = xml.at("login-url") && xml.at("login-url").inner_text
31
+ fi.homepage_url = xml.at("homepage-url") && xml.at("homepage-url").inner_text
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,144 @@
1
+ class Wesabe::Request
2
+ attr_reader :url, :username, :password, :method, :proxy, :payload
3
+
4
+ private
5
+
6
+ def initialize(options=Hash.new)
7
+ @url = options[:url] or raise ArgumentError, "Missing option 'url'"
8
+ @username = options[:username] or raise ArgumentError, "Missing option 'username'"
9
+ @password = options[:password] or raise ArgumentError, "Missing option 'password'"
10
+ @proxy = options[:proxy]
11
+ @method = options[:method] || :get
12
+ @payload = options[:payload]
13
+ end
14
+
15
+ # Returns a new Net::HTTP instance to connect to the Wesabe API.
16
+ #
17
+ # @return [Net::HTTP]
18
+ # A connection object all ready to be used to communicate securely.
19
+ def net
20
+ if proxy
21
+ proxy_uri = URI.parse(proxy)
22
+ http_klass = Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
23
+ else
24
+ http_klass = Net::HTTP
25
+ end
26
+
27
+ http = http_klass.new(uri.host, uri.port)
28
+ if uri.scheme == 'https'
29
+ http.use_ssl = true
30
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
31
+ http.ca_file = self.class.ca_file
32
+ end
33
+ http
34
+ end
35
+
36
+ def uri
37
+ URI.join(self.class.base_url, url)
38
+ end
39
+
40
+ def process_response(res)
41
+ if %w[200 201 202].include?(res.code)
42
+ res.body
43
+ elsif %w[301 302 303].include?(res.code)
44
+ url = res.header['Location']
45
+
46
+ if url !~ /^http/
47
+ uri = URI.parse(@url)
48
+ uri.path = "/#{url}".squeeze('/')
49
+ url = uri.to_s
50
+ end
51
+
52
+ raise Redirect, url
53
+ elsif res.code == "401"
54
+ raise Unauthorized
55
+ elsif res.code == "404"
56
+ raise ResourceNotFound
57
+ else
58
+ raise RequestFailed, url
59
+ end
60
+ end
61
+
62
+ public
63
+
64
+ # Executes the request and returns the response.
65
+ #
66
+ # @return [String]
67
+ # The response object for the request just made.
68
+ #
69
+ # @raise [Wesabe::ServerConnectionBroken]
70
+ # If the connection with the server breaks.
71
+ #
72
+ # @raise [Timeout::Error]
73
+ # If the request takes too long.
74
+ def execute
75
+ # set up the uri
76
+ @username = uri.user if uri.user
77
+ @password = uri.password if uri.password
78
+
79
+ # set up the request
80
+ req = Net::HTTP.const_get(method.to_s.capitalize).new(uri.request_uri)
81
+ req.basic_auth(username, password)
82
+
83
+ net.start do |http|
84
+ process_response http.request(req, payload || "")
85
+ end
86
+ end
87
+
88
+ # Executes a request and returns the response.
89
+ #
90
+ # @param [String] options[:url]
91
+ # The url relative to +Wesabe::Request.base_url+ to request (required).
92
+ #
93
+ # @param [String] options[:username]
94
+ # The Wesabe username (required).
95
+ #
96
+ # @param [String] options[:password]
97
+ # The Wesabe password (required).
98
+ #
99
+ # @param [String] options[:proxy]
100
+ # The proxy url to use (optional).
101
+ #
102
+ # @param [String, Symbol] options[:method]
103
+ # The HTTP method to use (defaults to +:get+).
104
+ #
105
+ # @param [String] options[:payload]
106
+ # The post-body to use (defaults to an empty string).
107
+ #
108
+ # @return [Net::HTTPResponse]
109
+ # The response object for the request just made.
110
+ #
111
+ # @raise [EOFError]
112
+ # If the connection with the server breaks.
113
+ #
114
+ # @raise [Timeout::Error]
115
+ # If the request takes too long.
116
+ def self.execute(options=Hash.new)
117
+ new(options).execute
118
+ end
119
+
120
+ def self.ca_file
121
+ [File.expand_path("~/.wesabe"), File.join(File.dirname(__FILE__), '..')].each do |dir|
122
+ file = File.join(dir, "cacert.pem")
123
+ return file if File.exist?(file)
124
+ end
125
+ raise "Unable to find a CA pem file to use for www.wesabe.com"
126
+ end
127
+
128
+ # Gets the base url for the Wesabe API.
129
+ def self.base_url
130
+ @base_url ||= "https://www.wesabe.com"
131
+ end
132
+
133
+ # Sets the base url for the Wesabe API.
134
+ def self.base_url=(base_url)
135
+ @base_url = base_url
136
+ end
137
+ end
138
+
139
+ class Wesabe::Request::Exception < RuntimeError; end
140
+ class Wesabe::Request::ServerBrokeConnection < Exception; end
141
+ class Wesabe::Request::Redirect < Exception; end
142
+ class Wesabe::Request::Unauthorized < Exception; end
143
+ class Wesabe::Request::ResourceNotFound < Exception; end
144
+ class Wesabe::Request::RequestFailed < Exception; end
data/lib/wesabe.rb ADDED
@@ -0,0 +1,122 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+
3
+ require 'net/https'
4
+ require 'hpricot'
5
+ require 'yaml'
6
+
7
+ # Provides an object-oriented interface to the Wesabe API.
8
+ class Wesabe
9
+ attr_accessor :username, :password
10
+
11
+ # Initializes access to the Wesabe API with a certain user. All requests
12
+ # will be made in the context of this user.
13
+ #
14
+ # @param [String] username
15
+ # The username of an active Wesabe user.
16
+ #
17
+ # @param [String] password
18
+ # The password of an active Wesabe user.
19
+ def initialize(username, password)
20
+ self.username = username
21
+ self.password = password
22
+ end
23
+
24
+ # Fetches the user's accounts list from Wesabe or, if the list was already
25
+ # fetched, returns the cached result.
26
+ #
27
+ # pp wesabe.accounts
28
+ # [#<Wesabe::Account:0x106105c
29
+ # @balance=-393.42,
30
+ # @currency=
31
+ # #<Wesabe::Currency:0x104fdc0
32
+ # @decimal_places=2,
33
+ # @delimiter=",",
34
+ # @separator=".",
35
+ # @symbol="$">,
36
+ # @financial_institution=
37
+ # #<Wesabe::FinancialInstitution:0x104b054
38
+ # @homepage_url=nil,
39
+ # @id="us-003383",
40
+ # @login_url=nil,
41
+ # @name="American Express Card">,
42
+ # @id=4,
43
+ # @name="Amex Blue">]
44
+ #
45
+ # @return [Array<Wesabe::Account>]
46
+ # A list of the user's active accounts.
47
+ def accounts
48
+ @accounts ||= load_accounts
49
+ end
50
+
51
+ # Returns an account with the given id or +nil+ if the account is not found.
52
+ #
53
+ # wesabe.account(4).name # => "Amex Blue"
54
+ #
55
+ # @param [#to_s] id
56
+ # Something whose +to_s+ result matches the +to_s+ result of the account id.
57
+ #
58
+ # @return [Wesabe::Account, nil]
59
+ # The account whose user-scoped id is +id+ or +nil+ if there is no account
60
+ # with that +id+.
61
+ def account(id)
62
+ accounts.find {|a| a.id.to_s == id.to_s}
63
+ end
64
+
65
+ # Fetches the user's accounts list from Wesabe or, if the list was already
66
+ # fetched, returns the cached result.
67
+ #
68
+ # pp wesabe.credentials
69
+ # [#<Wesabe::Credential:0x10ae870
70
+ # @accounts=[],
71
+ # @financial_institution=
72
+ # #<Wesabe::FinancialInstitution:0x1091928
73
+ # @homepage_url=nil,
74
+ # @id="us-003383",
75
+ # @login_url=nil,
76
+ # @name="American Express Card">,
77
+ # @id=3>]
78
+ #
79
+ # @return [Array<Wesabe::Account>]
80
+ # A list of the user's active accounts.
81
+ def credentials
82
+ @credentials ||= load_credentials
83
+ end
84
+
85
+ private
86
+
87
+ def load_accounts
88
+ process_accounts(
89
+ Hpricot::XML(
90
+ Request.execute(
91
+ :url => '/accounts.xml',
92
+ :username => username,
93
+ :password => password)))
94
+ end
95
+
96
+ def process_accounts(xml)
97
+ (xml / :accounts / :account).map do |element|
98
+ Account.from_xml(element)
99
+ end
100
+ end
101
+
102
+ def load_credentials
103
+ process_credentials(
104
+ Hpricot::XML(
105
+ Request.execute(
106
+ :url => '/credentials.xml',
107
+ :username => username,
108
+ :password => password)))
109
+ end
110
+
111
+ def process_credentials(xml)
112
+ (xml / :credentials / :credential).map do |element|
113
+ Credential.from_xml(element)
114
+ end
115
+ end
116
+ end
117
+
118
+ require 'wesabe/request'
119
+ require 'wesabe/account'
120
+ require 'wesabe/currency'
121
+ require 'wesabe/credential'
122
+ require 'wesabe/financial_institution'
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wesabe-wesabe
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Brian Donovan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-08-04 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hpricot
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - "="
21
+ - !ruby/object:Gem::Version
22
+ version: "0.6"
23
+ version:
24
+ description: Wraps communication with the Wesabe API
25
+ email: brian@wesabe.com
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - README.markdown
32
+ - LICENSE
33
+ files:
34
+ - LICENSE
35
+ - README.markdown
36
+ - Rakefile
37
+ - lib/cacert.pem
38
+ - lib/wesabe
39
+ - lib/wesabe/account.rb
40
+ - lib/wesabe/credential.rb
41
+ - lib/wesabe/currency.rb
42
+ - lib/wesabe/financial_institution.rb
43
+ - lib/wesabe/request.rb
44
+ - lib/wesabe.rb
45
+ has_rdoc: true
46
+ homepage: https://www.wesabe.com/page/api
47
+ post_install_message:
48
+ rdoc_options: []
49
+
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ requirements: []
65
+
66
+ rubyforge_project: wesabe
67
+ rubygems_version: 1.2.0
68
+ signing_key:
69
+ specification_version: 2
70
+ summary: Wraps communication with the Wesabe API
71
+ test_files: []
72
+