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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1495109d765259effd2efb1f7d97c7ad7e71e060ecb1d8f8f6cd6386e3a4d93c
4
- data.tar.gz: 73be12838b3bdb16f45215dd9b07152e798d62130ca0bdfbca999b57a21ae25d
3
+ metadata.gz: d99367de841cfb268442bccc306c3d43858cb6280a462cd513f21eb6c88ec366
4
+ data.tar.gz: da3360b4705d18a122a936a7a9dec0ab57839afc3dd846a24fc06dba46f645dd
5
5
  SHA512:
6
- metadata.gz: 35c2aa978606c8c1bd1c83838e9eb418eba8e4bada89936675fcb82c6a6a24304da7e34a56b38cf2bab42fd0b682efcf2577c2fecb4fd2c7fb02344869ab3cfd
7
- data.tar.gz: 47e6c245cfe310e9ad7b538028cac0e36531b3c856ac3ec6efbb66126ac5c51f26dbecc03a516eca9598f8c81f02de5e26a51c435cc2f40a86a7d39ff2253793
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
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2025 Holger Just
3
+ Copyright (c) 2025 - 2026 Holger Just
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,7 +1,13 @@
1
1
  # Weak
2
2
 
3
3
  [![gem version badge](https://badge.fury.io/rb/weak.svg)](https://rubygems.org/gems/weak)
4
- [![github repo badge](https://img.shields.io/badge/github-repo-blue?logo=github)](https://github.com/meineerde/weak)
4
+ [![license badge](https://img.shields.io/badge/license-MIT-brightgreen.svg)](#license)
5
+ [![github repo badge](https://img.shields.io/badge/github-meineerde/weak-blue.svg)](https://github.com/meineerde/weak)
6
+ [![documentation badge](https://img.shields.io/badge/docs-rubydoc.info-blue.svg)](https://www.rubydoc.info/gems/weak)
7
+
8
+ [![code style: standard badge](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/standardrb/standard)
9
+ [![CI status badge](https://github.com/meineerde/weak/actions/workflows/ci.yml/badge.svg)](https://github.com/meineerde/weak/actions/workflows/ci.yml)
10
+ [![Coverage Status](https://coveralls.io/repos/github/meineerde/weak/badge.svg?branch=main)](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
- Compared to the `Set` class however, there are a few differences:
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
- # => #<Weak::Set {"some string"}>
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
- # => #<Weak::Set {}>
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
- Compared to the `Hash` class however, there are a few differences:
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, the a checked-out connection can be checked-in again only once and only if it was initially created by the `ConnectionPool`.
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
- ## Development
247
+ ### Weak::Cache Example
214
248
 
215
- [![CI status badge](https://github.com/meineerde/weak/actions/workflows/ci.yml/badge.svg)](https://github.com/meineerde/weak/actions/workflows/ci.yml)
216
- [![Coverage Status](https://coveralls.io/repos/github/meineerde/weak/badge.svg?branch=main)](https://coveralls.io/github/meineerde/weak?branch=main)
249
+ We can simplify the above example by using `Weak::Cache`.
217
250
 
218
- 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.
251
+ ```ruby
252
+ require "weak/cache"
219
253
 
220
- [![code style: standard badge](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/standardrb/standard)
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
- 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.
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
- The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
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
@@ -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
- require "weak/map/deletable"
8
+ require_relative "deletable"
9
9
 
10
10
  ##
11
11
  module Weak
@@ -7,8 +7,8 @@
7
7
 
8
8
  require "set"
9
9
 
10
- require "weak/map/abstract_strong_keys"
11
- require "weak/undefined"
10
+ require_relative "abstract_strong_keys"
11
+ require_relative "../undefined"
12
12
 
13
13
  ##
14
14
  module Weak
@@ -7,8 +7,8 @@
7
7
 
8
8
  require "set"
9
9
 
10
- require "weak/map/abstract_strong_keys"
11
- require "weak/undefined"
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 stragegy should be
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?
@@ -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
- require "weak/map/deletable"
9
- require "weak/undefined"
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
- yield key unless missing?(@map[key])
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
- require "weak/undefined"
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 the same way a
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
- require "weak/map/weak_keys_with_delete"
9
- require "weak/map/weak_keys"
10
- require "weak/map/strong_keys"
11
- require "weak/map/strong_secondary_keys"
12
- require "weak/undefined"
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 _note_object_equality
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 _note_object_equality
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 _note_object_equality
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 _note_object_equality
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 _note_object_equality
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 _note_object_equality
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 an `default_value` nor a block is given, initializes both the
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::Set] a new `Weak::Map` object containing the same elements
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 rquested non-exiting key. The proc is
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::Set} objects can't be frozen since this is not enforced by the
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 _note_object_equality
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 "#<#{self.class} {...}>" if object_ids.include?(object_id)
496
+ return "{...}" if object_ids.include?(__id__)
487
497
 
488
- object_ids << object_id
498
+ object_ids << __id__
489
499
  begin
490
- elements = to_a.sort_by! { |k, _v| k.__id__ }.to_h
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 _note_object_equality
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
- # element(s) were deleted, `nil` if a block was given but no keys were
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
- # #see #delete_if
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
- # element(s) were deleted, `nil` if a block was given but nothing was
654
- # deleted, or an `Enumerator` if no block was given.
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 _note_object_equality
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 stragegy should be
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 the same way a
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] #=> #<Weak::Set {1, 2}>
75
- # s2 = Weak::Set.new [1, 2] #=> #<Weak::Set {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) #=> #<Weak::Set {1, 2, :foo}>
78
- # s1.merge([2, 6]) #=> #<Weak::Set {1, 2, :foo, 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 _note_object_equality
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) #=> #<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}>
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 _note_object_equality
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 _note_object_equality
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] #=> #<Weak::Set {1, :c, :s}>
168
- # set.replace([1, 2]) #=> #<Weak::Set {1, 2}>
169
- # set #=> #<Weak::Set {1, 2}>
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 ary [Array<Object>] a list of objects
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] # => #<Weak::Set {1, 2}>
220
- # Weak::Set[1, 2, 1] # => #<Weak::Set {1, 2}>
221
- # Weak::Set[1, :c, :s] # => #<Weak::Set {1, :c, :s}>
222
- def self.[](*ary)
223
- new(ary)
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 _note_object_equality
257
+ # @!macro weak_set_note_object_equality
257
258
  #
258
259
  # @example
259
- # Weak::Set[1, 2, 3] | Weak::Set[2, 4, 5] # => #<Weak::Set {1, 2, 3, 4, 5}>
260
- # Weak::Set[1, 3, :z] | (1..4) # => #<Weak::Set {1, 3, :z, 2, 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 _note_object_equality
275
+ # @!macro weak_set_note_object_equality
275
276
  #
276
277
  # @example
277
- # Weak::Set[1, 3, 5] - Weak::Set[1, 5] # => #<Weak::Set {3}>
278
- # Weak::Set['a', 'b', 'z'] - ['a', 'c'] # => #<Weak::Set {"b", "z"}>
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 _note_object_equality
288
+ # @!macro weak_set_note_object_equality
288
289
  #
289
290
  # @example
290
- # Weak::Set[1, 3, 5] & Weak::Set[3, 2, 1] # => #<Weak::Set {3, 1}>
291
- # Weak::Set[1, 2, 9] & [2, 1, 3] # => #<Weak::Set {1, 2}>
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 _note_object_equality
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 _note_object_equality
353
+ # @!macro weak_set_note_object_equality
353
354
  #
354
355
  # @example
355
- # Weak::Set[1, 2] ^ Set[2, 3] #=> #<Weak::Set {3, 1}>
356
- # Weak::Set[1, :b, :c] ^ [:b, :d] #=> #<Weak::Set {:d, 1, :c}>
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 _note_object_equality
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 _note_object_equality
383
+ # @!macro weak_set_note_object_equality
383
384
  #
384
385
  # @example
385
- # Weak::Set[1, 2].add?(3) #=> #<Weak::Set {1, 2, 3}>
386
- # Weak::Set[1, 2].add?([3, 4]) #=> #<Weak::Set {1, 2, [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 _note_object_equality
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 _note_object_equality
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., `"#<Weak::Set {element1, element2, ...}>"`
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 "#<#{self.class} {...}>" if object_ids.include?(object_id)
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
- "#<#{self.class} {#{elements}}>"
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 _note_object_equality
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, "#<#{self.class}", ">") do
553
- pp.breakable
554
- pp.group(1, "{", "}") do
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 "#<#{self.class} {#{"..." unless empty?}}>"
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 _note_object_equality
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
- # element(s) were deleted, `nil` if a block was given but no keys were
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
- # #see #delete_if
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
- # element(s) were deleted, `nil` if a block was given but nothing was
637
- # deleted, or an `Enumerator` if no block was given.
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 _note_object_equality
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}}. We follow semantic versioning.
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 = 2
16
+ MINOR = 3
17
17
  # PATCH version. It is incremented when making backwards-compatible
18
18
  # bug-fixes.
19
- PATCH = 1
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
@@ -8,6 +8,7 @@
8
8
  require_relative "weak/version"
9
9
  require_relative "weak/map"
10
10
  require_relative "weak/set"
11
+ require_relative "weak/cache"
11
12
 
12
13
  # Weak is a Ruby library which implements collections of unordered values
13
14
  # without strong object references.
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.2.1
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.2.1
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: 4.0.3
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: []