wesabe-wesabe 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -1,4 +1,9 @@
1
1
  wesabe
2
2
  ======
3
3
 
4
- Access the Wesabe API. See the examples directory for usage examples.
4
+ Access the Wesabe API. See the examples directory for usage examples.
5
+
6
+ Installation
7
+ ------------
8
+
9
+ $ sudo gem install -r --source http://gems.github.com/ wesabe-wesabe
data/bin/console ADDED
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.dirname(__FILE__) + "/../lib"
4
+ require 'rubygems'
5
+ require 'wesabe'
6
+
7
+ require 'yaml'
8
+
9
+ def usage(why = nil)
10
+ $stderr.puts "failed for reason: #{why}" if why
11
+ $stderr.puts "usage: console [url|name] [username] [password]"
12
+ exit(1)
13
+ end
14
+
15
+ @url = ARGV.shift if ARGV.size != 2
16
+ @username, @password = ARGV.shift, ARGV.shift
17
+
18
+ @url ||= "https://www.wesabe.com/"
19
+
20
+ config = YAML.load(File.read(ENV['HOME'] + "/.wesabe.console")) rescue {}
21
+
22
+ if c = config[@url]
23
+ @url = c["url"]
24
+ @username = c["username"] || @username
25
+ @password = c["password"] || @password
26
+ end
27
+
28
+ usage("invalid url #{@url.inspect}") unless @url =~ /^https?/
29
+ usage("missing username") unless @username
30
+ usage("missing password") unless @password
31
+
32
+ def w
33
+ @w ||= begin
34
+ Wesabe::Request.base_url = @url
35
+ Wesabe.new(@username, @password)
36
+ end
37
+ end
38
+
39
+ w # force it to load
40
+
41
+ def method_missing(s, *args, &b)
42
+ super unless w.respond_to?(s)
43
+ begin
44
+ w.send(s, *args, &b)
45
+ rescue Wesabe::Request::RequestFailed => e
46
+ puts e.response.body
47
+ raise e
48
+ end
49
+ end
50
+
51
+ require 'irb'
52
+ require 'irb/completion'
53
+
54
+ if File.exists? ".irbrc"
55
+ ENV['IRBRC'] = ".irbrc"
56
+ end
57
+
58
+ ARGV.clear
59
+
60
+ puts "Starting the Wesabe API console for #{@username} at #{@url}"
61
+ puts "(try 'accounts', 'credentials', or 'targets')"
62
+
63
+ IRB.start
64
+ exit!
data/lib/wesabe.rb CHANGED
@@ -1,29 +1,38 @@
1
1
  $:.unshift(File.dirname(__FILE__))
2
2
 
3
- require 'net/https'
3
+ begin
4
+ require 'rubygems'
5
+ rescue LoadError
6
+ # no rubygems, just keep going
7
+ end
8
+
4
9
  require 'hpricot'
10
+ require 'net/https'
5
11
  require 'yaml'
12
+ require 'time'
6
13
 
7
14
  # Provides an object-oriented interface to the Wesabe API.
8
15
  class Wesabe
9
16
  attr_accessor :username, :password
10
-
11
- # Initializes access to the Wesabe API with a certain user. All requests
17
+
18
+ VERSION = "0.0.1"
19
+
20
+ # Initializes access to the Wesabe API with a certain user. All requests
12
21
  # will be made in the context of this user.
13
- #
22
+ #
14
23
  # @param [String] username
15
24
  # The username of an active Wesabe user.
16
- #
25
+ #
17
26
  # @param [String] password
18
27
  # The password of an active Wesabe user.
19
28
  def initialize(username, password)
20
29
  self.username = username
21
30
  self.password = password
22
31
  end
23
-
32
+
24
33
  # Fetches the user's accounts list from Wesabe or, if the list was already
25
34
  # fetched, returns the cached result.
26
- #
35
+ #
27
36
  # pp wesabe.accounts
28
37
  # [#<Wesabe::Account:0x106105c
29
38
  # @balance=-393.42,
@@ -47,24 +56,24 @@ class Wesabe
47
56
  def accounts
48
57
  @accounts ||= load_accounts
49
58
  end
50
-
59
+
51
60
  # Returns an account with the given id or +nil+ if the account is not found.
52
- #
61
+ #
53
62
  # wesabe.account(4).name # => "Amex Blue"
