memcache-client 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Manifest.txt +5 -0
- data/README +44 -0
- data/Rakefile +55 -0
- data/lib/memcache.rb +361 -0
- data/lib/memcache_util.rb +68 -0
- metadata +49 -0
data/Manifest.txt
ADDED
data/README
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
= memcache-client
|
2
|
+
|
3
|
+
Rubyforge Project:
|
4
|
+
|
5
|
+
http://rubyforge.org/projects/rctools/
|
6
|
+
|
7
|
+
== About
|
8
|
+
|
9
|
+
memcache-client is a fast memcached client.
|
10
|
+
|
11
|
+
== Installing memcache-client
|
12
|
+
|
13
|
+
Just install the gem
|
14
|
+
|
15
|
+
$ sudo gem install memcache-client
|
16
|
+
|
17
|
+
== Using memcache-client
|
18
|
+
|
19
|
+
CACHE = MemCache.new :c_threshold => 10_000,
|
20
|
+
:compression => true,
|
21
|
+
:debug => false,
|
22
|
+
:namespace => 'my_namespace',
|
23
|
+
:readonly => false,
|
24
|
+
:urlencode => false
|
25
|
+
CACHE.servers = 'localhost:11211'
|
26
|
+
|
27
|
+
You can specify multiple servers by sending an Array of servers to #servers=.
|
28
|
+
|
29
|
+
=== Using memcache-client with Rails
|
30
|
+
|
31
|
+
Rails will automatically load the memcache-client gem, but you may wish to
|
32
|
+
uninstall Ruby-memcache, I don't know which one it will pick by default.
|
33
|
+
|
34
|
+
Add your environment-specific caches to config/environment/*. If you run both
|
35
|
+
development and production on the same machine be sure to use different
|
36
|
+
namespaces. Be careful when running tests under memcache, you may get strange
|
37
|
+
results, it will be less of a headache to simply use a readonly memcache when
|
38
|
+
testing.
|
39
|
+
|
40
|
+
memcache-client also comes with a wrapper called Cache in memcache_util.rb for
|
41
|
+
use with Rails. To use it be sure to assign your memcache connection to
|
42
|
+
CACHE. Cache returns nil on all memcache errors so you don't have to rescue
|
43
|
+
yourself. It has #get, #put and #delete module functions.
|
44
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
|
7
|
+
$VERBOSE = nil
|
8
|
+
|
9
|
+
spec = Gem::Specification.new do |s|
|
10
|
+
s.name = 'memcache-client'
|
11
|
+
s.version = '1.0.3'
|
12
|
+
s.summary = 'A Ruby memcached client'
|
13
|
+
s.author = 'Robert Cottrell'
|
14
|
+
s.email = 'bob@robotcoop.com'
|
15
|
+
|
16
|
+
s.has_rdoc = true
|
17
|
+
s.files = File.read('Manifest.txt').split($/)
|
18
|
+
s.require_path = 'lib'
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Run tests'
|
22
|
+
task :default => [ :test ]
|
23
|
+
|
24
|
+
Rake::TestTask.new('test') do |t|
|
25
|
+
t.libs << 'test'
|
26
|
+
t.pattern = 'test/test_*.rb'
|
27
|
+
t.verbose = true
|
28
|
+
end
|
29
|
+
|
30
|
+
desc 'Update Manifest.txt'
|
31
|
+
task :update_manifest do
|
32
|
+
sh "find . -type f | sed -e 's%./%%' | egrep -v 'svn|swp|~' | egrep -v '^(doc|pkg)/' | sort > Manifest.txt"
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'Generate RDoc'
|
36
|
+
Rake::RDocTask.new :rdoc do |rd|
|
37
|
+
rd.rdoc_dir = 'doc'
|
38
|
+
rd.rdoc_files.add 'lib', 'README', 'LICENSE'
|
39
|
+
rd.main = 'README'
|
40
|
+
rd.options << '-d' if `which dot` =~ /\/dot/
|
41
|
+
end
|
42
|
+
|
43
|
+
desc 'Build Gem'
|
44
|
+
Rake::GemPackageTask.new spec do |pkg|
|
45
|
+
pkg.need_tar = true
|
46
|
+
end
|
47
|
+
|
48
|
+
desc 'Clean up'
|
49
|
+
task :clean => [ :clobber_rdoc, :clobber_package ]
|
50
|
+
|
51
|
+
desc 'Clean up'
|
52
|
+
task :clobber => [ :clean ]
|
53
|
+
|
54
|
+
# vim: syntax=Ruby
|
55
|
+
|
data/lib/memcache.rb
ADDED
@@ -0,0 +1,361 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
# A Ruby client library for memcached.
|
5
|
+
#
|
6
|
+
# This is intended to provide access to basic memcached functionality. It
|
7
|
+
# does not attempt to be complete implementation of the entire API.
|
8
|
+
#
|
9
|
+
# In particular, the methods of this class are not thread safe. The calling
|
10
|
+
# application is responsible for implementing any necessary locking if a cache
|
11
|
+
# object will be called from multiple threads.
|
12
|
+
class MemCache
|
13
|
+
# Patterns for matching against server error replies.
|
14
|
+
GENERAL_ERROR = /^ERROR\r\n/
|
15
|
+
CLIENT_ERROR = /^CLIENT_ERROR/
|
16
|
+
SERVER_ERROR = /^SERVER_ERROR/
|
17
|
+
|
18
|
+
# Default options for the cache object.
|
19
|
+
DEFAULT_OPTIONS = {
|
20
|
+
:namespace => nil,
|
21
|
+
:readonly => false
|
22
|
+
}
|
23
|
+
|
24
|
+
# Default memcached port.
|
25
|
+
DEFAULT_PORT = 11211
|
26
|
+
|
27
|
+
# Default memcached server weight.
|
28
|
+
DEFAULT_WEIGHT = 1
|
29
|
+
|
30
|
+
# The amount of time to wait for a response from a memcached server. If a
|
31
|
+
# response is not completed within this time, the connection to the server
|
32
|
+
# will be closed and an error will be raised.
|
33
|
+
attr_accessor :request_timeout
|
34
|
+
|
35
|
+
# Valid options are:
|
36
|
+
#
|
37
|
+
# :namespace
|
38
|
+
# If specified, all keys will have the given value prepended
|
39
|
+
# before accessing the cache. Defaults to nil.
|
40
|
+
#
|
41
|
+
# :readonly
|
42
|
+
# If this is set, any attempt to write to the cache will generate
|
43
|
+
# an exception. Defaults to false.
|
44
|
+
#
|
45
|
+
def initialize(opts = {})
|
46
|
+
opts = DEFAULT_OPTIONS.merge(opts)
|
47
|
+
@namespace = opts[:namespace]
|
48
|
+
@readonly = opts[:readonly]
|
49
|
+
@mutex = Mutex.new
|
50
|
+
@servers = []
|
51
|
+
@buckets = []
|
52
|
+
end
|
53
|
+
|
54
|
+
# Return a string representation of the cache object.
|
55
|
+
def inspect
|
56
|
+
sprintf("<MemCache: %s servers, %s buckets, ns: %p, ro: %p>",
|
57
|
+
@servers.nitems, @buckets.nitems, @namespace, @readonly)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns whether there is at least one active server for the object.
|
61
|
+
def active?
|
62
|
+
not @servers.empty?
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns whether the cache was created read only.
|
66
|
+
def readonly?
|
67
|
+
@readonly
|
68
|
+
end
|
69
|
+
|
70
|
+
# Set the servers that the requests will be distributed between. Entries
|
71
|
+
# can be either strings of the form "hostname:port" or
|
72
|
+
# "hostname:port:weight" or MemCache::Server objects.
|
73
|
+
def servers=(servers)
|
74
|
+
# Create the server objects.
|
75
|
+
@servers = servers.collect do |server|
|
76
|
+
case server
|
77
|
+
when String
|
78
|
+
host, port, weight = server.split(/:/, 3)
|
79
|
+
port ||= DEFAULT_PORT
|
80
|
+
weight ||= DEFAULT_WEIGHT
|
81
|
+
Server::new(host, port, weight)
|
82
|
+
when Server
|
83
|
+
server
|
84
|
+
else
|
85
|
+
raise TypeError, "Cannot convert %s to MemCache::Server" %
|
86
|
+
svr.class.name
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Create an array of server buckets for weight selection of servers.
|
91
|
+
@buckets = []
|
92
|
+
@servers.each do |server|
|
93
|
+
server.weight.times { @buckets.push(server) }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def get(key)
|
98
|
+
@mutex.synchronize do
|
99
|
+
raise MemCacheError, "No active servers" unless self.active?
|
100
|
+
cache_key = make_cache_key(key)
|
101
|
+
server = get_server_for_key(cache_key)
|
102
|
+
|
103
|
+
sock = server.socket
|
104
|
+
if sock.nil?
|
105
|
+
raise MemCacheError, "No connection to server"
|
106
|
+
end
|
107
|
+
|
108
|
+
value = nil
|
109
|
+
begin
|
110
|
+
sock.write "get #{cache_key}\r\n"
|
111
|
+
text = sock.gets # "VALUE <key> <flags> <bytes>\r\n"
|
112
|
+
return nil if text =~ /^END/ # HACK: no regex
|
113
|
+
|
114
|
+
v, cache_key, flags, bytes = text.split(/ /)
|
115
|
+
value = sock.read(bytes.to_i)
|
116
|
+
sock.read(2) # "\r\n"
|
117
|
+
sock.gets # "END\r\n"
|
118
|
+
rescue SystemCallError, IOError => err
|
119
|
+
server.close
|
120
|
+
raise MemCacheError, err.message
|
121
|
+
end
|
122
|
+
|
123
|
+
# Return the unmarshaled value.
|
124
|
+
begin
|
125
|
+
return Marshal.load(value)
|
126
|
+
rescue ArgumentError, TypeError => err
|
127
|
+
server.close
|
128
|
+
raise MemCacheError, err.message
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Add an entry to the cache.
|
134
|
+
def set(key, value, expiry = 0)
|
135
|
+
@mutex.synchronize do
|
136
|
+
raise MemCacheError, "No active servers" unless self.active?
|
137
|
+
raise MemCacheError, "Update of readonly cache" if @readonly
|
138
|
+
cache_key = make_cache_key(key)
|
139
|
+
server = get_server_for_key(cache_key)
|
140
|
+
|
141
|
+
sock = server.socket
|
142
|
+
if sock.nil?
|
143
|
+
raise MemCacheError, "No connection to server"
|
144
|
+
end
|
145
|
+
|
146
|
+
marshaled_value = Marshal.dump(value)
|
147
|
+
command = "set #{cache_key} 0 #{expiry} #{marshaled_value.size}\r\n" + marshaled_value + "\r\n"
|
148
|
+
begin
|
149
|
+
sock.write command
|
150
|
+
sock.gets
|
151
|
+
rescue SystemCallError, IOError => err
|
152
|
+
server.close
|
153
|
+
raise MemCacheError, err.message
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Remove an entry from the cache.
|
159
|
+
def delete(key, expiry = 0)
|
160
|
+
@mutex.synchronize do
|
161
|
+
raise MemCacheError, "No active servers" unless self.active?
|
162
|
+
cache_key = make_cache_key(key)
|
163
|
+
server = get_server_for_key(cache_key)
|
164
|
+
|
165
|
+
sock = server.socket
|
166
|
+
if sock.nil?
|
167
|
+
raise MemCacheError, "No connection to server"
|
168
|
+
end
|
169
|
+
|
170
|
+
begin
|
171
|
+
sock.write "delete #{cache_key} #{expiry}\r\n"
|
172
|
+
sock.gets
|
173
|
+
rescue SystemCallError, IOError => err
|
174
|
+
server.close
|
175
|
+
raise MemCacheError, err.message
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Reset the connection to all memcache servers. This should be called if
|
181
|
+
# there is a problem with a cache lookup that might have left the
|
182
|
+
# connection in a corrupted state.
|
183
|
+
def reset
|
184
|
+
@mutex.synchronize do
|
185
|
+
@servers.each { |server| server.close }
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Shortcut to get a value from the cache.
|
190
|
+
def [](key)
|
191
|
+
self.get(key)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Shortcut to save a value in the cache. This method does not set an
|
195
|
+
# expiration on the entry. Use set to specify an explicit expiry.
|
196
|
+
def []=(key, value)
|
197
|
+
self.set(key, value)
|
198
|
+
end
|
199
|
+
|
200
|
+
# Create a key for the cache, incorporating the namespace qualifier if
|
201
|
+
# requested.
|
202
|
+
protected
|
203
|
+
def make_cache_key(key)
|
204
|
+
@namespace.nil? ? key.to_s : "#{@namespace}:#{key}"
|
205
|
+
end
|
206
|
+
|
207
|
+
# Pick a server to handle the request based on a hash of the key.
|
208
|
+
def get_server_for_key(key)
|
209
|
+
# Easy enough if there is only one server.
|
210
|
+
return @servers.first if @servers.length == 1
|
211
|
+
|
212
|
+
# Hash the value of the key to select the bucket.
|
213
|
+
hkey = key.hash
|
214
|
+
|
215
|
+
# Fetch a server for the given key, retrying if that server is
|
216
|
+
# offline.
|
217
|
+
server = nil
|
218
|
+
20.times do |tries|
|
219
|
+
server = @buckets[(hkey + tries) % @buckets.nitems]
|
220
|
+
break if server.alive?
|
221
|
+
end
|
222
|
+
|
223
|
+
raise MemCacheError, "No servers available" unless server
|
224
|
+
server
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
###########################################################################
|
229
|
+
# S E R V E R C L A S S
|
230
|
+
###########################################################################
|
231
|
+
|
232
|
+
# This class represents a memcached server instance.
|
233
|
+
class Server
|
234
|
+
# The amount of time to wait to establish a connection with a
|
235
|
+
# memcached server. If a connection cannot be established within
|
236
|
+
# this time limit, the server will be marked as down.
|
237
|
+
CONNECT_TIMEOUT = 0.25
|
238
|
+
|
239
|
+
# The amount of time to wait before attempting to re-establish a
|
240
|
+
# connection with a server that is marked dead.
|
241
|
+
RETRY_DELAY = 30.0
|
242
|
+
|
243
|
+
# The host the memcached server is running on.
|
244
|
+
attr_reader :host
|
245
|
+
|
246
|
+
# The port the memcached server is listening on.
|
247
|
+
attr_reader :port
|
248
|
+
|
249
|
+
# The weight given to the server.
|
250
|
+
attr_reader :weight
|
251
|
+
|
252
|
+
# The time of next retry if the connection is dead.
|
253
|
+
attr_reader :retry
|
254
|
+
|
255
|
+
# A text status string describing the state of the server.
|
256
|
+
attr_reader :status
|
257
|
+
|
258
|
+
# Create a new MemCache::Server object for the memcached instance
|
259
|
+
# listening on the given host and port, weighted by the given weight.
|
260
|
+
def initialize(host, port = DEFAULT_PORT, weight = DEFAULT_WEIGHT)
|
261
|
+
if host.nil? || host.empty?
|
262
|
+
raise ArgumentError, "No host specified"
|
263
|
+
elsif port.nil? || port.to_i.zero?
|
264
|
+
raise ArgumentError, "No port specified"
|
265
|
+
end
|
266
|
+
|
267
|
+
@host = host
|
268
|
+
@port = port.to_i
|
269
|
+
@weight = weight.to_i
|
270
|
+
|
271
|
+
@sock = nil
|
272
|
+
@retry = nil
|
273
|
+
@status = "NOT CONNECTED"
|
274
|
+
end
|
275
|
+
|
276
|
+
# Return a string representation of the server object.
|
277
|
+
def inspect
|
278
|
+
sprintf("<MemCache::Server: %s:%d [%d] (%s)>",
|
279
|
+
@host, @port, @weight, @status)
|
280
|
+
end
|
281
|
+
|
282
|
+
# Check whether the server connection is alive. This will cause the
|
283
|
+
# socket to attempt to connect if it isn't already connected and or if
|
284
|
+
# the server was previously marked as down and the retry time has
|
285
|
+
# been exceeded.
|
286
|
+
def alive?
|
287
|
+
!self.socket.nil?
|
288
|
+
end
|
289
|
+
|
290
|
+
# Try to connect to the memcached server targeted by this object.
|
291
|
+
# Returns the connected socket object on success or nil on failure.
|
292
|
+
def socket
|
293
|
+
# Attempt to connect if not already connected.
|
294
|
+
unless @sock || (!@sock.nil? && @sock.closed?)
|
295
|
+
# If the host was dead, don't retry for a while.
|
296
|
+
if @retry && (@retry > Time::now)
|
297
|
+
@sock = nil
|
298
|
+
else
|
299
|
+
begin
|
300
|
+
@sock = timeout(CONNECT_TIMEOUT) {
|
301
|
+
TCPSocket::new(@host, @port)
|
302
|
+
}
|
303
|
+
@retry = nil
|
304
|
+
@status = "CONNECTED"
|
305
|
+
rescue SystemCallError, IOError, Timeout::Error => err
|
306
|
+
self.mark_dead(err.message)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
@sock
|
311
|
+
end
|
312
|
+
|
313
|
+
# Close the connection to the memcached server targeted by this
|
314
|
+
# object. The server is not considered dead.
|
315
|
+
def close
|
316
|
+
@sock.close if @sock &&!@sock.closed?
|
317
|
+
@sock = nil
|
318
|
+
@retry = nil
|
319
|
+
@status = "NOT CONNECTED"
|
320
|
+
end
|
321
|
+
|
322
|
+
# Mark the server as dead and close its socket.
|
323
|
+
def mark_dead(reason = "Unknown error")
|
324
|
+
@sock.close if @sock && !@sock.closed?
|
325
|
+
@sock = nil
|
326
|
+
@retry = Time::now + RETRY_DELAY
|
327
|
+
|
328
|
+
@status = sprintf("DEAD: %s, will retry at %s", reason, @retry)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
|
333
|
+
###########################################################################
|
334
|
+
# E X C E P T I O N C L A S S E S
|
335
|
+
###########################################################################
|
336
|
+
|
337
|
+
# Base MemCache exception class.
|
338
|
+
class MemCacheError < ::Exception
|
339
|
+
end
|
340
|
+
|
341
|
+
# MemCache internal error class. Instances of this class mean that there
|
342
|
+
# is some internal error either in the memcache client library or the
|
343
|
+
# memcached server it is talking to.
|
344
|
+
class InternalError < MemCacheError
|
345
|
+
end
|
346
|
+
|
347
|
+
# MemCache client error class. Instances of this class mean that a
|
348
|
+
# "CLIENT_ERROR" response was seen in the dialog with a memcached server.
|
349
|
+
class ClientError < InternalError
|
350
|
+
end
|
351
|
+
|
352
|
+
# MemCache server error class. Instances of this class mean that a
|
353
|
+
# "SERVER_ERROR" response was seen in the dialog with a memcached server.
|
354
|
+
class ServerError < InternalError
|
355
|
+
attr_reader :server
|
356
|
+
|
357
|
+
def initalize(server)
|
358
|
+
@server = server
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
##
|
2
|
+
# A utility wrapper around the MemCache client to simplify cache access. All
|
3
|
+
# methods silently ignore MemCache errors.
|
4
|
+
|
5
|
+
module Cache
|
6
|
+
|
7
|
+
##
|
8
|
+
# Returns the object at +key+ from the cache if successful, or nil if
|
9
|
+
# either the object is not in the cache or if there was an error
|
10
|
+
# attermpting to access the cache.
|
11
|
+
|
12
|
+
def self.get(key)
|
13
|
+
start_time = Time.now.to_f
|
14
|
+
result = CACHE.get key
|
15
|
+
end_time = Time.now.to_f
|
16
|
+
ActiveRecord::Base.logger.debug(
|
17
|
+
sprintf("MemCache Get (%0.6f) %s",
|
18
|
+
end_time - start_time, key))
|
19
|
+
return result
|
20
|
+
rescue MemCache::MemCacheError => err
|
21
|
+
# MemCache error is a cache miss.
|
22
|
+
ActiveRecord::Base.logger.debug("MemCache Error: #{err.message}")
|
23
|
+
return nil
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Places +value+ in the cache at +key+, with an optional +expiry+ time in
|
28
|
+
# seconds. (?)
|
29
|
+
|
30
|
+
def self.put(key, value, expiry = 0)
|
31
|
+
start_time = Time.now.to_f
|
32
|
+
CACHE.set key, value, expiry
|
33
|
+
end_time = Time.now.to_f
|
34
|
+
ActiveRecord::Base.logger.debug(
|
35
|
+
sprintf("MemCache Set (%0.6f) %s",
|
36
|
+
end_time - start_time, key))
|
37
|
+
rescue MemCache::MemCacheError => err
|
38
|
+
# Ignore put failure.
|
39
|
+
ActiveRecord::Base.logger.debug("MemCache Error: #{err.message}")
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Deletes +key+ from the cache in +delay+ seconds. (?)
|
44
|
+
|
45
|
+
def self.delete(key, delay = nil)
|
46
|
+
start_time = Time.now.to_f
|
47
|
+
CACHE.delete key, delay
|
48
|
+
end_time = Time.now.to_f
|
49
|
+
ActiveRecord::Base.logger.debug(
|
50
|
+
sprintf("MemCache Delete (%0.6f) %s",
|
51
|
+
end_time - start_time, key))
|
52
|
+
rescue MemCache::MemCacheError => err
|
53
|
+
# Ignore delete failure.
|
54
|
+
ActiveRecord::Base.logger.debug("MemCache Error: #{err.message}")
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Resets all connections to mecache servers.
|
59
|
+
|
60
|
+
def self.reset
|
61
|
+
CACHE.reset
|
62
|
+
ActiveRecord::Base.logger.debug("MemCache Reset")
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
# vim: ts=4 sts=4 sw=4
|
68
|
+
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.11.6
|
3
|
+
specification_version: 1
|
4
|
+
name: memcache-client
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 1.0.3
|
7
|
+
date: 2006-01-17 00:00:00 -08:00
|
8
|
+
summary: A Ruby memcached client
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: bob@robotcoop.com
|
12
|
+
homepage:
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
authors:
|
29
|
+
- Robert Cottrell
|
30
|
+
files:
|
31
|
+
- Manifest.txt
|
32
|
+
- README
|
33
|
+
- Rakefile
|
34
|
+
- lib/memcache.rb
|
35
|
+
- lib/memcache_util.rb
|
36
|
+
test_files: []
|
37
|
+
|
38
|
+
rdoc_options: []
|
39
|
+
|
40
|
+
extra_rdoc_files: []
|
41
|
+
|
42
|
+
executables: []
|
43
|
+
|
44
|
+
extensions: []
|
45
|
+
|
46
|
+
requirements: []
|
47
|
+
|
48
|
+
dependencies: []
|
49
|
+
|