bigcommerce-multitrap 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0f6b5bf04d0745286817f5a37fadaf83bd98a0785e6e315225768862d2ce158b
4
+ data.tar.gz: 2372b5016ab0893e62d0daaf938387303f428536c5bfb9f8b7506fd0765ba4fd
5
+ SHA512:
6
+ metadata.gz: 62e88d91945e55594e7dd966bd2346b726f815c8bdd957b67804d38098980e9afe4fb51620cb49b8fd4e56d67cd5f9e4222d3a8283e5713c6a0d8d1f48d74b3d
7
+ data.tar.gz: a17fb32de84c9939d26d11832f206e64a036712f78975660e83407d872b8903a7fb5b5abcf13e8608adf8688629809286f15289079db920ccb2893a6c8534b1a
@@ -0,0 +1,15 @@
1
+ Multitrap changelog
2
+ ===================
3
+
4
+ ### v0.1.0 (August, 9, 2015)
5
+
6
+ * Added support for JRuby
7
+ * Added support for Rubinius
8
+ * Added support for CRuby 1.9
9
+ * Added support for symbols as signal keys ([#1](https://github.com/kyrylo/multitrap/issues/1))
10
+ * Added support for previously defined signals ([#2](https://github.com/kyrylo/multitrap/issues/2))
11
+ * Changed order of execution of handlers (FIFO now)
12
+
13
+ ### v0.0.1 (September 7, 2014)
14
+
15
+ * Initial release
@@ -0,0 +1,160 @@
1
+ Multitrap
2
+ =========
3
+
4
+ [![CircleCI](https://circleci.com/gh/bigcommerce/multitrap.svg?style=svg&circle-token=dc30a5b0d61a77d3547b795cca663e9309cf8cc0)](https://circleci.com/gh/bigcommerce/multitrap)
5
+
6
+ Introduction
7
+ ------------
8
+
9
+ By default, all Ruby implementations allow you to attach only one signal handler
10
+ per signal (via `Signal.trap` aka `trap`). This is not very useful, if you want
11
+ to perform multiple actions. Whenever you define a new signal handler, it sends
12
+ shivers down your spine, because you never know if you're overwriting someone
13
+ else's handler (usually, the handler of a library you depend on). Well, now you
14
+ don't have to worry about that anymore, because Multitrap solved this problem
15
+ for you! Define as many handlers as you wish and be sure they will all execute.
16
+
17
+ Examples
18
+ --------
19
+
20
+ ### Basic example
21
+
22
+ To use Multitrap just `require` it. No additional configuration is
23
+ needed. Internally, the library _redefines_ `trap`. The last defined signal
24
+ handler executes first.
25
+
26
+ ```ruby
27
+ require 'bigcommerce/multitrap'
28
+
29
+ trap(:INT) { puts 111 }
30
+ trap(:INT) { puts 222 }
31
+ trap(:INT) { puts 333 }
32
+
33
+ Process.kill(:INT, $$)
34
+
35
+ # Outputs:
36
+ # 333
37
+ # 222
38
+ # 111
39
+ ```
40
+
41
+ ### Nested traps
42
+
43
+ The library aims to be compatible with every Ruby implementation. For example,
44
+ JRuby doesn't support nested traps, but Rubinius and CRuby do. Multitrap obeys
45
+ this behaviour.
46
+
47
+ ```ruby
48
+ require 'bigcommerce/multitrap'
49
+
50
+ a = nil
51
+
52
+ trap(:INT) do
53
+ a = 1
54
+ trap(:INT) do
55
+ a = 2
56
+ end
57
+ end
58
+
59
+ puts a #=> nil
60
+
61
+ # On JRuby `a` will always be equal to 1.
62
+ Process.kill(:INT, $$)
63
+ puts a #=> 1
64
+
65
+ # CRuby and Rubinius will continue executing nested traps.
66
+ Process.kill(:INT, $$)
67
+ puts a #=> 2
68
+
69
+ Process.kill(:INT, $$)
70
+ puts a #=> 2
71
+ ```
72
+
73
+ ### Return value
74
+
75
+ With Multitrap, the `trap` method returns a hash with callbacks.
76
+
77
+ ```ruby
78
+ require 'pp'
79
+ require 'bigcommerce/multitrap'
80
+
81
+ trap(:INT) {}
82
+ trap(:HUP) {}
83
+ 3.times do
84
+ trap(:USR1) {}
85
+ end
86
+ handlers = trap(:USR2) {}
87
+
88
+ puts handlers
89
+ #=> {"INT"=>[#<Proc:0x00556161d34da8@test.rb:4>],
90
+ "HUP"=>[#<Proc:0x00556161d34420@test.rb:5>],
91
+ "USR1"=>
92
+ [#<Proc:0x00556161d36a68@test.rb:7>,
93
+ #<Proc:0x00556161d33750@test.rb:7>,
94
+ #<Proc:0x00556161d33048@test.rb:7>],
95
+ "USR2"=>[#<Proc:0x00556161d32ad0@test.rb:9>]}
96
+ ```
97
+
98
+ You can access this hash to modify the handlers at runtime (but be extremely
99
+ careful about that). For example, imagine if you write a test for your program's
100
+ `:INT` signal handler and use RSpec. RSpec defines its own `:INT` handler and
101
+ with the default `trap` you would simply overwrite it. With Multitrap you can
102
+ remove the handler from the hash and then append it later.
103
+
104
+ ```ruby
105
+ require 'spec_helper'
106
+ require 'multitrap'
107
+
108
+ RSpec.describe MyLibrary do
109
+ it "works" do
110
+ a = nil
111
+ handlers = trap(:INT) { a = 1 }
112
+
113
+ # Store the RSpecs handler. Usually, it's the first element in the array.
114
+ rspec_handler = handlers['INT'].shift
115
+
116
+ Process.kill(:INT, $$)
117
+ expect(a).to eq(1)
118
+
119
+ # Restore the handler.
120
+ handlers['INT'].unshift(rspec_handler)
121
+ end
122
+ end
123
+ ```
124
+
125
+ Installation
126
+ ------------
127
+
128
+ Add this line to your application's Gemfile:
129
+
130
+ gem 'multitrap'
131
+
132
+ And then execute:
133
+
134
+ $ bundle
135
+
136
+ Or install it yourself as:
137
+
138
+ $ gem install multitrap
139
+
140
+ Limitations
141
+ -----------
142
+
143
+ ### Rubies
144
+
145
+ * CRuby 1.9.2 >=
146
+ * Rubinius 2.5.8 >= (may work on older versions, untested)
147
+ * JRuby 9.0.0.0 >= (shouldn't work on older versions)
148
+
149
+ Roadmap
150
+ -------
151
+
152
+ ### Provide consistency
153
+
154
+ I'm not sure we need this, but it would be possible to remove inconsistencies
155
+ with respect to nested traps on all Ruby platforms.
156
+
157
+ License
158
+ -------
159
+
160
+ The project uses the MIT License. See LICENSE.txt file for more information.
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'bigcommerce-multitrap'
5
+ s.version = File.read('VERSION')
6
+ s.date = Time.now.strftime('%Y-%m-%d')
7
+ s.summary = 'Allows Signal.trap to have multiple callbacks'
8
+ s.authors = ['Kyrylo Silin', 'Shaun McCormick']
9
+ s.email = %w[silin@kyrylo.org shaun.mccormick@bigcommerce.com]
10
+ s.homepage = 'https://github.com/kyrylo/multitrap'
11
+ s.licenses = 'MIT'
12
+
13
+ s.files = Dir['README.md', 'CHANGELOG.md', 'CODE_OF_CONDUCT.md', 'lib/**/*', 'bigcommerce-multitrap.gemspec']
14
+ s.require_paths = ['lib']
15
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
16
+
17
+ s.add_development_dependency 'pry', '~> 0.12'
18
+ s.add_development_dependency 'rake', '~> 13.0'
19
+ s.add_development_dependency 'rspec', '~> 3.9'
20
+ s.add_development_dependency 'rspec_junit_formatter', '>= 0.4'
21
+ s.add_development_dependency 'rspec-wait', '~> 0.0.9'
22
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'bigcommerce/multitrap'
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thread'
4
+
5
+ module Bigcommerce
6
+ module Multitrap
7
+ # The VERSION file must be in the root directory of the library.
8
+ VERSION_FILE = File.expand_path('../../../VERSION', __FILE__)
9
+
10
+ VERSION = File.exist?(VERSION_FILE) ? File.read(VERSION_FILE).chomp : '(could not find VERSION file)'
11
+
12
+ def self.jruby?
13
+ RUBY_ENGINE == 'jruby'
14
+ end
15
+
16
+ def self.mri?
17
+ RUBY_ENGINE == 'ruby'
18
+ end
19
+
20
+ def self.rbx?
21
+ RUBY_ENGINE == 'rbx'
22
+ end
23
+ end
24
+ end
25
+
26
+ require_relative 'multitrap/trap'
27
+ require_relative 'multitrap/patched_trap'
28
+ require_relative 'multitrap/core_ext'
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ case RUBY_ENGINE
4
+ when 'ruby'
5
+ [Kernel, Kernel.singleton_class, Signal, Signal.singleton_class].each do |klass|
6
+ klass.__send__(:include, Bigcommerce::Multitrap::PatchedTrap)
7
+ end
8
+ when 'rbx'
9
+ Signal.__send__(:include, Bigcommerce::Multitrap::PatchedTrap)
10
+ Signal.singleton_class.__send__(:include, Bigcommerce::Multitrap::PatchedTrap)
11
+ when 'jruby'
12
+ Signal.singleton_class.__send__(:include, Bigcommerce::Multitrap::PatchedTrap)
13
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bigcommerce
4
+ module Multitrap
5
+ module PatchedTrap
6
+ def self.included(mod)
7
+ original_trap = Signal.method(:trap)
8
+
9
+ mod.instance_eval do
10
+ define_method(:trap) do |*args, &block|
11
+ if args.size < 1
12
+ if Bigcommerce::Multitrap.rbx?
13
+ raise ArgumentError, "method 'trap': given 0, expected 2"
14
+ elsif Bigcommerce::Multitrap.jruby?
15
+ raise ArgumentError, "wrong number of arguments (0 for 1)"
16
+ else
17
+ raise ArgumentError, "wrong number of arguments (0 for 1..2)"
18
+ end
19
+ end
20
+
21
+ if block.nil?
22
+ if Bigcommerce::Multitrap.rbx? && !args[1].respond_to?(:call)
23
+ raise ArgumentError, "Handler must respond to #call (was #{args[1].class})"
24
+ elsif args[1].nil?
25
+ raise ArgumentError, 'tried to create Proc object without a block'
26
+ end
27
+ end
28
+
29
+ Bigcommerce::Multitrap::Trap.trap(original_trap, *args, &block)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bigcommerce
4
+ module Multitrap
5
+ class Trap
6
+ OWN_MRI_FRAME = %r{/lib/multitrap/trap.rb:[0-9]{1,3}:in `block in store_trap'}
7
+
8
+ OWN_RBX_FRAME = %r{/lib/multitrap/trap.rb:[0-9]{1,3}:in `store_trap'}
9
+
10
+ TRAVIS_FRAME = %r{lib/jruby\.jar!/jruby/kernel/signal\.rb}
11
+
12
+ RESERVED_SIGNALS = %w|BUS SEGV ILL FPE VTALRM|
13
+
14
+ KNOWN_SIGNALS = Signal.list.keys - RESERVED_SIGNALS
15
+
16
+ def self.trap(original_trap, *args, &block)
17
+ @@multitrap ||= new(original_trap)
18
+
19
+ signal = args[0]
20
+ command = args[1]
21
+
22
+ @@multitrap.store_trap(signal, command, &block)
23
+ end
24
+
25
+ def self.__clear_all_handlers!
26
+ @@multitrap ||= nil
27
+ @@multitrap && @@multitrap.__clear_all_handlers!
28
+ end
29
+
30
+ def initialize(original_trap)
31
+ @original_trap = original_trap
32
+ @trap_list = create_trap_list
33
+ end
34
+
35
+ def store_trap(signal, command, &block)
36
+ signal = signal.to_s
37
+ command ||= block
38
+
39
+ trap_is_nested = nested_trap?
40
+
41
+ if trap_is_nested
42
+ # JRuby doesn't support nested traps.
43
+ return if Multitrap.jruby?
44
+ @trap_list[signal].pop
45
+ end
46
+
47
+ @trap_list[signal] << command
48
+
49
+ prev_trap_handler = @original_trap.call(signal) do |signo|
50
+ @trap_list[signal].reverse_each do |trap_handler|
51
+ trap_handler.call(signo || Signal.list[signal]) if trap_handler.respond_to?(:call)
52
+ end
53
+ end
54
+
55
+ if !trap_is_nested &&
56
+ @trap_list[signal].size == 1 &&
57
+ !default_handler?(prev_trap_handler) &&
58
+ !default_handler_path?(prev_trap_handler)
59
+ @trap_list[signal].unshift(prev_trap_handler)
60
+ end
61
+
62
+ @trap_list
63
+ end
64
+
65
+ def __clear_all_handlers!
66
+ @trap_list.keys.select {|s| KNOWN_SIGNALS.include?(s) }.each do |signal|
67
+ @original_trap.call(signal, 'DEFAULT')
68
+ end
69
+
70
+ @trap_list = create_trap_list
71
+ end
72
+
73
+ private
74
+
75
+ define_method(:nested_trap?) do
76
+ case RUBY_ENGINE
77
+ when 'ruby'
78
+ caller.any? { |stackframe| stackframe =~ OWN_MRI_FRAME }
79
+ when 'rbx'
80
+ caller.grep(OWN_RBX_FRAME).size > 1
81
+ when 'jruby'
82
+ if caller.any? { |stackframe| stackframe =~ TRAVIS_FRAME }
83
+ caller.grep(OWN_RBX_FRAME).size > 1
84
+ else
85
+ caller.grep(OWN_MRI_FRAME).size > 1
86
+ end
87
+ else
88
+ raise NotImplementedError, 'unsupported Ruby engine'
89
+ end
90
+ end
91
+
92
+ def create_trap_list
93
+ Hash.new { |h, k| h[k] = [] }
94
+ end
95
+
96
+ def default_handler?(prev)
97
+ %w[DEFAULT SYSTEM_DEFAULT IGNORE].any? { |h| h == prev }
98
+ end
99
+
100
+ def default_handler_path?(prev)
101
+ [%r{Proc:.+@kernel/loader\.rb:[0-9]{1,4}},
102
+ %r{Proc:.+@uri:classloader:/jruby/kernel/signal.rb:[0-9]{1,4}}].any? do |h|
103
+ h =~ prev.to_s
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bigcommerce-multitrap
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Kyrylo Silin
8
+ - Shaun McCormick
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2019-11-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: pry
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '0.12'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '0.12'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '13.0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '13.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rspec
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '3.9'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '3.9'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec_junit_formatter
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0.4'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0.4'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rspec-wait
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: 0.0.9
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: 0.0.9
84
+ description:
85
+ email:
86
+ - silin@kyrylo.org
87
+ - shaun.mccormick@bigcommerce.com
88
+ executables: []
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - CHANGELOG.md
93
+ - README.md
94
+ - bigcommerce-multitrap.gemspec
95
+ - lib/bigcommerce-multitrap.rb
96
+ - lib/bigcommerce/multitrap.rb
97
+ - lib/bigcommerce/multitrap/core_ext.rb
98
+ - lib/bigcommerce/multitrap/patched_trap.rb
99
+ - lib/bigcommerce/multitrap/trap.rb
100
+ homepage: https://github.com/kyrylo/multitrap
101
+ licenses:
102
+ - MIT
103
+ metadata: {}
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubygems_version: 3.0.1
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: Allows Signal.trap to have multiple callbacks
123
+ test_files: []