sigterm_extensions 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +17 -0
  3. data/Gemfile +6 -0
  4. data/LICENSE.md +0 -0
  5. data/README.md +0 -0
  6. data/bin/ctxirb +156 -0
  7. data/lib/git.rb +166 -0
  8. data/lib/git/LICENSE +21 -0
  9. data/lib/git/author.rb +14 -0
  10. data/lib/git/base.rb +551 -0
  11. data/lib/git/base/factory.rb +75 -0
  12. data/lib/git/branch.rb +126 -0
  13. data/lib/git/branches.rb +71 -0
  14. data/lib/git/config.rb +22 -0
  15. data/lib/git/diff.rb +159 -0
  16. data/lib/git/index.rb +5 -0
  17. data/lib/git/lib.rb +1041 -0
  18. data/lib/git/log.rb +128 -0
  19. data/lib/git/object.rb +312 -0
  20. data/lib/git/path.rb +31 -0
  21. data/lib/git/remote.rb +36 -0
  22. data/lib/git/repository.rb +6 -0
  23. data/lib/git/stash.rb +27 -0
  24. data/lib/git/stashes.rb +55 -0
  25. data/lib/git/status.rb +199 -0
  26. data/lib/git/version.rb +5 -0
  27. data/lib/git/working_directory.rb +4 -0
  28. data/lib/sigterm_extensions.rb +75 -0
  29. data/lib/sigterm_extensions/all.rb +12 -0
  30. data/lib/sigterm_extensions/backtrace_cleaner.rb +129 -0
  31. data/lib/sigterm_extensions/callbacks.rb +847 -0
  32. data/lib/sigterm_extensions/concern.rb +169 -0
  33. data/lib/sigterm_extensions/configurable.rb +38 -0
  34. data/lib/sigterm_extensions/core_ext.rb +4 -0
  35. data/lib/sigterm_extensions/core_ext/array.rb +3 -0
  36. data/lib/sigterm_extensions/core_ext/array/extract.rb +19 -0
  37. data/lib/sigterm_extensions/core_ext/array/extract_options.rb +29 -0
  38. data/lib/sigterm_extensions/core_ext/class.rb +3 -0
  39. data/lib/sigterm_extensions/core_ext/class/attribute.rb +139 -0
  40. data/lib/sigterm_extensions/core_ext/class/attribute_accessors.rb +4 -0
  41. data/lib/sigterm_extensions/core_ext/class/subclasses.rb +52 -0
  42. data/lib/sigterm_extensions/core_ext/custom.rb +12 -0
  43. data/lib/sigterm_extensions/core_ext/digest.rb +3 -0
  44. data/lib/sigterm_extensions/core_ext/digest/uuid.rb +51 -0
  45. data/lib/sigterm_extensions/core_ext/enumerable.rb +232 -0
  46. data/lib/sigterm_extensions/core_ext/file.rb +3 -0
  47. data/lib/sigterm_extensions/core_ext/file/atomic.rb +68 -0
  48. data/lib/sigterm_extensions/core_ext/hash.rb +3 -0
  49. data/lib/sigterm_extensions/core_ext/hash/deep_merge.rb +41 -0
  50. data/lib/sigterm_extensions/core_ext/hash/deep_transform_values.rb +44 -0
  51. data/lib/sigterm_extensions/core_ext/hash/except.rb +22 -0
  52. data/lib/sigterm_extensions/core_ext/hash/keys.rb +141 -0
  53. data/lib/sigterm_extensions/core_ext/hash/reverse_merge.rb +23 -0
  54. data/lib/sigterm_extensions/core_ext/hash/slice.rb +24 -0
  55. data/lib/sigterm_extensions/core_ext/kernel.rb +3 -0
  56. data/lib/sigterm_extensions/core_ext/kernel/concern.rb +12 -0
  57. data/lib/sigterm_extensions/core_ext/kernel/reporting.rb +43 -0
  58. data/lib/sigterm_extensions/core_ext/kernel/singleton_class.rb +6 -0
  59. data/lib/sigterm_extensions/core_ext/load_error.rb +7 -0
  60. data/lib/sigterm_extensions/core_ext/module.rb +3 -0
  61. data/lib/sigterm_extensions/core_ext/module/aliasing.rb +29 -0
  62. data/lib/sigterm_extensions/core_ext/module/anonymous.rb +28 -0
  63. data/lib/sigterm_extensions/core_ext/module/attr_internal.rb +36 -0
  64. data/lib/sigterm_extensions/core_ext/module/attribute_accessors.rb +208 -0
  65. data/lib/sigterm_extensions/core_ext/module/attribute_accessors_per_thread.rb +146 -0
  66. data/lib/sigterm_extensions/core_ext/module/concerning.rb +132 -0
  67. data/lib/sigterm_extensions/core_ext/module/delegation.rb +319 -0
  68. data/lib/sigterm_extensions/core_ext/module/redefine_method.rb +38 -0
  69. data/lib/sigterm_extensions/core_ext/module/remove_method.rb +15 -0
  70. data/lib/sigterm_extensions/core_ext/name_error.rb +36 -0
  71. data/lib/sigterm_extensions/core_ext/object.rb +3 -0
  72. data/lib/sigterm_extensions/core_ext/object/blank.rb +153 -0
  73. data/lib/sigterm_extensions/core_ext/object/colors.rb +39 -0
  74. data/lib/sigterm_extensions/core_ext/object/duplicable.rb +47 -0
  75. data/lib/sigterm_extensions/core_ext/object/inclusion.rb +27 -0
  76. data/lib/sigterm_extensions/core_ext/object/instance_variables.rb +28 -0
  77. data/lib/sigterm_extensions/core_ext/object/methods.rb +61 -0
  78. data/lib/sigterm_extensions/core_ext/object/with_options.rb +80 -0
  79. data/lib/sigterm_extensions/core_ext/range.rb +3 -0
  80. data/lib/sigterm_extensions/core_ext/range/compare_range.rb +74 -0
  81. data/lib/sigterm_extensions/core_ext/range/conversions.rb +39 -0
  82. data/lib/sigterm_extensions/core_ext/range/overlaps.rb +8 -0
  83. data/lib/sigterm_extensions/core_ext/securerandom.rb +43 -0
  84. data/lib/sigterm_extensions/core_ext/string.rb +3 -0
  85. data/lib/sigterm_extensions/core_ext/string/access.rb +93 -0
  86. data/lib/sigterm_extensions/core_ext/string/filters.rb +143 -0
  87. data/lib/sigterm_extensions/core_ext/string/starts_ends_with.rb +4 -0
  88. data/lib/sigterm_extensions/core_ext/string/strip.rb +25 -0
  89. data/lib/sigterm_extensions/core_ext/tryable.rb +132 -0
  90. data/lib/sigterm_extensions/descendants_tracker.rb +108 -0
  91. data/lib/sigterm_extensions/gem_methods.rb +47 -0
  92. data/lib/sigterm_extensions/hash_binding.rb +16 -0
  93. data/lib/sigterm_extensions/inflector.rb +339 -0
  94. data/lib/sigterm_extensions/inflector/acronyms.rb +42 -0
  95. data/lib/sigterm_extensions/inflector/inflections.rb +249 -0
  96. data/lib/sigterm_extensions/inflector/inflections/defaults.rb +117 -0
  97. data/lib/sigterm_extensions/inflector/rules.rb +37 -0
  98. data/lib/sigterm_extensions/inflector/version.rb +8 -0
  99. data/lib/sigterm_extensions/interactive_editor.rb +120 -0
  100. data/lib/sigterm_extensions/lazy.rb +34 -0
  101. data/lib/sigterm_extensions/lazy_load_hooks.rb +79 -0
  102. data/lib/sigterm_extensions/option_merger.rb +32 -0
  103. data/lib/sigterm_extensions/ordered_hash.rb +48 -0
  104. data/lib/sigterm_extensions/ordered_options.rb +83 -0
  105. data/lib/sigterm_extensions/paths.rb +235 -0
  106. data/lib/sigterm_extensions/per_thread_registry.rb +58 -0
  107. data/lib/sigterm_extensions/proxy_object.rb +14 -0
  108. data/lib/sigterm_extensions/staging/boot.rb +31 -0
  109. data/lib/sigterm_extensions/staging/boot/bundler_patch.rb +24 -0
  110. data/lib/sigterm_extensions/staging/boot/command.rb +26 -0
  111. data/lib/sigterm_extensions/staging/boot/gemfile_next_auto_sync.rb +79 -0
  112. data/lib/sigterm_extensions/version.rb +4 -0
  113. data/lib/sigterm_extensions/wrappable.rb +16 -0
  114. data/sigterm_extensions.gemspec +42 -0
  115. data/templates/dotpryrc.rb.erb +124 -0
  116. metadata +315 -0
