guard 0.8.1 → 0.8.2

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
@@ -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