guard 0.8.1 → 0.8.2

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