weak 0.2.1 → 0.3.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 +4 -4
- data/CHANGELOG.md +18 -3
- data/LICENSE.txt +1 -1
- data/README.md +87 -17
- data/lib/weak/cache.rb +181 -0
- data/lib/weak/map/abstract_strong_keys.rb +1 -1
- data/lib/weak/map/strong_keys.rb +2 -2
- data/lib/weak/map/strong_secondary_keys.rb +3 -3
- data/lib/weak/map/weak_keys.rb +4 -3
- data/lib/weak/map/weak_keys_with_delete.rb +3 -5
- data/lib/weak/map.rb +41 -33
- data/lib/weak/set/strong_secondary_keys.rb +1 -1
- data/lib/weak/set/weak_keys_with_delete.rb +2 -4
- data/lib/weak/set.rb +56 -55
- data/lib/weak/version.rb +3 -3
- data/lib/weak.rb +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d99367de841cfb268442bccc306c3d43858cb6280a462cd513f21eb6c88ec366
|
|
4
|
+
data.tar.gz: da3360b4705d18a122a936a7a9dec0ab57839afc3dd846a24fc06dba46f645dd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0abea00e5797abd15e865b366963ed481eaa70d519bb6cb46f1c21a68e836c9a5f8b7fb8450449671b250c155b4849e60df650f94d9b98d7ef74068366af8b38
|
|
7
|
+
data.tar.gz: 8e2ec5a83169a5f9e12fa965bc12e529fda24a05374eba811bc4dd6ee91f002e7de402b7372e44d234334cbe44d54f555eddff6f81a1286135ac06fe4b3faa31
|
data/CHANGELOG.md
CHANGED
|
@@ -2,13 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- Add `Weak::Cache` as a thread-safe wrapper around `Weak::Map` to provide an object cache.
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- Fix `Weak::Map#store` method alias to `Weak::Map#[]=`. Previously, it was errouneously aliased to `Weak::Map#[]`.
|
|
12
|
+
|
|
13
|
+
### Improvements
|
|
14
|
+
|
|
15
|
+
- Adapt `Weak::Set#inspect` output to more resemble the output of `Set#inspect` in Ruby 4.0
|
|
16
|
+
- Fix typos in code documentation
|
|
17
|
+
- Use `require_relative` instead of require for all gem files
|
|
18
|
+
- Add addititional specs for `Weak::Map`
|
|
19
|
+
- Clarify humanistic usage policy
|
|
20
|
+
|
|
5
21
|
## [0.2.1] - 2025-12-27
|
|
6
22
|
|
|
7
23
|
- Fix typos in code documentation
|
|
8
|
-
- Ignore some unnecessary methods defined on some `Set` implementations in `set_spec`
|
|
24
|
+
- Ignore some unnecessary methods defined on some `Weak::Set` implementations in `set_spec`
|
|
9
25
|
- Run specs on JRuby 10 in Github Actions
|
|
10
26
|
- Retry TruffleRuby rspec runs on Github Actions to avoid random failures due to flakey GC.
|
|
11
|
-
- Extract UNDEFINED to its own file and require it where used.
|
|
27
|
+
- Extract `UNDEFINED` to its own file and require it where used.
|
|
12
28
|
- Add more details about the gem version in `Weak::Version`
|
|
13
29
|
- Handle object cycles in `pretty_print`.
|
|
14
30
|
|
|
@@ -25,7 +41,6 @@
|
|
|
25
41
|
|
|
26
42
|
- Initial version of `Weak::Set` to store an unordered collection of objects.
|
|
27
43
|
- Initial version of `Weak::Map` to store key-value pairs of objects.
|
|
28
|
-
|
|
29
44
|
- Support for Ruby 3.0 using the following impementations
|
|
30
45
|
- Ruby (aka. MRI, aka. YARV) >= 3.0
|
|
31
46
|
- JRuby >= 9.4
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
# Weak
|
|
2
2
|
|
|
3
3
|
[](https://rubygems.org/gems/weak)
|
|
4
|
-
[](#license)
|
|
5
|
+
[](https://github.com/meineerde/weak)
|
|
6
|
+
[](https://www.rubydoc.info/gems/weak)
|
|
7
|
+
|
|
8
|
+
[](https://github.com/standardrb/standard)
|
|
9
|
+
[](https://github.com/meineerde/weak/actions/workflows/ci.yml)
|
|
10
|
+
[](https://coveralls.io/github/meineerde/weak?branch=main)
|
|
5
11
|
|
|
6
12
|
Weak is a Ruby library which implements collections of unordered values without strong object references.
|
|
7
13
|
|
|
@@ -11,7 +17,10 @@ We provide multiple classes which behave similar to their standard-library count
|
|
|
11
17
|
|
|
12
18
|
`Weak::Set` behaves similar to the [Set](https://docs.ruby-lang.org/en/3.4/Set.html) class of the Ruby standard library, but all values are only weakly referenced. That way, all values can be garbage collected and silently removed from the set unless they are still referenced from some other live object.
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
> [!CAUTION]
|
|
21
|
+
> `Weak::Set` objects are not inherently thread-safe. When accessing a weak set from multiple threads or fibers, you MUST use a mutex or another locking mechanism.
|
|
22
|
+
|
|
23
|
+
Compared to the `Set` class, there are a few differences:
|
|
15
24
|
|
|
16
25
|
- All element references are weak, allowing each element to be garbage collected unless there is a strong reference to it somwhere else.
|
|
17
26
|
- We do not necessarily retain the order of elements as they are inserted into the `Weak::Set`. You should not rely on a specific order.
|
|
@@ -23,13 +32,13 @@ require "weak/set"
|
|
|
23
32
|
set = Weak::Set.new
|
|
24
33
|
|
|
25
34
|
set << "some string"
|
|
26
|
-
# =>
|
|
35
|
+
# => Weak::Set["some string"]
|
|
27
36
|
|
|
28
37
|
# Do some work, wait a bit, or force a garbage collection run
|
|
29
38
|
3.times { GC.start }
|
|
30
39
|
|
|
31
40
|
set
|
|
32
|
-
# =>
|
|
41
|
+
# => Weak::Set[]
|
|
33
42
|
```
|
|
34
43
|
|
|
35
44
|
## Weak::Map
|
|
@@ -37,7 +46,10 @@ set
|
|
|
37
46
|
`Weak::Map` behaves similar to a `Hash` or an `ObjectSpace::WeakMap` in Ruby (aka. MRI, aka. YARV).
|
|
38
47
|
Both keys and values are weak references, allowing either of them to be garbage collected. If either the key or the value of a pair is garbage collected, the entire pair will be removed from the `Weak::Map`.
|
|
39
48
|
|
|
40
|
-
|
|
49
|
+
> [!CAUTION]
|
|
50
|
+
> `Weak::Map` objects are not inherently thread-safe. When accessing a weak map from multiple threads or fibers, you MUST use a mutex or another locking mechanism.
|
|
51
|
+
|
|
52
|
+
Compared to the `Hash` class, there are a few differences:
|
|
41
53
|
|
|
42
54
|
- Key and value references are weak, allowing each key-value pair to be garbage collected unless there is a strong reference to boith the key and the value somewhere else.
|
|
43
55
|
- We do not necessarily retain the order of elements as they are inserted into the `Weak::Map`. You should not rely on a specific order.
|
|
@@ -58,6 +70,29 @@ map
|
|
|
58
70
|
# => #<Weak::Map {}>
|
|
59
71
|
```
|
|
60
72
|
|
|
73
|
+
## Weak::Cache
|
|
74
|
+
|
|
75
|
+
`Weak::Cache` is a thread-safe wrapper around `Weak::Map`. The class behaves similar to an `ActiveSupport::Cache::Store`.
|
|
76
|
+
|
|
77
|
+
> [!TIP]
|
|
78
|
+
> `Weak::Cache` objects can safely be used from multiple threads and fibers concurrently without any additional locks.
|
|
79
|
+
|
|
80
|
+
Similar to a `Weak:Map`, both keys and values are weak references. Cache entries are removed if either the key of the value is garbage collected.
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
require "weak/cache"
|
|
84
|
+
cache = Weak::Cache.new
|
|
85
|
+
|
|
86
|
+
# By default, we return nil for missing keys
|
|
87
|
+
cache[:key] # => nil
|
|
88
|
+
|
|
89
|
+
# With fetch, we can get a key and if it's missing, write a value for it
|
|
90
|
+
cache.fetch(:key) { |key| key.upcase } # => :KEY
|
|
91
|
+
|
|
92
|
+
# The value stored in the fetch above is stored in the cache
|
|
93
|
+
cache[:key] # => :KEY
|
|
94
|
+
```
|
|
95
|
+
|
|
61
96
|
## Usage
|
|
62
97
|
|
|
63
98
|
Please refer to the documentation at:
|
|
@@ -65,9 +100,6 @@ Please refer to the documentation at:
|
|
|
65
100
|
- [📘 Documentation](https://www.rubydoc.info/gems/weak)
|
|
66
101
|
- [💥 Development Documentation](https://www.rubydoc.info/github/meineerde/weak) of the [main branch](https://github.com/meineerde/weak/tree/main)
|
|
67
102
|
|
|
68
|
-
> [!WARNING]
|
|
69
|
-
> The Weak collections are not inherently thread-safe. When accessing a collection from multiple threads or fibers, you MUST use a mutex or another locking mechanism.
|
|
70
|
-
|
|
71
103
|
The Weak collections use Ruby's [ObjectSpace::WeakMap](https://docs.ruby-lang.org/en/3.4/ObjectSpace/WeakMap.html) under the hood. Unfortunately, different Ruby implementations and versions such as Ruby (aka. MRI, aka. YARV), JRuby, or TruffleRuby show quite diverse behavior in their respective `ObjectSpace::WeakMap` implementations. To provide a unified behavior on all supported Rubies, we use multiple different storage strategies.
|
|
72
104
|
|
|
73
105
|
The appropriate strategy is selected automatically. Their exposed behavior should be identical across all implementations. If you experience diverging behavior, we consider this a bug. Please [open an issue](https://github.com/meineerde/weak/issues/new) and describe the diverging or unexpected behavior.
|
|
@@ -157,7 +189,7 @@ During `checkout` we remember a reference to the returned connection object in t
|
|
|
157
189
|
|
|
158
190
|
If the caller just "forgets" the connection, our pool will also forget it during the next Ruby garbage collection run.
|
|
159
191
|
|
|
160
|
-
If the caller returns the connection by calling `checkin` again, we can verify that we have in fact created the object by deleting it from the `@outstanding` list. That way,
|
|
192
|
+
If the caller returns the connection by calling `checkin` again, we can verify that we have in fact created the object by deleting it from the `@outstanding` list. That way, a checked-out connection can be checked-in again only once and only if it was initially created by the `ConnectionPool`.
|
|
161
193
|
|
|
162
194
|
### Weak::Map Example
|
|
163
195
|
|
|
@@ -168,6 +200,8 @@ Even if a single object is wrapped in multiple `LockedObject` instances, we stil
|
|
|
168
200
|
If all LockedObject instances for an `obj` and the `obj` itself vanish by being garbage collected, the associated mutex will also be garbage collected without requiring any external coordination.
|
|
169
201
|
|
|
170
202
|
```ruby
|
|
203
|
+
require "weak/map"
|
|
204
|
+
|
|
171
205
|
class LockedObject < BasicObject
|
|
172
206
|
LOCKS = Weak::Map.new
|
|
173
207
|
LOCKS_MUTEX = Mutex.new
|
|
@@ -210,24 +244,60 @@ string = "foo"
|
|
|
210
244
|
end
|
|
211
245
|
```
|
|
212
246
|
|
|
213
|
-
|
|
247
|
+
### Weak::Cache Example
|
|
214
248
|
|
|
215
|
-
|
|
216
|
-
[](https://coveralls.io/github/meineerde/weak?branch=main)
|
|
249
|
+
We can simplify the above example by using `Weak::Cache`.
|
|
217
250
|
|
|
218
|
-
|
|
251
|
+
```ruby
|
|
252
|
+
require "weak/cache"
|
|
219
253
|
|
|
220
|
-
|
|
254
|
+
class LockedObject < BasicObject
|
|
255
|
+
LOCKS = Weak::Cache.new
|
|
256
|
+
|
|
257
|
+
def initialize(obj)
|
|
258
|
+
@obj = obj
|
|
259
|
+
@mutex = LOCKS.fetch(obj) { Mutex.new }
|
|
260
|
+
end
|
|
221
261
|
|
|
222
|
-
|
|
262
|
+
private
|
|
263
|
+
|
|
264
|
+
def method_missing(m, *args, **kwargs, &block)
|
|
265
|
+
@mutex.synchronize do
|
|
266
|
+
obj.public_send(m, *args, **kwargs, &block)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def respond_to_missing?(m)
|
|
271
|
+
obj.respond_to?(m)
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
The `LockedObject` class we have defined here works exactly the same the the one in the `Weak::Map` example above. However, it avoids having to use a separate mutex for accessing the `LOCKS` cache.
|
|
277
|
+
|
|
278
|
+
In our `LockedObject#initialize`, if the given object already has an associated mutex in the `LOCKS` cache, we return it directly. If there was no previous mutex however, the `fetch` method will call the provided block and will store the result (i.e. the new `Mutex`) in the cache and return it.
|
|
279
|
+
|
|
280
|
+
Subsequent invocations of `fetch` will return the same mutex again, unless it was garbage collected in the meantime.
|
|
281
|
+
|
|
282
|
+
## Development
|
|
283
|
+
|
|
284
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
285
|
+
|
|
286
|
+
We follow the [Standard Ruby code style](https://github.com/standardrb/standard). Please make sure that all code is formatted according to the Standard rules. This is enforced by the CI. Please try to keep all code lines at or below 100 characters in length.
|
|
223
287
|
|
|
224
288
|
## Contributing
|
|
225
289
|
|
|
226
290
|
Bug reports and pull requests are welcome on GitHub at https://github.com/meineerde/weak. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/meineerde/weak/blob/main/CODE_OF_CONDUCT.md).
|
|
227
291
|
|
|
228
|
-
## License
|
|
292
|
+
## License and Usage Policy
|
|
293
|
+
|
|
294
|
+
Weak is available as free and open source under the terms of the [MIT License](https://github.com/meineerde/weak/blob/main/LICENSE.txt).
|
|
295
|
+
|
|
296
|
+
This license does not preclude you from using Weak with AI-encumbered or AI-associated projects, but you do not have the author's consent to do so. This may seem like a contradiction, but copyright is a blunt tool that does not in general have the humanistic nuance to describe something like "you can legally do this thing, but if you do, you're an asshole."
|
|
297
|
+
|
|
298
|
+
We also explicitly affirm: Trans Rights Are Human Rights. If you do not agree, please refrain from using Weak.
|
|
229
299
|
|
|
230
|
-
|
|
300
|
+
(Thanks to [Cassandra Granade](https://codeberg.org/cgranade/do#license-and-ai-policy) for the inspiration.)
|
|
231
301
|
|
|
232
302
|
## Code of Conduct
|
|
233
303
|
|
data/lib/weak/cache.rb
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright (c) Holger Just
|
|
4
|
+
#
|
|
5
|
+
# This software may be modified and distributed under the terms
|
|
6
|
+
# of the MIT license. See the LICENSE.txt file for details.
|
|
7
|
+
|
|
8
|
+
require_relative "map"
|
|
9
|
+
|
|
10
|
+
##
|
|
11
|
+
module Weak
|
|
12
|
+
# `Weak::Cache` provides a thread-safe wrapper around {Weak::Map} to provide
|
|
13
|
+
# an object cache. As with a {Weak::Map}, keys and values are both weakly
|
|
14
|
+
# referenced so that a stored key-value pair vanishes if either the key or
|
|
15
|
+
# the value is garbage-collected.
|
|
16
|
+
#
|
|
17
|
+
# We implement an interface similar to that of `ActiveSupport::Cache::Store`.
|
|
18
|
+
class Cache
|
|
19
|
+
# Returns a new empty {Weak::Cache} object
|
|
20
|
+
def initialize
|
|
21
|
+
@map = Map.new
|
|
22
|
+
@mutex = Mutex.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Clears the entire cache.
|
|
26
|
+
#
|
|
27
|
+
# @return [self]
|
|
28
|
+
def clear
|
|
29
|
+
@mutex.synchronize { @map.clear }
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# {Weak::Cache} objects can't be frozen since this is not enforced by the
|
|
34
|
+
# underlying {Weak::Map}, resp. its `ObjectSpace::WeakMap` implementation.
|
|
35
|
+
# Thus, we try to signal this by not actually setting the `frozen?` flag and
|
|
36
|
+
# ignoring attempts to freeze us with just a warning.
|
|
37
|
+
#
|
|
38
|
+
# @param freeze [Bool, nil] ignored; we always behave as if this is false.
|
|
39
|
+
# If this is set to a truethy value, we emit a warning.
|
|
40
|
+
# @return [Weak::Cache] a new `Weak::Cache` object containing the same elements
|
|
41
|
+
# as `self`
|
|
42
|
+
def clone(freeze: false)
|
|
43
|
+
warn("Can't freeze #{self.class}") if freeze
|
|
44
|
+
@mutex.synchronize { super(freeze: false) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Deletes an entry in the cache. Returns `true` if an entry was deleted,
|
|
48
|
+
# `false` otherwise.
|
|
49
|
+
#
|
|
50
|
+
# @param key [Object] the key to delete
|
|
51
|
+
# @return [Bool] `true` if the entry was deleted, `false` otherwise
|
|
52
|
+
# @!macro weak_map_note_object_equality
|
|
53
|
+
def delete(key)
|
|
54
|
+
@mutex.synchronize {
|
|
55
|
+
@map.delete(key) do
|
|
56
|
+
return false
|
|
57
|
+
end
|
|
58
|
+
true
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @return [Weak::Cache] a new `Weak::Cache` object containing the same elements
|
|
63
|
+
# as `self`
|
|
64
|
+
def dup
|
|
65
|
+
@mutex.synchronize { super }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def each_key(&block)
|
|
69
|
+
return enum_for(__method__) { size } unless block_given?
|
|
70
|
+
keys.each(&block)
|
|
71
|
+
self
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# (see Weak::Map#empty?)
|
|
75
|
+
def empty?
|
|
76
|
+
@mutex.synchronize { @map.empty? }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# (see Weak::Map#include?)
|
|
80
|
+
def exist?(key)
|
|
81
|
+
@mutex.synchronize { @map.include?(key) }
|
|
82
|
+
end
|
|
83
|
+
alias_method :include?, :exist?
|
|
84
|
+
alias_method :key?, :exist?
|
|
85
|
+
|
|
86
|
+
# Fetches data from the cache, using the given `key`. If there is a value in
|
|
87
|
+
# the cache for the given key, that value is returned.
|
|
88
|
+
#
|
|
89
|
+
# If there is no value in the cache (a cache miss), then the given block
|
|
90
|
+
# will be passed the key and executed in the event of a cache miss. The
|
|
91
|
+
# return value of the block will be written to the cache under the given
|
|
92
|
+
# cache key, and that return value will be returned.
|
|
93
|
+
#
|
|
94
|
+
# @param key [Object] the key for the requested value
|
|
95
|
+
# @param skip_nil [Bool] prevents caching a `nil` value from the block
|
|
96
|
+
# @yield [key] if no value was set at `key`, we call the block, write its
|
|
97
|
+
# returned value for the `key` in the cache and return the value
|
|
98
|
+
# @yieldparam key [String] the given `key`
|
|
99
|
+
# @return [Object] the value for the given `key` if present in the cache. If
|
|
100
|
+
# the key was not found, we return the value of the given block.
|
|
101
|
+
# @raise [ArgumentError] if no block was provided
|
|
102
|
+
# @!macro weak_map_note_object_equality
|
|
103
|
+
def fetch(key, skip_nil: false)
|
|
104
|
+
raise ArgumentError, "must provide a block" unless block_given?
|
|
105
|
+
|
|
106
|
+
@mutex.synchronize {
|
|
107
|
+
@map.fetch(key) {
|
|
108
|
+
value = yield(key)
|
|
109
|
+
@map[key] = value unless skip_nil && value.nil?
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# {Weak::Cache} objects can't be frozen since this is not enforced by the
|
|
115
|
+
# underlying {Weak::Map}, resp. its `ObjectSpace::WeakMap` implementation.
|
|
116
|
+
# Thus, we try to signal this by not actually setting the `frozen?` flag and
|
|
117
|
+
# ignoring attempts to freeze us with just a warning.
|
|
118
|
+
#
|
|
119
|
+
# @return [self]
|
|
120
|
+
def freeze
|
|
121
|
+
warn("Can't freeze #{self.class}")
|
|
122
|
+
self
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def inspect
|
|
126
|
+
@mutex.synchronize { "#<#{self.class} #{@map._inspect}>" }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# (see Weak::Map#keys)
|
|
130
|
+
def keys
|
|
131
|
+
@mutex.synchronize { @map.keys }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# @!visibility private
|
|
135
|
+
def pretty_print(pp)
|
|
136
|
+
pp.group(1, "#<#{self.class}", ">") do
|
|
137
|
+
pp.breakable
|
|
138
|
+
pp.pp @mutex.synchronize {
|
|
139
|
+
@map.to_a.sort_by! { |k, _v| k.__id__ }.to_h
|
|
140
|
+
}
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# @!visibility private
|
|
145
|
+
def pretty_print_cycle(pp)
|
|
146
|
+
pp.text "#<#{self.class} {#{"..." unless empty?}}>"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# @param key [Object] the key for the requested value
|
|
150
|
+
# @return [Object] the value associated with the given `key`, or `nil` if
|
|
151
|
+
# no value was found for the `key`
|
|
152
|
+
# @!macro weak_map_note_object_equality
|
|
153
|
+
def read(key)
|
|
154
|
+
@mutex.synchronize { @map[key] }
|
|
155
|
+
end
|
|
156
|
+
alias_method :[], :read
|
|
157
|
+
|
|
158
|
+
def size
|
|
159
|
+
@mutex.synchronize { @map.size }
|
|
160
|
+
end
|
|
161
|
+
alias_method :length, :size
|
|
162
|
+
|
|
163
|
+
# (see Weak::Map#to_h)
|
|
164
|
+
def to_h(&block)
|
|
165
|
+
@mutex.synchronize { @map.to_h(&block) }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# (see Weak::Map#[]=)
|
|
169
|
+
def write(key, value)
|
|
170
|
+
@mutex.synchronize { @map[key] = value }
|
|
171
|
+
end
|
|
172
|
+
alias_method :[]=, :write
|
|
173
|
+
|
|
174
|
+
private
|
|
175
|
+
|
|
176
|
+
def initialize_copy(orig)
|
|
177
|
+
@map = @map.dup
|
|
178
|
+
@mutex = Mutex.new
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
data/lib/weak/map/strong_keys.rb
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
require "set"
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
require_relative "abstract_strong_keys"
|
|
11
|
+
require_relative "../undefined"
|
|
12
12
|
|
|
13
13
|
##
|
|
14
14
|
module Weak
|
|
@@ -54,7 +54,7 @@ module Weak
|
|
|
54
54
|
|
|
55
55
|
# Checks if this strategy is usable for the current Ruby version.
|
|
56
56
|
#
|
|
57
|
-
# @return [Bool] always `true` to indicate that this
|
|
57
|
+
# @return [Bool] always `true` to indicate that this strategy should be
|
|
58
58
|
# usable with any Ruby implementation which provides an
|
|
59
59
|
# `ObjectSpace::WeakMap`.
|
|
60
60
|
def self.usable?
|
data/lib/weak/map/weak_keys.rb
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
# This software may be modified and distributed under the terms
|
|
6
6
|
# of the MIT license. See the LICENSE.txt file for details.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
require_relative "deletable"
|
|
9
|
+
require_relative "../undefined"
|
|
10
10
|
|
|
11
11
|
##
|
|
12
12
|
module Weak
|
|
@@ -66,7 +66,8 @@ module Weak
|
|
|
66
66
|
return enum_for(__method__) { size } unless block_given?
|
|
67
67
|
|
|
68
68
|
@map.keys.each do |key|
|
|
69
|
-
|
|
69
|
+
raw_value = @map[key]
|
|
70
|
+
yield key unless missing?(raw_value)
|
|
70
71
|
end
|
|
71
72
|
self
|
|
72
73
|
end
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# This software may be modified and distributed under the terms
|
|
6
6
|
# of the MIT license. See the LICENSE.txt file for details.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
require_relative "../undefined"
|
|
9
9
|
|
|
10
10
|
##
|
|
11
11
|
module Weak
|
|
@@ -21,16 +21,14 @@ module Weak
|
|
|
21
21
|
# either of them vanishes, the entry is removed.
|
|
22
22
|
#
|
|
23
23
|
# The `ObjectSpace::WeakMap` also allows to delete entries. This allows us
|
|
24
|
-
# to directly use the `ObjectSpace::WeakMap` as a storage
|
|
25
|
-
# `Set` uses a `Hash` object object as storage.
|
|
24
|
+
# to directly use the `ObjectSpace::WeakMap` as a storage object.
|
|
26
25
|
module WeakKeysWithDelete
|
|
27
26
|
# Checks if this strategy is usable for the current Ruby version.
|
|
28
27
|
#
|
|
29
28
|
# @return [Bool] truethy for Ruby (aka. MRI, aka. YARV) >= 3.3.0,
|
|
30
29
|
# falsey otherwise
|
|
31
30
|
def self.usable?
|
|
32
|
-
RUBY_ENGINE == "ruby" &&
|
|
33
|
-
ObjectSpace::WeakMap.instance_methods.include?(:delete)
|
|
31
|
+
RUBY_ENGINE == "ruby" && ObjectSpace::WeakMap.method_defined?(:delete)
|
|
34
32
|
end
|
|
35
33
|
|
|
36
34
|
# @!macro weak_map_accessor_read
|
data/lib/weak/map.rb
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
# This software may be modified and distributed under the terms
|
|
6
6
|
# of the MIT license. See the LICENSE.txt file for details.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
require_relative "map/weak_keys_with_delete"
|
|
9
|
+
require_relative "map/weak_keys"
|
|
10
|
+
require_relative "map/strong_keys"
|
|
11
|
+
require_relative "map/strong_secondary_keys"
|
|
12
|
+
require_relative "undefined"
|
|
13
13
|
|
|
14
14
|
##
|
|
15
15
|
module Weak
|
|
@@ -34,7 +34,8 @@ module Weak
|
|
|
34
34
|
#
|
|
35
35
|
# Note that {Weak::Map} is not inherently thread-safe. When accessing a
|
|
36
36
|
# {Weak::Map} from multiple threads or fibers, you MUST use a mutex or another
|
|
37
|
-
# locking mechanism.
|
|
37
|
+
# locking mechanism. You can also use {Weak::Cache} as a thread-safe
|
|
38
|
+
# alternative.
|
|
38
39
|
#
|
|
39
40
|
# ## Implementation Details
|
|
40
41
|
#
|
|
@@ -96,7 +97,7 @@ module Weak
|
|
|
96
97
|
# Here follows the documentation of strategy-specific methods which are
|
|
97
98
|
# implemented in one of the include modules depending on the current Ruby.
|
|
98
99
|
|
|
99
|
-
# @!macro
|
|
100
|
+
# @!macro weak_map_note_object_equality
|
|
100
101
|
# @note {Weak::Map} does not test member equality with `==` or `eql?`.
|
|
101
102
|
# Instead, it always checks strict object equality, so that, e.g.,
|
|
102
103
|
# different String keys are not considered equal, even if they may
|
|
@@ -108,7 +109,7 @@ module Weak
|
|
|
108
109
|
# `key` is not found, returns the default value, i.e. the value returned
|
|
109
110
|
# by the default proc (if defined) or the `default` value (which is
|
|
110
111
|
# initially `nil`.)
|
|
111
|
-
# @!macro
|
|
112
|
+
# @!macro weak_map_note_object_equality
|
|
112
113
|
|
|
113
114
|
# @!macro weak_map_accessor_write
|
|
114
115
|
# Associates the given `value` with the given `key`; returns `value`. If
|
|
@@ -117,7 +118,7 @@ module Weak
|
|
|
117
118
|
# @param key [Object] the key for the set key-value pair
|
|
118
119
|
# @param value [Object] the value of the set key-value pair
|
|
119
120
|
# @return [Object] the given `value`
|
|
120
|
-
# @!macro
|
|
121
|
+
# @!macro weak_map_note_object_equality
|
|
121
122
|
|
|
122
123
|
# @!macro weak_map_method_clear
|
|
123
124
|
# Removes all elements and returns `self`
|
|
@@ -136,7 +137,7 @@ module Weak
|
|
|
136
137
|
# if the key was not found and no block was given.
|
|
137
138
|
# @yield [key]
|
|
138
139
|
# @yieldparam key [Object] the given `key` if it was not part of the map
|
|
139
|
-
# @!macro
|
|
140
|
+
# @!macro weak_map_note_object_equality
|
|
140
141
|
|
|
141
142
|
# @!macro weak_map_method_each_pair
|
|
142
143
|
# Calls the given block once for each live key in `self`, passing the key
|
|
@@ -191,13 +192,13 @@ module Weak
|
|
|
191
192
|
# the given block.
|
|
192
193
|
# @raise [KeyError] if the key can not be found and no block or `default`
|
|
193
194
|
# value was provided
|
|
194
|
-
# @!macro
|
|
195
|
+
# @!macro weak_map_note_object_equality
|
|
195
196
|
|
|
196
197
|
# @!macro weak_map_method_include_question
|
|
197
198
|
# @param key [Object] a possible key
|
|
198
199
|
# @return [Bool] `true` if the given key is included in `self` and has an
|
|
199
200
|
# associated live value, `false` otherwise
|
|
200
|
-
# @!macro
|
|
201
|
+
# @!macro weak_map_note_object_equality
|
|
201
202
|
|
|
202
203
|
# @!macro weak_map_method_keys
|
|
203
204
|
# @return [Array] an `Array` containing all keys of the map for which we
|
|
@@ -288,7 +289,7 @@ module Weak
|
|
|
288
289
|
# The initial default value and initial default proc for the new hash depend
|
|
289
290
|
# on which form above was used.
|
|
290
291
|
#
|
|
291
|
-
# If neither
|
|
292
|
+
# If neither a `default_value` nor a block is given, initializes both the
|
|
292
293
|
# default value and the default proc to nil:
|
|
293
294
|
#
|
|
294
295
|
# map = Weak::Map.new
|
|
@@ -332,7 +333,7 @@ module Weak
|
|
|
332
333
|
alias_method :key?, :include?
|
|
333
334
|
alias_method :member?, :include?
|
|
334
335
|
alias_method :length, :size
|
|
335
|
-
alias_method :store, :[]
|
|
336
|
+
alias_method :store, :[]=
|
|
336
337
|
|
|
337
338
|
# {Weak::Map} objects can't be frozen since this is not enforced by the
|
|
338
339
|
# underlying `ObjectSpace::WeakMap` implementation. Thus, we try to signal
|
|
@@ -341,7 +342,7 @@ module Weak
|
|
|
341
342
|
#
|
|
342
343
|
# @param freeze [Bool, nil] ignored; we always behave as if this is false.
|
|
343
344
|
# If this is set to a truethy value, we emit a warning.
|
|
344
|
-
# @return [Weak::
|
|
345
|
+
# @return [Weak::Map] a new `Weak::Map` object containing the same elements
|
|
345
346
|
# as `self`
|
|
346
347
|
def clone(freeze: false)
|
|
347
348
|
warn("Can't freeze #{self.class}") if freeze
|
|
@@ -399,7 +400,7 @@ module Weak
|
|
|
399
400
|
# Sets the default proc for self to `proc` and clears the {#default} value.
|
|
400
401
|
#
|
|
401
402
|
# @param proc [Proc, #to_proc nil] a `Proc` which can be called with two
|
|
402
|
-
# arguments: the map and the
|
|
403
|
+
# arguments: the map and the requested non-exiting key. The proc is
|
|
403
404
|
# expected to return the default value for the key. Whe giving `nil`, the
|
|
404
405
|
# default proc is cleared.
|
|
405
406
|
# @return [Proc, nil] the new default proc
|
|
@@ -457,7 +458,7 @@ module Weak
|
|
|
457
458
|
size == 0
|
|
458
459
|
end
|
|
459
460
|
|
|
460
|
-
# {Weak::
|
|
461
|
+
# {Weak::Map} objects can't be frozen since this is not enforced by the
|
|
461
462
|
# underlying `ObjectSpace::WeakMap` implementation. Thus, we try to signal
|
|
462
463
|
# this by not actually setting the `frozen?` flag and ignoring attempts to
|
|
463
464
|
# freeze us with just a warning.
|
|
@@ -471,7 +472,7 @@ module Weak
|
|
|
471
472
|
# @param value [Object] a value to check
|
|
472
473
|
# @return [Bool] `true` if `value` is a value in `self`, `false` otherwise
|
|
473
474
|
#
|
|
474
|
-
# @!macro
|
|
475
|
+
# @!macro weak_map_note_object_equality
|
|
475
476
|
def has_value?(value)
|
|
476
477
|
id = value.__id__
|
|
477
478
|
each_value.any? { |v| v.__id__ == id }
|
|
@@ -482,18 +483,25 @@ module Weak
|
|
|
482
483
|
# the weak set, e.g.,
|
|
483
484
|
# `"#<Weak::Map {key1 => value1, key2 => value2, ...}>"`
|
|
484
485
|
def inspect
|
|
486
|
+
"#<#{self.class} #{_inspect}>"
|
|
487
|
+
end
|
|
488
|
+
alias_method :to_s, :inspect
|
|
489
|
+
|
|
490
|
+
# @return [String] a string containing a human-readable representation of
|
|
491
|
+
# just the data of the weak set, e.g.,
|
|
492
|
+
# `"{key1 => value1, key2 => value2, ...}"`
|
|
493
|
+
# @!visibility private
|
|
494
|
+
def _inspect
|
|
485
495
|
object_ids = (Thread.current[INSPECT_KEY] ||= [])
|
|
486
|
-
return "
|
|
496
|
+
return "{...}" if object_ids.include?(__id__)
|
|
487
497
|
|
|
488
|
-
object_ids <<
|
|
498
|
+
object_ids << __id__
|
|
489
499
|
begin
|
|
490
|
-
|
|
491
|
-
"#<#{self.class} #{elements}>"
|
|
500
|
+
to_a.sort_by! { |k, _v| k.__id__ }.to_h.inspect
|
|
492
501
|
ensure
|
|
493
502
|
object_ids.pop
|
|
494
503
|
end
|
|
495
504
|
end
|
|
496
|
-
alias_method :to_s, :inspect
|
|
497
505
|
|
|
498
506
|
# Deletes every key-value pair from `self` for which the given block
|
|
499
507
|
# evaluates to a falsey value.
|
|
@@ -537,7 +545,7 @@ module Weak
|
|
|
537
545
|
# h1 = {baz: 3, bar: 4}
|
|
538
546
|
# h2 = {bam: 5, baz: 6}
|
|
539
547
|
# map.merge(h1, h2)
|
|
540
|
-
# # => #<Weak::Map {:foo=>0, :bar=>4, :baz=>6, :bam=>5}
|
|
548
|
+
# # => #<Weak::Map {:foo=>0, :bar=>4, :baz=>6, :bam=>5}>
|
|
541
549
|
#
|
|
542
550
|
# With arguments and a block:
|
|
543
551
|
#
|
|
@@ -559,7 +567,7 @@ module Weak
|
|
|
559
567
|
# h1 = {baz: 3, bar: 4}
|
|
560
568
|
# h2 = {bam: 5, baz: 6}
|
|
561
569
|
# map.merge(h1, h2) { |key, old_value, new_value| old_value + new_value }
|
|
562
|
-
# # => #<Weak::Map {:foo=>0, :bar=>5, :baz=>9, :bam=>5}
|
|
570
|
+
# # => #<Weak::Map {:foo=>0, :bar=>5, :baz=>9, :bam=>5}>
|
|
563
571
|
#
|
|
564
572
|
# With no arguments:
|
|
565
573
|
#
|
|
@@ -578,7 +586,7 @@ module Weak
|
|
|
578
586
|
# `other_maps`
|
|
579
587
|
# @return [Weak::Map] a new weak map containing the merged pairs
|
|
580
588
|
#
|
|
581
|
-
# @!macro
|
|
589
|
+
# @!macro weak_map_note_object_equality
|
|
582
590
|
def merge(*other_maps, &block)
|
|
583
591
|
dup.merge!(*other_maps, &block)
|
|
584
592
|
end
|
|
@@ -606,9 +614,9 @@ module Weak
|
|
|
606
614
|
# @yield [key, value] calls the given block once for each key in the map
|
|
607
615
|
# @yieldparam key [Object] a key
|
|
608
616
|
# @return [Enumerator, self, nil] `self` if a block was given and some
|
|
609
|
-
#
|
|
617
|
+
# element(s) were deleted, `nil` if a block was given but no keys were
|
|
610
618
|
# deleted, or an `Enumerator` if no block was given.
|
|
611
|
-
#
|
|
619
|
+
# @see #delete_if
|
|
612
620
|
def reject!(&block)
|
|
613
621
|
return enum_for(__method__) { size } unless block_given?
|
|
614
622
|
|
|
@@ -650,8 +658,8 @@ module Weak
|
|
|
650
658
|
# @yieldparam key [Object] a key
|
|
651
659
|
# @yieldparam value [Object] the corresponding value
|
|
652
660
|
# @return [Enumerator, self, nil] `self` if a block was given and some
|
|
653
|
-
#
|
|
654
|
-
#
|
|
661
|
+
# element(s) were deleted, `nil` if a block was given but nothing was
|
|
662
|
+
# deleted, or an `Enumerator` if no block was given.
|
|
655
663
|
# @see keep_if
|
|
656
664
|
def select!(&block)
|
|
657
665
|
return enum_for(__method__) { size } unless block_given?
|
|
@@ -688,7 +696,7 @@ module Weak
|
|
|
688
696
|
# h1 = {baz: 3, bar: 4}
|
|
689
697
|
# h2 = {bam: 5, baz: 6}
|
|
690
698
|
# map.update(h1, h2)
|
|
691
|
-
# # => #<Weak::Map {:foo=>0, :bar=>4, :baz=>6, :bam=>5}
|
|
699
|
+
# # => #<Weak::Map {:foo=>0, :bar=>4, :baz=>6, :bam=>5}>
|
|
692
700
|
#
|
|
693
701
|
# With arguments and a block:
|
|
694
702
|
#
|
|
@@ -710,7 +718,7 @@ module Weak
|
|
|
710
718
|
# h1 = {baz: 3, bar: 4}
|
|
711
719
|
# h2 = {bam: 5, baz: 6}
|
|
712
720
|
# map.update(h1, h2) { |key, old_value, new_value| old_value + new_value }
|
|
713
|
-
# # => #<Weak::Map {:foo=>0, :bar=>5, :baz=>9, :bam=>5}
|
|
721
|
+
# # => #<Weak::Map {:foo=>0, :bar=>5, :baz=>9, :bam=>5}>
|
|
714
722
|
#
|
|
715
723
|
# With no arguments:
|
|
716
724
|
#
|
|
@@ -729,7 +737,7 @@ module Weak
|
|
|
729
737
|
# `other_maps`
|
|
730
738
|
# @return [self]
|
|
731
739
|
#
|
|
732
|
-
# @!macro
|
|
740
|
+
# @!macro weak_map_note_object_equality
|
|
733
741
|
def update(*other_maps)
|
|
734
742
|
if block_given?
|
|
735
743
|
missing = Object.new
|
|
@@ -44,7 +44,7 @@ module Weak
|
|
|
44
44
|
|
|
45
45
|
# Checks if this strategy is usable for the current Ruby version.
|
|
46
46
|
#
|
|
47
|
-
# @return [Bool] always `true` to indicate that this
|
|
47
|
+
# @return [Bool] always `true` to indicate that this strategy should be
|
|
48
48
|
# usable with any Ruby implementation which provides an
|
|
49
49
|
# `ObjectSpace::WeakMap`.
|
|
50
50
|
def self.usable?
|
|
@@ -19,16 +19,14 @@ module Weak
|
|
|
19
19
|
# either of them vanishes, the entry is removed.
|
|
20
20
|
#
|
|
21
21
|
# The `ObjectSpace::WeakMap` also allows to delete entries. This allows us
|
|
22
|
-
# to directly use the `ObjectSpace::WeakMap` as a storage
|
|
23
|
-
# `::Set` uses a `Hash` object object as storage.
|
|
22
|
+
# to directly use the `ObjectSpace::WeakMap` as a storage object.
|
|
24
23
|
module WeakKeysWithDelete
|
|
25
24
|
# Checks if this strategy is usable for the current Ruby version.
|
|
26
25
|
#
|
|
27
26
|
# @return [Bool] truethy for Ruby (aka. MRI, aka. YARV) >= 3.3.0,
|
|
28
27
|
# falsey otherwise
|
|
29
28
|
def self.usable?
|
|
30
|
-
RUBY_ENGINE == "ruby" &&
|
|
31
|
-
ObjectSpace::WeakMap.instance_methods.include?(:delete)
|
|
29
|
+
RUBY_ENGINE == "ruby" && ObjectSpace::WeakMap.method_defined?(:delete)
|
|
32
30
|
end
|
|
33
31
|
|
|
34
32
|
# @!macro weak_set_method_add
|
data/lib/weak/set.rb
CHANGED
|
@@ -71,11 +71,11 @@ module Weak
|
|
|
71
71
|
# @example
|
|
72
72
|
# require "weak/set"
|
|
73
73
|
#
|
|
74
|
-
# s1 = Weak::Set[1, 2] #=>
|
|
75
|
-
# s2 = Weak::Set.new [1, 2] #=>
|
|
74
|
+
# s1 = Weak::Set[1, 2] #=> Weak::Set[1, 2]
|
|
75
|
+
# s2 = Weak::Set.new [1, 2] #=> Weak::Set[1, 2]
|
|
76
76
|
# s1 == s2 #=> true
|
|
77
|
-
# s1.add(:foo) #=>
|
|
78
|
-
# s1.merge([2, 6]) #=>
|
|
77
|
+
# s1.add(:foo) #=> Weak::Set[1, 2, :foo]
|
|
78
|
+
# s1.merge([2, 6]) #=> Weak::Set[1, 2, 6, :foo]
|
|
79
79
|
# s1.subset?(s2) #=> false
|
|
80
80
|
# s2.subset?(s1) #=> true
|
|
81
81
|
class Set
|
|
@@ -97,7 +97,7 @@ module Weak
|
|
|
97
97
|
# Here follows the documentation of strategy-specific methods which are
|
|
98
98
|
# implemented in one of the include modules depending on the current Ruby.
|
|
99
99
|
|
|
100
|
-
# @!macro
|
|
100
|
+
# @!macro weak_set_note_object_equality
|
|
101
101
|
# @note {Weak::Set} does not test member equality with `==` or `eql?`.
|
|
102
102
|
# Instead, it always checks strict object equality, so that, e.g.,
|
|
103
103
|
# different strings are not considered equal, even if they may contain
|
|
@@ -115,9 +115,9 @@ module Weak
|
|
|
115
115
|
# @return [self]
|
|
116
116
|
#
|
|
117
117
|
# @example
|
|
118
|
-
# Weak::Set[1, 2].add(3) #=>
|
|
119
|
-
# Weak::Set[1, 2].add([3, 4]) #=>
|
|
120
|
-
# Weak::Set[1, 2].add(2) #=>
|
|
118
|
+
# Weak::Set[1, 2].add(3) #=> Weak::Set[1, 2, 3]
|
|
119
|
+
# Weak::Set[1, 2].add([3, 4]) #=> Weak::Set[1, 2, [3, 4]]
|
|
120
|
+
# Weak::Set[1, 2].add(2) #=> Weak::Set[1, 2]
|
|
121
121
|
|
|
122
122
|
# @!macro weak_set_method_clear
|
|
123
123
|
# Removes all elements and returns `self`
|
|
@@ -131,7 +131,7 @@ module Weak
|
|
|
131
131
|
# @param obj [Object]
|
|
132
132
|
# @return [self, nil] `self` if the given object was deleted from the set
|
|
133
133
|
# or `nil` if the object was not part of the set
|
|
134
|
-
# @!macro
|
|
134
|
+
# @!macro weak_set_note_object_equality
|
|
135
135
|
|
|
136
136
|
# @!macro weak_set_method_each
|
|
137
137
|
# Calls the given block once for each live element in `self`, passing that
|
|
@@ -148,7 +148,7 @@ module Weak
|
|
|
148
148
|
# @param obj [Object] an object
|
|
149
149
|
# @return [Bool] `true` if the given object is included in `self`, `false`
|
|
150
150
|
# otherwise
|
|
151
|
-
# @!macro
|
|
151
|
+
# @!macro weak_set_note_object_equality
|
|
152
152
|
|
|
153
153
|
# @!macro weak_set_method_prune
|
|
154
154
|
# Cleanup data structures from the set to remove data associated with
|
|
@@ -164,9 +164,9 @@ module Weak
|
|
|
164
164
|
# @param enum (see #do_with_enum)
|
|
165
165
|
# @return [self]
|
|
166
166
|
# @example
|
|
167
|
-
# set = Weak::Set[1, :c, :s] #=>
|
|
168
|
-
# set.replace([1, 2]) #=>
|
|
169
|
-
# set #=>
|
|
167
|
+
# set = Weak::Set[1, :c, :s] #=> Weak::Set[1, :c, :s]
|
|
168
|
+
# set.replace([1, 2]) #=> Weak::Set[1, 2]
|
|
169
|
+
# set #=> Weak::Set[1, 2]
|
|
170
170
|
|
|
171
171
|
# @!macro weak_set_method_size
|
|
172
172
|
# @return [Integer] the number of live elements in `self`
|
|
@@ -212,15 +212,16 @@ module Weak
|
|
|
212
212
|
INSPECT_KEY = :__inspect_key__
|
|
213
213
|
private_constant :INSPECT_KEY
|
|
214
214
|
|
|
215
|
-
# @param
|
|
215
|
+
# @param objects [Array<Object>] a list of objects
|
|
216
216
|
# @return [Weak::Set] a new weak set containing the given objects
|
|
217
|
+
# @see #initialize
|
|
217
218
|
#
|
|
218
219
|
# @example
|
|
219
|
-
# Weak::Set[1, 2] # =>
|
|
220
|
-
# Weak::Set[1, 2, 1] # =>
|
|
221
|
-
# Weak::Set[1, :c, :s] # =>
|
|
222
|
-
def self.[](*
|
|
223
|
-
new(
|
|
220
|
+
# Weak::Set[1, 2] # => Weak::Set[1, 2]
|
|
221
|
+
# Weak::Set[1, 2, 1] # => Weak::Set[1, 2]
|
|
222
|
+
# Weak::Set[1, :c, :s] # => Weak::Set[1, :c, :s]
|
|
223
|
+
def self.[](*objects)
|
|
224
|
+
new(objects)
|
|
224
225
|
end
|
|
225
226
|
|
|
226
227
|
# @param enum (see #do_with_enum)
|
|
@@ -253,11 +254,11 @@ module Weak
|
|
|
253
254
|
# @param enum (see #do_with_enum)
|
|
254
255
|
# @return [Weak::Set] a new weak set built by merging `self` and the elements
|
|
255
256
|
# of the given enumerable object.
|
|
256
|
-
# @!macro
|
|
257
|
+
# @!macro weak_set_note_object_equality
|
|
257
258
|
#
|
|
258
259
|
# @example
|
|
259
|
-
# Weak::Set[1, 2, 3] | Weak::Set[2, 4, 5] # =>
|
|
260
|
-
# Weak::Set[1, 3, :z] | (1..4) # =>
|
|
260
|
+
# Weak::Set[1, 2, 3] | Weak::Set[2, 4, 5] # => Weak::Set[1, 2, 3, 4, 5]
|
|
261
|
+
# Weak::Set[1, 3, :z] | (1..4) # => Weak::Set[1, 2, 3, 4, :z]
|
|
261
262
|
def |(enum)
|
|
262
263
|
new_set = dup
|
|
263
264
|
do_with_enum(enum) do |obj|
|
|
@@ -271,11 +272,11 @@ module Weak
|
|
|
271
272
|
# @param enum (see #do_with_enum)
|
|
272
273
|
# @return [Weak::Set] a new weak set built by duplicating `self`, removing
|
|
273
274
|
# every element that appears in the given enumerable object from that.
|
|
274
|
-
# @!macro
|
|
275
|
+
# @!macro weak_set_note_object_equality
|
|
275
276
|
#
|
|
276
277
|
# @example
|
|
277
|
-
# Weak::Set[1, 3, 5] - Weak::Set[1, 5] # =>
|
|
278
|
-
# Weak::Set['a', 'b', 'z'] - ['a', 'c'] # =>
|
|
278
|
+
# Weak::Set[1, 3, 5] - Weak::Set[1, 5] # => Weak::Set[3]
|
|
279
|
+
# Weak::Set['a', 'b', 'z'] - ['a', 'c'] # => Weak::Set["b", "z"]
|
|
279
280
|
def -(enum)
|
|
280
281
|
dup.subtract(enum)
|
|
281
282
|
end
|
|
@@ -284,11 +285,11 @@ module Weak
|
|
|
284
285
|
# @param enum (see #do_with_enum g)
|
|
285
286
|
# @return [Weak::Set] a new weak set containing elements common to `self`
|
|
286
287
|
# and the given enumerable object.
|
|
287
|
-
# @!macro
|
|
288
|
+
# @!macro weak_set_note_object_equality
|
|
288
289
|
#
|
|
289
290
|
# @example
|
|
290
|
-
# Weak::Set[1, 3, 5] & Weak::Set[3, 2, 1] # =>
|
|
291
|
-
# Weak::Set[1, 2, 9] & [2, 1, 3] # =>
|
|
291
|
+
# Weak::Set[1, 3, 5] & Weak::Set[3, 2, 1] # => Weak::Set[1, 3]
|
|
292
|
+
# Weak::Set[1, 2, 9] & [2, 1, 3] # => Weak::Set[1, 2]
|
|
292
293
|
def &(enum)
|
|
293
294
|
new_set = self.class.new
|
|
294
295
|
do_with_enum(enum) do |obj|
|
|
@@ -303,7 +304,7 @@ module Weak
|
|
|
303
304
|
# elements, `-1` / `+1` if `self` is a proper subset / superset of the
|
|
304
305
|
# given `set`, or `nil` if they both have unique elements or `set` is not
|
|
305
306
|
# a {Weak::Set}
|
|
306
|
-
# @!macro
|
|
307
|
+
# @!macro weak_set_note_object_equality
|
|
307
308
|
def <=>(other)
|
|
308
309
|
return unless Weak::Set === other
|
|
309
310
|
return 0 if equal?(other)
|
|
@@ -349,11 +350,11 @@ module Weak
|
|
|
349
350
|
#
|
|
350
351
|
# @param enum (see #do_with_enum)
|
|
351
352
|
# @return [Weak::Set] a new weak set
|
|
352
|
-
# @!macro
|
|
353
|
+
# @!macro weak_set_note_object_equality
|
|
353
354
|
#
|
|
354
355
|
# @example
|
|
355
|
-
# Weak::Set[1, 2] ^ Set[2, 3] #=>
|
|
356
|
-
# Weak::Set[1, :b, :c] ^ [:b, :d] #=>
|
|
356
|
+
# Weak::Set[1, 2] ^ Set[2, 3] #=> Weak::Set[1, 3]
|
|
357
|
+
# Weak::Set[1, :b, :c] ^ [:b, :d] #=> Weak::Set[1, :c, :d]
|
|
357
358
|
def ^(enum)
|
|
358
359
|
return dup if enum.nil?
|
|
359
360
|
|
|
@@ -368,7 +369,7 @@ module Weak
|
|
|
368
369
|
# @return [Object, nil] the provided `obj` if it is included in `self`,
|
|
369
370
|
# `nil` otherwise
|
|
370
371
|
# @see #include?
|
|
371
|
-
# @!macro
|
|
372
|
+
# @!macro weak_set_note_object_equality
|
|
372
373
|
def [](obj)
|
|
373
374
|
obj if include?(obj)
|
|
374
375
|
end
|
|
@@ -379,11 +380,11 @@ module Weak
|
|
|
379
380
|
# @param obj [Object] an object to add to the weak set
|
|
380
381
|
# @return [self, nil] `self` if the object was added, `nil` if it was part
|
|
381
382
|
# of the set already
|
|
382
|
-
# @!macro
|
|
383
|
+
# @!macro weak_set_note_object_equality
|
|
383
384
|
#
|
|
384
385
|
# @example
|
|
385
|
-
# Weak::Set[1, 2].add?(3) #=>
|
|
386
|
-
# Weak::Set[1, 2].add?([3, 4]) #=>
|
|
386
|
+
# Weak::Set[1, 2].add?(3) #=> Weak::Set[1, 2, 3]
|
|
387
|
+
# Weak::Set[1, 2].add?([3, 4]) #=> Weak::Set[1, 2, [3, 4]]
|
|
387
388
|
# Weak::Set[1, 2].add?(2) #=> nil
|
|
388
389
|
def add?(obj)
|
|
389
390
|
add(obj) unless include?(obj)
|
|
@@ -423,7 +424,7 @@ module Weak
|
|
|
423
424
|
#
|
|
424
425
|
# @param obj [Object] an object to delete from the weak set
|
|
425
426
|
# @return [self] always returns self
|
|
426
|
-
# @!macro
|
|
427
|
+
# @!macro weak_set_note_object_equality
|
|
427
428
|
def delete(obj)
|
|
428
429
|
delete?(obj)
|
|
429
430
|
self
|
|
@@ -450,7 +451,7 @@ module Weak
|
|
|
450
451
|
# @param enum (see #intersect)
|
|
451
452
|
# @return [Bool] `true` if `self` and the given `enum` have no element in
|
|
452
453
|
# common. This method is the opposite of {#intersect?}.
|
|
453
|
-
# @!macro
|
|
454
|
+
# @!macro weak_set_note_object_equality
|
|
454
455
|
def disjoint?(enum)
|
|
455
456
|
!intersect?(enum)
|
|
456
457
|
end
|
|
@@ -472,15 +473,18 @@ module Weak
|
|
|
472
473
|
end
|
|
473
474
|
|
|
474
475
|
# @return [String] a string containing a human-readable representation of
|
|
475
|
-
# the weak set, e.g., `"
|
|
476
|
+
# the weak set, e.g., `"Weak::Set[element1, element2, ...]"`
|
|
477
|
+
# @note The elements of the set are ordered by their object id in the
|
|
478
|
+
# inspect output. If we detect a reference cycle (e.g. with a set
|
|
479
|
+
# containing itself), we output `"Weak::Set[...]"`.
|
|
476
480
|
def inspect
|
|
477
481
|
object_ids = (Thread.current[INSPECT_KEY] ||= [])
|
|
478
|
-
return "
|
|
482
|
+
return "#{self.class}[...]" if object_ids.include?(object_id)
|
|
479
483
|
|
|
480
484
|
object_ids << object_id
|
|
481
485
|
begin
|
|
482
486
|
elements = to_a.sort_by!(&:__id__).inspect[1..-2]
|
|
483
|
-
"
|
|
487
|
+
"#{self.class}[#{elements}]"
|
|
484
488
|
ensure
|
|
485
489
|
object_ids.pop
|
|
486
490
|
end
|
|
@@ -490,7 +494,7 @@ module Weak
|
|
|
490
494
|
# @param enum (see #enumerable)
|
|
491
495
|
# @return [Bool] `true` if `self` and the given enumerable object have at
|
|
492
496
|
# least one element in common, `false` otherwise
|
|
493
|
-
# @!macro
|
|
497
|
+
# @!macro weak_set_note_object_equality
|
|
494
498
|
#
|
|
495
499
|
# @example
|
|
496
500
|
# Weak::Set[1, 2, 3].intersect? Weak::Set[4, 5] #=> false
|
|
@@ -549,19 +553,16 @@ module Weak
|
|
|
549
553
|
|
|
550
554
|
# @!visibility private
|
|
551
555
|
def pretty_print(pp)
|
|
552
|
-
pp.group(1, "
|
|
553
|
-
pp.
|
|
554
|
-
|
|
555
|
-
pp.seplist(to_a.sort_by!(&:__id__)) do |obj|
|
|
556
|
-
pp.pp obj
|
|
557
|
-
end
|
|
556
|
+
pp.group(1, "#{self.class}[", "]") do
|
|
557
|
+
pp.seplist(to_a.sort_by!(&:__id__)) do |obj|
|
|
558
|
+
pp.pp obj
|
|
558
559
|
end
|
|
559
560
|
end
|
|
560
561
|
end
|
|
561
562
|
|
|
562
563
|
# @!visibility private
|
|
563
564
|
def pretty_print_cycle(pp)
|
|
564
|
-
pp.text "
|
|
565
|
+
pp.text "#{self.class}[#{"..." unless empty?}]"
|
|
565
566
|
end
|
|
566
567
|
|
|
567
568
|
# @param other [Weak::Set] a weak set
|
|
@@ -584,7 +585,7 @@ module Weak
|
|
|
584
585
|
# @param other [Weak::Set] a weak set
|
|
585
586
|
# @return [Bool] `true` if `self` is a proper superset of the given `set`,
|
|
586
587
|
# `false` otherwise
|
|
587
|
-
# @!macro
|
|
588
|
+
# @!macro weak_set_note_object_equality
|
|
588
589
|
# @see superset?
|
|
589
590
|
def proper_superset?(other)
|
|
590
591
|
if Weak::Set === other
|
|
@@ -609,9 +610,9 @@ module Weak
|
|
|
609
610
|
# @yield [element] calls the given block once for each live object in `self`
|
|
610
611
|
# @yieldparam element [Object] the element to check
|
|
611
612
|
# @return [Enumerator, self, nil] `self` if a block was given and some
|
|
612
|
-
#
|
|
613
|
+
# element(s) were deleted, `nil` if a block was given but no keys were
|
|
613
614
|
# deleted, or an `Enumerator` if no block was given.
|
|
614
|
-
#
|
|
615
|
+
# @see #delete_if
|
|
615
616
|
def reject!(&block)
|
|
616
617
|
return enum_for(__method__) { size } unless block_given?
|
|
617
618
|
|
|
@@ -633,8 +634,8 @@ module Weak
|
|
|
633
634
|
# @yield [element] calls the given block once for each element in the set
|
|
634
635
|
# @yieldparam element [Object] the element to check
|
|
635
636
|
# @return [Enumerator, self, nil] `self` if a block was given and some
|
|
636
|
-
#
|
|
637
|
-
#
|
|
637
|
+
# element(s) were deleted, `nil` if a block was given but nothing was
|
|
638
|
+
# deleted, or an `Enumerator` if no block was given.
|
|
638
639
|
# @see keep_if
|
|
639
640
|
def select!(&block)
|
|
640
641
|
return enum_for(__method__) { size } unless block_given?
|
|
@@ -651,7 +652,7 @@ module Weak
|
|
|
651
652
|
# @param other [Weak::Set] a weak set
|
|
652
653
|
# @return [Bool] `true` if `self` is a subset of the given `set`, `false`
|
|
653
654
|
# otherwise
|
|
654
|
-
# @!macro
|
|
655
|
+
# @!macro weak_set_note_object_equality
|
|
655
656
|
# @see proper_subset?
|
|
656
657
|
def subset?(other)
|
|
657
658
|
if Weak::Set === other
|
data/lib/weak/version.rb
CHANGED
|
@@ -7,16 +7,16 @@
|
|
|
7
7
|
|
|
8
8
|
##
|
|
9
9
|
module Weak
|
|
10
|
-
# Version information about {Weak}
|
|
10
|
+
# Version information about {Weak}. We follow semantic versioning.
|
|
11
11
|
module Version
|
|
12
12
|
# MAJOR version. It is incremented after incompatible API changes
|
|
13
13
|
MAJOR = 0
|
|
14
14
|
# MINOR version. It is incremented after adding functionality in a
|
|
15
15
|
# backwards-compatible manner
|
|
16
|
-
MINOR =
|
|
16
|
+
MINOR = 3
|
|
17
17
|
# PATCH version. It is incremented when making backwards-compatible
|
|
18
18
|
# bug-fixes.
|
|
19
|
-
PATCH =
|
|
19
|
+
PATCH = 0
|
|
20
20
|
# PRERELEASE suffix. Set to a alphanumeric string on any pre-release
|
|
21
21
|
# versions like beta or RC releases; `nil` on regular releases
|
|
22
22
|
PRERELEASE = nil
|
data/lib/weak.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: weak
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Holger Just
|
|
@@ -24,6 +24,7 @@ files:
|
|
|
24
24
|
- LICENSE.txt
|
|
25
25
|
- README.md
|
|
26
26
|
- lib/weak.rb
|
|
27
|
+
- lib/weak/cache.rb
|
|
27
28
|
- lib/weak/map.rb
|
|
28
29
|
- lib/weak/map/abstract_strong_keys.rb
|
|
29
30
|
- lib/weak/map/deletable.rb
|
|
@@ -46,7 +47,7 @@ metadata:
|
|
|
46
47
|
homepage_uri: https://github.com/meineerde/weak
|
|
47
48
|
source_code_uri: https://github.com/meineerde/weak
|
|
48
49
|
changelog_uri: https://github.com/meineerde/weak/blob/main/CHANGELOG.md
|
|
49
|
-
documentation_uri: https://www.rubydoc.info/gems/weak/0.
|
|
50
|
+
documentation_uri: https://www.rubydoc.info/gems/weak/0.3.0
|
|
50
51
|
rdoc_options: []
|
|
51
52
|
require_paths:
|
|
52
53
|
- lib
|
|
@@ -61,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
61
62
|
- !ruby/object:Gem::Version
|
|
62
63
|
version: '0'
|
|
63
64
|
requirements: []
|
|
64
|
-
rubygems_version:
|
|
65
|
+
rubygems_version: 3.6.9
|
|
65
66
|
specification_version: 4
|
|
66
67
|
summary: Tools to handle collections of weakly-referenced values
|
|
67
68
|
test_files: []
|