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
Binary file
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'guard'
4
+ require 'guard/cli'
5
+
6
+ Guard::CLI.start
Binary file
Binary file
Binary file
@@ -0,0 +1,463 @@
1
+ require 'thread'
2
+
3
+ # Guard is the main module for all Guard related modules and classes.
4
+ # Also other Guard implementation should use this namespace.
5
+ #
6
+ module Guard
7
+
8
+ autoload :UI, 'guard/ui'
9
+ autoload :Dsl, 'guard/dsl'
10
+ autoload :DslDescriber, 'guard/dsl_describer'
11
+ autoload :Group, 'guard/group'
12
+ autoload :Interactor, 'guard/interactor'
13
+ autoload :Listener, 'guard/listener'
14
+ autoload :Watcher, 'guard/watcher'
15
+ autoload :Notifier, 'guard/notifier'
16
+ autoload :Hook, 'guard/hook'
17
+
18
+ # The Guardfile template for `guard init`
19
+ GUARDFILE_TEMPLATE = File.expand_path('../guard/templates/Guardfile', __FILE__)
20
+
21
+ class << self
22
+ attr_accessor :options, :interactor, :listener, :lock
23
+
24
+ # Creates the initial Guardfile template or add a Guard implementation
25
+ # Guardfile template to an existing Guardfile.
26
+ #
27
+ # @see Guard::Guard.init
28
+ #
29
+ # @param [String] guard_name the name of the Guard to initialize
30
+ #
31
+ def initialize_template(guard_name = nil)
32
+ if !File.exist?('Guardfile')
33
+ ::Guard::UI.info "Writing new Guardfile to #{ Dir.pwd }/Guardfile"
34
+ FileUtils.cp(GUARDFILE_TEMPLATE, 'Guardfile')
35
+ elsif guard_name.nil?
36
+ ::Guard::UI.error "Guardfile already exists at #{ Dir.pwd }/Guardfile"
37
+ exit 1
38
+ end
39
+
40
+ if guard_name
41
+ guard_class = ::Guard.get_guard_class(guard_name)
42
+ guard_class.init(guard_name)
43
+ end
44
+ end
45
+
46
+ # Initialize the Guard singleton.
47
+ #
48
+ # @option options [Boolean] clear if auto clear the UI should be done
49
+ # @option options [Boolean] notify if system notifications should be shown
50
+ # @option options [Boolean] debug if debug output should be shown
51
+ # @option options [Array<String>] group the list of groups to start
52
+ # @option options [String] watchdir the director to watch
53
+ # @option options [String] guardfile the path to the Guardfile
54
+ # @option options [Boolean] watch_all_modifications watches all file modifications if true
55
+ #
56
+ def setup(options = {})
57
+ @lock = Mutex.new
58
+
59
+ @options = options
60
+ @guards = []
61
+ self.reset_groups
62
+ @interactor = Interactor.new unless options[:no_interactions]
63
+ @listener = Listener.select_and_init(options[:watchdir] && File.expand_path(options[:watchdir]), options)
64
+
65
+ @options[:notify] && ENV['GUARD_NOTIFY'] != 'false' ? Notifier.turn_on : Notifier.turn_off
66
+
67
+ UI.clear if @options[:clear]
68
+
69
+ debug_command_execution if @options[:debug]
70
+
71
+ self
72
+ end
73
+
74
+ # Smart accessor for retrieving a specific guard or several guards at once.
75
+ #
76
+ # @param [String, Symbol] filter return the guard with the given name, or nil if not found
77
+ # @param [Regexp] filter returns all guards matching the Regexp, or [] if no guard found
78
+ # @param [Hash] filter returns all guards matching the given Hash.
79
+ # Example: `{ :name => 'rspec', :group => 'backend' }`, or [] if no guard found
80
+ # @param [NilClass] filter returns all guards
81
+ #
82
+ # @see Guard.groups
83
+ #
84
+ def guards(filter = nil)
85
+ case filter
86
+ when String, Symbol
87
+ @guards.find { |guard| guard.class.to_s.downcase.sub('guard::', '') == filter.to_s.downcase.gsub('-', '') }
88
+ when Regexp
89
+ @guards.find_all { |guard| guard.class.to_s.downcase.sub('guard::', '') =~ filter }
90
+ when Hash
91
+ filter.inject(@guards) do |matches, (k, v)|
92
+ if k.to_sym == :name
93
+ matches.find_all { |guard| guard.class.to_s.downcase.sub('guard::', '') == v.to_s.downcase.gsub('-', '') }
94
+ else
95
+ matches.find_all { |guard| guard.send(k).to_sym == v.to_sym }
96
+ end
97
+ end
98
+ else
99
+ @guards
100
+ end
101
+ end
102
+
103
+ # Smart accessor for retrieving a specific group or several groups at once.
104
+ #
105
+ # @param [NilClass] filter returns all groups
106
+ # @param [String, Symbol] filter return the group with the given name, or nil if not found
107
+ # @param [Regexp] filter returns all groups matching the Regexp, or [] if no group found
108
+ #
109
+ # @see Guard.guards
110
+ #
111
+ def groups(filter = nil)
112
+ case filter
113
+ when String, Symbol
114
+ @groups.find { |group| group.name == filter.to_sym }
115
+ when Regexp
116
+ @groups.find_all { |group| group.name.to_s =~ filter }
117
+ else
118
+ @groups
119
+ end
120
+ end
121
+
122
+ # Initialize the groups array with the `:default` group.
123
+ #
124
+ # @see Guard.groups
125
+ #
126
+ def reset_groups
127
+ @groups = [Group.new(:default)]
128
+ end
129
+
130
+ # Start Guard by evaluate the `Guardfile`, initialize the declared Guards
131
+ # and start the available file change listener.
132
+ #
133
+ # @option options [Boolean] clear if auto clear the UI should be done
134
+ # @option options [Boolean] notify if system notifications should be shown
135
+ # @option options [Boolean] debug if debug output should be shown
136
+ # @option options [Array<String>] group the list of groups to start
137
+ # @option options [String] watchdir the director to watch
138
+ # @option options [String] guardfile the path to the Guardfile
139
+ #
140
+ def start(options = {})
141
+ setup(options)
142
+
143
+ Dsl.evaluate_guardfile(options)
144
+
145
+ listener.on_change do |files|
146
+ Dsl.reevaluate_guardfile if Watcher.match_guardfile?(files)
147
+ listener.changed_files += files if Watcher.match_files?(guards, files)
148
+ end
149
+
150
+ UI.info "Guard is now watching at '#{ listener.directory }'"
151
+
152
+ run_on_guards do |guard|
153
+ run_supervised_task(guard, :start)
154
+ end
155
+
156
+ interactor.start if interactor
157
+ listener.start
158
+ end
159
+
160
+ # Stop Guard listening to file changes
161
+ #
162
+ def stop
163
+ UI.info 'Bye bye...', :reset => true
164
+
165
+ run_on_guards do |guard|
166
+ run_supervised_task(guard, :stop)
167
+ end
168
+
169
+ listener.stop
170
+ abort
171
+ end
172
+
173
+ # Reload all Guards currently enabled.
174
+ #
175
+ # @param [Hash] An hash with a guard or a group scope
176
+ #
177
+ def reload(scopes)
178
+ run do
179
+ run_on_guards(scopes) do |guard|
180
+ run_supervised_task(guard, :reload)
181
+ end
182
+ end
183
+ end
184
+
185
+ # Trigger `run_all` on all Guards currently enabled.
186
+ #
187
+ # @param [Hash] An hash with a guard or a group scope
188
+ #
189
+ def run_all(scopes)
190
+ run do
191
+ run_on_guards(scopes) do |guard|
192
+ run_supervised_task(guard, :run_all)
193
+ end
194
+ end
195
+ end
196
+
197
+ # Pause Guard listening to file changes.
198
+ #
199
+ def pause
200
+ if listener.paused?
201
+ UI.info 'Un-paused files modification listening', :reset => true
202
+ listener.clear_changed_files
203
+ listener.run
204
+ else
205
+ UI.info 'Paused files modification listening', :reset => true
206
+ listener.pause
207
+ end
208
+ end
209
+
210
+ # Trigger `run_on_change` on all Guards currently enabled.
211
+ #
212
+ def run_on_change(files)
213
+ run do
214
+ run_on_guards do |guard|
215
+ run_on_change_task(files, guard)
216
+ end
217
+ end
218
+ end
219
+
220
+ # Run a block where the listener and the interactor is
221
+ # blocked.
222
+ #
223
+ # @yield the block to run
224
+ #
225
+ def run
226
+ UI.clear if options[:clear]
227
+
228
+ lock.synchronize do
229
+ begin
230
+ interactor.stop if interactor
231
+ yield
232
+ rescue Interrupt
233
+ end
234
+
235
+ interactor.start if interactor
236
+ end
237
+ end
238
+
239
+ # Loop through all groups and run the given task (as block) for each Guard.
240
+ #
241
+ # Stop the task run for the all Guards within a group if one Guard
242
+ # throws `:task_has_failed`.
243
+ #
244
+ # @param [Hash] An hash with a guard or a group scope
245
+ #
246
+ def run_on_guards(scopes = {})
247
+ if guard = scopes[:guard]
248
+ yield(guard)
249
+ else
250
+ groups = scopes[:group] ? [scopes[:group]] : @groups
251
+ groups.each do |group|
252
+ catch :task_has_failed do
253
+ guards(:group => group.name).each do |guard|
254
+ yield(guard)
255
+ end
256
+ end
257
+ end
258
+ end
259
+ end
260
+
261
+ # Run the `:run_on_change` task. When the option `:watch_all_modifications` is set,
262
+ # the task is split to run changed paths on {Guard::Guard#run_on_change}, whereas
263
+ # deleted paths run on {Guard::Guard#run_on_deletion}.
264
+ #
265
+ # @param [Array<String>] files the list of files to pass to the task
266
+ # @param [Guard::Guard] guard the guard to run
267
+ # @raise [:task_has_failed] when task has failed
268
+ #
269
+ def run_on_change_task(files, guard)
270
+ paths = Watcher.match_files(guard, files)
271
+ changes = changed_paths(paths)
272
+ deletions = deleted_paths(paths)
273
+
274
+ unless changes.empty?
275
+ UI.debug "#{ guard.class.name }#run_on_change with #{ changes.inspect }"
276
+ run_supervised_task(guard, :run_on_change, changes)
277
+ end
278
+
279
+ unless deletions.empty?
280
+ UI.debug "#{ guard.class.name }#run_on_deletion with #{ deletions.inspect }"
281
+ run_supervised_task(guard, :run_on_deletion, deletions)
282
+ end
283
+ end
284
+
285
+ # Detects the paths that have changed.
286
+ #
287
+ # Deleted paths are prefixed by an exclamation point.
288
+ # @see Guard::Listener#modified_files
289
+ #
290
+ # @param [Array<String>] paths the watched paths
291
+ # @return [Array<String>] the changed paths
292
+ #
293
+ def changed_paths(paths)
294
+ paths.select { |f| !f.respond_to?(:start_with?) || !f.start_with?('!') }
295
+ end
296
+
297
+ # Detects the paths that have been deleted.
298
+ #
299
+ # Deleted paths are prefixed by an exclamation point.
300
+ # @see Guard::Listener#modified_files
301
+ #
302
+ # @param [Array<String>] paths the watched paths
303
+ # @return [Array<String>] the deleted paths
304
+ #
305
+ def deleted_paths(paths)
306
+ paths.select { |f| f.respond_to?(:start_with?) && f.start_with?('!') }.map { |f| f.slice(1..-1) }
307
+ end
308
+
309
+ # Run a Guard task, but remove the Guard when his work leads to a system failure.
310
+ #
311
+ # When the Group has `:halt_on_fail` disabled, we've to catch `:task_has_failed`
312
+ # here in order to avoid an uncaught throw error.
313
+ #
314
+ # @param [Guard::Guard] guard the Guard to execute
315
+ # @param [Symbol] task the task to run
316
+ # @param [Array] args the arguments for the task
317
+ # @raise [:task_has_failed] when task has failed
318
+ #
319
+ def run_supervised_task(guard, task, *args)
320
+ catch guard_symbol(guard) do
321
+ guard.hook("#{ task }_begin", *args)
322
+ result = guard.send(task, *args)
323
+ guard.hook("#{ task }_end", result)
324
+
325
+ result
326
+ end
327
+
328
+ rescue Exception => ex
329
+ UI.error("#{ guard.class.name } failed to achieve its <#{ task.to_s }>, exception was:" +
330
+ "\n#{ ex.class }: #{ ex.message }\n#{ ex.backtrace.join("\n") }")
331
+
332
+ guards.delete guard
333
+ UI.info("\n#{ guard.class.name } has just been fired")
334
+
335
+ ex
336
+ end
337
+
338
+ # Get the symbol we have to catch when running a supervised task.
339
+ # If we are within a Guard group that has the `:halt_on_fail`
340
+ # option set, we do NOT catch it here, it will be catched at the
341
+ # group level.
342
+ #
343
+ # @see .run_on_guards
344
+ #
345
+ # @param [Guard::Guard] guard the Guard to execute
346
+ # @return [Symbol] the symbol to catch
347
+ #
348
+ def guard_symbol(guard)
349
+ if guard.group.class == Symbol
350
+ group = groups(guard.group)
351
+ group.options[:halt_on_fail] ? :no_catch : :task_has_failed
352
+ else
353
+ :task_has_failed
354
+ end
355
+ end
356
+
357
+ # Add a Guard to use.
358
+ #
359
+ # @param [String] name the Guard name
360
+ # @param [Array<Watcher>] watchers the list of declared watchers
361
+ # @param [Array<Hash>] callbacks the list of callbacks
362
+ # @param [Hash] options the Guard options (see the given Guard documentation)
363
+ # @return [Guard::Guard] the guard added
364
+ #
365
+ def add_guard(name, watchers = [], callbacks = [], options = {})
366
+ if name.to_sym == :ego
367
+ UI.deprecation('Guard::Ego is now part of Guard. You can remove it from your Guardfile.')
368
+ else
369
+ guard_class = get_guard_class(name)
370
+ callbacks.each { |callback| Hook.add_callback(callback[:listener], guard_class, callback[:events]) }
371
+ guard = guard_class.new(watchers, options)
372
+ @guards << guard
373
+ guard
374
+ end
375
+ end
376
+
377
+ # Add a Guard group.
378
+ #
379
+ # @param [String] name the group name
380
+ # @option options [Boolean] halt_on_fail if a task execution
381
+ # should be halted for all Guards in this group if one Guard throws `:task_has_failed`
382
+ # @return [Guard::Group] the group added (or retrieved from the `@groups` variable if already present)
383
+ #
384
+ def add_group(name, options = {})
385
+ group = groups(name)
386
+ if group.nil?
387
+ group = Group.new(name, options)
388
+ @groups << group
389
+ end
390
+ group
391
+ end
392
+
393
+ # Tries to load the Guard main class.
394
+ #
395
+ # @param [String] name the name of the Guard
396
+ # @return [Class, nil] the loaded class
397
+ #
398
+ def get_guard_class(name)
399
+ name = name.to_s
400
+ try_require = false
401
+ const_name = name.downcase.gsub('-', '')
402
+ begin
403
+ require "guard/#{ name.downcase }" if try_require
404
+ self.const_get(self.constants.find { |c| c.to_s.downcase == const_name })
405
+ rescue TypeError
406
+ unless try_require
407
+ try_require = true
408
+ retry
409
+ else
410
+ UI.error "Could not find class Guard::#{ const_name.capitalize }"
411
+ end
412
+ rescue LoadError => loadError
413
+ UI.error "Could not load 'guard/#{ name.downcase }' or find class Guard::#{ const_name.capitalize }"
414
+ UI.error loadError.to_s
415
+ end
416
+ end
417
+
418
+ # Locate a path to a Guard gem.
419
+ #
420
+ # @param [String] name the name of the Guard without the prefix `guard-`
421
+ # @return [String] the full path to the Guard gem
422
+ #
423
+ def locate_guard(name)
424
+ if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
425
+ Gem::Specification.find_by_name("guard-#{ name }").full_gem_path
426
+ else
427
+ Gem.source_index.find_name("guard-#{ name }").last.full_gem_path
428
+ end
429
+ rescue
430
+ UI.error "Could not find 'guard-#{ name }' gem path."
431
+ end
432
+
433
+ # Returns a list of guard Gem names installed locally.
434
+ #
435
+ # @return [Array<String>] a list of guard gem names
436
+ #
437
+ def guard_gem_names
438
+ if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
439
+ Gem::Specification.find_all.select { |x| x.name =~ /^guard-/ }
440
+ else
441
+ Gem.source_index.find_name(/^guard-/)
442
+ end.map { |x| x.name.sub /^guard-/, '' }
443
+ end
444
+
445
+ # Adds a command logger in debug mode. This wraps common command
446
+ # execution functions and logs the executed command before execution.
447
+ #
448
+ def debug_command_execution
449
+ Kernel.send(:alias_method, :original_system, :system)
450
+ Kernel.send(:define_method, :system) do |command, *args|
451
+ ::Guard::UI.debug "Command execution: #{ command } #{ args.join(' ') }"
452
+ original_system command, *args
453
+ end
454
+
455
+ Kernel.send(:alias_method, :original_backtick, :'`')
456
+ Kernel.send(:define_method, :'`') do |command|
457
+ ::Guard::UI.debug "Command execution: #{ command }"
458
+ original_backtick command
459
+ end
460
+ end
461
+
462
+ end
463
+ end