coyodlee 0.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ba6d4efb440932238cd8a92bf8f85c0d5e875bd6
4
+ data.tar.gz: 9359ce7258b12ae59b71e42b9168ef3930632940
5
+ SHA512:
6
+ metadata.gz: f1d0518db712d34968cd30e0038aff7d02f6e55f1b070e309fce5ad9c2aa6fd2fd624fa192a17cef982757992e1ea83b56757051987f785450cd7406e9e6ea6f
7
+ data.tar.gz: 8452e4f1ed34d277b49d7d344c0ee7d7c4b58654eab8eb53ddbe70c24f7ea53138f8c1c27b576c5674540166345d7d299cef9b4d493a79a3f680e16932b9a206
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ .env
12
+
13
+ .rbenv-gemsets
14
+ .gems
15
+ *.swp
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.3
4
+ before_install: gem install bundler -v 1.11.2
5
+ env:
6
+ global:
7
+ - secure: uUfBk39flmbyd21ayyqVP4pm1FS7h18joIIAF+547CV6+6Ke7BG3DxR+ivIDG1MVXZNErQ+U5zRAcnG0kWYwbJ1bcFksJYpxR6hN+6MY6SDW7G2oN/Ono6DqHE/tnzrL+/Ax5ui0Yw/W6258tlgm1dj3K28/lmmoK1qbyNqeL5bf+PoggNSIk/DQIHotJsWR4QEIXQM077+PGRfm9TIBE9hU77Cphmu0jPk9NTUHdQHGo6SwDfl2X5CO1Qfp43cn0X7Giir3+tHWZZo8bdDmhDKiTVrLpCFvD6tXHzvU5zhyElUAM4jdPH+jXpmLSK+OwEJispCxHgt6Zd9RELEYMRidlInuxrqYrwikc55shuiIHRq7Sf0JmB2q+sj70+1bNSrJlfPZ6GK3+Ye+HIEGTf8K4B6PUZDrEsPb4fJiaq1kjB5Z5+s3olVqnl0oVIVWk6Pw6qhn0UgVwFMP26eIua7lGo1g9+DqhtZ9WFhBcLDe5f10M7YfaXgDhIW0yXz7Emqip+llpMg//5R2sSQy48M6jjMX4wDcXVrnYFSZAtUno6unxddctdqXQHDpwr60uEyeyb3j5xJp7LUKs7CkOGrfyPqmljD83Ibayh/yBXelme2l6esElWOUVUScuBint5AiPDrnhbApdUGTYbGDPzB4Et3wwfd4ddePTh8z16M=
8
+ - secure: t/ro3jAcnKuJCXqzvbxrukbcwD02+CZIUS6tUXBWexxowbvpC+el60ZLP+q9VD40hBy8SwFf7ukdmtunNRXBUmhPb/NhyBRmtcaHlrvReYV1d8AJX/tGtcuv4OirfcXj45u0mEOtUdDTYYvbbv3cecoKPLtjaZcLct2mwoTh0tcTei0qs/4fZNOm5yGrIVIO5lxyrnGTmWKbSceK9FAwz7FiZ108DrG8QApQNmpXS/5P6G4cnpsilRDXt0p4zMdi/QOgPw7dfANU5K1Hwg7cryBsqk4JwN0mJ6AWTkv4A8THuVAmDzojyqH0Su151HecNu5GOfsXiHzavkvQTdO+s00AkqkyZkzCq8KDx8DxeK363iw/gSVztWv3Q77oDhZ6UyOkPgpzYPbMrl9d2+Z/fC2CrSiVKCLnqjdSbSdhOEFgmc3LTSM7kuGEwtwMGPARmjrC7NVynol4Ji6YWAopO/B79dNNBUSAZRt8yJReN26sIZ3bTqqqIhqzZWvPWWRtYkS7TMu3djqb0ey/0rPI35i7esiIrD26XT+invuehZhRJ+qOikscVjwKSw5wVGoWODOELcL9lF5zccQQDp8xtPvQN8818wekGbBibnrnQ98kzVpR7C1Dl0tEM+ZcdfReC/6a5xL8x1/pS+lGvxN5xqMQFbejc6rKTWJjplUkPY4=
9
+ - secure: emoFvYI3rOkbj8YOOgmLkT7WQtSn/f/D5JTPZ1nl41P8/xaQLGFaa9aSYV3AqsZOER+hDEplek1DFPZNBvgTUND09hpS6qDa9oMGxzJlMxokZIMdHfFBMsb9eXdsvHnG6vilejQ7ic9EfISv4SqwoZmr6KMGBX5o17REtTHwgzuV/aMozhJi5agjWUuLhGFPPTg/fhqLqBZOXCiQjkdhtDkC4PX8FNnwzXWnjn/CuyTZvhapFOftfh76n72FLhMfCpOi3FZnkFcpSvs38uFv7RS9d0ci0XuNFzrhaUbr8WsB+nz1rGKdRUP9RhaU2OJPWmGcGfBeYApG8DhE6dMUJK3k+1X4aPkDjEYm+/xWTjD/2jbRjqvLJJ4B9S75GRz6O3zV4KMc9GkgQWXx+MzCkkc72TsL3XjSCxeqQMvQ8mjl7GFgfeZw5UFiMaCcQzDHECphBAt990rGhwXq0B2eEluOtHo2Kkh7FGvPkCOiMjTdlgNpe2BAh+sf+Bn+/rZ25iFIAWF8IDlfPJc+QiHLtZntwI0afzjWs+2TjDFj3PQ+Shx/Pshk8PXdaPUEP0t7HyLpiWOuyeJaG8O96W/cX51fopmDhlRaVREcpAaJwZ9AXoYSHGXFL9+027L3obG4PYyBF1goBcoe1BZKsLRjGxSftKPmXgCQnBQR3FPYgtM=
10
+ - secure: hrcrel8j12haIsL+8R01+Pc8wNzxB6y2N4jjb7Z1iW8DA6ytZMx+y1e0Ouz+db3k2RxdYOGfT1c0vtJeq6YKfZDuNuTVcOM4nGNfj8Pfe8Ip+faQ8yAuJpqZdq2EnryweUx9MMcCwulfcDiQnYncVeLtm0FByBqE6HjJYPVGFBnzNewAl0JxzpIVLmNXNAxkcBeN/ArKwyAgogUIfL2TRcnY54J8UQSIS2JILRPOkcdkahDQCa02QTaAtHiFtCd4/1P093dbhZIL72nKoGY15m2OWUGf286q9tDydzcZr0vMpoZNugO9I3f4kg4Q9XuIden5U2iLCVl5b9unbCvZsI0s3U4gFCCQhxvZaJRBAvvXzvjPOiBmq0hjwMovqb+nMaLdgLo1JtuZg2b5eZwbIimcGXZVuO+ZonPJxPckk6oyS5qy43IUWv9dMuTbKXiVYLKfoeoOev4c07y9HGyZ4nzkXe1zaBWiPmjjBgcB8zbh8n4jXtcdthlv9LAkHyF53UiUgPlvUll5niPh5bXR5vQBrMIoKzitPKjyUgQCEaugt/tGbsg0zZ1LEeNaeCmuHVfhY7swBMIUQBJ+JNo5ZIkSOcQeDA66oPzJ++zwI2oo30AZNsOhCaZMqTdi1CK0NkDGy8oMtO2qdaqDqWHFgq33e9xrTyQdD6Ihq+hK6HQ=
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at daniel.dyba@pnmac.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'yajl-ruby', require: 'yajl'
4
+
5
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Daniel Dyba
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # Coyodlee
2
+ [![Build Status](https://travis-ci.com/pennymac/coyodlee.svg?token=KvBtKQs616ELBMQxp2n7&branch=master)](https://travis-ci.com/pennymac/coyodlee)
3
+ [![Maintainability](https://api.codeclimate.com/v1/badges/f36b069069540b196fbd/maintainability)](https://codeclimate.com/github/pennymac/coyodlee/maintainability)
4
+
5
+ ## Setup
6
+
7
+ In sandbox mode, point the ```base_url``` to ```https://developer.api.yodlee.com/ysl/restserver/v1```.
8
+
9
+ Export the following environment variables:
10
+
11
+ <table>
12
+ <tr>
13
+ <td><strong>Environment Variables</strong></td>
14
+ <td><strong>Description</td>
15
+ </tr>
16
+ <tr>
17
+ <td>YODLEE_COBRAND_LOGIN</td>
18
+ <td>The Yodlee cobrand login</td>
19
+ <tr/>
20
+ <tr>
21
+ <td>YODLEE_COBRAND_PASSWORD</td>
22
+ <td>The Yodlee cobrand password</td>
23
+ <tr/>
24
+ </table>
25
+
26
+ ## Installation
27
+
28
+ Add this line to your application's Gemfile:
29
+
30
+ ```ruby
31
+ gem 'coyodlee'
32
+ ```
33
+
34
+ And then execute:
35
+
36
+ $ bundle
37
+
38
+ Or install it yourself as:
39
+
40
+ $ gem install coyodlee
41
+
42
+ ## Usage
43
+
44
+ This API exposes a ```setup``` method:
45
+
46
+ ``` ruby
47
+ require 'coyodlee'
48
+
49
+ Coyodlee.setup do |config|
50
+ config.base_url = ENV['YODLEE_BASE_URL']
51
+ config.cobrand_login = ENV['YODLEE_COBRAND_LOGIN']
52
+ config.cobrand_password = ENV['YODLEE_COBRAND_PASSWORD']
53
+ end
54
+
55
+ require 'coyodlee/sessions'
56
+
57
+ cob_session = Coyodlee::Sessions::CobrandSession.new
58
+ cob_session.login login_name: Coyodlee.cobrand_login, password: Coyodlee.cobrand_password
59
+
60
+ user_session = Coyodlee::Sessions::UserSession.new(cobrand_session: cob_session)
61
+ user_session.login login_name: 'yodlee-test-user-login', password: 'yodlee-test-user-password'
62
+
63
+ require 'coyodlee/client'
64
+
65
+ client = Coyodlee::Client.new(user_session)
66
+ resp = client.get_accounts
67
+
68
+ require 'json'
69
+
70
+ # Print all accounts for the test user
71
+ puts JSON.parse(resp.body)
72
+ ```
73
+
74
+ ## Testing
75
+
76
+ To run tests: ```bundle exec rake test```.
77
+
78
+ All tests are written in Minitest and HTTP requests are recorded using VCR.
79
+
80
+ ## Development
81
+
82
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
83
+
84
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
85
+
86
+ ## Why another Yodlee Ruby library?
87
+
88
+ * This library supports Yodlee's newer RESTful API. [yodlee-icious](https://github.com/liftforward/yodlee-icious) supports the older RESTful wrapper over the SOAP API.
89
+ * This library aims to be comprehensive in its feature set. [yodlee](https://github.com/aasmith/yodlee) only supports retrieving data from Yodlee MoneyCenter.
90
+
91
+ ## Contributing
92
+
93
+ Bug reports and pull requests are welcome on GitHub at https://github.com/pennymac/coyodlee. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
94
+
95
+ ## License
96
+
97
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
98
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "coyodlee"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/coyodlee.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'coyodlee/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "coyodlee"
8
+ spec.version = Coyodlee::VERSION
9
+ spec.authors = ["Daniel Dyba"]
10
+ spec.email = ["daniel.dyba@gmail.com"]
11
+
12
+ spec.summary = %q{A Ruby wrapper client for Envestnet's Yodlee API}
13
+ spec.description = %q{A Ruby wrapper client for Envestnet's Yodlee API. For details about the API endpoints, sign in to https://developer.yodlee.com}
14
+ spec.homepage = "https://github.com/pennymac/coyodlee"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "rest-client", "~> 2.0"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.11"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "minitest", "~> 5.0"
27
+ spec.add_development_dependency "vcr", "~> 3.0"
28
+ spec.add_development_dependency "webmock", '~> 3.1'
29
+ spec.add_development_dependency "yard", '~> 0.9'
30
+ spec.add_development_dependency "activesupport", '5.1'
31
+ spec.add_development_dependency "travis", '~> 1.8'
32
+ end
@@ -0,0 +1,550 @@
1
+ require_relative 'utils'
2
+
3
+ module Coyodlee
4
+ class Client
5
+ include Utils
6
+
7
+ def initialize(session)
8
+ @session = session
9
+ end
10
+
11
+ def get_accounts
12
+ HttpWrapper.get(
13
+ url: build_url('/accounts'),
14
+ headers: {
15
+ authorization: @session.auth_header,
16
+ accept: :json
17
+ }
18
+ )
19
+ end
20
+
21
+ def get_account_details(account_id:, container:)
22
+ params = [:container]
23
+ .map { |sym| uncapitalized_camelize sym.to_s }
24
+ .zip([container])
25
+ .reject { |query, value| value.to_s.strip.empty? }
26
+ .to_h
27
+ HttpWrapper.get(
28
+ url: build_url("/accounts/#{account_id}"),
29
+ params: params,
30
+ headers: {
31
+ authorization: @session.auth_header,
32
+ accept: :json
33
+ }
34
+ )
35
+ end
36
+
37
+ def update_account(account_id:, body:)
38
+ HttpWrapper.put(
39
+ url: build_url("/accounts/#{account_id}"),
40
+ body: body,
41
+ headers: {
42
+ authorization: @session.auth_header,
43
+ accept: :json,
44
+ content_type: :json
45
+ }
46
+ )
47
+ end
48
+
49
+ def delete_account(id:)
50
+ HttpWrapper.delete(
51
+ url: build_url("/accounts/#{account_id}"),
52
+ headers: {
53
+ authorization: @session.auth_header,
54
+ accept: :json
55
+ }
56
+ )
57
+ end
58
+
59
+ def add_manual_account(body:)
60
+ HttpWrapper.post(
61
+ url: build_url("/accounts"),
62
+ body: body,
63
+ headers: {
64
+ authorization: @session.auth_header,
65
+ accept: :json
66
+ }
67
+ )
68
+ end
69
+
70
+ def get_investment_options(_include: '', account_recon_type: '', account_id: '')
71
+ params = [:_include, :account_recon_type, :account_id]
72
+ .map { |sym| uncapitalized_camelize sym.to_s }
73
+ .map { |sym| sub_underscore sym.to_s }
74
+ .zip([_include, account_recon_type, account_id])
75
+ .reject { |query, value| value.to_s.strip.empty? }
76
+ .to_h
77
+ HttpWrapper.get(
78
+ url: build_url('/accounts/investmentPlan/investmentOptions'),
79
+ params: params,
80
+ headers: {
81
+ authorization: @session.auth_header,
82
+ accept: :json
83
+ }
84
+ )
85
+ end
86
+
87
+ def get_historical_balances(
88
+ include_cf: '',
89
+ account_id: '',
90
+ from_date: '',
91
+ to_date: '',
92
+ interval: '',
93
+ account_recon_type: '',
94
+ skip: '',
95
+ top: ''
96
+ )
97
+ params = [:include_cf, :account_id, :from_date, :to_date, :interval, :account_recon_type, :skip, :top]
98
+ .map { |sym| uncapitalized_camelize sym.to_s }
99
+ .zip([include_cf, account_id, from_date, to_date, interval, account_recon_type, skip, top])
100
+ .reject { |query, value| value.to_s.strip.empty? }
101
+ .to_h
102
+ HttpWrapper.get(
103
+ url: build_url('/accounts/historicalBalances'),
104
+ params: params,
105
+ headers: {
106
+ authorization: @session.auth_header,
107
+ accept: :json
108
+ }
109
+ )
110
+ end
111
+
112
+ def get_holdings(
113
+ _include: '',
114
+ account_id: '',
115
+ provider_account_id: '',
116
+ asset_classification__asset_classification_type: '',
117
+ classification_value: '',
118
+ account_recon_type: ''
119
+ )
120
+ params = [:_include, :account_id, :provider_account_id, :asset_classification__asset_classification_type, :classification_value, :account_recon_type]
121
+ .map { |sym| uncapitalized_camelize sym.to_s }
122
+ .map { |sym| sub_underscore sym.to_s }
123
+ .map { |sym| sub_double_underscore sym.to_s }
124
+ .zip([_include, account_id, provider_account_id, asset_classification__asset_classification_type, classification_value, account_recon_type])
125
+ .reject { |query, value| value.to_s.strip.empty? }
126
+ .to_h
127
+ HttpWrapper.get(
128
+ url: build_url('/holdings'),
129
+ params: params,
130
+ headers: {
131
+ authorization: @session.auth_header,
132
+ accept: :json
133
+ }
134
+ )
135
+ end
136
+
137
+ def get_extended_securities_info(holding_id: '')
138
+ params = [:holding_id]
139
+ .map { |sym| uncapitalized_camelize sym.to_s }
140
+ .zip([holding_id])
141
+ .reject { |query, value| value.to_s.strip.empty? }
142
+ .to_h
143
+ HttpWrapper.get(
144
+ url: build_url('/holdings/securities'),
145
+ params: params,
146
+ headers: {
147
+ authorization: @session.auth_header,
148
+ accept: :json
149
+ }
150
+ )
151
+ end
152
+
153
+ def get_holding_type_list
154
+ HttpWrapper.get(
155
+ url: build_url('/holdings/holdingTypeList'),
156
+ headers: {
157
+ authorization: @session.auth_header,
158
+ accept: :json
159
+ }
160
+ )
161
+ end
162
+
163
+ def get_asset_classification_list
164
+ HttpWrapper.get(
165
+ url: build_url('/holdings/assetClassificationList'),
166
+ headers: {
167
+ authorization: @session.auth_header,
168
+ accept: :json
169
+ }
170
+ )
171
+ end
172
+
173
+ def get_provider_details(provider_id:, provider_account_id:)
174
+ params = [:provider_account_id]
175
+ .map { |sym| uncapitalized_camelize sym.to_s }
176
+ .zip([provider_account_id])
177
+ .reject { |query, value| value.to_s.strip.empty? }
178
+ .to_h
179
+ HttpWrapper.post(
180
+ url: build_url("/providers/#{provider_id}"),
181
+ params: params,
182
+ headers: {
183
+ authorization: @session.auth_header,
184
+ accept: :json
185
+ }
186
+ )
187
+ end
188
+
189
+ def get_providers(
190
+ priority: '',
191
+ capability: '',
192
+ additional_data_set: '',
193
+ name: '',
194
+ skip: '',
195
+ top: '',
196
+ classification: ''
197
+ )
198
+ params = [:priority, :capability, :additional_data_set, :name, :skip, :top, :classification]
199
+ .map { |sym| uncapitalized_camelize sym.to_s }
200
+ .map { |sym| sub_underscore sym.to_s }
201
+ .map { |sym| sub_double_underscore sym.to_s }
202
+ .zip([priority, capability, additional_data_set, name, skip, top, classification])
203
+ .reject { |query, value| value.to_s.strip.empty? }
204
+ .to_h
205
+ HttpWrapper.get(
206
+ url: build_url('/providers'),
207
+ params: params,
208
+ headers: {
209
+ authorization: @session.auth_header,
210
+ accept: :json
211
+ }
212
+ )
213
+ end
214
+
215
+ def verify_provider_account(body:)
216
+ HttpWrapper.put(
217
+ url: build_url('/providerAccounts/verification'),
218
+ body: body,
219
+ headers: {
220
+ authorization: @session.auth_header,
221
+ accept: :json
222
+ }
223
+ )
224
+ end
225
+
226
+ def get_verification_status(provider_account_id:)
227
+ HttpWrapper.get(
228
+ url: build_url("/providerAccounts/verification/#{provider_account_id}"),
229
+ body: body,
230
+ headers: {
231
+ authorization: @session.auth_header,
232
+ accept: :json
233
+ }
234
+ )
235
+ end
236
+
237
+ def update_provider_account(body:)
238
+ HttpWrapper.put(
239
+ url: build_url('/provideAccounts'),
240
+ body: body,
241
+ headers: {
242
+ authorization: @session.auth_header,
243
+ accept: :json
244
+ }
245
+ )
246
+ end
247
+
248
+ def delete_provider_account(provider_account_id:)
249
+ HttpWrapper.delete(
250
+ url: build_url("/providerAccounts/#{provider_account_id}"),
251
+ headers: {
252
+ authorization: @session.auth_header,
253
+ accept: :json
254
+ }
255
+ )
256
+ end
257
+
258
+ def get_provider_account_details(provider_account_id:, _include: '')
259
+ params = [:provider_account_id, :_include]
260
+ .map { |sym| uncapitalized_camelize sym.to_s }
261
+ .zip([provider_account_id, _include])
262
+ .reject { |query, value| value.to_s.strip.empty? }
263
+ .to_h
264
+ HttpWrapper.get(
265
+ url: build_url("/providerAccounts/#{provider_account_id}"),
266
+ params: params,
267
+ headers: {
268
+ authorization: @session.auth_header,
269
+ accept: :json
270
+ }
271
+ )
272
+ end
273
+
274
+ def get_provider_accounts
275
+ HttpWrapper.get(
276
+ url: build_url('/providerAccounts'),
277
+ headers: {
278
+ authorization: @session.auth_header,
279
+ accept: :json
280
+ }
281
+ )
282
+ end
283
+
284
+ def add_provider_account(provider_id:, body:)
285
+ params = [:provider_id]
286
+ .map { |sym| uncapitalized_camelize sym.to_s }
287
+ .zip([provider_id])
288
+ .reject { |query, value| value.to_s.strip.empty? }
289
+ .to_h
290
+ HttpWrapper.post(
291
+ url: build_url('/providerAccounts'),
292
+ params: params,
293
+ body: body,
294
+ headers: {
295
+ authorization: @session.auth_header,
296
+ accept: :json
297
+ }
298
+ )
299
+ end
300
+
301
+ def get_transactions_count(params={})
302
+ params = params
303
+ .map { |sym, val| [uncapitalized_camelize(sym.to_s), val] }
304
+ .reject { |_, value| value.to_s.strip.empty? }
305
+ .to_h
306
+ HttpWrapper.get(
307
+ url: build_url('/transactions/count'),
308
+ params: params,
309
+ headers: {
310
+ authorization: @session.auth_header,
311
+ accept: :json
312
+ }
313
+ )
314
+ end
315
+
316
+ def get_transaction_categorization_rules
317
+ HttpWrapper.get(
318
+ url: build_url('/transactions/categories/rules'),
319
+ headers: {
320
+ authorization: @session.auth_header,
321
+ accept: :json
322
+ }
323
+ )
324
+ end
325
+
326
+ def create_transaction_categorization_rule(body:)
327
+ HttpWrapper.post(
328
+ url: build_url('/transactions/categories/rules'),
329
+ body: body,
330
+ headers: {
331
+ authorization: @session.auth_header,
332
+ accept: :json
333
+ }
334
+ )
335
+ end
336
+
337
+ def update_transaction_categorization_rule(rule_id:, body:)
338
+ HttpWrapper.put(
339
+ url: build_url("/transactions/categories/rules/#{rule_id}"),
340
+ body: body,
341
+ headers: {
342
+ authorization: @session.auth_header,
343
+ accept: :json
344
+ }
345
+ )
346
+ end
347
+
348
+ def delete_transaction_categorization_rule(rule_id:)
349
+ HttpWrapper.delete(
350
+ url: build_url("/transactions/categories/rules/#{rule_id}"),
351
+ headers: {
352
+ authorization: @session.auth_header,
353
+ accept: :json
354
+ }
355
+ )
356
+ end
357
+
358
+ def run_transaction_categorization_rule(rule_id:)
359
+ params = { action: 'run'}
360
+ HttpWrapper.post(
361
+ url: build_url("/transactions/categories/rules/#{rule_id}"),
362
+ params: params,
363
+ headers: {
364
+ authorization: @session.auth_header,
365
+ accept: :json
366
+ }
367
+ )
368
+ end
369
+
370
+ def run_all_transaction_categorization_rules
371
+ params = { action: 'run'}
372
+ HttpWrapper.post(
373
+ url: build_url('/transactions/categories/rules'),
374
+ params: params,
375
+ headers: {
376
+ authorization: @session.auth_header,
377
+ accept: :json
378
+ }
379
+ )
380
+ end
381
+
382
+ def delete_transaction_category(category_id:)
383
+ HttpWrapper.delete(
384
+ url: build_url("/transactions/categories/#{category_id}"),
385
+ headers: {
386
+ authorization: @session.auth_header,
387
+ accept: :json
388
+ }
389
+ )
390
+ end
391
+
392
+ def update_transaction_category(category_id:, container:, category_name:, transaction_id:)
393
+ params = [:container, :category_name, :transaction_id]
394
+ .map { |sym, val| [uncapitalized_camelize(sym.to_s), val] }
395
+ .zip([container, category_name, transaction_id])
396
+ .reject { |_, value| value.to_s.strip.empty? }
397
+ .to_h
398
+ HttpWrapper.put(
399
+ url: build_url("/transactions/categories/#{category_id}"),
400
+ params: params,
401
+ headers: {
402
+ authorization: @session.auth_header,
403
+ accept: :json
404
+ }
405
+ )
406
+ end
407
+
408
+ def get_transaction_category_list
409
+ HttpWrapper.get(
410
+ url: build_url('/transactions/categories'),
411
+ headers: {
412
+ authorization: @session.auth_header,
413
+ accept: :json
414
+ }
415
+ )
416
+ end
417
+
418
+ def create_category(body:)
419
+ HttpWrapper.post(
420
+ url: build_url('/transactions/categories'),
421
+ body: body,
422
+ headers: {
423
+ authorization: @session.auth_header,
424
+ accept: :json
425
+ }
426
+ )
427
+ end
428
+
429
+ def update_category(body:)
430
+ HttpWrapper.put(
431
+ url: build_url('/transactions/categories'),
432
+ body: body,
433
+ headers: {
434
+ authorization: @session.auth_header,
435
+ accept: :json
436
+ }
437
+ )
438
+ end
439
+
440
+ def get_transactions(params={})
441
+ params = params
442
+ .map { |sym, val| [uncapitalized_camelize(sym.to_s), val] }
443
+ .reject { |_, value| value.to_s.strip.empty? }
444
+ .to_h
445
+ HttpWrapper.get(
446
+ url: build_url('/transactions'),
447
+ params: params,
448
+ headers: {
449
+ authorization: @session.auth_header,
450
+ accept: :json
451
+ }
452
+ )
453
+ end
454
+
455
+ def get_statements(params={})
456
+ params = params
457
+ .map { |sym, val| [uncapitalized_camelize(sym.to_s), val] }
458
+ .reject { |_, value| value.to_s.strip.empty? }
459
+ .to_h
460
+ HttpWrapper.get(
461
+ url: build_url('/statements'),
462
+ params: params,
463
+ headers: {
464
+ authorization: @session.auth_header,
465
+ accept: :json
466
+ }
467
+ )
468
+ end
469
+
470
+ def get_transaction_summary(group_by:, params: {})
471
+ params = params
472
+ .merge({ group_by: group_by })
473
+ .map { |sym, val| [uncapitalized_camelize(sym.to_s), val] }
474
+ .reject { |_, value| value.to_s.strip.empty? }
475
+ .to_h
476
+ HttpWrapper.get(
477
+ url: build_url('/derived/transactionSummary'),
478
+ params: params,
479
+ headers: {
480
+ authorization: @session.auth_header,
481
+ accept: :json
482
+ }
483
+ )
484
+ end
485
+
486
+ def get_holding_summary(params={})
487
+ params = params
488
+ .map { |sym, val| [uncapitalized_camelize(sym.to_s), val] }
489
+ .map { |sym, val| [sub_underscore(sym.to_s), val] }
490
+ .reject { |_, value| value.to_s.strip.empty? }
491
+ .to_h
492
+ HttpWrapper.get(
493
+ url: build_url('/derived/holdingSummary'),
494
+ params: params,
495
+ headers: {
496
+ authorization: @session.auth_header,
497
+ accept: :json
498
+ }
499
+ )
500
+ end
501
+
502
+ def get_networth_summary(params={})
503
+ params = params
504
+ .map { |sym, val| [uncapitalized_camelize(sym.to_s), val] }
505
+ .map { |sym, val| [sub_underscore(sym.to_s), val] }
506
+ .reject { |_, value| value.to_s.strip.empty? }
507
+ .to_h
508
+ HttpWrapper.get(
509
+ url: build_url('/derived/networth'),
510
+ params: params,
511
+ headers: {
512
+ authorization: @session.auth_header,
513
+ accept: :json
514
+ }
515
+ )
516
+ end
517
+
518
+ def get_extract_events(event_name:, from_date:, to_date:)
519
+ params = [:event_name, :from_date, :to_date]
520
+ .map { |sym| uncapitalized_camelize sym.to_s }
521
+ .zip([event_name, from_date, to_date])
522
+ .reject { |_, value| value.to_s.strip.empty? }
523
+ .to_h
524
+ HttpWrapper.get(
525
+ url: build_url('/dataExtracts/events'),
526
+ params: params,
527
+ headers: {
528
+ authorization: @session.auth_header,
529
+ accept: :json
530
+ }
531
+ )
532
+ end
533
+
534
+ def get_user_data(login_name:, from_date:, to_date:)
535
+ params = [:login_name, :from_date, :to_date]
536
+ .map { |sym| uncapitalized_camelize sym.to_s }
537
+ .zip([login_name, from_date, to_date])
538
+ .reject { |_, value| value.to_s.strip.empty? }
539
+ .to_h
540
+ HttpWrapper.get(
541
+ url: build_url('/dataExtracts/userData'),
542
+ params: params,
543
+ headers: {
544
+ authorization: @session.auth_header,
545
+ accept: :json
546
+ }
547
+ )
548
+ end
549
+ end
550
+ end
@@ -0,0 +1,27 @@
1
+ require "rest-client"
2
+
3
+ module Coyodlee
4
+ module HttpWrapper
5
+ class << self
6
+ def get(url:, params: {}, headers: {})
7
+ if params.empty?
8
+ RestClient.get(url, headers)
9
+ else
10
+ RestClient.get(url, { params: params }.merge(headers))
11
+ end
12
+ end
13
+
14
+ def post(url:, body:, headers: {})
15
+ RestClient.post(url, body, headers)
16
+ end
17
+
18
+ def put(url:, body:, headers: {})
19
+ RestClient.put(url, body, headers)
20
+ end
21
+
22
+ def delete(url:, headers: {})
23
+ RestClient.delete(url, nil, headers)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,50 @@
1
+ require 'json'
2
+
3
+ module Coyodlee
4
+ module Sessions
5
+ class CobrandSession
6
+ # Holds the cobrand session token
7
+ #
8
+ # @return [String] the cobrand session token
9
+ attr_reader :token
10
+
11
+ # Creates a new cobrand session
12
+ def initialize
13
+ @token = ''
14
+ end
15
+
16
+ # Initiates a cobrand session
17
+ #
18
+ # @return the underlying response object for the HTTP client you've selected, RestClient by default
19
+ def login(login_name:, password:)
20
+ HttpWrapper.post(
21
+ url: "#{::Coyodlee.base_url}/cobrand/login",
22
+ body: {
23
+ cobrand: {
24
+ cobrandLogin: login_name,
25
+ cobrandPassword: password,
26
+ locale: 'en_US'
27
+ }
28
+ }.to_json,
29
+ headers: { content_type: :json, accept: :json }
30
+ ).tap { |response|
31
+ @token = JSON.parse(response.body)['session']['cobSession']
32
+ }
33
+ end
34
+
35
+ # Returns a string containing the cobrand session token which can be used as the value of the Authorization HTTP header
36
+ def auth_header
37
+ @token.empty? ? '' : "cobSession=#{@token}"
38
+ end
39
+
40
+ # Terminates the cobrand session
41
+ #
42
+ # @return the underlying response object for the HTTP client you've selected, RestClient by default
43
+ def logout
44
+ HttpWrapper.post(
45
+ url: "#{::Coyodlee.base_url}/cobrand/logout"
46
+ )
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,60 @@
1
+ require 'json'
2
+
3
+ module Coyodlee
4
+ module Sessions
5
+ class UserSession
6
+ # Holds the user session token
7
+ #
8
+ # @return [String] the user session token
9
+ attr_reader :token
10
+
11
+ # Creates a new user session
12
+ #
13
+ # @param cobrand_session [CobrandSession] the cobrand session
14
+ def initialize(cobrand_session:)
15
+ @token = ''
16
+ @cobrand_session = cobrand_session
17
+ end
18
+
19
+ # Initiates a user session. If the login is successful, the {#token} will be
20
+ # set
21
+ #
22
+ # @param login_name [String] the login name of the user
23
+ # @param password [String] the password of the user
24
+ # @return the underlying response object for the HTTP client you've selected, RestClient by default
25
+ def login(login_name:, password:)
26
+ HttpWrapper.post(
27
+ url: "#{::Coyodlee.base_url}/user/login",
28
+ body: {
29
+ user: {
30
+ loginName: login_name,
31
+ password: password,
32
+ locale: 'en_US'
33
+ }
34
+ }.to_json,
35
+ headers: {
36
+ content_type: :json,
37
+ accept: :json,
38
+ authorization: @cobrand_session.auth_header
39
+ }
40
+ ).tap { |response|
41
+ @token = JSON.parse(response.body)['user']['session']['userSession']
42
+ }
43
+ end
44
+
45
+ # Returns a string containing the cobrand session token and the user session token which can be used as the value of the Authorization HTTP header
46
+ def auth_header
47
+ @token.empty? ? '' : "cobSession=#{@cobrand_session.token},userSession=#{@token}"
48
+ end
49
+
50
+ # Terminates the user session
51
+ #
52
+ # @return the underlying response object for the HTTP client you've selected, RestClient by default
53
+ def logout
54
+ HttpWrapper.post(
55
+ url: "#{::Coyodlee.base_url}/user/logout"
56
+ )
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'sessions/cobrand_session'
2
+ require_relative 'sessions/user_session'
@@ -0,0 +1,26 @@
1
+ module Coyodlee
2
+ module Utils
3
+ # Converts a string to camel-case with the first letter uncapitalized
4
+ # @param str [String] The string to modify
5
+ # @return [String] The string as camel-cased with the first letter uncapitalized
6
+ def uncapitalized_camelize(str)
7
+ str
8
+ .split('_')
9
+ .map { |w| w.capitalize }
10
+ .join
11
+ .tap { |w| w[0] = w[0].downcase }
12
+ end
13
+
14
+ def sub_underscore(str)
15
+ str.sub(/^_/, '')
16
+ end
17
+
18
+ def sub_double_underscore(str)
19
+ str.gsub(/__/, '.')
20
+ end
21
+
22
+ def build_url(path)
23
+ ::Coyodlee.base_url.to_s + path
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module Coyodlee
2
+ VERSION = "0.1.0"
3
+ end
data/lib/coyodlee.rb ADDED
@@ -0,0 +1,26 @@
1
+ require_relative 'coyodlee/version'
2
+ require_relative 'coyodlee/http_wrapper'
3
+ require 'json'
4
+ require 'yajl'
5
+
6
+ # The global Yodlee configuration object
7
+ module Coyodlee
8
+
9
+ class << self
10
+ # The base url Yodlee provides for your cobrand
11
+ # @return [String] The base url Yodlee provides for your cobrand
12
+ attr_accessor :base_url
13
+ # The login of your cobrand
14
+ # @return [String] The login of your cobrand
15
+ attr_accessor :cobrand_login
16
+ # The password of your cobrand
17
+ # @return [String] The password of your cobrand
18
+ attr_accessor :cobrand_password
19
+
20
+ # The method to configure Yodlee parameters. Use this to set the global parameters such as {Yodlee.base_url}, {Yodlee.cobrand_login}, and {Yodlee.cobrand_password}
21
+ # @yieldparam config [Yodlee] The Yodlee object
22
+ def setup &block
23
+ yield self
24
+ end
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,189 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: coyodlee
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Dyba
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-12-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rest-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.11'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.11'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: vcr
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.1'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.1'
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.9'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.9'
111
+ - !ruby/object:Gem::Dependency
112
+ name: activesupport
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '='
116
+ - !ruby/object:Gem::Version
117
+ version: '5.1'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '='
123
+ - !ruby/object:Gem::Version
124
+ version: '5.1'
125
+ - !ruby/object:Gem::Dependency
126
+ name: travis
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.8'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.8'
139
+ description: A Ruby wrapper client for Envestnet's Yodlee API. For details about the
140
+ API endpoints, sign in to https://developer.yodlee.com
141
+ email:
142
+ - daniel.dyba@gmail.com
143
+ executables: []
144
+ extensions: []
145
+ extra_rdoc_files: []
146
+ files:
147
+ - ".gitignore"
148
+ - ".travis.yml"
149
+ - CODE_OF_CONDUCT.md
150
+ - Gemfile
151
+ - LICENSE.txt
152
+ - README.md
153
+ - Rakefile
154
+ - bin/console
155
+ - bin/setup
156
+ - coyodlee.gemspec
157
+ - lib/coyodlee.rb
158
+ - lib/coyodlee/client.rb
159
+ - lib/coyodlee/http_wrapper.rb
160
+ - lib/coyodlee/sessions.rb
161
+ - lib/coyodlee/sessions/cobrand_session.rb
162
+ - lib/coyodlee/sessions/user_session.rb
163
+ - lib/coyodlee/utils.rb
164
+ - lib/coyodlee/version.rb
165
+ homepage: https://github.com/pennymac/coyodlee
166
+ licenses:
167
+ - MIT
168
+ metadata: {}
169
+ post_install_message:
170
+ rdoc_options: []
171
+ require_paths:
172
+ - lib
173
+ required_ruby_version: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - ">="
176
+ - !ruby/object:Gem::Version
177
+ version: '0'
178
+ required_rubygems_version: !ruby/object:Gem::Requirement
179
+ requirements:
180
+ - - ">="
181
+ - !ruby/object:Gem::Version
182
+ version: '0'
183
+ requirements: []
184
+ rubyforge_project:
185
+ rubygems_version: 2.5.2
186
+ signing_key:
187
+ specification_version: 4
188
+ summary: A Ruby wrapper client for Envestnet's Yodlee API
189
+ test_files: []