standard-procedure-plumbing 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +24 -0
- data/.solargraph.yml +32 -0
- data/.standard.yml +9 -3
- data/CHANGELOG.md +12 -1
- data/README.md +138 -25
- data/checksums/standard-procedure-plumbing-0.1.1.gem.sha512 +1 -0
- data/checksums/standard-procedure-plumbing-0.1.2.gem.sha512 +1 -0
- data/lib/plumbing/blocked_pipe.rb +105 -0
- data/lib/plumbing/chain.rb +59 -0
- data/lib/plumbing/error.rb +19 -0
- data/lib/plumbing/event.rb +2 -13
- data/lib/plumbing/filter.rb +15 -12
- data/lib/plumbing/pipe.rb +13 -39
- data/lib/plumbing/version.rb +1 -1
- data/lib/plumbing.rb +2 -3
- metadata +15 -38
- data/Guardfile +0 -32
- data/lib/plumbing/.DS_Store +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0bfd8678cd958f92a30d6c1879e664ac790637b400a2fd73302a7ed60a3b450
|
4
|
+
data.tar.gz: f06824216f1d2da14fa645b0f246106180e6e8e69f8fa7d5526e8a343ac77923
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac2e9872aba1ecb6c4f2831ee6da3689afdcf1162c5bc0b65eaa2298ae1a00f4d698fb7909c34e10daa6b14cd10bcd8ab54cd92839fab7a1107cd93de831c2c5
|
7
|
+
data.tar.gz: 64f585e329a112504b692b788d6b6c9562bc188eb9c621a33001f2aed69d92c6260909c8a3df174436bfeda345791b7ca370afb09fdad2d281d466efcb0784fc
|
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
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
|
-
## [
|
1
|
+
## [0.1.2] - 2024-08-14
|
2
|
+
|
3
|
+
- Removed dependencies
|
4
|
+
- Removed Ractor-based concurrent pipe (as I don't trust it yet)
|
5
|
+
|
6
|
+
## [0.1.1] - 2024-08-14
|
7
|
+
|
8
|
+
- Tidied up the code
|
9
|
+
- Added Plumbing::Chain
|
2
10
|
|
3
11
|
## [0.1.0] - 2024-04-13
|
4
12
|
|
5
13
|
- Initial release
|
14
|
+
|
15
|
+
## [Unreleased]
|
16
|
+
|
data/README.md
CHANGED
@@ -1,50 +1,163 @@
|
|
1
1
|
# Plumbing
|
2
2
|
|
3
|
-
|
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
|
-
|
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
|
-
|
9
|
+
### Usage
|
10
10
|
|
11
|
-
|
11
|
+
A simple observer:
|
12
|
+
```ruby
|
13
|
+
require "plumbing"
|
12
14
|
|
13
|
-
|
15
|
+
@source = Plumbing::Pipe.start
|
14
16
|
|
15
|
-
|
17
|
+
@observer = @source.add_observer do |event|
|
18
|
+
puts event.type
|
19
|
+
end
|
16
20
|
|
17
|
-
|
21
|
+
@source.notify "something_happened", message: "But what was it?"
|
22
|
+
# => "something_happened"
|
23
|
+
```
|
18
24
|
|
19
|
-
|
25
|
+
Simple filtering:
|
26
|
+
```ruby
|
27
|
+
require "plumbing"
|
20
28
|
|
29
|
+
@source = Plumbing::Pipe.start
|
21
30
|
|
22
|
-
|
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
|
-
|
33
|
+
@observer = @filter.add_observer do |event|
|
34
|
+
puts event.type
|
35
|
+
end
|
27
36
|
|
28
|
-
|
37
|
+
@source.notify "important", message: "ALERT! ALERT!"
|
38
|
+
# => "important"
|
29
39
|
|
30
|
-
|
40
|
+
@source.notify "unimportant", message: "Nothing to see here"
|
41
|
+
# => <no output>
|
42
|
+
```
|
31
43
|
|
32
|
-
|
44
|
+
Custom filtering:
|
45
|
+
```ruby
|
46
|
+
require "plumbing"
|
33
47
|
|
34
|
-
|
48
|
+
class EveryThirdEvent < Plumbing::CustomFilter
|
49
|
+
def initialize source:
|
50
|
+
super source: source
|
51
|
+
@events = []
|
52
|
+
end
|
35
53
|
|
36
|
-
|
37
|
-
@
|
38
|
-
|
39
|
-
|
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
|
+
## Plumbing::Chain - a chain of operations that occur in sequence
|
99
|
+
|
100
|
+
Define a sequence of operations that proceed in order, passing their output from one operation as the input to another.
|
101
|
+
|
102
|
+
You can define pre-conditions (which validate the inputs supplied) or post-conditions (which validate the output).
|
103
|
+
|
104
|
+
### Usage:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
require "plumbing"
|
108
|
+
class BuildSequence < Plumbing::Chain
|
109
|
+
pre_condition :must_be_an_array do |input|
|
110
|
+
input.is_a? Array
|
111
|
+
end
|
112
|
+
|
113
|
+
post_condition :must_have_three_elements do |output|
|
114
|
+
# yes, this is a stupid post-condition but it shows how you can ensure your outputs are valid
|
115
|
+
output.length == 3
|
116
|
+
end
|
117
|
+
|
118
|
+
perform :add_first
|
119
|
+
perform :add_second
|
120
|
+
perform :add_third
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def add_first input
|
125
|
+
input << "first"
|
126
|
+
end
|
127
|
+
|
128
|
+
def add_second input
|
129
|
+
input << "second"
|
130
|
+
end
|
131
|
+
|
132
|
+
def add_third input
|
133
|
+
input << "third"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
BuildSequence.new.call []
|
138
|
+
# => ["first", "second", "third"]
|
139
|
+
|
140
|
+
BuildSequence.new.call 1
|
141
|
+
# => Plumbing::PreconditionError("must_be_an_array")
|
142
|
+
|
143
|
+
BuildSequence.new.call ["extra element"]
|
144
|
+
# => Plumbing::PostconditionError("must_have_three_elements")
|
145
|
+
```
|
146
|
+
|
147
|
+
## Installation
|
148
|
+
|
149
|
+
Install the gem and add to the application's Gemfile by executing:
|
150
|
+
|
151
|
+
```sh
|
152
|
+
bundle add standard-procedure-plumbing
|
153
|
+
```
|
154
|
+
|
155
|
+
Then:
|
41
156
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
# => "important"
|
157
|
+
```ruby
|
158
|
+
require 'plumbing'
|
159
|
+
```
|
46
160
|
|
47
|
-
@filter.remove_observer @observer
|
48
161
|
|
49
162
|
## Development
|
50
163
|
|
@@ -0,0 +1 @@
|
|
1
|
+
30cc1d4b434b322269e0100acd3a42088d93359e0d34b7a947ac272c55b2e82788ab6f0926f2cc95552d746b71791470412a7cdfa34605d4b20309df0de97a33
|
@@ -0,0 +1 @@
|
|
1
|
+
18d6d3138d2879c1672ab60b1534a6bc382e99a67adb4f8eff9c60ffb96b78b148bffac54ff1f7e78a2da1fc36d000041806b3a73993416c62279ef3ca09beba
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require_relative "error"
|
2
|
+
require_relative "event"
|
3
|
+
|
4
|
+
module Plumbing
|
5
|
+
# The "plumbing" for a Pipe.
|
6
|
+
# This class is "blocked", in that it won't push any events to registered observers.
|
7
|
+
# Instead, this is the basis for subclasses like [Plumbing::Pipe] which actually allow events to flow through them.
|
8
|
+
class BlockedPipe
|
9
|
+
# Create a new BlockedPipe
|
10
|
+
# Subclasses should call `super()` to ensure the pipe is initialised corrected
|
11
|
+
def initialize
|
12
|
+
@observers = []
|
13
|
+
end
|
14
|
+
|
15
|
+
# Push an event into the pipe
|
16
|
+
# @param event [Plumbing::Event] the event to push into the pipe
|
17
|
+
# Subclasses should implement this method
|
18
|
+
def << event
|
19
|
+
raise Plumbing::PipeIsBlocked
|
20
|
+
end
|
21
|
+
|
22
|
+
# A shortcut to creating and then pushing an event
|
23
|
+
# @param event_type [String] representing the type of event this is
|
24
|
+
# @param data [Hash] representing the event-specific data to be passed to the observers
|
25
|
+
def notify event_type, data = nil
|
26
|
+
Event.new(type: event_type, data: data).tap do |event|
|
27
|
+
self << event
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Add an observer to this pipe
|
32
|
+
# @param callable [Proc] (optional)
|
33
|
+
# @param &block [Block] (optional)
|
34
|
+
# @return an object representing this observer (dependent upon the implementation of the pipe itself)
|
35
|
+
# Either a `callable` or a `block` must be supplied. If the latter, it is converted to a [Proc]
|
36
|
+
def add_observer observer = nil, &block
|
37
|
+
observer ||= block.to_proc
|
38
|
+
raise Plumbing::InvalidObserver.new "observer_does_not_respond_to_call" unless observer.respond_to? :call
|
39
|
+
@observers << observer
|
40
|
+
end
|
41
|
+
|
42
|
+
# Remove an observer from this pipe
|
43
|
+
# @param observer
|
44
|
+
# This removes the given observer from this pipe. The observer should have previously been returned by #add_observer and is implementation-specific
|
45
|
+
def remove_observer observer
|
46
|
+
@observers.delete observer
|
47
|
+
end
|
48
|
+
|
49
|
+
# Test whether the given observer is observing this pipe
|
50
|
+
# @param observer
|
51
|
+
# @return [boolean]
|
52
|
+
def is_observer? observer
|
53
|
+
@observers.include? observer
|
54
|
+
end
|
55
|
+
|
56
|
+
# Close this pipe and perform any cleanup.
|
57
|
+
# Subclasses should override this to perform their own shutdown routines and call `super` to ensure everything is tidied up
|
58
|
+
def shutdown
|
59
|
+
# clean up and release any observers, just in case
|
60
|
+
@observers = []
|
61
|
+
end
|
62
|
+
|
63
|
+
# Start this pipe
|
64
|
+
# Subclasses may override this method to add any implementation specific details.
|
65
|
+
# By default any supplied parameters are called to the subclass' `initialize` method
|
66
|
+
def self.start(**params)
|
67
|
+
new(**params)
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
# Get the next event from the queue
|
73
|
+
# @return [Plumbing::Event]
|
74
|
+
# Subclasses should implement this method
|
75
|
+
def get_next_event
|
76
|
+
raise Plumbing::PipeIsBlocked
|
77
|
+
end
|
78
|
+
|
79
|
+
# Start the event loop
|
80
|
+
# This loop keeps running until `shutdown` is called
|
81
|
+
# Some subclasses may need to replace this method to deal with their own specific implementations
|
82
|
+
# @param initial_event [Plumbing::Event] optional; the first event in the queue
|
83
|
+
def start_run_loop initial_event = nil
|
84
|
+
loop do
|
85
|
+
event = initial_event || get_next_event
|
86
|
+
break if event == :shutdown
|
87
|
+
dispatch event
|
88
|
+
initial_event = nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Dispatch an event to all observers
|
93
|
+
# @param event [Plumbing::Event]
|
94
|
+
# Enumerates all observers and `calls` them with this event
|
95
|
+
# Discards any errors raised by the observer so that all observers will be successfully notified
|
96
|
+
def dispatch event
|
97
|
+
@observers.collect do |observer|
|
98
|
+
observer.call event
|
99
|
+
rescue => ex
|
100
|
+
puts ex
|
101
|
+
ex
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
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,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
|
+
class InvalidEvent < Error; end
|
13
|
+
|
14
|
+
# Error raised because an invalid observer was registered
|
15
|
+
class InvalidObserver < Error; end
|
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
|
data/lib/plumbing/event.rb
CHANGED
@@ -1,16 +1,5 @@
|
|
1
|
-
require "dry/types"
|
2
|
-
require "dry/struct"
|
3
|
-
|
4
1
|
module Plumbing
|
5
|
-
|
6
|
-
|
7
|
-
include Dry::Types()
|
8
|
-
SequenceNumber = Strict::Integer
|
9
|
-
EventType = Strict::String
|
10
|
-
EventData = Strict::Hash.default({}.freeze)
|
11
|
-
end
|
12
|
-
|
13
|
-
attribute :type, Types::EventType
|
14
|
-
attribute :data, Types::EventData
|
2
|
+
# An immutable data structure representing an Event
|
3
|
+
Event = Data.define :type, :data do
|
15
4
|
end
|
16
5
|
end
|
data/lib/plumbing/filter.rb
CHANGED
@@ -1,19 +1,21 @@
|
|
1
|
-
|
2
|
-
require_relative "pipe"
|
1
|
+
require_relative "blocked_pipe"
|
3
2
|
|
4
3
|
module Plumbing
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
Source = Instance(Plumbing::Pipe)
|
9
|
-
EventTypes = Array.of(Plumbing::Event::Types::EventType)
|
10
|
-
end
|
4
|
+
# A pipe that filters events from a source pipe
|
5
|
+
class Filter < BlockedPipe
|
6
|
+
class InvalidFilter < Error; end
|
11
7
|
|
8
|
+
# Chain this pipe to the source pipe
|
9
|
+
# @param source [Plumbing::BlockedPipe]
|
10
|
+
# @param accepts [Array[String]] event types that this filter will allow through (or pass [] to allow all)
|
11
|
+
# @param rejects [Array[String]] event types that this filter will not allow through
|
12
12
|
def initialize source:, accepts: [], rejects: []
|
13
13
|
super()
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
raise InvalidFilter.new "source must be a Plumbing::BlockedPipe descendant" unless source.is_a? Plumbing::BlockedPipe
|
15
|
+
raise InvalidFilter.new "accepts and rejects must be arrays" unless accepts.is_a?(Array) && rejects.is_a?(Array)
|
16
|
+
@accepted_event_types = accepts
|
17
|
+
@rejected_event_types = rejects
|
18
|
+
source.add_observer do |event|
|
17
19
|
filter_and_republish(event)
|
18
20
|
end
|
19
21
|
end
|
@@ -21,9 +23,10 @@ module Plumbing
|
|
21
23
|
private
|
22
24
|
|
23
25
|
def filter_and_republish event
|
26
|
+
raise InvalidEvent.new "event is not a Plumbing::Event" unless event.is_a? Plumbing::Event
|
24
27
|
return nil if @accepted_event_types.any? && !@accepted_event_types.include?(event.type)
|
25
28
|
return nil if @rejected_event_types.include? event.type
|
26
|
-
|
29
|
+
dispatch event
|
27
30
|
end
|
28
31
|
end
|
29
32
|
end
|
data/lib/plumbing/pipe.rb
CHANGED
@@ -1,55 +1,29 @@
|
|
1
|
-
|
2
|
-
require_relative "event"
|
1
|
+
require_relative "blocked_pipe"
|
3
2
|
|
4
3
|
module Plumbing
|
5
|
-
|
6
|
-
|
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
|
-
|
17
|
-
@fiber = Fiber.new do |
|
18
|
-
|
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
|
|
32
13
|
def << event
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
14
|
+
raise Plumbing::InvalidEvent.new "event is not a Plumbing::Event" unless event.is_a? Plumbing::Event
|
15
|
+
@fiber.resume event
|
45
16
|
end
|
46
17
|
|
47
18
|
def shutdown
|
19
|
+
super
|
48
20
|
@fiber.resume :shutdown
|
49
21
|
end
|
50
22
|
|
51
|
-
|
52
|
-
|
23
|
+
protected
|
24
|
+
|
25
|
+
def get_next_event
|
26
|
+
Fiber.yield
|
53
27
|
end
|
54
28
|
end
|
55
29
|
end
|
data/lib/plumbing/version.rb
CHANGED
data/lib/plumbing.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "dry/types"
|
4
3
|
module Plumbing
|
5
|
-
class Error < StandardError; end
|
6
|
-
|
7
4
|
require_relative "plumbing/version"
|
8
5
|
|
6
|
+
require_relative "plumbing/error"
|
9
7
|
require_relative "plumbing/event"
|
10
8
|
require_relative "plumbing/pipe"
|
9
|
+
require_relative "plumbing/chain"
|
11
10
|
end
|
metadata
CHANGED
@@ -1,44 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: standard-procedure-plumbing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
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-
|
12
|
-
dependencies:
|
13
|
-
|
14
|
-
name: dry-types
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: dry-struct
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
|
-
description: An event pipeline
|
11
|
+
date: 2024-08-14 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A composable event pipeline and sequential pipelines of operations
|
42
14
|
email:
|
43
15
|
- rahoulb@echodek.co
|
44
16
|
executables: []
|
@@ -46,15 +18,20 @@ extensions: []
|
|
46
18
|
extra_rdoc_files: []
|
47
19
|
files:
|
48
20
|
- ".rspec"
|
21
|
+
- ".rubocop.yml"
|
22
|
+
- ".solargraph.yml"
|
49
23
|
- ".standard.yml"
|
50
24
|
- CHANGELOG.md
|
51
25
|
- CODE_OF_CONDUCT.md
|
52
|
-
- Guardfile
|
53
26
|
- LICENSE
|
54
27
|
- README.md
|
55
28
|
- Rakefile
|
29
|
+
- checksums/standard-procedure-plumbing-0.1.1.gem.sha512
|
30
|
+
- checksums/standard-procedure-plumbing-0.1.2.gem.sha512
|
56
31
|
- lib/plumbing.rb
|
57
|
-
- lib/plumbing
|
32
|
+
- lib/plumbing/blocked_pipe.rb
|
33
|
+
- lib/plumbing/chain.rb
|
34
|
+
- lib/plumbing/error.rb
|
58
35
|
- lib/plumbing/event.rb
|
59
36
|
- lib/plumbing/filter.rb
|
60
37
|
- lib/plumbing/pipe.rb
|
@@ -65,8 +42,8 @@ licenses: []
|
|
65
42
|
metadata:
|
66
43
|
allowed_push_host: https://rubygems.org
|
67
44
|
homepage_uri: https://theartandscienceofruby.com
|
68
|
-
source_code_uri: https://github.com
|
69
|
-
changelog_uri: https://github.com
|
45
|
+
source_code_uri: https://github.com/standard-procedure/plumbing
|
46
|
+
changelog_uri: https://github.com/standard-procedure/plumbing/blob/main/CHANGELOG.md
|
70
47
|
post_install_message:
|
71
48
|
rdoc_options: []
|
72
49
|
require_paths:
|
@@ -82,8 +59,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
59
|
- !ruby/object:Gem::Version
|
83
60
|
version: '0'
|
84
61
|
requirements: []
|
85
|
-
rubygems_version: 3.5.
|
62
|
+
rubygems_version: 3.5.9
|
86
63
|
signing_key:
|
87
64
|
specification_version: 4
|
88
|
-
summary:
|
65
|
+
summary: Plumbing - various pipelines for your ruby application
|
89
66
|
test_files: []
|
data/Guardfile
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
ignore(/bin/, /log/, /public/, /storage/, /tmp/)
|
2
|
-
|
3
|
-
group :formatting do
|
4
|
-
guard :standardrb, fix: true, all_on_start: true, progress: true do
|
5
|
-
watch(/.+\.rb$/)
|
6
|
-
watch(/.+\.thor$/)
|
7
|
-
watch(/.+\.rake$/)
|
8
|
-
watch(/Guardfile$/)
|
9
|
-
watch(/Rakefile$/)
|
10
|
-
watch(/Gemfile$/)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
group :development do
|
15
|
-
guard :rspec, cmd: "bundle exec rspec" do
|
16
|
-
watch("spec/.+_helper.rb") { "spec" }
|
17
|
-
watch(%r{^spec/.+_spec\.rb$})
|
18
|
-
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
19
|
-
end
|
20
|
-
|
21
|
-
guard :bundler do
|
22
|
-
require "guard/bundler"
|
23
|
-
require "guard/bundler/verify"
|
24
|
-
helper = Guard::Bundler::Verify.new
|
25
|
-
|
26
|
-
files = ["Gemfile"]
|
27
|
-
files += Dir["*.gemspec"] if files.any? { |f| helper.uses_gemspec?(f) }
|
28
|
-
|
29
|
-
# Assume files are symlinked from somewhere
|
30
|
-
files.each { |file| watch(helper.real_path(file)) }
|
31
|
-
end
|
32
|
-
end
|
data/lib/plumbing/.DS_Store
DELETED
Binary file
|