memcache-client 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|