guard 0.8.0 → 0.8.1

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,384 +1,384 @@
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
+ 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