standard-procedure-plumbing 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 717c30b41fce6d982c4f3f9d5a0c8ac5abb8187e7bf03b13151fee49f42a3955
4
- data.tar.gz: e0857d6739d95a5861a66871168a9be8154d1854a03fd59534f939b4be06896c
3
+ metadata.gz: 45b26855ceaf54673b25fe4ca63a5e52ab302f585f7653731f444b312dec233f
4
+ data.tar.gz: 936af082c395b80018878f9a53842505473f2d9978c97fafc88aea48102153e4
5
5
  SHA512:
6
- metadata.gz: 9de7d23847f7776acb4c4c8d6585451d26a565e3c2982afdf6ebff45293d9977fc27ad79b9996eb192b5f80de713afae7cb725641682d21577beeac4ec76530a
7
- data.tar.gz: eecd88c9eba77690548c56ce683aec310d76955bf3e9d0c85a2f9a7f700a5df7e43206647cbedc18e1cd73530141c881570ee9451787bdca0e5cb4f2e9db7e43
6
+ metadata.gz: 1cce27319493e32407381bb3623734a5bcd87bd1fa986c83f6d8790afb05376c144f994adfd5e094dd9377ad11ee6bdef3d2348ec279d9f378706d52da49362c
7
+ data.tar.gz: 6594ec35d9753979414b79c524a27d3c033bf96739ec750a0774c1cbb0d7b6dc97ec7e64245026f93f42c8083cce1da91fa772cbcecc61edecba7e931e085c10
data/.rubocop.yml ADDED
@@ -0,0 +1,24 @@
1
+ # We want Exclude directives from different
2
+ # config files to get merged, not overwritten
3
+ inherit_mode:
4
+ merge:
5
+ - Exclude
6
+
7
+ require:
8
+ # Standard's config uses custom cops,
9
+ # so it must be loaded along with custom Standard gems
10
+ - standard
11
+ - standard-custom
12
+ - standard-performance
13
+ # rubocop-performance is required when using Performance cops
14
+ - rubocop-performance
15
+
16
+ inherit_gem:
17
+ standard: config/base.yml
18
+ standard-performance: config/base.yml
19
+ standard-custom: config/base.yml
20
+
21
+ # Global options, like Ruby version
22
+ AllCops:
23
+ SuggestExtensions: false
24
+ TargetRubyVersion: 3.2
data/.solargraph.yml ADDED
@@ -0,0 +1,32 @@
1
+ ---
2
+ include:
3
+ - "**/*.rb"
4
+ exclude:
5
+ - spec/**/*
6
+ - test/**/*
7
+ - vendor/**/*
8
+ - ".bundle/**/*"
9
+ require:
10
+ - actioncable
11
+ - actionmailer
12
+ - actionpack
13
+ - actionview
14
+ - activejob
15
+ - activemodel
16
+ - activerecord
17
+ - activestorage
18
+ - activesupport
19
+ domains: []
20
+ reporters:
21
+ - rubocop
22
+ - require_not_found
23
+ formatter:
24
+ rubocop:
25
+ cops: safe
26
+ except: []
27
+ only: []
28
+ extra_args: []
29
+ require_paths: []
30
+ plugins:
31
+ - solargraph-rails
32
+ max_files: 5000
data/.standard.yml CHANGED
@@ -1,3 +1,9 @@
1
- # For available configuration options, see:
2
- # https://github.com/standardrb/standard
3
- ruby_version: 3.0
1
+ fix: true
2
+ parallel: true
3
+ format: progress
4
+ default_ignores: true
5
+
6
+ ignore:
7
+ - 'vendor/**/*'
8
+ - 'Gemfile.lock'
9
+
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
- ## [Unreleased]
1
+ ## [0.1.1] - 2024-08-14
2
+
3
+ - Tidied up the code
4
+ - Added Plumbing::Chain
2
5
 
3
6
  ## [0.1.0] - 2024-04-13
