listen 1.3.1 → 2.0.0.beta.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 +4 -4
- data/CHANGELOG.md +1 -368
- data/README.md +82 -215
- data/lib/listen.rb +2 -36
- data/lib/listen/adapter.rb +23 -304
- data/lib/listen/adapter/base.rb +40 -0
- data/lib/listen/adapter/bsd.rb +93 -0
- data/lib/listen/adapter/darwin.rb +44 -0
- data/lib/listen/adapter/linux.rb +92 -0
- data/lib/listen/adapter/polling.rb +49 -0
- data/lib/listen/adapter/windows.rb +63 -0
- data/lib/listen/change.rb +42 -0
- data/lib/listen/directory.rb +73 -0
- data/lib/listen/file.rb +108 -0
- data/lib/listen/listener.rb +69 -260
- data/lib/listen/record.rb +41 -0
- data/lib/listen/silencer.rb +44 -0
- data/lib/listen/version.rb +1 -1
- metadata +35 -17
- data/lib/listen/adapters/bsd.rb +0 -75
- data/lib/listen/adapters/darwin.rb +0 -48
- data/lib/listen/adapters/linux.rb +0 -81
- data/lib/listen/adapters/polling.rb +0 -58
- data/lib/listen/adapters/windows.rb +0 -91
- data/lib/listen/directory_record.rb +0 -406
- data/lib/listen/turnstile.rb +0 -32
data/lib/listen/listener.rb
CHANGED
@@ -1,27 +1,17 @@
|
|
1
|
-
require '
|
1
|
+
require 'listen/adapter'
|
2
|
+
require 'listen/change'
|
3
|
+
require 'listen/record'
|
2
4
|
|
3
5
|
module Listen
|
4
6
|
class Listener
|
5
|
-
|
6
|
-
|
7
|
-
BLOCKING_PARAMETER_DEPRECATION_MESSAGE = <<-EOS.gsub(/^\s*/, '')
|
8
|
-
The blocking parameter of Listen::Listener#start is deprecated.\n
|
9
|
-
Please use Listen::Adapter#start for a non-blocking listener and Listen::Listener#start! for a blocking one.
|
10
|
-
EOS
|
7
|
+
attr_accessor :options, :directories, :paused, :changes, :block
|
11
8
|
|
12
9
|
RELATIVE_PATHS_WITH_MULTIPLE_DIRECTORIES_WARNING_MESSAGE = "The relative_paths option doesn't work when listening to multiple diretories."
|
13
10
|
|
14
11
|
# Initializes the directories listener.
|
15
12
|
#
|
16
13
|
# @param [String] directory the directories to listen to
|
17
|
-
# @param [Hash] options the listen options
|
18
|
-
# @option options [Regexp] ignore a pattern for ignoring paths
|
19
|
-
# @option options [Regexp] filter a pattern for filtering paths
|
20
|
-
# @option options [Float] latency the delay between checking for changes in seconds
|
21
|
-
# @option options [Boolean] relative_paths whether or not to use relative-paths in the callback
|
22
|
-
# @option options [Boolean] force_polling whether to force the polling adapter or not
|
23
|
-
# @option options [String, Boolean] polling_fallback_message to change polling fallback message or remove it
|
24
|
-
# @option options [Class] force_adapter force the use of this adapter class, skipping usual adapter selection
|
14
|
+
# @param [Hash] options the listen options (see Listen::Listener::Options)
|
25
15
|
#
|
26
16
|
# @yield [modified, added, removed] the changed files
|
27
17
|
# @yieldparam [Array<String>] modified the list of modified files
|
@@ -29,295 +19,114 @@ module Listen
|
|
29
19
|
# @yieldparam [Array<String>] removed the list of removed files
|
30
20
|
#
|
31
21
|
def initialize(*args, &block)
|
32
|
-
options = args.last.is_a?(Hash) ? args.pop : {}
|
33
|
-
directories = args.flatten
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
ignore(*options.delete(:ignore))
|
39
|
-
filter(*options.delete(:filter))
|
40
|
-
|
41
|
-
@adapter_options = options
|
22
|
+
@options = _init_options(args.last.is_a?(Hash) ? args.pop : {})
|
23
|
+
@directories = args.flatten.map { |path| Pathname.new(path) }
|
24
|
+
@changes = []
|
25
|
+
@block = block
|
26
|
+
_init_debug
|
42
27
|
end
|
43
28
|
|
44
29
|
# Starts the listener by initializing the adapter and building
|
45
30
|
# the directory record concurrently, then it starts the adapter to watch
|
46
31
|
# for changes. The current thread is not blocked after starting.
|
47
32
|
#
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
# Starts the listener by initializing the adapter and building
|
57
|
-
# the directory record concurrently, then it starts the adapter to watch
|
58
|
-
# for changes. The current thread is blocked after starting.
|
59
|
-
#
|
60
|
-
# @see Listen::Listener#start
|
61
|
-
#
|
62
|
-
# @since 1.0.0
|
63
|
-
#
|
64
|
-
def start!
|
65
|
-
setup
|
66
|
-
adapter.start!
|
33
|
+
def start
|
34
|
+
_signals_trap
|
35
|
+
_init_actors
|
36
|
+
unpause
|
37
|
+
adapter.async.start
|
38
|
+
@thread = Thread.new { _wait_for_changes }
|
67
39
|
end
|
68
40
|
|
69
|
-
# Stops the listener.
|
70
|
-
#
|
71
41
|
def stop
|
72
|
-
adapter
|
42
|
+
Celluloid::Actor.kill(adapter)
|
43
|
+
Celluloid::Actor[:listen_change_pool].terminate
|
44
|
+
record && record.terminate
|
45
|
+
@thread && @thread.kill
|
73
46
|
end
|
74
47
|
|
75
|
-
# Pauses the listener.
|
76
|
-
#
|
77
|
-
# @return [Listen::Listener] the listener
|
78
|
-
#
|
79
48
|
def pause
|
80
|
-
|
81
|
-
self
|
49
|
+
@paused = true
|
82
50
|
end
|
83
51
|
|
84
|
-
# Unpauses the listener.
|
85
|
-
#
|
86
|
-
# @return [Listen::Listener] the listener
|
87
|
-
#
|
88
52
|
def unpause
|
89
|
-
|
90
|
-
|
91
|
-
self
|
53
|
+
_build_record_if_needed
|
54
|
+
@paused = false
|
92
55
|
end
|
93
56
|
|
94
|
-
# Returns whether the listener is paused or not.
|
95
|
-
#
|
96
|
-
# @return [Boolean] adapter paused status
|
97
|
-
#
|
98
57
|
def paused?
|
99
|
-
|
58
|
+
@paused == true
|
100
59
|
end
|
101
60
|
|
102
|
-
|
103
|
-
|
104
|
-
# @param (see Listen::DirectoryRecord#ignore)
|
105
|
-
#
|
106
|
-
# @return [Listen::Listener] the listener
|
107
|
-
#
|
108
|
-
# @see Listen::DirectoryRecord#ignore
|
109
|
-
#
|
110
|
-
def ignore(*regexps)
|
111
|
-
directories_records.each { |r| r.ignore(*regexps) }
|
112
|
-
self
|
61
|
+
def listen?
|
62
|
+
@paused == false
|
113
63
|
end
|
114
64
|
|
115
|
-
|
116
|
-
|
117
|
-
# @param (see Listen::DirectoryRecord#ignore!)
|
118
|
-
#
|
119
|
-
# @return [Listen::Listener] the listener
|
120
|
-
#
|
121
|
-
# @see Listen::DirectoryRecord#ignore!
|
122
|
-
#
|
123
|
-
def ignore!(*regexps)
|
124
|
-
directories_records.each { |r| r.ignore!(*regexps) }
|
125
|
-
self
|
126
|
-
end
|
127
|
-
|
128
|
-
# Adds filtering patterns to the listener.
|
129
|
-
#
|
130
|
-
# @param (see Listen::DirectoryRecord#filter)
|
131
|
-
#
|
132
|
-
# @return [Listen::Listener] the listener
|
133
|
-
#
|
134
|
-
# @see Listen::DirectoryRecord#filter
|
135
|
-
#
|
136
|
-
def filter(*regexps)
|
137
|
-
directories_records.each { |r| r.filter(*regexps) }
|
138
|
-
self
|
65
|
+
def adapter
|
66
|
+
Celluloid::Actor[:listen_adapter]
|
139
67
|
end
|
140
68
|
|
141
|
-
|
142
|
-
|
143
|
-
# @param (see Listen::DirectoryRecord#filter!)
|
144
|
-
#
|
145
|
-
# @return [Listen::Listener] the listener
|
146
|
-
#
|
147
|
-
# @see Listen::DirectoryRecord#filter!
|
148
|
-
#
|
149
|
-
def filter!(*regexps)
|
150
|
-
directories_records.each { |r| r.filter!(*regexps) }
|
151
|
-
self
|
69
|
+
def record
|
70
|
+
Celluloid::Actor[:listen_record]
|
152
71
|
end
|
153
72
|
|
154
|
-
|
155
|
-
# to simplify changing the latency directly from the listener.
|
156
|
-
#
|
157
|
-
# @example Wait 0.5 seconds each time before checking changes
|
158
|
-
# latency 0.5
|
159
|
-
#
|
160
|
-
# @param [Float] seconds the amount of delay, in seconds
|
161
|
-
#
|
162
|
-
# @return [Listen::Listener] the listener
|
163
|
-
#
|
164
|
-
def latency(seconds)
|
165
|
-
@adapter_options[:latency] = seconds
|
166
|
-
self
|
167
|
-
end
|
73
|
+
private
|
168
74
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
#
|
175
|
-
# @param [Boolean] value whether to force the polling adapter or not
|
176
|
-
#
|
177
|
-
# @return [Listen::Listener] the listener
|
178
|
-
#
|
179
|
-
def force_polling(value)
|
180
|
-
@adapter_options[:force_polling] = value
|
181
|
-
self
|
75
|
+
def _init_options(options = {})
|
76
|
+
{ debug: false,
|
77
|
+
latency: nil,
|
78
|
+
force_polling: false,
|
79
|
+
polling_fallback_message: nil }.merge(options)
|
182
80
|
end
|
183
81
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
# @param [Class] adapter class to use for file system event notification.
|
191
|
-
#
|
192
|
-
# @return [Listen::Listener] the listener
|
193
|
-
#
|
194
|
-
def force_adapter(adapter_class)
|
195
|
-
@adapter_options[:force_adapter] = adapter_class
|
196
|
-
self
|
82
|
+
def _init_debug
|
83
|
+
if options[:debug]
|
84
|
+
Celluloid.logger.level = Logger::INFO
|
85
|
+
else
|
86
|
+
Celluloid.logger = nil
|
87
|
+
end
|
197
88
|
end
|
198
89
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
#
|
205
|
-
# @param [Boolean] value whether to enable relative paths in the callback or not
|
206
|
-
#
|
207
|
-
# @return [Listen::Listener] the listener
|
208
|
-
#
|
209
|
-
def relative_paths(value)
|
210
|
-
@use_relative_paths = value
|
211
|
-
self
|
90
|
+
def _init_actors
|
91
|
+
cores = Celluloid.cores || 2
|
92
|
+
Celluloid::Actor[:listen_change_pool] = Change.pool(size: cores, args: self)
|
93
|
+
Celluloid::Actor[:listen_adapter] = Adapter.new(self)
|
94
|
+
Celluloid::Actor[:listen_record] = Record.new(self)
|
212
95
|
end
|
213
96
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
#
|
219
|
-
# @param [String, Boolean] value to change polling fallback message or remove it
|
220
|
-
#
|
221
|
-
# @return [Listen::Listener] the listener
|
222
|
-
#
|
223
|
-
def polling_fallback_message(value)
|
224
|
-
@adapter_options[:polling_fallback_message] = value
|
225
|
-
self
|
97
|
+
def _signals_trap
|
98
|
+
if Signal.list.keys.include?('INT')
|
99
|
+
Signal.trap('INT') { exit }
|
100
|
+
end
|
226
101
|
end
|
227
102
|
|
228
|
-
|
229
|
-
|
230
|
-
# @example Assign a callback to be called on changes
|
231
|
-
# callback = lambda { |modified, added, removed| ... }
|
232
|
-
# change &callback
|
233
|
-
#
|
234
|
-
# @param [Proc] block the callback proc
|
235
|
-
#
|
236
|
-
# @return [Listen::Listener] the listener
|
237
|
-
#
|
238
|
-
def change(&block) # modified, added, removed
|
239
|
-
@block = block
|
240
|
-
self
|
103
|
+
def _build_record_if_needed
|
104
|
+
record && record.build
|
241
105
|
end
|
242
106
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
107
|
+
def _wait_for_changes
|
108
|
+
loop do
|
109
|
+
changes = _pop_changes
|
110
|
+
unless changes.values.all?(&:empty?)
|
111
|
+
block.call(
|
112
|
+
changes[:modified].uniq,
|
113
|
+
changes[:added].uniq,
|
114
|
+
changes[:removed].uniq)
|
115
|
+
end
|
116
|
+
sleep 0.1
|
253
117
|
end
|
254
118
|
rescue => ex
|
255
119
|
Kernel.warn "[Listen warning]: Change block raise an execption: #{$!}"
|
256
120
|
Kernel.warn "Backtrace:\n\t#{ex.backtrace.join("\n\t")}"
|
257
121
|
end
|
258
122
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
#
|
265
|
-
def initialize_directories_and_directories_records(directories)
|
266
|
-
@directories = directories.map { |d| Pathname.new(d).realpath.to_s }
|
267
|
-
@directories_records = directories.map { |d| DirectoryRecord.new(d) }
|
268
|
-
end
|
269
|
-
|
270
|
-
# Initializes whether or not using relative paths.
|
271
|
-
#
|
272
|
-
def initialize_relative_paths_usage(options)
|
273
|
-
if directories.size > 1 && options[:relative_paths]
|
274
|
-
Kernel.warn "[Listen warning]: #{RELATIVE_PATHS_WITH_MULTIPLE_DIRECTORIES_WARNING_MESSAGE}"
|
123
|
+
def _pop_changes
|
124
|
+
changes = { modified: [], added: [], removed: [] }
|
125
|
+
until @changes.empty?
|
126
|
+
change = @changes.pop
|
127
|
+
change.each { |k, v| changes[k] << v.to_s }
|
275
128
|
end
|
276
|
-
|
277
|
-
end
|
278
|
-
|
279
|
-
# Build the directory record concurrently and initialize the adapter.
|
280
|
-
#
|
281
|
-
def setup
|
282
|
-
t = Thread.new { build_directories_records }
|
283
|
-
@adapter = initialize_adapter
|
284
|
-
t.join
|
285
|
-
end
|
286
|
-
|
287
|
-
# Initializes an adapter passing it the callback and adapters' options.
|
288
|
-
#
|
289
|
-
def initialize_adapter
|
290
|
-
callback = lambda { |changed_directories, options| self.on_change(changed_directories, options) }
|
291
|
-
Adapter.select_and_initialize(directories, adapter_options, &callback)
|
129
|
+
changes
|
292
130
|
end
|
293
|
-
|
294
|
-
# Build the watched directories' records.
|
295
|
-
#
|
296
|
-
def build_directories_records
|
297
|
-
directories_records.each { |r| r.build }
|
298
|
-
end
|
299
|
-
|
300
|
-
# Returns the sum of all the changes to the directories records
|
301
|
-
#
|
302
|
-
# @param (see Listen::DirectoryRecord#fetch_changes)
|
303
|
-
#
|
304
|
-
# @return [Hash] the changes
|
305
|
-
#
|
306
|
-
def fetch_records_changes(directories_to_search, options)
|
307
|
-
directories_records.inject({}) do |h, r|
|
308
|
-
# directory records skips paths outside their range, so passing the
|
309
|
-
# whole `directories` array is not a problem.
|
310
|
-
record_changes = r.fetch_changes(directories_to_search, options.merge(:relative_paths => use_relative_paths))
|
311
|
-
|
312
|
-
if h.empty?
|
313
|
-
h.merge!(record_changes)
|
314
|
-
else
|
315
|
-
h.each { |k, v| h[k] += record_changes[k] }
|
316
|
-
end
|
317
|
-
|
318
|
-
h
|
319
|
-
end
|
320
|
-
end
|
321
|
-
|
322
131
|
end
|
323
132
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Listen
|
2
|
+
class Record
|
3
|
+
include Celluloid
|
4
|
+
|
5
|
+
attr_accessor :paths, :listener
|
6
|
+
|
7
|
+
def initialize(listener)
|
8
|
+
@listener = listener
|
9
|
+
@paths = _init_paths
|
10
|
+
end
|
11
|
+
|
12
|
+
def set_path(path, data)
|
13
|
+
@paths[::File.dirname(path)][::File.basename(path)] = file_data(path).merge(data)
|
14
|
+
end
|
15
|
+
|
16
|
+
def unset_path(path)
|
17
|
+
@paths[::File.dirname(path)].delete(::File.basename(path))
|
18
|
+
end
|
19
|
+
|
20
|
+
def file_data(path)
|
21
|
+
@paths[::File.dirname(path)][::File.basename(path)] || {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def dir_entries(path)
|
25
|
+
@paths[path.to_s]
|
26
|
+
end
|
27
|
+
|
28
|
+
def build
|
29
|
+
@paths = _init_paths
|
30
|
+
listener.directories.each do |path|
|
31
|
+
Actor[:listen_change_pool].change(path, type: 'Dir', recursive: true, silence: true)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def _init_paths
|
38
|
+
Hash.new { |h, k| h[k] = Hash.new }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Listen
|
2
|
+
class Silencer
|
3
|
+
# The default list of directories that get ignored.
|
4
|
+
DEFAULT_IGNORED_DIRECTORIES = %w[.rbx .bundle .git .svn .hg bundle log tmp vendor]
|
5
|
+
|
6
|
+
# The default list of files that get ignored.
|
7
|
+
DEFAULT_IGNORED_EXTENSIONS = %w[.DS_Store]
|
8
|
+
|
9
|
+
attr_accessor :options, :patterns
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@options = options
|
13
|
+
_init_patterns
|
14
|
+
end
|
15
|
+
|
16
|
+
def silenced?(path)
|
17
|
+
patterns.any? { |pattern| pattern =~ path.to_s }
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def _init_patterns
|
23
|
+
@patterns = []
|
24
|
+
@patterns << _default_patterns unless options[:ignore!]
|
25
|
+
@patterns << options[:ignore] << options[:ignore!]
|
26
|
+
@patterns.compact
|
27
|
+
@patterns.flatten!
|
28
|
+
end
|
29
|
+
|
30
|
+
def _default_patterns
|
31
|
+
[_default_ignored_directories_patterns, _default_ignored_extensions_patterns]
|
32
|
+
end
|
33
|
+
|
34
|
+
def _default_ignored_directories_patterns
|
35
|
+
ignored_directories = DEFAULT_IGNORED_DIRECTORIES.map { |d| Regexp.escape(d) }
|
36
|
+
%r{(?:#{ignored_directories.join('|')})}
|
37
|
+
end
|
38
|
+
|
39
|
+
def _default_ignored_extensions_patterns
|
40
|
+
ignored_extensions = DEFAULT_IGNORED_EXTENSIONS.map { |e| Regexp.escape(e) }
|
41
|
+
%r{(?:#{ignored_extensions.join('|')})$}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|