listen 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,8 +1,23 @@
1
+ ## 0.4.2 - May 1, 2012
2
+
3
+ ### Bug fixes
4
+
5
+ - [#21](https://github.com/guard/listen/issues/21): Issues when listening to changes in relative paths. (reported by [@akerbos][], fixed by [@Maher4Ever][])
6
+ - [#27](https://github.com/guard/listen/issues/27): Wrong reports for files modifications. (reported by [@cobychapple][], fixed by [@Maher4Ever][])
7
+ - Fix segmentation fault when profiling on Windows. ([@Maher4Ever][])
8
+ - Fix redundant watchers on Windows. ([@Maher4Ever][])
9
+
10
+ ### Improvements
11
+
12
+ - [#17](https://github.com/guard/listen/issues/17): Use regexp-patterns with the `ignore` method instead of supplying paths. (reported by [@fny][], added by [@Maher4Ever][])
13
+ - Speed improvement when listening to changes in directories with ignored paths. ([@Maher4Ever][])
14
+ - Added `.rbx` and `.svn` to ignored directories. ([@Maher4Ever][])
15
+
1
16
  ## 0.4.1 - April 15, 2012
2
17
 
3
18
  ### Bug fixes
4
19
 
5
- - [#18](https://github.com/guard/listen/issues/18): Listener crashes when removing directories with nested paths (reported by [@daemonza][], fixed by [@Maher4Ever][])
20
+ - [#18]((https://github.com/guard/listen/issues/18): Listener crashes when removing directories with nested paths. (reported by [@daemonza][], fixed by [@Maher4Ever][])
6
21
 
7
22
  ## 0.4.0 - April 9, 2012
8
23
 
@@ -70,3 +85,6 @@
70
85
  [@thibaudgg]: https://github.com/thibaudgg
71
86
  [@Maher4Ever]: https://github.com/Maher4Ever
72
87
  [@daemonza]: https://github.com/daemonza
88
+ [@akerbos]: https://github.com/akerbos
89
+ [@fny]: https://github.com/fny
90
+ [@cobychapple]: https://github.com/cobychapple
data/README.md CHANGED
@@ -10,7 +10,7 @@ The Listen gem listens to file modifications and notifies you about the changes.
10
10
  * Automatic fallback to polling if OS-specific adapter doesn't work.
11
11
  * Detects files modification, addidation and removal.
12
12
  * Checksum comparaison for modifications made under the same second.
13
- * Allows ignoring paths and supplying filters for better results.
13
+ * Allows supplying regexp-patterns to ignore and filter paths for better results.
14
14
  * Tested on all Ruby environments via [travis-ci](http://travis-ci.org/guard/listen).
15
15
 
16
16
  ## Install
@@ -26,18 +26,18 @@ There are **two ways** to use Listen:
26
26
  1. Call `Listen.to` with either a single directory or multiple directories, then define the `change` callback in a block.
27
27
  2. Create a `listener` object and use it in an (ARel style) chainable way.
28
28
 
29
- Feel free to give your feeback via [Listen issues](https://github.com/guard/listener/issues)
29
+ Feel free to give your feeback via [Listen issues](https://github.com/guard/listen/issues)
30
30
 
31
31
  ### Block API
32
32
 
33
33
  ``` ruby
34
34
  # Listen to a single directory.
35
- Listen.to('dir/path/to/listen', filter: /.*\.rb/, ignore: '/ignored/path') do |modified, added, removed|
35
+ Listen.to('dir/path/to/listen', filter: /\.rb$/, ignore: %r{ignored/path/}) do |modified, added, removed|
36
36
  # ...
37
37
  end
38
38
 
39
39
  # Listen to multiple directories.
40
- Listen.to('dir/to/awesome_app', 'dir/to/other_app', filter: /.*\.rb/, latency: 0.1) do |modified, added, removed|
40
+ Listen.to('dir/to/awesome_app', 'dir/to/other_app', filter: /\.rb$/, latency: 0.1) do |modified, added, removed|
41
41
  # ...
42
42
  end
43
43
  ```
@@ -46,8 +46,8 @@ end
46
46
 
47
47
  ``` ruby
48
48
  listener = Listen.to('dir/path/to/listen')
49
- listener = listener.ignore('/ignored/path')
50
- listener = listener.filter(/.*\.rb/)
49
+ listener = listener.ignore(%r{^ignored/path/})
50
+ listener = listener.filter(/\.rb$/)
51
51
  listener = listener.latency(0.5)
52
52
  listener = listener.force_polling(true)
53
53
  listener = listener.polling_fallback_message(false)
@@ -59,8 +59,8 @@ listener.start # blocks execution!
59
59
 
60
60
  ``` ruby
61
61
  Listen.to('dir/path/to/listen')
62
- .ignore('/ignored/path')
63
- .filter(/.*\.rb/)
62
+ .ignore(%r{^ignored/path/})
63
+ .filter(/\.rb$/)
64
64
  .latency(0.5)
65
65
  .force_polling(true)
66
66
  .polling_fallback_message('custom message')
@@ -99,7 +99,7 @@ For an easier access, the `Listen.to` method can also be used to create a multi-
99
99
 
100
100
  ``` ruby
101
101
  listener = Listen.to('app/css', 'app/js')
102
- .ignore('vendor') # both js/vendor and css/vendor will be ignored
102
+ .ignore(%r{^vendor/}) # both js/vendor and css/vendor will be ignored
103
103
  .change(&assets_callback)
104
104
 
105
105
  listener.start # blocks execution!
@@ -184,11 +184,11 @@ end
184
184
  These options can be set through `Listen.to` params or via methods (see the "Object" API)
185
185
 
186
186
  ```ruby
187
- :filter => /.*\.rb/, /.*\.coffee/ # Filter files to listen to via a regexps list.
187
+ :filter => /\.rb$/, /\.coffee$/ # Filter files to listen to via a regexps list.
188
188
  # default: none
189
189
 
190
- :ignore => 'path1', 'path2' # Ignore a list of paths (root directory or sub-dir)
191
- # default: '.bundle', '.git', '.DS_Store', 'log', 'tmp', 'vendor'
190
+ :ignore => %r{app/CMake/}, /\.pid$/ # Ignore a list of paths (root directory or sub-dir)
191
+ # default: See DEFAULT_IGNORED_DIRECTORIES and DEFAULT_IGNORED_EXTENSIONS in Listen::DirectoryRecord
192
192
 
193
193
  :latency => 0.5 # Set the delay (**in seconds**) between checking for changes
194
194
  # default: 0.1 sec (1.0 sec for polling)
@@ -200,6 +200,18 @@ These options can be set through `Listen.to` params or via methods (see the "Obj
200
200
  # default: "WARNING: Listen fallen back to polling, learn more at https://github.com/guard/listen#fallback."
201
201
  ```
202
202
 
203
+ ### The patterns for filtering and ignoring paths
204
+
205
+ Just like the unix convention of beginning absolute paths with the
206
+ directory-separator (forward slash `/` in unix) and with no prefix for relative paths,
207
+ Listen doesn't prefix relative paths (to the watched directory) with a directory-separator.
208
+
209
+ Therefore make sure _NOT_ to prefix your regexp-patterns for filtering or ignoring paths
210
+ with a directory-separator, otherwise they won't work as expected.
211
+
212
+ As an example: to ignore the `build` directory in a C-project, use `%r{build/}`
213
+ and not `%r{/build/}`.
214
+
203
215
  ### Non-blocking listening to changes
204
216
 
205
217
  Starting a listener blocks the current thread by default. That means any code after the
@@ -63,17 +63,16 @@ module Listen
63
63
  # @return [FChange::Notifier] initialized worker
64
64
  #
65
65
  def init_worker
66
- worker = FChange::Notifier.new
67
- @directories.each do |directory|
68
- watcher = worker.watch(directory, :all_events, :recursive) do |event|
69
- next if @paused
70
- @mutex.synchronize do
71
- @changed_dirs << File.expand_path(event.watcher.path)
66
+ FChange::Notifier.new.tap do |worker|
67
+ @directories.each do |directory|
68
+ worker.watch(directory, :all_events, :recursive) do |event|
69
+ next if @paused
70
+ @mutex.synchronize do
71
+ @changed_dirs << File.expand_path(event.watcher.path)
72
+ end
72
73
  end
73
74
  end
74
- worker.add_watcher(watcher)
75
75
  end
76
- worker
77
76
  end
78
77
 
79
78
  end
@@ -1,6 +1,5 @@
1
1
  require 'set'
2
2
  require 'find'
3
- require 'pathname'
4
3
  require 'digest/sha1'
5
4
 
6
5
  module Listen
@@ -12,61 +11,85 @@ module Listen
12
11
  class DirectoryRecord
13
12
  attr_reader :directory, :paths, :sha1_checksums
14
13
 
15
- # Default paths' beginnings that doesn't get stored in the record
16
- DEFAULT_IGNORED_PATHS = %w[.bundle .git .DS_Store log tmp vendor]
14
+ DEFAULT_IGNORED_DIRECTORIES = %w[.rbx .bundle .git .svn log tmp vendor]
15
+
16
+ DEFAULT_IGNORED_EXTENSIONS = %w[.DS_Store]
17
+
18
+ # Class methods
19
+ #
20
+ class << self
21
+
22
+ # Creates the ignoring patterns from the default ignored
23
+ # directories and extensions. It memoizes the generated patterns
24
+ # to avoid unnecessary computation.
25
+ #
26
+ def generate_default_ignoring_patterns
27
+ @@default_ignoring_patterns ||= Array.new.tap do |default_patterns|
28
+ # Add directories
29
+ ignored_directories = DEFAULT_IGNORED_DIRECTORIES.map { |d| Regexp.escape(d) }
30
+ default_patterns << %r{^(?:#{ignored_directories.join('|')})/}
31
+
32
+ # Add extensions
33
+ ignored_extensions = DEFAULT_IGNORED_EXTENSIONS.map { |e| Regexp.escape(e) }
34
+ default_patterns << %r{(?:#{ignored_extensions.join('|')})$}
35
+ end
36
+ end
37
+ end
17
38
 
18
39
  # Initializes a directory record.
19
40
  #
20
41
  # @option [String] directory the directory to keep track of
21
42
  #
22
43
  def initialize(directory)
23
- @directory = directory
24
- raise ArgumentError, "The path '#{directory}' is not a directory!" unless File.directory?(@directory)
44
+ raise ArgumentError, "The path '#{directory}' is not a directory!" unless File.directory?(directory)
45
+
46
+ @directory = directory
47
+ @ignoring_patterns = Set.new
48
+ @filtering_patterns = Set.new
49
+ @sha1_checksums = Hash.new
25
50
 
26
- @ignored_paths = Set.new(DEFAULT_IGNORED_PATHS)
27
- @filters = Set.new
28
- @sha1_checksums = Hash.new
51
+ @ignoring_patterns.merge(DirectoryRecord.generate_default_ignoring_patterns)
29
52
  end
30
53
 
31
- # Returns the ignored paths in the record
54
+ # Returns the ignoring patterns in the record
32
55
  #
33
- # @return [Array<String>] the ignored paths
56
+ # @return [Array<Regexp>] the ignoring patterns
34
57
  #
35
- def ignored_paths
36
- @ignored_paths.to_a
58
+ def ignoring_patterns
59
+ @ignoring_patterns.to_a
37
60
  end
38
61
 
39
- # Returns the filters used in the record to know
62
+ # Returns the filtering patterns used in the record to know
40
63
  # which paths should be stored.
41
64
  #
42
- # @return [Array<String>] the used filters
65
+ # @return [Array<Regexp>] the filtering patterns
43
66
  #
44
- def filters
45
- @filters.to_a
67
+ def filtering_patterns
68
+ @filtering_patterns.to_a
46
69
  end
47
70
 
48
- # Adds ignored path to the record.
71
+ # Adds ignoring patterns to the record.
49
72
  #
50
73
  # @example Ignore some paths
51
74
  # ignore ".git", ".svn"
52
75
  #
53
- # @param [String, Array<String>] paths a path or a list of paths to ignore
76
+ # @param [Regexp] regexp a pattern for ignoring paths
54
77
  #
55
- def ignore(*paths)
56
- @ignored_paths.merge(paths)
78
+ def ignore(*regexps)
79
+ @ignoring_patterns.merge(regexps)
57
80
  end
58
81
 
59
- # Adds file filters to the listener.
82
+ # Adds filtering patterns to the listener.
60
83
  #
61
84
  # @example Filter some files
62
85
  # ignore /\.txt$/, /.*\.zip/
63
86
  #
64
- # @param [Array<Regexp>] regexps a list of regexps file filters
87
+ # @param [Regexp] regexp a pattern for filtering paths
65
88
  #
66
89
  # @return [Listen::Listener] the listener itself
67
90
  #
68
91
  def filter(*regexps)
69
- @filters.merge(regexps)
92
+ @filtering_patterns.merge(regexps)
70
93
  end
71
94
 
72
95
  # Returns whether a path should be ignored or not.
@@ -76,7 +99,8 @@ module Listen
76
99
  # @return [Boolean]
77
100
  #
78
101
  def ignored?(path)
79
- @ignored_paths.any? { |ignored_path| path =~ /#{ignored_path}$/ }
102
+ path = relative_to_base(path)
103
+ @ignoring_patterns.any? { |pattern| pattern =~ path }
80
104
  end
81
105
 
82
106
  # Returns whether a path should be filtered or not.
@@ -86,7 +110,11 @@ module Listen
86
110
  # @return [Boolean]
87
111
  #
88
112
  def filtered?(path)
89
- @filters.empty? || @filters.any? { |filter| path =~ filter }
113
+ # When no filtering patterns are set, ALL files are stored.
114
+ return true if @filtering_patterns.empty?
115
+
116
+ path = relative_to_base(path)
117
+ @filtering_patterns.any? { |pattern| pattern =~ path }
90
118
  end
91
119
 
92
120
  # Finds the paths that should be stored and adds them
@@ -95,7 +123,7 @@ module Listen
95
123
  def build
96
124
  @paths = Hash.new { |h, k| h[k] = Hash.new }
97
125
  important_paths { |path| insert_path(path) }
98
- @updated_at = Time.now.to_i
126
+ @updated_at = Time.now.to_f
99
127
  end
100
128
 
101
129
  # Detects changes in the passed directories, updates
@@ -116,10 +144,21 @@ module Listen
116
144
  detect_modifications_and_removals(directory, options)
117
145
  detect_additions(directory, options)
118
146
  end
119
- @updated_at = Time.now.to_i
147
+ @updated_at = Time.now.to_f
120
148
  @changes
121
149
  end
122
150
 
151
+ # Converts an absolute path to a path that's relative to the base directory.
152
+ #
153
+ # @param [String] path the path to convert
154
+ #
155
+ # @return [String] the relative path
156
+ #
157
+ def relative_to_base(path)
158
+ return nil unless path[@directory]
159
+ path.sub(%r{^#{@directory}#{File::SEPARATOR}?}, '')
160
+ end
161
+
123
162
  private
124
163
 
125
164
  # Detects modifications and removals recursively in a directory.
@@ -148,7 +187,7 @@ module Listen
148
187
  end
149
188
  when 'File'
150
189
  if File.exist?(path)
151
- new_mtime = File.mtime(path).to_i
190
+ new_mtime = File.mtime(path).to_f
152
191
  if @updated_at < new_mtime || (@updated_at == new_mtime && content_modified?(path))
153
192
  @changes[:modified] << (options[:relative_paths] ? relative_to_base(path) : path)
154
193
  end
@@ -176,7 +215,9 @@ module Listen
176
215
  next if path == @directory
177
216
 
178
217
  if File.directory?(path)
179
- if ignored?(path) || (directory != path && (!options[:recursive] && existing_path?(path)))
218
+ # Add a trailing slash to directories when checking if a directory is
219
+ # ignored to optimize finding them as Find.find doesn't.
220
+ if ignored?(path + File::SEPARATOR) || (directory != path && (!options[:recursive] && existing_path?(path)))
180
221
  Find.prune # Don't look any further into this directory.
181
222
  else
182
223
  insert_path(path)
@@ -215,7 +256,9 @@ module Listen
215
256
  next if path == @directory
216
257
 
217
258
  if File.directory?(path)
218
- if ignored?(path)
259
+ # Add a trailing slash to directories when checking if a directory is
260
+ # ignored to optimize finding them as Find.find doesn't.
261
+ if ignored?(path + File::SEPARATOR)
219
262
  Find.prune # Don't look any further into this directory.
220
263
  else
221
264
  yield(path)
@@ -243,15 +286,5 @@ module Listen
243
286
  def existing_path?(path)
244
287
  @paths[File.dirname(path)][File.basename(path)] != nil
245
288
  end
246
-
247
- # Converts an absolute path to a path that's relative to the base directory.
248
- #
249
- # @param [String] path the path to convert
250
- #
251
- # @return [String] the relative path
252
- #
253
- def relative_to_base(path)
254
- path.sub(%r(^#{@directory}/?/), '')
255
- end
256
289
  end
257
290
  end
@@ -1,3 +1,5 @@
1
+ require 'pathname'
2
+
1
3
  module Listen
2
4
  class Listener
3
5
  attr_reader :directory, :directory_record, :adapter
@@ -9,8 +11,8 @@ module Listen
9
11
  #
10
12
  # @param [String] directory the directory to listen to
11
13
  # @param [Hash] options the listen options
12
- # @option options [String] ignore a list of paths to ignore
13
- # @option options [Regexp] filter a list of regexps file filters
14
+ # @option options [Regexp] ignore a pattern for ignoring paths
15
+ # @option options [Regexp] filter a pattern for filtering paths
14
16
  # @option options [Float] latency the delay between checking for changes in seconds
15
17
  # @option options [Boolean] relative_paths whether or not to use relative-paths in the callback
16
18
  # @option options [Boolean] force_polling whether to force the polling adapter or not
@@ -23,8 +25,8 @@ module Listen
23
25
  #
24
26
  def initialize(directory, options = {}, &block)
25
27
  @block = block
26
- @directory = directory
27
- @directory_record = DirectoryRecord.new(directory)
28
+ @directory = Pathname.new(directory).realpath.to_s
29
+ @directory_record = DirectoryRecord.new(@directory)
28
30
  @use_relative_paths = DEFAULT_TO_RELATIVE_PATHS
29
31
 
30
32
  @use_relative_paths = options.delete(:relative_paths) if options[:relative_paths]
@@ -41,8 +43,8 @@ module Listen
41
43
  # @param [Boolean] blocking whether or not to block the current thread after starting
42
44
  #
43
45
  def start(blocking = true)
44
- t = Thread.new { @adapter = initialize_adapter }
45
- @directory_record.build
46
+ t = Thread.new { @directory_record.build }
47
+ @adapter = initialize_adapter
46
48
  t.join
47
49
  @adapter.start(blocking)
48
50
  end
@@ -80,18 +82,18 @@ module Listen
80
82
  !!@adapter && @adapter.paused == true
81
83
  end
82
84
 
83
- # Adds ignored paths to the listener.
85
+ # Adds ignoring patterns to the listener.
84
86
  #
85
87
  # @param (see Listen::DirectoryRecord#ignore)
86
88
  #
87
89
  # @return [Listen::Listener] the listener
88
90
  #
89
- def ignore(*paths)
90
- @directory_record.ignore(*paths)
91
+ def ignore(*regexps)
92
+ @directory_record.ignore(*regexps)
91
93
  self
92
94
  end
93
95
 
94
- # Adds file filters to the listener.
96
+ # Adds filtering patterns to the listener.
95
97
  #
96
98
  # @param (see Listen::DirectoryRecord#filter)
97
99
  #
@@ -6,8 +6,8 @@ module Listen
6
6
  #
7
7
  # @param [String] directories the directories to listen to
8
8
  # @param [Hash] options the listen options
9
- # @option options [String] ignore a list of paths to ignore
10
- # @option options [Regexp] filter a list of regexps file filters
9
+ # @option options [Regexp] ignore a pattern for ignoring paths
10
+ # @option options [Regexp] filter a pattern for filtering paths
11
11
  # @option options [Float] latency the delay between checking for changes in seconds
12
12
  # @option options [Boolean] force_polling whether to force the polling adapter or not
13
13
  # @option options [String, Boolean] polling_fallback_message to change polling fallback message or remove it
@@ -22,8 +22,8 @@ module Listen
22
22
  directories = args
23
23
 
24
24
  @block = block
25
- @directories = directories
26
- @directories_records = directories.map { |d| DirectoryRecord.new(d) }
25
+ @directories = directories.map { |d| Pathname.new(d).realpath.to_s }
26
+ @directories_records = @directories.map { |d| DirectoryRecord.new(d) }
27
27
 
28
28
  ignore(*options.delete(:ignore)) if options[:ignore]
29
29
  filter(*options.delete(:filter)) if options[:filter]
@@ -38,8 +38,8 @@ module Listen
38
38
  # @param [Boolean] blocking whether or not to block the current thread after starting
39
39
  #
40
40
  def start(blocking = true)
41
- t = Thread.new { @adapter = initialize_adapter }
42
- @directories_records.each { |r| r.build }
41
+ t = Thread.new { @directories_records.each { |r| r.build } }
42
+ @adapter = initialize_adapter
43
43
  t.join
44
44
  @adapter.start(blocking)
45
45
  end
@@ -1,3 +1,3 @@
1
1
  module Listen
2
- VERSION = '0.4.1'
2
+ VERSION = '0.4.2'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: listen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-04-15 00:00:00.000000000 Z
13
+ date: 2012-05-01 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rb-fsevent
17
- requirement: &22009780 !ruby/object:Gem::Requirement
17
+ requirement: &14607320 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ~>
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: 0.9.1
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *22009780
25
+ version_requirements: *14607320
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rb-inotify
28
- requirement: &22008640 !ruby/object:Gem::Requirement
28
+ requirement: &14606840 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ~>
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: 0.8.8
34
34
  type: :runtime
35
35
  prerelease: false
36
- version_requirements: *22008640
36
+ version_requirements: *14606840
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: rb-fchange
39
- requirement: &22006880 !ruby/object:Gem::Requirement
39
+ requirement: &14606320 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ~>
@@ -44,10 +44,10 @@ dependencies:
44
44
  version: 0.0.5
45
45
  type: :runtime
46
46
  prerelease: false
47
- version_requirements: *22006880
47
+ version_requirements: *14606320
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: bundler
50
- requirement: &22005160 !ruby/object:Gem::Requirement
50
+ requirement: &14605920 !ruby/object:Gem::Requirement
51
51
  none: false
52
52
  requirements:
53
53
  - - ! '>='
@@ -55,7 +55,7 @@ dependencies:
55
55
  version: '0'
56
56
  type: :development
57
57
  prerelease: false
58
- version_requirements: *22005160
58
+ version_requirements: *14605920
59
59
  description: The Listen gem listens to file modifications and notifies you about the
60
60
  changes. Works everywhere!
61
61
  email: