meddleware 0.1.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: 453a4fea42903735f1b7e502f5fb5ba323ecfd1240db7d476453dabd0e773f8d
4
- data.tar.gz: c459598e2a9145a7ad8a4128cefc5de8ed3a018db28e1cf95a24a26b15b42883
3
+ metadata.gz: '0197bd468d02ee66541d9ae7b369afc2ad0632cb6058cc3d8856a2b87f8ff897'
4
+ data.tar.gz: 4dab2733ff8907db8abbe3b3b11d45ba536d2bc76831d7128b68a4db5847900e
5
5
  SHA512:
6
- metadata.gz: 94db19316854023dece52b027bd87f0c423936c3ccf4fba424b85eaa798672aa936820ecb0b5e427c5832c29fe7deb6dce876563c70c3869b15b1b137d26b511
7
- data.tar.gz: 57188689d33ca8a987e4cdcde3792da430b86946c6c0ec99ace62b6e7be05c9658eacbc83f6e3cc26c20d3c8e06a47ef53fa4937919d7d4c1e49a556469cc0c3
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
@@ -0,0 +1,143 @@
1
+ # backwards compatible functionality for Ruby 2.7
2
+
3
+ module Meddleware
4
+ module V2_7
5
+ def use(*klass_and_args, &block)
6
+ entry = create_entry(klass_and_args, block)
7
+ remove(entry[0])
8
+ stack << entry
9
+ self
10
+ end
11
+ alias append use
12
+
13
+ def prepend(*klass_and_args, &block)
14
+ entry = create_entry(klass_and_args, block)
15
+ remove(entry[0])
16
+ stack.insert(0, entry)
17
+ self
18
+ end
19
+
20
+ def after(after_klass, *klass_and_args, &block)
21
+ entry = create_entry(klass_and_args, block)
22
+ remove(entry[0])
23
+
24
+ i = if after_klass.is_a? Array
25
+ after_klass.map {|x| index(x) }.compact.max
26
+ else
27
+ index(after_klass)
28
+ end
29
+ i ||= count - 1 # last element
30
+
31
+ stack.insert(i + 1, entry)
32
+ self
33
+ end
34
+
35
+ def before(before_klass, *klass_and_args, &block)
36
+ entry = create_entry(klass_and_args, block)
37
+ remove(entry[0])
38
+
39
+ i = if before_klass.is_a? Array
40
+ before_klass.map {|x| index(x) }.compact.min
41
+ else
42
+ index(before_klass)
43
+ end
44
+ i ||= 0 # first element
45
+
46
+ stack.insert(i, entry)
47
+ self
48
+ end
49
+
50
+ def replace(old_klass, *klass_and_args, &block)
51
+ entry = create_entry(klass_and_args, block)
52
+ remove(entry[0])
53
+
54
+ i = index(old_klass)
55
+
56
+ unless i
57
+ raise RuntimeError, "middleware not present: #{old_klass}"
58
+ end
59
+
60
+ stack[i] = entry
61
+ self
62
+ end
63
+
64
+ def call(*args)
65
+ chain = build_chain
66
+ default_args = args
67
+
68
+ traverse = proc do |*args|
69
+ if args.empty?
70
+ args = default_args
71
+ else
72
+ default_args = args
73
+ end
74
+
75
+ if chain.empty?
76
+ yield(*args) if block_given?
77
+ else
78
+ middleware = chain.shift
79
+
80
+ if middleware.is_a?(Proc) && !middleware.lambda?
81
+ middleware.call(*args)
82
+
83
+ # implicit yield
84
+ traverse.call(*args)
85
+ else
86
+ middleware.call(*args, &traverse)
87
+ end
88
+ end
89
+ end
90
+ traverse.call(*args)
91
+ end
92
+
93
+
94
+ private
95
+
96
+ def create_entry(klass_and_args, block)
97
+ klass, *args = klass_and_args
98
+
99
+ if [ klass, block ].compact.count == 0
100
+ raise ArgumentError, 'either a middleware or block must be provided'
101
+ end
102
+
103
+ if klass
104
+ # validate
105
+ if klass.is_a? Class
106
+ unless klass.method_defined?(:call)
107
+ raise ArgumentError, "middleware must implement `.call`: #{klass}"
108
+ end
109
+ else
110
+ unless klass.respond_to?(:call)
111
+ raise ArgumentError, "middleware must respond to `.call`: #{klass}"
112
+ end
113
+
114
+ unless block.nil?
115
+ raise ArgumentError, 'can not supply middleware instance and block'
116
+ end
117
+ end
118
+
119
+ [ klass, args, block ].compact
120
+ else
121
+ [ block ]
122
+ end
123
+ end
124
+
125
+ def build_chain
126
+ # build the middleware stack
127
+ stack.map do |klass, args, block|
128
+ if klass.is_a? Class
129
+ klass.new(*args, &block)
130
+ else
131
+ if args.nil? || args.empty?
132
+ klass
133
+ else
134
+ # curry args
135
+ ->(*more_args, &block) do
136
+ klass.call(*args, *more_args, &block)
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -1,3 +1,3 @@
1
- class Meddleware
2
- VERSION = '0.1.0'
1
+ module Meddleware
2
+ VERSION = "0.3.0"
3
3
  end