listen 0.5.1 → 0.5.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.
@@ -1,167 +1,173 @@
1
- ## 0.5.1 - Septemper 18, 2012
2
-
3
- ### Bug fixes
4
-
5
- - [#61] Fix a synchronisation bug that caused constant fallback to polling. (fixed by [@Maher4Ever][])
6
-
7
- ## 0.5.0 - Septemper 1, 2012
8
-
9
- ### New features
10
-
11
- - Add a dependency manager to handle platform-specific gems. So there is no need anymore to install
12
- extra gems which will never be used on the user system. ([@Maher4Ever][])
13
- - Add a manual reporting mode to the adapters. ([@Maher4Ever][])
14
-
15
- ### Improvements
16
-
17
- - [#28] Enhance the speed of detecting changes on Windows by using the [WDM][] library. ([@Maher4Ever][])
18
-
19
- ## 0.4.7 - June 27, 2012
20
-
21
- ### Bug fixes
22
-
23
- - Increase latency to 0.25, to avoid useless polling fallback. (fixed by [@thibaudgg][])
24
- - Change watched inotify events, to avoid duplication callback. (fixed by [@thibaudgg][])
25
- - [#41](https://github.com/guard/listen/issues/41) Use lstat instead of stat when calculating mtime. (fixed by [@ebroder][])
26
-
27
- ## 0.4.6 - June 20, 2012
28
-
29
- ### Bug fix
30
-
31
- - [#39](https://github.com/guard/listen/issues/39) Fix digest race condition. (fixed by [@dkubb][])
32
-
33
- ## 0.4.5 - June 13, 2012
34
-
35
- ### Bug fix
36
-
37
- - [#39](https://github.com/guard/listen/issues/39) Rescue Errno::ENOENT when path inserted doesn't exist. (reported by [@textgoeshere][], fixed by [@thibaudgg][] and [@rymai][])
38
-
39
- ## 0.4.4 - June 8, 2012
40
-
41
- ### Bug fixes
42
-
43
- - ~~[#39](https://github.com/guard/listen/issues/39) Non-existing path insertion bug. (reported by [@textgoeshere][], fixed by [@thibaudgg][])~~
44
- - Fix relative path for directories containing special characters. (reported by [@napcs][], fixed by [@netzpirat][])
45
-
46
- ## 0.4.3 - June 6, 2012
47
-
48
- ### Bug fixes
49
-
50
- - [#24](https://github.com/guard/listen/issues/24) Fail gracefully when the inotify limit is not enough for Listen to function. (reported by [@daemonza][], fixed by [@Maher4Ever][])
51
- - [#32](https://github.com/guard/listen/issues/32) Fix a crash when trying to calculate the checksum of unreadable files. (reported by [@nex3][], fixed by [@Maher4Ever][])
52
-
53
- ### Improvements
54
-
55
- - Add `#relative_paths` method to listeners. ([@Maher4Ever][])
56
- - Add `#started?` query-method to adapters. ([@Maher4Ever][])
57
- - Dynamically detect the mtime precision used on a system. ([@Maher4Ever][] with help from [@nex3][])
58
-
59
- ## 0.4.2 - May 1, 2012
60
-
61
- ### Bug fixes
62
-
63
- - [#21](https://github.com/guard/listen/issues/21) Issues when listening to changes in relative paths. (reported by [@akerbos][], fixed by [@Maher4Ever][])
64
- - [#27](https://github.com/guard/listen/issues/27) Wrong reports for files modifications. (reported by [@cobychapple][], fixed by [@Maher4Ever][])
65
- - Fix segmentation fault when profiling on Windows. ([@Maher4Ever][])
66
- - Fix redundant watchers on Windows. ([@Maher4Ever][])
67
-
68
- ### Improvements
69
-
70
- - [#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][])
71
- - Speed improvement when listening to changes in directories with ignored paths. ([@Maher4Ever][])
72
- - Added `.rbx` and `.svn` to ignored directories. ([@Maher4Ever][])
73
-
74
- ## 0.4.1 - April 15, 2012
75
-
76
- ### Bug fix
77
-
78
- - [#18](https://github.com/guard/listen/issues/18) Listener crashes when removing directories with nested paths. (reported by [@daemonza][], fixed by [@Maher4Ever][])
79
-
80
- ## 0.4.0 - April 9, 2012
81
-
82
- ### New features
83
-
84
- - Add `wait_for_callback` method to all adapters. ([@Maher4Ever][])
85
- - Add `Listen::MultiListener` class to listen to multiple directories at once. ([@Maher4Ever][])
86
- - Allow passing multiple directories to the `Listen.to` method. ([@Maher4Ever][])
87
- - Add `blocking` option to `Listen#start` which can be used to disable blocking the current thread upon starting. ([@Maher4Ever][])
88
- - Use absolute-paths in callbacks by default instead of relative-paths. ([@Maher4Ever][])
89
- - Add `relative_paths` option to `Listen::Listener` to retain the old functionality. ([@Maher4Ever][])
90
-
91
- ### Improvements
92
-
93
- - Encapsulate thread spawning in the linux-adapter. ([@Maher4Ever][])
94
- - Encapsulate thread spawning in the darwin-adapter. ([@Maher4Ever][] with [@scottdavis][] help)
95
- - Encapsulate thread spawning in the windows-adapter. ([@Maher4Ever][])
96
- - Fix linux-adapter bug where Listen would report file-modification events on the parent-directory. ([@Maher4Ever][])
97
-
98
- ### Change
99
-
100
- - Remove `wait_until_listening` as adapters doesn't need to run inside threads anymore ([@Maher4Ever][])
101
-
102
- ## 0.3.3 - March 6, 2012
103
-
104
- ### Improvement
105
-
106
- - Improve pause/unpause. ([@thibaudgg][])
107
-
108
- ## 0.3.2 - March 4, 2012
109
-
110
- ### New feature
111
-
112
- - Add pause/unpause listener's methods. ([@thibaudgg][])
113
-
114
- ## 0.3.1 - February 22, 2012
115
-
116
- ### Bug fix
117
-
118
- - [#9](https://github.com/guard/listen/issues/9) Ignore doesn't seem to work. (reported by [@markiz][], fixed by [@thibaudgg][])
119
-
120
- ## 0.3.0 - February 21, 2012
121
-
122
- ### New features
123
-
124
- - Add automatic fallback to polling if system adapter doesn't work (like a DropBox folder). ([@thibaudgg][])
125
- - Add latency and force_polling options. ([@Maher4Ever][])
126
-
127
- ## 0.2.0 - February 13, 2012
128
-
129
- ### New features
130
-
131
- - Add checksum comparaison support for detecting consecutive file modifications made during the same second. ([@thibaudgg][])
132
- - Add rb-fchange support. ([@thibaudgg][])
133
- - Add rb-inotify support. ([@thibaudgg][] with [@Maher4Ever][] help)
134
- - Add rb-fsevent support. ([@thibaudgg][])
135
- - Add non-recursive diff with multiple directories support. ([@thibaudgg][])
136
- - Ignore .DS_Store by default. ([@thibaudgg][])
137
-
138
- ## 0.1.0 - January 28, 2012
139
-
140
- - First version with only a polling adapter and basic features set (ignore & filter). ([@thibaudgg][])
141
-
142
- <!--- The following link definition list is generated by PimpMyChangelog --->
143
- [#9]: https://github.com/guard/listen/issues/9
144
- [#17]: https://github.com/guard/listen/issues/17
145
- [#18]: https://github.com/guard/listen/issues/18
146
- [#21]: https://github.com/guard/listen/issues/21
147
- [#24]: https://github.com/guard/listen/issues/24
148
- [#27]: https://github.com/guard/listen/issues/27
149
- [#28]: https://github.com/guard/listen/issues/28
150
- [#32]: https://github.com/guard/listen/issues/32
151
- [#39]: https://github.com/guard/listen/issues/39
152
- [@Maher4Ever]: https://github.com/Maher4Ever
153
- [@dkubb]: https://github.com/dkubb
154
- [@ebroder]: https://github.com/ebroder
155
- [@akerbos]: https://github.com/akerbos
156
- [@cobychapple]: https://github.com/cobychapple
157
- [@daemonza]: https://github.com/daemonza
158
- [@fny]: https://github.com/fny
159
- [@markiz]: https://github.com/markiz
160
- [@napcs]: https://github.com/napcs
161
- [@netzpirat]: https://github.com/netzpirat
162
- [@nex3]: https://github.com/nex3
163
- [@rymai]: https://github.com/rymai
164
- [@scottdavis]: https://github.com/scottdavis
165
- [@textgoeshere]: https://github.com/textgoeshere
166
- [@thibaudgg]: https://github.com/thibaudgg
167
- [WDM]: https://github.com/Maher4Ever/wdm
1
+ ## 0.5.2 - Septemper 23, 2012
2
+
3
+ ### Bug fix
4
+
5
+ - [#62] Fix double change callback with polling adapter. (fixed by [@thibaudgg][])
6
+
7
+ ## 0.5.1 - Septemper 18, 2012
8
+
9
+ ### Bug fix
10
+
11
+ - [#61] Fix a synchronisation bug that caused constant fallback to polling. (fixed by [@Maher4Ever][])
12
+
13
+ ## 0.5.0 - Septemper 1, 2012
14
+
15
+ ### New features
16
+
17
+ - Add a dependency manager to handle platform-specific gems. So there is no need anymore to install
18
+ extra gems which will never be used on the user system. ([@Maher4Ever][])
19
+ - Add a manual reporting mode to the adapters. ([@Maher4Ever][])
20
+
21
+ ### Improvements
22
+
23
+ - [#28] Enhance the speed of detecting changes on Windows by using the [WDM][] library. ([@Maher4Ever][])
24
+
25
+ ## 0.4.7 - June 27, 2012
26
+
27
+ ### Bug fixes
28
+
29
+ - Increase latency to 0.25, to avoid useless polling fallback. (fixed by [@thibaudgg][])
30
+ - Change watched inotify events, to avoid duplication callback. (fixed by [@thibaudgg][])
31
+ - [#41](https://github.com/guard/listen/issues/41) Use lstat instead of stat when calculating mtime. (fixed by [@ebroder][])
32
+
33
+ ## 0.4.6 - June 20, 2012
34
+
35
+ ### Bug fix
36
+
37
+ - [#39](https://github.com/guard/listen/issues/39) Fix digest race condition. (fixed by [@dkubb][])
38
+
39
+ ## 0.4.5 - June 13, 2012
40
+
41
+ ### Bug fix
42
+
43
+ - [#39](https://github.com/guard/listen/issues/39) Rescue Errno::ENOENT when path inserted doesn't exist. (reported by [@textgoeshere][], fixed by [@thibaudgg][] and [@rymai][])
44
+
45
+ ## 0.4.4 - June 8, 2012
46
+
47
+ ### Bug fixes
48
+
49
+ - ~~[#39](https://github.com/guard/listen/issues/39) Non-existing path insertion bug. (reported by [@textgoeshere][], fixed by [@thibaudgg][])~~
50
+ - Fix relative path for directories containing special characters. (reported by [@napcs][], fixed by [@netzpirat][])
51
+
52
+ ## 0.4.3 - June 6, 2012
53
+
54
+ ### Bug fixes
55
+
56
+ - [#24](https://github.com/guard/listen/issues/24) Fail gracefully when the inotify limit is not enough for Listen to function. (reported by [@daemonza][], fixed by [@Maher4Ever][])
57
+ - [#32](https://github.com/guard/listen/issues/32) Fix a crash when trying to calculate the checksum of unreadable files. (reported by [@nex3][], fixed by [@Maher4Ever][])
58
+
59
+ ### Improvements
60
+
61
+ - Add `#relative_paths` method to listeners. ([@Maher4Ever][])
62
+ - Add `#started?` query-method to adapters. ([@Maher4Ever][])
63
+ - Dynamically detect the mtime precision used on a system. ([@Maher4Ever][] with help from [@nex3][])
64
+
65
+ ## 0.4.2 - May 1, 2012
66
+
67
+ ### Bug fixes
68
+
69
+ - [#21](https://github.com/guard/listen/issues/21) Issues when listening to changes in relative paths. (reported by [@akerbos][], fixed by [@Maher4Ever][])
70
+ - [#27](https://github.com/guard/listen/issues/27) Wrong reports for files modifications. (reported by [@cobychapple][], fixed by [@Maher4Ever][])
71
+ - Fix segmentation fault when profiling on Windows. ([@Maher4Ever][])
72
+ - Fix redundant watchers on Windows. ([@Maher4Ever][])
73
+
74
+ ### Improvements
75
+
76
+ - [#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][])
77
+ - Speed improvement when listening to changes in directories with ignored paths. ([@Maher4Ever][])
78
+ - Added `.rbx` and `.svn` to ignored directories. ([@Maher4Ever][])
79
+
80
+ ## 0.4.1 - April 15, 2012
81
+
82
+ ### Bug fix
83
+
84
+ - [#18](https://github.com/guard/listen/issues/18) Listener crashes when removing directories with nested paths. (reported by [@daemonza][], fixed by [@Maher4Ever][])
85
+
86
+ ## 0.4.0 - April 9, 2012
87
+
88
+ ### New features
89
+
90
+ - Add `wait_for_callback` method to all adapters. ([@Maher4Ever][])
91
+ - Add `Listen::MultiListener` class to listen to multiple directories at once. ([@Maher4Ever][])
92
+ - Allow passing multiple directories to the `Listen.to` method. ([@Maher4Ever][])
93
+ - Add `blocking` option to `Listen#start` which can be used to disable blocking the current thread upon starting. ([@Maher4Ever][])
94
+ - Use absolute-paths in callbacks by default instead of relative-paths. ([@Maher4Ever][])
95
+ - Add `relative_paths` option to `Listen::Listener` to retain the old functionality. ([@Maher4Ever][])
96
+
97
+ ### Improvements
98
+
99
+ - Encapsulate thread spawning in the linux-adapter. ([@Maher4Ever][])
100
+ - Encapsulate thread spawning in the darwin-adapter. ([@Maher4Ever][] with [@scottdavis][] help)
101
+ - Encapsulate thread spawning in the windows-adapter. ([@Maher4Ever][])
102
+ - Fix linux-adapter bug where Listen would report file-modification events on the parent-directory. ([@Maher4Ever][])
103
+
104
+ ### Change
105
+
106
+ - Remove `wait_until_listening` as adapters doesn't need to run inside threads anymore ([@Maher4Ever][])
107
+
108
+ ## 0.3.3 - March 6, 2012
109
+
110
+ ### Improvement
111
+
112
+ - Improve pause/unpause. ([@thibaudgg][])
113
+
114
+ ## 0.3.2 - March 4, 2012
115
+
116
+ ### New feature
117
+
118
+ - Add pause/unpause listener's methods. ([@thibaudgg][])
119
+
120
+ ## 0.3.1 - February 22, 2012
121
+
122
+ ### Bug fix
123
+
124
+ - [#9](https://github.com/guard/listen/issues/9) Ignore doesn't seem to work. (reported by [@markiz][], fixed by [@thibaudgg][])
125
+
126
+ ## 0.3.0 - February 21, 2012
127
+
128
+ ### New features
129
+
130
+ - Add automatic fallback to polling if system adapter doesn't work (like a DropBox folder). ([@thibaudgg][])
131
+ - Add latency and force_polling options. ([@Maher4Ever][])
132
+
133
+ ## 0.2.0 - February 13, 2012
134
+
135
+ ### New features
136
+
137
+ - Add checksum comparaison support for detecting consecutive file modifications made during the same second. ([@thibaudgg][])
138
+ - Add rb-fchange support. ([@thibaudgg][])
139
+ - Add rb-inotify support. ([@thibaudgg][] with [@Maher4Ever][] help)
140
+ - Add rb-fsevent support. ([@thibaudgg][])
141
+ - Add non-recursive diff with multiple directories support. ([@thibaudgg][])
142
+ - Ignore .DS_Store by default. ([@thibaudgg][])
143
+
144
+ ## 0.1.0 - January 28, 2012
145
+
146
+ - First version with only a polling adapter and basic features set (ignore & filter). ([@thibaudgg][])
147
+
148
+ <!--- The following link definition list is generated by PimpMyChangelog --->
149
+ [#9]: https://github.com/guard/listen/issues/9
150
+ [#17]: https://github.com/guard/listen/issues/17
151
+ [#18]: https://github.com/guard/listen/issues/18
152
+ [#21]: https://github.com/guard/listen/issues/21
153
+ [#24]: https://github.com/guard/listen/issues/24
154
+ [#27]: https://github.com/guard/listen/issues/27
155
+ [#28]: https://github.com/guard/listen/issues/28
156
+ [#32]: https://github.com/guard/listen/issues/32
157
+ [#39]: https://github.com/guard/listen/issues/39
158
+ [@Maher4Ever]: https://github.com/Maher4Ever
159
+ [@dkubb]: https://github.com/dkubb
160
+ [@ebroder]: https://github.com/ebroder
161
+ [@akerbos]: https://github.com/akerbos
162
+ [@cobychapple]: https://github.com/cobychapple
163
+ [@daemonza]: https://github.com/daemonza
164
+ [@fny]: https://github.com/fny
165
+ [@markiz]: https://github.com/markiz
166
+ [@napcs]: https://github.com/napcs
167
+ [@netzpirat]: https://github.com/netzpirat
168
+ [@nex3]: https://github.com/nex3
169
+ [@rymai]: https://github.com/rymai
170
+ [@scottdavis]: https://github.com/scottdavis
171
+ [@textgoeshere]: https://github.com/textgoeshere
172
+ [@thibaudgg]: https://github.com/thibaudgg
173
+ [WDM]: https://github.com/Maher4Ever/wdm
data/LICENSE CHANGED
@@ -1,20 +1,20 @@
1
- Copyright (c) 2012 Thibaud Guillaume-Gentil
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1
+ Copyright (c) 2012 Thibaud Guillaume-Gentil
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -50,7 +50,7 @@ module Listen
50
50
  #
51
51
  def poll
52
52
  until @stop
53
- sleep(0.1) && next if @paused
53
+ next if @paused
54
54
 
55
55
  start = Time.now.to_f
56
56
  @callback.call(@directories.dup, :recursive => true)
@@ -1,318 +1,344 @@
1
- require 'set'
2
- require 'find'
3
- require 'digest/sha1'
4
-
5
- module Listen
6
-
7
- # The directory record stores information about
8
- # a directory and keeps track of changes to
9
- # the structure of its childs.
10
- #
11
- class DirectoryRecord
12
- attr_reader :directory, :paths, :sha1_checksums
13
-
14
- DEFAULT_IGNORED_DIRECTORIES = %w[.rbx .bundle .git .svn log tmp vendor]
15
-
16
- DEFAULT_IGNORED_EXTENSIONS = %w[.DS_Store]
17
-
18
- # Defines the used precision based on the type of mtime returned by the
19
- # system (whether its in milliseconds or just seconds)
20
- #
21
- HIGH_PRECISION_SUPPORTED = File.mtime(__FILE__).to_f.to_s[-2..-1] != '.0'
22
-
23
- # Data structure used to save meta data about a path
24
- #
25
- MetaData = Struct.new(:type, :mtime)
26
-
27
- # Class methods
28
- #
29
- class << self
30
-
31
- # Creates the ignoring patterns from the default ignored
32
- # directories and extensions. It memoizes the generated patterns
33
- # to avoid unnecessary computation.
34
- #
35
- def generate_default_ignoring_patterns
36
- @@default_ignoring_patterns ||= Array.new.tap do |default_patterns|
37
- # Add directories
38
- ignored_directories = DEFAULT_IGNORED_DIRECTORIES.map { |d| Regexp.escape(d) }
39
- default_patterns << %r{^(?:#{ignored_directories.join('|')})/}
40
-
41
- # Add extensions
42
- ignored_extensions = DEFAULT_IGNORED_EXTENSIONS.map { |e| Regexp.escape(e) }
43
- default_patterns << %r{(?:#{ignored_extensions.join('|')})$}
44
- end
45
- end
46
- end
47
-
48
- # Initializes a directory record.
49
- #
50
- # @option [String] directory the directory to keep track of
51
- #
52
- def initialize(directory)
53
- raise ArgumentError, "The path '#{directory}' is not a directory!" unless File.directory?(directory)
54
-
55
- @directory = directory
56
- @ignoring_patterns = Set.new
57
- @filtering_patterns = Set.new
58
- @sha1_checksums = Hash.new
59
-
60
- @ignoring_patterns.merge(DirectoryRecord.generate_default_ignoring_patterns)
61
- end
62
-
63
- # Returns the ignoring patterns in the record
64
- #
65
- # @return [Array<Regexp>] the ignoring patterns
66
- #
67
- def ignoring_patterns
68
- @ignoring_patterns.to_a
69
- end
70
-
71
- # Returns the filtering patterns used in the record to know
72
- # which paths should be stored.
73
- #
74
- # @return [Array<Regexp>] the filtering patterns
75
- #
76
- def filtering_patterns
77
- @filtering_patterns.to_a
78
- end
79
-
80
- # Adds ignoring patterns to the record.
81
- #
82
- # @example Ignore some paths
83
- # ignore %r{^ignored/path/}, /man/
84
- #
85
- # @param [Regexp] regexp a pattern for ignoring paths
86
- #
87
- def ignore(*regexps)
88
- @ignoring_patterns.merge(regexps)
89
- end
90
-
91
- # Adds filtering patterns to the listener.
92
- #
93
- # @example Filter some files
94
- # ignore /\.txt$/, /.*\.zip/
95
- #
96
- # @param [Regexp] regexp a pattern for filtering paths
97
- #
98
- def filter(*regexps)
99
- @filtering_patterns.merge(regexps)
100
- end
101
-
102
- # Returns whether a path should be ignored or not.
103
- #
104
- # @param [String] path the path to test.
105
- #
106
- # @return [Boolean]
107
- #
108
- def ignored?(path)
109
- path = relative_to_base(path)
110
- @ignoring_patterns.any? { |pattern| pattern =~ path }
111
- end
112
-
113
- # Returns whether a path should be filtered or not.
114
- #
115
- # @param [String] path the path to test.
116
- #
117
- # @return [Boolean]
118
- #
119
- def filtered?(path)
120
- # When no filtering patterns are set, ALL files are stored.
121
- return true if @filtering_patterns.empty?
122
-
123
- path = relative_to_base(path)
124
- @filtering_patterns.any? { |pattern| pattern =~ path }
125
- end
126
-
127
- # Finds the paths that should be stored and adds them
128
- # to the paths' hash.
129
- #
130
- def build
131
- @paths = Hash.new { |h, k| h[k] = Hash.new }
132
- important_paths { |path| insert_path(path) }
133
- end
134
-
135
- # Detects changes in the passed directories, updates
136
- # the record with the new changes and returns the changes
137
- #
138
- # @param [Array] directories the list of directories scan for changes
139
- # @param [Hash] options
140
- # @option options [Boolean] recursive scan all sub-directories recursively
141
- # @option options [Boolean] relative_paths whether or not to use relative paths for changes
142
- #
143
- # @return [Hash<Array>] the changes
144
- #
145
- def fetch_changes(directories, options = {})
146
- @changes = { :modified => [], :added => [], :removed => [] }
147
- directories = directories.sort_by { |el| el.length }.reverse # diff sub-dir first
148
-
149
- directories.each do |directory|
150
- next unless directory[@directory] # Path is or inside directory
151
- detect_modifications_and_removals(directory, options)
152
- detect_additions(directory, options)
153
- end
154
-
155
- @changes
156
- end
157
-
158
- # Converts an absolute path to a path that's relative to the base directory.
159
- #
160
- # @param [String] path the path to convert
161
- #
162
- # @return [String] the relative path
163
- #
164
- def relative_to_base(path)
165
- return nil unless path[@directory]
166
- path.sub(%r{^#{Regexp.quote(@directory)}#{File::SEPARATOR}?}, '')
167
- end
168
-
169
- private
170
-
171
- # Detects modifications and removals recursively in a directory.
172
- #
173
- # @note Modifications detection begins by checking the modification time (mtime)
174
- # of files and then by checking content changes (using SHA1-checksum)
175
- # when the mtime of files is not changed.
176
- #
177
- # @param [String] directory the path to analyze
178
- # @param [Hash] options
179
- # @option options [Boolean] recursive scan all sub-directories recursively
180
- # @option options [Boolean] relative_paths whether or not to use relative paths for changes
181
- #
182
- def detect_modifications_and_removals(directory, options = {})
183
- @paths[directory].each do |basename, meta_data|
184
- path = File.join(directory, basename)
185
-
186
- case meta_data.type
187
- when 'Dir'
188
- if File.directory?(path)
189
- detect_modifications_and_removals(path, options) if options[:recursive]
190
- else
191
- detect_modifications_and_removals(path, { :recursive => true }.merge(options))
192
- @paths[directory].delete(basename)
193
- @paths.delete("#{directory}/#{basename}")
194
- end
195
- when 'File'
196
- if File.exist?(path)
197
- new_mtime = mtime_of(path)
198
-
199
- # First check if we are in the same second (to update checksums)
200
- # before checking the time difference
201
- if (meta_data.mtime.to_i == new_mtime.to_i && content_modified?(path)) || meta_data.mtime < new_mtime
202
- # Update the meta data of the files
203
- meta_data.mtime = new_mtime
204
- @paths[directory][basename] = meta_data
205
-
206
- @changes[:modified] << (options[:relative_paths] ? relative_to_base(path) : path)
207
- end
208
- else
209
- @paths[directory].delete(basename)
210
- @sha1_checksums.delete(path)
211
- @changes[:removed] << (options[:relative_paths] ? relative_to_base(path) : path)
212
- end
213
- end
214
- end
215
- end
216
-
217
- # Detects additions in a directory.
218
- #
219
- # @param [String] directory the path to analyze
220
- # @param [Hash] options
221
- # @option options [Boolean] recursive scan all sub-directories recursively
222
- # @option options [Boolean] relative_paths whether or not to use relative paths for changes
223
- #
224
- def detect_additions(directory, options = {})
225
- # Don't process removed directories
226
- return unless File.exist?(directory)
227
-
228
- Find.find(directory) do |path|
229
- next if path == @directory
230
-
231
- if File.directory?(path)
232
- # Add a trailing slash to directories when checking if a directory is
233
- # ignored to optimize finding them as Find.find doesn't.
234
- if ignored?(path + File::SEPARATOR) || (directory != path && (!options[:recursive] && existing_path?(path)))
235
- Find.prune # Don't look any further into this directory.
236
- else
237
- insert_path(path)
238
- end
239
- elsif !ignored?(path) && filtered?(path) && !existing_path?(path)
240
- if File.file?(path)
241
- @changes[:added] << (options[:relative_paths] ? relative_to_base(path) : path)
242
- insert_path(path)
243
- end
244
- end
245
- end
246
- end
247
-
248
- # Returns whether or not a file's content has been modified by
249
- # comparing the SHA1-checksum to a stored one.
250
- #
251
- # @param [String] path the file path
252
- #
253
- def content_modified?(path)
254
- sha1_checksum = Digest::SHA1.file(path).to_s
255
- return false if @sha1_checksums[path] == sha1_checksum
256
- @sha1_checksums.key?(path)
257
- rescue Errno::EACCES, Errno::ENOENT
258
- false
259
- ensure
260
- @sha1_checksums[path] = sha1_checksum if sha1_checksum
261
- end
262
-
263
- # Traverses the base directory looking for paths that should
264
- # be stored; thus paths that are filters or not ignored.
265
- #
266
- # @yield [path] an important path
267
- #
268
- def important_paths
269
- Find.find(@directory) do |path|
270
- next if path == @directory
271
-
272
- if File.directory?(path)
273
- # Add a trailing slash to directories when checking if a directory is
274
- # ignored to optimize finding them as Find.find doesn't.
275
- if ignored?(path + File::SEPARATOR)
276
- Find.prune # Don't look any further into this directory.
277
- else
278
- yield(path)
279
- end
280
- elsif !ignored?(path) && filtered?(path)
281
- yield(path)
282
- end
283
- end
284
- end
285
-
286
- # Inserts a path with its type (Dir or File) in paths hash.
287
- #
288
- # @param [String] path the path to insert in @paths.
289
- #
290
- def insert_path(path)
291
- meta_data = MetaData.new
292
- meta_data.type = File.directory?(path) ? 'Dir' : 'File'
293
- meta_data.mtime = mtime_of(path) unless meta_data.type == 'Dir' # mtimes of dirs are not used yet
294
- @paths[File.dirname(path)][File.basename(path)] = meta_data
295
- rescue Errno::ENOENT
296
- end
297
-
298
- # Returns whether or not a path exists in the paths hash.
299
- #
300
- # @param [String] path the path to check
301
- #
302
- # @return [Boolean]
303
- #
304
- def existing_path?(path)
305
- @paths[File.dirname(path)][File.basename(path)] != nil
306
- end
307
-
308
- # Returns the modification time of a file based on the precision defined by the system
309
- #
310
- # @param [String] file the file for which the mtime must be returned
311
- #
312
- # @return [Fixnum, Float] the mtime of the file
313
- #
314
- def mtime_of(file)
315
- File.lstat(file).mtime.send(HIGH_PRECISION_SUPPORTED ? :to_f : :to_i)
316
- end
317
- end
318
- end
1
+ require 'set'
2
+ require 'find'
3
+ require 'digest/sha1'
4
+
5
+ module Listen
6
+
7
+ # The directory record stores information about
8
+ # a directory and keeps track of changes to
9
+ # the structure of its childs.
10
+ #
11
+ class DirectoryRecord
12
+ attr_reader :directory, :paths, :sha1_checksums
13
+
14
+ DEFAULT_IGNORED_DIRECTORIES = %w[.rbx .bundle .git .svn log tmp vendor]
15
+
16
+ DEFAULT_IGNORED_EXTENSIONS = %w[.DS_Store]
17
+
18
+ # Defines the used precision based on the type of mtime returned by the
19
+ # system (whether its in milliseconds or just seconds)
20
+ #
21
+ HIGH_PRECISION_SUPPORTED = File.mtime(__FILE__).to_f.to_s[-2..-1] != '.0'
22
+
23
+ # Data structure used to save meta data about a path
24
+ #
25
+ MetaData = Struct.new(:type, :mtime)
26
+
27
+ # Class methods
28
+ #
29
+ class << self
30
+
31
+ # Creates the ignoring patterns from the default ignored
32
+ # directories and extensions. It memoizes the generated patterns
33
+ # to avoid unnecessary computation.
34
+ #
35
+ def generate_default_ignoring_patterns
36
+ @@default_ignoring_patterns ||= Array.new.tap do |default_patterns|
37
+ # Add directories
38
+ ignored_directories = DEFAULT_IGNORED_DIRECTORIES.map { |d| Regexp.escape(d) }
39
+ default_patterns << %r{^(?:#{ignored_directories.join('|')})/}
40
+
41
+ # Add extensions
42
+ ignored_extensions = DEFAULT_IGNORED_EXTENSIONS.map { |e| Regexp.escape(e) }
43
+ default_patterns << %r{(?:#{ignored_extensions.join('|')})$}
44
+ end
45
+ end
46
+ end
47
+
48
+ # Initializes a directory record.
49
+ #
50
+ # @option [String] directory the directory to keep track of
51
+ #
52
+ def initialize(directory)
53
+ raise ArgumentError, "The path '#{directory}' is not a directory!" unless File.directory?(directory)
54
+
55
+ @directory = directory
56
+ @ignoring_patterns = Set.new
57
+ @filtering_patterns = Set.new
58
+ @sha1_checksums = Hash.new
59
+
60
+ @ignoring_patterns.merge(DirectoryRecord.generate_default_ignoring_patterns)
61
+ end
62
+
63
+ # Returns the ignoring patterns in the record
64
+ #
65
+ # @return [Array<Regexp>] the ignoring patterns
66
+ #
67
+ def ignoring_patterns
68
+ @ignoring_patterns.to_a
69
+ end
70
+
71
+ # Returns the filtering patterns used in the record to know
72
+ # which paths should be stored.
73
+ #
74
+ # @return [Array<Regexp>] the filtering patterns
75
+ #
76
+ def filtering_patterns
77
+ @filtering_patterns.to_a
78
+ end
79
+
80
+ # Adds ignoring patterns to the record.
81
+ #
82
+ # @example Ignore some paths
83
+ # ignore %r{^ignored/path/}, /man/
84
+ #
85
+ # @param [Regexp] regexp a pattern for ignoring paths
86
+ #
87
+ def ignore(*regexps)
88
+ @ignoring_patterns.merge(regexps)
89
+ end
90
+
91
+ # Adds filtering patterns to the listener.
92
+ #
93
+ # @example Filter some files
94
+ # ignore /\.txt$/, /.*\.zip/
95
+ #
96
+ # @param [Regexp] regexp a pattern for filtering paths
97
+ #
98
+ def filter(*regexps)
99
+ @filtering_patterns.merge(regexps)
100
+ end
101
+
102
+ # Returns whether a path should be ignored or not.
103
+ #
104
+ # @param [String] path the path to test.
105
+ #
106
+ # @return [Boolean]
107
+ #
108
+ def ignored?(path)
109
+ path = relative_to_base(path)
110
+ @ignoring_patterns.any? { |pattern| pattern =~ path }
111
+ end
112
+
113
+ # Returns whether a path should be filtered or not.
114
+ #
115
+ # @param [String] path the path to test.
116
+ #
117
+ # @return [Boolean]
118
+ #
119
+ def filtered?(path)
120
+ # When no filtering patterns are set, ALL files are stored.
121
+ return true if @filtering_patterns.empty?
122
+
123
+ path = relative_to_base(path)
124
+ @filtering_patterns.any? { |pattern| pattern =~ path }
125
+ end
126
+
127
+ # Finds the paths that should be stored and adds them
128
+ # to the paths' hash.
129
+ #
130
+ def build
131
+ @paths = Hash.new { |h, k| h[k] = Hash.new }
132
+ important_paths { |path| insert_path(path) }
133
+ end
134
+
135
+ # Detects changes in the passed directories, updates
136
+ # the record with the new changes and returns the changes
137
+ #
138
+ # @param [Array] directories the list of directories scan for changes
139
+ # @param [Hash] options
140
+ # @option options [Boolean] recursive scan all sub-directories recursively
141
+ # @option options [Boolean] relative_paths whether or not to use relative paths for changes
142
+ #
143
+ # @return [Hash<Array>] the changes
144
+ #
145
+ def fetch_changes(directories, options = {})
146
+ @changes = { :modified => [], :added => [], :removed => [] }
147
+ directories = directories.sort_by { |el| el.length }.reverse # diff sub-dir first
148
+
149
+ directories.each do |directory|
150
+ next unless directory[@directory] # Path is or inside directory
151
+ detect_modifications_and_removals(directory, options)
152
+ detect_additions(directory, options)
153
+ end
154
+
155
+ @changes
156
+ end
157
+
158
+ # Converts an absolute path to a path that's relative to the base directory.
159
+ #
160
+ # @param [String] path the path to convert
161
+ #
162
+ # @return [String] the relative path
163
+ #
164
+ def relative_to_base(path)
165
+ return nil unless path[@directory]
166
+ path.sub(%r{^#{Regexp.quote(@directory)}#{File::SEPARATOR}?}, '')
167
+ end
168
+
169
+ private
170
+
171
+ # Detects modifications and removals recursively in a directory.
172
+ #
173
+ # @note Modifications detection begins by checking the modification time (mtime)
174
+ # of files and then by checking content changes (using SHA1-checksum)
175
+ # when the mtime of files is not changed.
176
+ #
177
+ # @param [String] directory the path to analyze
178
+ # @param [Hash] options
179
+ # @option options [Boolean] recursive scan all sub-directories recursively
180
+ # @option options [Boolean] relative_paths whether or not to use relative paths for changes
181
+ #
182
+ def detect_modifications_and_removals(directory, options = {})
183
+ @paths[directory].each do |basename, meta_data|
184
+ path = File.join(directory, basename)
185
+
186
+ case meta_data.type
187
+ when 'Dir'
188
+ if File.directory?(path)
189
+ detect_modifications_and_removals(path, options) if options[:recursive]
190
+ else
191
+ detect_modifications_and_removals(path, { :recursive => true }.merge(options))
192
+ @paths[directory].delete(basename)
193
+ @paths.delete("#{directory}/#{basename}")
194
+ end
195
+ when 'File'
196
+ if File.exist?(path)
197
+ new_mtime = mtime_of(path)
198
+
199
+ # First check if we are in the same second (to update checksums)
200
+ # before checking the time difference
201
+ if (meta_data.mtime.to_i == new_mtime.to_i && content_modified?(path)) || meta_data.mtime < new_mtime
202
+ # Update the sha1 checksum of the file
203
+ insert_sha1_checksum(path)
204
+
205
+ # Update the meta data of the file
206
+ meta_data.mtime = new_mtime
207
+ @paths[directory][basename] = meta_data
208
+
209
+ @changes[:modified] << (options[:relative_paths] ? relative_to_base(path) : path)
210
+ end
211
+ else
212
+ @paths[directory].delete(basename)
213
+ @sha1_checksums.delete(path)
214
+ @changes[:removed] << (options[:relative_paths] ? relative_to_base(path) : path)
215
+ end
216
+ end
217
+ end
218
+ end
219
+
220
+ # Detects additions in a directory.
221
+ #
222
+ # @param [String] directory the path to analyze
223
+ # @param [Hash] options
224
+ # @option options [Boolean] recursive scan all sub-directories recursively
225
+ # @option options [Boolean] relative_paths whether or not to use relative paths for changes
226
+ #
227
+ def detect_additions(directory, options = {})
228
+ # Don't process removed directories
229
+ return unless File.exist?(directory)
230
+
231
+ Find.find(directory) do |path|
232
+ next if path == @directory
233
+
234
+ if File.directory?(path)
235
+ # Add a trailing slash to directories when checking if a directory is
236
+ # ignored to optimize finding them as Find.find doesn't.
237
+ if ignored?(path + File::SEPARATOR) || (directory != path && (!options[:recursive] && existing_path?(path)))
238
+ Find.prune # Don't look any further into this directory.
239
+ else
240
+ insert_path(path)
241
+ end
242
+ elsif !ignored?(path) && filtered?(path) && !existing_path?(path)
243
+ if File.file?(path)
244
+ @changes[:added] << (options[:relative_paths] ? relative_to_base(path) : path)
245
+ insert_path(path)
246
+ end
247
+ end
248
+ end
249
+ end
250
+
251
+ # Returns whether or not a file's content has been modified by
252
+ # comparing the SHA1-checksum to a stored one.
253
+ # Ensure that the SHA1-checksum is inserted to the sha1_checksums
254
+ # array for later comparaison if false.
255
+ #
256
+ # @param [String] path the file path
257
+ #
258
+ def content_modified?(path)
259
+ @sha1_checksum = sha1_checksum(path)
260
+ if @sha1_checksums[path] == @sha1_checksum || !@sha1_checksums.key?(path)
261
+ insert_sha1_checksum(path)
262
+ false
263
+ else
264
+ true
265
+ end
266
+ end
267
+
268
+ # Inserts a SHA1-checksum path in @SHA1-checksums hash.
269
+ #
270
+ # @param [String] path the SHA1-checksum path to insert in @sha1_checksums.
271
+ #
272
+ def insert_sha1_checksum(path)
273
+ if @sha1_checksum ||= sha1_checksum(path)
274
+ @sha1_checksums[path] = @sha1_checksum
275
+ @sha1_checksum = nil
276
+ end
277
+ end
278
+
279
+ # Returns the SHA1-checksum for the file path.
280
+ #
281
+ # @param [String] path the file path
282
+ #
283
+ def sha1_checksum(path)
284
+ Digest::SHA1.file(path).to_s
285
+ rescue Errno::EACCES, Errno::ENOENT
286
+ nil
287
+ end
288
+
289
+ # Traverses the base directory looking for paths that should
290
+ # be stored; thus paths that are filters or not ignored.
291
+ #
292
+ # @yield [path] an important path
293
+ #
294
+ def important_paths
295
+ Find.find(@directory) do |path|
296
+ next if path == @directory
297
+
298
+ if File.directory?(path)
299
+ # Add a trailing slash to directories when checking if a directory is
300
+ # ignored to optimize finding them as Find.find doesn't.
301
+ if ignored?(path + File::SEPARATOR)
302
+ Find.prune # Don't look any further into this directory.
303
+ else
304
+ yield(path)
305
+ end
306
+ elsif !ignored?(path) && filtered?(path)
307
+ yield(path)
308
+ end
309
+ end
310
+ end
311
+
312
+ # Inserts a path with its type (Dir or File) in paths hash.
313
+ #
314
+ # @param [String] path the path to insert in @paths.
315
+ #
316
+ def insert_path(path)
317
+ meta_data = MetaData.new
318
+ meta_data.type = File.directory?(path) ? 'Dir' : 'File'
319
+ meta_data.mtime = mtime_of(path) unless meta_data.type == 'Dir' # mtimes of dirs are not used yet
320
+ @paths[File.dirname(path)][File.basename(path)] = meta_data
321
+ rescue Errno::ENOENT
322
+ end
323
+
324
+ # Returns whether or not a path exists in the paths hash.
325
+ #
326
+ # @param [String] path the path to check
327
+ #
328
+ # @return [Boolean]
329
+ #
330
+ def existing_path?(path)
331
+ @paths[File.dirname(path)][File.basename(path)] != nil
332
+ end
333
+
334
+ # Returns the modification time of a file based on the precision defined by the system
335
+ #
336
+ # @param [String] file the file for which the mtime must be returned
337
+ #
338
+ # @return [Fixnum, Float] the mtime of the file
339
+ #
340
+ def mtime_of(file)
341
+ File.lstat(file).mtime.send(HIGH_PRECISION_SUPPORTED ? :to_f : :to_i)
342
+ end
343
+ end
344
+ end