listen 3.1.5 → 3.7.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 +5 -5
- data/CONTRIBUTING.md +10 -3
- data/README.md +240 -75
- data/bin/listen +3 -4
- data/lib/listen/adapter/base.rb +23 -35
- data/lib/listen/adapter/bsd.rb +6 -5
- data/lib/listen/adapter/config.rb +3 -4
- data/lib/listen/adapter/darwin.rb +29 -44
- data/lib/listen/adapter/linux.rb +15 -13
- data/lib/listen/adapter/polling.rb +8 -5
- data/lib/listen/adapter/windows.rb +15 -17
- data/lib/listen/adapter.rb +9 -11
- data/lib/listen/backend.rb +2 -0
- data/lib/listen/change.rb +14 -21
- data/lib/listen/cli.rb +6 -6
- data/lib/listen/directory.rb +14 -8
- data/lib/listen/error.rb +11 -0
- data/lib/listen/event/config.rb +9 -24
- data/lib/listen/event/loop.rb +44 -67
- data/lib/listen/event/processor.rb +41 -37
- data/lib/listen/event/queue.rb +12 -13
- data/lib/listen/file.rb +22 -7
- data/lib/listen/fsm.rb +72 -71
- data/lib/listen/listener/config.rb +4 -4
- data/lib/listen/listener.rb +26 -23
- data/lib/listen/logger.rb +24 -20
- data/lib/listen/monotonic_time.rb +27 -0
- data/lib/listen/options.rb +11 -8
- data/lib/listen/queue_optimizer.rb +13 -16
- data/lib/listen/record/entry.rb +4 -2
- data/lib/listen/record/symlink_detector.rb +10 -8
- data/lib/listen/record.rb +34 -32
- data/lib/listen/silencer/controller.rb +2 -0
- data/lib/listen/silencer.rb +20 -15
- data/lib/listen/thread.rb +54 -0
- data/lib/listen/version.rb +3 -1
- data/lib/listen.rb +15 -25
- metadata +20 -45
- data/lib/listen/internals/thread_pool.rb +0 -29
data/lib/listen/listener.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'English'
|
2
4
|
|
3
5
|
require 'listen/version'
|
@@ -19,7 +21,6 @@ require 'listen/listener/config'
|
|
19
21
|
|
20
22
|
module Listen
|
21
23
|
class Listener
|
22
|
-
# TODO: move the state machine's methods private
|
23
24
|
include Listen::FSM
|
24
25
|
|
25
26
|
# Initializes the directories listener.
|
@@ -32,13 +33,14 @@ module Listen
|
|
32
33
|
# @yieldparam [Array<String>] added the list of added files
|
33
34
|
# @yieldparam [Array<String>] removed the list of removed files
|
34
35
|
#
|
36
|
+
# rubocop:disable Metrics/MethodLength
|
35
37
|
def initialize(*dirs, &block)
|
36
38
|
options = dirs.last.is_a?(Hash) ? dirs.pop : {}
|
37
39
|
|
38
40
|
@config = Config.new(options)
|
39
41
|
|
40
42
|
eq_config = Event::Queue::Config.new(@config.relative?)
|
41
|
-
queue = Event::Queue.new(eq_config)
|
43
|
+
queue = Event::Queue.new(eq_config)
|
42
44
|
|
43
45
|
silencer = Silencer.new
|
44
46
|
rules = @config.silencer_rules
|
@@ -57,41 +59,43 @@ module Listen
|
|
57
59
|
|
58
60
|
@processor = Event::Loop.new(pconfig)
|
59
61
|
|
60
|
-
|
62
|
+
initialize_fsm
|
61
63
|
end
|
64
|
+
# rubocop:enable Metrics/MethodLength
|
62
65
|
|
63
|
-
|
66
|
+
start_state :initializing
|
64
67
|
|
65
68
|
state :initializing, to: [:backend_started, :stopped]
|
66
69
|
|
67
|
-
state :backend_started, to: [:
|
68
|
-
backend.start
|
69
|
-
end
|
70
|
-
|
71
|
-
state :frontend_ready, to: [:processing_events, :stopped] do
|
72
|
-
processor.setup
|
70
|
+
state :backend_started, to: [:processing_events, :stopped] do
|
71
|
+
@backend.start
|
73
72
|
end
|
74
73
|
|
75
74
|
state :processing_events, to: [:paused, :stopped] do
|
76
|
-
processor.
|
75
|
+
@processor.start
|
77
76
|
end
|
78
77
|
|
79
78
|
state :paused, to: [:processing_events, :stopped] do
|
80
|
-
processor.pause
|
79
|
+
@processor.pause
|
81
80
|
end
|
82
81
|
|
83
82
|
state :stopped, to: [:backend_started] do
|
84
|
-
backend.stop #
|
85
|
-
processor.
|
83
|
+
@backend.stop # halt events ASAP
|
84
|
+
@processor.stop
|
86
85
|
end
|
87
86
|
|
88
87
|
# Starts processing events and starts adapters
|
89
88
|
# or resumes invoking callbacks if paused
|
90
89
|
def start
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
90
|
+
case state
|
91
|
+
when :initializing
|
92
|
+
transition :backend_started
|
93
|
+
transition :processing_events
|
94
|
+
when :paused
|
95
|
+
transition :processing_events
|
96
|
+
else
|
97
|
+
raise ArgumentError, "cannot start from state #{state.inspect}"
|
98
|
+
end
|
95
99
|
end
|
96
100
|
|
97
101
|
# Stops both listening for events and processing them
|
@@ -113,6 +117,10 @@ module Listen
|
|
113
117
|
state == :paused
|
114
118
|
end
|
115
119
|
|
120
|
+
def stopped?
|
121
|
+
state == :stopped
|
122
|
+
end
|
123
|
+
|
116
124
|
def ignore(regexps)
|
117
125
|
@silencer_controller.append_ignores(regexps)
|
118
126
|
end
|
@@ -124,10 +132,5 @@ module Listen
|
|
124
132
|
def only(regexps)
|
125
133
|
@silencer_controller.replace_with_only(regexps)
|
126
134
|
end
|
127
|
-
|
128
|
-
private
|
129
|
-
|
130
|
-
attr_reader :processor
|
131
|
-
attr_reader :backend
|
132
135
|
end
|
133
136
|
end
|
data/lib/listen/logger.rb
CHANGED
@@ -1,32 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Listen
|
2
|
-
|
3
|
-
@logger ||= nil
|
4
|
-
end
|
4
|
+
@logger = nil
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
end
|
6
|
+
# Listen.logger will always be present.
|
7
|
+
# If you don't want logging, set Listen.logger = ::Logger.new('/dev/null', level: ::Logger::UNKNOWN)
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
9
|
+
class << self
|
10
|
+
attr_writer :logger
|
11
|
+
|
12
|
+
def logger
|
13
|
+
@logger ||= default_logger
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def default_logger
|
19
|
+
level =
|
20
|
+
case ENV['LISTEN_GEM_DEBUGGING'].to_s
|
21
|
+
when /debug|2/i
|
16
22
|
::Logger::DEBUG
|
17
|
-
when /true|yes|1/i
|
23
|
+
when /info|true|yes|1/i
|
18
24
|
::Logger::INFO
|
25
|
+
when /warn/i
|
26
|
+
::Logger::WARN
|
27
|
+
when /fatal/i
|
28
|
+
::Logger::FATAL
|
19
29
|
else
|
20
30
|
::Logger::ERROR
|
21
31
|
end
|
22
|
-
end
|
23
|
-
end
|
24
32
|
|
25
|
-
|
26
|
-
[:fatal, :error, :warn, :info, :debug].each do |meth|
|
27
|
-
define_singleton_method(meth) do |*args, &block|
|
28
|
-
Listen.logger.public_send(meth, *args, &block) if Listen.logger
|
29
|
-
end
|
33
|
+
::Logger.new(STDERR, level: level)
|
30
34
|
end
|
31
35
|
end
|
32
36
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Listen
|
4
|
+
module MonotonicTime
|
5
|
+
class << self
|
6
|
+
if defined?(Process::CLOCK_MONOTONIC)
|
7
|
+
|
8
|
+
def now
|
9
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
10
|
+
end
|
11
|
+
|
12
|
+
elsif defined?(Process::CLOCK_MONOTONIC_RAW)
|
13
|
+
|
14
|
+
def now
|
15
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC_RAW)
|
16
|
+
end
|
17
|
+
|
18
|
+
else
|
19
|
+
|
20
|
+
def now
|
21
|
+
Time.now.to_f
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/listen/options.rb
CHANGED
@@ -1,23 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Listen
|
2
4
|
class Options
|
3
5
|
def initialize(opts, defaults)
|
4
6
|
@options = {}
|
5
7
|
given_options = opts.dup
|
6
|
-
defaults.
|
8
|
+
defaults.each_key do |key|
|
7
9
|
@options[key] = given_options.delete(key) || defaults[key]
|
8
10
|
end
|
9
11
|
|
10
|
-
|
12
|
+
given_options.empty? or raise ArgumentError, "Unknown options: #{given_options.inspect}"
|
13
|
+
end
|
11
14
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
+
# rubocop:disable Lint/MissingSuper
|
16
|
+
def respond_to_missing?(name, *_)
|
17
|
+
@options.has_key?(name)
|
15
18
|
end
|
16
19
|
|
17
20
|
def method_missing(name, *_)
|
18
|
-
|
19
|
-
|
20
|
-
fail NameError, msg
|
21
|
+
respond_to_missing?(name) or raise NameError, "Bad option: #{name.inspect} (valid:#{@options.keys.inspect})"
|
22
|
+
@options[name]
|
21
23
|
end
|
24
|
+
# rubocop:enable Lint/MissingSuper
|
22
25
|
end
|
23
26
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Listen
|
2
4
|
class QueueOptimizer
|
3
5
|
class Config
|
@@ -60,7 +62,7 @@ module Listen
|
|
60
62
|
actions << :added if actions.delete(:moved_to)
|
61
63
|
actions << :removed if actions.delete(:moved_from)
|
62
64
|
|
63
|
-
modified = actions.
|
65
|
+
modified = actions.find { |x| x == :modified }
|
64
66
|
_calculate_add_remove_difference(actions, path, modified)
|
65
67
|
end
|
66
68
|
|
@@ -89,10 +91,8 @@ module Listen
|
|
89
91
|
def _reinterpret_related_changes(cookies)
|
90
92
|
table = { moved_to: :added, moved_from: :removed }
|
91
93
|
cookies.flat_map do |_, changes|
|
92
|
-
|
93
|
-
|
94
|
-
to_dir, to_file = data
|
95
|
-
[[:modified, to_dir, to_file]]
|
94
|
+
if (editor_modified = editor_modified?(changes))
|
95
|
+
[[:modified, *editor_modified]]
|
96
96
|
else
|
97
97
|
not_silenced = changes.reject do |type, _, _, path, _|
|
98
98
|
config.silenced?(Pathname(path), type)
|
@@ -104,29 +104,26 @@ module Listen
|
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
107
|
-
def
|
107
|
+
def editor_modified?(changes)
|
108
108
|
return unless changes.size == 2
|
109
109
|
|
110
|
-
from_type =
|
111
|
-
to_type =
|
110
|
+
from_type = from = nil
|
111
|
+
to_type = to_dir = to = nil
|
112
112
|
|
113
113
|
changes.each do |data|
|
114
114
|
case data[1]
|
115
115
|
when :moved_from
|
116
|
-
from_type,
|
116
|
+
from_type, _from_change, _, from, = data
|
117
117
|
when :moved_to
|
118
|
-
to_type,
|
119
|
-
else
|
120
|
-
return nil
|
118
|
+
to_type, _to_change, to_dir, to, = data
|
121
119
|
end
|
122
120
|
end
|
123
121
|
|
124
|
-
return unless from && to
|
125
|
-
|
126
122
|
# Expect an ignored moved_from and non-ignored moved_to
|
127
123
|
# to qualify as an "editor modify"
|
128
|
-
|
129
|
-
|
124
|
+
if from && to && config.silenced?(Pathname(from), from_type) && !config.silenced?(Pathname(to), to_type)
|
125
|
+
[to_dir, to]
|
126
|
+
end
|
130
127
|
end
|
131
128
|
end
|
132
129
|
end
|
data/lib/listen/record/entry.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Listen
|
2
4
|
# @private api
|
3
5
|
class Record
|
@@ -15,14 +17,14 @@ module Listen
|
|
15
17
|
|
16
18
|
def children
|
17
19
|
child_relative = _join
|
18
|
-
(_entries(sys_path) - %w
|
20
|
+
(_entries(sys_path) - %w[. ..]).map do |name|
|
19
21
|
Entry.new(@root, child_relative, name)
|
20
22
|
end
|
21
23
|
end
|
22
24
|
|
23
25
|
def meta
|
24
26
|
lstat = ::File.lstat(sys_path)
|
25
|
-
{ mtime: lstat.mtime.to_f, mode: lstat.mode }
|
27
|
+
{ mtime: lstat.mtime.to_f, mode: lstat.mode, size: lstat.size }
|
26
28
|
end
|
27
29
|
|
28
30
|
# record hash is e.g.
|
@@ -1,23 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'set'
|
4
|
+
require 'listen/error'
|
2
5
|
|
3
6
|
module Listen
|
4
7
|
# @private api
|
5
8
|
class Record
|
6
9
|
class SymlinkDetector
|
7
|
-
|
10
|
+
README_URL = 'https://github.com/guard/listen/blob/master/README.md'
|
8
11
|
|
9
|
-
SYMLINK_LOOP_ERROR = <<-EOS
|
12
|
+
SYMLINK_LOOP_ERROR = <<-EOS
|
10
13
|
** ERROR: directory is already being watched! **
|
11
14
|
|
12
15
|
Directory: %s
|
13
16
|
|
14
17
|
is already being watched through: %s
|
15
18
|
|
16
|
-
MORE INFO: #{
|
19
|
+
MORE INFO: #{README_URL}
|
17
20
|
EOS
|
18
21
|
|
19
|
-
|
20
|
-
end
|
22
|
+
Error = ::Listen::Error # for backward compatibility
|
21
23
|
|
22
24
|
def initialize
|
23
25
|
@real_dirs = Set.new
|
@@ -25,14 +27,14 @@ module Listen
|
|
25
27
|
|
26
28
|
def verify_unwatched!(entry)
|
27
29
|
real_path = entry.real_path
|
28
|
-
@real_dirs.add?(real_path)
|
30
|
+
@real_dirs.add?(real_path) or _fail(entry.sys_path, real_path)
|
29
31
|
end
|
30
32
|
|
31
33
|
private
|
32
34
|
|
33
35
|
def _fail(symlinked, real_path)
|
34
|
-
|
35
|
-
|
36
|
+
warn(format(SYMLINK_LOOP_ERROR, symlinked, real_path))
|
37
|
+
raise ::Listen::Error::SymlinkLoop, 'Failed due to looped symlinks'
|
36
38
|
end
|
37
39
|
end
|
38
40
|
end
|
data/lib/listen/record.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'thread'
|
2
4
|
require 'listen/record/entry'
|
3
5
|
require 'listen/record/symlink_detector'
|
@@ -8,14 +10,17 @@ module Listen
|
|
8
10
|
# TODO: deprecate
|
9
11
|
|
10
12
|
attr_reader :root
|
11
|
-
|
13
|
+
|
14
|
+
def initialize(directory, silencer)
|
12
15
|
@tree = _auto_hash
|
13
16
|
@root = directory.to_s
|
17
|
+
@silencer = silencer
|
14
18
|
end
|
15
19
|
|
16
20
|
def add_dir(rel_path)
|
17
|
-
|
18
|
-
|
21
|
+
if ![nil, '', '.'].include?(rel_path)
|
22
|
+
@tree[rel_path] ||= {}
|
23
|
+
end
|
19
24
|
end
|
20
25
|
|
21
26
|
def update_file(rel_path, data)
|
@@ -31,30 +36,27 @@ module Listen
|
|
31
36
|
def file_data(rel_path)
|
32
37
|
dirname, basename = Pathname(rel_path).split.map(&:to_s)
|
33
38
|
if [nil, '', '.'].include? dirname
|
34
|
-
tree[basename] ||= {}
|
35
|
-
tree[basename].dup
|
39
|
+
@tree[basename] ||= {}
|
40
|
+
@tree[basename].dup
|
36
41
|
else
|
37
|
-
tree[dirname] ||= {}
|
38
|
-
tree[dirname][basename] ||= {}
|
39
|
-
tree[dirname][basename].dup
|
42
|
+
@tree[dirname] ||= {}
|
43
|
+
@tree[dirname][basename] ||= {}
|
44
|
+
@tree[dirname][basename].dup
|
40
45
|
end
|
41
46
|
end
|
42
47
|
|
43
48
|
def dir_entries(rel_path)
|
44
|
-
subtree =
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
49
|
+
subtree = if ['', '.'].include? rel_path.to_s
|
50
|
+
@tree
|
51
|
+
else
|
52
|
+
@tree[rel_path.to_s] ||= _auto_hash
|
53
|
+
@tree[rel_path.to_s]
|
54
|
+
end
|
51
55
|
|
52
|
-
|
53
|
-
subtree.each do |key, values|
|
56
|
+
subtree.transform_values do |values|
|
54
57
|
# only get data for file entries
|
55
|
-
|
58
|
+
values.key?(:mtime) ? values : {}
|
56
59
|
end
|
57
|
-
result
|
58
60
|
end
|
59
61
|
|
60
62
|
def build
|
@@ -71,34 +73,34 @@ module Listen
|
|
71
73
|
private
|
72
74
|
|
73
75
|
def _auto_hash
|
74
|
-
Hash.new { |h, k| h[k] =
|
76
|
+
Hash.new { |h, k| h[k] = {} }
|
75
77
|
end
|
76
78
|
|
77
|
-
attr_reader :tree
|
78
|
-
|
79
79
|
def _fast_update_file(dirname, basename, data)
|
80
|
-
if [nil, '', '.'].include?
|
81
|
-
tree[basename] = (tree[basename] || {}).merge(data)
|
80
|
+
if [nil, '', '.'].include?(dirname)
|
81
|
+
@tree[basename] = (@tree[basename] || {}).merge(data)
|
82
82
|
else
|
83
|
-
tree[dirname] ||= {}
|
84
|
-
tree[dirname][basename] = (tree[dirname][basename] || {}).merge(data)
|
83
|
+
@tree[dirname] ||= {}
|
84
|
+
@tree[dirname][basename] = (@tree[dirname][basename] || {}).merge(data)
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
88
|
def _fast_unset_path(dirname, basename)
|
89
89
|
# this may need to be reworked to properly remove
|
90
90
|
# entries from a tree, without adding non-existing dirs to the record
|
91
|
-
if [nil, '', '.'].include?
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
tree[dirname].delete(basename)
|
91
|
+
if [nil, '', '.'].include?(dirname)
|
92
|
+
if @tree.key?(basename)
|
93
|
+
@tree.delete(basename)
|
94
|
+
end
|
95
|
+
elsif @tree.key?(dirname)
|
96
|
+
@tree[dirname].delete(basename)
|
97
97
|
end
|
98
98
|
end
|
99
99
|
|
100
100
|
def _fast_build_dir(remaining, symlink_detector)
|
101
101
|
entry = remaining.pop
|
102
|
+
return if @silencer.silenced?(entry.record_dir_key, :dir)
|
103
|
+
|
102
104
|
children = entry.children # NOTE: children() implicitly tests if dir
|
103
105
|
symlink_detector.verify_unwatched!(entry)
|
104
106
|
children.each { |child| remaining << child }
|
data/lib/listen/silencer.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Listen
|
2
4
|
class Silencer
|
3
5
|
# The default list of directories that get ignored.
|
@@ -12,7 +14,7 @@ module Listen
|
|
12
14
|
| log
|
13
15
|
| tmp
|
14
16
|
|vendor/ruby
|
15
|
-
)(/|$)}x
|
17
|
+
)(/|$)}x.freeze
|
16
18
|
|
17
19
|
# The default list of files that get ignored.
|
18
20
|
DEFAULT_IGNORED_EXTENSIONS = %r{(?:
|
@@ -46,40 +48,43 @@ module Listen
|
|
46
48
|
)
|
47
49
|
)
|
48
50
|
|
51
|
+
# Mutagen sync temporary files
|
52
|
+
| \.mutagen-temporary.*
|
53
|
+
|
49
54
|
# other files
|
50
55
|
| \.DS_Store
|
51
56
|
| \.tmp
|
52
57
|
| ~
|
53
|
-
)$}x
|
58
|
+
)$}x.freeze
|
54
59
|
|
60
|
+
# TODO: deprecate these mutators; use attr_reader instead
|
55
61
|
attr_accessor :only_patterns, :ignore_patterns
|
56
62
|
|
57
|
-
def initialize
|
58
|
-
configure(
|
63
|
+
def initialize(**options)
|
64
|
+
configure(options)
|
59
65
|
end
|
60
66
|
|
67
|
+
# TODO: deprecate this mutator
|
61
68
|
def configure(options)
|
62
69
|
@only_patterns = options[:only] ? Array(options[:only]) : nil
|
63
70
|
@ignore_patterns = _init_ignores(options[:ignore], options[:ignore!])
|
64
71
|
end
|
65
72
|
|
66
|
-
# Note: relative_path is temporarily expected to be a relative Pathname to
|
67
|
-
# make refactoring easier (ideally, it would take a string)
|
68
|
-
|
69
|
-
# TODO: switch type and path places - and verify
|
70
73
|
def silenced?(relative_path, type)
|
71
|
-
path = relative_path.to_s
|
72
|
-
|
73
|
-
if only_patterns && type == :file
|
74
|
-
return true unless only_patterns.any? { |pattern| path =~ pattern }
|
75
|
-
end
|
74
|
+
path = relative_path.to_s # in case it is a Pathname
|
76
75
|
|
77
|
-
|
76
|
+
_ignore?(path) || (only_patterns && type == :file && !_only?(path))
|
78
77
|
end
|
79
78
|
|
80
79
|
private
|
81
80
|
|
82
|
-
|
81
|
+
def _ignore?(path)
|
82
|
+
ignore_patterns.any? { |pattern| path =~ pattern }
|
83
|
+
end
|
84
|
+
|
85
|
+
def _only?(path)
|
86
|
+
only_patterns.any? { |pattern| path =~ pattern }
|
87
|
+
end
|
83
88
|
|
84
89
|
def _init_ignores(ignores, overrides)
|
85
90
|
patterns = []
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
require_relative 'logger'
|
6
|
+
|
7
|
+
module Listen
|
8
|
+
module Thread
|
9
|
+
class << self
|
10
|
+
# Creates a new thread with the given name.
|
11
|
+
# Any exceptions raised by the thread will be logged with the thread name and complete backtrace.
|
12
|
+
# rubocop:disable Style/MultilineBlockChain
|
13
|
+
def new(name, &block)
|
14
|
+
thread_name = "listen-#{name}"
|
15
|
+
caller_stack = caller
|
16
|
+
|
17
|
+
::Thread.new do
|
18
|
+
rescue_and_log(thread_name, caller_stack: caller_stack, &block)
|
19
|
+
end.tap do |thread|
|
20
|
+
thread.name = thread_name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
# rubocop:enable Style/MultilineBlockChain
|
24
|
+
|
25
|
+
def rescue_and_log(method_name, *args, caller_stack: nil)
|
26
|
+
yield(*args)
|
27
|
+
rescue => exception
|
28
|
+
_log_exception(exception, method_name, caller_stack: caller_stack)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def _log_exception(exception, thread_name, caller_stack: nil)
|
34
|
+
complete_backtrace = if caller_stack
|
35
|
+
[*exception.backtrace, "--- Thread.new ---", *caller_stack]
|
36
|
+
else
|
37
|
+
exception.backtrace
|
38
|
+
end
|
39
|
+
message = "Exception rescued in #{thread_name}:\n#{_exception_with_causes(exception)}\n#{complete_backtrace * "\n"}"
|
40
|
+
Listen.logger.error(message)
|
41
|
+
end
|
42
|
+
|
43
|
+
def _exception_with_causes(exception)
|
44
|
+
result = +"#{exception.class}: #{exception}"
|
45
|
+
if exception.cause
|
46
|
+
result << "\n"
|
47
|
+
result << "--- Caused by: ---\n"
|
48
|
+
result << _exception_with_causes(exception.cause)
|
49
|
+
end
|
50
|
+
result
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/listen/version.rb
CHANGED