gdbm 0.9
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/README.md +30 -0
- data/lib/gdbm.rb +615 -0
- data/test/test_gdbm-1.8.7.rb +692 -0
- data/test/test_gdbm-1.9.1.rb +723 -0
- metadata +58 -0
data/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
## ffi-gdbm
|
|
2
|
+
|
|
3
|
+
An attempt to make gdbm available beyond the C Ruby implementation.
|
|
4
|
+
|
|
5
|
+
## Installing
|
|
6
|
+
|
|
7
|
+
You can download and use `gdbm.rb` anyhow you would like.
|
|
8
|
+
|
|
9
|
+
You can also install it using Ruby Gems:
|
|
10
|
+
|
|
11
|
+
`jgem install gdbm --source http://gemcutter.org`
|
|
12
|
+
|
|
13
|
+
## Notes
|
|
14
|
+
|
|
15
|
+
* Conforms to tests for 1.8.7 and 1.9.1 and follows the C library for MRI if there are contradictions with the documentation
|
|
16
|
+
* Should be compatible with gdbm files created with MRI
|
|
17
|
+
* Does not work with JRuby 1.3, try using it JRuby 1.4 or the master from http://github.com/jruby/jruby
|
|
18
|
+
* Only works with JRuby, as it relies on features from JRuby's FFI that are not available in Ruby FFI
|
|
19
|
+
|
|
20
|
+
## Status
|
|
21
|
+
|
|
22
|
+
Passes all tests from 1.8.7 and 1.9.1 except those related to [this JRuby bug](http://jira.codehaus.org/browse/JRUBY-4071), which will probably not matter to too many people. Once this is resolved, the version should go to 1.0.
|
|
23
|
+
|
|
24
|
+
## Testing
|
|
25
|
+
|
|
26
|
+
Two sets of tests are included, copied straight from the MRI distribution. However, they do require the use of ObjectSpace, so this is how to run them:
|
|
27
|
+
|
|
28
|
+
`jruby -X+O -r lib/gdbm test/test_gdbm-1.8.7.rb`
|
|
29
|
+
|
|
30
|
+
`jruby --1.9 -X+O -r lib/gdbm test/test_gdbm-1.9.1.rb`
|
data/lib/gdbm.rb
ADDED
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
JRuby access to gdbm via FFI. Faithfully mimics MRI's standard library and is
|
|
3
|
+
compatible with gdbm files produced by that version.
|
|
4
|
+
|
|
5
|
+
Author: Justin Collins
|
|
6
|
+
Based on the C version by: yugui
|
|
7
|
+
Website: http://github.com/presidentbeef/ffi-gdbm
|
|
8
|
+
Documentation: http://ruby-doc.org/stdlib/libdoc/gdbm/rdoc/classes/GDBM.html
|
|
9
|
+
JRuby: http://www.jruby.org/
|
|
10
|
+
gdbm: http://directory.fsf.org/project/gdbm/
|
|
11
|
+
|
|
12
|
+
The MIT License
|
|
13
|
+
|
|
14
|
+
Copyright (c) 2009, Justin Collins
|
|
15
|
+
|
|
16
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
17
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
18
|
+
in the Software without restriction, including without limitation the rights
|
|
19
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
20
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
21
|
+
furnished to do so, subject to the following conditions:
|
|
22
|
+
|
|
23
|
+
The above copyright notice and this permission notice shall be included in
|
|
24
|
+
all copies or substantial portions of the Software.
|
|
25
|
+
|
|
26
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
27
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
28
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
29
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
30
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
31
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
32
|
+
THE SOFTWARE.
|
|
33
|
+
=end
|
|
34
|
+
|
|
35
|
+
require 'ffi'
|
|
36
|
+
|
|
37
|
+
module GDBM_FFI
|
|
38
|
+
extend FFI::Library
|
|
39
|
+
ffi_lib "gdbm"
|
|
40
|
+
|
|
41
|
+
#Note that MRI does not store the null byte, so neither does this version,
|
|
42
|
+
#even though FFI automatically appends one to the String.
|
|
43
|
+
class Datum < FFI::Struct
|
|
44
|
+
layout :dptr, :pointer, :dsize, :int
|
|
45
|
+
|
|
46
|
+
#Expects either a MemoryPointer or a String as an argument.
|
|
47
|
+
#If it is given a String, it will initialize the fields, including
|
|
48
|
+
#setting dsize.
|
|
49
|
+
def initialize(*args)
|
|
50
|
+
if args.length == 0 or (args.length == 1 and args[0].is_a? FFI::MemoryPointer)
|
|
51
|
+
super
|
|
52
|
+
elsif args.length == 1 and args[0].is_a? String
|
|
53
|
+
super()
|
|
54
|
+
self.dptr = args[0]
|
|
55
|
+
self[:dsize] = args[0].length
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def value
|
|
60
|
+
if self[:dptr].nil? or self[:dptr].null?
|
|
61
|
+
nil
|
|
62
|
+
else
|
|
63
|
+
self[:dptr].read_string(self.size)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
#_Do not use_. Creates a new MemoryPointer from the String.
|
|
68
|
+
def dptr=(str)
|
|
69
|
+
@dptr = FFI::MemoryPointer.from_string(str)
|
|
70
|
+
self[:dptr] = @dptr
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
#Returns the size of the stored String.
|
|
74
|
+
def size
|
|
75
|
+
self[:dsize]
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
callback :fatal_func, [:string], :void
|
|
80
|
+
|
|
81
|
+
#Attach gdbm functions
|
|
82
|
+
attach_function :gdbm_open, [ :string, :int, :int, :int, :fatal_func ], :pointer
|
|
83
|
+
attach_function :close, :gdbm_close, [ :pointer ], :void
|
|
84
|
+
attach_function :gdbm_store, [ :pointer, Datum.by_value, Datum.by_value, :int ], :int
|
|
85
|
+
attach_function :gdbm_fetch, [ :pointer, Datum.by_value ], Datum.by_value
|
|
86
|
+
attach_function :gdbm_delete, [ :pointer, Datum.by_value ], :int
|
|
87
|
+
attach_function :gdbm_firstkey, [ :pointer ], Datum.by_value
|
|
88
|
+
attach_function :gdbm_nextkey, [ :pointer, Datum.by_value ], Datum.by_value
|
|
89
|
+
attach_function :reorganize, :gdbm_reorganize, [ :pointer ], :int
|
|
90
|
+
attach_function :sync, :gdbm_sync, [ :pointer ], :void
|
|
91
|
+
attach_function :gdbm_exists, [ :pointer, Datum.by_value ], :int
|
|
92
|
+
attach_function :set_opt, :gdbm_setopt, [ :pointer, :int, :pointer, :int ], :int
|
|
93
|
+
attach_function :error_string, :gdbm_strerror, [ :int ], :string
|
|
94
|
+
|
|
95
|
+
READER = 0
|
|
96
|
+
WRITER = 1
|
|
97
|
+
WRCREAT = 2
|
|
98
|
+
NEWDB = 3
|
|
99
|
+
FAST = 0x10
|
|
100
|
+
SYNC = 0x20
|
|
101
|
+
NOLOCK = 0x40
|
|
102
|
+
REPLACE = 1
|
|
103
|
+
CACHE_SIZE = 1
|
|
104
|
+
FAST_MODE = 2
|
|
105
|
+
SYNC_MODE = 3
|
|
106
|
+
CANT_BE_READER = 9
|
|
107
|
+
CANT_BE_WRITER = 10
|
|
108
|
+
FILE_OPEN_ERROR = 3
|
|
109
|
+
|
|
110
|
+
FATAL = Proc.new { |msg| raise RuntimeError, msg }
|
|
111
|
+
|
|
112
|
+
attach_variable :error_number, :gdbm_errno, :int
|
|
113
|
+
attach_variable :VERSION, :gdbm_version, :string
|
|
114
|
+
|
|
115
|
+
#Store the given Strings in _file_. _file_ is always GDBM_FILE pointer in these functions.
|
|
116
|
+
def self.store(file, key, value)
|
|
117
|
+
key_datum = Datum.new key
|
|
118
|
+
val_datum = Datum.new value
|
|
119
|
+
|
|
120
|
+
result = gdbm_store file, key_datum, val_datum, GDBM_FFI::REPLACE
|
|
121
|
+
raise GDBMError, last_error if result != 0
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
#Fetch a String from the _file_ matching the given _key_. Returns _nil_ if
|
|
125
|
+
#there is no such key.
|
|
126
|
+
def self.fetch(file, key)
|
|
127
|
+
key_datum = Datum.new key
|
|
128
|
+
|
|
129
|
+
val_datum = gdbm_fetch file, key_datum
|
|
130
|
+
|
|
131
|
+
val_datum.value
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
#Returns the first key in the _file_.
|
|
135
|
+
def self.first_key(file)
|
|
136
|
+
key_datum = GDBM_FFI.gdbm_firstkey file
|
|
137
|
+
key_datum.value
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
#Deletes the _key_ from the _file_.
|
|
141
|
+
def self.delete(file, key)
|
|
142
|
+
return nil if not self.exists? file, key
|
|
143
|
+
key_datum = Datum.new key
|
|
144
|
+
result = gdbm_delete file, key_datum
|
|
145
|
+
raise GDBMError, last_error if result != 0
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
#Checks if the _file_ contains the given _key_.
|
|
149
|
+
def self.exists?(file, key)
|
|
150
|
+
key_datum = Datum.new key
|
|
151
|
+
|
|
152
|
+
gdbm_exists(file, key_datum) != 0
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
#Iterates over each _key_, _value_ pair in the _file_.
|
|
156
|
+
def self.each_pair(file)
|
|
157
|
+
current = self.gdbm_firstkey file
|
|
158
|
+
until current.value.nil?
|
|
159
|
+
value = gdbm_fetch file, current
|
|
160
|
+
yield current.value, value.value
|
|
161
|
+
current = self.gdbm_nextkey file, current
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
#Iterates over each key in the _file_.
|
|
166
|
+
def self.each_key(file)
|
|
167
|
+
current = self.gdbm_firstkey file
|
|
168
|
+
until current.value.nil?
|
|
169
|
+
yield current.value
|
|
170
|
+
current = self.gdbm_nextkey file, current
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
#Iterates over each value in the _file_.
|
|
175
|
+
def self.each_value(file)
|
|
176
|
+
current = self.gdbm_firstkey file
|
|
177
|
+
until current.value.nil?
|
|
178
|
+
value = gdbm_fetch file, current
|
|
179
|
+
yield value.value
|
|
180
|
+
current = self.gdbm_nextkey file, current
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
#Deletes all keys and values from the _file_.
|
|
185
|
+
def self.clear(file)
|
|
186
|
+
until (key = self.gdbm_firstkey(file)).value.nil?
|
|
187
|
+
until key.value.nil?
|
|
188
|
+
next_key = self.gdbm_nextkey(file, key)
|
|
189
|
+
result = self.gdbm_delete file, key
|
|
190
|
+
raise GDBMError, last_error if result != 0
|
|
191
|
+
key = next_key
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
#Returns the last error encountered from the gdbm library as a String.
|
|
197
|
+
def self.last_error
|
|
198
|
+
error_string(error_number)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
#Opens a gdbm file. Returns the GDBM_FILE pointer, which should be treated
|
|
202
|
+
#as an opaque value, to be passed in to GDBM_FFI methods.
|
|
203
|
+
def self.open(filename, blocksize, flags, mode)
|
|
204
|
+
self.gdbm_open filename, blocksize, flags, mode, FATAL
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
#Sets the cache size.
|
|
208
|
+
def self.set_cache_size(file, size)
|
|
209
|
+
opt = MemoryPointer.new size
|
|
210
|
+
self.set_opt file, CACHE_SIZE, opt, opt.size
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
#Sets the sync mode.
|
|
214
|
+
def self.set_sync_mode(file, boolean)
|
|
215
|
+
if boolean
|
|
216
|
+
opt = MemoryPointer.new 1
|
|
217
|
+
else
|
|
218
|
+
opt = MemoryPointer.new 0
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
self.set_opt file, SYNC_MODE, opt, opt.size
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
class GDBMError < StandardError; end
|
|
226
|
+
class GDBMFatalError < Exception; end
|
|
227
|
+
|
|
228
|
+
class GDBM
|
|
229
|
+
include Enumerable
|
|
230
|
+
|
|
231
|
+
#This constant is to check if a flag is READER, WRITER, WRCREAT, or NEWDB
|
|
232
|
+
RUBY_GDBM_RW_BIT = 0x20000000
|
|
233
|
+
|
|
234
|
+
BLOCKSIZE = 2048
|
|
235
|
+
READER = GDBM_FFI::READER | RUBY_GDBM_RW_BIT
|
|
236
|
+
WRITER = GDBM_FFI::WRITER | RUBY_GDBM_RW_BIT
|
|
237
|
+
WRCREAT = GDBM_FFI::WRCREAT | RUBY_GDBM_RW_BIT
|
|
238
|
+
NEWDB = GDBM_FFI::NEWDB | RUBY_GDBM_RW_BIT
|
|
239
|
+
FAST = GDBM_FFI::FAST
|
|
240
|
+
SYNC = GDBM_FFI::SYNC
|
|
241
|
+
NOLOCK = GDBM_FFI::NOLOCK
|
|
242
|
+
VERSION = GDBM_FFI.VERSION
|
|
243
|
+
|
|
244
|
+
def initialize(filename, mode = 0666, flags = nil)
|
|
245
|
+
|
|
246
|
+
mode = -1 if mode.nil?
|
|
247
|
+
flags = 0 if flags.nil?
|
|
248
|
+
@file = nil
|
|
249
|
+
|
|
250
|
+
if flags & RUBY_GDBM_RW_BIT != 0 #Check if flags are appropriate
|
|
251
|
+
flags &= ~RUBY_GDBM_RW_BIT #Remove check to make flag match GDBM constants
|
|
252
|
+
|
|
253
|
+
@file = GDBM_FFI.open filename, BLOCKSIZE, flags, mode
|
|
254
|
+
else
|
|
255
|
+
if mode >= 0
|
|
256
|
+
@file = GDBM_FFI.open filename, BLOCKSIZE, WRCREAT | flags, mode
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
@file = GDBM_FFI.open filename, BLOCKSIZE, WRITER | flags, 0 if @file.nil? or @file.null?
|
|
260
|
+
@file = GDBM_FFI.open filename, BLOCKSIZE, READER | flags, 0 if @file.nil? or @file.null?
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
if @file.nil? or @file.null?
|
|
264
|
+
return if mode == -1 #C code returns Qnil, but we can't
|
|
265
|
+
if GDBM_FFI.error_number == GDBM_FFI::FILE_OPEN_ERROR ||
|
|
266
|
+
GDBM_FFI.error_number == GDBM_FFI::CANT_BE_READER ||
|
|
267
|
+
GDBM_FFI.error_number == GDBM_FFI::CANT_BE_WRITER
|
|
268
|
+
|
|
269
|
+
raise SystemCallError.new(GDBM_FFI.last_error, FFI.errno)
|
|
270
|
+
else
|
|
271
|
+
raise GDBMError, GDBM_FFI.last_error;
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def self.open(filename, mode = 0666, flags = nil)
|
|
277
|
+
obj = self.new filename, mode, flags
|
|
278
|
+
|
|
279
|
+
if block_given?
|
|
280
|
+
begin
|
|
281
|
+
result = yield obj
|
|
282
|
+
ensure
|
|
283
|
+
obj.close unless obj.closed?
|
|
284
|
+
end
|
|
285
|
+
result
|
|
286
|
+
elsif obj.nil?
|
|
287
|
+
nil
|
|
288
|
+
else
|
|
289
|
+
obj
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def [](key)
|
|
294
|
+
GDBM_FFI.fetch file, key
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def []=(key, value)
|
|
298
|
+
modifiable?
|
|
299
|
+
GDBM_FFI.store file, key, value
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
alias :store :[]=
|
|
303
|
+
|
|
304
|
+
def cachesize=(size)
|
|
305
|
+
GDBM_FFI.set_cache_size file, size
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def clear
|
|
309
|
+
modifiable?
|
|
310
|
+
GDBM_FFI.clear file
|
|
311
|
+
self
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def close
|
|
315
|
+
if closed?
|
|
316
|
+
raise RuntimeError, "closed GDBM file"
|
|
317
|
+
else
|
|
318
|
+
GDBM_FFI.close @file
|
|
319
|
+
@file = nil unless frozen?
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def closed?
|
|
324
|
+
@file.nil? or @file.null?
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def delete(key)
|
|
328
|
+
modifiable?
|
|
329
|
+
value = self[key]
|
|
330
|
+
#This is bizarre and not mentioned in the docs,
|
|
331
|
+
#but this is what the tests expect and what the MRI
|
|
332
|
+
#version does.
|
|
333
|
+
if value.nil? and block_given?
|
|
334
|
+
value = yield key
|
|
335
|
+
end
|
|
336
|
+
GDBM_FFI.delete file, key
|
|
337
|
+
value
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def delete_if
|
|
341
|
+
modifiable?
|
|
342
|
+
rejects = []
|
|
343
|
+
begin
|
|
344
|
+
GDBM_FFI.each_pair(file) do |k,v|
|
|
345
|
+
if yield k, v
|
|
346
|
+
rejects << k
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
#unsure about this, but it handles breaking during
|
|
350
|
+
#the iteration
|
|
351
|
+
ensure
|
|
352
|
+
rejects.each do |k|
|
|
353
|
+
GDBM_FFI.delete file, k
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
self
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
alias :reject! :delete_if
|
|
361
|
+
|
|
362
|
+
def each_key(&block)
|
|
363
|
+
GDBM_FFI.each_key file, &block
|
|
364
|
+
self
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def each_pair(&block)
|
|
368
|
+
GDBM_FFI.each_pair file, &block
|
|
369
|
+
self
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
alias :each :each_pair
|
|
373
|
+
|
|
374
|
+
def each_value(&block)
|
|
375
|
+
GDBM_FFI.each_value file, &block
|
|
376
|
+
self
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def empty?
|
|
380
|
+
key = GDBM_FFI.first_key file
|
|
381
|
+
key.nil?
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def fastmode=(boolean)
|
|
385
|
+
GDBM_FFI.set_sync_mode file, !boolean
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def fetch(key, default = nil)
|
|
389
|
+
result = GDBM_FFI.fetch file, key
|
|
390
|
+
if result.nil?
|
|
391
|
+
if default
|
|
392
|
+
default
|
|
393
|
+
elsif block_given?
|
|
394
|
+
yield key
|
|
395
|
+
else
|
|
396
|
+
raise IndexError, "key not found"
|
|
397
|
+
end
|
|
398
|
+
else
|
|
399
|
+
result
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def has_key?(key)
|
|
404
|
+
GDBM_FFI.exists? file, key
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
alias :key? :has_key?
|
|
408
|
+
alias :member? :has_key?
|
|
409
|
+
alias :include? :has_key?
|
|
410
|
+
|
|
411
|
+
def has_value?(value)
|
|
412
|
+
GDBM_FFI.each_value(file) do |v|
|
|
413
|
+
if v == value
|
|
414
|
+
return true
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
false
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
alias :value? :has_value?
|
|
421
|
+
|
|
422
|
+
def index(value)
|
|
423
|
+
if RUBY_VERSION >= "1.9"
|
|
424
|
+
warn "GDBM#index is deprecated; use GDBM#key"
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
self.key(value)
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def invert
|
|
431
|
+
result = {}
|
|
432
|
+
|
|
433
|
+
GDBM_FFI.each_pair(file) do |k,v|
|
|
434
|
+
result[v] = k
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
result
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def key(value)
|
|
441
|
+
GDBM_FFI.each_pair(file) do |k,v|
|
|
442
|
+
if v == value
|
|
443
|
+
return k
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
nil
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def keys
|
|
450
|
+
keys = []
|
|
451
|
+
|
|
452
|
+
GDBM_FFI.each_key(file) do |k|
|
|
453
|
+
keys << k
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
keys
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def length
|
|
460
|
+
len = 0
|
|
461
|
+
|
|
462
|
+
GDBM_FFI.each_key(file) do |k|
|
|
463
|
+
len = len + 1
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
len
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
alias :size :length
|
|
470
|
+
|
|
471
|
+
def nil?
|
|
472
|
+
@file.nil? or @file.null?
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
def reject
|
|
476
|
+
result = {}
|
|
477
|
+
|
|
478
|
+
GDBM_FFI.each_pair(file) do |k,v|
|
|
479
|
+
if not yield k, v
|
|
480
|
+
result[k] = v
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
result
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def reorganize
|
|
488
|
+
modifiable?
|
|
489
|
+
GDBM_FFI.reorganize file
|
|
490
|
+
self
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def replace(other)
|
|
494
|
+
self.clear
|
|
495
|
+
self.update other
|
|
496
|
+
self
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
def select(*args)
|
|
500
|
+
result = []
|
|
501
|
+
#This method behaves completely contrary to what the docs state:
|
|
502
|
+
#http://ruby-doc.org/stdlib/libdoc/gdbm/rdoc/classes/GDBM.html#M000318
|
|
503
|
+
#Instead, it yields a pair and returns [[k1, v1], [k2, v2], ...]
|
|
504
|
+
#But this is how it is in 1.8.7 and 1.9.1, so...
|
|
505
|
+
|
|
506
|
+
if block_given?
|
|
507
|
+
if args.length > 0
|
|
508
|
+
raise ArgumentError, "wrong number of arguments(#{args.length} for 0)"
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
GDBM_FFI.each_pair(file) do |k, v|
|
|
512
|
+
if yield k, v
|
|
513
|
+
result << [k, v]
|
|
514
|
+
end
|
|
515
|
+
end
|
|
516
|
+
#This is for 1.8.7 compatibility
|
|
517
|
+
elsif RUBY_VERSION <= "1.8.7"
|
|
518
|
+
warn "GDBM#select(index..) is deprecated; use GDBM#values_at"
|
|
519
|
+
|
|
520
|
+
result = values_at(*args)
|
|
521
|
+
else
|
|
522
|
+
result = []
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
result
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def shift
|
|
529
|
+
modifiable?
|
|
530
|
+
key = GDBM_FFI.first_key file
|
|
531
|
+
if key
|
|
532
|
+
value = GDBM_FFI.fetch file, key
|
|
533
|
+
GDBM_FFI.delete file, key
|
|
534
|
+
[key, value]
|
|
535
|
+
else
|
|
536
|
+
nil
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
def sync
|
|
541
|
+
modifiable?
|
|
542
|
+
GDBM_FFI.sync file
|
|
543
|
+
self
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
def syncmode=(boolean)
|
|
547
|
+
GDBM_FFI.set_sync_mode file, boolean
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
def to_a
|
|
551
|
+
result = []
|
|
552
|
+
GDBM_FFI.each_pair(file) do |k,v|
|
|
553
|
+
result << [k, v]
|
|
554
|
+
end
|
|
555
|
+
result
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
def to_hash
|
|
559
|
+
result = {}
|
|
560
|
+
GDBM_FFI.each_pair(file) do |k,v|
|
|
561
|
+
result[k] = v
|
|
562
|
+
end
|
|
563
|
+
result
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def update(other)
|
|
567
|
+
other.each_pair do |k,v|
|
|
568
|
+
GDBM_FFI.store file, k, v
|
|
569
|
+
end
|
|
570
|
+
self
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
def values
|
|
574
|
+
values = []
|
|
575
|
+
|
|
576
|
+
GDBM_FFI.each_value(file) do |v|
|
|
577
|
+
values << v
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
values
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
def values_at(*keys)
|
|
584
|
+
results = []
|
|
585
|
+
|
|
586
|
+
keys.each do |k|
|
|
587
|
+
results << self[k]
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
results
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
private
|
|
594
|
+
|
|
595
|
+
def modifiable?
|
|
596
|
+
#raise SecurityError, "Insecure operation at level #$SAFE" if $SAFE >= 4 #Not currently supported in JRuby
|
|
597
|
+
if self.frozen?
|
|
598
|
+
if RUBY_VERSION > "1.8.7"
|
|
599
|
+
raise RuntimeError, "Can't modify frozen #{self}"
|
|
600
|
+
else
|
|
601
|
+
raise TypeError, "Can't modify frozen #{self}"
|
|
602
|
+
end
|
|
603
|
+
end
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
#Raises a RuntimeError if the file is closed.
|
|
607
|
+
def file
|
|
608
|
+
unless @file.nil? or @file.null?
|
|
609
|
+
@file
|
|
610
|
+
else
|
|
611
|
+
raise(RuntimeError, "closed GDBM file")
|
|
612
|
+
end
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
|