@@ -0,0 +1,847 @@
1
+ require "sigterm_extensions/concern"
2
+ require "sigterm_extensions/descendants_tracker"
3
+ require "sigterm_extensions/core_ext/array/extract_options"
4
+ require "sigterm_extensions/core_ext/class/attribute"
5
+ require "sigterm_extensions/core_ext/kernel/reporting"
6
+ require "sigterm_extensions/core_ext/kernel/singleton_class"
7
+ require "sigterm_extensions/core_ext/string/filters"
8
+ require "thread"
9
+
10
+ module SigtermExtensions
11
+ # Callbacks are code hooks that are run at key points in an object's life cycle.
12
+ # The typical use case is to have a base class define a set of callbacks
13
+ # relevant to the other functionality it supplies, so that subclasses can
14
+ # install callbacks that enhance or modify the base functionality without
15
+ # needing to override or redefine methods of the base class.
16
+ #
17
+ # Mixing in this module allows you to define the events in the object's
18
+ # life cycle that will support callbacks (via +ClassMethods.define_callbacks+),
19
+ # set the instance methods, procs, or callback objects to be called (via
20
+ # +ClassMethods.set_callback+), and run the installed callbacks at the
21
+ # appropriate times (via +run_callbacks+).
22
+ #
23
+ # By default callbacks are halted by throwing +:abort+.
24
+ # See +ClassMethods.define_callbacks+ for details.
25
+ #
26
+ # Three kinds of callbacks are supported: before callbacks, run before a
27
+ # certain event; after callbacks, run after the event; and around callbacks,
28
+ # blocks that surround the event, triggering it when they yield. Callback code
29
+ # can be contained in instance methods, procs or lambdas, or callback objects
30
+ # that respond to certain predetermined methods. See +ClassMethods.set_callback+
31
+ # for details.
32
+ #
33
+ # class Record
34
+ # include SigtermExtensions::Callbacks
35
+ # define_callbacks :save
36
+ #
37
+ # def save
38
+ # run_callbacks :save do
39
+ # puts "- save"
40
+ # end
41
+ # end
42
+ # end
43
+ #
44
+ # class PersonRecord < Record
45
+ # set_callback :save, :before, :saving_message
46
+ # def saving_message
47
+ # puts "saving..."
48
+ # end
49
+ #
50
+ # set_callback :save, :after do |object|
51
+ # puts "saved"
52
+ # end
53
+ # end
54
+ #
55
+ # person = PersonRecord.new
56
+ # person.save
57
+ #
58
+ # Output:
59
+ # saving...
60
+ # - save
61
+ # saved
62
+ module Callbacks
63
+ extend Concern
64
+
65
+ included do
66
+ extend SigtermExtensions::DescendantsTracker
67
+ class_attribute :__callbacks, instance_writer: false, default: {}
68
+ end
69
+
70
+ CALLBACK_FILTER_TYPES = [:before, :after, :around]
71
+
72
+ # Runs the callbacks for the given event.
73
+ #
74
+ # Calls the before and around callbacks in the order they were set, yields
75
+ # the block (if given one), and then runs the after callbacks in reverse
76
+ # order.
77
+ #
78
+ # If the callback chain was halted, returns +false+. Otherwise returns the
79
+ # result of the block, +nil+ if no callbacks have been set, or +true+
80
+ # if callbacks have been set but no block is given.
81
+ #
82
+ # run_callbacks :save do
83
+ # save
84
+ # end
85
+ #
86
+ #--
87
+ #
88
+ # As this method is used in many places, and often wraps large portions of
89
+ # user code, it has an additional design goal of minimizing its impact on
90
+ # the visible call stack. An exception from inside a :before or :after
91
+ # callback can be as noisy as it likes -- but when control has passed
92
+ # smoothly through and into the supplied block, we want as little evidence
93
+ # as possible that we were here.
94
+ def run_callbacks(kind)
95
+ callbacks = __callbacks[kind.to_sym]
96
+
97
+ if callbacks.empty?
98
+ yield if block_given?
99
+ else
100
+ env = Filters::Environment.new(self, false, nil)
101
+ next_sequence = callbacks.compile
102
+
103
+ invoke_sequence = Proc.new do
104
+ skipped = nil
105
+ while true
106
+ current = next_sequence
107
+ current.invoke_before(env)
108
+ if current.final?
109
+ env.value = !env.halted && (!block_given? || yield)
110
+ elsif current.skip?(env)
111
+ (skipped ||= []) << current
112
+ next_sequence = next_sequence.nested
113
+ next
114
+ else
115
+ next_sequence = next_sequence.nested
116
+ begin
117
+ target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
118
+ target.send(method, *arguments, &block)
119
+ ensure
120
+ next_sequence = current
121
+ end
122
+ end
123
+ current.invoke_after(env)
124
+ skipped.pop.invoke_after(env) while skipped && skipped.first
125
+ break env.value
126
+ end
127
+ end
128
+
129
+ # Common case: no 'around' callbacks defined
130
+ if next_sequence.final?
131
+ next_sequence.invoke_before(env)
132
+ env.value = !env.halted && (!block_given? || yield)
133
+ next_sequence.invoke_after(env)
134
+ env.value
135
+ else
136
+ invoke_sequence.call
137
+ end
138
+ end
139
+ end
140
+
141
+ private
142
+ # A hook invoked every time a before callback is halted.
143
+ # This can be overridden in SigtermExtensions::Callbacks implementors in order
144
+ # to provide better debugging/logging.
145
+ def halted_callback_hook(filter)
146
+ end
147
+
148
+ module Conditionals # :nodoc:
149
+ class Value
150
+ def initialize(&block)
151
+ @block = block
152
+ end
153
+ def call(target, value); @block.call(value); end
154
+ end
155
+ end
156
+
157
+ module Filters
158
+ Environment = Struct.new(:target, :halted, :value)
159
+
160
+ class Before
161
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter)
162
+ halted_lambda = chain_config[:terminator]
163
+
164
+ if user_conditions.any?
165
+ halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
166
+ else
167
+ halting(callback_sequence, user_callback, halted_lambda, filter)
168
+ end
169
+ end
170
+
171
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
172
+ callback_sequence.before do |env|
173
+ target = env.target
174
+ value = env.value
175
+ halted = env.halted
176
+
177
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
178
+ result_lambda = -> { user_callback.call target, value }
179
+ env.halted = halted_lambda.call(target, result_lambda)
180
+ if env.halted
181
+ target.send :halted_callback_hook, filter
182
+ end
183
+ end
184
+
185
+ env
186
+ end
187
+ end
188
+ private_class_method :halting_and_conditional
189
+
190
+ def self.halting(callback_sequence, user_callback, halted_lambda, filter)
191
+ callback_sequence.before do |env|
192
+ target = env.target
193
+ value = env.value
194
+ halted = env.halted
195
+
196
+ unless halted
197
+ result_lambda = -> { user_callback.call target, value }
198
+ env.halted = halted_lambda.call(target, result_lambda)
199
+
200
+ if env.halted
201
+ target.send :halted_callback_hook, filter
202
+ end
203
+ end
204
+
205
+ env
206
+ end
207
+ end
208
+ private_class_method :halting
209
+ end
210
+
211
+ class After
212
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config)
213
+ if chain_config[:skip_after_callbacks_if_terminated]
214
+ if user_conditions.any?
215
+ halting_and_conditional(callback_sequence, user_callback, user_conditions)
216
+ else
217
+ halting(callback_sequence, user_callback)
218
+ end
219
+ else
220
+ if user_conditions.any?
221
+ conditional callback_sequence, user_callback, user_conditions
222
+ else
223
+ simple callback_sequence, user_callback
224
+ end
225
+ end
226
+ end
227
+
228
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
229
+ callback_sequence.after do |env|
230
+ target = env.target
231
+ value = env.value
232
+ halted = env.halted
233
+
234
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
235
+ user_callback.call target, value
236
+ end
237
+
238
+ env
239
+ end
240
+ end
241
+ private_class_method :halting_and_conditional
242
+
243
+ def self.halting(callback_sequence, user_callback)
244
+ callback_sequence.after do |env|
245
+ unless env.halted
246
+ user_callback.call env.target, env.value
247
+ end
248
+
249
+ env
250
+ end
251
+ end
252
+ private_class_method :halting
253
+
254
+ def self.conditional(callback_sequence, user_callback, user_conditions)
255
+ callback_sequence.after do |env|
256
+ target = env.target
257
+ value = env.value
258
+
259
+ if user_conditions.all? { |c| c.call(target, value) }
260
+ user_callback.call target, value
261
+ end
262
+
263
+ env
264
+ end
265
+ end
266
+ private_class_method :conditional
267
+
268
+ def self.simple(callback_sequence, user_callback)
269
+ callback_sequence.after do |env|
270
+ user_callback.call env.target, env.value
271
+
272
+ env
273
+ end
274
+ end
275
+ private_class_method :simple
276
+ end
277
+ end
278
+
279
+ class Callback #:nodoc:#
280
+ def self.build(chain, filter, kind, options)
281
+ if filter.is_a?(String)
282
+ raise ArgumentError, <<-MSG.squish
283
+ Passing string to define a callback is not supported. See the `.set_callback`
284
+ documentation to see supported values.
285
+ MSG
286
+ end
287
+
288
+ new chain.name, filter, kind, options, chain.config
289
+ end
290
+
291
+ attr_accessor :kind, :name
292
+ attr_reader :chain_config
293
+
294
+ def initialize(name, filter, kind, options, chain_config)
295
+ @chain_config = chain_config
296
+ @name = name
297
+ @kind = kind
298
+ @filter = filter
299
+ @key = compute_identifier filter
300
+ @if = check_conditionals(Array(options[:if]))
301
+ @unless = check_conditionals(Array(options[:unless]))
302
+ end
303
+
304
+ def filter; @key; end
305
+ def raw_filter; @filter; end
306
+
307
+ def merge_conditional_options(chain, if_option:, unless_option:)
308
+ options = {
309
+ if: @if.dup,
310
+ unless: @unless.dup
311
+ }
312
+
313
+ options[:if].concat Array(unless_option)
314
+ options[:unless].concat Array(if_option)
315
+
316
+ self.class.build chain, @filter, @kind, options
317
+ end
318
+
319
+ def matches?(_kind, _filter)
320
+ @kind == _kind && filter == _filter
321
+ end
322
+
323
+ def duplicates?(other)
324
+ case @filter
325
+ when Symbol
326
+ matches?(other.kind, other.filter)
327
+ else
328
+ false
329
+ end
330
+ end
331
+
332
+ # Wraps code with filter
333
+ def apply(callback_sequence)
334
+ user_conditions = conditions_lambdas
335
+ user_callback = CallTemplate.build(@filter, self)
336
+
337
+ case kind
338
+ when :before
339
+ Filters::Before.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config, @filter)
340
+ when :after
341
+ Filters::After.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config)
342
+ when :around
343
+ callback_sequence.around(user_callback, user_conditions)
344
+ end
345
+ end
346
+
347
+ def current_scopes
348
+ Array(chain_config[:scope]).map { |s| public_send(s) }
349
+ end
350
+
351
+ private
352
+ def check_conditionals(conditionals)
353
+ if conditionals.any? { |c| c.is_a?(String) }
354
+ raise ArgumentError, <<-MSG.squish
355
+ Passing string to be evaluated in :if and :unless conditional
356
+ options is not supported. Pass a symbol for an instance method,
357
+ or a lambda, proc or block, instead.
358
+ MSG
359
+ end
360
+
361
+ conditionals
362
+ end
363
+
364
+ def compute_identifier(filter)
365
+ case filter
366
+ when ::Proc
367
+ filter.object_id
368
+ else
369
+ filter
370
+ end
371
+ end
372
+
373
+ def conditions_lambdas
374
+ @if.map { |c| CallTemplate.build(c, self).make_lambda } +
375
+ @unless.map { |c| CallTemplate.build(c, self).inverted_lambda }
376
+ end
377
+ end
378
+
379
+ # A future invocation of user-supplied code (either as a callback,
380
+ # or a condition filter).
381
+ class CallTemplate # :nodoc:
382
+ def initialize(target, method, arguments, block)
383
+ @override_target = target
384
+ @method_name = method
385
+ @arguments = arguments
386
+ @override_block = block
387
+ end
388
+
389
+ # Return the parts needed to make this call, with the given
390
+ # input values.
391
+ #
392
+ # Returns an array of the form:
393
+ #
394
+ # [target, block, method, *arguments]
395
+ #
396
+ # This array can be used as such:
397
+ #
398
+ # target.send(method, *arguments, &block)
399
+ #
400
+ # The actual invocation is left up to the caller to minimize
401
+ # call stack pollution.
402
+ def expand(target, value, block)
403
+ result = @arguments.map { |arg|
404
+ case arg
405
+ when :value; value
406
+ when :target; target
407
+ when :block; block || raise(ArgumentError)
408
+ end
409
+ }
410
+
411
+ result.unshift @method_name
412
+ result.unshift @override_block || block
413
+ result.unshift @override_target || target
414
+
415
+ # target, block, method, *arguments = result
416
+ # target.send(method, *arguments, &block)
417
+ result
418
+ end
419
+
420
+ # Return a lambda that will make this call when given the input
421
+ # values.
422
+ def make_lambda
423
+ lambda do |target, value, &block|
424
+ target, block, method, *arguments = expand(target, value, block)
425
+ target.send(method, *arguments, &block)
426
+ end
427
+ end
428
+
429
+ # Return a lambda that will make this call when given the input
430
+ # values, but then return the boolean inverse of that result.
431
+ def inverted_lambda
432
+ lambda do |target, value, &block|
433
+ target, block, method, *arguments = expand(target, value, block)
434
+ ! target.send(method, *arguments, &block)
435
+ end
436
+ end
437
+
438
+ # Filters support:
439
+ #
440
+ # Symbols:: A method to call.
441
+ # Procs:: A proc to call with the object.
442
+ # Objects:: An object with a <tt>before_foo</tt> method on it to call.
443
+ #
444
+ # All of these objects are converted into a CallTemplate and handled
445
+ # the same after this point.
446
+ def self.build(filter, callback)
447
+ case filter
448
+ when Symbol
449
+ new(nil, filter, [], nil)
450
+ when Conditionals::Value
451
+ new(filter, :call, [:target, :value], nil)
452
+ when ::Proc
453
+ if filter.arity > 1
454
+ new(nil, :instance_exec, [:target, :block], filter)
455
+ elsif filter.arity > 0
456
+ new(nil, :instance_exec, [:target], filter)
457
+ else
458
+ new(nil, :instance_exec, [], filter)
459
+ end
460
+ else
461
+ method_to_call = callback.current_scopes.join("_")
462
+
463
+ new(filter, method_to_call, [:target], nil)
464
+ end
465
+ end
466
+ end
467
+
468
+ # Execute before and after filters in a sequence instead of
469
+ # chaining them with nested lambda calls, see:
470
+ # https://github.com/rails/rails/issues/18011
471
+ class CallbackSequence # :nodoc:
472
+ def initialize(nested = nil, call_template = nil, user_conditions = nil)
473
+ @nested = nested
474
+ @call_template = call_template
475
+ @user_conditions = user_conditions
476
+
477
+ @before = []
478
+ @after = []
479
+ end
480
+
481
+ def before(&before)
482
+ @before.unshift(before)
483
+ self
484
+ end
485
+
486
+ def after(&after)
487
+ @after.push(after)
488
+ self
489
+ end
490
+
491
+ def around(call_template, user_conditions)
492
+ CallbackSequence.new(self, call_template, user_conditions)
493
+ end
494
+
495
+ def skip?(arg)
496
+ arg.halted || !@user_conditions.all? { |c| c.call(arg.target, arg.value) }
497
+ end
498
+
499
+ attr_reader :nested
500
+
501
+ def final?
502
+ !@call_template
503
+ end
504
+
505
+ def expand_call_template(arg, block)
506
+ @call_template.expand(arg.target, arg.value, block)
507
+ end
508
+
509
+ def invoke_before(arg)
510
+ @before.each { |b| b.call(arg) }
511
+ end
512
+
513
+ def invoke_after(arg)
514
+ @after.each { |a| a.call(arg) }
515
+ end
516
+ end
517
+
518
+ class CallbackChain #:nodoc:#
519
+ include Enumerable
520
+
521
+ attr_reader :name, :config
522
+
523
+ def initialize(name, config)
524
+ @name = name
525
+ @config = {
526
+ scope: [:kind],
527
+ terminator: default_terminator
528
+ }.merge!(config)
529
+ @chain = []
530
+ @callbacks = nil
531
+ @mutex = Mutex.new
532
+ end
533
+
534
+ def each(&block); @chain.each(&block); end
535
+ def index(o); @chain.index(o); end
536
+ def empty?; @chain.empty?; end
537
+
538
+ def insert(index, o)
539
+ @callbacks = nil
540
+ @chain.insert(index, o)
541
+ end
542
+
543
+ def delete(o)
544
+ @callbacks = nil
545
+ @chain.delete(o)
546
+ end
547
+
548
+ def clear
549
+ @callbacks = nil
550
+ @chain.clear
551
+ self
552
+ end
553
+
554
+ def initialize_copy(other)
555
+ @callbacks = nil
556
+ @chain = other.chain.dup
557
+ @mutex = Mutex.new
558
+ end
559
+
560
+ def compile
561
+ @callbacks || @mutex.synchronize do
562
+ final_sequence = CallbackSequence.new
563
+ @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
564
+ callback.apply callback_sequence
565
+ end
566
+ end
567
+ end
568
+
569
+ def append(*callbacks)
570
+ callbacks.each { |c| append_one(c) }
571
+ end
572
+
573
+ def prepend(*callbacks)
574
+ callbacks.each { |c| prepend_one(c) }
575
+ end
576
+
577
+ protected
578
+ attr_reader :chain
579
+
580
+ private
581
+ def append_one(callback)
582
+ @callbacks = nil
583
+ remove_duplicates(callback)
584
+ @chain.push(callback)
585
+ end
586
+
587
+ def prepend_one(callback)
588
+ @callbacks = nil
589
+ remove_duplicates(callback)
590
+ @chain.unshift(callback)
591
+ end
592
+
593
+ def remove_duplicates(callback)
594
+ @callbacks = nil
595
+ @chain.delete_if { |c| callback.duplicates?(c) }
596
+ end
597
+
598
+ def default_terminator
599
+ Proc.new do |target, result_lambda|
600
+ terminate = true
601
+ catch(:abort) do
602
+ result_lambda.call
603
+ terminate = false
604
+ end
605
+ terminate
606
+ end
607
+ end
608
+ end
609
+
610
+ module ClassMethods
611
+ def normalize_callback_params(filters, block) # :nodoc:
612
+ type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before
613
+ options = filters.extract_options!
614
+ filters.unshift(block) if block
615
+ [type, filters, options.dup]
616
+ end
617
+
618
+ # This is used internally to append, prepend and skip callbacks to the
619
+ # CallbackChain.
620
+ def __update_callbacks(name) #:nodoc:
621
+ ([self] + SigtermExtensions::DescendantsTracker.descendants(self)).reverse_each do |target|
622
+ chain = target.get_callbacks name
623
+ yield target, chain.dup
624
+ end
625
+ end
626
+
627
+ # Install a callback for the given event.
628
+ #
629
+ # set_callback :save, :before, :before_method
630
+ # set_callback :save, :after, :after_method, if: :condition
631
+ # set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
632
+ #
633
+ # The second argument indicates whether the callback is to be run +:before+,
634
+ # +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
635
+ # means the first example above can also be written as:
636
+ #
637
+ # set_callback :save, :before_method
638
+ #
639
+ # The callback can be specified as a symbol naming an instance method; as a
640
+ # proc, lambda, or block; or as an object that responds to a certain method
641
+ # determined by the <tt>:scope</tt> argument to +define_callbacks+.
642
+ #
643
+ # If a proc, lambda, or block is given, its body is evaluated in the context
644
+ # of the current object. It can also optionally accept the current object as
645
+ # an argument.
646
+ #
647
+ # Before and around callbacks are called in the order that they are set;
648
+ # after callbacks are called in the reverse order.
649
+ #
650
+ # Around callbacks can access the return value from the event, if it
651
+ # wasn't halted, from the +yield+ call.
652
+ #
653
+ # ===== Options
654
+ #
655
+ # * <tt>:if</tt> - A symbol or an array of symbols, each naming an instance
656
+ # method or a proc; the callback will be called only when they all return
657
+ # a true value.
658
+ #
659
+ # If a proc is given, its body is evaluated in the context of the
660
+ # current object. It can also optionally accept the current object as
661
+ # an argument.
662
+ # * <tt>:unless</tt> - A symbol or an array of symbols, each naming an
663
+ # instance method or a proc; the callback will be called only when they
664
+ # all return a false value.
665
+ #
666
+ # If a proc is given, its body is evaluated in the context of the
667
+ # current object. It can also optionally accept the current object as
668
+ # an argument.
669
+ # * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
670
+ # existing chain rather than appended.
671
+ def set_callback(name, *filter_list, &block)
672
+ type, filters, options = normalize_callback_params(filter_list, block)
673
+
674
+ self_chain = get_callbacks name
675
+ mapped = filters.map do |filter|
676
+ Callback.build(self_chain, filter, type, options)
677
+ end
678
+
679
+ __update_callbacks(name) do |target, chain|
680
+ options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
681
+ target.set_callbacks name, chain
682
+ end
683
+ end
684
+
685
+ # Skip a previously set callback. Like +set_callback+, <tt>:if</tt> or
686
+ # <tt>:unless</tt> options may be passed in order to control when the
687
+ # callback is skipped.
688
+ #
689
+ # class Writer < Person
690
+ # skip_callback :validate, :before, :check_membership, if: -> { age > 18 }
691
+ # end
692
+ #
693
+ # An <tt>ArgumentError</tt> will be raised if the callback has not
694
+ # already been set (unless the <tt>:raise</tt> option is set to <tt>false</tt>).
695
+ def skip_callback(name, *filter_list, &block)
696
+ type, filters, options = normalize_callback_params(filter_list, block)
697
+
698
+ options[:raise] = true unless options.key?(:raise)
699
+
700
+ __update_callbacks(name) do |target, chain|
701
+ filters.each do |filter|
702
+ callback = chain.find { |c| c.matches?(type, filter) }
703
+
704
+ if !callback && options[:raise]
705
+ raise ArgumentError, "#{type.to_s.capitalize} #{name} callback #{filter.inspect} has not been defined"
706
+ end
707
+
708
+ if callback && (options.key?(:if) || options.key?(:unless))
709
+ new_callback = callback.merge_conditional_options(chain, if_option: options[:if], unless_option: options[:unless])
710
+ chain.insert(chain.index(callback), new_callback)
711
+ end
712
+
713
+ chain.delete(callback)
714
+ end
715
+ target.set_callbacks name, chain
716
+ end
717
+ end
718
+
719
+ # Remove all set callbacks for the given event.
720
+ def reset_callbacks(name)
721
+ callbacks = get_callbacks name
722
+
723
+ SigtermExtensions::DescendantsTracker.descendants(self).each do |target|
724
+ chain = target.get_callbacks(name).dup
725
+ callbacks.each { |c| chain.delete(c) }
726
+ target.set_callbacks name, chain
727
+ end
728
+
729
+ set_callbacks(name, callbacks.dup.clear)
730
+ end
731
+
732
+ # Define sets of events in the object life cycle that support callbacks.
733
+ #
734
+ # define_callbacks :validate
735
+ # define_callbacks :initialize, :save, :destroy
736
+ #
737
+ # ===== Options
738
+ #
739
+ # * <tt>:terminator</tt> - Determines when a before filter will halt the
740
+ # callback chain, preventing following before and around callbacks from
741
+ # being called and the event from being triggered.
742
+ # This should be a lambda to be executed.
743
+ # The current object and the result lambda of the callback will be provided
744
+ # to the terminator lambda.
745
+ #
746
+ # define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false }
747
+ #
748
+ # In this example, if any before validate callbacks returns +false+,
749
+ # any successive before and around callback is not executed.
750
+ #
751
+ # The default terminator halts the chain when a callback throws +:abort+.
752
+ #
753
+ # * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
754
+ # callbacks should be terminated by the <tt>:terminator</tt> option. By
755
+ # default after callbacks are executed no matter if callback chain was
756
+ # terminated or not. This option has no effect if <tt>:terminator</tt>
757
+ # option is set to +nil+.
758
+ #
759
+ # * <tt>:scope</tt> - Indicates which methods should be executed when an
760
+ # object is used as a callback.
761
+ #
762
+ # class Audit
763
+ # def before(caller)
764
+ # puts 'Audit: before is called'
765
+ # end
766
+ #
767
+ # def before_save(caller)
768
+ # puts 'Audit: before_save is called'
769
+ # end
770
+ # end
771
+ #
772
+ # class Account
773
+ # include SigtermExtensions::Callbacks
774
+ #
775
+ # define_callbacks :save
776
+ # set_callback :save, :before, Audit.new
777
+ #
778
+ # def save
779
+ # run_callbacks :save do
780
+ # puts 'save in main'
781
+ # end
782
+ # end
783
+ # end
784
+ #
785
+ # In the above case whenever you save an account the method
786
+ # <tt>Audit#before</tt> will be called. On the other hand
787
+ #
788
+ # define_callbacks :save, scope: [:kind, :name]
789
+ #
790
+ # would trigger <tt>Audit#before_save</tt> instead. That's constructed
791
+ # by calling <tt>#{kind}_#{name}</tt> on the given instance. In this
792
+ # case "kind" is "before" and "name" is "save". In this context +:kind+
793
+ # and +:name+ have special meanings: +:kind+ refers to the kind of
794
+ # callback (before/after/around) and +:name+ refers to the method on
795
+ # which callbacks are being defined.
796
+ #
797
+ # A declaration like
798
+ #
799
+ # define_callbacks :save, scope: [:name]
800
+ #
801
+ # would call <tt>Audit#save</tt>.
802
+ #
803
+ # ===== Notes
804
+ #
805
+ # +names+ passed to +define_callbacks+ must not end with
806
+ # <tt>!</tt>, <tt>?</tt> or <tt>=</tt>.
807
+ #
808
+ # Calling +define_callbacks+ multiple times with the same +names+ will
809
+ # overwrite previous callbacks registered with +set_callback+.
810
+ def define_callbacks(*names)
811
+ options = names.extract_options!
812
+
813
+ names.each do |name|
814
+ name = name.to_sym
815
+
816
+ ([self] + SigtermExtensions::DescendantsTracker.descendants(self)).each do |target|
817
+ target.set_callbacks name, CallbackChain.new(name, options)
818
+ end
819
+
820
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
821
+ def _run_#{name}_callbacks(&block)
822
+ run_callbacks #{name.inspect}, &block
823
+ end
824
+ def self._#{name}_callbacks
825
+ get_callbacks(#{name.inspect})
826
+ end
827
+ def self._#{name}_callbacks=(value)
828
+ set_callbacks(#{name.inspect}, value)
829
+ end
830
+ def _#{name}_callbacks
831
+ __callbacks[#{name.inspect}]
832
+ end
833
+ RUBY
834
+ end
835
+ end
836
+
837
+ protected
838
+ def get_callbacks(name) # :nodoc:
839
+ __callbacks[name.to_sym]
840
+ end
841
+
842
+ def set_callbacks(name, callbacks) # :nodoc:
843
+ self.__callbacks = __callbacks.merge(name.to_sym => callbacks)
844
+ end
845
+ end
846
+ end
847
+ end