philiprehberger-queue_stack 0.2.0 → 0.4.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: d2a9266feb351be5204a7e04178e22531c6e74d0a905c71f878eefe8cf94b606
4
- data.tar.gz: e52a17282b21d3961e042bd235e9eb0bcb3885fbc9b5998e15224e07a8e8bbd9
3
+ metadata.gz: 8f720c2ee97526d57826cb37fba4be96e6e1143879b7627a2792cef97ffaa1dc
4
+ data.tar.gz: 311fdae1a3eb9f31d27f4ef77006eae1f224050418ac97721540d761ac081ac0
5
5
  SHA512:
6
- metadata.gz: 89a316f59b197ba77230f35da990b9c5ef6c27199466c91cfe039e3574f9b92a217469586975e3814e3ed13a0f9c0223321872d595ec6be07185a15467bc2855
7
- data.tar.gz: cf85a470ea4e242be5f99452d0b74f771187f27037b86e7c17960a5968e9ffe83eaef6eb862c38cae65754901e24053235170e93f58b19173b5e1b36cda12104
6
+ metadata.gz: fd4c5bc553fde4d2278b2efa422ed0c4c67aaf63d9c5bc049bce395066ab544533bf2b11696a564769cbd6dbdd900a4c802a12abec032d888d17c0d0d5d0dc36
7
+ data.tar.gz: aa1cc6ea3d889cde7c372943df2bb9b5c46d0f87f3e628659e01ebbbf3697a8ce548f41c36508a3f82af14818ca0bd852da74aa577e13abf29899f1b60e6a162
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.4.0] - 2026-04-15
11
+
12
+ ### Added
13
+ - `Queue#peek_at(index)` returns the item at the given position without removing it, supporting negative indices like `Array#[]` and returning `nil` when out of range
14
+
15
+ ## [0.3.0] - 2026-04-09
16
+
17
+ ### Added
18
+ - `Queue#try_enqueue(item, timeout: nil)` and `Stack#try_push(item, timeout: nil)` non-blocking insertion variants
19
+ - `Queue#clear` and `Stack#clear` to discard all items and wake blocked producers
20
+
10
21
  ## [0.2.0] - 2026-04-03
11
22
 
12
23
  ### Added
data/README.md CHANGED
@@ -40,9 +40,13 @@ item = q.dequeue # => 'task'
40
40
  q = Philiprehberger::QueueStack::Queue.new(capacity: 10)
41
41
  q.enqueue('first')
42
42
  q.enqueue('second')
43
- q.dequeue # => 'first'
44
- q.peek # => 'second'
45
- q.size # => 1
43
+ q.enqueue('third')
44
+ q.peek # => 'first'
45
+ q.peek_at(1) # => 'second'
46
+ q.peek_at(-1) # => 'third'
47
+ q.peek_at(99) # => nil (out of range)
48
+ q.dequeue # => 'first'
49
+ q.size # => 2
46
50
  ```
47
51
 
48
52
  ### Stack (LIFO)
@@ -92,6 +96,28 @@ q.each { |item| puts item } # prints 'a', 'b'
92
96
  q.to_a # => ['a', 'b'] (queue unchanged)
93
97
  ```
94
98
 
99
+ ### Non-Blocking Insertion
100
+
101
+ ```ruby
102
+ q = Philiprehberger::QueueStack::Queue.new(capacity: 1)
103
+ q.enqueue('a')
104
+ q.try_enqueue('b') # => false (full, no wait)
105
+ q.try_enqueue('b', timeout: 0.5) # => false after waiting up to 0.5s
106
+
107
+ s = Philiprehberger::QueueStack::Stack.new(capacity: 1)
108
+ s.push('a')
109
+ s.try_push('b') # => false (full, no wait)
110
+ ```
111
+
112
+ ### Clear
113
+
114
+ ```ruby
115
+ q = Philiprehberger::QueueStack::Queue.new
116
+ q.enqueue('a'); q.enqueue('b')
117
+ q.clear
118
+ q.empty? # => true
119
+ ```
120
+
95
121
  ### Close / Shutdown
96
122
 
97
123
  ```ruby
@@ -122,9 +148,12 @@ q.full? # => true
122
148
  |--------|-------------|
123
149
  | `.new(capacity:)` | Create a queue with optional capacity limit |
124
150
  | `#enqueue(item)` | Add item to back (blocks if full) |
151
+ | `#try_enqueue(item, timeout: nil)` | Non-blocking enqueue, returns true/false (waits up to timeout if given) |
125
152
  | `#dequeue` | Remove and return front item (blocks if empty) |
126
153
  | `#try_dequeue(timeout:)` | Dequeue with timeout, returns nil on timeout |
154
+ | `#clear` | Remove all items without returning them |
127
155
  | `#peek` | View front item without removing |
156
+ | `#peek_at(index)` | View item at the given position (supports negative indices, returns nil if out of range) |
128
157
  | `#drain` | Remove and return all items as array (FIFO order) |
129
158
  | `#each` | Iterate items without removing (returns Enumerator if no block) |
130
159
  | `#to_a` | Snapshot as array (FIFO order) |
@@ -140,8 +169,10 @@ q.full? # => true
140
169
  |--------|-------------|
