meddleware 0.2.0 → 0.4.0

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: 3bfe09bf6e8d403864543e0c74fea482608b78f31c83b32a6cca71ad1d8a3973
4
- data.tar.gz: 93c888f10763f70779e9a834249e475f6eab18db3f2eacf9bd1442cb87f155d4
3
+ metadata.gz: 0c9a407cd470f194fda6286e743513706d094408be53bd75a31ebd14eef97fa2
4
+ data.tar.gz: 57243177c0047d94f58ca39eb3f9ceb6b9cdcb37aa204be3f2c511a934905e68
5
5
  SHA512:
6
- metadata.gz: abaee8a6faafe5ed08d04d9a5166efedbf3e9cc51de2fc3bd1608ba57496e38676860b18bc44ab4a8dfc4030709fc287c84cfaa5001be7a391be7f5f09f4b736
7
- data.tar.gz: 5222b652ae8a45723d4b20798d4c852d7b8a0e24a1449531c0c8fc96a3c3fa3a265e4bbe8bec3e81f1958a77112fd189f8a75b9bce2f66f3c4c8e5ae8770dac3
6
+ metadata.gz: 52816fdea29cea1af9a32ce346079686fe75ced2ac34d4bf972afe174e7d74a7bd62cd1e0bac6aab6433eb3373c66772d373ed17778ee251857625e1d7368fde
7
+ data.tar.gz: e4eb77b1748be5c025b3be3bf3c049ed167e7cd7624deb1ef980542667b4eb556a60c40ee911ac805f2aaa7de01a0c199129f45edc3b6701dd589bb23130434f
data/CHANGELOG.md ADDED
@@ -0,0 +1,36 @@
1
+ ### v0.4.0 (2024-04-05)
2
+ - drop ruby 2.7
3
+ - gem constraints
4
+ - simplify codecov
5
+ - drop rake
6
+ - test coverage
7
+
8
+ ### v0.3.0 (2022-10-11)
9
+ - extendable
10
+ - drop ruby 2.5 (#4)
11
+ - remove rake
12
+
13
+ ### v0.2.0 (2021-09-14)
14
+ - ruby 3 compatibility
15
+ - usage spec
16
+ - fix tests
17
+ - simplify: move dedup functionality out of build method
18
+ - array support
19
+
20
+ ### v0.1.0 (2021-04-05)
21
+ - spec examples
22
+ - Update README.md
23
+ - test coverage
24
+ - bugs and tests
25
+ - Meddleware.replace
26
+ - proc support
27
+ - validation and currying
28
+ - test coverage
29
+ - support instances and procs
30
+ - gemfile cleanup
31
+ - fix github actions
32
+ - add specs, clean up
33
+ - thanks
34
+
35
+ ### v0.0.1 (2021-03-31)
36
+ - skeleton
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
data/Gemfile.lock ADDED
@@ -0,0 +1,42 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ meddleware (0.3.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ byebug (11.1.3)
10
+ diff-lcs (1.5.1)
11
+ docile (1.4.0)
12
+ rspec (3.13.0)
13
+ rspec-core (~> 3.13.0)
14
+ rspec-expectations (~> 3.13.0)
15
+ rspec-mocks (~> 3.13.0)
16
+ rspec-core (3.13.0)
17
+ rspec-support (~> 3.13.0)
18
+ rspec-expectations (3.13.0)
19
+ diff-lcs (>= 1.2.0, < 2.0)
20
+ rspec-support (~> 3.13.0)
21
+ rspec-mocks (3.13.0)
22
+ diff-lcs (>= 1.2.0, < 2.0)
23
+ rspec-support (~> 3.13.0)
24
+ rspec-support (3.13.1)
25
+ simplecov (0.22.0)
26
+ docile (~> 1.1)
27
+ simplecov-html (~> 0.11)
28
+ simplecov_json_formatter (~> 0.1)
29
+ simplecov-html (0.12.3)
30
+ simplecov_json_formatter (0.1.4)
31
+
32
+ PLATFORMS
33
+ ruby
34
+
35
+ DEPENDENCIES
36
+ byebug (>= 11)
37
+ meddleware!
38
+ rspec (>= 3.13)
39
+ simplecov (>= 0.22)
40
+
41
+ BUNDLED WITH
42
+ 2.5.7
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Daniel Pepper
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,145 @@
1
+ Meddleware
2
+ ======
3
+ ![Gem](https://img.shields.io/gem/dt/meddleware?style=plastic)
4
+ [![codecov](https://codecov.io/gh/dpep/meddleware_rb/branch/main/graph/badge.svg)](https://codecov.io/gh/dpep/meddleware_rb)
5
+
6
+
7
+ A middleware framework to make meddling easy. Middleware is a popular pattern from Rack and Rails, which provides callers a way to execute code before and after yours. This gives callers the ability to modify parameters or results, conditionally skip execution, and log activity without the need to monkey patch or subclass.
8
+
9
+
10
+ ```ruby
11
+ require 'meddleware'
12
+
13
+ # lib/mywidget.rb
14
+ class MyWidget
15
+ extend Meddleware
16
+
17
+ def do_the_thing
18
+ # invoke middleware chain
19
+ middleware.call do
20
+ # do your thing
21
+ ...
22
+ end
23
+ end
24
+ end
25
+
26
+ # config/initializers/mywidget.rb
27
+ MyWidget.middleware do
28
+ # add a logger
29
+ use { puts "before the thing" }
30
+
31
+ # add another middleware
32
+ use MyMiddleware
33
+ end
34
+
35
+
36
+ # use it from whereever
37
+ MyWidget.new.do_the_thing
38
+ ```
39
+
40
+
41
+ ## Usage
42
+ Extend your class with Meddleware to add a `middleware` method. Or use `include` to give each instance its own, individual middleware.
43
+
44
+ ```ruby
45
+ class MyWidget
46
+ extend Meddleware
47
+ end
48
+
49
+ MyWidget.middleware
50
+ ```
51
+
52
+ Then wrap your class's functionality so it will get executed along with the all the registered middleware.
53
+
54
+ ```ruby
55
+ class MyWidget
56
+ extend Meddleware
57
+
58
+ def do_the_thing
59
+ # invoke middleware chain
60
+ middleware.call(*args) do
61
+ # do your thing
62
+ ...
63
+ end
64
+ end
65
+ end
66
+ ```
67
+
68
+
69
+ See [documentation](https://github.com/dpep/meddleware_rb/wiki/DSL) for full DSL.
70
+
71
+
72
+ ----
73
+ ## Full Example
74
+ ```ruby
75
+ # lib/mylist.rb
76
+ module MyList
77
+ extend Meddleware
78
+
79
+ # generate an array from 1 to n
80
+ def self.generate(n)
81
+ # invoke middleware chain
82
+ middleware.call(n) do |n|
83
+ # do the actual work of generating your results
84
+ (1..n).to_a
85
+ end
86
+ end
87
+ end
88
+
89
+ # app/initializers/mylist.rb
90
+ class OneExtra
91
+ def call(n)
92
+ # adds one to the argument being passed in
93
+ yield(n + 1)
94
+ end
95
+ end
96
+
97
+ class Doubler
98
+ def call(*)
99
+ # modifies the results by doubles each value
100
+ yield.map {|x| x * 2 }
101
+ end
102
+ end
103
+
104
+ MyList.middleware do
105
+ use OneExtra
106
+ use Doubler
107
+
108
+ # loggers
109
+ prepend {|x| puts "n starts as #{x}" }
110
+ append {|x| puts "n ends as #{x}" }
111
+ end
112
+
113
+
114
+ # use it
115
+ > MyList.generate(2)
116
+
117
+ # would normally output [ 1, 2 ]
118
+ # but with middleware:
119
+
120
+ n starts as 2
121
+ n ends as 3
122
+ => [2, 4, 6]
123
+ ```
124
+
125
+ ----
126
+ ## Contributing
127
+
128
+ Yes please :)
129
+
130
+ 1. Fork it
131
+ 1. Create your feature branch (`git checkout -b my-feature`)
132
+ 1. Ensure the tests pass (`bundle exec rspec`)
133
+ 1. Commit your changes (`git commit -am 'awesome new feature'`)
134
+ 1. Push your branch (`git push origin my-feature`)
135
+ 1. Create a Pull Request
136
+
137
+
138
+ ----
139
+ ### Inspired by / Thanks to
140
+
141
+ [@mitchellh](https://github.com/mitchellh) + [middleware](https://github.com/mitchellh/middleware/tree/master/lib/middleware)
142
+
143
+ [@mperham](https://github.com/mperham) + [Sidekiq](https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/middleware/chain.rb)
144
+
145
+ [Rails](https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/middleware/stack.rb)
@@ -0,0 +1,187 @@
1
+ module Meddleware
2
+ class Stack
3
+ def initialize(&block)
4
+ instance_eval(&block) if block_given?
5
+ end
6
+
7
+ def use(*args, **kwargs, &block)
8
+ entry = create_entry(args, kwargs, block)
9
+ remove(entry[0])
10
+ stack << entry
11
+ self
12
+ end
13
+ alias append use
14
+
15
+ def prepend(*args, **kwargs, &block)
16
+ entry = create_entry(args, kwargs, block)
17
+ remove(entry[0])
18
+ stack.insert(0, entry)
19
+ self
20
+ end
21
+
22
+ def after(after_klass, *args, **kwargs, &block)
23
+ entry = create_entry(args, kwargs, block)
24
+ remove(entry[0])
25
+
26
+ i = if after_klass.is_a? Array
27
+ after_klass.map {|x| index(x) }.compact.max
28
+ else
29
+ index(after_klass)
30
+ end
31
+ i ||= count - 1 # last element
32
+
33
+ stack.insert(i + 1, entry)
34
+ self
35
+ end
36
+
37
+ def before(before_klass, *args, **kwargs, &block)
38
+ entry = create_entry(args, kwargs, block)
39
+ remove(entry[0])
40
+
41
+ i = if before_klass.is_a? Array
42
+ before_klass.map {|x| index(x) }.compact.min
43
+ else
44
+ index(before_klass)
45
+ end
46
+ i ||= 0 # first element
47
+
48
+ stack.insert(i, entry)
49
+ self
50
+ end
51
+
52
+ def include?(*klass)
53
+ klass.all? {|x| index(x) }
54
+ end
55
+
56
+ def remove(*klass)
57
+ stack.reject! { |entry| klass.include?(entry[0]) }
58
+ self
59
+ end
60
+
61
+ def replace(old_klass, *args, **kwargs, &block)
62
+ entry = create_entry(args, kwargs, block)
63
+ remove(entry[0])
64
+
65
+ i = index(old_klass)
66
+
67
+ unless i
68
+ raise RuntimeError, "middleware not present: #{old_klass}"
69
+ end
70
+
71
+ stack[i] = entry
72
+ self
73
+ end
74
+
75
+ def count
76
+ stack.count
77
+ end
78
+ alias size count
79
+
80
+ def clear
81
+ stack.clear
82
+ end
83
+
84
+ def empty?
85
+ stack.empty?
86
+ end
87
+
88
+ def call(*args, **kwargs)
89
+ chain = build_chain
90
+ default_args = args
91
+ default_kwargs = kwargs
92
+
93
+ traverse = proc do |*args, **kwargs|
94
+ if args.empty? && kwargs.empty?
95
+ args = default_args
96
+ kwargs = default_kwargs
97
+ else
98
+ default_args = args
99
+ default_kwargs = kwargs
100
+ end
101
+
102
+ if chain.empty?
103
+ yield(*args, **kwargs) if block_given?
104
+ else
105
+ middleware = chain.shift
106
+
107
+ if middleware.is_a?(Proc) && !middleware.lambda?
108
+ middleware.call(*args, **kwargs)
109
+
110
+ # implicit yield
111
+ traverse.call(*args, **kwargs)
112
+ else
113
+ middleware.call(*args, **kwargs, &traverse)
114
+ end
115
+ end
116
+ end
117
+
118
+ traverse.call(*args, **kwargs)
119
+ end
120
+
121
+
122
+ protected
123
+
124
+ def stack
125
+ @stack ||= []
126
+ end
127
+
128
+ def index(klass)
129
+ stack.index {|entry| entry[0] == klass }
130
+ end
131
+
132
+ def create_entry(args, kwargs, block)
133
+ klass, *args = args
134
+
135
+ if [ klass, block ].none?
136
+ raise ArgumentError, 'either a middleware or block must be provided'
137
+ end
138
+
139
+ if klass
140
+ # validate
141
+ if klass.is_a? Class
142
+ unless klass.method_defined?(:call)
143
+ raise ArgumentError, "middleware must implement `.call`: #{klass}"
144
+ end
145
+ else
146
+ unless klass.respond_to?(:call)
147
+ raise ArgumentError, "middleware must respond to `.call`: #{klass}"
148
+ end
149
+
150
+ unless block.nil?
151
+ raise ArgumentError, 'can not supply middleware instance and block'
152
+ end
153
+ end
154
+
155
+ [ klass, args, kwargs, block ].compact
156
+ else
157
+ [ block ]
158
+ end
159
+ end
160
+
161
+ def build_chain
162
+ # build the middleware stack
163
+ stack.map do |klass, args, kwargs, block|
164
+ if klass.is_a? Class
165
+ klass.new(*args, **kwargs, &block)
166
+ else
167
+ if args.nil? && kwargs.nil?
168
+ # middleware is a block
169
+ klass
170
+ elsif args.empty? && kwargs.empty?
171
+ # nothing to curry, just pass through middleware instance
172
+ klass
173
+ else
174
+ # curry args
175
+ ->(*more_args, **more_kwargs, &block) do
176
+ klass.call(
177
+ *(args + more_args),
178
+ **kwargs.merge(more_kwargs),
179
+ &block
180
+ )
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
@@ -1,3 +1,3 @@
1
- class Meddleware
2
- VERSION = "0.2.0"
1
+ module Meddleware
2
+ VERSION = "0.4.0"
3
3
  end
data/lib/meddleware.rb CHANGED
@@ -1,192 +1,29 @@
1
+ require 'meddleware/stack'
1
2
  require 'meddleware/version'
2
3
 
3
- class Meddleware
4
- def initialize(&block)
5
- instance_eval(&block) if block_given?
6
- end
7
-
8
- def use(*args, **kwargs, &block)
9
- entry = create_entry(args, kwargs, block)
10
- remove(entry[0])
11
- stack << entry
12
- self
13
- end
14
- alias append use
15
-
16
- def prepend(*args, **kwargs, &block)
17
- entry = create_entry(args, kwargs, block)
18
- remove(entry[0])
19
- stack.insert(0, entry)
20
- self
21
- end
22
-
23
- def after(after_klass, *args, **kwargs, &block)
24
- entry = create_entry(args, kwargs, block)
25
- remove(entry[0])
26
-
27
- i = if after_klass.is_a? Array
28
- after_klass.map {|x| index(x) }.compact.max
29
- else
30
- index(after_klass)
31
- end
32
- i ||= count - 1 # last element
33
-
34
- stack.insert(i + 1, entry)
35
- self
36
- end
37
-
38
- def before(before_klass, *args, **kwargs, &block)
39
- entry = create_entry(args, kwargs, block)
40
- remove(entry[0])
41
-
42
- i = if before_klass.is_a? Array
43
- before_klass.map {|x| index(x) }.compact.min
44
- else
45
- index(before_klass)
46
- end
47
- i ||= 0 # first element
48
-
49
- stack.insert(i, entry)
50
- self
51
- end
52
-
53
- def include?(*klass)
54
- klass.all? {|x| index(x) }
55
- end
56
-
57
- def remove(*klass)
58
- stack.reject! { |entry| klass.include?(entry[0]) }
59
- self
60
- end
61
-
62
- def replace(old_klass, *args, **kwargs, &block)
63
- entry = create_entry(args, kwargs, block)
64
- remove(entry[0])
65
-
66
- i = index(old_klass)
67
-
68
- unless i
69
- raise RuntimeError, "middleware not present: #{old_klass}"
70
- end
71
-
72
- stack[i] = entry
73
- self
74
- end
75
-
76
- def count
77
- stack.count
78
- end
79
- alias size count
80
-
81
- def clear
82
- stack.clear
83
- end
84
-
85
- def empty?
86
- stack.empty?
87
- end
88
-
89
- def call(*args, **kwargs)
90
- chain = build_chain
91
- default_args = args
92
- default_kwargs = kwargs
93
-
94
- traverse = proc do |*args, **kwargs|
95
- if args.empty? && kwargs.empty?
96
- args = default_args
97
- kwargs = default_kwargs
98
- else
99
- default_args = args
100
- default_kwargs = kwargs
101
- end
102
-
103
- if chain.empty?
104
- yield(*args, **kwargs) if block_given?
105
- else
106
- middleware = chain.shift
107
-
108
- if middleware.is_a?(Proc) && !middleware.lambda?
109
- middleware.call(*args, **kwargs)
110
-
111
- # implicit yield
112
- traverse.call(*args, **kwargs)
113
- else
114
- middleware.call(*args, **kwargs, &traverse)
115
- end
116
- end
4
+ module Meddleware
5
+ def middleware(&block)
6
+ (@middleware ||= Meddleware::Stack.new).tap do
7
+ @middleware.instance_eval(&block) if block_given?
117
8
  end
118
-
119
- traverse.call(*args, **kwargs)
120
- end
121
-
122
-
123
- protected
124
-
125
- def stack
126
- @stack ||= []
127
- end
128
-
129
- def index(klass)
130
- stack.index {|entry| entry[0] == klass }
131
9
  end
132
10
 
133
- def create_entry(args, kwargs, block)
134
- klass, *args = args
135
-
136
- if [ klass, block ].compact.empty?
137
- raise ArgumentError, 'either a middleware or block must be provided'
138
- end
139
-
140
- if klass
141
- # validate
142
- if klass.is_a? Class
143
- unless klass.method_defined?(:call)
144
- raise ArgumentError, "middleware must implement `.call`: #{klass}"
145
- end
146
- else
147
- unless klass.respond_to?(:call)
148
- raise ArgumentError, "middleware must respond to `.call`: #{klass}"
149
- end
11
+ private
150
12
 
151
- unless block.nil?
152
- raise ArgumentError, 'can not supply middleware instance and block'
13
+ def self.extended(base)
14
+ unless base.instance_methods.include?(:middleware)
15
+ base.class_eval do
16
+ def middleware
17
+ self.class.middleware
153
18
  end
154
19
  end
155
-
156
- [ klass, args, kwargs, block ].compact
157
- else
158
- [ block ]
159
20
  end
160
21
  end
161
22
 
162
- def build_chain
163
- # build the middleware stack
164
- stack.map do |klass, args, kwargs, block|
165
- if klass.is_a? Class
166
- klass.new(*args, **kwargs, &block)
167
- else
168
- if args.nil? && kwargs.nil?
169
- # middleware is a block
170
- klass
171
- elsif args.empty? && kwargs.empty?
172
- # nothing to curry, just pass through middleware instance
173
- klass
174
- else
175
- # curry args
176
- ->(*more_args, **more_kwargs, &block) do
177
- klass.call(
178
- *(args + more_args),
179
- **kwargs.merge(more_kwargs),
180
- &block
181
- )
182
- end
183
- end
184
- end
185
- end
186
- end
23
+ def self.append_features(base)
24
+ # remove instance helper from `extended`
25
+ base.remove_method(:middleware) if base.instance_methods(false).include?(:middleware)
187
26
 
188
- if RUBY_VERSION < '3'
189
- require 'meddleware/v2_5'
190
- prepend Meddleware::V2_5
27
+ super
191
28
  end
192
29
  end
@@ -0,0 +1,19 @@
1
+ require_relative "lib/meddleware/version"
2
+ package = Meddleware
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = File.basename(__FILE__).split(".")[0]
6
+ s.version = package.const_get 'VERSION'
7
+ s.authors = ['Daniel Pepper']
8
+ s.summary = package.to_s
9
+ s.description = 'A middleware framework to make meddling easy.'
10
+ s.homepage = "https://github.com/dpep/meddleware_rb"
11
+ s.license = 'MIT'
12
+ s.files = `git ls-files * ':!:spec'`.split("\n")
13
+
14
+ s.required_ruby_version = '>= 3'
15
+
16
+ s.add_development_dependency 'byebug', '>= 11'
17
+ s.add_development_dependency 'rspec', '>= 3.13'
18
+ s.add_development_dependency 'simplecov', '>= 0.22'
19
+ end