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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff50a60ddd8a9bd5d462120f2030251c948659105edfe76ee9a87646d16f3a7d
4
- data.tar.gz: e34e36ff80548f16e92aa9fd2b604161f74140caff317efec05ae48648bf732e
3
+ metadata.gz: d2a9266feb351be5204a7e04178e22531c6e74d0a905c71f878eefe8cf94b606
4
+ data.tar.gz: e52a17282b21d3961e042bd235e9eb0bcb3885fbc9b5998e15224e07a8e8bbd9
5
5
  SHA512:
6
- metadata.gz: bb6a2bade86267597f71fe64a549fd82b85f5376a3c4e57b4f2ecc96a5f944abc9f63112c0c50407c185ee41ffa8336a6f1328c866ee70ee21ebbd5eff0eac25
7
- data.tar.gz: 0ba54d2272dee5f4fa95ce8e614ef4a7bde2f1db07c3887adf4caee7495395fb387ec76288cd0fc59ed87b7af1fa9d137e4ea866c777a06aa510b147255bdfc7
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
- @not_empty.wait(@mutex) while @items.empty?
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
- @not_empty.wait(@mutex) while @items.empty?
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module QueueStack
5
- VERSION = '0.1.4'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
@@ -7,5 +7,6 @@ require_relative 'queue_stack/stack'
7
7
  module Philiprehberger
8
8
  module QueueStack
9
9
  class Error < StandardError; end
10
+ class ClosedError < Error; end
10
11
  end
11
12
  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.1.4
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-31 00:00:00.000000000 Z
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://github.com/philiprehberger/rb-queue-stack
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://github.com/philiprehberger/rb-queue-stack
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