sigterm_extensions 0.0.4

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