54
- #
63
+ #
55
64
  # @param [#to_s] id
56
65
  # Something whose +to_s+ result matches the +to_s+ result of the account id.
57
- #
66
+ #
58
67
  # @return [Wesabe::Account, nil]
59
68
  # The account whose user-scoped id is +id+ or +nil+ if there is no account
60
69
  # with that +id+.
61
70
  def account(id)
62
71
  accounts.find {|a| a.id.to_s == id.to_s}
63
72
  end
64
-
73
+
65
74
  # Fetches the user's accounts list from Wesabe or, if the list was already
66
75
  # fetched, returns the cached result.
67
- #
76
+ #
68
77
  # pp wesabe.credentials
69
78
  # [#<Wesabe::Credential:0x10ae870
70
79
  # @accounts=[],
@@ -75,48 +84,81 @@ class Wesabe
75
84
  # @login_url=nil,
76
85
  # @name="American Express Card">,
77
86
  # @id=3>]
78
- #
87
+ #
79
88
  # @return [Array<Wesabe::Account>]
80
89
  # A list of the user's active accounts.
81
90
  def credentials
82
91
  @credentials ||= load_credentials
83
92
  end
84
-
93
+
94
+ # Fetches the user's targets list from Wesabe or, if the list was already
95
+ # fetched, returns the cached result.
96
+ def targets
97
+ @targets ||= load_targets
98
+ end
99
+
100
+ # Executes a request via POST with the initial username and password.
101
+ #
102
+ # @see Wesabe::Request::execute
103
+ def post(options)
104
+ Request.execute({:method => :post, :username => username, :password => password}.merge(options))
105
+ end
106
+
107
+ # Executes a request via GET with the initial username and password.
108
+ #
109
+ # @see Wesabe::Request::execute
110
+ def get(options)
111
+ Request.execute({:method => :get, :username => username, :password => password}.merge(options))
112
+ end
113
+
114
+ def inspect
115
+ "#<#{self.class.name} username=#{username.inspect} password=#{password.gsub(/./, '*').inspect} url=#{Wesabe::Request.base_url.inspect}>"
116
+ end
117
+
85
118
  private
86
-
119
+
87
120
  def load_accounts
88
- process_accounts(
89
- Hpricot::XML(
90
- Request.execute(
91
- :url => '/accounts.xml',
92
- :username => username,
93
- :password => password)))
121
+ process_accounts( Hpricot::XML( get(:url => '/accounts.xml') ) )
94
122
  end
95
-
123
+
96
124
  def process_accounts(xml)
97
- (xml / :accounts / :account).map do |element|
125
+ associate((xml / :accounts / :account).map do |element|
98
126
  Account.from_xml(element)
99
- end
127
+ end)
100
128
  end
101
-
129
+
102
130
  def load_credentials
103
- process_credentials(
104
- Hpricot::XML(
105
- Request.execute(
106
- :url => '/credentials.xml',
107
- :username => username,
108
- :password => password)))
131
+ process_credentials( Hpricot::XML( get(:url => '/credentials.xml') ) )
109
132
  end
110
-
133
+
111
134
  def process_credentials(xml)
112
- (xml / :credentials / :credential).map do |element|
135
+ associate((xml / :credentials / :credential).map do |element|
113
136
  Credential.from_xml(element)
114
- end
137
+ end)
138
+ end
139
+
140
+ def load_targets
141
+ process_targets( Hpricot::XML( get(:url => '/targets.xml') ) )
142
+ end
143
+
144
+ def process_targets(xml)
145
+ associate((xml / :targets / :target).map do |element|
146
+ Target.from_xml(element)
147
+ end)
148
+ end
149
+
150
+ def associate(what)
151
+ Wesabe::Util.all_or_one(what) {|obj| obj.wesabe = self}
115
152
  end
116
153
  end
117
154
 
155
+ require 'wesabe/util'
118
156
  require 'wesabe/request'
157
+ require 'wesabe/base_model'
119
158
  require 'wesabe/account'
159
+ require 'wesabe/upload'
160
+ require 'wesabe/financial_institution'
120
161
  require 'wesabe/currency'
121
162
  require 'wesabe/credential'
