philiprehberger-queue_stack 0.5.0 → 0.7.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: dba395bc3ee76baae4cdbbbb100fc91103049635c386e02fe5ac3ae118e9997a
4
- data.tar.gz: 921a8e4241383d570d951e154b19e7abcce5cdb682d93b42888d36c9804871a2
3
+ metadata.gz: dbcea69e9c62dd59232e450824070497d480a78224b1f04894664832e1e5c019
4
+ data.tar.gz: b9c91c0ff26af13c51ce30667c5eee0ad0bedce13c807164ddcd273e147d2513
5
5
  SHA512:
6
- metadata.gz: 30320f35b2ae5d2e01221091e203c31cc799ec5d6a1ff279a0201a04dd9c038f254ad8319d1e4f8cf3a50e1e292e2522a813e8330383f2f49ce99b86d5704b1c
7
- data.tar.gz: 2ddd79499fa0b97b99fcb45c5c941984c1f8b3ddeee25cc4edeed317b9317509d9ade981940ca685266770db6dc3d472b11e3fdb8610b45731abd44f7f3a284e
6
+ metadata.gz: 8db38faf1a4c7090ee405fededf09f3f939fd11217d46d0220641ff12b3ef286ca915c845b98847e3c22e7f4e0a4ab55d5c71a14d18db9af7b99859e24b454ff
7
+ data.tar.gz: 748285864c78717094e6f34a8c0cf9b4fc2e9c7bde05c17d14b8bd60821790bca8687b9076e2f4d24b9f5bb3cab3a23c131e1dd87cd64f7a4b6da1b30985df38
data/CHANGELOG.md CHANGED
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.0] - 2026-05-29
11
+
12
+ ### Added
13
+ - `Queue#enqueue_all(items)` and `Stack#push_all(items)` — enqueue/push an array of items under a single mutex acquisition; each item respects capacity and `ClosedError` semantics
14
+ - `Queue#dequeue_batch(max)` and `Stack#pop_batch(max)` — remove and return up to `max` items in one synchronized step; non-blocking; signals waiting producers
15
+
16
+ ## [0.6.0] - 2026-04-27
17
+
18
+ ### Added
19
+ - `Queue#capacity`, `Queue#remaining_capacity`, `Stack#capacity`, `Stack#remaining_capacity` — capacity introspection. `capacity` returns the configured limit (or `nil` for unlimited); `remaining_capacity` returns the number of additional items that can still be accepted (or `nil` for unlimited, `0` when full). Mutex-synchronized for consistent reads.
20
+
10
21
  ## [0.5.0] - 2026-04-16
11
22
 
12
23
  ### Added
