listen 0.4.1 → 0.4.2

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.
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: