weak_key_map 1.0.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +18 -0
- data/README.md +37 -0
- data/Rakefile +9 -0
- data/lib/weak_key_map/impl.rb +100 -0
- data/lib/weak_key_map/version.rb +5 -0
- data/lib/weak_key_map.rb +5 -0
- data/test/test_helper.rb +5 -0
- data/test/weak_key_map_test.rb +124 -0
- data/weak_key_map.gemspec +16 -0
- metadata +52 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a1342b3db5bf5ff1157e1d7b8590699c913cce68685aa72b40492ec7bf8307e4
|
|
4
|
+
data.tar.gz: 1b08647a4aab5103d70e75bc476bf2745b1f688b981ecbfa2e1ac907c447b006
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a4752359db7288c57dffd016ce0a8cfea8c4a26d7eb9e19cd32f1405c2f1b8f770439f9981a93a92a8767c592fd9711a0244954572c5743d7b0ecc6a0fbfe751
|
|
7
|
+
data.tar.gz: d95dfc108ecc8da00c1cf27fa12ee84ce06899e458e33f3dc921e5853a3b28ccb9a1f151c02c7445077eb8e633141e023a250d9e0d26fb542ceb6378c0018d09
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
source "https://rubygems.org"
|
|
2
|
+
|
|
3
|
+
gemspec
|
|
4
|
+
|
|
5
|
+
group :development, :test do
|
|
6
|
+
gem "debug"
|
|
7
|
+
gem "rake"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
group :development do
|
|
11
|
+
gem "appraisal"
|
|
12
|
+
gem "appraisal-run"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
group :test do
|
|
16
|
+
gem "minitest", "~> 5.0"
|
|
17
|
+
gem "m", "~> 1.6"
|
|
18
|
+
end
|
data/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
## weak_key_map
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
A backport of `ObjectSpace::WeakKeyMap` for Ruby < 3.3
|
|
6
|
+
|
|
7
|
+
## Introduction
|
|
8
|
+
|
|
9
|
+
Ruby 3.3 introduced the handy `ObjectSpace::WeakKeyMap` class to complement `ObjectSpace::WeakMap`. The `WeakMap` class holds weak references to both keys _and_ values, meaning if the value for a particular key is garbage collected, the entire key/value pair will be removed from the map. In contrast, the `WeakKeyMap` class only removes values if the _key_ is garbage collected.
|
|
10
|
+
|
|
11
|
+
This gem is a backport of `WeakKeyMap` for older rubies in the 3.x line.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
`WeakKeyMap`s work in a very similar fashion to a normal Ruby hash, eg:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
map = ObjectSpace::WeakKeyMap.new
|
|
19
|
+
key = "key"
|
|
20
|
+
map[key] = "value"
|
|
21
|
+
map["key"] # => "value"
|
|
22
|
+
key = nil
|
|
23
|
+
GC.start
|
|
24
|
+
map["key"] # => nil
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Running Tests
|
|
28
|
+
|
|
29
|
+
`bundle exec rake` should do the trick.
|
|
30
|
+
|
|
31
|
+
## License
|
|
32
|
+
|
|
33
|
+
Licensed under the MIT license. See LICENSE for details.
|
|
34
|
+
|
|
35
|
+
## Authors
|
|
36
|
+
|
|
37
|
+
* Cameron C. Dutro: http://github.com/camertron
|
data/Rakefile
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "weakref"
|
|
4
|
+
|
|
5
|
+
module ObjectSpace
|
|
6
|
+
class WeakKeyMap
|
|
7
|
+
def initialize
|
|
8
|
+
@map = {}
|
|
9
|
+
@refs = {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def [](key)
|
|
13
|
+
hash = key.hash
|
|
14
|
+
ref = @refs[hash]
|
|
15
|
+
return nil unless ref
|
|
16
|
+
return @map[hash] if ref.weakref_alive?
|
|
17
|
+
|
|
18
|
+
@map.delete(hash)
|
|
19
|
+
@refs.delete(hash)
|
|
20
|
+
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def []=(key, value)
|
|
25
|
+
case key
|
|
26
|
+
when true, false, nil, Integer, Float, Symbol
|
|
27
|
+
raise ArgumentError, "WeakKeyMap keys must be garbage collectable"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
hash = key.hash
|
|
31
|
+
@refs[hash] = WeakRef.new(key)
|
|
32
|
+
@map[hash] = value
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def inspect
|
|
36
|
+
"#<#{self.class}:0x#{(object_id << 1).to_s(16).rjust(14, '0')} size=#{@map.size}>"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def clear
|
|
40
|
+
@map.clear
|
|
41
|
+
@refs.clear
|
|
42
|
+
self
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def delete(key, &block)
|
|
46
|
+
hash = key.hash
|
|
47
|
+
retval = nil
|
|
48
|
+
|
|
49
|
+
if @refs.key?(hash)
|
|
50
|
+
retval = @map[hash]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if block_given?
|
|
54
|
+
retval = yield key
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
@refs.delete(hash)
|
|
58
|
+
@map.delete(hash)
|
|
59
|
+
|
|
60
|
+
retval
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def getkey(key)
|
|
64
|
+
hash = key.hash
|
|
65
|
+
|
|
66
|
+
if @refs.key?(hash)
|
|
67
|
+
ref = @refs[hash]
|
|
68
|
+
|
|
69
|
+
begin
|
|
70
|
+
return ref.__getobj__
|
|
71
|
+
rescue WeakRef::RefError
|
|
72
|
+
@refs.delete(hash)
|
|
73
|
+
@map.delete(hash)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def key?(key)
|
|
81
|
+
hash = key.hash
|
|
82
|
+
|
|
83
|
+
if @refs.key?(hash)
|
|
84
|
+
ref = @refs[hash]
|
|
85
|
+
return true if ref.weakref_alive?
|
|
86
|
+
|
|
87
|
+
@refs.delete(hash)
|
|
88
|
+
@map.delete(hash)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
false
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def size
|
|
95
|
+
@refs.size
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
alias length size
|
|
99
|
+
end
|
|
100
|
+
end
|
data/lib/weak_key_map.rb
ADDED
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
require_relative "./test_helper"
|
|
4
|
+
require "weak_key_map"
|
|
5
|
+
|
|
6
|
+
class WeakKeyMapTest < Minitest::Test
|
|
7
|
+
def setup
|
|
8
|
+
@wm = ObjectSpace::WeakKeyMap.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def test_map
|
|
12
|
+
x = Object.new
|
|
13
|
+
k = "foo"
|
|
14
|
+
@wm[k] = x
|
|
15
|
+
assert_same(x, @wm[k])
|
|
16
|
+
assert_same(x, @wm["FOO".downcase])
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_aset_const
|
|
20
|
+
x = Object.new
|
|
21
|
+
assert_raises(ArgumentError) { @wm[true] = x }
|
|
22
|
+
assert_raises(ArgumentError) { @wm[false] = x }
|
|
23
|
+
assert_raises(ArgumentError) { @wm[nil] = x }
|
|
24
|
+
assert_raises(ArgumentError) { @wm[42] = x }
|
|
25
|
+
assert_raises(ArgumentError) { @wm[2**128] = x }
|
|
26
|
+
assert_raises(ArgumentError) { @wm[1.23] = x }
|
|
27
|
+
assert_raises(ArgumentError) { @wm[:foo] = x }
|
|
28
|
+
assert_raises(ArgumentError) { @wm["foo#{rand}".to_sym] = x }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_getkey
|
|
32
|
+
k = "foo"
|
|
33
|
+
@wm[k] = true
|
|
34
|
+
assert_same(k, @wm.getkey("FOO".downcase))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_key?
|
|
38
|
+
assert_weak_include(:key?, "foo")
|
|
39
|
+
assert !@wm.key?("bar")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_delete
|
|
43
|
+
k1 = "foo"
|
|
44
|
+
x1 = Object.new
|
|
45
|
+
@wm[k1] = x1
|
|
46
|
+
assert_equal x1, @wm[k1]
|
|
47
|
+
assert_equal x1, @wm.delete(k1)
|
|
48
|
+
assert_nil @wm[k1]
|
|
49
|
+
assert_nil @wm.delete(k1)
|
|
50
|
+
|
|
51
|
+
fallback = @wm.delete(k1) do |key|
|
|
52
|
+
assert_equal k1, key
|
|
53
|
+
42
|
|
54
|
+
end
|
|
55
|
+
assert_equal 42, fallback
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_clear
|
|
59
|
+
k = "foo"
|
|
60
|
+
@wm[k] = true
|
|
61
|
+
assert @wm[k]
|
|
62
|
+
assert_same @wm, @wm.clear
|
|
63
|
+
refute @wm[k]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_inspect
|
|
67
|
+
x = Object.new
|
|
68
|
+
k = Object.new
|
|
69
|
+
@wm[k] = x
|
|
70
|
+
assert_match(/\A\#<#{@wm.class.name}:[\dxa-f]+ size=\d+>\z/, @wm.inspect)
|
|
71
|
+
|
|
72
|
+
1000.times do |i|
|
|
73
|
+
@wm[i.to_s] = Object.new
|
|
74
|
+
@wm.inspect
|
|
75
|
+
end
|
|
76
|
+
assert_match(/\A\#<#{@wm.class.name}:[\dxa-f]+ size=\d+>\z/, @wm.inspect)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_no_hash_method
|
|
80
|
+
k = BasicObject.new
|
|
81
|
+
assert_raises NoMethodError do
|
|
82
|
+
@wm[k] = 42
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_frozen_object
|
|
87
|
+
o = Object.new.freeze
|
|
88
|
+
@wm[o] = 'foo' # no error raised
|
|
89
|
+
@wm['foo'] = o # no error raised
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def assert_normal_exit(code, message = nil)
|
|
95
|
+
require "tempfile"
|
|
96
|
+
|
|
97
|
+
Tempfile.create(["weak_key_map_test", ".rb"]) do |f|
|
|
98
|
+
f.write(code)
|
|
99
|
+
f.flush
|
|
100
|
+
|
|
101
|
+
lib_path = File.expand_path("../lib", __dir__)
|
|
102
|
+
output = `ruby -I#{lib_path} #{f.path} 2>&1`
|
|
103
|
+
status = $?
|
|
104
|
+
|
|
105
|
+
assert status.success?,
|
|
106
|
+
"Expected script to exit normally but got exit code #{status.exitstatus}.\n" +
|
|
107
|
+
"Output:\n#{output}" +
|
|
108
|
+
(message ? "\n#{message}" : "")
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def assert_weak_include(m, k, n = 100)
|
|
113
|
+
if n > 0
|
|
114
|
+
return assert_weak_include(m, k, n - 1)
|
|
115
|
+
end
|
|
116
|
+
1.times do
|
|
117
|
+
x = Object.new
|
|
118
|
+
@wm[k] = x
|
|
119
|
+
assert @wm.send(m, k)
|
|
120
|
+
assert @wm.send(m, "FOO".downcase)
|
|
121
|
+
x = Object.new
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), "lib")
|
|
2
|
+
require "weak_key_map/version"
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |s|
|
|
5
|
+
s.name = "weak_key_map"
|
|
6
|
+
s.version = ::WeakKeyMap::VERSION
|
|
7
|
+
s.authors = ["Cameron Dutro"]
|
|
8
|
+
s.email = ["camertron@gmail.com"]
|
|
9
|
+
s.homepage = "http://github.com/camertron/weak_key_map"
|
|
10
|
+
s.description = s.summary = "A backport of ObjectSpace::WeakKeyMap for Ruby < 3.3"
|
|
11
|
+
s.platform = Gem::Platform::RUBY
|
|
12
|
+
|
|
13
|
+
s.require_path = "lib"
|
|
14
|
+
|
|
15
|
+
s.files = Dir["{lib,test}/**/*", "Gemfile", "LICENSE", "CHANGELOG.md", "README.md", "Rakefile", "weak_key_map.gemspec"]
|
|
16
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: weak_key_map
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Cameron Dutro
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-11-10 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: A backport of ObjectSpace::WeakKeyMap for Ruby < 3.3
|
|
14
|
+
email:
|
|
15
|
+
- camertron@gmail.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- CHANGELOG.md
|
|
21
|
+
- Gemfile
|
|
22
|
+
- README.md
|
|
23
|
+
- Rakefile
|
|
24
|
+
- lib/weak_key_map.rb
|
|
25
|
+
- lib/weak_key_map/impl.rb
|
|
26
|
+
- lib/weak_key_map/version.rb
|
|
27
|
+
- test/test_helper.rb
|
|
28
|
+
- test/weak_key_map_test.rb
|
|
29
|
+
- weak_key_map.gemspec
|
|
30
|
+
homepage: http://github.com/camertron/weak_key_map
|
|
31
|
+
licenses: []
|
|
32
|
+
metadata: {}
|
|
33
|
+
post_install_message:
|
|
34
|
+
rdoc_options: []
|
|
35
|
+
require_paths:
|
|
36
|
+
- lib
|
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - ">="
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '0'
|
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
requirements: []
|
|
48
|
+
rubygems_version: 3.4.19
|
|
49
|
+
signing_key:
|
|
50
|
+
specification_version: 4
|
|
51
|
+
summary: A backport of ObjectSpace::WeakKeyMap for Ruby < 3.3
|
|
52
|
+
test_files: []
|