wesabe-wesabe 0.0.1

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