122
- require 'wesabe/financial_institution'
163
+ require 'wesabe/job'
164
+ require 'wesabe/target'
@@ -1,34 +1,53 @@
1
- class Wesabe::Account
1
+ # Encapsulates an account from Wesabe's API.
2
+ class Wesabe::Account < Wesabe::BaseModel
2
3
  # The user-scoped account id, used to identify the account in URLs.
3
4
  attr_accessor :id
5
+ # The application-scoped account id, used in upload
6
+ attr_accessor :number
4
7
  # The user-provided account name ("Bank of America - Checking")
5
8
  attr_accessor :name
9
+ # The account type ("Credit Card", "Savings" ...)
10
+ attr_accessor :type
6
11
  # This account's balance or +nil+ if the account is a cash account.
7
12
  attr_accessor :balance
8
13
  # This account's currency.
9
14
  attr_accessor :currency
10
15
  # The financial institution this account is held at.
11
16
  attr_accessor :financial_institution
12
-
17
+
13
18
  # Initializes a +Wesabe::Account+ and yields itself.
14
- #
19
+ #
15
20
  # @yieldparam [Wesabe::Account] account
16
21
  # The newly-created account.
17
22
  def initialize
18
23
  yield self if block_given?
19
24
  end
20
-
25
+
26
+ # Creates a +Wesabe::Upload+ that can be used to upload to this account.
27
+ #
28
+ # @return [Wesabe::Upload]
29
+ # The newly-created upload, ready to be used to upload a statement.
30
+ def new_upload
31
+ Wesabe::Upload.new do |upload|
32
+ upload.accounts = [self]
33
+ upload.financial_institution = financial_institution
34
+ associate upload
35
+ end
36
+ end
37
+
21
38
  # Returns a +Wesabe::Account+ generated from Wesabe's API XML.
22
- #
39
+ #
23
40
  # @param [Hpricot::Element] xml
24
41
  # The <account> element from the API.
25
- #
42
+ #
26
43
  # @return [Wesabe::Account]
27
44
  # The newly-created account populated by +xml+.
28
45
  def self.from_xml(xml)
29
46
  new do |account|
30
47
  account.id = xml.at("id").inner_text.to_i
31
48
  account.name = xml.at("name").inner_text
49
+ account.type = xml.at("account-type").inner_text
50
+ account.number = xml.at("account-number").inner_text if xml.at("account-number")
32
51
  balance = xml.at("current-balance")
33
52
  account.balance = balance.inner_text.to_f if balance
34
53
  account.currency = Wesabe::Currency.from_xml(xml.at("currency"))
@@ -36,4 +55,8 @@ class Wesabe::Account
36
55
  account.financial_institution = Wesabe::FinancialInstitution.from_xml(fi) if fi
37
56
  end
38
57
  end
58
+
59
+ def inspect
60
+ inspect_these :id, :number, :type, :name, :balance, :financial_institution, :currency
61
+ end
39
62
  end
@@ -0,0 +1,31 @@
1
+ class Wesabe::BaseModel
2
+ include Wesabe::Util
3
+ # The +Wesabe+ instance this model uses.
4
+ #
5
+ # @return [Wesabe] The object containing the username/password to use.
6
+ attr_accessor :wesabe
7
+
8
+ # Requests via POST using the given options.
9
+ #
10
+ # @see Wesabe#post
11
+ def post(options)
12
+ wesabe.post(options)
13
+ end
14
+
15
+ # Requests via GET using the given options.
16
+ #
17
+ # @see Wesabe#get
18
+ def get(options)
19
+ wesabe.get(options)
20
+ end
21
+
22
+ private
23
+
24
+ def associate(what)
25
+ all_or_one(what) {|obj| obj.wesabe = wesabe}
26
+ end
27
+
28
+ def inspect_these(*attributes)
29
+ "#<#{self.class.name}#{attributes.map{|a| " #{a}=#{send(a).inspect}"}.join}>"
30
+ end
31
+ end
@@ -1,24 +1,32 @@
1
- class Wesabe::Credential
1
+ class Wesabe::Credential < Wesabe::BaseModel
2
2
  # The id of the credential, used to identify the account in URLs.
3
3
  attr_accessor :id
4
4
  # The financial institution this credential is for.
5
5
  attr_accessor :financial_institution
6
6
  # The accounts linked to this credential.
7
7
  attr_accessor :accounts
