listen 0.5.3 → 3.7.1
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 +7 -0
- data/CHANGELOG.md +1 -186
- data/CONTRIBUTING.md +45 -0
- data/{LICENSE → LICENSE.txt} +3 -1
- data/README.md +332 -181
- data/bin/listen +11 -0
- data/lib/listen/adapter/base.rb +129 -0
- data/lib/listen/adapter/bsd.rb +107 -0
- data/lib/listen/adapter/config.rb +25 -0
- data/lib/listen/adapter/darwin.rb +77 -0
- data/lib/listen/adapter/linux.rb +108 -0
- data/lib/listen/adapter/polling.rb +40 -0
- data/lib/listen/adapter/windows.rb +96 -0
- data/lib/listen/adapter.rb +32 -201
- data/lib/listen/backend.rb +40 -0
- data/lib/listen/change.rb +69 -0
- data/lib/listen/cli.rb +65 -0
- data/lib/listen/directory.rb +93 -0
- data/lib/listen/error.rb +11 -0
- data/lib/listen/event/config.rb +40 -0
- data/lib/listen/event/loop.rb +94 -0
- data/lib/listen/event/processor.rb +126 -0
- data/lib/listen/event/queue.rb +54 -0
- data/lib/listen/file.rb +95 -0
- data/lib/listen/fsm.rb +133 -0
- data/lib/listen/listener/config.rb +41 -0
- data/lib/listen/listener.rb +93 -160
- data/lib/listen/logger.rb +36 -0
- data/lib/listen/monotonic_time.rb +27 -0
- data/lib/listen/options.rb +26 -0
- data/lib/listen/queue_optimizer.rb +129 -0
- data/lib/listen/record/entry.rb +66 -0
- data/lib/listen/record/symlink_detector.rb +41 -0
- data/lib/listen/record.rb +123 -0
- data/lib/listen/silencer/controller.rb +50 -0
- data/lib/listen/silencer.rb +106 -0
- data/lib/listen/thread.rb +54 -0
- data/lib/listen/version.rb +3 -1
- data/lib/listen.rb +40 -32
- metadata +87 -38
- data/lib/listen/adapters/darwin.rb +0 -85
- data/lib/listen/adapters/linux.rb +0 -113
- data/lib/listen/adapters/polling.rb +0 -67
- data/lib/listen/adapters/windows.rb +0 -87
- data/lib/listen/dependency_manager.rb +0 -126
- data/lib/listen/directory_record.rb +0 -344
- data/lib/listen/multi_listener.rb +0 -121
- data/lib/listen/turnstile.rb +0 -28
data/bin/listen
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'listen/options'
|
4
|
+
require 'listen/record'
|
5
|
+
require 'listen/change'
|
6
|
+
require 'listen/thread'
|
7
|
+
|
8
|
+
module Listen
|
9
|
+
module Adapter
|
10
|
+
class Base
|
11
|
+
attr_reader :options, :config
|
12
|
+
|
13
|
+
# TODO: only used by tests
|
14
|
+
DEFAULTS = {}.freeze
|
15
|
+
|
16
|
+
def initialize(config)
|
17
|
+
@started = false
|
18
|
+
@config = config
|
19
|
+
|
20
|
+
@configured = nil
|
21
|
+
|
22
|
+
fail 'No directories to watch!' if config.directories.empty?
|
23
|
+
|
24
|
+
defaults = self.class.const_get('DEFAULTS')
|
25
|
+
@options = Listen::Options.new(config.adapter_options, defaults)
|
26
|
+
rescue
|
27
|
+
_log_exception 'adapter config failed: %s:%s called from: %s', caller
|
28
|
+
raise
|
29
|
+
end
|
30
|
+
|
31
|
+
# TODO: it's a separate method as a temporary workaround for tests
|
32
|
+
# rubocop:disable Metrics/MethodLength
|
33
|
+
def configure
|
34
|
+
if @configured
|
35
|
+
Listen.logger.warn('Adapter already configured!')
|
36
|
+
return
|
37
|
+
end
|
38
|
+
|
39
|
+
@configured = true
|
40
|
+
|
41
|
+
@callbacks ||= {}
|
42
|
+
config.directories.each do |dir|
|
43
|
+
callback = @callbacks[dir] || lambda do |event|
|
44
|
+
_process_event(dir, event)
|
45
|
+
end
|
46
|
+
@callbacks[dir] = callback
|
47
|
+
_configure(dir, &callback)
|
48
|
+
end
|
49
|
+
|
50
|
+
@snapshots ||= {}
|
51
|
+
# TODO: separate config per directory (some day maybe)
|
52
|
+
change_config = Change::Config.new(config.queue, config.silencer)
|
53
|
+
config.directories.each do |dir|
|
54
|
+
record = Record.new(dir, config.silencer)
|
55
|
+
snapshot = Change.new(change_config, record)
|
56
|
+
@snapshots[dir] = snapshot
|
57
|
+
end
|
58
|
+
end
|
59
|
+
# rubocop:enable Metrics/MethodLength
|
60
|
+
|
61
|
+
def started?
|
62
|
+
@started
|
63
|
+
end
|
64
|
+
|
65
|
+
def start
|
66
|
+
configure
|
67
|
+
|
68
|
+
if started?
|
69
|
+
Listen.logger.warn('Adapter already started!')
|
70
|
+
return
|
71
|
+
end
|
72
|
+
|
73
|
+
@started = true
|
74
|
+
|
75
|
+
@run_thread = Listen::Thread.new("run_thread") do
|
76
|
+
@snapshots.each_value do |snapshot|
|
77
|
+
_timed('Record.build()') { snapshot.record.build }
|
78
|
+
end
|
79
|
+
_run
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def stop
|
84
|
+
_stop
|
85
|
+
config.queue.close # this causes queue.pop to return `nil` to the front-end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def _stop
|
91
|
+
@run_thread&.kill
|
92
|
+
@run_thread = nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def _timed(title)
|
96
|
+
start = MonotonicTime.now
|
97
|
+
yield
|
98
|
+
diff = MonotonicTime.now - start
|
99
|
+
Listen.logger.info format('%s: %.05f seconds', title, diff)
|
100
|
+
rescue
|
101
|
+
Listen.logger.warn "#{title} crashed: #{$ERROR_INFO.inspect}"
|
102
|
+
raise
|
103
|
+
end
|
104
|
+
|
105
|
+
# TODO: allow backend adapters to pass specific invalidation objects
|
106
|
+
# e.g. Darwin -> DirRescan, INotify -> MoveScan, etc.
|
107
|
+
def _queue_change(type, dir, rel_path, options)
|
108
|
+
@snapshots[dir].invalidate(type, rel_path, options)
|
109
|
+
end
|
110
|
+
|
111
|
+
def _log_exception(msg, caller_stack)
|
112
|
+
formatted = format(
|
113
|
+
msg,
|
114
|
+
$ERROR_INFO,
|
115
|
+
$ERROR_POSITION * "\n",
|
116
|
+
caller_stack * "\n"
|
117
|
+
)
|
118
|
+
|
119
|
+
Listen.logger.error(formatted)
|
120
|
+
end
|
121
|
+
|
122
|
+
class << self
|
123
|
+
def usable?
|
124
|
+
const_get('OS_REGEXP') =~ RbConfig::CONFIG['target_os']
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Listener implementation for BSD's `kqueue`.
|
4
|
+
# @see http://www.freebsd.org/cgi/man.cgi?query=kqueue
|
5
|
+
# @see https://github.com/mat813/rb-kqueue/blob/master/lib/rb-kqueue/queue.rb
|
6
|
+
#
|
7
|
+
module Listen
|
8
|
+
module Adapter
|
9
|
+
class BSD < Base
|
10
|
+
OS_REGEXP = /bsd|dragonfly/i.freeze
|
11
|
+
|
12
|
+
DEFAULTS = {
|
13
|
+
events: [
|
14
|
+
:delete,
|
15
|
+
:write,
|
16
|
+
:extend,
|
17
|
+
:attrib,
|
18
|
+
:rename
|
19
|
+
# :link, :revoke
|
20
|
+
]
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '')
|
24
|
+
Please add the following to your Gemfile to avoid polling for changes:
|
25
|
+
require 'rbconfig'
|
26
|
+
if RbConfig::CONFIG['target_os'] =~ /#{OS_REGEXP}/
|
27
|
+
gem 'rb-kqueue', '>= 0.2'
|
28
|
+
end
|
29
|
+
EOS
|
30
|
+
|
31
|
+
def self.usable?
|
32
|
+
return false unless super
|
33
|
+
require 'rb-kqueue'
|
34
|
+
require 'find'
|
35
|
+
true
|
36
|
+
rescue LoadError
|
37
|
+
Kernel.warn BUNDLER_DECLARE_GEM
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def _configure(directory, &callback)
|
44
|
+
@worker ||= KQueue::Queue.new
|
45
|
+
@callback = callback
|
46
|
+
# use Record to make a snapshot of dir, so we
|
47
|
+
# can detect new files
|
48
|
+
_find(directory.to_s) { |path| _watch_file(path, @worker) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def _run
|
52
|
+
@worker.run
|
53
|
+
end
|
54
|
+
|
55
|
+
def _process_event(dir, event)
|
56
|
+
full_path = _event_path(event)
|
57
|
+
if full_path.directory?
|
58
|
+
# Force dir content tracking to kick in, or we won't have
|
59
|
+
# names of added files
|
60
|
+
_queue_change(:dir, dir, '.', recursive: true)
|
61
|
+
elsif full_path.exist?
|
62
|
+
path = full_path.relative_path_from(dir)
|
63
|
+
_queue_change(:file, dir, path.to_s, change: _change(event.flags))
|
64
|
+
end
|
65
|
+
|
66
|
+
# If it is a directory, and it has a write flag, it means a
|
67
|
+
# file has been added so find out which and deal with it.
|
68
|
+
# No need to check for removed files, kqueue will forget them
|
69
|
+
# when the vfs does.
|
70
|
+
_watch_for_new_file(event) if full_path.directory?
|
71
|
+
end
|
72
|
+
|
73
|
+
def _change(event_flags)
|
74
|
+
{ modified: [:attrib, :extend],
|
75
|
+
added: [:write],
|
76
|
+
removed: [:rename, :delete] }.each do |change, flags|
|
77
|
+
return change unless (flags & event_flags).empty?
|
78
|
+
end
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
|
82
|
+
def _event_path(event)
|
83
|
+
Pathname.new(event.watcher.path)
|
84
|
+
end
|
85
|
+
|
86
|
+
def _watch_for_new_file(event)
|
87
|
+
queue = event.watcher.queue
|
88
|
+
_find(_event_path(event).to_s) do |file_path|
|
89
|
+
unless queue.watchers.find { |_, v| v.path == file_path.to_s }
|
90
|
+
_watch_file(file_path, queue)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def _watch_file(path, queue)
|
96
|
+
queue.watch_file(path, *options.events, &@callback)
|
97
|
+
rescue Errno::ENOENT => e
|
98
|
+
Listen.logger.warn "kqueue: watch file failed: #{e.message}"
|
99
|
+
end
|
100
|
+
|
101
|
+
# Quick rubocop workaround
|
102
|
+
def _find(*paths, &block)
|
103
|
+
Find.send(:find, *paths, &block)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module Listen
|
6
|
+
module Adapter
|
7
|
+
class Config
|
8
|
+
attr_reader :directories, :silencer, :queue, :adapter_options
|
9
|
+
|
10
|
+
def initialize(directories, queue, silencer, adapter_options)
|
11
|
+
# Default to current directory if no directories are supplied
|
12
|
+
directories = [Dir.pwd] if directories.to_a.empty?
|
13
|
+
|
14
|
+
# TODO: fix (flatten, array, compact?)
|
15
|
+
@directories = directories.map do |directory|
|
16
|
+
Pathname.new(directory.to_s).realpath
|
17
|
+
end
|
18
|
+
|
19
|
+
@silencer = silencer
|
20
|
+
@queue = queue
|
21
|
+
@adapter_options = adapter_options
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'listen/thread'
|
4
|
+
|
5
|
+
module Listen
|
6
|
+
module Adapter
|
7
|
+
# Adapter implementation for Mac OS X `FSEvents`.
|
8
|
+
#
|
9
|
+
class Darwin < Base
|
10
|
+
OS_REGEXP = /darwin(?<major_version>(1|2)\d+)/i.freeze
|
11
|
+
|
12
|
+
# The default delay between checking for changes.
|
13
|
+
DEFAULTS = { latency: 0.1 }.freeze
|
14
|
+
|
15
|
+
INCOMPATIBLE_GEM_VERSION = <<-EOS.gsub(/^ {8}/, '')
|
16
|
+
rb-fsevent > 0.9.4 no longer supports OS X 10.6 through 10.8.
|
17
|
+
|
18
|
+
Please add the following to your Gemfile to avoid polling for changes:
|
19
|
+
require 'rbconfig'
|
20
|
+
if RbConfig::CONFIG['target_os'] =~ /darwin(1[0-3])/i
|
21
|
+
gem 'rb-fsevent', '<= 0.9.4'
|
22
|
+
end
|
23
|
+
EOS
|
24
|
+
|
25
|
+
def self.usable?
|
26
|
+
version = RbConfig::CONFIG['target_os'][OS_REGEXP, :major_version]
|
27
|
+
return false unless version
|
28
|
+
return true if version.to_i >= 13 # darwin13 is OS X 10.9
|
29
|
+
|
30
|
+
require 'rb-fsevent'
|
31
|
+
fsevent_version = Gem::Version.new(FSEvent::VERSION)
|
32
|
+
return true if fsevent_version <= Gem::Version.new('0.9.4')
|
33
|
+
Kernel.warn INCOMPATIBLE_GEM_VERSION
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def _configure(dir, &callback)
|
40
|
+
@callbacks[dir] = callback
|
41
|
+
end
|
42
|
+
|
43
|
+
def _run
|
44
|
+
require 'rb-fsevent'
|
45
|
+
worker = FSEvent.new
|
46
|
+
dirs_to_watch = @callbacks.keys.map(&:to_s)
|
47
|
+
Listen.logger.info { "fsevent: watching: #{dirs_to_watch.inspect}" }
|
48
|
+
worker.watch(dirs_to_watch, { latency: options.latency }, &method(:_process_changes))
|
49
|
+
@worker_thread = Listen::Thread.new("worker_thread") { worker.run }
|
50
|
+
end
|
51
|
+
|
52
|
+
def _process_changes(dirs)
|
53
|
+
dirs.each do |dir|
|
54
|
+
dir = Pathname.new(dir.sub(%r{/$}, ''))
|
55
|
+
|
56
|
+
@callbacks.each do |watched_dir, callback|
|
57
|
+
if watched_dir.eql?(dir) || Listen::Directory.ascendant_of?(watched_dir, dir)
|
58
|
+
callback.call(dir)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def _process_event(dir, path)
|
65
|
+
Listen.logger.debug { "fsevent: processing path: #{path.inspect}" }
|
66
|
+
# TODO: does this preserve symlinks?
|
67
|
+
rel_path = path.relative_path_from(dir).to_s
|
68
|
+
_queue_change(:dir, dir, rel_path, recursive: true)
|
69
|
+
end
|
70
|
+
|
71
|
+
def _stop
|
72
|
+
@worker_thread&.kill
|
73
|
+
super
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Listen
|
4
|
+
module Adapter
|
5
|
+
# @see https://github.com/nex3/rb-inotify
|
6
|
+
class Linux < Base
|
7
|
+
OS_REGEXP = /linux/i.freeze
|
8
|
+
|
9
|
+
DEFAULTS = {
|
10
|
+
events: [
|
11
|
+
:recursive,
|
12
|
+
:attrib,
|
13
|
+
:create,
|
14
|
+
:modify,
|
15
|
+
:delete,
|
16
|
+
:move,
|
17
|
+
:close_write
|
18
|
+
],
|
19
|
+
wait_for_delay: 0.1
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
README_URL = 'https://github.com/guard/listen'\
|
25
|
+
'/blob/master/README.md#increasing-the-amount-of-inotify-watchers'
|
26
|
+
|
27
|
+
def _configure(directory, &callback)
|
28
|
+
require 'rb-inotify'
|
29
|
+
@worker ||= ::INotify::Notifier.new
|
30
|
+
@worker.watch(directory.to_s, *options.events, &callback)
|
31
|
+
rescue Errno::ENOSPC
|
32
|
+
raise ::Listen::Error::INotifyMaxWatchesExceeded, <<~EOS
|
33
|
+
Unable to monitor directories for changes because iNotify max watches exceeded. See #{README_URL} .
|
34
|
+
EOS
|
35
|
+
end
|
36
|
+
|
37
|
+
def _run
|
38
|
+
@worker.run
|
39
|
+
end
|
40
|
+
|
41
|
+
# rubocop:disable Metrics/MethodLength
|
42
|
+
def _process_event(dir, event)
|
43
|
+
# NOTE: avoid using event.absolute_name since new API
|
44
|
+
# will need to have a custom recursion implemented
|
45
|
+
# to properly match events to configured directories
|
46
|
+
path = Pathname.new(event.watcher.path) + event.name
|
47
|
+
rel_path = path.relative_path_from(dir).to_s
|
48
|
+
|
49
|
+
Listen.logger.debug { "inotify: #{rel_path} (#{event.flags.inspect})" }
|
50
|
+
|
51
|
+
if /1|true/ =~ ENV['LISTEN_GEM_SIMULATE_FSEVENT']
|
52
|
+
if (event.flags & [:moved_to, :moved_from]) || _dir_event?(event)
|
53
|
+
rel_path = path.dirname.relative_path_from(dir).to_s
|
54
|
+
end
|
55
|
+
_queue_change(:dir, dir, rel_path, {})
|
56
|
+
return
|
57
|
+
end
|
58
|
+
|
59
|
+
return if _skip_event?(event)
|
60
|
+
|
61
|
+
cookie_params = event.cookie.zero? ? {} : { cookie: event.cookie }
|
62
|
+
|
63
|
+
# Note: don't pass options to force rescanning the directory, so we can
|
64
|
+
# detect moving/deleting a whole tree
|
65
|
+
if _dir_event?(event)
|
66
|
+
_queue_change(:dir, dir, rel_path, cookie_params)
|
67
|
+
return
|
68
|
+
end
|
69
|
+
|
70
|
+
params = cookie_params.merge(change: _change(event.flags))
|
71
|
+
|
72
|
+
_queue_change(:file, dir, rel_path, params)
|
73
|
+
end
|
74
|
+
# rubocop:enable Metrics/MethodLength
|
75
|
+
|
76
|
+
def _skip_event?(event)
|
77
|
+
# Event on root directory
|
78
|
+
return true if event.name == ''
|
79
|
+
# INotify reports changes to files inside directories as events
|
80
|
+
# on the directories themselves too.
|
81
|
+
#
|
82
|
+
# @see http://linux.die.net/man/7/inotify
|
83
|
+
_dir_event?(event) && (event.flags & [:close, :modify]).any?
|
84
|
+
end
|
85
|
+
|
86
|
+
def _change(event_flags)
|
87
|
+
{ modified: [:attrib, :close_write],
|
88
|
+
moved_to: [:moved_to],
|
89
|
+
moved_from: [:moved_from],
|
90
|
+
added: [:create],
|
91
|
+
removed: [:delete] }.each do |change, flags|
|
92
|
+
return change unless (flags & event_flags).empty?
|
93
|
+
end
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
|
97
|
+
def _dir_event?(event)
|
98
|
+
event.flags.include?(:isdir)
|
99
|
+
end
|
100
|
+
|
101
|
+
def _stop
|
102
|
+
@worker&.close
|
103
|
+
|
104
|
+
super
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Listen
|
4
|
+
module Adapter
|
5
|
+
# Polling Adapter that works cross-platform and
|
6
|
+
# has no dependencies. This is the adapter that
|
7
|
+
# uses the most CPU processing power and has higher
|
8
|
+
# file IO than the other implementations.
|
9
|
+
#
|
10
|
+
class Polling < Base
|
11
|
+
OS_REGEXP = //.freeze # match every OS
|
12
|
+
|
13
|
+
DEFAULTS = { latency: 1.0, wait_for_delay: 0.05 }.freeze
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def _configure(_, &callback)
|
18
|
+
@polling_callbacks ||= []
|
19
|
+
@polling_callbacks << callback
|
20
|
+
end
|
21
|
+
|
22
|
+
def _run
|
23
|
+
loop do
|
24
|
+
start = MonotonicTime.now
|
25
|
+
@polling_callbacks.each do |callback|
|
26
|
+
callback.call(nil)
|
27
|
+
if (nap_time = options.latency - (MonotonicTime.now - start)) > 0
|
28
|
+
# TODO: warn if nap_time is negative (polling too slow)
|
29
|
+
sleep(nap_time)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def _process_event(dir, _)
|
36
|
+
_queue_change(:dir, dir, '.', recursive: true)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Listen
|
4
|
+
module Adapter
|
5
|
+
# Adapter implementation for Windows `wdm`.
|
6
|
+
#
|
7
|
+
class Windows < Base
|
8
|
+
OS_REGEXP = /mswin|mingw|cygwin/i.freeze
|
9
|
+
|
10
|
+
BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '')
|
11
|
+
Please add the following to your Gemfile to avoid polling for changes:
|
12
|
+
gem 'wdm', '>= 0.1.0' if Gem.win_platform?
|
13
|
+
EOS
|
14
|
+
|
15
|
+
def self.usable?
|
16
|
+
return false unless super
|
17
|
+
require 'wdm'
|
18
|
+
true
|
19
|
+
rescue LoadError
|
20
|
+
Listen.logger.debug format('wdm - load failed: %s:%s', $ERROR_INFO,
|
21
|
+
$ERROR_POSITION * "\n")
|
22
|
+
|
23
|
+
Kernel.warn BUNDLER_DECLARE_GEM
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def _configure(dir)
|
30
|
+
require 'wdm'
|
31
|
+
Listen.logger.debug 'wdm - starting...'
|
32
|
+
@worker ||= WDM::Monitor.new
|
33
|
+
@worker.watch_recursively(dir.to_s, :files) do |change|
|
34
|
+
yield([:file, change])
|
35
|
+
end
|
36
|
+
|
37
|
+
@worker.watch_recursively(dir.to_s, :directories) do |change|
|
38
|
+
yield([:dir, change])
|
39
|
+
end
|
40
|
+
|
41
|
+
@worker.watch_recursively(dir.to_s, :attributes, :last_write) do |change|
|
42
|
+
yield([:attr, change])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def _run
|
47
|
+
@worker.run!
|
48
|
+
end
|
49
|
+
|
50
|
+
# rubocop:disable Metrics/MethodLength
|
51
|
+
def _process_event(dir, event)
|
52
|
+
Listen.logger.debug "wdm - callback: #{event.inspect}"
|
53
|
+
|
54
|
+
type, change = event
|
55
|
+
|
56
|
+
full_path = Pathname(change.path)
|
57
|
+
|
58
|
+
rel_path = full_path.relative_path_from(dir).to_s
|
59
|
+
|
60
|
+
options = { change: _change(change.type) }
|
61
|
+
|
62
|
+
case type
|
63
|
+
when :file
|
64
|
+
_queue_change(:file, dir, rel_path, options)
|
65
|
+
when :attr
|
66
|
+
unless full_path.directory?
|
67
|
+
_queue_change(:file, dir, rel_path, options)
|
68
|
+
end
|
69
|
+
when :dir
|
70
|
+
case change.type
|
71
|
+
when :removed
|
72
|
+
# TODO: check if watched dir?
|
73
|
+
_queue_change(:dir, dir, Pathname(rel_path).dirname.to_s, {})
|
74
|
+
when :added
|
75
|
+
_queue_change(:dir, dir, rel_path, {})
|
76
|
+
# do nothing - changed directory means either:
|
77
|
+
# - removed subdirs (handled above)
|
78
|
+
# - added subdirs (handled above)
|
79
|
+
# - removed files (handled by _file_callback)
|
80
|
+
# - added files (handled by _file_callback)
|
81
|
+
# so what's left?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
# rubocop:enable Metrics/MethodLength
|
86
|
+
|
87
|
+
def _change(type)
|
88
|
+
{ modified: [:modified, :attrib], # TODO: is attrib really passed?
|
89
|
+
added: [:added, :renamed_new_file],
|
90
|
+
removed: [:removed, :renamed_old_file] }.find do |change, types|
|
91
|
+
types.include?(type) and break change
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|