wesabe 0.0.3
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 +20 -0
- data/README.markdown +16 -0
- data/Rakefile +75 -0
- data/bin/console +64 -0
- data/lib/cacert.pem +19 -0
- data/lib/wesabe.rb +164 -0
- data/lib/wesabe/account.rb +62 -0
- data/lib/wesabe/base_model.rb +31 -0
- data/lib/wesabe/credential.rb +52 -0
- data/lib/wesabe/currency.rb +34 -0
- data/lib/wesabe/financial_institution.rb +38 -0
- data/lib/wesabe/job.rb +97 -0
- data/lib/wesabe/request.rb +186 -0
- data/lib/wesabe/target.rb +35 -0
- data/lib/wesabe/upload.rb +96 -0
- data/lib/wesabe/util.rb +16 -0
- metadata +80 -0
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,16 @@
|
|
1
|
+
wesabe
|
2
|
+
======
|
3
|
+
|
4
|
+
Access the Wesabe API. See the examples directory for usage examples.
|
5
|
+
|
6
|
+
Installation
|
7
|
+
------------
|
8
|
+
|
9
|
+
Make sure you have bundler installed, then run this:
|
10
|
+
|
11
|
+
$ gem bundle --cached
|
12
|
+
$ rake spec gem
|
13
|
+
|
14
|
+
Assuming the specs pass and the gem is generated, run this (with `sudo` if required):
|
15
|
+
|
16
|
+
$ gem install pkg/wesabe*.gem
|
data/Rakefile
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rubygems/specification'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'bundler'
|
5
|
+
require File.dirname(__FILE__)+'/lib/wesabe'
|
6
|
+
|
7
|
+
GEM = "wesabe"
|
8
|
+
GEM_VERSION = Wesabe::VERSION
|
9
|
+
AUTHOR = "Brian Donovan"
|
10
|
+
EMAIL = "brian@wesabe.com"
|
11
|
+
HOMEPAGE = "https://www.wesabe.com/page/api"
|
12
|
+
SUMMARY = "Wraps communication with the Wesabe API"
|
13
|
+
PROJECT = "wesabe"
|
14
|
+
|
15
|
+
SPEC = Gem::Specification.new do |s|
|
16
|
+
s.name = GEM
|
17
|
+
s.version = GEM_VERSION
|
18
|
+
s.platform = Gem::Platform::RUBY
|
19
|
+
s.has_rdoc = true
|
20
|
+
s.extra_rdoc_files = ["README.markdown", "LICENSE"]
|
21
|
+
s.summary = SUMMARY
|
22
|
+
s.description = s.summary
|
23
|
+
s.author = AUTHOR
|
24
|
+
s.email = EMAIL
|
25
|
+
s.homepage = HOMEPAGE
|
26
|
+
s.rubyforge_project = PROJECT
|
27
|
+
|
28
|
+
env = Bundler::Bundle.load.environment
|
29
|
+
env.dependencies.each do |dep|
|
30
|
+
s.add_dependency(dep.name, dep.version.to_s)
|
31
|
+
end
|
32
|
+
|
33
|
+
s.require_path = 'lib'
|
34
|
+
# s.bindir = "bin"
|
35
|
+
# s.executables = %w( wesabe )
|
36
|
+
s.files = %w(LICENSE README.markdown Rakefile) + Dir.glob("{bin,lib,specs}/**/*")
|
37
|
+
end
|
38
|
+
|
39
|
+
Rake::GemPackageTask.new(SPEC) do |pkg|
|
40
|
+
pkg.gem_spec = SPEC
|
41
|
+
end
|
42
|
+
|
43
|
+
require 'spec/rake/spectask'
|
44
|
+
desc "Run specs"
|
45
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
46
|
+
t.spec_opts << %w(-fs --color) << %w(-O spec/spec.opts)
|
47
|
+
t.spec_opts << '--loadby' << 'random'
|
48
|
+
t.spec_files = Dir["spec/**/*_spec.rb"]
|
49
|
+
end
|
50
|
+
|
51
|
+
desc "Generates the documentation for this project"
|
52
|
+
task :doc do
|
53
|
+
`yardoc 'lib/**/*.rb' 2>/dev/null`
|
54
|
+
`open doc/index.html 2>/dev/null`
|
55
|
+
end
|
56
|
+
|
57
|
+
namespace :wesabe do
|
58
|
+
desc "Downloads and installs an updated PEM file (requires openssl)"
|
59
|
+
task :update_pem do
|
60
|
+
# get the certificate
|
61
|
+
certs = `echo QUIT | openssl s_client -showcerts -connect www.wesabe.com:443 2>/dev/null`
|
62
|
+
pem = certs[/-----BEGIN CERTIFICATE-----(?:.|\n)+?-----END CERTIFICATE-----/, 0]
|
63
|
+
|
64
|
+
# write it to our pem file
|
65
|
+
dir = File.expand_path("~/.wesabe")
|
66
|
+
path = File.join(dir, "cacert.pem")
|
67
|
+
FileUtils.mkdir_p(dir)
|
68
|
+
File.open(path, 'w') do |file|
|
69
|
+
file.puts pem
|
70
|
+
end
|
71
|
+
puts "Wrote PEM file to #{path}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
task :default => :spec
|
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/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-----
|
data/lib/wesabe.rb
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rubygems'
|
5
|
+
rescue LoadError
|
6
|
+
# no rubygems, just keep going
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'hpricot'
|
10
|
+
require 'net/https'
|
11
|
+
require 'yaml'
|
12
|
+
require 'time'
|
13
|
+
|
14
|
+
# Provides an object-oriented interface to the Wesabe API.
|
15
|
+
class Wesabe
|
16
|
+
attr_accessor :username, :password
|
17
|
+
|
18
|
+
VERSION = '0.0.3'
|
19
|
+
|
20
|
+
# Initializes access to the Wesabe API with a certain user. All requests
|
21
|
+
# will be made in the context of this user.
|
22
|
+
#
|
23
|
+
# @param [String] username
|
24
|
+
# The username of an active Wesabe user.
|
25
|
+
#
|
26
|
+
# @param [String] password
|
27
|
+
# The password of an active Wesabe user.
|
28
|
+
def initialize(username, password)
|
29
|
+
self.username = username
|
30
|
+
self.password = password
|
31
|
+
end
|
32
|
+
|
33
|
+
# Fetches the user's accounts list from Wesabe or, if the list was already
|
34
|
+
# fetched, returns the cached result.
|
35
|
+
#
|
36
|
+
# pp wesabe.accounts
|
37
|
+
# [#<Wesabe::Account:0x106105c
|
38
|
+
# @balance=-393.42,
|
39
|
+
# @currency=
|
40
|
+
# #<Wesabe::Currency:0x104fdc0
|
41
|
+
# @decimal_places=2,
|
42
|
+
# @delimiter=",",
|
43
|
+
# @separator=".",
|
44
|
+
# @symbol="$">,
|
45
|
+
# @financial_institution=
|
46
|
+
# #<Wesabe::FinancialInstitution:0x104b054
|
47
|
+
# @homepage_url=nil,
|
48
|
+
# @id="us-003383",
|
49
|
+
# @login_url=nil,
|
50
|
+
# @name="American Express Card">,
|
51
|
+
# @id=4,
|
52
|
+
# @name="Amex Blue">]
|
53
|
+
#
|
54
|
+
# @return [Array<Wesabe::Account>]
|
55
|
+
# A list of the user's active accounts.
|
56
|
+
def accounts
|
57
|
+
@accounts ||= load_accounts
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns an account with the given id or +nil+ if the account is not found.
|
61
|
+
#
|
62
|
+
# wesabe.account(4).name # => "Amex Blue"
|
63
|
+
#
|
64
|
+
# @param [#to_s] id
|
65
|
+
# Something whose +to_s+ result matches the +to_s+ result of the account id.
|
66
|
+
#
|
67
|
+
# @return [Wesabe::Account, nil]
|
68
|
+
# The account whose user-scoped id is +id+ or +nil+ if there is no account
|
69
|
+
# with that +id+.
|
70
|
+
def account(id)
|
71
|
+
accounts.find {|a| a.id.to_s == id.to_s}
|
72
|
+
end
|
73
|
+
|
74
|
+
# Fetches the user's accounts list from Wesabe or, if the list was already
|
75
|
+
# fetched, returns the cached result.
|
76
|
+
#
|
77
|
+
# pp wesabe.credentials
|
78
|
+
# [#<Wesabe::Credential:0x10ae870
|
79
|
+
# @accounts=[],
|
80
|
+
# @financial_institution=
|
81
|
+
# #<Wesabe::FinancialInstitution:0x1091928
|
82
|
+
# @homepage_url=nil,
|
83
|
+
# @id="us-003383",
|
84
|
+
# @login_url=nil,
|
85
|
+
# @name="American Express Card">,
|
86
|
+
# @id=3>]
|
87
|
+
#
|
88
|
+
# @return [Array<Wesabe::Account>]
|
89
|
+
# A list of the user's active accounts.
|
90
|
+
def credentials
|
91
|
+
@credentials ||= load_credentials
|
92
|
+
end
|
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
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def load_accounts
|
121
|
+
process_accounts( Hpricot::XML( get(:url => '/accounts.xml') ) )
|
122
|
+
end
|
123
|
+
|
124
|
+
def process_accounts(xml)
|
125
|
+
associate((xml / :accounts / :account).map do |element|
|
126
|
+
Account.from_xml(element)
|
127
|
+
end)
|
128
|
+
end
|
129
|
+
|
130
|
+
def load_credentials
|
131
|
+
process_credentials( Hpricot::XML( get(:url => '/credentials.xml') ) )
|
132
|
+
end
|
133
|
+
|
134
|
+
def process_credentials(xml)
|
135
|
+
associate((xml / :credentials / :credential).map do |element|
|
136
|
+
Credential.from_xml(element)
|
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}
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
require 'wesabe/util'
|
156
|
+
require 'wesabe/request'
|
157
|
+
require 'wesabe/base_model'
|
158
|
+
require 'wesabe/account'
|
159
|
+
require 'wesabe/upload'
|
160
|
+
require 'wesabe/financial_institution'
|
161
|
+
require 'wesabe/currency'
|
162
|
+
require 'wesabe/credential'
|
163
|
+
require 'wesabe/job'
|
164
|
+
require 'wesabe/target'
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Encapsulates an account from Wesabe's API.
|
2
|
+
class Wesabe::Account < Wesabe::BaseModel
|
3
|
+
# The user-scoped account id, used to identify the account in URLs.
|
4
|
+
attr_accessor :id
|
5
|
+
# The application-scoped account id, used in upload
|
6
|
+
attr_accessor :number
|
7
|
+
# The user-provided account name ("Bank of America - Checking")
|
8
|
+
attr_accessor :name
|
9
|
+
# The account type ("Credit Card", "Savings" ...)
|
10
|
+
attr_accessor :type
|
11
|
+
# This account's balance or +nil+ if the account is a cash account.
|
12
|
+
attr_accessor :balance
|
13
|
+
# This account's currency.
|
14
|
+
attr_accessor :currency
|
15
|
+
# The financial institution this account is held at.
|
16
|
+
attr_accessor :financial_institution
|
17
|
+
|
18
|
+
# Initializes a +Wesabe::Account+ and yields itself.
|
19
|
+
#
|
20
|
+
# @yieldparam [Wesabe::Account] account
|
21
|
+
# The newly-created account.
|
22
|
+
def initialize
|
23
|
+
yield self if block_given?
|
24
|
+
end
|
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
|
+
|
38
|
+
# Returns a +Wesabe::Account+ generated from Wesabe's API XML.
|
39
|
+
#
|
40
|
+
# @param [Hpricot::Element] xml
|
41
|
+
# The <account> element from the API.
|
42
|
+
#
|
43
|
+
# @return [Wesabe::Account]
|
44
|
+
# The newly-created account populated by +xml+.
|
45
|
+
def self.from_xml(xml)
|
46
|
+
new do |account|
|
47
|
+
account.id = xml.at("id").inner_text.to_i
|
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")
|
51
|
+
balance = xml.at("current-balance")
|
52
|
+
account.balance = balance.inner_text.to_f if balance
|
53
|
+
account.currency = Wesabe::Currency.from_xml(xml.at("currency"))
|
54
|
+
fi = xml.at("financial-institution")
|
55
|
+
account.financial_institution = Wesabe::FinancialInstitution.from_xml(fi) if fi
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def inspect
|
60
|
+
inspect_these :id, :number, :type, :name, :balance, :financial_institution, :currency
|
61
|
+
end
|
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
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class Wesabe::Credential < Wesabe::BaseModel
|
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
|
+
# 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
|
+
|
25
|
+
# Returns a +Wesabe::Credential+ generated from Wesabe's API XML.
|
26
|
+
#
|
27
|
+
# @param [Hpricot::Element] xml
|
28
|
+
# The <credential> element from the API.
|
29
|
+
#
|
30
|
+
# @return [Wesabe::Credential]
|
31
|
+
# The newly-created credential populated by +xml+.
|
32
|
+
def self.from_xml(xml)
|
33
|
+
new do |cred|
|
34
|
+
cred.id = xml.at('id').inner_text.to_i
|
35
|
+
cred.financial_institution = Wesabe::FinancialInstitution.from_xml(
|
36
|
+
xml.children_of_type('financial-institution')[0])
|
37
|
+
cred.accounts = xml.search('accounts account').map do |account|
|
38
|
+
Wesabe::Account.from_xml(account)
|
39
|
+
end
|
40
|
+
end
|
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
|
52
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Wesabe::Currency < Wesabe::BaseModel
|
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
|
+
|
31
|
+
def inspect
|
32
|
+
inspect_these :symbol, :decimal_places, :separator, :delimiter
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class Wesabe::FinancialInstitution < Wesabe::BaseModel
|
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
|
+
|
35
|
+
def inspect
|
36
|
+
inspect_these :id, :name
|
37
|
+
end
|
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
|
@@ -0,0 +1,186 @@
|
|
1
|
+
class Wesabe::Request
|
2
|
+
attr_reader :url, :username, :password, :method, :proxy, :payload
|
3
|
+
|
4
|
+
DEFAULT_HEADERS = {
|
5
|
+
'User-Agent' => "Wesabe-RubyGem/#{Wesabe::VERSION} (Ruby #{RUBY_VERSION}; #{RUBY_PLATFORM})"
|
6
|
+
}
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def initialize(options=Hash.new)
|
11
|
+
@url = options[:url] or raise ArgumentError, "Missing option 'url'"
|
12
|
+
@username = options[:username] or raise ArgumentError, "Missing option 'username'"
|
13
|
+
@password = options[:password] or raise ArgumentError, "Missing option 'password'"
|
14
|
+
@proxy = options[:proxy]
|
15
|
+
@method = options[:method] || :get
|
16
|
+
@payload = options[:payload]
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns a new Net::HTTP instance to connect to the Wesabe API.
|
20
|
+
#
|
21
|
+
# @return [Net::HTTP]
|
22
|
+
# A connection object all ready to be used to communicate securely.
|
23
|
+
def net
|
24
|
+
http = net_http_class.new(uri.host, uri.port)
|
25
|
+
if uri.scheme == 'https'
|
26
|
+
http.use_ssl = true
|
27
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
28
|
+
http.ca_file = self.class.ca_file
|
29
|
+
end
|
30
|
+
http
|
31
|
+
end
|
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
|
+
|
42
|
+
def uri
|
43
|
+
URI.join(self.class.base_url, url)
|
44
|
+
end
|
45
|
+
|
46
|
+
def process_response(res)
|
47
|
+
if %w[200 201 202].include?(res.code)
|
48
|
+
res.body
|
49
|
+
elsif %w[301 302 303].include?(res.code)
|
50
|
+
url = res.header['Location']
|
51
|
+
|
52
|
+
if url !~ /^http/
|
53
|
+
uri = URI.parse(@url)
|
54
|
+
uri.path = "/#{url}".squeeze('/')
|
55
|
+
url = uri.to_s
|
56
|
+
end
|
57
|
+
|
58
|
+
raise Redirect, url
|
59
|
+
elsif res.code == "401"
|
60
|
+
raise Unauthorized
|
61
|
+
elsif res.code == "404"
|
62
|
+
raise ResourceNotFound
|
63
|
+
else
|
64
|
+
raise RequestFailed, res
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
public
|
69
|
+
|
70
|
+
# Executes the request and returns the response.
|
71
|
+
#
|
72
|
+
# @return [String]
|
73
|
+
# The response object for the request just made.
|
74
|
+
#
|
75
|
+
# @raise [Wesabe::ServerConnectionBroken]
|
76
|
+
# If the connection with the server breaks.
|
77
|
+
#
|
78
|
+
# @raise [Timeout::Error]
|
79
|
+
# If the request takes too long.
|
80
|
+
def execute
|
81
|
+
# set up the uri
|
82
|
+
@username = uri.user if uri.user
|
83
|
+
@password = uri.password if uri.password
|
84
|
+
|
85
|
+
# set up the request
|
86
|
+
req = Net::HTTP.const_get(method.to_s.capitalize).new(uri.request_uri, DEFAULT_HEADERS)
|
87
|
+
req.basic_auth(username, password)
|
88
|
+
|
89
|
+
net.start do |http|
|
90
|
+
process_response http.request(req, payload || "")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Executes a request and returns the response.
|
95
|
+
#
|
96
|
+
# @param [String] options[:url]
|
97
|
+
# The url relative to +Wesabe::Request.base_url+ to request (required).
|
98
|
+
#
|
99
|
+
# @param [String] options[:username]
|
100
|
+
# The Wesabe username (required).
|
101
|
+
#
|
102
|
+
# @param [String] options[:password]
|
103
|
+
# The Wesabe password (required).
|
104
|
+
#
|
105
|
+
# @param [String] options[:proxy]
|
106
|
+
# The proxy url to use (optional).
|
107
|
+
#
|
108
|
+
# @param [String, Symbol] options[:method]
|
109
|
+
# The HTTP method to use (defaults to +:get+).
|
110
|
+
#
|
111
|
+
# @param [String] options[:payload]
|
112
|
+
# The post-body to use (defaults to an empty string).
|
113
|
+
#
|
114
|
+
# @return [Net::HTTPResponse]
|
115
|
+
# The response object for the request just made.
|
116
|
+
#
|
117
|
+
# @raise [EOFError]
|
118
|
+
# If the connection with the server breaks.
|
119
|
+
#
|
120
|
+
# @raise [Timeout::Error]
|
121
|
+
# If the request takes too long.
|
122
|
+
def self.execute(options=Hash.new)
|
123
|
+
new(options).execute
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.ca_file
|
127
|
+
[File.expand_path("~/.wesabe"), File.join(File.dirname(__FILE__), '..')].each do |dir|
|
128
|
+
file = File.join(dir, "cacert.pem")
|
129
|
+
return file if File.exist?(file)
|
130
|
+
end
|
131
|
+
raise "Unable to find a CA pem file to use for www.wesabe.com"
|
132
|
+
end
|
133
|
+
|
134
|
+
# Gets the base url for the Wesabe API.
|
135
|
+
def self.base_url
|
136
|
+
@base_url ||= "https://www.wesabe.com"
|
137
|
+
end
|
138
|
+
|
139
|
+
# Sets the base url for the Wesabe API.
|
140
|
+
def self.base_url=(base_url)
|
141
|
+
@base_url = base_url
|
142
|
+
end
|
143
|
+
|
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
|
data/lib/wesabe/util.rb
ADDED
@@ -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
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wesabe
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brian Donovan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-19 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hpricot
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - "="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0.6"
|
24
|
+
version:
|
25
|
+
description: Wraps communication with the Wesabe API
|
26
|
+
email: brian@wesabe.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.markdown
|
33
|
+
- LICENSE
|
34
|
+
files:
|
35
|
+
- LICENSE
|
36
|
+
- README.markdown
|
37
|
+
- Rakefile
|
38
|
+
- bin/console
|
39
|
+
- lib/cacert.pem
|
40
|
+
- lib/wesabe/account.rb
|
41
|
+
- lib/wesabe/base_model.rb
|
42
|
+
- lib/wesabe/credential.rb
|
43
|
+
- lib/wesabe/currency.rb
|
44
|
+
- lib/wesabe/financial_institution.rb
|
45
|
+
- lib/wesabe/job.rb
|
46
|
+
- lib/wesabe/request.rb
|
47
|
+
- lib/wesabe/target.rb
|
48
|
+
- lib/wesabe/upload.rb
|
49
|
+
- lib/wesabe/util.rb
|
50
|
+
- lib/wesabe.rb
|
51
|
+
has_rdoc: true
|
52
|
+
homepage: https://www.wesabe.com/page/api
|
53
|
+
licenses: []
|
54
|
+
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
version:
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project: wesabe
|
75
|
+
rubygems_version: 1.3.5
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: Wraps communication with the Wesabe API
|
79
|
+
test_files: []
|
80
|
+
|