ffi-gdbm 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +56 -0
  3. data/lib/gdbm.rb +608 -0
  4. metadata +44 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fba866c16c1d442d2af541352007d8680de4c62f
4
+ data.tar.gz: 5a08a11f7da622796c38c0e361c849d1b3941a14
5
+ SHA512:
6
+ metadata.gz: c295ceb52a12db3405a436f5ce77f9862a357fd1872f593233d97ad2579eefe9df3d4cc855f0da5957ab76c5c0c45bf0a9fbe52b79ae7df8385b5a8d2df8527c
7
+ data.tar.gz: 95d952f4b05fd4860e594116e5df97c227854acb7056343f68ad98601505ecaab52f3017fe2c1090637e6818c80e8f5ab341b43de151fbeb08a41572e6e78101
@@ -0,0 +1,56 @@
1
+ ## ffi-gdbm
2
+
3
+ An attempt to make [gdbm](http://www.vivtek.com/gdbm/) available beyond the C Ruby implementation.
4
+
5
+ Faithfully mimics MRI's standard library and is compatible with gdbm files produced by that version.
6
+
7
+ ## Installing
8
+
9
+ You can download and use `gdbm.rb` anyhow you would like.
10
+
11
+ You can also install it using Ruby Gems:
12
+
13
+ `gem install ffi-gdbm`
14
+
15
+ or, if using JRuby:
16
+
17
+ `jgem install gdbm`
18
+
19
+ JRuby does not require further installation, but Rubinius will need the FFI gem:
20
+
21
+ `gem install ffi`
22
+
23
+ ## Notes
24
+
25
+ * 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
26
+ * Should be compatible with gdbm files created with MRI's standard library
27
+ * Certainly works with JRuby, may work with other alternative Ruby implementations
28
+
29
+ ## Status
30
+
31
+ Tests passing on 64 bit Linux with
32
+
33
+ * JRuby 1.7.21 and 9.1.7.0
34
+
35
+ ### Older Tests
36
+
37
+ Passing all tests with JRuby 1.4, 1.5.3, 1.6 on 32-bit Linux.
38
+
39
+ Passing all tests with MRI Ruby 1.8.7, 1.9.1, 1.9.2 with Ruby-FFI 0.5.4, 0.6.3, 1.0.7 on 32-bit Linux.
40
+
41
+ Further testing on other systems is welcome!
42
+
43
+ ## Testing
44
+
45
+ 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:
46
+
47
+ `jruby --1.8 -X+O -r lib/gdbm test/test_gdbm-1.8.7.rb` (Note: these tests only work with JRuby prior to 9.0.0.0)
48
+
49
+ `jruby -X+O -r ./lib/gdbm test/test_gdbm-1.9.1.rb`
50
+
51
+ ## License
52
+
53
+ Copyright (c), Justin Collins
54
+
55
+ This library is released under the same tri-license (GPL/LGPL/CPL) as JRuby.
56
+ Please see the COPYING file distributed with JRuby for details.
@@ -0,0 +1,608 @@
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
+ Copyright (c) 2009, Justin Collins
13
+
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.
16
+ =end
17
+
18
+ unless defined? FFI
19
+ require 'ffi'
20
+ end
21
+
22
+ module GDBM_FFI
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 == 1 and args[0].is_a? String
36
+ super()
37
+ self.dptr = args[0]
38
+ self[:dsize] = args[0].bytesize
39
+ else
40
+ super
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
+ keys = []
153
+ current = self.gdbm_firstkey file
154
+ until current.value.nil?
155
+ if block_given?
156
+ yield current.value
157
+ else
158
+ keys << current.value
159
+ end
160
+ current = self.gdbm_nextkey file, current
161
+ end
162
+ block_given? ? nil : keys.each
163
+ end
164
+
165
+ #Iterates over each value in the _file_.
166
+ def self.each_value(file)
167
+ current = self.gdbm_firstkey file
168
+ until current.value.nil?
169
+ value = gdbm_fetch file, current
170
+ yield value.value
171
+ current = self.gdbm_nextkey file, current
172
+ end
173
+ end
174
+
175
+ #Deletes all keys and values from the _file_.
176
+ def self.clear(file)
177
+ until (key = self.gdbm_firstkey(file)).value.nil?
178
+ until key.value.nil?
179
+ next_key = self.gdbm_nextkey(file, key)
180
+ result = self.gdbm_delete file, key
181
+ raise GDBMError, last_error if result != 0
182
+ key = next_key
183
+ end
184
+ end
185
+ end
186
+
187
+ #Returns the last error encountered from the gdbm library as a String.
188
+ def self.last_error
189
+ error_string(error_number)
190
+ end
191
+
192
+ #Opens a gdbm file. Returns the GDBM_FILE pointer, which should be treated
193
+ #as an opaque value, to be passed in to GDBM_FFI methods.
194
+ def self.open(filename, blocksize, flags, mode)
195
+ self.gdbm_open filename, blocksize, flags, mode, FATAL
196
+ end
197
+
198
+ #Sets the cache size.
199
+ def self.set_cache_size(file, size)
200
+ opt = FFI::MemoryPointer.new size
201
+ self.set_opt file, CACHE_SIZE, opt, opt.size
202
+ end
203
+
204
+ #Sets the sync mode.
205
+ def self.set_sync_mode(file, boolean)
206
+ if boolean
207
+ opt = FFI::MemoryPointer.new 1
208
+ else
209
+ opt = FFI::MemoryPointer.new 0
210
+ end
211
+
212
+ self.set_opt file, SYNC_MODE, opt, opt.size
213
+ end
214
+ end
215
+
216
+ class GDBMError < StandardError; end
217
+ class GDBMFatalError < Exception; end
218
+
219
+ class GDBM
220
+ include Enumerable
221
+
222
+ #This constant is to check if a flag is READER, WRITER, WRCREAT, or NEWDB
223
+ RUBY_GDBM_RW_BIT = 0x20000000
224
+
225
+ BLOCKSIZE = 2048
226
+ READER = GDBM_FFI::READER | RUBY_GDBM_RW_BIT
227
+ WRITER = GDBM_FFI::WRITER | RUBY_GDBM_RW_BIT
228
+ WRCREAT = GDBM_FFI::WRCREAT | RUBY_GDBM_RW_BIT
229
+ NEWDB = GDBM_FFI::NEWDB | RUBY_GDBM_RW_BIT
230
+ FAST = GDBM_FFI::FAST
231
+ SYNC = GDBM_FFI::SYNC
232
+ NOLOCK = GDBM_FFI::NOLOCK
233
+ VERSION = GDBM_FFI.VERSION
234
+
235
+ def initialize(filename, mode = 0666, flags = nil)
236
+
237
+ mode = -1 if mode.nil?
238
+ flags = 0 if flags.nil?
239
+ @file = nil
240
+
241
+ if flags & RUBY_GDBM_RW_BIT != 0 #Check if flags are appropriate
242
+ flags &= ~RUBY_GDBM_RW_BIT #Remove check to make flag match GDBM constants
243
+
244
+ @file = GDBM_FFI.open filename, BLOCKSIZE, flags, mode
245
+ else
246
+ if mode >= 0
247
+ @file = GDBM_FFI.open filename, BLOCKSIZE, WRCREAT | flags, mode
248
+ end
249
+
250
+ @file = GDBM_FFI.open filename, BLOCKSIZE, WRITER | flags, 0 if @file.nil? or @file.null?
251
+ @file = GDBM_FFI.open filename, BLOCKSIZE, READER | flags, 0 if @file.nil? or @file.null?
252
+ end
253
+
254
+ if @file.nil? or @file.null?
255
+ return if mode == -1 #C code returns Qnil, but we can't
256
+ if GDBM_FFI.error_number == GDBM_FFI::FILE_OPEN_ERROR ||
257
+ GDBM_FFI.error_number == GDBM_FFI::CANT_BE_READER ||
258
+ GDBM_FFI.error_number == GDBM_FFI::CANT_BE_WRITER
259
+
260
+ raise SystemCallError.new(GDBM_FFI.last_error, FFI.errno)
261
+ else
262
+ raise GDBMError, GDBM_FFI.last_error;
263
+ end
264
+ end
265
+ end
266
+
267
+ def self.open(filename, mode = 0666, flags = nil)
268
+ obj = self.new filename, mode, flags
269
+
270
+ if block_given?
271
+ begin
272
+ result = yield obj
273
+ ensure
274
+ obj.close unless obj.closed?
275
+ end
276
+ result
277
+ elsif obj.nil?
278
+ nil
279
+ else
280
+ obj
281
+ end
282
+ end
283
+
284
+ def [](key)
285
+ GDBM_FFI.fetch file, key
286
+ end
287
+
288
+ def []=(key, value)
289
+ modifiable?
290
+ GDBM_FFI.store file, key, value
291
+ end
292
+
293
+ alias :store :[]=
294
+
295
+ def cachesize=(size)
296
+ GDBM_FFI.set_cache_size file, size
297
+ end
298
+
299
+ def clear
300
+ modifiable?
301
+ GDBM_FFI.clear file
302
+ self
303
+ end
304
+
305
+ def close
306
+ if closed?
307
+ raise RuntimeError, "closed GDBM file"
308
+ else
309
+ GDBM_FFI.close @file
310
+ @file = nil unless frozen?
311
+ end
312
+ end
313
+
314
+ def closed?
315
+ @file.nil? or @file.null?
316
+ end
317
+
318
+ def delete(key)
319
+ modifiable?
320
+ value = self[key]
321
+ #This is bizarre and not mentioned in the docs,
322
+ #but this is what the tests expect and what the MRI
323
+ #version does.
324
+ if value.nil? and block_given?
325
+ value = yield key
326
+ end
327
+ GDBM_FFI.delete file, key
328
+ value
329
+ end
330
+
331
+ def delete_if
332
+ modifiable?
333
+ rejects = []
334
+ begin
335
+ GDBM_FFI.each_pair(file) do |k,v|
336
+ if yield k, v
337
+ rejects << k
338
+ end
339
+ end
340
+ #unsure about this, but it handles breaking during
341
+ #the iteration
342
+ ensure
343
+ rejects.each do |k|
344
+ GDBM_FFI.delete file, k
345
+ end
346
+ end
347
+
348
+ self
349
+ end
350
+
351
+ alias :reject! :delete_if
352
+
353
+ def each_key(&block)
354
+ enumerator = GDBM_FFI.each_key(file, &block)
355
+ enumerator || self
356
+ end
357
+
358
+ def each_pair(&block)
359
+ GDBM_FFI.each_pair file, &block
360
+ self
361
+ end
362
+
363
+ alias :each :each_pair
364
+
365
+ def each_value(&block)
366
+ GDBM_FFI.each_value file, &block
367
+ self
368
+ end
369
+
370
+ def empty?
371
+ key = GDBM_FFI.first_key file
372
+ key.nil?
373
+ end
374
+
375
+ def fastmode=(boolean)
376
+ GDBM_FFI.set_sync_mode file, !boolean
377
+ end
378
+
379
+ def fetch(key, default = nil)
380
+ result = GDBM_FFI.fetch file, key
381
+ if result.nil?
382
+ if default
383
+ default
384
+ elsif block_given?
385
+ yield key
386
+ else
387
+ raise IndexError, "key not found"
388
+ end
389
+ else
390
+ result
391
+ end
392
+ end
393
+
394
+ def has_key?(key)
395
+ GDBM_FFI.exists? file, key
396
+ end
397
+
398
+ alias :key? :has_key?
399
+ alias :member? :has_key?
400
+ alias :include? :has_key?
401
+
402
+ def has_value?(value)
403
+ GDBM_FFI.each_value(file) do |v|
404
+ if v == value
405
+ return true
406
+ end
407
+ end
408
+ false
409
+ end
410
+
411
+ alias :value? :has_value?
412
+
413
+ def index(value)
414
+ if RUBY_VERSION >= "1.9"
415
+ warn "GDBM#index is deprecated; use GDBM#key"
416
+ end
417
+
418
+ self.key(value)
419
+ end
420
+
421
+ def invert
422
+ result = {}
423
+
424
+ GDBM_FFI.each_pair(file) do |k,v|
425
+ result[v] = k
426
+ end
427
+
428
+ result
429
+ end
430
+
431
+ def key(value)
432
+ GDBM_FFI.each_pair(file) do |k,v|
433
+ if v == value
434
+ return k
435
+ end
436
+ end
437
+ nil
438
+ end
439
+
440
+ def keys
441
+ keys = []
442
+
443
+ GDBM_FFI.each_key(file) do |k|
444
+ keys << k
445
+ end
446
+
447
+ keys
448
+ end
449
+
450
+ def length
451
+ len = 0
452
+
453
+ GDBM_FFI.each_key(file) do |k|
454
+ len = len + 1
455
+ end
456
+
457
+ len
458
+ end
459
+
460
+ alias :size :length
461
+
462
+ def nil?
463
+ @file.nil? or @file.null?
464
+ end
465
+
466
+ def reject
467
+ result = {}
468
+
469
+ GDBM_FFI.each_pair(file) do |k,v|
470
+ if not yield k, v
471
+ result[k] = v
472
+ end
473
+ end
474
+
475
+ result
476
+ end
477
+
478
+ def reorganize
479
+ modifiable?
480
+ GDBM_FFI.reorganize file
481
+ self
482
+ end
483
+
484
+ def replace(other)
485
+ self.clear
486
+ self.update other
487
+ self
488
+ end
489
+
490
+ def select(*args)
491
+ result = []
492
+ #This method behaves completely contrary to what the docs state:
493
+ #http://ruby-doc.org/stdlib/libdoc/gdbm/rdoc/classes/GDBM.html#M000318
494
+ #Instead, it yields a pair and returns [[k1, v1], [k2, v2], ...]
495
+ #But this is how it is in 1.8.7 and 1.9.1, so...
496
+ #
497
+ #Update: Docs have been patched: http://redmine.ruby-lang.org/repositories/revision/1?rev=25300
498
+
499
+ if block_given?
500
+ if args.length > 0
501
+ raise ArgumentError, "wrong number of arguments(#{args.length} for 0)"
502
+ end
503
+
504
+ GDBM_FFI.each_pair(file) do |k, v|
505
+ if yield k, v
506
+ result << [k, v]
507
+ end
508
+ end
509
+ #This is for 1.8.7 compatibility
510
+ elsif RUBY_VERSION <= "1.8.7"
511
+ warn "GDBM#select(index..) is deprecated; use GDBM#values_at"
512
+
513
+ result = values_at(*args)
514
+ else
515
+ result = []
516
+ end
517
+
518
+ result
519
+ end
520
+
521
+ def shift
522
+ modifiable?
523
+ key = GDBM_FFI.first_key file
524
+ if key
525
+ value = GDBM_FFI.fetch file, key
526
+ GDBM_FFI.delete file, key
527
+ [key, value]
528
+ else
529
+ nil
530
+ end
531
+ end
532
+
533
+ def sync
534
+ modifiable?
535
+ GDBM_FFI.sync file
536
+ self
537
+ end
538
+
539
+ def syncmode=(boolean)
540
+ GDBM_FFI.set_sync_mode file, boolean
541
+ end
542
+
543
+ def to_a
544
+ result = []
545
+ GDBM_FFI.each_pair(file) do |k,v|
546
+ result << [k, v]
547
+ end
548
+ result
549
+ end
550
+
551
+ def to_hash
552
+ result = {}
553
+ GDBM_FFI.each_pair(file) do |k,v|
554
+ result[k] = v
555
+ end
556
+ result
557
+ end
558
+
559
+ def update(other)
560
+ other.each_pair do |k,v|
561
+ GDBM_FFI.store file, k, v
562
+ end
563
+ self
564
+ end
565
+
566
+ def values
567
+ values = []
568
+
569
+ GDBM_FFI.each_value(file) do |v|
570
+ values << v
571
+ end
572
+
573
+ values
574
+ end
575
+
576
+ def values_at(*keys)
577
+ results = []
578
+
579
+ keys.each do |k|
580
+ results << self[k]
581
+ end
582
+
583
+ results
584
+ end
585
+
586
+ private
587
+
588
+ def modifiable?
589
+ #raise SecurityError, "Insecure operation at level #$SAFE" if $SAFE >= 4 #Not currently supported in JRuby
590
+ if self.frozen?
591
+ if RUBY_VERSION > "1.8.7"
592
+ raise RuntimeError, "Can't modify frozen #{self}"
593
+ else
594
+ raise TypeError, "Can't modify frozen #{self}"
595
+ end
596
+ end
597
+ end
598
+
599
+ #Raises a RuntimeError if the file is closed.
600
+ def file
601
+ unless @file.nil? or @file.null?
602
+ @file
603
+ else
604
+ raise(RuntimeError, "closed GDBM file")
605
+ end
606
+ end
607
+ end
608
+
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ffi-gdbm
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.3.1
5
+ platform: ruby
6
+ authors:
7
+ - Justin Collins
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-05-04 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: This library provides a gdbm library compatible with the MRI standard library, but using Ruby-FFI rather than a C extension. This allows gdbm to easily be used from alternative Ruby implementations, such as JRuby. It can also be used with MRI, if there is some kind of need for that.
14
+ email:
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - README.md
20
+ - lib/gdbm.rb
21
+ homepage: http://github.com/presidentbeef/ffi-gdbm
22
+ licenses: []
23
+ metadata: {}
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ requirements: []
39
+ rubyforge_project:
40
+ rubygems_version: 2.6.8
41
+ signing_key:
42
+ specification_version: 4
43
+ summary: Provides access to gdbm through Ruby-FFI, particularly for JRuby and other alternative Ruby implementations.
44
+ test_files: []