joshbuddy-guard 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/CHANGELOG.md +370 -0
  2. data/LICENSE +20 -0
  3. data/README.md +470 -0
  4. data/bin/fsevent_watch_guard +0 -0
  5. data/bin/guard +6 -0
  6. data/images/failed.png +0 -0
  7. data/images/pending.png +0 -0
  8. data/images/success.png +0 -0
  9. data/lib/guard.rb +463 -0
  10. data/lib/guard/cli.rb +125 -0
  11. data/lib/guard/dsl.rb +370 -0
  12. data/lib/guard/dsl_describer.rb +150 -0
  13. data/lib/guard/group.rb +37 -0
  14. data/lib/guard/guard.rb +129 -0
  15. data/lib/guard/hook.rb +118 -0
  16. data/lib/guard/interactor.rb +116 -0
  17. data/lib/guard/listener.rb +351 -0
  18. data/lib/guard/listeners/darwin.rb +60 -0
  19. data/lib/guard/listeners/linux.rb +91 -0
  20. data/lib/guard/listeners/polling.rb +55 -0
  21. data/lib/guard/listeners/windows.rb +61 -0
  22. data/lib/guard/notifier.rb +290 -0
  23. data/lib/guard/templates/Guardfile +2 -0
  24. data/lib/guard/ui.rb +193 -0
  25. data/lib/guard/version.rb +6 -0
  26. data/lib/guard/watcher.rb +114 -0
  27. data/lib/vendor/darwin/Gemfile +6 -0
  28. data/lib/vendor/darwin/Guardfile +8 -0
  29. data/lib/vendor/darwin/LICENSE +20 -0
  30. data/lib/vendor/darwin/README.rdoc +254 -0
  31. data/lib/vendor/darwin/Rakefile +21 -0
  32. data/lib/vendor/darwin/ext/extconf.rb +61 -0
  33. data/lib/vendor/darwin/ext/fsevent/fsevent_watch.c +226 -0
  34. data/lib/vendor/darwin/lib/rb-fsevent.rb +2 -0
  35. data/lib/vendor/darwin/lib/rb-fsevent/fsevent.rb +105 -0
  36. data/lib/vendor/darwin/lib/rb-fsevent/version.rb +3 -0
  37. data/lib/vendor/darwin/rb-fsevent.gemspec +24 -0
  38. data/lib/vendor/darwin/spec/fixtures/folder1/file1.txt +0 -0
  39. data/lib/vendor/darwin/spec/fixtures/folder1/folder2/file2.txt +0 -0
  40. data/lib/vendor/darwin/spec/rb-fsevent/fsevent_spec.rb +75 -0
  41. data/lib/vendor/darwin/spec/spec_helper.rb +24 -0
  42. data/lib/vendor/linux/MIT-LICENSE +20 -0
  43. data/lib/vendor/linux/README.md +66 -0
  44. data/lib/vendor/linux/Rakefile +54 -0
  45. data/lib/vendor/linux/VERSION +1 -0
  46. data/lib/vendor/linux/lib/rb-inotify.rb +17 -0
  47. data/lib/vendor/linux/lib/rb-inotify/event.rb +139 -0
  48. data/lib/vendor/linux/lib/rb-inotify/native.rb +31 -0
  49. data/lib/vendor/linux/lib/rb-inotify/native/flags.rb +89 -0
  50. data/lib/vendor/linux/lib/rb-inotify/notifier.rb +308 -0
  51. data/lib/vendor/linux/lib/rb-inotify/watcher.rb +83 -0
  52. data/lib/vendor/linux/rb-inotify.gemspec +53 -0
  53. data/lib/vendor/windows/Gemfile +4 -0
  54. data/lib/vendor/windows/README.md +34 -0
  55. data/lib/vendor/windows/Rakefile +18 -0
  56. data/lib/vendor/windows/lib/rb-fchange.rb +14 -0
  57. data/lib/vendor/windows/lib/rb-fchange/event.rb +29 -0
  58. data/lib/vendor/windows/lib/rb-fchange/native.rb +45 -0
  59. data/lib/vendor/windows/lib/rb-fchange/native/flags.rb +78 -0
  60. data/lib/vendor/windows/lib/rb-fchange/notifier.rb +149 -0
  61. data/lib/vendor/windows/lib/rb-fchange/version.rb +3 -0
  62. data/lib/vendor/windows/lib/rb-fchange/watcher.rb +99 -0
  63. data/lib/vendor/windows/rb-fchange.gemspec +34 -0
  64. data/lib/vendor/windows/spec/fixtures/folder1/file1.txt +0 -0
  65. data/lib/vendor/windows/spec/fixtures/folder1/folder2/file2.txt +0 -0
  66. data/lib/vendor/windows/spec/rb-fchange/fchange_spec.rb +119 -0
  67. data/lib/vendor/windows/spec/spec_helper.rb +21 -0
  68. data/man/guard.1 +96 -0
  69. data/man/guard.1.html +181 -0
  70. metadata +193 -0
