cachext 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: 3072a5999fa8a90d21259de13caec3f355eebdc4
4
+ data.tar.gz: 7ef116659a1bfa08174f3c27157752a3bdd13c8d
5
+ SHA512:
6
+ metadata.gz: 1c7308ee711bb3c43522f1801fe51f6de0653057a2e6cccd7e9ae0e27be8d42e7ce5285b3287a371f1fe5b26b6ffee1e789db1a1a793ec26fdc991a7ded85489
7
+ data.tar.gz: 3cb8a48dcd02fac5070bb695e35064fe5562f8303265c334e8846c44bf0a358e6b011e5a63ce60dfe1ea5d61e096f5f070652668ec2dea0fe929fd29d9cf84fe
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.3
4
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cachext.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Donald Plummer
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,48 @@
1
+ # Cachext
2
+
3
+ Extensions to normal Rails caching:
4
+
5
+ * Lock
6
+ * Backup
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'cachext'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ ## Usage
21
+
22
+ Replace `Rails.cache.fetch` with `Cachext.fetch`.
23
+
24
+ ## Development
25
+
26
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
27
+ `rake spec` to run the tests. You can also run `bin/console` for an interactive
28
+ prompt that will allow you to experiment.
29
+
30
+ To install this gem onto your local machine, run `bundle exec rake install`. To
31
+ release a new version, update the version number in `version.rb`, and then run
32
+ `bundle exec rake release`, which will create a git tag for the version, push
33
+ git commits and tags, and push the `.gem` file to
34
+ [rubygems.org](https://rubygems.org).
35
+
36
+ Having trouble with a test? Set the `CACHEXT_DEBUG` environmental variable to
37
+ "true" to get debug logs.
38
+
39
+ ## Contributing
40
+
41
+ Bug reports and pull requests are welcome on GitHub at
42
+ https://github.com/dplummer/cachext.
43
+
44
+ ## License
45
+
46
+ The gem is available as open source under the terms of the
47
+ [MIT License](http://opensource.org/licenses/MIT).
48
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "cachext"
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,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/cachext.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cachext/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "cachext"
8
+ spec.version = Cachext::VERSION
9
+ spec.authors = ["Donald Plummer"]
10
+ spec.email = ["donald.plummer@gmail.com"]
11
+
12
+ spec.summary = %q{Cache with lock and backup extensions}
13
+ spec.description = %q{Don't calculate the cached value twice at the same time. Use a backup of the data if the service is down.}
14
+ spec.homepage = "https://github.com/dplummer/cachext"
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 "activesupport"
23
+ spec.add_dependency "redis"
24
+ spec.add_dependency "redis-namespace"
25
+ spec.add_dependency "redlock"
26
+ spec.add_dependency "faraday"
27
+ spec.add_dependency "dalli"
28
+
29
+ spec.add_development_dependency "bundler", "~> 1.10"
30
+ spec.add_development_dependency "rake", "~> 10.0"
31
+ spec.add_development_dependency "rspec"
32
+ spec.add_development_dependency "pry"
33
+ spec.add_development_dependency "thread"
34
+ end
data/lib/cachext.rb ADDED
@@ -0,0 +1,39 @@
1
+ require "cachext/version"
2
+ require "faraday/error"
3
+
4
+ module Cachext
5
+ autoload :Client, "cachext/client"
6
+ autoload :Configuration, "cachext/configuration"
7
+ autoload :Key, "cachext/key"
8
+ autoload :Features, "cachext/features"
9
+ autoload :Options, "cachext/options"
10
+
11
+ def self.Key raw_key
12
+ raw_key.is_a?(Key) ? raw_key : Key.new(raw_key)
13
+ end
14
+
15
+ def self.fetch raw_key, overrides = {}, &block
16
+ client.fetch Key(raw_key), overrides, &block
17
+ end
18
+
19
+ def self.clear raw_key
20
+ Key(raw_key).clear
21
+ end
22
+
23
+ def self.locked? raw_key
24
+ Key(raw_key).locked?
25
+ end
26
+
27
+ def self.client
28
+ @client ||= Client.new config
29
+ end
30
+
31
+ def self.config
32
+ @config ||= Configuration.new
33
+ end
34
+
35
+ def self.flush
36
+ config.cache.clear
37
+ config.redis.del "cachext:*"
38
+ end
39
+ end
@@ -0,0 +1,55 @@
1
+ require "digest/sha1"
2
+
3
+ module Cachext
4
+ class Client
5
+ prepend Features::DebugLogging
6
+ prepend Features::Lock
7
+ prepend Features::Backup
8
+ prepend Features::Default
9
+
10
+ def initialize config
11
+ @config = config
12
+ end
13
+
14
+ def fetch key, options_hash, &block
15
+ options = Options.new @config, options_hash
16
+
17
+ retval = read key, options
18
+ return retval unless retval.nil?
19
+
20
+ call_block key, options, &block
21
+
22
+ rescue *Array(options.not_found_error) => e
23
+ handle_not_found key, options, e
24
+ rescue Features::Lock::TimeoutWaitingForLock, *options.errors => e
25
+ handle_error key, options, e
26
+ end
27
+
28
+ private
29
+
30
+ def call_block key, options, &block
31
+ fresh = block.call
32
+
33
+ write key, fresh, options
34
+
35
+ fresh
36
+ end
37
+
38
+ def handle_not_found key, options, error
39
+ raise if options.reraise_errors
40
+ end
41
+
42
+ def handle_error key, options, error
43
+ @config.error_logger.error error
44
+ raise if @config.raise_errors && reraise_errors
45
+ end
46
+
47
+ def read key, options
48
+ key.read
49
+ end
50
+
51
+ def write key, fresh, options
52
+ key.write fresh, expires_in: options.expires_in
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,40 @@
1
+ require "redlock"
2
+ require "redis-namespace"
3
+
4
+ module Cachext
5
+ class Configuration
6
+ attr_accessor :raise_errors, # raise all errors all the time? (for tests)
7
+ :default_expires_in, # in seconds
8
+ :cache, # conform to ActiveSupport::Cache interface
9
+ :redis, # redis client for locking
10
+ :error_logger, # conform to Honeybadger interface
11
+ :default_errors, # array of errors to catch and not reraise
12
+ :not_found_errors, # array of errors where we delete the backup and reraise
13
+ :max_lock_wait, # time in seconds to wait for a lock
14
+ :debug, # output debug messages to STDERR
15
+ :heartbeat_expires # time in seconds for process heardbeat to expire
16
+
17
+ def initialize
18
+ self.raise_errors = false
19
+ self.default_errors = [
20
+ Faraday::Error::ConnectionFailed,
21
+ Faraday::Error::TimeoutError,
22
+ ]
23
+ self.not_found_errors = [
24
+ Faraday::Error::ResourceNotFound,
25
+ ]
26
+ self.default_expires_in = 60
27
+ self.max_lock_wait = 5
28
+ self.debug = ENV['CACHEXT_DEBUG'] == "true"
29
+ self.heartbeat_expires = 2
30
+ end
31
+
32
+ def lock_manager
33
+ @lock_manager ||= Redlock::Client.new [lock_redis], retry_count: 1
34
+ end
35
+
36
+ def lock_redis
37
+ @lock_redis ||= Redis::Namespace.new :cachext, redis: redis
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,8 @@
1
+ module Cachext
2
+ module Features
3
+ autoload :Backup, "cachext/features/backup"
4
+ autoload :DebugLogging, "cachext/features/debug_logging"
5
+ autoload :Default, "cachext/features/default"
6
+ autoload :Lock, "cachext/features/lock"
7
+ end
8
+ end
@@ -0,0 +1,20 @@
1
+ module Cachext
2
+ module Features
3
+ module Backup
4
+ def handle_not_found key, options, error
5
+ key.delete_backup
6
+ super
7
+ end
8
+
9
+ def handle_error key, options, error
10
+ super
11
+ key.read_backup
12
+ end
13
+
14
+ def write key, fresh, options
15
+ super
16
+ key.write_backup fresh
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,50 @@
1
+ module Cachext
2
+ module Features
3
+ module DebugLogging
4
+
5
+ private
6
+
7
+ def read key, options
8
+ retval = super
9
+ debug_log { { m: :read, key: key, retval: retval } }
10
+ retval
11
+ end
12
+
13
+ def handle_not_found key, options, error
14
+ debug_log { { m: :handle_not_found, key: key, error: error, reraise_errors: options.reraise_errors } }
15
+ super
16
+ end
17
+
18
+ def handle_error key, options, error
19
+ debug_log { { m: :handle_error, key: key, error: error } }
20
+ super
21
+ end
22
+
23
+ def obtain_lock key, options
24
+ lock_info = super
25
+ debug_log { { m: :obtain_lock, key: key }.merge(lock_info) }
26
+ lock_info
27
+ end
28
+
29
+ def wait_for_lock key, start_time
30
+ debug_log { { m: :wait_for_lock, key: key, waited: (Time.now - start_time) } }
31
+ super
32
+ end
33
+
34
+ def write key, fresh, options
35
+ super
36
+ debug_log { { m: :write, key: key, fresh: fresh, expires_in: options.expires_in, read: key.read } }
37
+ end
38
+
39
+ def debug_log
40
+ if @config.debug
41
+ Thread.exclusive do
42
+ log = yield
43
+ msg = log.is_a?(String) ? log : log.inspect
44
+ $stderr.puts "[#{Time.now.to_s(:db)}] [#{Process.pid} #{Thread.current.object_id.to_s(16)}] #{msg}"
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,14 @@
1
+ module Cachext
2
+ module Features
3
+ module Default
4
+ def handle_not_found key, options, error
5
+ super
6
+ options.default.respond_to?(:call) ? options.default.call(key) : options.default
7
+ end
8
+
9
+ def handle_error key, options, error
10
+ super || (options.default.respond_to?(:call) ? options.default.call(key) : options.default)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,64 @@
1
+ module Cachext
2
+ module Features
3
+ module Lock
4
+ TimeoutWaitingForLock = Class.new(StandardError)
5
+
6
+ def read key, options
7
+ retval = super
8
+ return retval unless retval.nil?
9
+
10
+ @lock_info = obtain_lock key, options
11
+
12
+ retval = super
13
+
14
+ if !retval.nil?
15
+ @config.lock_manager.unlock @lock_info
16
+ end
17
+
18
+ retval
19
+ end
20
+
21
+ def call_block key, options, &block
22
+ with_heartbeat_extender key.digest, options.heartbeat_expires do
23
+ super
24
+ end
25
+ end
26
+
27
+ def with_heartbeat_extender lock_key, heartbeat_expires, &block
28
+ done = false
29
+ heartbeat_frequency = heartbeat_expires / 2
30
+
31
+ Thread.new do
32
+ loop do
33
+ break if done
34
+ sleep heartbeat_frequency
35
+ break if done
36
+ @config.lock_manager.lock lock_key, (heartbeat_expires * 1000).ceil, extend: @lock_info
37
+ end
38
+ end
39
+
40
+ block.call
41
+ ensure
42
+ @config.lock_manager.unlock @lock_info
43
+ done = true
44
+ end
45
+
46
+ def obtain_lock key, options
47
+ start_time = Time.now
48
+
49
+ until lock_info = @config.lock_manager.lock(key.digest, (options.heartbeat_expires * 1000).ceil)
50
+ wait_for_lock key, start_time
51
+ end
52
+
53
+ lock_info
54
+ end
55
+
56
+ def wait_for_lock key, start_time
57
+ sleep rand
58
+ if Time.now - start_time > @config.max_lock_wait
59
+ raise TimeoutWaitingForLock
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,60 @@
1
+ module Cachext
2
+ class Key
3
+ attr_reader :raw
4
+
5
+ def initialize(raw, config: Cachext.config)
6
+ @raw = Array(raw)
7
+ @config = config
8
+ end
9
+
10
+ def inspect
11
+ "#<Cachext::Key:#{object_id.to_s(16)} @raw=#{@raw.inspect} digest=#{digest}>"
12
+ end
13
+
14
+ def digest
15
+ ::Digest::SHA1.hexdigest ::Marshal.dump(raw)
16
+ end
17
+
18
+ def backup
19
+ [:backup_cache] + raw
20
+ end
21
+
22
+ def locked?
23
+ lock_redis.exists digest
24
+ end
25
+
26
+ def read
27
+ cache.read raw
28
+ end
29
+
30
+ def write value, options = {}
31
+ cache.write raw, value, options
32
+ end
33
+
34
+ def clear
35
+ cache.delete raw
36
+ end
37
+
38
+ def read_backup
39
+ cache.read backup
40
+ end
41
+
42
+ def write_backup value
43
+ cache.write backup, value
44
+ end
45
+
46
+ def delete_backup
47
+ cache.delete backup
48
+ end
49
+
50
+ private
51
+
52
+ def cache
53
+ @config.cache
54
+ end
55
+
56
+ def lock_redis
57
+ @config.lock_redis
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,25 @@
1
+ require "ostruct"
2
+ module Cachext
3
+ class Options
4
+ attr_reader :expires_in,
5
+ :default,
6
+ :errors,
7
+ :reraise_errors,
8
+ :not_found_error,
9
+ :heartbeat_expires
10
+
11
+ def initialize config, expires_in: config.default_expires_in,
12
+ default: nil,
13
+ errors: config.default_errors,
14
+ reraise_errors: true,
15
+ not_found_error: config.not_found_errors,
16
+ heartbeat_expires: config.heartbeat_expires
17
+ @expires_in = expires_in
18
+ @default = default
19
+ @errors = errors
20
+ @reraise_errors = reraise_errors
21
+ @not_found_error = not_found_error
22
+ @heartbeat_expires = heartbeat_expires
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module Cachext
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,220 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cachext
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Donald Plummer
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-12-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
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: redis
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: redis-namespace
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: redlock
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: faraday
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: dalli
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: bundler
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.10'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.10'
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: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: thread
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description: Don't calculate the cached value twice at the same time. Use a backup
168
+ of the data if the service is down.
169
+ email:
170
+ - donald.plummer@gmail.com
171
+ executables: []
172
+ extensions: []
173
+ extra_rdoc_files: []
174
+ files:
175
+ - ".gitignore"
176
+ - ".rspec"
177
+ - ".travis.yml"
178
+ - Gemfile
179
+ - LICENSE.txt
180
+ - README.md
181
+ - Rakefile
182
+ - bin/console
183
+ - bin/setup
184
+ - cachext.gemspec
185
+ - lib/cachext.rb
186
+ - lib/cachext/client.rb
187
+ - lib/cachext/configuration.rb
188
+ - lib/cachext/features.rb
189
+ - lib/cachext/features/backup.rb
190
+ - lib/cachext/features/debug_logging.rb
191
+ - lib/cachext/features/default.rb
192
+ - lib/cachext/features/lock.rb
193
+ - lib/cachext/key.rb
194
+ - lib/cachext/options.rb
195
+ - lib/cachext/version.rb
196
+ homepage: https://github.com/dplummer/cachext
197
+ licenses:
198
+ - MIT
199
+ metadata: {}
200
+ post_install_message:
201
+ rdoc_options: []
202
+ require_paths:
203
+ - lib
204
+ required_ruby_version: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
209
+ required_rubygems_version: !ruby/object:Gem::Requirement
210
+ requirements:
211
+ - - ">="
212
+ - !ruby/object:Gem::Version
213
+ version: '0'
214
+ requirements: []
215
+ rubyforge_project:
216
+ rubygems_version: 2.4.5.1
217
+ signing_key:
218
+ specification_version: 4
219
+ summary: Cache with lock and backup extensions
220
+ test_files: []