8
-
8
+
9
9
  # Initializes a +Wesabe::Credential+ and yields itself.
10
- #
10
+ #
11
11
  # @yieldparam [Wesabe::Credential] credential
12
12
  # The newly-created credential.
13
13
  def initialize
14
14
  yield self if block_given?
15
15
  end
16
-
16
+
17
+ # Starts a new sync job for this +Wesabe::Credential+.
18
+ #
19
+ # @return [Wesabe::Job]
20
+ # The job that was just started.
21
+ def start_job
22
+ associate(Wesabe::Job.from_xml(Hpricot::XML(post(:url => "/credentials/#{id}/jobs.xml")) / :job))
23
+ end
24
+
17
25
  # Returns a +Wesabe::Credential+ generated from Wesabe's API XML.
18
- #
26
+ #
19
27
  # @param [Hpricot::Element] xml
20
28
  # The <credential> element from the API.
21
- #
29
+ #
22
30
  # @return [Wesabe::Credential]
23
31
  # The newly-created credential populated by +xml+.
24
32
  def self.from_xml(xml)
@@ -31,4 +39,14 @@ class Wesabe::Credential
31
39
  end
32
40
  end
33
41
  end
42
+
43
+ def inspect
44
+ inspect_these :id, :financial_institution, :accounts
45
+ end
46
+
47
+ private
48
+
49
+ def associate(what)
50
+ all_or_one(super(what)) {|obj| obj.credential = self}
51
+ end
34
52
  end
@@ -1,22 +1,22 @@
1
- class Wesabe::Currency
1
+ class Wesabe::Currency < Wesabe::BaseModel
2
2
  attr_accessor :decimal_places, :symbol, :separator, :delimiter
3
-
3
+
4
4
  # Initializes a +Wesabe::Currency+ and yields itself.
5
- #
5
+ #
6
6
  # @yieldparam [Wesabe::Currency] currency
7
7
  # The newly-created currency.
8
8
  def initialize
9
9
  yield self if block_given?
10
10
  end
11
-
11
+
12
12
  alias_method :precision, :decimal_places
13
13
  alias_method :precision=, :decimal_places=
14
-
14
+
15
15
  # Returns a +Wesabe::Currency+ generated from Wesabe's API XML.
16
- #
16
+ #
17
17
  # @param [Hpricot::Element] xml
18
18
  # The <currency> element from the API.
19
- #
19
+ #
20
20
  # @return [Wesabe::Currency]
21
21
  # The newly-created currency populated by +xml+.
22
22
  def self.from_xml(xml)
@@ -27,4 +27,8 @@ class Wesabe::Currency
27
27
  currency.delimiter = xml[:delimiter].to_s
28
28
  end
29
29
  end
30
+
31
+ def inspect
32
+ inspect_these :symbol, :decimal_places, :separator, :delimiter
33
+ end
30
34
  end
@@ -1,4 +1,4 @@
1
- class Wesabe::FinancialInstitution
1
+ class Wesabe::FinancialInstitution < Wesabe::BaseModel
2
2
  # The id of this +FinancialInstitution+, as used in URLs.
3
3
  attr_accessor :id
4
4
  # The name of this +FinancialInstitution+ ("Bank of America").
@@ -7,20 +7,20 @@ class Wesabe::FinancialInstitution
7
7
  attr_accessor :login_url
8
8
  # The home url of this +FinancialInstitution+.
9
9
  attr_accessor :homepage_url
10
-
10
+
11
11
  # Initializes a +Wesabe::FinancialInstitution+ and yields itself.
12
- #
12
+ #
13
13
  # @yieldparam [Wesabe::FinancialInstitution] financial_institution
14
14
  # The newly-created financial institution.
15
15
  def initialize
16
16
  yield self if block_given?
17
17
  end
18
-
18
+
19
19
  # Returns a +Wesabe::FinancialInstitution+ generated from Wesabe's API XML.
20
- #
20
+ #
21
21
  # @param [Hpricot::Element] xml
22
22
  # The <financial-institution> element from the API.
23
- #
23
+ #
24
24
  # @return [Wesabe::FinancialInstitution]
25
25
  # The newly-created financial institution populated by +xml+.
26
26
  def self.from_xml(xml)