141
170
  | `.new(capacity:)` | Create a stack with optional capacity limit |
142
171
  | `#push(item)` | Push item on top (blocks if full) |
172
+ | `#try_push(item, timeout: nil)` | Non-blocking push, returns true/false (waits up to timeout if given) |
143
173
  | `#pop` | Remove and return top item (blocks if empty) |
144
174
  | `#try_pop(timeout:)` | Pop with timeout, returns nil on timeout |
175
+ | `#clear` | Remove all items without returning them |
145
176
  | `#peek` | View top item without removing |
146
177
  | `#drain` | Remove and return all items as array (LIFO order) |
147
178
  | `#each` | Iterate items without removing (returns Enumerator if no block) |
@@ -54,6 +54,48 @@ module Philiprehberger
54
54
  end
55
55
  end
56
56
 
57
+ # Try to enqueue an item without blocking indefinitely.
58
+ #
59
+ # With timeout: nil, returns immediately. With a numeric timeout, waits up to
60
+ # that many seconds for space to become available.
61
+ #
62
+ # @param item [Object] the item to enqueue
63
+ # @param timeout [Numeric, nil] seconds to wait, or nil for non-blocking
64
+ # @return [Boolean] true if enqueued, false if full (or timed out)
65
+ # @raise [ClosedError] if the queue has been closed
66
+ def try_enqueue(item, timeout: nil)
67
+ @mutex.synchronize do
68
+ raise ClosedError, 'cannot enqueue on a closed queue' if @closed
69
+
70
+ if @capacity && @items.length >= @capacity
71
+ return false if timeout.nil? || timeout <= 0
72
+
73
+ deadline = Time.now + timeout
74
+ while @items.length >= @capacity
75
+ remaining = deadline - Time.now
76
+ return false if remaining <= 0
77
+
78
+ @not_full.wait(@mutex, remaining)
79
+ raise ClosedError, 'cannot enqueue on a closed queue' if @closed
80
+ end
81
+ end
82
+
83
+ @items.push(item)
84
+ @not_empty.signal
85
+ true
86
+ end
87
+ end
88
+
89
+ # Remove all items without returning them. Signals any blocked producers.
90
+ #
91
+ # @return [void]
92
+ def clear
93
+ @mutex.synchronize do
94
+ @items.clear
95
+ @not_full.broadcast
96
+ end
97
+ end
98
+
57
99
  # Try to dequeue an item with a timeout.
58
100
  #
59
101
  # @param timeout [Numeric] seconds to wait
@@ -133,6 +175,18 @@ module Philiprehberger
133
175
  @mutex.synchronize { @items.first }
134
176
  end
135
177
 
178
+ # Peek at the item at the given position without removing it.
179
+ #
180
+ # Indexing follows +Array#[]+ semantics: +0+ is the front of the queue,
181
+ # +size - 1+ is the back, and negative indices count from the back
182
+ # (+-1+ is the last item). Returns +nil+ when the index is out of range.
183
+ #
184
+ # @param index [Integer] position in the queue (supports negative indices)
185
+ # @return [Object, nil] the item at the position or nil if out of range
186
+ def peek_at(index)
187
+ @mutex.synchronize { @items[index] }
188
+ end
189
+
136
190
  # Return the number of items in the queue.
137
191
  #
138
192
  # @return [Integer]
@@ -54,6 +54,48 @@ module Philiprehberger
54
54
  end
55
55
  end
56
56
 
57
+ # Try to push an item without blocking indefinitely.
58
+ #
59
+ # With timeout: nil, returns immediately. With a numeric timeout, waits up to
60
+ # that many seconds for space to become available.
61
+ #
62
+ # @param item [Object] the item to push
63
+ # @param timeout [Numeric, nil] seconds to wait, or nil for non-blocking
64
+ # @return [Boolean] true if pushed, false if full (or timed out)
65
+ # @raise [ClosedError] if the stack has been closed
66
+ def try_push(item, timeout: nil)
67
+ @mutex.synchronize do
68
+ raise ClosedError, 'cannot push on a closed stack' if @closed
69
+
70
+ if @capacity && @items.length >= @capacity
71
+ return false if timeout.nil? || timeout <= 0
72
+
73
+ deadline = Time.now + timeout
74
+ while @items.length >= @capacity
75
+ remaining = deadline - Time.now
76
+ return false if remaining <= 0
77
+
78
+ @not_full.wait(@mutex, remaining)
79
+ raise ClosedError, 'cannot push on a closed stack' if @closed
80
+ end
81
+ end
82
+
83
+ @items.push(item)
84
+ @not_empty.signal
85
+ true
86
+ end
87
+ end
88
+
89
+ # Remove all items without returning them. Signals any blocked producers.
90
+ #
91
+ # @return [void]
92
+ def clear
93
+ @mutex.synchronize do
94
+ @items.clear
95
+ @not_full.broadcast
96
+ end
97
+ end
98
+
57
99
  # Try to pop an item with a timeout.
58
100
  #
59
101
  # @param timeout [Numeric] seconds to wait
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module QueueStack
5
- VERSION = '0.2.0'
5
+ VERSION = '0.4.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.2.0
4
+ version: 0.4.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-03 00:00:00.000000000 Z
11
+ date: 2026-04-15 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