graft 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/graft/callback.rb +41 -0
- data/lib/graft/hook.rb +136 -0
- data/lib/graft/hook_point.rb +410 -0
- data/lib/graft/stack.rb +24 -0
- data/lib/graft.rb +5 -0
- metadata +36 -109
- data/README.rdoc +0 -138
- data/Rakefile +0 -43
- data/lib/graft/core_ext/hash.rb +0 -9
- data/lib/graft/json/attribute.rb +0 -18
- data/lib/graft/json/model.rb +0 -28
- data/lib/graft/json.rb +0 -14
- data/lib/graft/model.rb +0 -39
- data/lib/graft/version.rb +0 -13
- data/lib/graft/xml/attribute.rb +0 -55
- data/lib/graft/xml/model.rb +0 -49
- data/lib/graft/xml/type.rb +0 -91
- data/lib/graft/xml.rb +0 -27
- data/test/test_helper.rb +0 -38
- data/test/unit/core_ext/hash_test.rb +0 -29
- data/test/unit/json/attribute_test.rb +0 -51
- data/test/unit/json/model_test.rb +0 -86
- data/test/unit/xml/attribute_test.rb +0 -161
- data/test/unit/xml/model_test.rb +0 -173
- data/test/unit/xml/type_test.rb +0 -65
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4127a179ba1dd78b8575fac807051f3072a4fed22ec5f1521afe25626d3432a5
|
4
|
+
data.tar.gz: 4cfe831c87a9b0b2be7b2a37945825a6329d15fbcf5d037ea08c492855f26f68
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cdbab8db3916a2f8f62d2563a0c2da6a3a4391573ee77277f6f381f4daf1b8af3185341a3c94efd9d3fbc63e03047b5e2b2dddeb0ef859e3f3af1bc9e97d523a
|
7
|
+
data.tar.gz: 7becc0d2ae1f84fcb34057005b43ec6cfb40bbe2069e735e263c22c15cea5b383c828ceabb9e88c87b64cd8bdc4c5c7e55b7dda0cc136df2e6369cb1103dd9cc
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Graft
|
4
|
+
class Callback
|
5
|
+
attr_reader :name
|
6
|
+
|
7
|
+
# NOTE: opts is not used in the current implementation
|
8
|
+
def initialize(name = nil, opts = {}, &block)
|
9
|
+
@name = name
|
10
|
+
@opts = opts
|
11
|
+
@block = block
|
12
|
+
@enabled = true
|
13
|
+
end
|
14
|
+
|
15
|
+
if RUBY_VERSION < "3.0"
|
16
|
+
def call(*args, &block)
|
17
|
+
return unless enabled?
|
18
|
+
|
19
|
+
@block.call(*args, &block)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
def call(*args, **kwargs, &block)
|
23
|
+
return unless enabled?
|
24
|
+
|
25
|
+
@block.call(*args, **kwargs, &block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def disable
|
30
|
+
@enabled = false
|
31
|
+
end
|
32
|
+
|
33
|
+
def enable
|
34
|
+
@enabled = true
|
35
|
+
end
|
36
|
+
|
37
|
+
def enabled?
|
38
|
+
@enabled
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/graft/hook.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "stack"
|
4
|
+
require_relative "callback"
|
5
|
+
require_relative "hook_point"
|
6
|
+
|
7
|
+
module Graft
|
8
|
+
class Hook
|
9
|
+
DEFAULT_STRATEGY = HookPoint::DEFAULT_STRATEGY
|
10
|
+
|
11
|
+
@hooks = {}
|
12
|
+
|
13
|
+
def self.[](hook_point, strategy = DEFAULT_STRATEGY)
|
14
|
+
@hooks[hook_point] ||= new(hook_point, nil, strategy)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.add(hook_point, strategy = DEFAULT_STRATEGY, &block)
|
18
|
+
self[hook_point, strategy].add(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.ignore
|
22
|
+
Thread.current[:hook_entered] = true
|
23
|
+
yield
|
24
|
+
ensure
|
25
|
+
Thread.current[:hook_entered] = false
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :point, :stack
|
29
|
+
|
30
|
+
def initialize(hook_point, dependency_test = nil, strategy = DEFAULT_STRATEGY)
|
31
|
+
@disabled = false
|
32
|
+
@point = hook_point.is_a?(HookPoint) ? hook_point : HookPoint.new(hook_point, strategy)
|
33
|
+
@dependency_test = dependency_test || proc { point.exist? }
|
34
|
+
@stack = Stack.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def dependency?
|
38
|
+
return true if @dependency_test.nil?
|
39
|
+
|
40
|
+
@dependency_test.call
|
41
|
+
end
|
42
|
+
|
43
|
+
def add(&block)
|
44
|
+
tap { instance_eval(&block) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def callback_name(tag = nil)
|
48
|
+
point.to_s << (tag ? ":#{tag}" : "")
|
49
|
+
end
|
50
|
+
|
51
|
+
def append(tag = nil, opts = {}, &block)
|
52
|
+
@stack << Callback.new(callback_name(tag), opts, &block)
|
53
|
+
end
|
54
|
+
|
55
|
+
def unshift(tag = nil, opts = {}, &block)
|
56
|
+
@stack.unshift Callback.new(callback_name(tag), opts, &block)
|
57
|
+
end
|
58
|
+
|
59
|
+
def before(tag = nil, opts = {}, &block)
|
60
|
+
# TODO
|
61
|
+
end
|
62
|
+
|
63
|
+
def after(tag = nil, opts = {}, &block)
|
64
|
+
# TODO
|
65
|
+
end
|
66
|
+
|
67
|
+
def depends_on(&block)
|
68
|
+
@dependency_test = block
|
69
|
+
end
|
70
|
+
|
71
|
+
def enable
|
72
|
+
@disabled = false
|
73
|
+
end
|
74
|
+
|
75
|
+
def disable
|
76
|
+
@disabled = true
|
77
|
+
end
|
78
|
+
|
79
|
+
def disabled?
|
80
|
+
@disabled
|
81
|
+
end
|
82
|
+
|
83
|
+
def install
|
84
|
+
return unless point.exist?
|
85
|
+
|
86
|
+
point.install("hook", &Hook.wrapper(self))
|
87
|
+
end
|
88
|
+
|
89
|
+
def uninstall
|
90
|
+
return unless point.exist?
|
91
|
+
|
92
|
+
point.uninstall("hook")
|
93
|
+
end
|
94
|
+
|
95
|
+
class << self
|
96
|
+
if RUBY_VERSION < "3.0"
|
97
|
+
def wrapper(hook)
|
98
|
+
proc do |*args, &block|
|
99
|
+
env = {
|
100
|
+
self: self,
|
101
|
+
args: args,
|
102
|
+
block: block
|
103
|
+
}
|
104
|
+
supa = proc { |*args, &block| super(*args, &block) }
|
105
|
+
mid = proc { |_, env| {return: supa.call(*env[:args], &env[:block])} }
|
106
|
+
stack = hook.stack.dup
|
107
|
+
stack << mid
|
108
|
+
|
109
|
+
stack.call(env)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
else
|
113
|
+
def wrapper(hook)
|
114
|
+
proc do |*args, **kwargs, &block|
|
115
|
+
env = {
|
116
|
+
receiver: self,
|
117
|
+
method: hook.point.method_name,
|
118
|
+
kind: hook.point.method_kind,
|
119
|
+
strategy: hook.point.instance_variable_get(:@strategy),
|
120
|
+
args: args,
|
121
|
+
kwargs: kwargs,
|
122
|
+
block: block
|
123
|
+
}
|
124
|
+
|
125
|
+
supa = proc { |*args, **kwargs, &block| super(*args, **kwargs, &block) }
|
126
|
+
mid = proc { |_, env| {return: supa.call(*env[:args], **env[:kwargs], &env[:block])} }
|
127
|
+
stack = hook.stack.dup
|
128
|
+
stack << mid
|
129
|
+
|
130
|
+
stack.call(env)[:return]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,410 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Graft
|
4
|
+
class HookPointError < StandardError; end
|
5
|
+
|
6
|
+
class HookModule < Module
|
7
|
+
def initialize(key)
|
8
|
+
super()
|
9
|
+
|
10
|
+
@key = key
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :key
|
14
|
+
|
15
|
+
def inspect
|
16
|
+
"#<#{self.class.name}: #{@key.inspect}>"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class HookPoint
|
21
|
+
DEFAULT_STRATEGY = Module.respond_to?(:prepend) ? :prepend : :chain
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def parse(hook_point)
|
25
|
+
klass_name, separator, method_name = hook_point.split(/(\#|\.)/, 2)
|
26
|
+
|
27
|
+
raise ArgumentError, hook_point if klass_name.nil? || separator.nil? || method_name.nil?
|
28
|
+
raise ArgumentError, hook_point unless [".", "#"].include?(separator)
|
29
|
+
|
30
|
+
method_kind = (separator == ".") ? :klass_method : :instance_method
|
31
|
+
|
32
|
+
[klass_name.to_sym, method_kind, method_name.to_sym]
|
33
|
+
end
|
34
|
+
|
35
|
+
def const_exist?(name)
|
36
|
+
resolve_const(name) && true
|
37
|
+
rescue NameError, ArgumentError
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
def resolve_const(name)
|
42
|
+
raise ArgumentError, "const not found: #{name}" if name.nil? || name.empty?
|
43
|
+
|
44
|
+
name.to_s.split("::").inject(Object) { |a, e| a.const_get(e, false) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def resolve_module(name)
|
48
|
+
const = resolve_const(name)
|
49
|
+
|
50
|
+
raise ArgumentError, "not a Module: #{name}" unless const.is_a?(Module)
|
51
|
+
|
52
|
+
const
|
53
|
+
end
|
54
|
+
|
55
|
+
def strategy_module(strategy)
|
56
|
+
case strategy
|
57
|
+
when :prepend then Prepend
|
58
|
+
when :chain then Chain
|
59
|
+
else
|
60
|
+
raise HookPointError, "unknown strategy: #{strategy.inspect}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
attr_reader :klass_name, :method_kind, :method_name
|
66
|
+
|
67
|
+
def initialize(hook_point, strategy = DEFAULT_STRATEGY)
|
68
|
+
@klass_name, @method_kind, @method_name = HookPoint.parse(hook_point)
|
69
|
+
@strategy = strategy
|
70
|
+
|
71
|
+
extend HookPoint.strategy_module(strategy)
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_s
|
75
|
+
@to_s ||= "#{@klass_name}#{(@method_kind == :instance_method) ? "#" : "."}#{@method_name}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def exist?
|
79
|
+
return false unless HookPoint.const_exist?(@klass_name)
|
80
|
+
|
81
|
+
if klass_method?
|
82
|
+
(
|
83
|
+
klass.singleton_class.public_instance_methods(false) +
|
84
|
+
klass.singleton_class.protected_instance_methods(false) +
|
85
|
+
klass.singleton_class.private_instance_methods(false)
|
86
|
+
).include?(@method_name)
|
87
|
+
elsif instance_method?
|
88
|
+
(
|
89
|
+
klass.public_instance_methods(false) +
|
90
|
+
klass.protected_instance_methods(false) +
|
91
|
+
klass.private_instance_methods(false)
|
92
|
+
).include?(@method_name)
|
93
|
+
else
|
94
|
+
raise HookPointError, "#{self} unknown hook point kind"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def klass
|
99
|
+
HookPoint.resolve_module(@klass_name)
|
100
|
+
end
|
101
|
+
|
102
|
+
def klass_method?
|
103
|
+
@method_kind == :klass_method
|
104
|
+
end
|
105
|
+
|
106
|
+
def instance_method?
|
107
|
+
@method_kind == :instance_method
|
108
|
+
end
|
109
|
+
|
110
|
+
def private_method?
|
111
|
+
if klass_method?
|
112
|
+
klass.private_methods.include?(@method_name)
|
113
|
+
elsif instance_method?
|
114
|
+
klass.private_instance_methods.include?(@method_name)
|
115
|
+
else
|
116
|
+
raise HookPointError, "#{self} unknown hook point kind"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def protected_method?
|
121
|
+
if klass_method?
|
122
|
+
klass.protected_methods.include?(@method_name)
|
123
|
+
elsif instance_method?
|
124
|
+
klass.protected_instance_methods.include?(@method_name)
|
125
|
+
else
|
126
|
+
raise HookPointError, "#{self} unknown hook point kind"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def installed?(key) # rubocop:disable Lint/UselessMethodDefinition
|
131
|
+
super
|
132
|
+
end
|
133
|
+
|
134
|
+
def install(key, &block)
|
135
|
+
return unless exist?
|
136
|
+
|
137
|
+
return if installed?(key)
|
138
|
+
|
139
|
+
super
|
140
|
+
end
|
141
|
+
|
142
|
+
def uninstall(key)
|
143
|
+
return unless exist?
|
144
|
+
|
145
|
+
return unless installed?(key)
|
146
|
+
|
147
|
+
super
|
148
|
+
end
|
149
|
+
|
150
|
+
def enable(key) # rubocop:disable Lint/UselessMethodDefinition
|
151
|
+
super
|
152
|
+
end
|
153
|
+
|
154
|
+
def disable(key) # rubocop:disable Lint/UselessMethodDefinition
|
155
|
+
super
|
156
|
+
end
|
157
|
+
|
158
|
+
def disabled?(key) # rubocop:disable Lint/UselessMethodDefinition
|
159
|
+
super
|
160
|
+
end
|
161
|
+
|
162
|
+
module Prepend
|
163
|
+
def installed?(key)
|
164
|
+
prepended?(key) && overridden?(key)
|
165
|
+
end
|
166
|
+
|
167
|
+
def install(key, &block)
|
168
|
+
prepend(key)
|
169
|
+
override(key, &block)
|
170
|
+
end
|
171
|
+
|
172
|
+
def uninstall(key)
|
173
|
+
unoverride(key) if overridden?(key)
|
174
|
+
end
|
175
|
+
|
176
|
+
def enable(key)
|
177
|
+
raise HookPointError, "enable(#{key.inspect}) with prepend strategy"
|
178
|
+
end
|
179
|
+
|
180
|
+
def disable(key)
|
181
|
+
unoverride(key)
|
182
|
+
end
|
183
|
+
|
184
|
+
def disabled?(key)
|
185
|
+
!overridden?(key)
|
186
|
+
end
|
187
|
+
|
188
|
+
private
|
189
|
+
|
190
|
+
def hook_module(key)
|
191
|
+
target = klass_method? ? klass.singleton_class : klass
|
192
|
+
|
193
|
+
mod = target.ancestors.each do |e|
|
194
|
+
break if e == target
|
195
|
+
break(e) if e.instance_of?(HookModule) && e.key == key
|
196
|
+
end
|
197
|
+
|
198
|
+
raise HookPointError, "Inconsistency detected: #{target} missing from its own ancestors" if mod.is_a?(Array)
|
199
|
+
|
200
|
+
[target, mod]
|
201
|
+
end
|
202
|
+
|
203
|
+
def prepend(key)
|
204
|
+
target, mod = hook_module(key)
|
205
|
+
|
206
|
+
mod ||= HookModule.new(key)
|
207
|
+
|
208
|
+
target.prepend(mod)
|
209
|
+
end
|
210
|
+
|
211
|
+
def prepended?(key)
|
212
|
+
_, mod = hook_module(key)
|
213
|
+
|
214
|
+
mod != nil
|
215
|
+
end
|
216
|
+
|
217
|
+
def overridden?(key)
|
218
|
+
_, mod = hook_module(key)
|
219
|
+
|
220
|
+
(
|
221
|
+
mod.instance_methods(false) +
|
222
|
+
mod.protected_instance_methods(false) +
|
223
|
+
mod.private_instance_methods(false)
|
224
|
+
).include?(method_name)
|
225
|
+
end
|
226
|
+
|
227
|
+
def override(key, &block)
|
228
|
+
hook_point = self
|
229
|
+
method_name = @method_name
|
230
|
+
|
231
|
+
_, mod = hook_module(key)
|
232
|
+
|
233
|
+
mod.instance_eval do
|
234
|
+
if hook_point.private_method?
|
235
|
+
private
|
236
|
+
elsif hook_point.protected_method?
|
237
|
+
protected
|
238
|
+
else
|
239
|
+
public
|
240
|
+
end
|
241
|
+
|
242
|
+
define_method(:"#{method_name}", &block)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def unoverride(key)
|
247
|
+
method_name = @method_name
|
248
|
+
|
249
|
+
_, mod = hook_module(key)
|
250
|
+
|
251
|
+
mod.instance_eval { remove_method(method_name) }
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
module Chain
|
256
|
+
def installed?(key)
|
257
|
+
defined(key)
|
258
|
+
end
|
259
|
+
|
260
|
+
def install(key, &block)
|
261
|
+
define(key, &block)
|
262
|
+
chain(key)
|
263
|
+
end
|
264
|
+
|
265
|
+
def uninstall(key)
|
266
|
+
disable(key)
|
267
|
+
remove(key)
|
268
|
+
end
|
269
|
+
|
270
|
+
def enable(key)
|
271
|
+
chain(key)
|
272
|
+
end
|
273
|
+
|
274
|
+
def disable(key)
|
275
|
+
unchain(key)
|
276
|
+
end
|
277
|
+
|
278
|
+
def disabled?(key)
|
279
|
+
!chained?(key)
|
280
|
+
end
|
281
|
+
|
282
|
+
private
|
283
|
+
|
284
|
+
def defined(suffix)
|
285
|
+
if klass_method?
|
286
|
+
(
|
287
|
+
klass.methods +
|
288
|
+
klass.protected_methods +
|
289
|
+
klass.private_methods
|
290
|
+
).include?(:"#{method_name}_with_#{suffix}")
|
291
|
+
elsif instance_method?
|
292
|
+
(
|
293
|
+
klass.instance_methods +
|
294
|
+
klass.protected_instance_methods +
|
295
|
+
klass.private_instance_methods
|
296
|
+
).include?(:"#{method_name}_with_#{suffix}")
|
297
|
+
else
|
298
|
+
raise HookPointError, "#{self} unknown hook point kind"
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def define(suffix, &block)
|
303
|
+
hook_point = self
|
304
|
+
method_name = @method_name
|
305
|
+
|
306
|
+
if klass_method?
|
307
|
+
klass.singleton_class.instance_eval do
|
308
|
+
if hook_point.private_method?
|
309
|
+
private
|
310
|
+
elsif hook_point.protected_method?
|
311
|
+
protected
|
312
|
+
else
|
313
|
+
public
|
314
|
+
end
|
315
|
+
|
316
|
+
define_method(:"#{method_name}_with_#{suffix}", &block)
|
317
|
+
end
|
318
|
+
elsif instance_method?
|
319
|
+
klass.class_eval do
|
320
|
+
if hook_point.private_method?
|
321
|
+
private
|
322
|
+
elsif hook_point.protected_method?
|
323
|
+
protected
|
324
|
+
else
|
325
|
+
public
|
326
|
+
end
|
327
|
+
|
328
|
+
define_method(:"#{method_name}_with_#{suffix}", &block)
|
329
|
+
end
|
330
|
+
else
|
331
|
+
raise HookPointError, "unknown hook point kind"
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def remove(suffix)
|
336
|
+
method_name = @method_name
|
337
|
+
|
338
|
+
if klass_method?
|
339
|
+
klass.singleton_class.instance_eval do
|
340
|
+
remove_method(:"#{method_name}_with_#{suffix}")
|
341
|
+
end
|
342
|
+
elsif instance_method?
|
343
|
+
klass.class_eval do
|
344
|
+
remove_method(:"#{method_name}_with_#{suffix}")
|
345
|
+
end
|
346
|
+
else
|
347
|
+
raise HookPointError, "unknown hook point kind"
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def chained?(suffix)
|
352
|
+
method_name = @method_name
|
353
|
+
|
354
|
+
if klass_method?
|
355
|
+
klass.singleton_class.instance_eval do
|
356
|
+
instance_method(:"#{method_name}").original_name == :"#{method_name}_with_#{suffix}"
|
357
|
+
end
|
358
|
+
elsif instance_method?
|
359
|
+
klass.class_eval do
|
360
|
+
instance_method(:"#{method_name}").original_name == :"#{method_name}_with_#{suffix}"
|
361
|
+
end
|
362
|
+
else
|
363
|
+
raise HookPointError, "unknown hook point kind"
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
def chain(suffix)
|
368
|
+
method_name = @method_name
|
369
|
+
|
370
|
+
if klass_method?
|
371
|
+
klass.singleton_class.instance_eval do
|
372
|
+
alias_method :"#{method_name}_without_#{suffix}", :"#{method_name}"
|
373
|
+
alias_method :"#{method_name}", :"#{method_name}_with_#{suffix}"
|
374
|
+
end
|
375
|
+
elsif instance_method?
|
376
|
+
klass.class_eval do
|
377
|
+
alias_method :"#{method_name}_without_#{suffix}", :"#{method_name}"
|
378
|
+
alias_method :"#{method_name}", :"#{method_name}_with_#{suffix}"
|
379
|
+
end
|
380
|
+
else
|
381
|
+
raise HookPointError, "unknown hook point kind"
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
def unchain(suffix)
|
386
|
+
method_name = @method_name
|
387
|
+
|
388
|
+
if klass_method?
|
389
|
+
klass.singleton_class.instance_eval do
|
390
|
+
alias_method :"#{method_name}", :"#{method_name}_without_#{suffix}"
|
391
|
+
end
|
392
|
+
elsif instance_method?
|
393
|
+
klass.class_eval do
|
394
|
+
alias_method :"#{method_name}", :"#{method_name}_without_#{suffix}"
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
if RUBY_VERSION < "3.0"
|
400
|
+
def apply(obj, suffix, *args, &block)
|
401
|
+
obj.send(:"#{method_name}_without_#{suffix}", *args, &block)
|
402
|
+
end
|
403
|
+
else
|
404
|
+
def apply(obj, suffix, *args, **kwargs, &block)
|
405
|
+
obj.send(:"#{method_name}_without_#{suffix}", *args, **kwargs, &block)
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
data/lib/graft/stack.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Graft
|
4
|
+
class Stack < Array
|
5
|
+
def call(env = {})
|
6
|
+
head.call(tail, env)
|
7
|
+
end
|
8
|
+
|
9
|
+
def head
|
10
|
+
# TODO: raise EmptyStackError?
|
11
|
+
|
12
|
+
first
|
13
|
+
end
|
14
|
+
|
15
|
+
def tail
|
16
|
+
tail = self[1..size]
|
17
|
+
|
18
|
+
# TODO: raise EmptyStackError?
|
19
|
+
return Stack.new if tail.nil?
|
20
|
+
|
21
|
+
Stack.new(tail)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|