@@ -0,0 +1,351 @@
1
+ require 'rbconfig'
2
+ require 'digest/sha1'
3
+
4
+ module Guard
5
+
6
+ autoload :Darwin, 'guard/listeners/darwin'
7
+ autoload :Linux, 'guard/listeners/linux'
8
+ autoload :Windows, 'guard/listeners/windows'
9
+ autoload :Polling, 'guard/listeners/polling'
10
+
11
+ # The Listener is the base class for all listener
12
+ # implementations.
13
+ #
14
+ # @abstract
15
+ #
16
+ class Listener
17
+
18
+ # Default paths that gets ignored by the listener
19
+ DEFAULT_IGNORE_PATHS = %w[. .. .bundle .git log tmp vendor]
20
+
21
+ attr_accessor :changed_files
22
+ attr_reader :directory, :ignore_paths
23
+
24
+ def paused?
25
+ @paused
26
+ end
27
+
28
+ # Select the appropriate listener implementation for the
29
+ # current OS and initializes it.
30
+ #
31
+ # @param [Array] args the arguments for the listener
32
+ # @return [Guard::Listener] the chosen listener
33
+ #
34
+ def self.select_and_init(watchdir = Dir.pwd, options = nil)
35
+ no_vendor = options && options.key?(:no_vendor) ? options[:no_vendor] : false
36
+ if mac? && Darwin.usable?(no_vendor)
37
+ Darwin.new(watchdir, options)
38
+ elsif linux? && Linux.usable?(no_vendor)
39
+ Linux.new(watchdir, options)
40
+ elsif windows? && Windows.usable?(no_vendor)
41
+ Windows.new(watchdir, options)
42
+ else
43
+ UI.info 'Using polling (Please help us to support your system better than that).'
44
+ Polling.new(watchdir, options)
45
+ end
46
+ end
47
+
48
+ # Initialize the listener.
49
+ #
50
+ # @param [String] directory the root directory to listen to
51
+ # @option options [Boolean] relativize_paths use only relative paths
52
+ # @option options [Array<String>] ignore_paths the paths to ignore by the listener
53
+ #
54
+ def initialize(directory = Dir.pwd, options = {})
55
+ @directory = directory.to_s
56
+ @sha1_checksums_hash = {}
57
+ @file_timestamp_hash = {}
58
+ @relativize_paths = options.fetch(:relativize_paths, true)
59
+ @changed_files = []
60
+ @paused = false
61
+ @ignore_paths = DEFAULT_IGNORE_PATHS
62
+ @ignore_paths |= options[:ignore_paths] if options[:ignore_paths]
63
+ @watch_all_modifications = options.fetch(:watch_all_modifications, false)
64
+
65
+ update_last_event
66
+ start_reactor
67
+ end
68
+
69
+ # Start the listener thread.
70
+ #
71
+ def start_reactor
72
+ return if ENV["GUARD_ENV"] == 'test'
73
+
74
+ Thread.new do
75
+ loop do
76
+ if @changed_files != [] && !@paused
77
+ changed_files = @changed_files.dup
78
+ clear_changed_files
79
+ ::Guard.run_on_change(changed_files)
80
+ else
81
+ sleep 0.1
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ # Start watching the root directory.
88
+ #
89
+ def start
90
+ watch(@directory)
91
+ timestamp_files
92
+ end
93
+
94
+ # Stop listening for events.
95
+ #
96
+ def stop
97
+ end
98
+
99
+ # Pause the listener to ignore change events.
100
+ #
101
+ def pause
102
+ @paused = true
103
+ end
104
+
105
+ # Unpause the listener to listen again to change events.
106
+ #
107
+ def run
108
+ @paused = false
109
+ end
110
+
111
+ # Clear the list of changed files.
112
+ #
113
+ def clear_changed_files
114
+ @changed_files.clear
115
+ end
116
+
117
+ # Store a listener callback.
118
+ #
119
+ # @param [Block] callback the callback to store
120
+ #
121
+ def on_change(&callback)
122
+ @callback = callback
123
+ end
124
+
125
+ # Updates the timestamp of the last event.
126
+ #
127
+ def update_last_event
128
+ @last_event = Time.now
129
+ end
130
+
131
+ # Get the modified files.
132
+ #
133
+ # If the `:watch_all_modifications` option is true, then moved and
134
+ # deleted files are also reported, but prefixed by an exclamation point.
135
+ #
136
+ # @example Deleted or moved file
137
+ # !/home/user/dir/file.rb
138
+ #
139
+ # @param [Array<String>] dirs the watched directories
140
+ # @param [Hash] options the listener options
141
+ # @option options [Symbol] all whether to files in sub directories
142
+ # @return [Array<String>] paths of files that have been modified
143
+ #
144
+ def modified_files(dirs, options = {})
145
+ last_event = @last_event
146
+ files = []
147
+ if @watch_all_modifications
148
+ deleted_files = @file_timestamp_hash.collect do |path, ts|
149
+ unless File.exists?(path)
150
+ @sha1_checksums_hash.delete(path)
151
+ @file_timestamp_hash.delete(path)
152
+ "!#{path}"
153
+ end
154
+ end
155
+ files.concat(deleted_files.compact)
156
+ end
157
+ update_last_event
158
+ files.concat(potentially_modified_files(dirs, options).select { |path| file_modified?(path, last_event) })
159
+
160
+ relativize_paths(files)
161
+ end
162
+
163
+ # Register a directory to watch.
164
+ # Must be implemented by the subclasses.
165
+ #
166
+ # @param [String] directory the directory to watch
167
+ #
168
+ def watch(directory)
169
+ raise NotImplementedError, "do whatever you want here, given the directory as only argument"
170
+ end
171
+
172
+ # Get all files that are in the watched directory.
173
+ #
174
+ # @return [Array<String>] the list of files
175
+ #
176
+ def all_files
177
+ potentially_modified_files([@directory], :all => true)
178
+ end
179
+
180
+ # Scopes all given paths to the current directory.
181
+ #
182
+ # @param [Array<String>] paths the paths to change
183
+ # @return [Array<String>] all paths now relative to the current dir
184
+ #
185
+ def relativize_paths(paths)
186
+ return paths unless relativize_paths?
187
+ paths.map do |path|
188
+ path.gsub(%r{^(!)?#{ @directory }/},'\1')
189
+ end
190
+ end
191
+
192
+ # Use paths relative to the current directory.
193
+ #
194
+ # @return [Boolean] whether to use relative or absolute paths
195
+ #
196
+ def relativize_paths?
197
+ !!@relativize_paths
198
+ end
199
+
200
+ # Populate initial timestamp file hash to watch for deleted or moved files.
201
+ #
202
+ def timestamp_files
203
+ all_files.each {|path| set_file_timestamp_hash(path, file_timestamp(path)) } if @watch_all_modifications
204
+ end
205
+
206
+ # Removes the ignored paths from the directory list.
207
+ #
208
+ # @param [Array<String>] dirs the directory to listen to
209
+ # @param [Array<String>] ignore_paths the paths to ignore
210
+ # @return children of the passed dirs that are not in the ignore_paths list
211
+ #
212
+ def exclude_ignored_paths(dirs, ignore_paths = self.ignore_paths)
213
+ Dir.glob(dirs.map { |d| "#{d.sub(%r{/+$}, '')}/*" }, File::FNM_DOTMATCH).reject do |path|
214
+ ignore_paths.include?(File.basename(path))
215
+ end
216
+ end
217
+
218
+ private
219
+
220
+ # Gets a list of files that are in the modified directories.
221
+ #
222
+ # @param [Array<String>] dirs the list of directories
223
+ # @param [Hash] options the find file option
224
+ # @option options [Symbol] all whether to files in sub directories
225
+ #
226
+ def potentially_modified_files(dirs, options = {})
227
+ paths = exclude_ignored_paths(dirs)
228
+
229
+ if options[:all]
230
+ paths.inject([]) do |array, path|
231
+ if File.file?(path)
232
+ array << path
233
+ else
234
+ array += Dir.glob("#{ path }/**/*", File::FNM_DOTMATCH).select { |p| File.file?(p) }
235
+ end
236
+ array
237
+ end
238
+ else
239
+ paths.select { |path| File.file?(path) }
240
+ end
241
+ end
242
+
243
+ # Test if the file content has changed.
244
+ #
245
+ # Depending on the filesystem, mtime/ctime is probably only precise to the second, so round
246
+ # both values down to the second for the comparison.
247
+ #
248
+ # ctime is used only on == comparison to always catches Rails 3.1 Assets pipelined on Mac OSX
249
+ #
250
+ # @param [String] path the file path
251
+ # @param [Time] last_event the time of the last event
252
+ # @return [Boolean] Whether the file content has changed or not.
253
+ #
254
+ def file_modified?(path, last_event)
255
+ ctime = File.ctime(path).to_i
256
+ mtime = File.mtime(path).to_i
257
+ if [mtime, ctime].max == last_event.to_i
258
+ file_content_modified?(path, sha1_checksum(path))
259
+ elsif mtime > last_event.to_i
260
+ set_sha1_checksums_hash(path, sha1_checksum(path))
261
+ true
262
+ elsif @watch_all_modifications
263
+ ts = file_timestamp(path)
264
+ if ts != @file_timestamp_hash[path]
265
+ set_file_timestamp_hash(path, ts)
266
+ true
267
+ end
268
+ else
269
+ false
270
+ end
271
+ rescue
272
+ false
273
+ end
274
+
275
+ # Tests if the file content has been modified by
276
+ # comparing the SHA1 checksum.
277
+ #
278
+ # @param [String] path the file path
279
+ # @param [String] sha1_checksum the checksum of the file
280
+ #
281
+ def file_content_modified?(path, sha1_checksum)
282
+ if @sha1_checksums_hash[path] != sha1_checksum
283
+ set_sha1_checksums_hash(path, sha1_checksum)
284
+ true
285
+ else
286
+ false
287
+ end
288
+ end
289
+
290
+ # Set save a files current timestamp
291
+ #
292
+ # @param [String] path the file path
293
+ # @param [Int] file_timestamp the files modified timestamp
294
+ #
295
+ def set_file_timestamp_hash(path, file_timestamp)
296
+ @file_timestamp_hash[path] = file_timestamp
297
+ end
298
+
299
+ # Set the current checksum of a file.
300
+ #
301
+ # @param [String] path the file path
302
+ # @param [String] sha1_checksum the checksum of the file
303
+ #
304
+ def set_sha1_checksums_hash(path, sha1_checksum)
305
+ @sha1_checksums_hash[path] = sha1_checksum
306
+ end
307
+
308
+ # Gets a files modified timestamp
309
+ #
310
+ # @path [String] path the file path
311
+ # @return [Int] file modified timestamp
312
+ #
313
+ def file_timestamp(path)
314
+ File.mtime(path).to_i
315
+ end
316
+
317
+ # Calculates the SHA1 checksum of a file.
318
+ #
319
+ # @param [String] path the path to the file
320
+ # @return [String] the SHA1 checksum
321
+ #
322
+ def sha1_checksum(path)
323
+ Digest::SHA1.file(path).to_s
324
+ end
325
+
326
+ # Test if the OS is Mac OS X.
327
+ #
328
+ # @return [Boolean] Whether the OS is Mac OS X
329
+ #
330
+ def self.mac?
331
+ RbConfig::CONFIG['target_os'] =~ /darwin/i
332
+ end
333
+
334
+ # Test if the OS is Linux.
335
+ #
336
+ # @return [Boolean] Whether the OS is Linux
337
+ #
338
+ def self.linux?
339
+ RbConfig::CONFIG['target_os'] =~ /linux/i
340
+ end
341
+
342
+ # Test if the OS is Windows.
343
+ #
344
+ # @return [Boolean] Whether the OS is Windows
345
+ #
346
+ def self.windows?
347
+ RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
348
+ end
349
+
350
+ end
351
+ end
@@ -0,0 +1,60 @@
1
+ module Guard
2
+
3
+ # Listener implementation for Mac OS X `FSEvents`.
4
+ #
5
+ class Darwin < Listener
6
+
7
+ # Initialize the Listener.
8
+ #
9
+ def initialize(*)
10
+ super
11
+ @fsevent = FSEvent.new
12
+ end
13
+
14
+ # Start the listener.
15
+ #
16
+ def start
17
+ super
18
+ worker.run
19
+ end
20
+
21
+ # Stop the listener.
22
+ #
23
+ def stop
24
+ super
25
+ worker.stop
26
+ end
27
+
28
+ # Check if the listener is usable on the current OS.
29
+ #
30
+ # @return [Boolean] whether usable or not
31
+ #
32
+ def self.usable?(no_vendor = false)
33
+ $LOAD_PATH << File.expand_path('../../../vendor/darwin/lib', __FILE__) unless no_vendor
34
+ require 'rb-fsevent'
35
+ true
36
+ rescue LoadError
37
+ false
38
+ end
39
+
40
+ private
41
+
42
+ # Get the listener worker.
43
+ #
44
+ def worker
45
+ @fsevent
46
+ end
47
+
48
+ # Watch the given directory for file changes.
49
+ #
50
+ # @param [String] directory the directory to watch
51
+ #
52
+ def watch(directory)
53
+ worker.watch(directory) do |modified_dirs|
54
+ files = modified_files(modified_dirs)
55
+ @callback.call(files) unless files.empty?
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,91 @@
1
+ module Guard
2
+
3
+ # Listener implementation for Linux `inotify`.
4
+ #
5
+ class Linux < Listener
6
+
7
+ # Initialize the Listener.
8
+ #
9
+ def initialize(*)
10
+ super
11
+ @inotify = INotify::Notifier.new
12
+ @files = []
13
+ @latency = 0.5
14
+ end
15
+
16
+ # Start the listener.
17
+ #
18
+ def start
19
+ @stop = false
20
+ super
21
+ watch_change unless watch_change?
22
+ end
23
+
24
+ # Stop the listener.
25
+ #
26
+ def stop
27
+ super
28
+ @stop = true
29
+ end
30
+
31
+ # Check if the listener is usable on the current OS.
32
+ #
33
+ # @return [Boolean] whether usable or not
34
+ #
35
+ def self.usable?(no_vendor = false)
36
+ $LOAD_PATH << File.expand_path('../../../vendor/linux/lib', __FILE__) unless no_vendor
37
+ require 'rb-inotify'
38
+ true
39
+ rescue LoadError
40
+ false
41
+ end
42
+
43
+ private
44
+
45
+ # Get the listener worker.
46
+ #
47
+ def worker
48
+ @inotify
49
+ end
50
+
51
+ # Watch the given directory for file changes.
52
+ #
53
+ # @param [String] directory the directory to watch
54
+ #
55
+ def watch(directory)
56
+ worker.watch(directory, :recursive, :attrib, :create, :move_self, :close_write) do |event|
57
+ unless event.name == "" # Event on root directory
58
+ @files << event.absolute_name
59
+ end
60
+ end
61
+ rescue Interrupt
62
+ end
63
+
64
+ # Test if inotify is watching for changes.
65
+ #
66
+ # @return [Boolean] whether inotify is active or not
67
+ #
68
+ def watch_change?
69
+ !!@watch_change
70
+ end
71
+
72
+ # Watch for file system changes.
73
+ #
74
+ def watch_change
75
+ @watch_change = true
76
+ until @stop
77
+ if RbConfig::CONFIG['build'] =~ /java/ || IO.select([worker.to_io], [], [], @latency)
78
+ break if @stop
79
+
80
+ sleep(@latency)
81
+ worker.process
82
+
83
+ files = modified_files(@files.shift(@files.size).map { |f| File.dirname(f) }.uniq)
84
+ @callback.call(files) unless files.empty?
85
+ end
86
+ end
87
+ @watch_change = false
88
+ end
89
+
90
+ end
91
+ end