meddleware 0.2.0 → 0.3.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: '0197bd468d02ee66541d9ae7b369afc2ad0632cb6058cc3d8856a2b87f8ff897'
4
+ data.tar.gz: 4dab2733ff8907db8abbe3b3b11d45ba536d2bc76831d7128b68a4db5847900e
5
5
  SHA512:
6
- metadata.gz: abaee8a6faafe5ed08d04d9a5166efedbf3e9cc51de2fc3bd1608ba57496e38676860b18bc44ab4a8dfc4030709fc287c84cfaa5001be7a391be7f5f09f4b736
7
- data.tar.gz: 5222b652ae8a45723d4b20798d4c852d7b8a0e24a1449531c0c8fc96a3c3fa3a265e4bbe8bec3e81f1958a77112fd189f8a75b9bce2f66f3c4c8e5ae8770dac3
6
+ metadata.gz: 305a9da953ca540f424296aca10836829cff777c39698b1938f998f7f425045cd3fb69fef9563a34579858e87b8fd38d02971a7fc72b1ef34df3b242f80d18f2
7
+ data.tar.gz: '08023838c15da52c5339ad7efeb6124eea151aa8d4ea334dd15b01f64c191ae8f4d4eaf01e7f9171a10606c368aa70a87c30ac297792d7c2e443dec575820b81'
data/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ ### v0.3.0 (2022-10-11)
2
+ - extendable
3
+ - drop ruby 2.5 (#4)
4
+ - remove rake
5
+
6
+ ### v0.2.0 (2021-09-14)
7
+ - ruby 3 compatibility
8
+ - usage spec
9
+ - fix tests
10
+ - simplify: move dedup functionality out of build method
11
+ - array support
12
+
13
+ ### v0.1.0 (2021-04-05)
14
+ - spec examples
15
+ - Update README.md
16
+ - test coverage
17
+ - bugs and tests
18
+ - Meddleware.replace
19
+ - proc support
20
+ - validation and currying
21
+ - test coverage
22
+ - support instances and procs
23
+ - gemfile cleanup
24
+ - fix github actions
25
+ - add specs, clean up
26
+ - thanks
27
+
28
+ ### v0.0.1 (2021-03-31)
29
+ - skeleton
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,146 @@
1
+ Meddleware
2
+ ======
3
+ 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.
4
+
5
+
6
+ ```ruby
7
+ require 'meddleware'
8
+
9
+ # lib/mywidget.rb
10
+ class MyWidget
11
+ extend Meddleware
12
+
13
+ def do_the_thing
14
+ # invoke middleware chain
15
+ middleware.call do
16
+ # do your thing
17
+ ...
18
+ end
19
+ end
20
+ end
21
+
22
+ # config/initializers/mywidget.rb
23
+ MyWidget.middleware do
24
+ # add a logger
25
+ use { puts "before the thing" }
26
+
27
+ # add another middleware
28
+ use MyMiddleware
29
+ end
30
+
31
+
32
+ # use it from whereever
33
+ MyWidget.new.do_the_thing
34
+ ```
35
+
36
+
37
+ ## Usage
38
+ Extend your class with Meddleware to add a `middleware` method. Or use `include` to give each instance its own, individual middleware.
39
+
40
+ ```ruby
41
+ class MyWidget
42
+ extend Meddleware
43
+ end
44
+
45
+ MyWidget.middleware
46
+ ```
47
+
48
+ Then wrap your class's functionality so it will get executed along with the all the registered middleware.
49
+
50
+ ```ruby
51
+ class MyWidget
52
+ extend Meddleware
53
+
54
+ def do_the_thing
55
+ # invoke middleware chain
56
+ middleware.call(*args) do
57
+ # do your thing
58
+ ...
59
+ end
60
+ end
61
+ end
62
+ ```
63
+
64
+
65
+ See [documentation](https://github.com/dpep/meddleware_rb/wiki/DSL) for full DSL.
66
+
67
+
68
+ ----
69
+ ## Full Example
70
+ ```ruby
71
+ # lib/mylist.rb
72
+ module MyList
73
+ extend Meddleware
74
+
75
+ # generate an array from 1 to n
76
+ def self.generate(n)
77
+ # invoke middleware chain
78
+ middleware.call(n) do |n|
79
+ # do the actual work of generating your results
80
+ (1..n).to_a
81
+ end
82
+ end
83
+ end
84
+
85
+ # app/initializers/mylist.rb
86
+ class OneExtra
87
+ def call(n)
88
+ # adds one to the argument being passed in
89
+ yield(n + 1)
90
+ end
91
+ end
92
+
93
+ class Doubler
94
+ def call(*)
95
+ # modifies the results by doubles each value
96
+ yield.map {|x| x * 2 }
97
+ end
98
+ end
99
+
100
+ MyList.middleware do
101
+ use OneExtra
102
+ use Doubler
103
+
104
+ # loggers
105
+ prepend {|x| puts "n starts as #{x}" }
106
+ append {|x| puts "n ends as #{x}" }
107
+ end
108
+
109
+
110
+ # use it
111
+ > MyList.generate(2)
112
+
113
+ # would normally output [ 1, 2 ]
114
+ # but with middleware:
115
+
116
+ n starts as 2
117
+ n ends as 3
118
+ => [2, 4, 6]
119
+ ```
120
+
121
+ ----
122
+ ## Contributing
123
+
124
+ Yes please :)
125
+
126
+ 1. Fork it
127
+ 1. Create your feature branch (`git checkout -b my-feature`)
128
+ 1. Ensure the tests pass (`bundle exec rspec`)
129
+ 1. Commit your changes (`git commit -am 'awesome new feature'`)
130
+ 1. Push your branch (`git push origin my-feature`)
131
+ 1. Create a Pull Request
132
+
133
+
134
+ ----
135
+ ### Inspired by / Thanks to
136
+
137
+ [@mitchellh](https://github.com/mitchellh) + [middleware](https://github.com/mitchellh/middleware/tree/master/lib/middleware)
138
+
139
+ [@mperham](https://github.com/mperham) + [Sidekiq](https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/middleware/chain.rb)
140
+
141
+ [Rails](https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/middleware/stack.rb)
142
+
143
+
144
+ ----
145
+ ![Gem](https://img.shields.io/gem/dt/meddleware?style=plastic)
146
+ [![codecov](https://codecov.io/gh/dpep/meddleware_rb/branch/main/graph/badge.svg)](https://codecov.io/gh/dpep/meddleware_rb)
@@ -0,0 +1,192 @@
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
+
187
+ if RUBY_VERSION < '3'
188
+ require 'meddleware/v2_7'
189
+ prepend Meddleware::V2_7
190
+ end
191
+ end
192
+ end
@@ -1,7 +1,7 @@
1
- # backwards compatible functionality for Ruby 2.5
1
+ # backwards compatible functionality for Ruby 2.7
2
2
 
3
- class Meddleware
4
- module V2_5
3
+ module Meddleware
4
+ module V2_7
5
5
  def use(*klass_and_args, &block)
6
6
  entry = create_entry(klass_and_args, block)
7
7
  remove(entry[0])
@@ -1,3 +1,3 @@
1
- class Meddleware
2
- VERSION = "0.2.0"
1
+ module Meddleware
2
+ VERSION = "0.3.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,29 @@
1
+ package_name = File.basename(__FILE__).split(".")[0]
2
+ load Dir.glob("lib/**/version.rb")[0]
3
+
4
+ package = Meddleware
5
+
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = package_name
9
+ s.version = package.const_get 'VERSION'
10
+ s.authors = ['Daniel Pepper']
11
+ s.summary = package.to_s
12
+ s.description = 'A middleware framework to make meddling easy.'
13
+ s.homepage = "https://github.com/dpep/#{package_name}_rb"
14
+ s.license = 'MIT'
15
+ s.required_ruby_version = '>= 2.7'
16
+
17
+ s.files = Dir[
18
+ __FILE__,
19
+ 'lib/**/*',
20
+ 'CHANGELOG*',
21
+ 'LICENSE*',
22
+ 'README*',
23
+ ]
24
+
25
+ s.add_development_dependency 'byebug'
26
+ s.add_development_dependency 'codecov'
27
+ s.add_development_dependency 'rspec'
28
+ s.add_development_dependency 'simplecov'
29
+ end