@@ -31,4 +31,8 @@ class Wesabe::FinancialInstitution
31
31
  fi.homepage_url = xml.at("homepage-url") && xml.at("homepage-url").inner_text
32
32
  end
33
33
  end
34
+
35
+ def inspect
36
+ inspect_these :id, :name
37
+ end
34
38
  end
data/lib/wesabe/job.rb ADDED
@@ -0,0 +1,97 @@
1
+ class Wesabe::Job < Wesabe::BaseModel
2
+ # The globally unique identifier for this job.
3
+ attr_accessor :id
4
+ # The status of this job (pending|successful|failed).
5
+ attr_accessor :status
6
+ # The result of this job, which gives more specific
7
+ # information for "pending" and "failed" statuses.
8
+ attr_accessor :result
9
+ # When this job was created.
10
+ attr_accessor :created_at
11
+ # The credential that this job belongs to.
12
+ attr_accessor :credential
13
+
14
+ # Initializes a +Wesabe::Job+ and yields itself.
15
+ #
16
+ # @yieldparam [Wesabe::Job] job
17
+ # The newly-created job.
18
+ def initialize
19
+ yield self if block_given?
20
+ end
21
+
22
+ # Reloads this job from the server, useful when polling for updates.
23
+ #
24
+ # job = credential.start_job
25
+ # until job.complete?
26
+ # print '.'
27
+ # job.reload
28
+ # sleep 1
29
+ # end
30
+ # puts
31
+ # puts "Job finished with status=#{job.status}, result=#{job.result}"
32
+ #
33
+ # @return [Wesabe::Job] Returns self.
34
+ def reload
35
+ replace(
36
+ Wesabe::Job.from_xml(
37
+ Hpricot.XML(
38
+ get(:url => "/credentials/#{credential.id}/jobs/#{id}.xml"))))
39
+ return self
40
+ end
41
+
42
+ # Determines whether this job is still running.
43
+ #
44
+ # @return [Boolean] Whether the job is still running.
45
+ def pending?
46
+ status == 'pending'
47
+ end
48
+
49
+ # Determines whether this job is finished.
50
+ #
51
+ # @return [Boolean] Whether the job is finished running.
52
+ def complete?
53
+ !pending?
54
+ end
55
+
56
+ # Determines whether this job is successful.
57
+ #
58
+ # @return [Boolean]
59
+ # Whether this job has completed and, if so, whether it was successful.
60
+ def successful?
61
+ status == 'successful'
62
+ end
63
+
64
+ # Determines whether this job failed.
65
+ #
66
+ # @return [Boolean]
67
+ # Whether this job has completed and, if so, whether it failed.
68
+ def failed?
69
+ status == 'failed'
70
+ end
71
+
72
+ # Returns a +Wesabe::Job+ generated from Wesabe's API XML.
73
+ #
74
+ # @param [Hpricot::Element] xml
75
+ # The <job> element from the API.
76
+ #
77
+ # @return [Wesabe::Job]
78
+ # The newly-created job populated by +xml+.
79
+ def self.from_xml(xml)
80
+ new do |job|
81
+ job.id = xml.at('id').inner_text
82
+ job.status = xml.at('status').inner_text
83
+ job.result = xml.at('result').inner_text
84
+ job.created_at = Time.parse(xml.at('created-at').inner_text)
85
+ end
86
+ end
87
+
88
+ def inspect
89
+ inspect_these :id, :status, :result, :created_at
90
+ end
91
+
92
+ private
93
+
94
+ def replace(with)
95
+ with.instance_variables.each {|ivar| instance_variable_set(ivar, with.instance_variable_get(ivar))}
96
+ end
97
+ end
@@ -1,30 +1,27 @@
1
1
  class Wesabe::Request
2
2
  attr_reader :url, :username, :password, :method, :proxy, :payload
3
-
3
+
4
+ DEFAULT_HEADERS = {
5
+ 'User-Agent' => "Wesabe-RubyGem/#{Wesabe::VERSION} (Ruby #{RUBY_VERSION}; #{RUBY_PLATFORM})"
6
+ }
7
+
4
8
  private
5
-
9
+
6
10
  def initialize(options=Hash.new)
7
11
  @url = options[:url] or raise ArgumentError, "Missing option 'url'"
8
12
  @username = options[:username] or raise ArgumentError, "Missing option 'username'"
