guard 0.8.4 → 0.8.5

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