gdbm 1.0 → 1.1

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