9
- @password = options[:password] or raise ArgumentError, "Missing option 'password'"
13
+ @password = options[:password] or raise ArgumentError, "Missing option 'password'"
10
14
  @proxy = options[:proxy]
11
15
  @method = options[:method] || :get
12
16
  @payload = options[:payload]
13
17
  end
14
-
18
+
15
19
  # Returns a new Net::HTTP instance to connect to the Wesabe API.
16
- #
20
+ #
17
21
  # @return [Net::HTTP]
18
22
  # A connection object all ready to be used to communicate securely.
19
23
  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)
24
+ http = net_http_class.new(uri.host, uri.port)
28
25
  if uri.scheme == 'https'
29
26
  http.use_ssl = true
30
27
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER
@@ -32,91 +29,100 @@ class Wesabe::Request
32
29
  end
33
30
  http
34
31
  end
35
-
32
+
33
+ def net_http_class
34
+ if proxy
35
+ proxy_uri = URI.parse(proxy)
36
+ Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
37
+ else
38
+ Net::HTTP
39
+ end
40
+ end
41
+
36
42
  def uri
37
43
  URI.join(self.class.base_url, url)
38
44
  end
39
-
45
+
40
46
  def process_response(res)
41
47
  if %w[200 201 202].include?(res.code)
42
48
  res.body
43
49
  elsif %w[301 302 303].include?(res.code)
44
50
  url = res.header['Location']
45
-
51
+
46
52
  if url !~ /^http/
47
53
  uri = URI.parse(@url)
48
54
  uri.path = "/#{url}".squeeze('/')
49
55
  url = uri.to_s
50
56
  end
51
-
57
+
52
58
  raise Redirect, url
53
59
  elsif res.code == "401"
54
60
  raise Unauthorized
55
61
  elsif res.code == "404"
56
62
  raise ResourceNotFound
57
63
  else
58
- raise RequestFailed, url
64
+ raise RequestFailed, res
59
65
  end
60
66
  end
61
-
67
+
62
68
  public
63
-
69
+
64
70
  # Executes the request and returns the response.
65
- #
71
+ #
66
72
  # @return [String]
67
73
  # The response object for the request just made.
68
- #
74
+ #
69
75
  # @raise [Wesabe::ServerConnectionBroken]
70
76
  # If the connection with the server breaks.
71
- #
77
+ #
72
78
  # @raise [Timeout::Error]
73
79
  # If the request takes too long.
74
80
  def execute
75
81
  # set up the uri
76
82
  @username = uri.user if uri.user
77
83
  @password = uri.password if uri.password
78
-
84
+
79
85
  # set up the request
80
- req = Net::HTTP.const_get(method.to_s.capitalize).new(uri.request_uri)
86
+ req = Net::HTTP.const_get(method.to_s.capitalize).new(uri.request_uri, DEFAULT_HEADERS)
81
87
  req.basic_auth(username, password)
82
-
88
+
83
89
  net.start do |http|
84
90
  process_response http.request(req, payload || "")
85
91
  end
86
92
  end
87
-
93
+
88
94
  # Executes a request and returns the response.
89
- #
95
+ #
90
96
  # @param [String] options[:url]
91
97
  # The url relative to +Wesabe::Request.base_url+ to request (required).
92
- #
98
+ #
93
99
  # @param [String] options[:username]
94
100
  # The Wesabe username (required).
95
- #
101
+ #
96
102
  # @param [String] options[:password]
97
103
  # The Wesabe password (required).
98
- #
104
+ #
99
105
  # @param [String] options[:proxy]
100
106
  # The proxy url to use (optional).
101
- #
107
+ #
102
108
  # @param [String, Symbol] options[:method]
103
109
  # The HTTP method to use (defaults to +:get+).
104
- #
110
+ #
105
111
  # @param [String] options[:payload]
106
112
  # The post-body to use (defaults to an empty string).
107
- #
113
+ #
108
114
  # @return [Net::HTTPResponse]
109
115
  # The response object for the request just made.
110
- #
116
+ #
111
117
  # @raise [EOFError]
112
118
  # If the connection with the server breaks.
113
- #
119
+ #
114
120
  # @raise [Timeout::Error]
115
121
  # If the request takes too long.
116
122
  def self.execute(options=Hash.new)
117
123
  new(options).execute
118
124
  end
