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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86165785c0d1874c5c54d7a72c3b703f5a21832cf0df8ffa917b4793dc40c502
4
- data.tar.gz: a02c81a859c4f887181a2e33a124d6584a987d40cde92770fd1d6953a836f10d
3
+ metadata.gz: d99367de841cfb268442bccc306c3d43858cb6280a462cd513f21eb6c88ec366
4
+ data.tar.gz: da3360b4705d18a122a936a7a9dec0ab57839afc3dd846a24fc06dba46f645dd
5
5
  SHA512:
6
- metadata.gz: 60d9a3259f88cd37a001b60fdf5247a856d1c637a54d0085712b347ab9b4c0dda330e8cabe69ead1bdf8193fdb70d8a0f7b419ea407afdcbea0b8e659331473c
7
- data.tar.gz: b2dbc7c76ee9fd24d1a06df292e4683f5f5eb1a1b7a2d9db6ab653e691e818d3d6b3975589e020c47daede1b50ab48a4c417830af1c7b4cac12a9e2b61dc47f1
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
@@ -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,6 +1,11 @@
1
1
  # Weak
2
2
 
3
3
  [![gem version badge](https://badge.fury.io/rb/weak.svg)](https://rubygems.org/gems/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)
4
9
  [![CI status badge](https://github.com/meineerde/weak/actions/workflows/ci.yml/badge.svg)](https://github.com/meineerde/weak/actions/workflows/ci.yml)
5
10
  [![Coverage Status](https://coveralls.io/repos/github/meineerde/weak/badge.svg?branch=main)](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
- 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:
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
- # => #<Weak::Set {"some string"}>
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
- # => #<Weak::Set {}>
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
- Compared to the `Set` 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:
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 the `hash` and `eql?` methods of the elements. A `Weak::Map` thus works similat to a `Hash` marked as [compare_by_identity](https://docs.ruby-lang.org/en/3.4/Hash.html#method-i-compare_by_identity).
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/main) of the [main branch](https://github.com/meineerde/weak/tree/main)
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, 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`.
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
- [![code style: standard badge](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/standardrb/standard)
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
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
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
- require "weak/map/deletable"
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"-mdule to automatically cleanup any internal data.
38
- # The implemented `auto_prune` method should quickly check if a prune is
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
@@ -7,7 +7,8 @@
7
7
 
8
8
  require "set"
9
9
 
10
- require "weak/map/abstract_strong_keys"
10
+ require_relative "abstract_strong_keys"
11
+ require_relative "../undefined"
11
12
 
12
13
  ##
13
14
  module Weak
@@ -7,7 +7,8 @@
7
7
 
8
8
  require "set"
9
9
 
10
- require "weak/map/abstract_strong_keys"
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 stragegy should be
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?
@@ -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
- require "weak/map/deletable"
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
- yield key unless missing?(@map[key])
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 the same way a
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 _note_object_equality
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 _note_object_equality
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 _note_object_equality
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 _note_object_equality
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 _note_object_equality
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 _note_object_equality
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 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
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::Set] a new `Weak::Map` object containing the same elements
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 rquested non-exiting key. The proc is
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::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
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 _note_object_equality
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 "#<#{self.class} {...}>" if object_ids.include?(object_id)
496
+ return "{...}" if object_ids.include?(__id__)
486
497
 
487
- object_ids << object_id
498
+ object_ids << __id__
488
499
  begin
489
- elements = to_a.sort_by! { |k, _v| k.__id__ }.to_h.inspect[1..-2]
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 _note_object_equality
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
- # 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
604
618
  # deleted, or an `Enumerator` if no block was given.
605
- # #see #delete_if
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
- # element(s) were deleted, `nil` if a block was given but nothing was
648
- # 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.
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 _note_object_equality
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 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} {#{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
@@ -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
- # The {Weak} version as a `Gem::Version` string. We follow semantic
11
- # versioning.
12
- # @see https://semver.org/
13
- VERSION = "0.2.0"
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.2.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: 2025-04-09 00:00:00.000000000 Z
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.2.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.3
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: []