nil-passer 0.1.0 → 0.2.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 +4 -4
- data/lib/gen.rb +6 -0
- data/lib/gen/code.rb +73 -0
- data/lib/gen/if.rb +18 -0
- data/lib/gen/method.rb +41 -0
- data/lib/gen/predicate.rb +54 -0
- data/lib/nil_passer.rb +389 -45
- data/lib/utils/monitor_method.rb +146 -0
- data/lib/utils/wrap_method.rb +134 -0
- metadata +11 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7d414f7dcb6b414d6ee0187856bbdd4c6c7a5fb2
|
4
|
+
data.tar.gz: e3267e649eba8323f21ee0d98afbae30706337c1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6407a8c15abdd083f7f1bb12bb0467d091838c950d6a44289568714ed0f2c442d22080d8d91bcb6ed64049586d4b77dc0e7b0e999e24acaeabc7c8bcd37a9f12
|
7
|
+
data.tar.gz: 00cd12932a6610f117b4d105f62daa3f83dd174dc26dfb14e6f7bcdcb4fd5ee16f656308a35439136262875f9562bcf593fface3b29fd8771572b6e079d1efe5
|
data/lib/gen.rb
ADDED
data/lib/gen/code.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'gen/method'
|
2
|
+
|
3
|
+
module Gen
|
4
|
+
class Gen::Code < Gen::Method
|
5
|
+
attr_accessor :local_var_count, :bound_procs, :bound_constants
|
6
|
+
|
7
|
+
# lambda_block: call on code and (optionally) arguments to generate a lambda
|
8
|
+
def_block_gen :lambda
|
9
|
+
|
10
|
+
# proc_block: call on code and (optionally) arguments to generate a proc
|
11
|
+
def_block_gen :proc
|
12
|
+
|
13
|
+
# local_block: call on code to keep localized during execution
|
14
|
+
def_block_gen :local, "#{self}.wrap_it", nil
|
15
|
+
|
16
|
+
def_block_gen :class_exec
|
17
|
+
|
18
|
+
def initialize(local_var_count=0, bound_procs=[], bound_constants=[])
|
19
|
+
@local_var_count = local_var_count
|
20
|
+
@bound_procs = bound_procs
|
21
|
+
@bound_constants = bound_constants
|
22
|
+
end
|
23
|
+
|
24
|
+
# Evaluate a string using the locally bound procs and constants
|
25
|
+
def bound_eval(source)
|
26
|
+
self.generate_binding.eval source
|
27
|
+
end
|
28
|
+
|
29
|
+
# Generate a binding containing the locally bound procs and constants
|
30
|
+
def generate_binding(a_binding=binding)
|
31
|
+
a_binding.local_variables do |local_var|
|
32
|
+
a_binding.local_variable_set local_var, nil
|
33
|
+
end
|
34
|
+
@bound_procs.zip(0..Float::INFINITY).each do |bound_proc, num|
|
35
|
+
a_binding.local_variable_set "proc_#{num}", bound_proc
|
36
|
+
end
|
37
|
+
@bound_constants.zip(0..Float::INFINITY).each do |bound_const, num|
|
38
|
+
a_binding.local_variable_set "const_#{num}", bound_const
|
39
|
+
end
|
40
|
+
a_binding
|
41
|
+
end
|
42
|
+
|
43
|
+
# The current local variable, of form: `"x_#{@local_var_count}"`
|
44
|
+
def local_var
|
45
|
+
"x_#{@local_var_count}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Assigns a new local variable to its argument (incrementing `@local_var_count`)
|
49
|
+
def new_local_var(other)
|
50
|
+
@local_var_count += 1
|
51
|
+
"#{self.local_var} = #{other}"
|
52
|
+
end
|
53
|
+
|
54
|
+
# Bound within `#bound_eval` as `proc_#{position in @bound_procs}`
|
55
|
+
def bind_proc(a_proc)
|
56
|
+
unless a_proc.is_a? Proc
|
57
|
+
raise ArgumentError, "#{a_proc.inspect} is not a Proc, it is a #{a_proc.class}".freeze
|
58
|
+
end
|
59
|
+
proc_string = "proc_#{@bound_procs.length}"
|
60
|
+
@bound_procs << a_proc.freeze
|
61
|
+
proc_string
|
62
|
+
end
|
63
|
+
|
64
|
+
# Bound within `#bound_eval` as `const_#{position in @bound_constants}`
|
65
|
+
def bind_const(a_const)
|
66
|
+
const_string = "const_#{@bound_constants.length}"
|
67
|
+
@bound_constants << a_const.freeze
|
68
|
+
const_string
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
data/lib/gen/if.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'gen/predicate'
|
2
|
+
|
3
|
+
# This class is just sugar on Gen::Predicate
|
4
|
+
|
5
|
+
module Gen
|
6
|
+
class Gen::If < Gen::Predicate
|
7
|
+
def self.[](*test_hashes)
|
8
|
+
test_hash = {}
|
9
|
+
test_hashes.each do |a_test_hash|
|
10
|
+
test_hash.merge! a_test_hash
|
11
|
+
end
|
12
|
+
a_class = Class.new
|
13
|
+
self.make_test a_class, :a_test, test_hash
|
14
|
+
a_class.freeze
|
15
|
+
a_class.new.method(:a_test).to_proc.freeze
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/gen/method.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
module Gen
|
3
|
+
class Gen::Method
|
4
|
+
# define a method on both the class and the instance
|
5
|
+
def self.def_self(method_name, &block)
|
6
|
+
define_method method_name, block # defines instance method
|
7
|
+
define_singleton_method method_name, block # defines class method
|
8
|
+
end
|
9
|
+
|
10
|
+
# define a block generator on both the class and the instance
|
11
|
+
def self.def_block_gen(name, initializer=name.to_s, default_args=nil)
|
12
|
+
def_self "#{name}_block".to_sym do |code_lines=nil, args=default_args, &block|
|
13
|
+
code_lines ||= block.call
|
14
|
+
["#{initializer} do #{args && "|#{args}|"}",
|
15
|
+
indent(code_lines),
|
16
|
+
"end"].join("\n")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Wrap a code block to keep variables local
|
21
|
+
# puts (Benchmark.measure do
|
22
|
+
# 1000000.times do
|
23
|
+
# CodeGen.wrap_it{}
|
24
|
+
# end
|
25
|
+
# end)
|
26
|
+
# 0.780000 0.020000 0.800000 ( 0.805655)
|
27
|
+
def_self :wrap_it do |&block|
|
28
|
+
block&.call
|
29
|
+
end.freeze
|
30
|
+
|
31
|
+
# indent some lines of code (with two spaces). Accepts a newline seperated string or an array of strings.
|
32
|
+
def_self :indent do |code_lines|
|
33
|
+
if code_lines.is_a? String
|
34
|
+
code_lines.lines.map{|line| " " + line}.join
|
35
|
+
elsif code_lines.is_a?(Array)
|
36
|
+
code_lines.map{|line| indent line}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'gen/code'
|
2
|
+
|
3
|
+
module Gen
|
4
|
+
class Gen::Predicate < Gen::Code
|
5
|
+
# you enter a nested hash where the branches define the execution path and the leaves either have a value
|
6
|
+
# (which will be compared with the actual value) or a single-argument Proc, which is called on it
|
7
|
+
# e.g.
|
8
|
+
# pry(main)> make_test({:to_a=> {:length=>5}}).call (1..4)
|
9
|
+
# => false
|
10
|
+
# pry(main)> make_test({:to_a=> {:length=>5}}).call (1..5)
|
11
|
+
# => true
|
12
|
+
def make_test_code(test_hash)
|
13
|
+
input_var = local_var
|
14
|
+
test_hash.map do |method, value|
|
15
|
+
[self.new_local_var("#{input_var}.#{method}"),
|
16
|
+
if value.is_a? Hash
|
17
|
+
make_test_code(value)
|
18
|
+
elsif value.is_a? Proc
|
19
|
+
"return false unless #{bind_proc value}.call(#{local_var})"
|
20
|
+
else
|
21
|
+
"return false unless #{bind_const value} == #{local_var} "
|
22
|
+
end]
|
23
|
+
end.flatten << "return true"
|
24
|
+
end
|
25
|
+
|
26
|
+
# given a class, a name for the test (instance) method, and a test hash, generate a predicate
|
27
|
+
def make_test(klass, test_name, test_hash)
|
28
|
+
test_code = self.make_test_code test_hash
|
29
|
+
test_code = self.proc_block test_code, 'x_0'
|
30
|
+
|
31
|
+
arg_vars = []
|
32
|
+
args = []
|
33
|
+
@bound_procs.zip(0..Float::INFINITY).each do |bound_proc, num|
|
34
|
+
arg_vars << "proc_#{num}"
|
35
|
+
args << bound_proc
|
36
|
+
end
|
37
|
+
@bound_constants.zip(0..Float::INFINITY).each do |bound_const, num|
|
38
|
+
arg_vars << "const_#{num}"
|
39
|
+
args << bound_const
|
40
|
+
end
|
41
|
+
|
42
|
+
test_code = self.proc_block test_code, arg_vars.to_s.gsub(/[\[\]\"]/, '').freeze
|
43
|
+
klass.class_exec do
|
44
|
+
define_method test_name, eval(test_code).call(*args)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# An alias of `#make_test`
|
49
|
+
def self.make_test(klass, test_name, test_hash)
|
50
|
+
self.new.make_test klass, test_name, test_hash
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
data/lib/nil_passer.rb
CHANGED
@@ -109,70 +109,414 @@ class NilPasser
|
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
112
|
-
|
113
|
-
|
112
|
+
|
113
|
+
|
114
|
+
|
115
|
+
# require 'rails'
|
116
|
+
# require_relative 'predicate_gen'
|
117
|
+
# require_relative 'code_gen'
|
118
|
+
# require_relative 'self'
|
119
|
+
|
120
|
+
# class Gen::MonitorArgs
|
121
|
+
# attr_accessor :block_given, :in, :only
|
122
|
+
# end
|
123
|
+
|
124
|
+
# class Gen::MonitorOptions
|
125
|
+
# # recurse the options, replacing convenient options with pedantic ones
|
126
|
+
# def recurse(options)
|
127
|
+
# if options.is_a? Hash
|
128
|
+
# options.map do |k, v|
|
129
|
+
# k2, v2 = if self.respond_to? "options_#{k}"
|
130
|
+
# self.send("options_#{k}", v) || [k, v]
|
131
|
+
# end
|
132
|
+
# v2 ||= self.recurse v2
|
133
|
+
# [k2, v2]
|
134
|
+
# end.to_h
|
135
|
+
# end
|
136
|
+
# end
|
137
|
+
|
138
|
+
# def options_in(x)
|
139
|
+
# if x
|
140
|
+
# if x.is_a?(Proc) && x.arity == 1
|
141
|
+
# # add proc(source_location) as prerequisite
|
142
|
+
# [:source_location, x]
|
143
|
+
# elsif x.is_a? Pathname
|
144
|
+
# if x.extname == ".rb"
|
145
|
+
# [:source_location, Proc.new{|source_location| source_location == x}]
|
146
|
+
# else
|
147
|
+
# [:source_location, Proc.new{|source_location| source_location.start_with?(x)}]
|
148
|
+
# end
|
149
|
+
# elsif x.is_a? String
|
150
|
+
# x = Pathname.new x
|
151
|
+
# if x.extname == ".rb"
|
152
|
+
# [:source_location, Proc.new{|source_location| source_location == x}]
|
153
|
+
# else
|
154
|
+
# [:source_location, Proc.new{|source_location| source_location.start_with?(x)}]
|
155
|
+
# end
|
156
|
+
# else
|
157
|
+
# raise ArgumentError, "Gen::MonitorOptions#in: Expected a single-argument Proc, a Pathname/String of a directory/source file location, or a list of those, got: #{x}"
|
158
|
+
# end
|
159
|
+
# end
|
160
|
+
# end
|
161
|
+
|
162
|
+
# def options_arguments(x)
|
163
|
+
# if x.is_a? Hash
|
164
|
+
# x.map do |k, v|
|
165
|
+
# if /_(?<num>\d+)/ =~ k
|
166
|
+
# if v.is_a? Proc
|
167
|
+
# [k, Proc.new{|y| v.call(y[num.to_i])}]
|
168
|
+
# else
|
169
|
+
# [k, Proc.new{|y| v == y[num.to_i] }]
|
170
|
+
# end
|
171
|
+
# elsif "has_defaults" == k.to_s && [false, true].include?(v)
|
172
|
+
# [k, Proc.new{|y| y.parameters.map{|z, _| z}.any?{|w| w == :opt} == v}]
|
173
|
+
# else
|
174
|
+
# [k, v]
|
175
|
+
# end
|
176
|
+
# end
|
177
|
+
# end
|
178
|
+
# end
|
179
|
+
|
180
|
+
# def options_only(x)
|
181
|
+
# if x.respond_to? :to_f
|
182
|
+
# Proc.new{ rand <= x.to_f }
|
183
|
+
# end
|
184
|
+
# end
|
185
|
+
# end
|
186
|
+
|
187
|
+
# # idea is that if predicate, pass args to a Proc and continue on. used for logging, tests, etc.
|
188
|
+
# class Gen::Monitor < Gen::Code
|
189
|
+
# def self.nice_options(options={})
|
190
|
+
# options
|
191
|
+
# end
|
192
|
+
|
193
|
+
# def self.class_method(options={}, &block)
|
194
|
+
# end
|
195
|
+
|
196
|
+
# def self.instance_method(options={}, &block)
|
197
|
+
# arguments_test = make_arguments_test (options[:arguments] || options[:args])
|
198
|
+
# block_test = make_block_test options[:block_given]
|
199
|
+
# in_test = make_in_test options[:in]
|
200
|
+
# method_test = make_method_test options[:method]
|
201
|
+
# only_test = make_only_test options[:only]
|
202
|
+
# Proc.new do |method, *args, &block|
|
203
|
+
# in
|
204
|
+
# method
|
205
|
+
# block
|
206
|
+
# arguments
|
207
|
+
# only
|
208
|
+
# end
|
209
|
+
# end
|
210
|
+
# end
|
211
|
+
|
212
|
+
|
213
|
+
# class NilPasser < MethodMonitor
|
214
|
+
# @@rails_path ||= Rails.root.to_s
|
215
|
+
|
216
|
+
# # instance_method block_given: { arity: 1 }, in: Rails.root, only: 0.5 do |method, args, block|
|
217
|
+
# instance_method block_given: { arity: 1, in: Rails.root } do |method, args, block|
|
114
218
|
# begin
|
115
|
-
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
#
|
219
|
+
# block.call nil
|
220
|
+
# rescue Exception => e
|
221
|
+
# puts "found block at #{block.source_location}, error: #{e.inspect}"
|
222
|
+
# end
|
223
|
+
# end
|
224
|
+
# end
|
225
|
+
|
226
|
+
|
227
|
+
|
228
|
+
|
229
|
+
# class NiceOptions
|
230
|
+
# # here, we reformat a few options to match the format of MethodTester
|
231
|
+
|
232
|
+
# # assume applies to all class/instance variables of given symbol (when called like MethodMonitor.monitor Klass, :sym)
|
233
|
+
# # default monitors do nothing
|
234
|
+
# # only adds the features used to the method (so probably have to generate source to remain fast)
|
235
|
+
# # only: (0..1) adds the monitor with percent probability
|
236
|
+
# # only: Fixnum adds only Fixnum monitors
|
237
|
+
# end
|
238
|
+
|
239
|
+
|
240
|
+
# class MethodMonitor
|
241
|
+
# def self.test(a_caller, block)
|
242
|
+
# if (block&.arity == 1) && a_caller.first&.start_with?(@@rails_path)
|
243
|
+
# begin
|
244
|
+
# block.call nil
|
245
|
+
# rescue Exception => e
|
246
|
+
# @@rails_logger.tagged("no-nil") { @@rails_logger.info "found a block that can't handle nil at #{block.source_location}, error: #{e.inspect}" }
|
247
|
+
# end
|
119
248
|
# end
|
120
249
|
# end
|
121
250
|
|
122
|
-
#
|
251
|
+
# def self.clone(klass, method_name)
|
123
252
|
# begin
|
124
|
-
#
|
125
|
-
#
|
253
|
+
# # ensure that the method is not recursively redefined
|
254
|
+
# klass.class_variable_get("@@old_#{method_name}".to_sym)
|
126
255
|
# rescue NameError
|
127
|
-
#
|
256
|
+
# klass.instance_method(method_name).clone
|
257
|
+
# end
|
258
|
+
# end
|
259
|
+
|
260
|
+
# # stuff -> @@old_stuff
|
261
|
+
# def to_old(x)
|
262
|
+
# # need to convert method-only symbols into symbols acceptible for class variables
|
263
|
+
# clean_x = x.to_s.gsub(/[^0-9a-zA-Z_]/) do |y|
|
264
|
+
# y.codepoints.map do |z|
|
265
|
+
# z.to_s
|
266
|
+
# end.join('_')
|
128
267
|
# end
|
268
|
+
# "@@old_#{clean_x}".to_sym
|
129
269
|
# end
|
130
270
|
|
131
|
-
# old_decls = (methods + no_arg_methods).map do |method|
|
132
|
-
# "@@old_#{sane method} = NilPasser.clone #{klass}, :#{method}"
|
133
|
-
# end.map{|str| " " + str}
|
134
271
|
|
135
|
-
#
|
136
|
-
#
|
137
|
-
#
|
138
|
-
#
|
139
|
-
#
|
140
|
-
#
|
272
|
+
# def initialize_instance(inst, method_name=nil, accepts_args=true)
|
273
|
+
# if method_name
|
274
|
+
# begin
|
275
|
+
# @obj = inst
|
276
|
+
# @method_name = method_name
|
277
|
+
# @orig_proc = inst.method(method_name).to_proc
|
278
|
+
# rescue NameError => e
|
279
|
+
# raise ArgumentError, "The provided method must be a valid method of the provided object, got: #{e}"
|
280
|
+
# end
|
281
|
+
# else
|
282
|
+
# (inst.methods - Object.methods).map do |method|
|
283
|
+
# NilPasser.new(obj, method)
|
284
|
+
# end
|
285
|
+
# end
|
141
286
|
# end
|
142
287
|
|
143
|
-
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
# " @@old_#{sane method}.bind(self)&.call(&block)",
|
147
|
-
# "end"
|
148
|
-
# ].map{|str| " " + str}
|
288
|
+
# def call(*args, &block)
|
289
|
+
# NilPasser.test caller, block
|
290
|
+
# @orig_proc.call(*args, &block)
|
149
291
|
# end
|
150
292
|
|
151
|
-
#
|
152
|
-
#
|
153
|
-
#
|
154
|
-
|
155
|
-
#
|
156
|
-
#
|
157
|
-
#
|
293
|
+
# def teardown
|
294
|
+
# @obj.send(:define_singleton_method, @method_name, @orig_proc)
|
295
|
+
# end
|
296
|
+
|
297
|
+
# def sanitize_options(options)
|
298
|
+
# clean_options = {}
|
299
|
+
# [:arguments_given, :block_given, :in, :has_defaults, :called_on].each do |symbol|
|
300
|
+
# clean_options[symbol] = options[symbol]
|
301
|
+
# end
|
302
|
+
# clean_options
|
303
|
+
# end
|
304
|
+
|
305
|
+
# # at the _where_ point, call predicate and only continue if it returns a truthy value
|
306
|
+
# def add_prerequisite_from_proc_or_equal(where, klass, predicate)
|
307
|
+
# end
|
308
|
+
|
309
|
+
# def singleton_method(*args, &block)
|
310
|
+
# def self.test_singleton_method
|
311
|
+
# end
|
312
|
+
# end
|
313
|
+
|
314
|
+
# def instance_method(*args, &block)
|
315
|
+
# def self.test_instance_method
|
316
|
+
# end
|
317
|
+
# end
|
318
|
+
|
319
|
+
# def class_method(*args, &block)
|
320
|
+
# def self.test_class_method
|
321
|
+
# end
|
322
|
+
# end
|
323
|
+
|
324
|
+
# def monitor_singleton_method(object, method_name=nil, accepts_args=true)
|
325
|
+
# end
|
158
326
|
|
159
|
-
#
|
160
|
-
#
|
327
|
+
# def monitor_instance_method(klass, method_name=nil, accepts_args=true)
|
328
|
+
# if method_name
|
329
|
+
# obj.class.class_variable_set(to_old method_name, self.clone(obj, method_name))
|
330
|
+
# if accepts_args
|
331
|
+
# # I don't know how to send this to klass without eval
|
332
|
+
# eval [ "class #{klass}",
|
333
|
+
# " def #{method_name}(*args, &block)",
|
334
|
+
# " #{self.class}.test_instance_method caller, block",
|
335
|
+
# " #{to_old method_name}.bind(self)&.call(*args, &block)",
|
336
|
+
# " end",
|
337
|
+
# "end"
|
338
|
+
# ].join("\n")
|
339
|
+
# else
|
340
|
+
# eval [ "class #{klass}",
|
341
|
+
# " def #{method_name}(&block)",
|
342
|
+
# " #{self.class}.test_instance_method caller, block",
|
343
|
+
# " #{to_old method_name}.bind(self)&.call(&block)",
|
344
|
+
# " end",
|
345
|
+
# "end"
|
346
|
+
# ].join("\n")
|
347
|
+
# end
|
348
|
+
# else
|
349
|
+
# klass.instance_methods.each{|method| self.monitor_instance_method method}
|
350
|
+
# end
|
351
|
+
# end
|
352
|
+
|
353
|
+
# def monitor_class_method(klass, method_name=nil, accepts_args=true)
|
354
|
+
# if method_name
|
355
|
+
# obj.class.class_variable_set(to_old(method_name, :class), self.clone(obj, method_name))
|
356
|
+
# if accepts_args
|
357
|
+
# # I don't know how to send this to klass without eval
|
358
|
+
# eval [ "class self.#{klass}",
|
359
|
+
# " def #{method_name}(*args, &block)",
|
360
|
+
# " #{self.class}.test_instance_method caller, block",
|
361
|
+
# " #{to_old method_name}.bind(self)&.call(*args, &block)",
|
362
|
+
# " end",
|
363
|
+
# "end"
|
364
|
+
# ].join("\n")
|
365
|
+
# else
|
366
|
+
# eval [ "class self.#{klass}",
|
367
|
+
# " def #{method_name}(&block)",
|
368
|
+
# " #{self.class}.test_instance_method caller, block",
|
369
|
+
# " #{to_old method_name}.bind(self)&.call(&block)",
|
370
|
+
# " end",
|
371
|
+
# "end"
|
372
|
+
# ].join("\n")
|
373
|
+
# end
|
374
|
+
# else
|
375
|
+
# klass.instance_methods.each{|method| self.monitor_instance_method method}
|
376
|
+
# end
|
377
|
+
# end
|
378
|
+
|
379
|
+
# def monitor(object, method_name=nil)
|
380
|
+
# if object.is_a?(Class)
|
381
|
+
# self.monitor_instance_method object, method_name
|
382
|
+
# self.monitor_class_method object, method_name
|
383
|
+
# else
|
384
|
+
# self.monitor_singleton_method object, method_name
|
385
|
+
# end
|
386
|
+
# end
|
161
387
|
# end
|
162
388
|
|
163
|
-
# eval generate_all_nil_passers(ActiveRecord::Relation)
|
164
|
-
# eval generate_all_nil_passers(Hash)
|
165
|
-
# eval generate_all_nil_passers(Enumerator)
|
166
|
-
# eval generate_all_nil_passers(Array)
|
167
|
-
# eval generate_all_nil_passers(String)
|
168
389
|
|
169
390
|
|
170
|
-
# eval generate_nil_passers(ActiveRecord::Relation, [:select, :find_each], [:each])
|
171
|
-
# eval generate_nil_passers(Hash, [], [:each_key, :each_value])
|
172
|
-
# eval generate_nil_passers(Enumerator, [], [:each])
|
173
|
-
# eval generate_nil_passers(Array, [:collect, :select], [:each, :map])
|
174
|
-
# eval generate_nil_passers(String, [:each_line])
|
175
391
|
|
176
|
-
#
|
392
|
+
# # class NilPasser < MethodMonitor
|
393
|
+
# # @@rails_path ||= Rails.root.to_s
|
394
|
+
|
395
|
+
# # # instance_method block_given: { arity: 1 }, in: Rails.root, only: 0.5 do |method, args, block|
|
396
|
+
# # instance_method block_given: { arity: 1, in: Rails.root } do |method, args, block|
|
397
|
+
# # begin
|
398
|
+
# # block.call nil
|
399
|
+
# # rescue Exception => e
|
400
|
+
# # puts "found block at #{block.source_location}, error: #{e.inspect}"
|
401
|
+
# # end
|
402
|
+
# # end
|
403
|
+
# # end
|
404
|
+
|
405
|
+
# # Array.monitor
|
406
|
+
|
407
|
+
# # Array.monitor(:map)
|
408
|
+
|
409
|
+
# # NilPasser.monitor Array, :map
|
410
|
+
# # NilPasser.monitor Array
|
411
|
+
# # NilPasser.monitor Array.method
|
412
|
+
|
413
|
+
|
414
|
+
# class NilPasser
|
415
|
+
# attr_accessor :obj, :method_name, :orig_proc
|
416
|
+
# @@rails_path ||= Rails.root.to_s
|
417
|
+
# @@rails_logger ||= Rails.logger
|
418
|
+
|
419
|
+
# def self.test(a_caller, block)
|
420
|
+
# if (block&.arity == 1) && a_caller.first&.start_with?(@@rails_path)
|
421
|
+
# begin
|
422
|
+
# block.call nil
|
423
|
+
# rescue Exception => e
|
424
|
+
# @@rails_logger.tagged("no-nil") { @@rails_logger.info "found a block that can't handle nil at #{block.source_location}, error: #{e.inspect}" }
|
425
|
+
# end
|
426
|
+
# end
|
427
|
+
# end
|
177
428
|
|
429
|
+
# def self.clone(klass, method_name)
|
430
|
+
# begin
|
431
|
+
# # ensure that the method is not recursively redefined
|
432
|
+
# klass.class_variable_get("@@old_#{method_name}".to_sym)
|
433
|
+
# rescue NameError
|
434
|
+
# klass.instance_method(method_name).clone
|
435
|
+
# end
|
436
|
+
# end
|
437
|
+
|
438
|
+
# def self.teardown(nil_passers)
|
439
|
+
# nil_passers.each do |nil_passer|
|
440
|
+
# nil_passer.teardown
|
441
|
+
# end
|
442
|
+
# end
|
443
|
+
|
444
|
+
# def initialize(obj, method_name=nil, accepts_args=true)
|
445
|
+
# if obj.is_a? Class
|
446
|
+
# self.initialize_class obj, method_name, accepts_args
|
447
|
+
# else
|
448
|
+
# self.initialize_instance obj, method_name, accepts_args
|
449
|
+
# end
|
450
|
+
# end
|
451
|
+
|
452
|
+
# # stuff -> @@old_stuff
|
453
|
+
# def to_old(x)
|
454
|
+
# # need to convert method-only symbols into symbols acceptible for class variables
|
455
|
+
# clean_x = x.to_s.gsub(/[^0-9a-zA-Z_]/) do |y|
|
456
|
+
# y.codepoints.map do |z|
|
457
|
+
# z.to_s
|
458
|
+
# end.join('_')
|
459
|
+
# end
|
460
|
+
# "@@old_#{clean_x}".to_sym
|
461
|
+
# end
|
462
|
+
|
463
|
+
# def initialize_class(klass, method_name=nil, accepts_args=true)
|
464
|
+
# if method_name
|
465
|
+
# obj.class.class_variable_set(to_old method_name, NilPasser.clone(obj, method_name))
|
466
|
+
# if accepts_args
|
467
|
+
# # I don't know how to send this to klass without eval
|
468
|
+
# eval [ "class #{klass}",
|
469
|
+
# " def #{method_name}(*args, &block)",
|
470
|
+
# " NilPasser.test caller, block",
|
471
|
+
# " #{to_old method_name}.bind(self)&.call(*args, &block)",
|
472
|
+
# " end",
|
473
|
+
# "end"
|
474
|
+
# ].join("\n")
|
475
|
+
# else
|
476
|
+
# eval [ "class #{klass}",
|
477
|
+
# " def #{method_name}(&block)",
|
478
|
+
# " NilPasser.test caller, block",
|
479
|
+
# " #{to_old method_name}.bind(self)&.call(&block)",
|
480
|
+
# " end",
|
481
|
+
# "end"
|
482
|
+
# ].join("\n")
|
483
|
+
# end
|
484
|
+
# else
|
485
|
+
# obj.methods.map do |method|
|
486
|
+
# NilPasser.new(obj, method)
|
487
|
+
# end
|
488
|
+
# end
|
489
|
+
# end
|
490
|
+
|
491
|
+
# def initialize_instance(inst, method_name=nil, accepts_args=true)
|
492
|
+
# if method_name
|
493
|
+
# begin
|
494
|
+
# @obj = inst
|
495
|
+
# @method_name = method_name
|
496
|
+
# @orig_proc = inst.method(method_name).to_proc
|
497
|
+
# rescue NameError => e
|
498
|
+
# raise ArgumentError, "The provided method must be a valid method of the provided object, got: #{e}"
|
499
|
+
# end
|
500
|
+
# else
|
501
|
+
# (inst.methods - Object.methods).map do |method|
|
502
|
+
# NilPasser.new(obj, method)
|
503
|
+
# end
|
504
|
+
# end
|
505
|
+
# end
|
506
|
+
|
507
|
+
# def setup
|
508
|
+
# @orig_proc = self.obj.method(@method_name).to_proc.clone
|
509
|
+
# @obj.send(:define_singleton_method, @method_name, self.method(:call).to_proc)
|
510
|
+
# self
|
511
|
+
# end
|
512
|
+
|
513
|
+
# def call(*args, &block)
|
514
|
+
# NilPasser.test caller, block
|
515
|
+
# @orig_proc.call(*args, &block)
|
516
|
+
# end
|
517
|
+
|
518
|
+
# def teardown
|
519
|
+
# @obj.send(:define_singleton_method, @method_name, @orig_proc)
|
520
|
+
# end
|
521
|
+
# end
|
178
522
|
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'utils/wrap_method'
|
2
|
+
|
3
|
+
# Example usage:
|
4
|
+
# instance_monitor Array, :map do |map|
|
5
|
+
# method_monitor nil, If[empty: true], If[args: 1] do |*args, &block|
|
6
|
+
# block.call nil
|
7
|
+
# [args, block]
|
8
|
+
# end
|
9
|
+
# end
|
10
|
+
|
11
|
+
|
12
|
+
class MonitorMethod < WrapMethod
|
13
|
+
def self.new
|
14
|
+
raise NoMethodError
|
15
|
+
end
|
16
|
+
|
17
|
+
# return a proc that takes a method
|
18
|
+
# block takes |*args, &block| and returns [new_args, new_block] or falsey
|
19
|
+
def self.method_monitor(args_predicate=nil, block_predicate=nil, &passed_block)
|
20
|
+
unless passed_block.parameters.map(&:first) == [:rest, :block]
|
21
|
+
raise ArgumentError, "must pass a block with arguments of the form: |*args, &block|, was of the form: #{passed_block&.parameters.inspect}".freeze
|
22
|
+
end
|
23
|
+
lambda do |_|
|
24
|
+
lambda do |*args, &block|
|
25
|
+
if !args_predicate || args_predicate.call(args)
|
26
|
+
if !block_predicate || block_predicate.call(block)
|
27
|
+
passed_block.call(*args, &block)
|
28
|
+
end
|
29
|
+
end || [args, block]
|
30
|
+
end
|
31
|
+
end.freeze
|
32
|
+
end
|
33
|
+
|
34
|
+
# return a proc that takes an unbound method
|
35
|
+
# block takes |bound_to, *args, &block| and returns [new_args, new_block] or falsey
|
36
|
+
def self.unbound_method_monitor(bound_predicate=nil, args_predicate=nil, block_predicate=nil, &passed_block)
|
37
|
+
unless passed_block.parameters.map(&:first) == [:opt, :rest, :block]
|
38
|
+
raise ArgumentError, "must pass a block with arguments of the form: |bound_to, *args, &block|, was of the form: #{passed_block&.parameters.inspect}".freeze
|
39
|
+
end
|
40
|
+
lambda do |_|
|
41
|
+
lambda do |bound_to|
|
42
|
+
if !bound_predicate || bound_predicate.call(bound_to)
|
43
|
+
lambda do |*args, &block|
|
44
|
+
if !args_predicate || args_predicate.call(args)
|
45
|
+
if !block_predicate || block_predicate.call(block)
|
46
|
+
passed_block.call(bound_to, *args, &block)
|
47
|
+
end
|
48
|
+
end || [args, block]
|
49
|
+
end
|
50
|
+
else
|
51
|
+
lambda do |*args, &block|
|
52
|
+
[args, block]
|
53
|
+
end.freeze
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end.freeze
|
57
|
+
end
|
58
|
+
|
59
|
+
class << self
|
60
|
+
alias :class_monitor :class_wrap
|
61
|
+
alias :class_monitor! :class_wrap!
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.class_monitors(klass, method_predicate=nil, &block)
|
65
|
+
if method_predicate
|
66
|
+
klass.public_methods.each do |method|
|
67
|
+
if method_predicate.call klass, method
|
68
|
+
self.class_monitor klass, method, &block
|
69
|
+
end
|
70
|
+
end
|
71
|
+
else
|
72
|
+
klass.public_methods.each do |method|
|
73
|
+
self.class_monitor klass, method, &block
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.classes_monitors(klass_predicate=nil, method_predicate=nil, &block)
|
79
|
+
if klass_predicate
|
80
|
+
ObjectSpace.each_object(Class) do |klass|
|
81
|
+
if klass_predicate.call klass
|
82
|
+
self.class_monitors klass, method_predicate, &block
|
83
|
+
end
|
84
|
+
end
|
85
|
+
else
|
86
|
+
ObjectSpace.each_object(Class) do |klass|
|
87
|
+
self.class_monitors klass, method_predicate, &block
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class << self
|
93
|
+
alias :instance_monitor :instance_wrap
|
94
|
+
alias :instance_monitor! :instance_wrap
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.instance_monitors(klass, method_predicate=nil, &block)
|
98
|
+
if method_predicate
|
99
|
+
klass.public_instance_methods.each do |method|
|
100
|
+
if method_predicate.call klass, method
|
101
|
+
self.instance_monitor klass, method, &block
|
102
|
+
end
|
103
|
+
end
|
104
|
+
else
|
105
|
+
klass.public_instance_methods.each do |method|
|
106
|
+
self.instance_monitor klass, method, &block
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.instances_monitors(klass_predicate=nil, method_predicate=nil, &block)
|
112
|
+
if klass_predicate
|
113
|
+
ObjectSpace.each_object(Class) do |klass|
|
114
|
+
if klass_predicate.call klass
|
115
|
+
self.instance_monitors klass, method_predicate, &block
|
116
|
+
end
|
117
|
+
end
|
118
|
+
else
|
119
|
+
ObjectSpace.each_object(Class) do |klass|
|
120
|
+
self.instance_monitors klass, method_predicate, &block
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class << self
|
126
|
+
alias :singleton_monitor :singleton_wrap
|
127
|
+
alias :singleton_monitor! :singleton_wrap!
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.singleton_monitors(object, method_predicate=nil, &block)
|
131
|
+
if method_predicate
|
132
|
+
object.public_methods.each do |method|
|
133
|
+
if method_predicate.call object, method
|
134
|
+
self.instance_monitor object, method, &block
|
135
|
+
end
|
136
|
+
end
|
137
|
+
else
|
138
|
+
object.public_methods.each do |method|
|
139
|
+
self.singleton_monitor object, method, &block
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
MonitorMethod.freeze
|
146
|
+
|
@@ -0,0 +1,134 @@
|
|
1
|
+
|
2
|
+
class WrapMethod
|
3
|
+
def self.oldify_name(method_name)
|
4
|
+
# need to convert method-only symbols into symbols acceptible for class variables
|
5
|
+
clean_method_name = method_name.to_s.gsub(/[^0-9a-zA-Z_]/) do |x|
|
6
|
+
x.codepoints.map do |y|
|
7
|
+
y.to_s
|
8
|
+
end.join('_')
|
9
|
+
end
|
10
|
+
"@@old_#{clean_method_name}".to_sym
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.class_method_exists?(klass, method_name)
|
14
|
+
if method_name.is_a?(Symbol) || method_name.is_a?(String)
|
15
|
+
klass.respond_to? method_name
|
16
|
+
else
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.instance_method_exists?(klass, method_name)
|
22
|
+
if method_name.is_a?(Symbol) || method_name.is_a?(String)
|
23
|
+
klass.method_defined? method_name
|
24
|
+
else
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
alias :singleton_method_exists? :class_method_exists?
|
31
|
+
end
|
32
|
+
|
33
|
+
# The block is passed the previous method (frozen).
|
34
|
+
# `rewrap=true` means the method will be redefined (safely) if it has already been redefined.
|
35
|
+
# Note that in redefinition, the previous wrapped definition is tossed.
|
36
|
+
def self.raw_class_wrap(klass, method_name, rewrap=false, &block)
|
37
|
+
old_method = nil
|
38
|
+
if self.class_method_exists? klass, self.oldify_name(method_name)
|
39
|
+
if rewrap
|
40
|
+
old_method = klass.method self.oldify_name(method_name)
|
41
|
+
else
|
42
|
+
return nil
|
43
|
+
end
|
44
|
+
else
|
45
|
+
old_method = klass.method(method_name).clone.freeze
|
46
|
+
klass.define_singleton_method self.oldify_name(method_name), old_method
|
47
|
+
end
|
48
|
+
klass.define_singleton_method method_name, block.call(old_method)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.class_wrap(klass, method_name, &block)
|
52
|
+
unless self.class_method_exists? klass, method_name
|
53
|
+
return nil
|
54
|
+
end
|
55
|
+
self.raw_class_wrap klass, method_name, &block
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.class_wrap!(klass, method_name, &block)
|
59
|
+
unless self.class_method_exists? klass, method_name
|
60
|
+
raise ArgumentError, "WrapMethod::class_wrap!: #{klass}::#{method_name} does not exist".freeze
|
61
|
+
end
|
62
|
+
self.raw_class_wrap klass, method_name, &block
|
63
|
+
end
|
64
|
+
|
65
|
+
# The block is passed the previous unbound method (frozen).
|
66
|
+
# `rewrap=true` means the method will be redefined (safely) if it has already been redefined.
|
67
|
+
# Note that in redefinition, the previous wrapped definition is tossed.
|
68
|
+
def self.raw_instance_wrap(klass, method_name, rewrap=false, &block)
|
69
|
+
old_method = nil
|
70
|
+
if self.instance_method_exists? klass, self.oldify_name(method_name)
|
71
|
+
if rewrap
|
72
|
+
old_method = klass.instance_method self.oldify_name(method_name)
|
73
|
+
else
|
74
|
+
return nil
|
75
|
+
end
|
76
|
+
else
|
77
|
+
old_method = klass.instance_method(method_name).clone.freeze
|
78
|
+
klass.send :define_method, self.oldify_name(method_name), old_method
|
79
|
+
end
|
80
|
+
klass.send :define_method, method_name, block.call(old_method)
|
81
|
+
end
|
82
|
+
|
83
|
+
# See `WrapMethod::raw_instance_wrap`. This adds a check that the method exists and returns `nil` if it doesn't
|
84
|
+
def self.instance_wrap(klass, method_name, rewrap=false, &block)
|
85
|
+
unless self.instance_method_exists? klass, method_name
|
86
|
+
return nil
|
87
|
+
end
|
88
|
+
self.raw_instance_wrap klass, method_name, rewrap, &block
|
89
|
+
end
|
90
|
+
|
91
|
+
# See `WrapMethod::raw_instance_wrap`. This adds a check that the method exists and raises an `ArgumentError` if it doesn't
|
92
|
+
def self.instance_wrap!(klass, method_name, rewrap=false, &block)
|
93
|
+
unless self.instance_method_exists? klass, method_name
|
94
|
+
raise ArgumentError, "WrapMethod::instance_wrap!: #{klass}::#{method_name} does not exist".freeze
|
95
|
+
end
|
96
|
+
self.raw_instance_wrap klass, method_name, rewrap, &block
|
97
|
+
end
|
98
|
+
|
99
|
+
# The block is passed the previous method (frozen).
|
100
|
+
# `rewrap=true` means the method will be redefined (safely) if it has already been redefined.
|
101
|
+
# Note that in redefinition, the previous wrapped definition is tossed.
|
102
|
+
def self.raw_singleton_wrap(object, method_name, rewrap=false, &block)
|
103
|
+
old_method = nil
|
104
|
+
if self.singleton_method_exists? object, self.oldify_name(method_name)
|
105
|
+
if rewrap
|
106
|
+
old_method = object.method self.oldify_name(method_name)
|
107
|
+
else
|
108
|
+
return nil
|
109
|
+
end
|
110
|
+
else
|
111
|
+
old_method = object.method(method_name).clone.freeze
|
112
|
+
object.define_singleton_method self.oldify_name(method_name), old_method
|
113
|
+
end
|
114
|
+
object.define_singleton_method method_name, block.call(old_method)
|
115
|
+
end
|
116
|
+
|
117
|
+
# See `WrapMethod::raw_singleton_wrap`. This adds a check that the method exists and returns `nil` if it doesn't
|
118
|
+
def self.singleton_wrap(object, method_name, rewrap=false, &block)
|
119
|
+
unless self.singleton_method_exists? object, method_name
|
120
|
+
return nil
|
121
|
+
end
|
122
|
+
self.raw_singleton_wrap object, method_name, rewrap=false, &block
|
123
|
+
end
|
124
|
+
|
125
|
+
# See `WrapMethod::raw_singleton_wrap`. This adds a check that the method exists and raises an `ArgumentError` if it doesn't
|
126
|
+
def self.singleton_wrap!(object, method_name, rewrap=false, &block)
|
127
|
+
unless self.singleton_method_exists? object, method_name
|
128
|
+
raise ArgumentError, "WrapMethod::singleton_wrap!: #{object}::#{method_name} does not exist".freeze
|
129
|
+
end
|
130
|
+
self.raw_singleton_wrap object, method_name, rewrap=false, &block
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nil-passer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Klein
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-03
|
11
|
+
date: 2017-04-03 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A nil-tester for blocks
|
14
14
|
email: lambdamichael@gmail.com
|
@@ -16,8 +16,15 @@ executables: []
|
|
16
16
|
extensions: []
|
17
17
|
extra_rdoc_files: []
|
18
18
|
files:
|
19
|
+
- lib/gen.rb
|
20
|
+
- lib/gen/code.rb
|
21
|
+
- lib/gen/if.rb
|
22
|
+
- lib/gen/method.rb
|
23
|
+
- lib/gen/predicate.rb
|
19
24
|
- lib/nil_passer.rb
|
20
|
-
|
25
|
+
- lib/utils/monitor_method.rb
|
26
|
+
- lib/utils/wrap_method.rb
|
27
|
+
homepage: https://michaeljklein.github.io/nil-passer/
|
21
28
|
licenses:
|
22
29
|
- MIT
|
23
30
|
metadata: {}
|
@@ -37,7 +44,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
37
44
|
version: '0'
|
38
45
|
requirements: []
|
39
46
|
rubyforge_project:
|
40
|
-
rubygems_version: 2.6.
|
47
|
+
rubygems_version: 2.6.12
|
41
48
|
signing_key:
|
42
49
|
specification_version: 4
|
43
50
|
summary: Pass nil to all the blocks in an app, catching and logging exceptions
|