4
7
 
5
8
  - Initial release
9
+
10
+ ## [Unreleased]
11
+
data/README.md CHANGED
@@ -1,50 +1,165 @@
1
1
  # Plumbing
2
2
 
3
- Composable Observer
3
+ ## Plumbing::Pipe - a composable observer
4
4
 
5
5
  [Observers](https://ruby-doc.org/3.3.0/stdlibs/observer/Observable.html) in Ruby are a pattern where objects (observers) register their interest in another object (the observable). This pattern is common throughout programming languages (event listeners in Javascript, the dependency protocol in [Smalltalk](https://en.wikipedia.org/wiki/Smalltalk)).
6
6
 
7
- Unlike ruby's in-built observers, this gem makes observers "composable". Instead of simply registering for notifications from the observable, we observe a stream of notifications, which could be produced by multiple observables, all being sent through the same pipe. We can then chain observers together, composing a "pipeline" of operations from a single source of events.
7
+ [Plumbing::Pipe](lib/plumbing/pipe.rb) makes observers "composable". Instead of simply registering for notifications from the observable, we observe a stream of notifications, which could be produced by multiple observables, all being sent through the same pipe. We can then chain observers together, composing a "pipeline" of operations from a single source of events.
8
8
 
9
- For example, in a social-networking application, you may push all events associated with a user through a single pipe. But one module within the application is only interested in follow requests, another module in comments. So the "followers" module would attach a "filter pipe" to the "users" pipe, filtering out everything except follow requests. Then the code within that module observes this "filter pipe" so only gets notified about follow requests. And similarly, the "comments" module attaches a "filter pipe" to the "users" pipe, filtering out everything except "comments".
9
+ ### Usage
10
10
 
11
- However, the pipeline can do much more than simple filtering.
11
+ A simple observer:
12
+ ```ruby
13
+ require "plumbing"
12
14
 
13
- In a search engine application, it is important to keep a record of what has been searched for, but more importantly, which of those search results resulted in a click - as you can then use this data to improve your search results in future. We could implement a chain of observers, from the pipe that records all search related events, a filter that looks at the events from a single user, to another observer that maintains a log of every search result for a given user from the last 30 minutes and then matches any clicks to those results - sending those matches to an analytics service.
15
+ @source = Plumbing::Pipe.start
14
16
 
15
- The key fact is that each element in the chain of observers is only aware of the stream of events from the element just before it. And when it outputs its own events, any observers to that stream are only aware of the element they have subscribed to. This means that the chain works in a similar manner to unix pipes.
17
+ @observer = @source.add_observer do |event|
18
+ puts event.type
19
+ end
16
20
 
17
- In unix, you can use `cat logfile | grep -o "some text" | ec -l` to easily count the number of times "some text" occurs in your logfile. Each individual command in that pipeline is extremely simple and optimised for its one task. But piping them together gives you incredible flexibility and power.
21
+ @source.notify "something_happened", message: "But what was it?"
22
+ # => "something_happened"
23
+ ```
18
24
 
19
- The same is true when you compose a pipeline of observers, each of which watches the events in a stream. You can attach observers which buffer the incoming events, so the receivers aren't swamped. You can attach observers which manipulate the incoming data (see the inline emoji writer in the examples folder), or de-duplicate or merge events (which is very useful if you want to prevent flicker and unnecessary redraws in your user-interface). And observers can republish events to different streams - we could take events on one stream and send those same events, or a subset, to a web-socket.
25
+ Simple filtering:
26
+ ```ruby
27
+ require "plumbing"
20
28
 
29
+ @source = Plumbing::Pipe.start
21
30
 
22
- ## Installation
23
-
24
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
31
+ @filter = Plumbing::Filter.start source: @source, accepts: %w[important urgent]
25
32
 
26
- Install the gem and add to the application's Gemfile by executing:
33
+ @observer = @filter.add_observer do |event|
34
+ puts event.type
35
+ end
27
36
 
28
- $ bundle add standard-procedure-plumbing
37
+ @source.notify "important", message: "ALERT! ALERT!"
38
+ # => "important"
29
39
 
30
- ## Usage
40
+ @source.notify "unimportant", message: "Nothing to see here"
41
+ # => <no output>
42
+ ```
31
43
 
32
- Create a pipe, chain pipes together, add observers and push events
44
+ Custom filtering:
45
+ ```ruby
46
+ require "plumbing"
33
47
 
34
- require 'plumbing'
48
+ class EveryThirdEvent < Plumbing::CustomFilter
49
+ def initialize source:
50
+ super source: source
51
+ @events = []
52
+ end
35
53
 
36
- @pipe = Plumbing::Pipe.start
37
- @filter = Plumbing::Filter.start source: @pipe, accepts: %w[important urgent]
38
- @observer = @filter.add_observer do |event|
39
- puts event.type
54
+ def received event
55
+ @events << event
56
+ # if we've already stored 2 events in the buffer then broadcast the newest event and clear the buffer
57
+ if @events.count >= 2
58
+ @events.clear
59
+ self << event
40
60
  end
61
+ end
62
+ end
63
+
64
+ @source = Plumbing::Pipe.start
65
+ @filter = EveryThirdEvent.new(source: @source)
66
+
67
+ @observer = @filter.add_observer do |event|
68
+ puts event.type
69
+ end
70
+
71
+ 1.upto 10 do |i|
72
+ @source.notify i.to_s
73
+ end
74
+ # => "3"
75
+ # => "6"
76
+ # => "9"
77
+ ```
78
+
79
+ Joining multiple sources
80
+ ```ruby
81
+ require "plumbing"
82
+
83
+ @first_source = Plumbing::Pipe.start
84
+ @second_source = Plumbing::Pipe.start
85
+
86
+ @join = Plumbing::Junction.start @first_source, @second_source
87
+
88
+ @observer = @join.add_observer do |event|
89
+ puts event.type
90
+ end
91
+
92
+ @first_source.notify "one"
93
+ # => "one"
94
+ @second_source.notify "two"
95
+ # => "two"
96
+ ```
97
+
98
+ There is also [Plumbing::Concurrent::Pipe](/lib/plumbing/concurrent/pipe.rb) that uses a Ractor to dispatch events. Use with caution as Ractors are still experimental, plus they have strict conditions about the data that can be passed across Ractor boundaries.
99
+
100
+ ## Plumbing::Chain - a chain of operations that occur in sequence
101
+
102
+ Define a sequence of operations that proceed in order, passing their output from one operation as the input to another.
103
+
104
+ You can define pre-conditions (which validate the inputs supplied) or post-conditions (which validate the output).
105
+
106
+ ### Usage:
107
+
108
+ ```ruby
109
+ require "plumbing"
110
+ class BuildSequence < Plumbing::Chain
111
+ pre_condition :must_be_an_array do |input|
112
+ input.is_a? Array
113
+ end
114
+
115
+ post_condition :must_have_three_elements do |output|
116
+ # yes, this is a stupid post-condition but it shows how you can ensure your outputs are valid
117
+ output.length == 3
118
+ end
119
+
120
+ perform :add_first
121
+ perform :add_second
122
+ perform :add_third
123
+
124
+ private
125
+
126
+ def add_first input
127
+ input << "first"
128
+ end
129
+
130
+ def add_second input
131
+ input << "second"
132
+ end
133
+
134
+ def add_third input
135
+ input << "third"
136
+ end
137
+ end
138
+
139
+ BuildSequence.new.call []
140
+ # => ["first", "second", "third"]
141
+
142
+ BuildSequence.new.call 1
143
+ # => Plumbing::PreconditionError("must_be_an_array")
144
+
145
+ BuildSequence.new.call ["extra element"]
146
+ # => Plumbing::PostconditionError("must_have_three_elements")
147
+ ```
148
+
149
+ ## Installation
150
+
151
+ Install the gem and add to the application's Gemfile by executing:
152
+
153
+ ```sh
154
+ bundle add standard-procedure-plumbing
155
+ ```
156
+
157
+ Then:
41
158
 
42
- @pipe << Event.new(type: "unimportant", data: { some: "data"})
43
- # => no output
44
- @pipe << Event.new(type: "important", data: { some: "data"})
45
- # => "important"
159
+ ```ruby
160
+ require 'plumbing'
161
+ ```
46
162
 
47
- @filter.remove_observer @observer
48
163
 
49
164
  ## Development
50
165
 
@@ -0,0 +1 @@
1
+ 30cc1d4b434b322269e0100acd3a42088d93359e0d34b7a947ac272c55b2e82788ab6f0926f2cc95552d746b71791470412a7cdfa34605d4b20309df0de97a33
@@ -0,0 +1,115 @@
1
+ require "dry/types"
2
+ require_relative "error"
3
+ require_relative "event"
4
+
5
+ module Plumbing
6
+ # The "plumbing" for a Pipe.
7
+ # This class is "blocked", in that it won't push any events to registered observers.
8
+ # Instead, this is the basis for subclasses like [Plumbing::Pipe] which actually allow events to flow through them.
9
+ class BlockedPipe
10
+ module Types
11
+ include Dry::Types()
12
+ # Events must be Plumbing::Event instances or subclasses
13
+ Event = Instance(Plumbing::Event)
14
+ # Observers must have a `call` method
15
+ Observer = Interface(:call)
16
+ end
17
+
18
+ # Create a new BlockedPipe
19
+ # Subclasses should call `super()` to ensure the pipe is initialised corrected
20
+ def initialize
21
+ @observers = []
22
+ end
23
+
24
+ # Push an event into the pipe
25
+ # @param event [Plumbing::Event] the event to push into the pipe
26
+ # Subclasses should implement this method
27
+ def << event
28
+ raise PipeIsBlocked
29
+ end
30
+
31
+ # A shortcut to creating and then pushing an event
32
+ # @param event_type [String] representing the type of event this is
33
+ # @param data [Hash] representing the event-specific data to be passed to the observers
34
+ def notify event_type, **data
35
+ Event.new(type: event_type, data: data).tap do |event|
36
+ self << event
37
+ end
38
+ end
39
+
40
+ # Add an observer to this pipe
41
+ # @param callable [Proc] (optional)
42
+ # @param &block [Block] (optional)
43
+ # @return an object representing this observer (dependent upon the implementation of the pipe itself)
44
+ # Either a `callable` or a `block` must be supplied. If the latter, it is converted to a [Proc]
45
+ def add_observer callable = nil, &block
46
+ callable ||= block.to_proc
47
+ Types::Observer[callable].tap do |observer|
48
+ @observers << observer
49
+ end
50
+ end
51
+
52
+ # Remove an observer from this pipe
53
+ # @param observer
54
+ # This removes the given observer from this pipe. The observer should have previously been returned by #add_observer and is implementation-specific
55
+ def remove_observer observer
56
+ @observers.delete observer
57
+ end
58
+
59
+ # Test whether the given observer is observing this pipe
60
+ # @param observer
61
+ # @return [boolean]
62
+ def is_observer? observer
63
+ @observers.include? observer
64
+ end
65
+
66
+ # Close this pipe and perform any cleanup.
67
+ # Subclasses should override this to perform their own shutdown routines and call `super` to ensure everything is tidied up
68
+ def shutdown
69
+ # clean up and release any observers, just in case
70
+ @observers = []
71
+ end
72
+
73
+ # Start this pipe
74
+ # Subclasses may override this method to add any implementation specific details.
75
+ # By default any supplied parameters are called to the subclass' `initialize` method
76
+ def self.start(**params)
77
+ new(**params)
78
+ end
79
+
80
+ protected
81
+
82
+ # Get the next event from the queue
83
+ # @return [Plumbing::Event]
84
+ # Subclasses should implement this method
85
+ def get_next_event
86
+ raise PipeIsBlocked
87
+ end
88
+
89
+ # Start the event loop
90
+ # This loop keeps running until `shutdown` is called
91
+ # Some subclasses may need to replace this method to deal with their own specific implementations
92
+ # @param initial_event [Plumbing::Event] optional; the first event in the queue
93
+ def start_run_loop initial_event = nil
94
+ loop do
95
+ event = initial_event || get_next_event
96
+ break if event == :shutdown
97
+ dispatch event
98
+ initial_event = nil
99
+ end
100
+ end
101
+
102
+ # Dispatch an event to all observers
103
+ # @param event [Plumbing::Event]
104
+ # Enumerates all observers and `calls` them with this event
105
+ # Discards any errors raised by the observer so that all observers will be successfully notified
106
+ def dispatch event
107
+ @observers.collect do |observer|
108
+ observer.call event
109
+ rescue => ex
110
+ puts ex
111
+ ex
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,59 @@
1
+ module Plumbing
2
+ # A chain of operations that are executed in sequence
3
+ class Chain
4
+ def call params
5
+ self.class._call params, self
6
+ end
7
+
8
+ class << self
9
+ def pre_condition name, &validator
10
+ pre_conditions[name.to_sym] = validator
11
+ end
12
+
13
+ def perform method, &implementation
14
+ implementation ||= ->(params, instance) { instance.send(method, params) }
15
+ operations << implementation
16
+ end
17
+
18
+ def post_condition name, &validator
19
+ post_conditions[name.to_sym] = validator
20
+ end
21
+
22
+ def _call params, instance
23
+ validate_preconditions_for params
24
+ result = params
25
+ operations.each do |operation|
26
+ result = operation.call(result, instance)
27
+ end
28
+ validate_postconditions_for result
29
+ result
30
+ end
31
+
32
+ private
33
+
34
+ def operations
35
+ @operations ||= []
36
+ end
37
+
38
+ def pre_conditions
39
+ @pre_conditions ||= {}
40
+ end
41
+
42
+ def post_conditions
43
+ @post_conditions ||= {}
44
+ end
45
+
46
+ def validate_preconditions_for input
47
+ failed_preconditions = pre_conditions.select { |name, validator| !validator.call(input) }
48
+ raise PreConditionError, failed_preconditions.keys.join(", ") if failed_preconditions.any?
49
+ input
50
+ end
51
+
52
+ def validate_postconditions_for output
53
+ failed_postconditions = post_conditions.select { |name, validator| !validator.call(output) }
54
+ raise PostConditionError, failed_postconditions.keys.join(", ") if failed_postconditions.any?
55
+ output
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,72 @@
1
+ require "dry/types"
2
+ require_relative "../blocked_pipe"
3
+
4
+ module Plumbing
5
+ module Concurrent
6
+ class Pipe < Plumbing::BlockedPipe
7
+ module Types
8
+ include Dry::Types()
9
+ # Observers must be Ractors
10
+ Observer = Instance(Ractor)
11
+ end
12
+
13
+ def initialize
14
+ super
15
+ @queue = Ractor.new(self) do |instance|
16
+ while (message = Ractor.receive) != :shutdown
17
+ case message.first
18
+ when :add_observer then instance.send :add_observing_ractor, message.last
19
+ when :is_observer? then Ractor.yield(instance.send(:is_observing_ractor?, message.last))
20
+ when :remove_observer then instance.send :remove_observing_ractor, message.last
21
+ else instance.send :dispatch, message.last
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ def add_observer ractor = nil, &block
28
+ Plumbing::Concurrent::Pipe::Types::Observer[ractor].tap do |observer|
29
+ @queue << [:add_observer, observer]
30
+ end
31
+ end
32
+
33
+ def remove_observer ractor = nil, &block
34
+ @queue << [:remove_observer, ractor]
35
+ end
36
+
37
+ def is_observer? ractor
38
+ @queue << [:is_observer?, ractor]
39
+ @queue.take
40
+ end
41
+
42
+ def << event
43
+ @queue << [:dispatch, Plumbing::BlockedPipe::Types::Event[event]]
44
+ end
45
+
46
+ def shutdown
47
+ @queue << :shutdown
48
+ super
49
+ end
50
+
51
+ private
52
+
53
+ def dispatch event
54
+ @observers.each do |observer|
55
+ observer << event
56
+ end
57
+ end
58
+
59
+ def add_observing_ractor observer
60
+ @observers << observer
61
+ end
62
+
63
+ def is_observing_ractor? observer
64
+ @observers.include? observer
65
+ end
66
+
67
+ def remove_observing_ractor observer
68
+ @observers.delete observer
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,19 @@
1
+ module Plumbing
2
+ # Base error class for all Plumbing errors
3
+ class Error < StandardError; end
4
+
5
+ # Error raised because a pre-condition failed
6
+ class PreConditionError < Error; end
7
+
8
+ # Error raised because a post-condition failed
9
+ class PostConditionError < Error; end
10
+
11
+ # Error raised because an invalid [Event] object was pushed into the pipe
12
+ InvalidEvent = Dry::Types::ConstraintError
13
+
14
+ # Error raised because an invalid observer was registered
15
+ InvalidObserver = Dry::Types::ConstraintError
16
+
17
+ # Error raised because a BlockedPipe was used instead of an actual implementation of a Pipe
18
+ class PipeIsBlocked < Plumbing::Error; end
19
+ end
@@ -2,15 +2,16 @@ require "dry/types"
2
2
  require "dry/struct"
3
3
 
4
4
  module Plumbing
5
+ # An immutable data structure representing an Event
5
6
  class Event < Dry::Struct
6
7
  module Types
7
8
  include Dry::Types()
8
9
  SequenceNumber = Strict::Integer
9
- EventType = Strict::String
10
- EventData = Strict::Hash.default({}.freeze)
10
+ Type = Strict::String
11
+ Data = Strict::Hash.map(Coercible::Symbol, Nominal::Any).default({}.freeze)
11
12
  end
12
13
 
13
- attribute :type, Types::EventType
14
- attribute :data, Types::EventData
14
+ attribute :type, Types::Type
15
+ attribute :data, Types::Data
15
16
  end
16
17
  end
@@ -1,14 +1,19 @@
1
1
  require "dry/types"
2
- require_relative "pipe"
2
+ require_relative "blocked_pipe"
3
3
 
4
4
  module Plumbing
5
- class Filter < Pipe
5
+ # A pipe that filters events from a source pipe
6
+ class Filter < BlockedPipe
6
7
  module Types
7
8
  include Dry::Types()
8
- Source = Instance(Plumbing::Pipe)
9
- EventTypes = Array.of(Plumbing::Event::Types::EventType)
9
+ Source = Instance(Plumbing::BlockedPipe)
10
+ EventTypes = Array.of(Plumbing::Event::Types::Type)
10
11
  end
11
12
 
13
+ # Chain this pipe to the source pipe
14
+ # @param source [Plumbing::BlockedPipe]
15
+ # @param accepts [Array[String]] event types that this filter will allow through (or pass [] to allow all)
16
+ # @param rejects [Array[String]] event types that this filter will not allow through
12
17
  def initialize source:, accepts: [], rejects: []
13
18
  super()
14
19
  @accepted_event_types = Types::EventTypes[accepts]
@@ -23,7 +28,7 @@ module Plumbing
23
28
  def filter_and_republish event
24
29
  return nil if @accepted_event_types.any? && !@accepted_event_types.include?(event.type)
25
30
  return nil if @rejected_event_types.include? event.type
26
- self << event
31
+ dispatch event
27
32
  end
28
33
  end
29
34
  end
data/lib/plumbing/pipe.rb CHANGED
@@ -1,31 +1,12 @@
1
- require "dry/types"
2
- require_relative "event"
1
+ require_relative "blocked_pipe"
3
2
 
4
3
  module Plumbing
5
- InvalidEvent = Dry::Types::ConstraintError
6
- InvalidObserver = Dry::Types::ConstraintError
7
-
8
- class Pipe
9
- module Types
10
- include Dry::Types()
11
- Event = Instance(Plumbing::Event)
12
- Observer = Interface(:call)
13
- end
14
-
4
+ # An implementation of a pipe that uses Fibers
5
+ class Pipe < BlockedPipe
15
6
  def initialize
16
- @observers = []
17
- @fiber = Fiber.new do |event|
18
- loop do
19
- break if event == :shutdown
20
- @observers.each do |observer|
21
- observer.call event
22
- rescue
23
- nil
24
- end
25
- event = Fiber.yield
26
- end
27
- # clean up and release any observers, just in case
28
- @observers = []
7
+ super
8
+ @fiber = Fiber.new do |initial_event|
9
+ start_run_loop initial_event
29
10
  end
30
11
  end
31
12
 
@@ -33,23 +14,15 @@ module Plumbing
33
14
  @fiber.resume Types::Event[event]
34
15
  end
35
16
 
36
- def add_observer callable = nil, &block
37
- callable ||= block.to_proc
38
- Types::Observer[callable].tap do |observer|
39
- @observers << observer
40
- end
41
- end
42
-
43
- def remove_observer callable
44
- @observers.delete callable
45
- end
46
-
47
17
  def shutdown
18
+ super
48
19
  @fiber.resume :shutdown
49
20
  end
50
21
 
51
- def self.start(**params)
52
- new(**params)
22
+ protected
23
+
24
+ def get_next_event
25
+ Fiber.yield
53
26
  end
54
27
  end
55
28
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Plumbing
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
data/lib/plumbing.rb CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  require "dry/types"
4
4
  module Plumbing
5
- class Error < StandardError; end
6
-
7
5
  require_relative "plumbing/version"
8
6
 
7
+ require_relative "plumbing/error"
9
8
  require_relative "plumbing/event"
10
9
  require_relative "plumbing/pipe"
10
+ require_relative "plumbing/chain"
11
11
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard-procedure-plumbing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rahoul Baruah
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-14 00:00:00.000000000 Z
11
+ date: 2024-08-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-types
@@ -46,6 +46,8 @@ extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
48
  - ".rspec"
49
+ - ".rubocop.yml"
50
+ - ".solargraph.yml"
49
51
  - ".standard.yml"
50
52
  - CHANGELOG.md
51
53
  - CODE_OF_CONDUCT.md
@@ -53,8 +55,12 @@ files:
53
55
  - LICENSE
54
56
  - README.md
55
57
  - Rakefile
58
+ - checksums/standard-procedure-plumbing-0.1.1.gem.sha512
56
59
  - lib/plumbing.rb
57
- - lib/plumbing/.DS_Store
60
+ - lib/plumbing/blocked_pipe.rb
61
+ - lib/plumbing/chain.rb
62
+ - lib/plumbing/concurrent/pipe.rb
63
+ - lib/plumbing/error.rb
58
64
  - lib/plumbing/event.rb
59
65
  - lib/plumbing/filter.rb
60
66
  - lib/plumbing/pipe.rb
@@ -82,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
82
88
  - !ruby/object:Gem::Version
83
89
  version: '0'
84
90
  requirements: []
85
- rubygems_version: 3.5.6
91
+ rubygems_version: 3.5.9
86
92
  signing_key:
87
93
  specification_version: 4
88
94
  summary: An event pipeline
Binary file