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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f6d055dc32bd54aaaaf42bcd4855ed0495a66d90
4
- data.tar.gz: a6ebb173920fb8932ea10737597b3a2929c5d5c9
3
+ metadata.gz: 7d414f7dcb6b414d6ee0187856bbdd4c6c7a5fb2
4
+ data.tar.gz: e3267e649eba8323f21ee0d98afbae30706337c1
5
5
  SHA512:
6
- metadata.gz: b8ebd1d43e856966534755ad0b52bd31a037db688039c36da80ae9297a856d9a0c24d03b038ae7690ede4b71ea40118bdabbd78aacd752bfeec2185c68448be1
7
- data.tar.gz: 25842a53a205c36958d3460d264f6e657904c7766a92cd494918fff89009af9860116cafb1fe981841e2807dfc43e4d642473df16012483781194fa9ef60caa5
6
+ metadata.gz: 6407a8c15abdd083f7f1bb12bb0467d091838c950d6a44289568714ed0f2c442d22080d8d91bcb6ed64049586d4b77dc0e7b0e999e24acaeabc7c8bcd37a9f12
7
+ data.tar.gz: 00cd12932a6610f117b4d105f62daa3f83dd174dc26dfb14e6f7bcdcb4fd5ee16f656308a35439136262875f9562bcf593fface3b29fd8771572b6e079d1efe5
@@ -0,0 +1,6 @@
1
+
2
+
3
+ # This module provides code and method generation tools
4
+
5
+ module Gen
6
+ end
@@ -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
+
@@ -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
@@ -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
+
@@ -109,70 +109,414 @@ class NilPasser
109
109
  end
110
110
  end
111
111
 
112
- # def generate_nil_passers(klass, methods, no_arg_methods=[])
113
- # methods = methods.select do |method|
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
- # klass.instance_method method
116
- # true
117
- # rescue NameError
118
- # false
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
- # no_arg_methods = no_arg_methods.select do |method|
251
+ # def self.clone(klass, method_name)
123
252
  # begin
124
- # klass.instance_method method
125
- # true
253
+ # # ensure that the method is not recursively redefined
254
+ # klass.class_variable_get("@@old_#{method_name}".to_sym)
126
255
  # rescue NameError
127
- # false
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
- # new_decls = methods.map do |method|
136
- # [ "def #{method}(*args, &block)",
137
- # " NilPasser.test caller, block",
138
- # " @@old_#{sane method}.bind(self)&.call(*args, &block)",
139
- # "end"
140
- # ].map{|str| " " + str}
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
- # new_no_arg_decls = no_arg_methods.map do |method|
144
- # [ "def #{method}(&block)",
145
- # " NilPasser.test caller, block",
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
- # [ "class #{klass}",
152
- # old_decls,
153
- # new_decls,
154
- # new_no_arg_decls,
155
- # "end"
156
- # ].flatten.join("\n")
157
- # end
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
- # def generate_all_nil_passers(klass)
160
- # generate_nil_passers klass, (klass.methods - klass.superclass.methods).select{|x| x.to_s.present?}
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
- # puts "**** loaded nil passer *********"
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.1.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-07 00:00:00.000000000 Z
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
- homepage: http://github.com/michaeljklein/nil-passer
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.8
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