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 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