guard 0.8.4 → 0.8.5

Sign up to get free protection for your applications and to get access to all the features.
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