119
-
125
+
120
126
  def self.ca_file
121
127
  [File.expand_path("~/.wesabe"), File.join(File.dirname(__FILE__), '..')].each do |dir|
122
128
  file = File.join(dir, "cacert.pem")
@@ -124,21 +130,57 @@ class Wesabe::Request
124
130
  end
125
131
  raise "Unable to find a CA pem file to use for www.wesabe.com"
126
132
  end
127
-
133
+
128
134
  # Gets the base url for the Wesabe API.
129
135
  def self.base_url
130
136
  @base_url ||= "https://www.wesabe.com"
131
137
  end
132
-
138
+
133
139
  # Sets the base url for the Wesabe API.
134
140
  def self.base_url=(base_url)
135
141
  @base_url = base_url
136
142
  end
137
- end
138
143
 
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
144
+ class Exception < RuntimeError; end
145
+ class ServerBrokeConnection < Exception; end
146
+ class Redirect < Exception
147
+ attr_reader :location
148
+
149
+ def initialize(location)
150
+ @location = location
151
+ end
152
+
153
+ def message
154
+ "You've been redirected to #{location}"
155
+ end
156
+
157
+ def inspect
158
+ "#<#{self.class.name} Location=#{location.inspect}>"
159
+ end
160
+ end
161
+ class Unauthorized < Exception; end
162
+ class ResourceNotFound < Exception; end
163
+ class RequestFailed < Exception
164
+ attr_reader :response
165
+
166
+ def initialize(response=nil)
167
+ @response = response
168
+ end
169
+
170
+ def message
171
+ begin
172
+ (Hpricot.XML(response.body) / :error / :message).inner_text
173
+ rescue
174
+ response.body
175
+ end
176
+ end
177
+
178
+ def to_s
179
+ message
180
+ end
181
+
182
+ def inspect
183
+ "#<#{self.class.name} Status=#{response.code} Message=#{message.inspect}>"
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,35 @@
1
+ class Wesabe::Target < Wesabe::BaseModel
2
+ # The tag name
3
+ attr_accessor :tag
4
+ # This target's monthly limit ($)
5
+ attr_accessor :monthly_limit
6
+ # This target's amount remaining ($)
7
+ attr_accessor :amount_remaining
8
+
9
+ # Initializes a +Wesabe::Target+ and yields itself.
10
+ #
11
+ # @yieldparam [Wesabe::Target] Target
12
+ # The newly-created Target.
13
+ def initialize
14
+ yield self if block_given?
15
+ end
16
+
17
+ # Returns a +Wesabe::Target+ generated from Wesabe's API XML.
18
+ #
19
+ # @param [Hpricot::Element] xml
20
+ # The <Target> element from the API.
21
+ #
22
+ # @return [Wesabe::Target]
23
+ # The newly-created Target populated by +xml+.
24
+ def self.from_xml(xml)
25
+ new do |target|
26
+ target.tag = xml.at("tag").at("name").inner_text
27
+ target.monthly_limit = xml.at("monthly-limit").inner_text.to_f
28
+ target.amount_remaining = xml.at("amount-remaining").inner_text.to_f
29
+ end
30
+ end
31
+
32
+ def inspect
33
+ inspect_these :tag, :monthly_limit, :amount_remaining
34
+ end
35
+ end
@@ -0,0 +1,96 @@
1
+ # Encapsulates an upload and allows uploading files to wesabe.com.
2
+ class Wesabe::Upload < Wesabe::BaseModel
3
+ # The accounts this upload is associated with.
4
+ attr_accessor :accounts
5
+ # The financial institution this upload is associated with.
6
+ attr_accessor :financial_institution
7
+ # Whether this upload succeeded or failed, or +nil+ if it hasn't started.
8
+ attr_accessor :status
9
+ # The raw statement to post.
10
+ attr_accessor :statement
11
+
12
+ # Initializes a +Wesabe::Upload+ and yields itself.
13
+ #
14
+ # @yieldparam [Wesabe::Upload] upload
15
+ # The newly-created upload.
16
+ def initialize
17
+ yield self if block_given?
18
+ end
19
+
20
+ # Uploads the statement to Wesabe, raising on problems. It can raise
21
+ # anything that is raised by +Wesabe::Request#execute+ in addition to
22
+ # the list below.
23
+ #
24
+ # begin
25
+ # upload.upload!
26
+ # rescue Wesabe::Upload::StatementError => e
27
+ # $stderr.puts "The file you chose to upload couldn't be imported."
28
+ # $stderr.puts "This is what Wesabe said: #{e.message}"
29
+ # rescue Wesabe::Request::Exception
30
+ # $stderr.puts "There was a problem communicating with Wesabe."
31
+ # end
32
+ #
33
+ # @raise [Wesabe::Upload::StatementError]
34
+ # When the statement cannot be processed, this is returned (error code 5).
35
+ #
36
+ # @see Wesabe::Request#execute
37
+ def upload!
38
+ process_response do
39
+ post(:url => '/rest/upload/statement', :payload => pack_statement)
40
+ end
41
+ end
42
+
43
+ # Determines whether this upload succeeded or not.
44
+ #
45
+ # @return [Boolean]
46
+ # +true+ if +status+ is +"processed"+, +false+ otherwise.
47
+ def successful?
48
+ status == "processed"
49
+ end
50
+
51
+ # Determines whether this upload failed or not.
52
+ #
53
+ # @return [Boolean]
54
+ # +false+ if +status+ is +"processed"+, +true+ otherwise.
55
+ def failed?
56
+ !successful?
57
+ end
58
+
59
+ private
60
+
61
+ # Generates XML to upload to wesabe.com to create this +Upload+.
62
+ #
63
+ # @return [String]
64
+ # An XML document containing the relevant upload data.
65
+ def pack_statement
66
+ upload = self
67
+
68
+ Hpricot.build do
69
+ tag! :upload do
70
+ tag! :statement, :accttype => upload.accounts[0].type, :acctid => upload.accounts[0].number, :wesabe_id => upload.financial_institution.id do
71
+ text! upload.statement
72
+ end
73
+ end
74
+ end.inner_html
75
+ end
76
+
77
+ # Processes the response that is the result of +yield+ing.
78
+ #
79
+ # @see upload!
80
+ def process_response
81
+ self.status = nil
82
+ raw = yield
83
+ doc = Hpricot.XML(raw)
84
+ response = doc.at("response")
85
+ raise Exception, "There was an error processing the response: #{raw}" unless response
86
+ self.status = response["status"]
87
+
88
+ if !successful?
89
+ message = response.at("error>message")
90
+ raise StatementError, message && message.inner_text
91
+ end
92
+ end
93
+ end
94
+
95
+ class Wesabe::Upload::Exception < RuntimeError; end
96
+ class Wesabe::Upload::StatementError < Wesabe::Upload::Exception; end
@@ -0,0 +1,16 @@
1
+ module Wesabe::Util
2
+ extend self
3
+
4
+ # Yields +what+ or, if +what+ is an array, each element of +what+. It's
5
+ # sort of like an argument-agnostic +map+.
6
+ #
7
+ # @yieldparam [Object] element
8
+ # If +what+ is an +Array+, this is +what+. Otherwise it's elements of +what+.
9
+ #
10
+ # @return [Array<Object>,Object]
11
+ # If +what+ is an array, it acts like +what.map+. Otherwise +yield(what)+.
12
+ def all_or_one(what, &block)
13
+ result = Array(what).each(&block)
14
+ return what.is_a?(Array) ? result : result.first
15
+ end
16
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wesabe-wesabe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Donovan
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-08-04 00:00:00 -07:00
12
+ date: 2009-02-03 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -34,13 +34,19 @@ files:
34
34
  - LICENSE
35
35
  - README.markdown
36
36
  - Rakefile
37
+ - bin/console
37
38
  - lib/cacert.pem
38
39
  - lib/wesabe
39
40
  - lib/wesabe/account.rb
41
+ - lib/wesabe/base_model.rb
40
42
  - lib/wesabe/credential.rb
41
43
  - lib/wesabe/currency.rb
42
44
  - lib/wesabe/financial_institution.rb
45
+ - lib/wesabe/job.rb
43
46
  - lib/wesabe/request.rb
47
+ - lib/wesabe/target.rb
48
+ - lib/wesabe/upload.rb
49
+ - lib/wesabe/util.rb
44
50
  - lib/wesabe.rb
45
51
  has_rdoc: true
46
52
  homepage: https://www.wesabe.com/page/api