weak 0.2.0 → 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 +26 -1
- data/LICENSE.txt +1 -1
- data/README.md +87 -15
- data/lib/weak/cache.rb +181 -0
- data/lib/weak/map/abstract_strong_keys.rb +4 -4
- data/lib/weak/map/strong_keys.rb +2 -1
- data/lib/weak/map/strong_secondary_keys.rb +3 -2
- data/lib/weak/map/weak_keys.rb +4 -2
- data/lib/weak/map/weak_keys_with_delete.rb +4 -4
- data/lib/weak/map.rb +42 -28
- 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/undefined.rb +35 -0
- data/lib/weak/version.rb +32 -4
- data/lib/weak.rb +1 -25
- metadata +6 -4
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,6 +2,32 @@
|
|
|
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
|
+
|
|
21
|
+
## [0.2.1] - 2025-12-27
|
|
22
|
+
|
|
23
|
+
- Fix typos in code documentation
|
|
24
|
+
- Ignore some unnecessary methods defined on some `Weak::Set` implementations in `set_spec`
|
|
25
|
+
- Run specs on JRuby 10 in Github Actions
|
|
26
|
+
- Retry TruffleRuby rspec runs on Github Actions to avoid random failures due to flakey GC.
|
|
27
|
+
- Extract `UNDEFINED` to its own file and require it where used.
|
|
28
|
+
- Add more details about the gem version in `Weak::Version`
|
|
29
|
+
- Handle object cycles in `pretty_print`.
|
|
30
|
+
|
|
5
31
|
## [0.2.0] - 2025-04-09
|
|
6
32
|
|
|
7
33
|
- Add `Weak::Map#delete_if`
|
|
@@ -15,7 +41,6 @@
|
|
|
15
41
|
|
|
16
42
|
- Initial version of `Weak::Set` to store an unordered collection of objects.
|
|
17
43
|
- Initial version of `Weak::Map` to store key-value pairs of objects.
|
|
18
|
-
|
|
19
44
|
- Support for Ruby 3.0 using the following impementations
|
|
20
45
|
- Ruby (aka. MRI, aka. YARV) >= 3.0
|
|
21
46
|
- JRuby >= 9.4
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
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)
|
|
4
9
|
[](https://github.com/meineerde/weak/actions/workflows/ci.yml)
|
|
5
10
|
[](https://coveralls.io/github/meineerde/weak?branch=main)
|
|
6
11
|
|
|
@@ -12,7 +17,10 @@ We provide multiple classes which behave similar to their standard-library count
|
|
|
12
17
|
|
|
13
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.
|
|
14
19
|
|
|
15
|
-
|
|
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:
|
|
16
24
|
|
|
17
25
|
- All element references are weak, allowing each element to be garbage collected unless there is a strong reference to it somwhere else.
|
|
18
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.
|
|
@@ -24,13 +32,13 @@ require "weak/set"
|
|
|
24
32
|
set = Weak::Set.new
|
|
25
33
|
|
|
26
34
|
set << "some string"
|
|
27
|
-
# =>
|
|
35
|
+
# => Weak::Set["some string"]
|
|
28
36
|
|
|
29
37
|
# Do some work, wait a bit, or force a garbage collection run
|
|
30
38
|
3.times { GC.start }
|
|
31
39
|
|
|
32
40
|
set
|
|
33
|
-
# =>
|
|
41
|
+
# => Weak::Set[]
|
|
34
42
|
```
|
|
35
43
|
|
|
36
44
|
## Weak::Map
|
|
@@ -38,11 +46,14 @@ set
|
|
|
38
46
|
`Weak::Map` behaves similar to a `Hash` or an `ObjectSpace::WeakMap` in Ruby (aka. MRI, aka. YARV).
|
|
39
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`.
|
|
40
48
|
|
|
41
|
-
|
|
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:
|
|
42
53
|
|
|
43
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.
|
|
44
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.
|
|
45
|
-
- Map membership is governed by object identity of the key rather than by using
|
|
56
|
+
- Map membership is governed by object identity of the key rather than by using its `hash` and `eql?` methods. A `Weak::Map` thus works similar to a `Hash` marked as [compare_by_identity](https://docs.ruby-lang.org/en/3.4/Hash.html#method-i-compare_by_identity).
|
|
46
57
|
- You can freely change both keys and values added to the `Weak::Map`.
|
|
47
58
|
|
|
48
59
|
```ruby
|
|
@@ -59,15 +70,35 @@ map
|
|
|
59
70
|
# => #<Weak::Map {}>
|
|
60
71
|
```
|
|
61
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
|
+
|
|
62
96
|
## Usage
|
|
63
97
|
|
|
64
98
|
Please refer to the documentation at:
|
|
65
99
|
|
|
66
100
|
- [📘 Documentation](https://www.rubydoc.info/gems/weak)
|
|
67
|
-
- [💥 Development Documentation](https://www.rubydoc.info/github/meineerde/weak
|
|
68
|
-
|
|
69
|
-
> [!WARNING]
|
|
70
|
-
> 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.
|
|
101
|
+
- [💥 Development Documentation](https://www.rubydoc.info/github/meineerde/weak) of the [main branch](https://github.com/meineerde/weak/tree/main)
|
|
71
102
|
|
|
72
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.
|
|
73
104
|
|
|
@@ -158,7 +189,7 @@ During `checkout` we remember a reference to the returned connection object in t
|
|
|
158
189
|
|
|
159
190
|
If the caller just "forgets" the connection, our pool will also forget it during the next Ruby garbage collection run.
|
|
160
191
|
|
|
161
|
-
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`.
|
|
162
193
|
|
|
163
194
|
### Weak::Map Example
|
|
164
195
|
|
|
@@ -169,6 +200,8 @@ Even if a single object is wrapped in multiple `LockedObject` instances, we stil
|
|
|
169
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.
|
|
170
201
|
|
|
171
202
|
```ruby
|
|
203
|
+
require "weak/map"
|
|
204
|
+
|
|
172
205
|
class LockedObject < BasicObject
|
|
173
206
|
LOCKS = Weak::Map.new
|
|
174
207
|
LOCKS_MUTEX = Mutex.new
|
|
@@ -211,21 +244,60 @@ string = "foo"
|
|
|
211
244
|
end
|
|
212
245
|
```
|
|
213
246
|
|
|
247
|
+
### Weak::Cache Example
|
|
248
|
+
|
|
249
|
+
We can simplify the above example by using `Weak::Cache`.
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
require "weak/cache"
|
|
253
|
+
|
|
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
|
|
261
|
+
|
|
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
|
+
|
|
214
282
|
## Development
|
|
215
283
|
|
|
216
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.
|
|
217
285
|
|
|
218
|
-
[
|
|
219
|
-
|
|
220
|
-
We follow the Standard Ruby style. 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.
|
|
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.
|
|
221
287
|
|
|
222
288
|
## Contributing
|
|
223
289
|
|
|
224
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).
|
|
225
291
|
|
|
226
|
-
## 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.
|
|
227
299
|
|
|
228
|
-
|
|
300
|
+
(Thanks to [Cassandra Granade](https://codeberg.org/cgranade/do#license-and-ai-policy) for the inspiration.)
|
|
229
301
|
|
|
230
302
|
## Code of Conduct
|
|
231
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
|
|
@@ -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 "deletable"
|
|
9
9
|
|
|
10
10
|
##
|
|
11
11
|
module Weak
|
|
@@ -34,9 +34,9 @@ module Weak
|
|
|
34
34
|
private
|
|
35
35
|
|
|
36
36
|
# This method is called during {#_get}. It generally needs to be
|
|
37
|
-
# overwritten in a "sub"-
|
|
38
|
-
# The implemented `auto_prune` method should quickly check if a
|
|
39
|
-
# necessary and then either call the `prune` method or return.
|
|
37
|
+
# overwritten in a "sub"-module to automatically cleanup any internal
|
|
38
|
+
# data. The implemented `auto_prune` method should quickly check if a
|
|
39
|
+
# prune is necessary and then either call the `prune` method or return.
|
|
40
40
|
#
|
|
41
41
|
# @return [void]
|
|
42
42
|
def auto_prune
|
data/lib/weak/map/strong_keys.rb
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
require "set"
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
require_relative "abstract_strong_keys"
|
|
11
|
+
require_relative "../undefined"
|
|
11
12
|
|
|
12
13
|
##
|
|
13
14
|
module Weak
|
|
@@ -53,7 +54,7 @@ module Weak
|
|
|
53
54
|
|
|
54
55
|
# Checks if this strategy is usable for the current Ruby version.
|
|
55
56
|
#
|
|
56
|
-
# @return [Bool] always `true` to indicate that this
|
|
57
|
+
# @return [Bool] always `true` to indicate that this strategy should be
|
|
57
58
|
# usable with any Ruby implementation which provides an
|
|
58
59
|
# `ObjectSpace::WeakMap`.
|
|
59
60
|
def self.usable?
|
data/lib/weak/map/weak_keys.rb
CHANGED
|
@@ -5,7 +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
|
-
|
|
8
|
+
require_relative "deletable"
|
|
9
|
+
require_relative "../undefined"
|
|
9
10
|
|
|
10
11
|
##
|
|
11
12
|
module Weak
|
|
@@ -65,7 +66,8 @@ module Weak
|
|
|
65
66
|
return enum_for(__method__) { size } unless block_given?
|
|
66
67
|
|
|
67
68
|
@map.keys.each do |key|
|
|
68
|
-
|
|
69
|
+
raw_value = @map[key]
|
|
70
|
+
yield key unless missing?(raw_value)
|
|
69
71
|
end
|
|
70
72
|
self
|
|
71
73
|
end
|
|
@@ -5,6 +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
|
+
require_relative "../undefined"
|
|
9
|
+
|
|
8
10
|
##
|
|
9
11
|
module Weak
|
|
10
12
|
class Map
|
|
@@ -19,16 +21,14 @@ module Weak
|
|
|
19
21
|
# either of them vanishes, the entry is removed.
|
|
20
22
|
#
|
|
21
23
|
# 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.
|
|
24
|
+
# to directly use the `ObjectSpace::WeakMap` as a storage object.
|
|
24
25
|
module WeakKeysWithDelete
|
|
25
26
|
# Checks if this strategy is usable for the current Ruby version.
|
|
26
27
|
#
|
|
27
28
|
# @return [Bool] truethy for Ruby (aka. MRI, aka. YARV) >= 3.3.0,
|
|
28
29
|
# falsey otherwise
|
|
29
30
|
def self.usable?
|
|
30
|
-
RUBY_ENGINE == "ruby" &&
|
|
31
|
-
ObjectSpace::WeakMap.instance_methods.include?(:delete)
|
|
31
|
+
RUBY_ENGINE == "ruby" && ObjectSpace::WeakMap.method_defined?(:delete)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# @!macro weak_map_accessor_read
|
data/lib/weak/map.rb
CHANGED
|
@@ -9,6 +9,7 @@ require_relative "map/weak_keys_with_delete"
|
|
|
9
9
|
require_relative "map/weak_keys"
|
|
10
10
|
require_relative "map/strong_keys"
|
|
11
11
|
require_relative "map/strong_secondary_keys"
|
|
12
|
+
require_relative "undefined"
|
|
12
13
|
|
|
13
14
|
##
|
|
14
15
|
module Weak
|
|
@@ -33,7 +34,8 @@ module Weak
|
|
|
33
34
|
#
|
|
34
35
|
# Note that {Weak::Map} is not inherently thread-safe. When accessing a
|
|
35
36
|
# {Weak::Map} from multiple threads or fibers, you MUST use a mutex or another
|
|
36
|
-
# locking mechanism.
|
|
37
|
+
# locking mechanism. You can also use {Weak::Cache} as a thread-safe
|
|
38
|
+
# alternative.
|
|
37
39
|
#
|
|
38
40
|
# ## Implementation Details
|
|
39
41
|
#
|
|
@@ -95,7 +97,7 @@ module Weak
|
|
|
95
97
|
# Here follows the documentation of strategy-specific methods which are
|
|
96
98
|
# implemented in one of the include modules depending on the current Ruby.
|
|
97
99
|
|
|
98
|
-
# @!macro
|
|
100
|
+
# @!macro weak_map_note_object_equality
|
|
99
101
|
# @note {Weak::Map} does not test member equality with `==` or `eql?`.
|
|
100
102
|
# Instead, it always checks strict object equality, so that, e.g.,
|
|
101
103
|
# different String keys are not considered equal, even if they may
|
|
@@ -107,7 +109,7 @@ module Weak
|
|
|
107
109
|
# `key` is not found, returns the default value, i.e. the value returned
|
|
108
110
|
# by the default proc (if defined) or the `default` value (which is
|
|
109
111
|
# initially `nil`.)
|
|
110
|
-
# @!macro
|
|
112
|
+
# @!macro weak_map_note_object_equality
|
|
111
113
|
|
|
112
114
|
# @!macro weak_map_accessor_write
|
|
113
115
|
# Associates the given `value` with the given `key`; returns `value`. If
|
|
@@ -116,7 +118,7 @@ module Weak
|
|
|
116
118
|
# @param key [Object] the key for the set key-value pair
|
|
117
119
|
# @param value [Object] the value of the set key-value pair
|
|
118
120
|
# @return [Object] the given `value`
|
|
119
|
-
# @!macro
|
|
121
|
+
# @!macro weak_map_note_object_equality
|
|
120
122
|
|
|
121
123
|
# @!macro weak_map_method_clear
|
|
122
124
|
# Removes all elements and returns `self`
|
|
@@ -135,7 +137,7 @@ module Weak
|
|
|
135
137
|
# if the key was not found and no block was given.
|
|
136
138
|
# @yield [key]
|
|
137
139
|
# @yieldparam key [Object] the given `key` if it was not part of the map
|
|
138
|
-
# @!macro
|
|
140
|
+
# @!macro weak_map_note_object_equality
|
|
139
141
|
|
|
140
142
|
# @!macro weak_map_method_each_pair
|
|
141
143
|
# Calls the given block once for each live key in `self`, passing the key
|
|
@@ -190,13 +192,13 @@ module Weak
|
|
|
190
192
|
# the given block.
|
|
191
193
|
# @raise [KeyError] if the key can not be found and no block or `default`
|
|
192
194
|
# value was provided
|
|
193
|
-
# @!macro
|
|
195
|
+
# @!macro weak_map_note_object_equality
|
|
194
196
|
|
|
195
197
|
# @!macro weak_map_method_include_question
|
|
196
198
|
# @param key [Object] a possible key
|
|
197
199
|
# @return [Bool] `true` if the given key is included in `self` and has an
|
|
198
200
|
# associated live value, `false` otherwise
|
|
199
|
-
# @!macro
|
|
201
|
+
# @!macro weak_map_note_object_equality
|
|
200
202
|
|
|
201
203
|
# @!macro weak_map_method_keys
|
|
202
204
|
# @return [Array] an `Array` containing all keys of the map for which we
|
|
@@ -287,7 +289,7 @@ module Weak
|
|
|
287
289
|
# The initial default value and initial default proc for the new hash depend
|
|
288
290
|
# on which form above was used.
|
|
289
291
|
#
|
|
290
|
-
# If neither
|
|
292
|
+
# If neither a `default_value` nor a block is given, initializes both the
|
|
291
293
|
# default value and the default proc to nil:
|
|
292
294
|
#
|
|
293
295
|
# map = Weak::Map.new
|
|
@@ -331,7 +333,7 @@ module Weak
|
|
|
331
333
|
alias_method :key?, :include?
|
|
332
334
|
alias_method :member?, :include?
|
|
333
335
|
alias_method :length, :size
|
|
334
|
-
alias_method :store, :[]
|
|
336
|
+
alias_method :store, :[]=
|
|
335
337
|
|
|
336
338
|
# {Weak::Map} objects can't be frozen since this is not enforced by the
|
|
337
339
|
# underlying `ObjectSpace::WeakMap` implementation. Thus, we try to signal
|
|
@@ -340,7 +342,7 @@ module Weak
|
|
|
340
342
|
#
|
|
341
343
|
# @param freeze [Bool, nil] ignored; we always behave as if this is false.
|
|
342
344
|
# If this is set to a truethy value, we emit a warning.
|
|
343
|
-
# @return [Weak::
|
|
345
|
+
# @return [Weak::Map] a new `Weak::Map` object containing the same elements
|
|
344
346
|
# as `self`
|
|
345
347
|
def clone(freeze: false)
|
|
346
348
|
warn("Can't freeze #{self.class}") if freeze
|
|
@@ -398,7 +400,7 @@ module Weak
|
|
|
398
400
|
# Sets the default proc for self to `proc` and clears the {#default} value.
|
|
399
401
|
#
|
|
400
402
|
# @param proc [Proc, #to_proc nil] a `Proc` which can be called with two
|
|
401
|
-
# arguments: the map and the
|
|
403
|
+
# arguments: the map and the requested non-exiting key. The proc is
|
|
402
404
|
# expected to return the default value for the key. Whe giving `nil`, the
|
|
403
405
|
# default proc is cleared.
|
|
404
406
|
# @return [Proc, nil] the new default proc
|
|
@@ -456,7 +458,7 @@ module Weak
|
|
|
456
458
|
size == 0
|
|
457
459
|
end
|
|
458
460
|
|
|
459
|
-
# {Weak::
|
|
461
|
+
# {Weak::Map} objects can't be frozen since this is not enforced by the
|
|
460
462
|
# underlying `ObjectSpace::WeakMap` implementation. Thus, we try to signal
|
|
461
463
|
# this by not actually setting the `frozen?` flag and ignoring attempts to
|
|
462
464
|
# freeze us with just a warning.
|
|
@@ -470,7 +472,7 @@ module Weak
|
|
|
470
472
|
# @param value [Object] a value to check
|
|
471
473
|
# @return [Bool] `true` if `value` is a value in `self`, `false` otherwise
|
|
472
474
|
#
|
|
473
|
-
# @!macro
|
|
475
|
+
# @!macro weak_map_note_object_equality
|
|
474
476
|
def has_value?(value)
|
|
475
477
|
id = value.__id__
|
|
476
478
|
each_value.any? { |v| v.__id__ == id }
|
|
@@ -481,18 +483,25 @@ module Weak
|
|
|
481
483
|
# the weak set, e.g.,
|
|
482
484
|
# `"#<Weak::Map {key1 => value1, key2 => value2, ...}>"`
|
|
483
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
|
|
484
495
|
object_ids = (Thread.current[INSPECT_KEY] ||= [])
|
|
485
|
-
return "
|
|
496
|
+
return "{...}" if object_ids.include?(__id__)
|
|
486
497
|
|
|
487
|
-
object_ids <<
|
|
498
|
+
object_ids << __id__
|
|
488
499
|
begin
|
|
489
|
-
|
|
490
|
-
"#<#{self.class} {#{elements}}>"
|
|
500
|
+
to_a.sort_by! { |k, _v| k.__id__ }.to_h.inspect
|
|
491
501
|
ensure
|
|
492
502
|
object_ids.pop
|
|
493
503
|
end
|
|
494
504
|
end
|
|
495
|
-
alias_method :to_s, :inspect
|
|
496
505
|
|
|
497
506
|
# Deletes every key-value pair from `self` for which the given block
|
|
498
507
|
# evaluates to a falsey value.
|
|
@@ -536,7 +545,7 @@ module Weak
|
|
|
536
545
|
# h1 = {baz: 3, bar: 4}
|
|
537
546
|
# h2 = {bam: 5, baz: 6}
|
|
538
547
|
# map.merge(h1, h2)
|
|
539
|
-
# # => #<Weak::Map {:foo=>0, :bar=>4, :baz=>6, :bam=>5}
|
|
548
|
+
# # => #<Weak::Map {:foo=>0, :bar=>4, :baz=>6, :bam=>5}>
|
|
540
549
|
#
|
|
541
550
|
# With arguments and a block:
|
|
542
551
|
#
|
|
@@ -558,7 +567,7 @@ module Weak
|
|
|
558
567
|
# h1 = {baz: 3, bar: 4}
|
|
559
568
|
# h2 = {bam: 5, baz: 6}
|
|
560
569
|
# map.merge(h1, h2) { |key, old_value, new_value| old_value + new_value }
|
|
561
|
-
# # => #<Weak::Map {:foo=>0, :bar=>5, :baz=>9, :bam=>5}
|
|
570
|
+
# # => #<Weak::Map {:foo=>0, :bar=>5, :baz=>9, :bam=>5}>
|
|
562
571
|
#
|
|
563
572
|
# With no arguments:
|
|
564
573
|
#
|
|
@@ -577,7 +586,7 @@ module Weak
|
|
|
577
586
|
# `other_maps`
|
|
578
587
|
# @return [Weak::Map] a new weak map containing the merged pairs
|
|
579
588
|
#
|
|
580
|
-
# @!macro
|
|
589
|
+
# @!macro weak_map_note_object_equality
|
|
581
590
|
def merge(*other_maps, &block)
|
|
582
591
|
dup.merge!(*other_maps, &block)
|
|
583
592
|
end
|
|
@@ -590,6 +599,11 @@ module Weak
|
|
|
590
599
|
end
|
|
591
600
|
end
|
|
592
601
|
|
|
602
|
+
# @!visibility private
|
|
603
|
+
def pretty_print_cycle(pp)
|
|
604
|
+
pp.text "#<#{self.class} {#{"..." unless empty?}}>"
|
|
605
|
+
end
|
|
606
|
+
|
|
593
607
|
# Deletes every key-value pair from `self` for which the given block
|
|
594
608
|
# evaluates to a truethy value.
|
|
595
609
|
#
|
|
@@ -600,9 +614,9 @@ module Weak
|
|
|
600
614
|
# @yield [key, value] calls the given block once for each key in the map
|
|
601
615
|
# @yieldparam key [Object] a key
|
|
602
616
|
# @return [Enumerator, self, nil] `self` if a block was given and some
|
|
603
|
-
#
|
|
617
|
+
# element(s) were deleted, `nil` if a block was given but no keys were
|
|
604
618
|
# deleted, or an `Enumerator` if no block was given.
|
|
605
|
-
#
|
|
619
|
+
# @see #delete_if
|
|
606
620
|
def reject!(&block)
|
|
607
621
|
return enum_for(__method__) { size } unless block_given?
|
|
608
622
|
|
|
@@ -644,8 +658,8 @@ module Weak
|
|
|
644
658
|
# @yieldparam key [Object] a key
|
|
645
659
|
# @yieldparam value [Object] the corresponding value
|
|
646
660
|
# @return [Enumerator, self, nil] `self` if a block was given and some
|
|
647
|
-
#
|
|
648
|
-
#
|
|
661
|
+
# element(s) were deleted, `nil` if a block was given but nothing was
|
|
662
|
+
# deleted, or an `Enumerator` if no block was given.
|
|
649
663
|
# @see keep_if
|
|
650
664
|
def select!(&block)
|
|
651
665
|
return enum_for(__method__) { size } unless block_given?
|
|
@@ -682,7 +696,7 @@ module Weak
|
|
|
682
696
|
# h1 = {baz: 3, bar: 4}
|
|
683
697
|
# h2 = {bam: 5, baz: 6}
|
|
684
698
|
# map.update(h1, h2)
|
|
685
|
-
# # => #<Weak::Map {:foo=>0, :bar=>4, :baz=>6, :bam=>5}
|
|
699
|
+
# # => #<Weak::Map {:foo=>0, :bar=>4, :baz=>6, :bam=>5}>
|
|
686
700
|
#
|
|
687
701
|
# With arguments and a block:
|
|
688
702
|
#
|
|
@@ -704,7 +718,7 @@ module Weak
|
|
|
704
718
|
# h1 = {baz: 3, bar: 4}
|
|
705
719
|
# h2 = {bam: 5, baz: 6}
|
|
706
720
|
# map.update(h1, h2) { |key, old_value, new_value| old_value + new_value }
|
|
707
|
-
# # => #<Weak::Map {:foo=>0, :bar=>5, :baz=>9, :bam=>5}
|
|
721
|
+
# # => #<Weak::Map {:foo=>0, :bar=>5, :baz=>9, :bam=>5}>
|
|
708
722
|
#
|
|
709
723
|
# With no arguments:
|
|
710
724
|
#
|
|
@@ -723,7 +737,7 @@ module Weak
|
|
|
723
737
|
# `other_maps`
|
|
724
738
|
# @return [self]
|
|
725
739
|
#
|
|
726
|
-
# @!macro
|
|
740
|
+
# @!macro weak_map_note_object_equality
|
|
727
741
|
def update(*other_maps)
|
|
728
742
|
if block_given?
|
|
729
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
|
|
@@ -0,0 +1,35 @@
|
|
|
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 "singleton"
|
|
9
|
+
|
|
10
|
+
##
|
|
11
|
+
module Weak
|
|
12
|
+
# A class for the {UNDEFINED} object. Being a singleton, there will only be
|
|
13
|
+
# exactly one object of this class: the {UNDEFINED} object.
|
|
14
|
+
#
|
|
15
|
+
# @private
|
|
16
|
+
class UndefinedClass
|
|
17
|
+
include Singleton
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
freeze
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [String] the string `"UNDEFINED"`
|
|
24
|
+
def to_s
|
|
25
|
+
"UNDEFINED"
|
|
26
|
+
end
|
|
27
|
+
alias_method :inspect, :to_s
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The {UNDEFINED} object can be used as the default value for method arguments
|
|
31
|
+
# to distinguish it from `nil`.
|
|
32
|
+
#
|
|
33
|
+
# @see https://holgerjust.de/2016/detecting-default-arguments-in-ruby/#special-default-value
|
|
34
|
+
UNDEFINED = UndefinedClass.instance
|
|
35
|
+
end
|
data/lib/weak/version.rb
CHANGED
|
@@ -7,8 +7,36 @@
|
|
|
7
7
|
|
|
8
8
|
##
|
|
9
9
|
module Weak
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
# Version information about {Weak}. We follow semantic versioning.
|
|
11
|
+
module Version
|
|
12
|
+
# MAJOR version. It is incremented after incompatible API changes
|
|
13
|
+
MAJOR = 0
|
|
14
|
+
# MINOR version. It is incremented after adding functionality in a
|
|
15
|
+
# backwards-compatible manner
|
|
16
|
+
MINOR = 3
|
|
17
|
+
# PATCH version. It is incremented when making backwards-compatible
|
|
18
|
+
# bug-fixes.
|
|
19
|
+
PATCH = 0
|
|
20
|
+
# PRERELEASE suffix. Set to a alphanumeric string on any pre-release
|
|
21
|
+
# versions like beta or RC releases; `nil` on regular releases
|
|
22
|
+
PRERELEASE = nil
|
|
23
|
+
|
|
24
|
+
# The {Weak} version as a `Gem::Version` string. We follow semantic
|
|
25
|
+
# versioning.
|
|
26
|
+
# @see https://semver.org/
|
|
27
|
+
STRING = [MAJOR, MINOR, PATCH, PRERELEASE].compact.join(".").freeze
|
|
28
|
+
|
|
29
|
+
# @return [Gem::Version] the {Weak} version as a `Gem::Version` object
|
|
30
|
+
def self.gem_version
|
|
31
|
+
Gem::Version.new STRING
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @return [String] the Weak version as a `Gem::Version` string
|
|
35
|
+
def self.to_s
|
|
36
|
+
STRING
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# (see Version::STRING)
|
|
41
|
+
VERSION = Weak::Version::STRING
|
|
14
42
|
end
|
data/lib/weak.rb
CHANGED
|
@@ -5,11 +5,10 @@
|
|
|
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
|
-
require "singleton"
|
|
9
|
-
|
|
10
8
|
require_relative "weak/version"
|
|
11
9
|
require_relative "weak/map"
|
|
12
10
|
require_relative "weak/set"
|
|
11
|
+
require_relative "weak/cache"
|
|
13
12
|
|
|
14
13
|
# Weak is a Ruby library which implements collections of unordered values
|
|
15
14
|
# without strong object references.
|
|
@@ -19,27 +18,4 @@ require_relative "weak/set"
|
|
|
19
18
|
# elements can be garbage collected and silently removed from the collection
|
|
20
19
|
# unless they are still referenced from some other live object.
|
|
21
20
|
module Weak
|
|
22
|
-
# A class for the {UNDEFINED} object. Being a singleton, there will only be
|
|
23
|
-
# exactly one object of this class: the {UNDEFINED} object.
|
|
24
|
-
#
|
|
25
|
-
# @private
|
|
26
|
-
class UndefinedClass
|
|
27
|
-
include Singleton
|
|
28
|
-
|
|
29
|
-
def initialize
|
|
30
|
-
freeze
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# @return [String] the string `"UNDEFINED"`
|
|
34
|
-
def to_s
|
|
35
|
-
"UNDEFINED"
|
|
36
|
-
end
|
|
37
|
-
alias_method :inspect, :to_s
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# The {UNDEFINED} object can be used as the default value for method arguments
|
|
41
|
-
# to distinguish it from `nil`.
|
|
42
|
-
#
|
|
43
|
-
# @see https://holgerjust.de/2016/detecting-default-arguments-in-ruby/#special-default-value
|
|
44
|
-
UNDEFINED = UndefinedClass.instance
|
|
45
21
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
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
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
12
|
description: |
|
|
13
13
|
The Weak library provides a Weak::Set class to store an unordered list of
|
|
@@ -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
|
|
@@ -36,6 +37,7 @@ files:
|
|
|
36
37
|
- lib/weak/set/strong_secondary_keys.rb
|
|
37
38
|
- lib/weak/set/weak_keys.rb
|
|
38
39
|
- lib/weak/set/weak_keys_with_delete.rb
|
|
40
|
+
- lib/weak/undefined.rb
|
|
39
41
|
- lib/weak/version.rb
|
|
40
42
|
homepage: https://github.com/meineerde/weak
|
|
41
43
|
licenses:
|
|
@@ -45,7 +47,7 @@ metadata:
|
|
|
45
47
|
homepage_uri: https://github.com/meineerde/weak
|
|
46
48
|
source_code_uri: https://github.com/meineerde/weak
|
|
47
49
|
changelog_uri: https://github.com/meineerde/weak/blob/main/CHANGELOG.md
|
|
48
|
-
documentation_uri: https://www.rubydoc.info/gems/weak/0.
|
|
50
|
+
documentation_uri: https://www.rubydoc.info/gems/weak/0.3.0
|
|
49
51
|
rdoc_options: []
|
|
50
52
|
require_paths:
|
|
51
53
|
- lib
|
|
@@ -60,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
60
62
|
- !ruby/object:Gem::Version
|
|
61
63
|
version: '0'
|
|
62
64
|
requirements: []
|
|
63
|
-
rubygems_version: 3.6.
|
|
65
|
+
rubygems_version: 3.6.9
|
|
64
66
|
specification_version: 4
|
|
65
67
|
summary: Tools to handle collections of weakly-referenced values
|
|
66
68
|
test_files: []
|