philiprehberger-queue_stack 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +48 -0
- data/lib/philiprehberger/queue_stack/queue.rb +66 -3
- data/lib/philiprehberger/queue_stack/stack.rb +66 -3
- data/lib/philiprehberger/queue_stack/version.rb +1 -1
- data/lib/philiprehberger/queue_stack.rb +1 -0
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d2a9266feb351be5204a7e04178e22531c6e74d0a905c71f878eefe8cf94b606
|
|
4
|
+
data.tar.gz: e52a17282b21d3961e042bd235e9eb0bcb3885fbc9b5998e15224e07a8e8bbd9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 89a316f59b197ba77230f35da990b9c5ef6c27199466c91cfe039e3574f9b92a217469586975e3814e3ed13a0f9c0223321872d595ec6be07185a15467bc2855
|
|
7
|
+
data.tar.gz: cf85a470ea4e242be5f99452d0b74f771187f27037b86e7c17960a5968e9ffe83eaef6eb862c38cae65754901e24053235170e93f58b19173b5e1b36cda12104
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] - 2026-04-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `drain` method to remove and return all items at once
|
|
14
|
+
- `each` and `to_a` for non-destructive iteration
|
|
15
|
+
- `close` / `closed?` for graceful shutdown semantics
|
|
16
|
+
- `ClosedError` raised when adding to a closed container
|
|
17
|
+
|
|
18
|
+
## [0.1.5] - 2026-03-31
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- Add GitHub issue templates, dependabot config, and PR template
|
|
22
|
+
|
|
10
23
|
## [0.1.4] - 2026-03-31
|
|
11
24
|
|
|
12
25
|
### Changed
|
data/README.md
CHANGED
|
@@ -66,6 +66,44 @@ s = Philiprehberger::QueueStack::Stack.new
|
|
|
66
66
|
item = s.try_pop(timeout: 5) # waits up to 5 seconds
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
+
### Drain
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
q = Philiprehberger::QueueStack::Queue.new
|
|
73
|
+
q.enqueue('a')
|
|
74
|
+
q.enqueue('b')
|
|
75
|
+
q.enqueue('c')
|
|
76
|
+
q.drain # => ['a', 'b', 'c'] (queue is now empty)
|
|
77
|
+
|
|
78
|
+
s = Philiprehberger::QueueStack::Stack.new
|
|
79
|
+
s.push('a')
|
|
80
|
+
s.push('b')
|
|
81
|
+
s.push('c')
|
|
82
|
+
s.drain # => ['c', 'b', 'a'] (stack is now empty)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Iteration
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
q = Philiprehberger::QueueStack::Queue.new
|
|
89
|
+
q.enqueue('a')
|
|
90
|
+
q.enqueue('b')
|
|
91
|
+
q.each { |item| puts item } # prints 'a', 'b'
|
|
92
|
+
q.to_a # => ['a', 'b'] (queue unchanged)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Close / Shutdown
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
q = Philiprehberger::QueueStack::Queue.new
|
|
99
|
+
q.enqueue('a')
|
|
100
|
+
q.close
|
|
101
|
+
q.closed? # => true
|
|
102
|
+
q.dequeue # => 'a'
|
|
103
|
+
q.dequeue # => nil (closed and empty)
|
|
104
|
+
q.enqueue('b') # raises Philiprehberger::QueueStack::ClosedError
|
|
105
|
+
```
|
|
106
|
+
|
|
69
107
|
### Capacity Limits
|
|
70
108
|
|
|
71
109
|
```ruby
|
|
@@ -87,6 +125,11 @@ q.full? # => true
|
|
|
87
125
|
| `#dequeue` | Remove and return front item (blocks if empty) |
|
|
88
126
|
| `#try_dequeue(timeout:)` | Dequeue with timeout, returns nil on timeout |
|
|
89
127
|
| `#peek` | View front item without removing |
|
|
128
|
+
| `#drain` | Remove and return all items as array (FIFO order) |
|
|
129
|
+
| `#each` | Iterate items without removing (returns Enumerator if no block) |
|
|
130
|
+
| `#to_a` | Snapshot as array (FIFO order) |
|
|
131
|
+
| `#close` | Mark as closed (new enqueues raise `ClosedError`) |
|
|
132
|
+
| `#closed?` | Whether the queue has been closed |
|
|
90
133
|
| `#size` | Number of items |
|
|
91
134
|
| `#empty?` | Whether the queue is empty |
|
|
92
135
|
| `#full?` | Whether the queue is at capacity |
|
|
@@ -100,6 +143,11 @@ q.full? # => true
|
|
|
100
143
|
| `#pop` | Remove and return top item (blocks if empty) |
|
|
101
144
|
| `#try_pop(timeout:)` | Pop with timeout, returns nil on timeout |
|
|
102
145
|
| `#peek` | View top item without removing |
|
|
146
|
+
| `#drain` | Remove and return all items as array (LIFO order) |
|
|
147
|
+
| `#each` | Iterate items without removing (returns Enumerator if no block) |
|
|
148
|
+
| `#to_a` | Snapshot as array (LIFO order) |
|
|
149
|
+
| `#close` | Mark as closed (new pushes raise `ClosedError`) |
|
|
150
|
+
| `#closed?` | Whether the stack has been closed |
|
|
103
151
|
| `#size` | Number of items |
|
|
104
152
|
| `#empty?` | Whether the stack is empty |
|
|
105
153
|
| `#full?` | Whether the stack is at capacity |
|
|
@@ -9,12 +9,15 @@ module Philiprehberger
|
|
|
9
9
|
# q.enqueue('item')
|
|
10
10
|
# q.dequeue # => 'item'
|
|
11
11
|
class Queue
|
|
12
|
+
include Enumerable
|
|
13
|
+
|
|
12
14
|
# Create a new queue.
|
|
13
15
|
#
|
|
14
16
|
# @param capacity [Integer, nil] maximum number of items (nil for unlimited)
|
|
15
17
|
def initialize(capacity: nil)
|
|
16
18
|
@items = []
|
|
17
19
|
@capacity = capacity
|
|
20
|
+
@closed = false
|
|
18
21
|
@mutex = Mutex.new
|
|
19
22
|
@not_empty = ConditionVariable.new
|
|
20
23
|
@not_full = ConditionVariable.new
|
|
@@ -24,20 +27,27 @@ module Philiprehberger
|
|
|
24
27
|
#
|
|
25
28
|
# @param item [Object] the item to enqueue
|
|
26
29
|
# @return [void]
|
|
30
|
+
# @raise [ClosedError] if the queue has been closed
|
|
27
31
|
def enqueue(item)
|
|
28
32
|
@mutex.synchronize do
|
|
33
|
+
raise ClosedError, 'cannot enqueue on a closed queue' if @closed
|
|
34
|
+
|
|
29
35
|
@not_full.wait(@mutex) while @capacity && @items.length >= @capacity
|
|
30
36
|
@items.push(item)
|
|
31
37
|
@not_empty.signal
|
|
32
38
|
end
|
|
33
39
|
end
|
|
34
40
|
|
|
35
|
-
# Remove and return the front item. Blocks if empty.
|
|
41
|
+
# Remove and return the front item. Blocks if empty (returns nil if closed and empty).
|
|
36
42
|
#
|
|
37
|
-
# @return [Object] the dequeued item
|
|
43
|
+
# @return [Object, nil] the dequeued item or nil if closed and empty
|
|
38
44
|
def dequeue
|
|
39
45
|
@mutex.synchronize do
|
|
40
|
-
|
|
46
|
+
while @items.empty?
|
|
47
|
+
return nil if @closed
|
|
48
|
+
|
|
49
|
+
@not_empty.wait(@mutex)
|
|
50
|
+
end
|
|
41
51
|
item = @items.shift
|
|
42
52
|
@not_full.signal
|
|
43
53
|
item
|
|
@@ -52,6 +62,8 @@ module Philiprehberger
|
|
|
52
62
|
deadline = Time.now + timeout
|
|
53
63
|
@mutex.synchronize do
|
|
54
64
|
while @items.empty?
|
|
65
|
+
return nil if @closed
|
|
66
|
+
|
|
55
67
|
remaining = deadline - Time.now
|
|
56
68
|
return nil if remaining <= 0
|
|
57
69
|
|
|
@@ -63,6 +75,57 @@ module Philiprehberger
|
|
|
63
75
|
end
|
|
64
76
|
end
|
|
65
77
|
|
|
78
|
+
# Remove and return all items as an array (FIFO order). Non-blocking.
|
|
79
|
+
#
|
|
80
|
+
# @return [Array] all items in FIFO order
|
|
81
|
+
def drain
|
|
82
|
+
@mutex.synchronize do
|
|
83
|
+
result = @items.dup
|
|
84
|
+
@items.clear
|
|
85
|
+
@not_full.broadcast
|
|
86
|
+
result
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Iterate items without removing them (snapshot of current state, FIFO order).
|
|
91
|
+
# Returns an Enumerator if no block is given.
|
|
92
|
+
#
|
|
93
|
+
# @yield [item] each item in FIFO order
|
|
94
|
+
# @return [Enumerator, self]
|
|
95
|
+
def each(&block)
|
|
96
|
+
snapshot = @mutex.synchronize { @items.dup }
|
|
97
|
+
return snapshot.each unless block
|
|
98
|
+
|
|
99
|
+
snapshot.each(&block)
|
|
100
|
+
self
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Return a snapshot of items as an array (FIFO order).
|
|
104
|
+
#
|
|
105
|
+
# @return [Array]
|
|
106
|
+
def to_a
|
|
107
|
+
@mutex.synchronize { @items.dup }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Mark the queue as closed. New enqueue calls will raise ClosedError.
|
|
111
|
+
# Existing items can still be dequeued. Wakes all waiting threads.
|
|
112
|
+
#
|
|
113
|
+
# @return [void]
|
|
114
|
+
def close
|
|
115
|
+
@mutex.synchronize do
|
|
116
|
+
@closed = true
|
|
117
|
+
@not_empty.broadcast
|
|
118
|
+
@not_full.broadcast
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Whether the queue has been closed.
|
|
123
|
+
#
|
|
124
|
+
# @return [Boolean]
|
|
125
|
+
def closed?
|
|
126
|
+
@mutex.synchronize { @closed }
|
|
127
|
+
end
|
|
128
|
+
|
|
66
129
|
# Peek at the front item without removing it.
|
|
67
130
|
#
|
|
68
131
|
# @return [Object, nil] the front item or nil if empty
|
|
@@ -9,12 +9,15 @@ module Philiprehberger
|
|
|
9
9
|
# s.push('item')
|
|
10
10
|
# s.pop # => 'item'
|
|
11
11
|
class Stack
|
|
12
|
+
include Enumerable
|
|
13
|
+
|
|
12
14
|
# Create a new stack.
|
|
13
15
|
#
|
|
14
16
|
# @param capacity [Integer, nil] maximum number of items (nil for unlimited)
|
|
15
17
|
def initialize(capacity: nil)
|
|
16
18
|
@items = []
|
|
17
19
|
@capacity = capacity
|
|
20
|
+
@closed = false
|
|
18
21
|
@mutex = Mutex.new
|
|
19
22
|
@not_empty = ConditionVariable.new
|
|
20
23
|
@not_full = ConditionVariable.new
|
|
@@ -24,20 +27,27 @@ module Philiprehberger
|
|
|
24
27
|
#
|
|
25
28
|
# @param item [Object] the item to push
|
|
26
29
|
# @return [void]
|
|
30
|
+
# @raise [ClosedError] if the stack has been closed
|
|
27
31
|
def push(item)
|
|
28
32
|
@mutex.synchronize do
|
|
33
|
+
raise ClosedError, 'cannot push on a closed stack' if @closed
|
|
34
|
+
|
|
29
35
|
@not_full.wait(@mutex) while @capacity && @items.length >= @capacity
|
|
30
36
|
@items.push(item)
|
|
31
37
|
@not_empty.signal
|
|
32
38
|
end
|
|
33
39
|
end
|
|
34
40
|
|
|
35
|
-
# Pop and return the top item. Blocks if empty.
|
|
41
|
+
# Pop and return the top item. Blocks if empty (returns nil if closed and empty).
|
|
36
42
|
#
|
|
37
|
-
# @return [Object] the popped item
|
|
43
|
+
# @return [Object, nil] the popped item or nil if closed and empty
|
|
38
44
|
def pop
|
|
39
45
|
@mutex.synchronize do
|
|
40
|
-
|
|
46
|
+
while @items.empty?
|
|
47
|
+
return nil if @closed
|
|
48
|
+
|
|
49
|
+
@not_empty.wait(@mutex)
|
|
50
|
+
end
|
|
41
51
|
item = @items.pop
|
|
42
52
|
@not_full.signal
|
|
43
53
|
item
|
|
@@ -52,6 +62,8 @@ module Philiprehberger
|
|
|
52
62
|
deadline = Time.now + timeout
|
|
53
63
|
@mutex.synchronize do
|
|
54
64
|
while @items.empty?
|
|
65
|
+
return nil if @closed
|
|
66
|
+
|
|
55
67
|
remaining = deadline - Time.now
|
|
56
68
|
return nil if remaining <= 0
|
|
57
69
|
|
|
@@ -63,6 +75,57 @@ module Philiprehberger
|
|
|
63
75
|
end
|
|
64
76
|
end
|
|
65
77
|
|
|
78
|
+
# Remove and return all items as an array (LIFO order, top first). Non-blocking.
|
|
79
|
+
#
|
|
80
|
+
# @return [Array] all items in LIFO order (top first)
|
|
81
|
+
def drain
|
|
82
|
+
@mutex.synchronize do
|
|
83
|
+
result = @items.reverse
|
|
84
|
+
@items.clear
|
|
85
|
+
@not_full.broadcast
|
|
86
|
+
result
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Iterate items without removing them (snapshot of current state, LIFO order).
|
|
91
|
+
# Returns an Enumerator if no block is given.
|
|
92
|
+
#
|
|
93
|
+
# @yield [item] each item in LIFO order (top first)
|
|
94
|
+
# @return [Enumerator, self]
|
|
95
|
+
def each(&block)
|
|
96
|
+
snapshot = @mutex.synchronize { @items.reverse }
|
|
97
|
+
return snapshot.each unless block
|
|
98
|
+
|
|
99
|
+
snapshot.each(&block)
|
|
100
|
+
self
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Return a snapshot of items as an array (LIFO order, top first).
|
|
104
|
+
#
|
|
105
|
+
# @return [Array]
|
|
106
|
+
def to_a
|
|
107
|
+
@mutex.synchronize { @items.reverse }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Mark the stack as closed. New push calls will raise ClosedError.
|
|
111
|
+
# Existing items can still be popped. Wakes all waiting threads.
|
|
112
|
+
#
|
|
113
|
+
# @return [void]
|
|
114
|
+
def close
|
|
115
|
+
@mutex.synchronize do
|
|
116
|
+
@closed = true
|
|
117
|
+
@not_empty.broadcast
|
|
118
|
+
@not_full.broadcast
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Whether the stack has been closed.
|
|
123
|
+
#
|
|
124
|
+
# @return [Boolean]
|
|
125
|
+
def closed?
|
|
126
|
+
@mutex.synchronize { @closed }
|
|
127
|
+
end
|
|
128
|
+
|
|
66
129
|
# Peek at the top item without removing it.
|
|
67
130
|
#
|
|
68
131
|
# @return [Object, nil] the top item or nil if empty
|
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.
|
|
4
|
+
version: 0.2.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-03
|
|
11
|
+
date: 2026-04-03 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
|
|
@@ -26,11 +26,11 @@ files:
|
|
|
26
26
|
- lib/philiprehberger/queue_stack/queue.rb
|
|
27
27
|
- lib/philiprehberger/queue_stack/stack.rb
|
|
28
28
|
- lib/philiprehberger/queue_stack/version.rb
|
|
29
|
-
homepage: https://
|
|
29
|
+
homepage: https://philiprehberger.com/open-source-packages/ruby/philiprehberger-queue_stack
|
|
30
30
|
licenses:
|
|
31
31
|
- MIT
|
|
32
32
|
metadata:
|
|
33
|
-
homepage_uri: https://
|
|
33
|
+
homepage_uri: https://philiprehberger.com/open-source-packages/ruby/philiprehberger-queue_stack
|
|
34
34
|
source_code_uri: https://github.com/philiprehberger/rb-queue-stack
|
|
35
35
|
changelog_uri: https://github.com/philiprehberger/rb-queue-stack/blob/main/CHANGELOG.md
|
|
36
36
|
bug_tracker_uri: https://github.com/philiprehberger/rb-queue-stack/issues
|