nil-passer 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|