blinkbox-common_mapping 0.1.4
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 +15 -0
- data/CHANGELOG.md +73 -0
- data/README.md +16 -0
- data/VERSION +1 -0
- data/lib/blinkbox/common_mapping.rb +260 -0
- metadata +139 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NGZlZjU1MzQyOTQ4YjY0YWEwNDg4NDEwODcxZTI3YjNhNGQxOTE3ZA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NjgxYWFjMTQ2ZGZjNjkxMjZkYzEwMDY2YWI3OGJkYjk4YWQ0M2U4Mw==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NGJhNjE2YmIyMmM1ZDg5ODQ5NTVjMjllNTAxZmI2ODhmM2Y5ZjExNjAwMGY1
|
10
|
+
NTk2OWFiYjhlNGEzYWQ4NWJhNjIxMzlkYWZmMmIxMWM4ZmVhYzFhMjE5NzU5
|
11
|
+
MTUxMGEzYzUzOGM4OWYzNmIxMTM4NmVkZmU4MTM1NjZkMWIwZDc=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NmExZWZjZjRhOWI4Yjg0ZjE1Mzk1NzE5ODkyZWY2NWExNzQ1MTI2NjE5ZDBl
|
14
|
+
YTNiNTg0NDdlMGVkYzExOTEwMmY4NTgwY2JhZmRhMTM3ODI2N2MxY2RiZjA2
|
15
|
+
ODZjYmUyNmQ0YTY5YWFjMDA4MGYyMDc4OTNiNmRlYzMyNGJmY2I=
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# Change log
|
2
|
+
|
3
|
+
## Open Source release (2015-01-28 14:11:21)
|
4
|
+
|
5
|
+
Today we have decided to publish parts of our codebase on Github under the [MIT licence](LICENCE). The licence is very permissive, but we will always appreciate hearing if and where our work is used!
|
6
|
+
|
7
|
+
## 0.1.4 ([#8](https://git.mobcastdev.com/Platform/common_mapping.rb/pull/8) 2015-01-14 15:06:59)
|
8
|
+
|
9
|
+
Deal with Storage Service omissions
|
10
|
+
|
11
|
+
### Bug fix
|
12
|
+
|
13
|
+
- Deal with the situation where the storage service doesn't have status information for the given token. (This *always* occurs with the fake storage service)
|
14
|
+
|
15
|
+
## 0.1.3 ([#7](https://git.mobcastdev.com/Platform/common_mapping.rb/pull/7) 2015-01-07 17:13:29)
|
16
|
+
|
17
|
+
Return
|
18
|
+
|
19
|
+
### Bugfix
|
20
|
+
|
21
|
+
- The `open` method should return the value the block yields when a block is given.
|
22
|
+
|
23
|
+
## 0.1.2 ([#6](https://git.mobcastdev.com/Platform/common_mapping.rb/pull/6) 2014-11-25 17:48:31)
|
24
|
+
|
25
|
+
Pluralise default folder
|
26
|
+
|
27
|
+
### Improvements
|
28
|
+
|
29
|
+
- The default schema file is `schemas` not `schema`
|
30
|
+
- Upgraded to needing the version of common_messaging from [#8](/Platform/common_messaging.rb/pull/8)
|
31
|
+
|
32
|
+
## 0.1.1 ([#5](https://git.mobcastdev.com/Platform/common_mapping.rb/pull/5) 2014-11-20 09:25:18)
|
33
|
+
|
34
|
+
Valid User Agent
|
35
|
+
|
36
|
+
### Bug fix
|
37
|
+
|
38
|
+
- Previous User-Agent was invalid.
|
39
|
+
|
40
|
+
## 0.1.0 ([#4](https://git.mobcastdev.com/Platform/common_mapping.rb/pull/4) 2014-11-18 16:05:09)
|
41
|
+
|
42
|
+
Legit
|
43
|
+
|
44
|
+
### New features
|
45
|
+
|
46
|
+
- Actually works!
|
47
|
+
- Can deal with HTTP and FILE urls passed in the mapping file from the storage service
|
48
|
+
- Opens files and downloads HTTP resources into temporary files, passing an IO object back for interaction with.
|
49
|
+
|
50
|
+
## 0.0.3 ([#3](https://git.mobcastdev.com/Platform/common_mapping.rb/pull/3) 2014-10-27 18:11:33)
|
51
|
+
|
52
|
+
Unescape URI components
|
53
|
+
|
54
|
+
### Improvement
|
55
|
+
|
56
|
+
- Allows URI encoded files to come through (allows spaces!)
|
57
|
+
|
58
|
+
## 0.0.2 ([#2](https://git.mobcastdev.com/Platform/common_mapping.rb/pull/2) 2014-10-08 13:06:55)
|
59
|
+
|
60
|
+
Deal with triple or single slash
|
61
|
+
|
62
|
+
### Bugfix
|
63
|
+
|
64
|
+
- URLs with a single or triple slash were failing to process (eg. `file:/path/to/stuff`)
|
65
|
+
|
66
|
+
## 0.0.1 ([#1](https://git.mobcastdev.com/Platform/common_mapping.rb/pull/1) 2014-10-06 14:35:20)
|
67
|
+
|
68
|
+
Correct the gemspec
|
69
|
+
|
70
|
+
### Improvement
|
71
|
+
|
72
|
+
- First release, mock only (while the Quartermaster is being spec'd out)
|
73
|
+
|
data/README.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Blinkbox::CommonMapping
|
2
|
+
|
3
|
+
Deals with blinkbox Books virtual URIs and acts like a local `File` object.
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
mapper = Mappings.new(
|
7
|
+
"http://quatermaster.blinkbox.local",
|
8
|
+
service_name: "Labs/example_code"
|
9
|
+
)
|
10
|
+
|
11
|
+
mapper.open("bbbmap::testfile:/some/path/component.epub") do |io|
|
12
|
+
p io
|
13
|
+
# This is a Tempfile object, interact with it as you wish!
|
14
|
+
end
|
15
|
+
# Temporary file has been deleted
|
16
|
+
```
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.4
|
@@ -0,0 +1,260 @@
|
|
1
|
+
require "uri"
|
2
|
+
require "json"
|
3
|
+
require "time"
|
4
|
+
require "socket"
|
5
|
+
require "net/http"
|
6
|
+
require "tempfile"
|
7
|
+
require "blinkbox/common_messaging"
|
8
|
+
|
9
|
+
module Blinkbox
|
10
|
+
class CommonMapping
|
11
|
+
VERSION = begin
|
12
|
+
File.read(File.join(__dir__, "../../VERSION")).strip
|
13
|
+
rescue Errno::ENOENT
|
14
|
+
"0.0.0-unknown"
|
15
|
+
end
|
16
|
+
|
17
|
+
# Set a logger to send all log messages to
|
18
|
+
#
|
19
|
+
# @param [:debug,:info,:warn,:error,:fatal] logger A logger instance.
|
20
|
+
def self.logger=(logger)
|
21
|
+
@@logger = logger
|
22
|
+
end
|
23
|
+
|
24
|
+
# NullLogger by default
|
25
|
+
@@logger = Class.new {
|
26
|
+
def debug(*); end
|
27
|
+
def info(*); end
|
28
|
+
def warn(*); end
|
29
|
+
def error(*); end
|
30
|
+
def fatal(*); end
|
31
|
+
}.new
|
32
|
+
|
33
|
+
# Initializing a mapper will retrieve the mapping file from the specified storage service and set up an exclusive queue
|
34
|
+
# to receive updates which might occur while this instance is in use.
|
35
|
+
#
|
36
|
+
# @param [String] storage_service_url The Base URL for the storage service.
|
37
|
+
# @param [String] :service_name The name of your service. Defines the name of the mapping updates queue.
|
38
|
+
# @param [String, nil] :schema_root If not nil, the location (relative to the current directory) of the schema root (mapping/update/v1.schema.json will be used to validate messages).
|
39
|
+
# @param [Integer] :mapping_timeout The length of time before a new mapping file is requested from the storage service.
|
40
|
+
def initialize(storage_service_url, service_name: raise(ArgumentError, "A service name is required"), schema_root: "schemas", mapping_timeout: 7 * 24 * 3600)
|
41
|
+
@ss = URI.parse(storage_service_url)
|
42
|
+
@service_name = service_name
|
43
|
+
uid = [Socket.gethostname, Process.pid].join("$")
|
44
|
+
queue_name = "#{service_name.tr('/', '.')}.mapping_updates.#{uid}"
|
45
|
+
|
46
|
+
@queue = CommonMessaging::Queue.new(
|
47
|
+
queue_name,
|
48
|
+
exchange: "Mapping",
|
49
|
+
bindings: [{ "content-type" => "application/vnd.blinkbox.books.mapping.update.v1+json" }],
|
50
|
+
prefetch: 1,
|
51
|
+
exclusive: true,
|
52
|
+
temporary: true,
|
53
|
+
dlx: nil
|
54
|
+
)
|
55
|
+
|
56
|
+
@timeout = mapping_timeout
|
57
|
+
|
58
|
+
opts = { block: false }
|
59
|
+
if !schema_root.nil?
|
60
|
+
CommonMessaging.init_from_schema_at(File.join(schema_root, "mapping"), schema_root)
|
61
|
+
opts[:accept] = [CommonMessaging::MappingUpdateV1]
|
62
|
+
end
|
63
|
+
|
64
|
+
# We're about to request the latest mapping file, so we don't need any of the ones on the queue.
|
65
|
+
@queue.purge!
|
66
|
+
@queue.subscribe(opts) do |metadata, update|
|
67
|
+
next :reject unless metadata[:timestamp].is_a?(Time)
|
68
|
+
update_mapping!(metadata[:timestamp], update)
|
69
|
+
:ack
|
70
|
+
end
|
71
|
+
|
72
|
+
@@logger.debug "Queue #{queue_name} created, bound and subscribed to"
|
73
|
+
retrieve_mapping!
|
74
|
+
@@logger.info "Mapping initialized"
|
75
|
+
end
|
76
|
+
|
77
|
+
# Opens a given token and returns an IO object for the associated asset or - if a block
|
78
|
+
# is passed - yields with the IO object as the only argument.
|
79
|
+
#
|
80
|
+
# @param [String] token The token referring to the asset to be opened
|
81
|
+
# @raise InvalidTokenError if the given token string isn't a valid token
|
82
|
+
# @return An IO object referencing the object or (if a block was given) the value returned from the block
|
83
|
+
def open(token)
|
84
|
+
raise InvalidTokenError unless valid_token?(token)
|
85
|
+
@@logger.debug "Opening #{token}"
|
86
|
+
locations = map(token)
|
87
|
+
@@logger.debug "Locations for #{token} are: #{locations.inspect}"
|
88
|
+
# TODO: We currently assume the first is the best. Later iterations of this library may be more intelligent
|
89
|
+
while locations.size > 0
|
90
|
+
provider, uri = locations.shift
|
91
|
+
@@logger.debug "Trying #{uri} from #{provider}"
|
92
|
+
begin
|
93
|
+
io = open_uri(URI.parse(uri))
|
94
|
+
return io if !block_given?
|
95
|
+
returns = yield(io)
|
96
|
+
io.close
|
97
|
+
return returns
|
98
|
+
rescue
|
99
|
+
# There was a problem with this provider file, register it and move on to another
|
100
|
+
status = retrieve_status(token, provider_failure: provider)
|
101
|
+
if status.nil?
|
102
|
+
@@logger.warn "Storage service has no status for #{token}."
|
103
|
+
break
|
104
|
+
end
|
105
|
+
available_providers = status['providers'].map { |this_provider, this_status|
|
106
|
+
this_status['available'] ? this_provider : nil
|
107
|
+
}.compact
|
108
|
+
locations.delete_if { |p, u|
|
109
|
+
!(status['providers'][p] && status['providers'][p]['available'])
|
110
|
+
}
|
111
|
+
end
|
112
|
+
end
|
113
|
+
raise MissingAssetError, "The asset for #{token} could not be downloaded from anywhere."
|
114
|
+
end
|
115
|
+
|
116
|
+
# Gets information about a specific token.
|
117
|
+
#
|
118
|
+
# @param [String] token The token for the asset to get the status of
|
119
|
+
def status(token)
|
120
|
+
# Duplicate method so the external API can't register a provider failure
|
121
|
+
retrieve_status(token)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Collects the mappings from the storage service specified when initialised.
|
125
|
+
#
|
126
|
+
# @return [Boolean] Returns true if the mapping file was updated, false if the mapping already stored was the same or more recent.
|
127
|
+
# @raise StorageServiceUnavailableError if the response from the server isn't 200, isn't JSON or (if the schema are available) isn't a valid mapping document.
|
128
|
+
def retrieve_mapping!
|
129
|
+
response = ss_get("/mappings")
|
130
|
+
raise StorageServiceUnavailableError, "Storage service gave #{response.code} response code, cannot update the mapping details." unless response.code == "200"
|
131
|
+
mapping = JSON.parse(response.body)
|
132
|
+
# This will raise a JSON::Schema::ValidationError if the mapping file isn't valid
|
133
|
+
CommonMessaging::MappingUpdateV1.new(mapping) if CommonMessaging.const_defined?('MappingUpdateV1')
|
134
|
+
timestamp = response['Date'].nil? ? Time.now : Time.parse(response['Date'])
|
135
|
+
update_mapping!(timestamp, mapping)
|
136
|
+
rescue JSON::ParserError, JSON::Schema::ValidationError
|
137
|
+
raise StorageServiceUnavailableError, "The response from the storage service wasn't a valid mapping update."
|
138
|
+
end
|
139
|
+
|
140
|
+
def inspect
|
141
|
+
"<Token Mapper: #{@ss.host}>"
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
# Stores a retrieved mapping file along with the timestamp
|
147
|
+
#
|
148
|
+
# @param [Time] timestamp The timestamp at which the given mapping was accurate
|
149
|
+
# @param [Hash, CommonMessaging::MappingUpdateV1] mapping The mapping file which needs to be stored
|
150
|
+
# @return [Boolean] Returns true if the mapping file was updated, false if the mapping already stored was the same or more recent.
|
151
|
+
def update_mapping!(timestamp, mapping)
|
152
|
+
return false if (!@mapping.nil? && timestamp < @mapping[:timestamp])
|
153
|
+
return false if (!@mapping.nil? && @mapping[:data] == mapping)
|
154
|
+
@mapping = {
|
155
|
+
data: mapping,
|
156
|
+
timestamp: timestamp
|
157
|
+
}
|
158
|
+
true
|
159
|
+
end
|
160
|
+
|
161
|
+
# Gets the status of a specific token, optionally recording that a provider has failed for a specific
|
162
|
+
# asset if specified.
|
163
|
+
#
|
164
|
+
# @return [nil] if the token does not exist
|
165
|
+
# @return [Hash] details of the asset
|
166
|
+
def retrieve_status(token, provider_failure: nil)
|
167
|
+
raise InvalidTokenError unless valid_token?(token)
|
168
|
+
res = ss_get("/resources/#{token}")
|
169
|
+
return nil if res.code == "404"
|
170
|
+
# TODO: Deal with other response codes
|
171
|
+
raise StorageServiceUnavailableError, "Storage service responded with #{res.code}" unless res.code == "200"
|
172
|
+
JSON.parse(res.body)
|
173
|
+
end
|
174
|
+
|
175
|
+
# TODO: Deal with unlinking tempfiles
|
176
|
+
def open_uri(uri)
|
177
|
+
case uri.scheme
|
178
|
+
when "file"
|
179
|
+
path = URI.decode(uri.path)
|
180
|
+
@@logger.debug "Attempting to open #{path}"
|
181
|
+
raise MissingAssetError unless File.exist?(path)
|
182
|
+
io = File.open(path)
|
183
|
+
when "http", "https"
|
184
|
+
io = Tempfile.new("common_mapping_file")
|
185
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
186
|
+
http.request_get(uri.path) do |resp|
|
187
|
+
raise MissingAssetError, "Received a #{resp.code} while trying to retrieve #{uri}" if resp.code != "200"
|
188
|
+
resp.read_body do |segment|
|
189
|
+
io.write(segment)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
io.rewind
|
194
|
+
else
|
195
|
+
raise NotImplementedError
|
196
|
+
end
|
197
|
+
io
|
198
|
+
end
|
199
|
+
|
200
|
+
def ss_get(path)
|
201
|
+
@http ||= Net::HTTP.new(@ss.host, @ss.port)
|
202
|
+
request = Net::HTTP::Get.new(path)
|
203
|
+
request.initialize_http_header({"User-Agent" => "common_mapping.rb/#{VERSION}"})
|
204
|
+
@http.request(request)
|
205
|
+
rescue Timeout::Error
|
206
|
+
raise StorageServiceUnavailableError, "A request to the storage service timed out"
|
207
|
+
end
|
208
|
+
|
209
|
+
# @param [String] token The token that wants to be checked
|
210
|
+
# @return [Boolean] Whether the token is valid or not
|
211
|
+
def valid_token?(token)
|
212
|
+
uri = URI(token)
|
213
|
+
uri.scheme == "bbbmap"
|
214
|
+
rescue URI::InvalidURIError
|
215
|
+
false
|
216
|
+
end
|
217
|
+
|
218
|
+
# Uses the mapping file retrieved to convert a token into URLs. The first item in the hash
|
219
|
+
# will be the first provider listed in the first matched label group etc.
|
220
|
+
#
|
221
|
+
# Will retrieve the mapping afresh if the mapping data has expired (is older than the
|
222
|
+
# timeout value used to initialise this object)
|
223
|
+
#
|
224
|
+
# @param [URI] token The token (as a URI object) to look up.
|
225
|
+
# @raise UnknownLabelError if no mappings exist for the token given.
|
226
|
+
# @raise InvalidTokenError if the given token string isn't a valid token
|
227
|
+
# @return [Hash] A hash of provider names (key) to URLs (value) for this asset.
|
228
|
+
def map(token)
|
229
|
+
raise InvalidTokenError unless valid_token?(token)
|
230
|
+
retrieve_mapping! if (Time.now.to_i - @mapping[:timestamp].to_i > @timeout)
|
231
|
+
@@logger.debug "Using mapping: #{@mapping[:data]}"
|
232
|
+
matched_providers = {}
|
233
|
+
@mapping[:data].each do |label_map|
|
234
|
+
re = Regexp.new(label_map['extractor'])
|
235
|
+
if token.match(re)
|
236
|
+
@@logger.debug "Using #{label_map['label']}"
|
237
|
+
capture_groups = Hash[
|
238
|
+
Regexp.last_match.names.zip(Regexp.last_match.captures)
|
239
|
+
].inject({}){ |memo, (k, v)|
|
240
|
+
memo[k.to_sym] = v
|
241
|
+
memo
|
242
|
+
}
|
243
|
+
label_map['providers'].each_pair do |name, url_template|
|
244
|
+
# If there are multiple matching label_maps then providers from the first will take priority over later ones.
|
245
|
+
begin
|
246
|
+
matched_providers[name] ||= (url_template % capture_groups)
|
247
|
+
rescue
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
matched_providers
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
class MissingAssetError < RuntimeError; end
|
257
|
+
class UnknownLabelError < RuntimeError; end
|
258
|
+
class InvalidTokenError < URI::InvalidURIError; end
|
259
|
+
class StorageServiceUnavailableError < RuntimeError; end
|
260
|
+
end
|
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: blinkbox-common_mapping
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- JP Hastings-Spital
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: blinkbox-common_messaging
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.5'
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.5.1
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.5'
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.5.1
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rake
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3.0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '3.0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rspec-mocks
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ! '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: webmock
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: simplecov
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
description: Deal with blinkbox Books virtual URLs
|
104
|
+
email:
|
105
|
+
- jphastings@blinkbox.com
|
106
|
+
executables: []
|
107
|
+
extensions: []
|
108
|
+
extra_rdoc_files:
|
109
|
+
- CHANGELOG.md
|
110
|
+
- README.md
|
111
|
+
files:
|
112
|
+
- CHANGELOG.md
|
113
|
+
- README.md
|
114
|
+
- VERSION
|
115
|
+
- lib/blinkbox/common_mapping.rb
|
116
|
+
homepage: ''
|
117
|
+
licenses: []
|
118
|
+
metadata: {}
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options: []
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ! '>='
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ! '>='
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
requirements: []
|
134
|
+
rubyforge_project:
|
135
|
+
rubygems_version: 2.4.5
|
136
|
+
signing_key:
|
137
|
+
specification_version: 4
|
138
|
+
summary: Deal with blinkbox Books virtual URLs
|
139
|
+
test_files: []
|