books-ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 56c312777264b990552b33b49fe6469a5f801fd9
4
+ data.tar.gz: c9200f4d03f6fec2e1c2f2141017940532eedc76
5
+ SHA512:
6
+ metadata.gz: 919ad1c847dfc49c3dcdf3c0646b153ad96c920e99470fb80e803b6f35978aa81db023de44aa79b26c0ab639e189428d5bd3a9058d4d0ff9489f2aeac11ae30d
7
+ data.tar.gz: 95a7e4e2f304266680b6f50d821dc0e6bf346dd59c3ad6dc539b96dae1d236bdbb75bfedcadfcb9471a307e82d8b84acedaf1d829443eafa1e55554fff9c017b
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ README.md
3
+ ChangeLog.md
4
+
5
+ LICENSE.txt
@@ -0,0 +1,5 @@
1
+ /.bundle
2
+ /Gemfile.lock
3
+ /html/
4
+ /pkg/
5
+ /vendor/cache/*.gem
@@ -0,0 +1,16 @@
1
+ --- !ruby/object:RDoc::Options
2
+ encoding: UTF-8
3
+ static_path: []
4
+ rdoc_include:
5
+ - .
6
+ charset: UTF-8
7
+ exclude:
8
+ hyperlink_all: false
9
+ line_numbers: false
10
+ main_page: README.md
11
+ markup: markdown
12
+ show_hash: false
13
+ tab_width: 8
14
+ title: books-ruby Documentation
15
+ visibility: :protected
16
+ webcvs:
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour --format documentation
@@ -0,0 +1 @@
1
+ 2.4.4
@@ -0,0 +1,4 @@
1
+ ### 0.1.0 / 2018-04-16
2
+
3
+ * Initial release:
4
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://gems.vip.global.square/'
2
+ source 'https://gems.vip.global.square/private/'
3
+
4
+ gemspec
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2018 abishopric
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.
@@ -0,0 +1,27 @@
1
+ # books-ruby
2
+
3
+ * [Homepage](https://rubygems.org/gems/books-ruby)
4
+ * [Documentation](http://rubydoc.info/gems/books-ruby/frames)
5
+ * [Email](mailto:abishopric at squareup.com)
6
+
7
+ ## Description
8
+
9
+ TODO: Description
10
+
11
+ ## Features
12
+
13
+ ## Examples
14
+
15
+ require 'books'
16
+
17
+ ## Requirements
18
+
19
+ ## Install
20
+
21
+ $ gem install books-ruby
22
+
23
+ ## Copyright
24
+
25
+ Copyright (c) 2018 abishopric
26
+
27
+ See LICENSE.txt for details.
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+
5
+ begin
6
+ require 'bundler/setup'
7
+ rescue LoadError => e
8
+ abort e.message
9
+ end
10
+
11
+ require 'rake'
12
+
13
+
14
+ require 'rubygems/tasks'
15
+ Gem::Tasks.new
16
+
17
+ require 'rdoc/task'
18
+ RDoc::Task.new
19
+ task :doc => :rdoc
20
+
21
+ require 'rspec/core/rake_task'
22
+ RSpec::Core::RakeTask.new
23
+
24
+ task :test => :spec
25
+ task :default => :spec
@@ -0,0 +1,47 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'books/version'
6
+
7
+ Gem::Specification.new do |gem|
8
+ gem.name = "books-ruby"
9
+ gem.version = Books::VERSION
10
+ gem.summary = "Books Ruby SDK"
11
+ gem.description = "Interact with Books in Ruby"
12
+ gem.license = "MIT"
13
+ gem.authors = ["abishopric"]
14
+ gem.email = "abishopric@squareup.com"
15
+ gem.homepage = "https://stash.corp.squareup.com/projects/SQ/repos/books-ruby/browse"
16
+
17
+ gem.files = `git ls-files`.split($/)
18
+
19
+ `git submodule --quiet foreach --recursive pwd`.split($/).each do |submodule|
20
+ submodule.sub!("#{Dir.pwd}/",'')
21
+
22
+ Dir.chdir(submodule) do
23
+ `git ls-files`.split($/).map do |subpath|
24
+ gem.files << File.join(submodule,subpath)
25
+ end
26
+ end
27
+ end
28
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
29
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
30
+ gem.require_paths = ['lib']
31
+
32
+ gem.required_ruby_version = '>= 2.4.4'
33
+
34
+ gem.add_dependency 'sq-common'
35
+ gem.add_dependency 'sq-protos'
36
+ gem.add_dependency 'sq-p2'
37
+ gem.add_dependency 'square_connector'
38
+ gem.add_dependency 'ed25519'
39
+
40
+ gem.add_development_dependency 'bundler', '~> 1.10'
41
+ gem.add_development_dependency 'pry'
42
+ gem.add_development_dependency 'rake', '~> 10.0'
43
+ gem.add_development_dependency 'rdoc', '~> 4.0'
44
+ gem.add_development_dependency 'rspec', '~> 3.0'
45
+ gem.add_development_dependency 'rubygems-tasks', '~> 0.2'
46
+ gem.add_development_dependency 'rubocop'
47
+ end
@@ -0,0 +1,4 @@
1
+ require 'books/version'
2
+
3
+ require 'books/client'
4
+ require 'books/fake_authority_client'
@@ -0,0 +1,128 @@
1
+ require 'sq/protos'
2
+ require 'ed25519'
3
+ require 'base64'
4
+
5
+ require 'squareup/books/service.pb'
6
+ require 'squareup/booksauthority/service.pb'
7
+
8
+ module Books
9
+ class StandardClient
10
+ attr_reader :books_client, :books_authority_client, :eddsa_private_key, :eddsa_public_key
11
+
12
+ #
13
+ # Instantiate a new client that can connect to Books.
14
+ #
15
+ # ** eddsa_public_key_path and eddsa_private_key_path (required) ** :
16
+ # Both are paths to your eddsa keys you intend to use to sign journal entries.
17
+ # The private key will be used to sign journal entries as they are sent, while
18
+ # the public key can be used to verify journal entries afterwards. Note that
19
+ # these are generally not the same keys used to intitiate TLS.
20
+ #
21
+ # ** connector ** :
22
+ # A connector from which we can request the clients "books" and "booksAuthority".
23
+ # This is only required if either books_client or books_authority_client
24
+ # are not provided. Connectors are provided by the square_connector gem or from
25
+ # sq/common.
26
+ #
27
+ # ** books_client and books_authority_client (optional) ** :
28
+ # RPC clients that would otherwise be provided by the connector. Helpful for testing.
29
+ # If provided, will take precedence over what would have been returned by the connector
30
+ #
31
+ def initialize(books_client: nil,
32
+ books_authority_client: nil,
33
+ connector: nil,
34
+ eddsa_public_key_path:,
35
+ eddsa_private_key_path:)
36
+ @books_client = books_client || begin
37
+ fail "Neither connector nor books_client given" unless connector
38
+ books_http_client = connector.create(:books)
39
+ Sq::Protos::RpcClient.new(books_http_client, Squareup::Books::Service::BooksService)
40
+ end
41
+
42
+ @books_authority_client = books_authority_client || begin
43
+ fail "Neither connector nor books_authority_client given" unless connector
44
+ books_authority_http_client = connector.create(:booksAuthority)
45
+ Sq::Protos::RpcClient.new(books_authority_http_client,
46
+ Squareup::Booksauthority::Service::BooksAuthorityService)
47
+ end
48
+
49
+ @eddsa_private_key = Ed25519::SigningKey.new(extract_key(eddsa_private_key_path, "ED25519 PRIVATE KEY"))
50
+ @eddsa_public_key_data = File.read(eddsa_public_key_path) # want PEM format
51
+ @eddsa_public_key = Ed25519::VerifyKey.new(extract_key(eddsa_public_key_path, "ED25519 PUBLIC KEY"))
52
+ end
53
+
54
+ def get_books_matching(req)
55
+ @books_client.https_rpc(:get_books_matching, req)
56
+ end
57
+
58
+ def register_myself
59
+ register_client({
60
+ public_key: @eddsa_public_key_data
61
+ })
62
+ end
63
+
64
+ def register_client(req)
65
+ @books_client.https_rpc(:register_client, req)
66
+ end
67
+
68
+ def create_denomination(req)
69
+ @books_client.https_rpc(:create_denomination, req)
70
+ end
71
+
72
+ def create_book(req)
73
+ bas_response = @books_authority_client.https_rpc(:generate_sskg, {})
74
+ with_sskg = req.dup
75
+ with_sskg[:first_sskg_key] = bas_response[:first_key]
76
+ with_sskg[:root_sskg_token] = bas_response[:root_sskg_token]
77
+ @books_client.https_rpc(:create_book, with_sskg)
78
+ end
79
+
80
+ def journal(req)
81
+ raise "Journal entry type not provided" unless req[:journal_entry_type]
82
+ raise "Journal idempotence token not provided" unless req[:idempotence_token]
83
+ raise "Journal entries not provided" unless req[:entries] && req[:entries].size > 1
84
+
85
+ total = req[:entries].map do |entry|
86
+ raise "Must set exactly one of to_amount or from_amount" unless (entry[:to_amount] ^ entry[:from_amount])
87
+ entry[:to_amount] - entry[:from_amount]
88
+ end.sum
89
+
90
+ raise "Total change in book entry amounts is not 0" unless total == 0
91
+
92
+ raise "The signature field must not be set (the SDK will assign this field for you)" if req[:signature]
93
+
94
+ # sign the request body
95
+ jreq = Squareup::Books::Service::JournalRequest.new(req)
96
+
97
+ encoded_proto = jreq.encode
98
+ signature = @eddsa_private_key.sign(encoded_proto)
99
+ req[:signature] = signature
100
+
101
+ journal_response = @books_client.https_rpc(:journal, req)
102
+ if journal_response.status != Squareup::Books::Service::JournalStatus::JOURNAL_SUCCESS
103
+ raise "Journaling error received: #{journal_resp.status}"
104
+ end
105
+ journal_response
106
+ end
107
+
108
+ private
109
+
110
+ # Given a filepath, read the file as a PEM file. Assert that
111
+ # the given header is present in the PEM header and footer.
112
+ def extract_key(pem_filepath, expected_pem_header)
113
+ pem_data = File.read(pem_filepath)
114
+ contents = pem_data.split("\n")
115
+ raise "Invalid PEM file, too short" unless contents.size > 2
116
+
117
+ unless contents[0].include?(expected_pem_header)
118
+ raise "Header for #{pem_filepath} isn't what we expected. Is it a #{expected_pem_header} ?"
119
+ end
120
+ unless contents[contents.length - 1].include?(expected_pem_header)
121
+ raise "Footer for #{pem_filepath} isn't what we expected. Is it a #{expected_pem_header} ?"
122
+ end
123
+
124
+ base64Encoded = contents[1..contents.size-2].join("")
125
+ return Base64.decode64(base64Encoded)
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,46 @@
1
+ require 'base64'
2
+
3
+ module Books
4
+ #
5
+ # Test class that provides a fake RPC client for books authority.
6
+ # Useful for tests, it provides real SSKG generation. It does not
7
+ # do the full suite of verification.
8
+ #
9
+ class FakeAuthorityClient
10
+ def initialize(
11
+ raise_on_self_register:false,
12
+ raise_on_verify:false)
13
+ @sskgs = {}
14
+ @raise_on_self_register = raise_on_self_register
15
+ end
16
+
17
+ def https_rpc(rpc, req)
18
+ case rpc
19
+ when :generate_sskg
20
+ generate_sskg(req)
21
+ when :verify_book
22
+ verify_book(req)
23
+ when :register_self
24
+ register_self(req)
25
+ else
26
+ raise "Invalid RPC method #{rpc}"
27
+ end
28
+ end
29
+
30
+ def register_self(req)
31
+ raise "Client already registered" if @raise_on_self_register
32
+ end
33
+
34
+ def verify_book(req)
35
+ raise "Book has been tampered with" if @raise_on_verify
36
+ end
37
+
38
+ def generate_sskg(req)
39
+ {
40
+ root_sskg_token: Base64.decode64("gqhwb3NpdGlvbgClbm9kZXORgqVzdGF0ZcQg/z6NXBXEZRXi1qKfcsmSmu5x2HCYgbT9ZC97IVJyqWymaGVpZ2h0Pw=="),
41
+ first_key: Base64.decode64("gqhwb3NpdGlvbgGlbm9kZXOSgqVzdGF0ZcQgd1N+3oVeOJri1Jz8k0fBHSMv6YejZcT8EjW0ixZwoAOmaGVpZ2h0PoKlc3RhdGXEILIzf84dcqwznf807/8DVWJNE/Pj5MTkILqeTOOCaK3GpmhlaWdodD4="),
42
+ }
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,4 @@
1
+ module Books
2
+ # books-ruby version
3
+ VERSION = "0.1.0"
4
+ end
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+ require 'books'
3
+ require 'pry'
4
+ require 'square_connector'
5
+ require 'securerandom'
6
+ require 'uri'
7
+
8
+ describe Books do
9
+ it "should have a VERSION constant" do
10
+ expect(subject.const_get('VERSION')).to_not be_empty
11
+ end
12
+
13
+ it "should create books and journal end-to-end" do
14
+ books_host = "https://abishopric.local:32191"
15
+ client_tls_cert = "/data/app/books/secrets/books-abishopric.local-s2s.crt"
16
+ client_tls_key = "/data/app/books/secrets/books-abishopric.local-s2s.key"
17
+ s2s_ca = "/data/app/books/secrets/service2service.ca.pem"
18
+ client_signing_pubkey = "/Users/abishopric/books/books-abishopric-32-byte-private-key.pub"
19
+ client_signing_privkey = "/Users/abishopric/books/books-abishopric-32-byte-private-key.priv"
20
+
21
+ ################
22
+
23
+ connector = SquareConnector::Client.new(
24
+ services: {
25
+ books: { url: URI.parse(books_host) },
26
+ },
27
+ certs: {
28
+ client_cert: client_tls_cert,
29
+ client_key: clinet_tls_key,
30
+ ca_certificate: s2s_ca,
31
+ },
32
+ logger: Logger.new(STDERR),
33
+ app_name: 'TestApp'
34
+ )
35
+ client = Books::StandardClient.new(connector: connector,
36
+ books_authority_client: FakeAuthorityClient.new,
37
+ eddsa_public_key_path: client_signing_pubkey,
38
+ eddsa_private_key_path: client_signing_privkey)
39
+
40
+ client.register_myself
41
+
42
+ client.create_denomination({
43
+ full_name: "United States Dollars",
44
+ short_name: "USD"
45
+ })
46
+
47
+ owner = SecureRandom.hex
48
+
49
+ asset_book = client.create_book({
50
+ denomination: "USD",
51
+ min_balance: 0,
52
+ idempotence_token: SecureRandom.hex,
53
+ contra: false,
54
+ max_balance: 1000,
55
+ type: "cash",
56
+ owner: owner,
57
+ category: Squareup::Books::Service::BookCategory::ASSET
58
+ }).book
59
+
60
+ expect(asset_book).to_not be_nil
61
+
62
+ liability_book = client.create_book({
63
+ denomination: "USD",
64
+ max_balance: 0,
65
+ idempotence_token: SecureRandom.hex,
66
+ contra: false,
67
+ min_balance: -1000,
68
+ type: "cash",
69
+ owner: owner,
70
+ category: Squareup::Books::Service::BookCategory::LIABILITY
71
+ }).book
72
+
73
+ response = client.get_books_matching(filter: {
74
+ :owner => owner
75
+ })
76
+
77
+ expect(response.books.length).to eq(2)
78
+
79
+ journal_resp = client.journal({
80
+ entries: [{
81
+ to_amount: 100,
82
+ from_amount: 0,
83
+ book_id: asset_book.book_id,
84
+ book_entry_type: "cash_received"
85
+ }, {
86
+ from_amount: 100,
87
+ to_amount: 0,
88
+ book_id: liability_book.book_id,
89
+ book_entry_type: "cash_loaned"
90
+ }],
91
+ idempotence_token: SecureRandom.hex,
92
+ journal_entry_type: "loan"
93
+ })
94
+
95
+ expect(journal_resp.status).to eq(Squareup::Books::Service::JournalStatus::JOURNAL_SUCCESS)
96
+ end
97
+ end
@@ -0,0 +1,4 @@
1
+ require 'rspec'
2
+ require 'books/version'
3
+
4
+ include Books
metadata ADDED
@@ -0,0 +1,230 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: books-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - abishopric
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-05-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sq-common
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sq-protos
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sq-p2
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: square_connector
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: ed25519
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.10'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.10'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '10.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '10.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rdoc
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '4.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '4.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubygems-tasks
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.2'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0.2'
167
+ - !ruby/object:Gem::Dependency
168
+ name: rubocop
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ description: Interact with Books in Ruby
182
+ email: abishopric@squareup.com
183
+ executables: []
184
+ extensions: []
185
+ extra_rdoc_files: []
186
+ files:
187
+ - ".document"
188
+ - ".gitignore"
189
+ - ".rdoc_options"
190
+ - ".rspec"
191
+ - ".ruby-version"
192
+ - ChangeLog.md
193
+ - Gemfile
194
+ - LICENSE.txt
195
+ - README.md
196
+ - Rakefile
197
+ - books-ruby.gemspec
198
+ - lib/books.rb
199
+ - lib/books/client.rb
200
+ - lib/books/fake_authority_client.rb
201
+ - lib/books/version.rb
202
+ - spec/books_spec.rb
203
+ - spec/spec_helper.rb
204
+ homepage: https://stash.corp.squareup.com/projects/SQ/repos/books-ruby/browse
205
+ licenses:
206
+ - MIT
207
+ metadata: {}
208
+ post_install_message:
209
+ rdoc_options: []
210
+ require_paths:
211
+ - lib
212
+ required_ruby_version: !ruby/object:Gem::Requirement
213
+ requirements:
214
+ - - ">="
215
+ - !ruby/object:Gem::Version
216
+ version: 2.4.4
217
+ required_rubygems_version: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - ">="
220
+ - !ruby/object:Gem::Version
221
+ version: '0'
222
+ requirements: []
223
+ rubyforge_project:
224
+ rubygems_version: 2.6.14.1
225
+ signing_key:
226
+ specification_version: 4
227
+ summary: Books Ruby SDK
228
+ test_files:
229
+ - spec/books_spec.rb
230
+ - spec/spec_helper.rb