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 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
@@ -0,0 +1,2 @@
1
+ # 1.0.0
2
+ * Birthday!
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
+ ![Unit Tests](https://github.com/camertron/weak_key_map/actions/workflows/tests.yml/badge.svg?branch=main)
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,9 @@
1
+ require "bundler"
2
+ require "rubygems/package_task"
3
+ require "minitest/test_task"
4
+
5
+ task default: :test
6
+
7
+ Minitest::TestTask.create
8
+
9
+ Bundler::GemHelper.install_tasks
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class WeakKeyMap
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ if !ObjectSpace.const_defined?(:WeakKeyMap, false)
4
+ require_relative "weak_key_map/impl"
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "weak_key_map"
4
+ require "minitest"
5
+ require "minitest/autorun"
@@ -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: []