data/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
  [![Gem Version](https://badge.fury.io/rb/philiprehberger-queue_stack.svg)](https://rubygems.org/gems/philiprehberger-queue_stack)
5
5
  [![Last updated](https://img.shields.io/github/last-commit/philiprehberger/rb-queue-stack)](https://github.com/philiprehberger/rb-queue-stack/commits/main)
6
6
 
7
+ ![philiprehberger-queue_stack](https://raw.githubusercontent.com/philiprehberger/rb-queue-stack/main/package-card.webp)
8
+
7
9
  Thread-safe Queue and Stack with capacity limits and blocking operations
8
10
 
9
11
  ## Requirements
@@ -159,6 +161,37 @@ q.full? # => true
159
161
  # enqueue blocks until space is available
160
162
  ```
161
163
 
164
+ ### Capacity
165
+
166
+ Read the configured capacity and the number of additional items that can be
167
+ accepted. Both return `nil` for unbounded containers; `remaining_capacity`
168
+ returns `0` when full. Useful for sizing batches or backpressure decisions.
169
+
170
+ ```ruby
171
+ q = Philiprehberger::QueueStack::Queue.new(capacity: 100)
172
+ q.capacity # => 100
173
+ q.remaining_capacity # => 100
174
+ 50.times { |i| q.enqueue(i) }
175
+ q.remaining_capacity # => 50
176
+
177
+ batch_size = [items.length, q.remaining_capacity].min
178
+ ```
179
+
180
+ ### Batch Insertion and Draining
181
+
182
+ `enqueue_all` / `push_all` insert an array of items under a single mutex acquisition. `dequeue_batch` / `pop_batch` remove up to `max` items at once and signal waiting producers. All four respect capacity and `ClosedError` semantics.
183
+
184
+ ```ruby
185
+ q = Philiprehberger::QueueStack::Queue.new
186
+ q.enqueue_all(%w[a b c d])
187
+ q.dequeue_batch(2) # => ["a", "b"]
188
+ q.dequeue_batch(99) # => ["c", "d"] (clamped to available)
189
+
190
+ s = Philiprehberger::QueueStack::Stack.new
191
+ s.push_all(%w[a b c])
192
+ s.pop_batch(2) # => ["c", "b"] (LIFO: top first)
193
+ ```
194
+
162
195
  ## API
163
196
 
164
197
  ### `Queue`
@@ -167,8 +200,10 @@ q.full? # => true
167
200
  |--------|-------------|
168
201
  | `.new(capacity:)` | Create a queue with optional capacity limit |
169
202
  | `#enqueue(item)` | Add item to back (blocks if full) |
203
+ | `#enqueue_all(items)` | Enqueue an array of items in FIFO order (blocks per-element if full) |
170
204
  | `#try_enqueue(item, timeout: nil)` | Non-blocking enqueue, returns true/false (waits up to timeout if given) |
171
205
  | `#dequeue` | Remove and return front item (blocks if empty) |
206
+ | `#dequeue_batch(max)` | Remove and return up to `max` items in FIFO order (non-blocking) |
172
207
  | `#dequeue_if { \|item\| ... }` | Remove and return the front item only if the block is truthy (non-blocking) |
173
208
  | `#try_dequeue(timeout:)` | Dequeue with timeout, returns nil on timeout |
174
209
  | `#clear` | Remove all items without returning them |
@@ -182,6 +217,8 @@ q.full? # => true
182
217
  | `#size` | Number of items |
183
218
  | `#empty?` | Whether the queue is empty |
184
219
  | `#full?` | Whether the queue is at capacity |
220
+ | `#capacity` | Configured capacity, or `nil` for an unlimited queue |
221
+ | `#remaining_capacity` | Items the queue can still accept (`nil` for unlimited, `0` when full) |
185
222
 
186
223
  ### `Stack`
187
224
 
@@ -189,8 +226,10 @@ q.full? # => true
189
226
  |--------|-------------|
190
227
  | `.new(capacity:)` | Create a stack with optional capacity limit |
191
228
  | `#push(item)` | Push item on top (blocks if full) |
229
+ | `#push_all(items)` | Push an array of items in order; last becomes the top (blocks per-element if full) |
192
230
  | `#try_push(item, timeout: nil)` | Non-blocking push, returns true/false (waits up to timeout if given) |
193
231
  | `#pop` | Remove and return top item (blocks if empty) |
232
+ | `#pop_batch(max)` | Pop up to `max` items, top first (non-blocking) |
194
233
  | `#pop_if { \|item\| ... }` | Remove and return the top item only if the block is truthy (non-blocking) |
195
234
  | `#try_pop(timeout:)` | Pop with timeout, returns nil on timeout |
196
235
  | `#clear` | Remove all items without returning them |
@@ -203,6 +242,8 @@ q.full? # => true
203
242
  | `#size` | Number of items |
204
243
  | `#empty?` | Whether the stack is empty |
205
244
  | `#full?` | Whether the stack is at capacity |
245
+ | `#capacity` | Configured capacity, or `nil` for an unlimited stack |
246
+ | `#remaining_capacity` | Items the stack can still accept (`nil` for unlimited, `0` when full) |
206
247
 
207
248
  ## Development
208
249
 
@@ -225,6 +225,68 @@ module Philiprehberger
225
225
  def full?
226
226
  @mutex.synchronize { @capacity ? @items.length >= @capacity : false }
227
227
  end
228
+
229
+ # The configured capacity, or +nil+ for an unlimited queue.
230
+ #
231
+ # @return [Integer, nil]
232
+ def capacity
233
+ @mutex.synchronize { @capacity }
234
+ end
235
+
236
+ # Number of additional items the queue can accept before it is full.
237
+ #
238
+ # Returns +nil+ for unlimited queues. For bounded queues returns
239
+ # +capacity - size+, clamped to a minimum of 0.
240
+ #
241
+ # @return [Integer, nil]
242
+ def remaining_capacity
243
+ @mutex.synchronize do
244
+ next nil unless @capacity
245
+
246
+ [@capacity - @items.length, 0].max
247
+ end
248
+ end
249
+
250
+ # Enqueue many items in FIFO order. Each item is enqueued under the
251
+ # same Mutex acquisition; the call blocks while waiting for capacity
252
+ # exactly as +enqueue+ would for the individual elements that cannot
253
+ # fit immediately.
254
+ #
255
+ # @param items [Array] the items to enqueue, in order
256
+ # @return [void]
257
+ # @raise [ClosedError] if the queue has been closed
258
+ def enqueue_all(items)
259
+ @mutex.synchronize do
260
+ raise ClosedError, 'cannot enqueue on a closed queue' if @closed
261
+
262
+ items.each do |item|
263
+ @not_full.wait(@mutex) while @capacity && @items.length >= @capacity
264
+ raise ClosedError, 'cannot enqueue on a closed queue' if @closed
265
+
266
+ @items.push(item)
267
+ @not_empty.signal
268
+ end
269
+ end
270
+ end
271
+
272
+ # Remove and return up to +max+ items from the front of the queue.
273
+ # Non-blocking: returns an empty array if the queue is empty.
274
+ #
275
+ # @param max [Integer] maximum number of items to remove (must be a non-negative Integer)
276
+ # @return [Array] up to +max+ items in FIFO order
277
+ # @raise [ArgumentError] if +max+ is not a non-negative Integer
278
+ def dequeue_batch(max)
279
+ raise ArgumentError, 'max must be a non-negative Integer' unless max.is_a?(Integer) && max >= 0
280
+
281
+ @mutex.synchronize do
282
+ count = [max, @items.length].min
283
+ next [] if count.zero?
284
+
285
+ batch = @items.shift(count)
286
+ @not_full.broadcast
287
+ batch
288
+ end
289
+ end
228
290
  end
229
291
  end
230
292
  end
@@ -213,6 +213,67 @@ module Philiprehberger
213
213
  def full?
214
214
  @mutex.synchronize { @capacity ? @items.length >= @capacity : false }
215
215
  end
216
+
217
+ # The configured capacity, or +nil+ for an unlimited stack.
218
+ #
219
+ # @return [Integer, nil]
220
+ def capacity
221
+ @mutex.synchronize { @capacity }
222
+ end
223
+
224
+ # Number of additional items the stack can accept before it is full.
225
+ #
226
+ # Returns +nil+ for unlimited stacks. For bounded stacks returns
227
+ # +capacity - size+, clamped to a minimum of 0.
228
+ #
229
+ # @return [Integer, nil]
230
+ def remaining_capacity
231
+ @mutex.synchronize do
232
+ next nil unless @capacity
233
+
234
+ [@capacity - @items.length, 0].max
235
+ end
236
+ end
237
+
238
+ # Push many items onto the stack in order. The last element of +items+
239
+ # ends up on top. Each push respects capacity exactly as +push+ would.
240
+ #
241
+ # @param items [Array] the items to push, in order
242
+ # @return [void]
243
+ # @raise [ClosedError] if the stack has been closed
244
+ def push_all(items)
245
+ @mutex.synchronize do
246
+ raise ClosedError, 'cannot push on a closed stack' if @closed
247
+
248
+ items.each do |item|
249
+ @not_full.wait(@mutex) while @capacity && @items.length >= @capacity
250
+ raise ClosedError, 'cannot push on a closed stack' if @closed
251
+
252
+ @items.push(item)
253
+ @not_empty.signal
254
+ end
255
+ end
256
+ end
257
+
258
+ # Pop up to +max+ items from the top of the stack in LIFO order
259
+ # (top first). Non-blocking: returns an empty array if the stack is
260
+ # empty.
261
+ #
262
+ # @param max [Integer] maximum number of items to pop (must be a non-negative Integer)
263
+ # @return [Array] up to +max+ items, top first
264
+ # @raise [ArgumentError] if +max+ is not a non-negative Integer
265
+ def pop_batch(max)
266
+ raise ArgumentError, 'max must be a non-negative Integer' unless max.is_a?(Integer) && max >= 0
267
+
268
+ @mutex.synchronize do
269
+ count = [max, @items.length].min
270
+ next [] if count.zero?
271
+
272
+ batch = Array.new(count) { @items.pop }
273
+ @not_full.broadcast
274
+ batch
275
+ end
276
+ end
216
277
  end
217
278
  end
218
279
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module QueueStack
5
- VERSION = '0.5.0'
5
+ VERSION = '0.7.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: philiprehberger-queue_stack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Philip Rehberger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-16 00:00:00.000000000 Z
11
+ date: 2026-05-30 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Thread-safe queue and stack data structures with configurable capacity
14
14
  limits, blocking enqueue/dequeue with timeouts, and peek operations. Uses Mutex