joshbuddy-guard 0.10.0

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