guard 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +23 -1
- data/README.md +173 -226
- data/bin/guard +1 -1
- data/lib/guard.rb +243 -45
- data/lib/guard/cli.rb +135 -46
- data/lib/guard/dsl.rb +260 -38
- data/lib/guard/dsl_describer.rb +36 -4
- data/lib/guard/group.rb +22 -0
- data/lib/guard/guard.rb +53 -13
- data/lib/guard/hook.rb +61 -15
- data/lib/guard/interactor.rb +52 -14
- data/lib/guard/listener.rb +181 -26
- data/lib/guard/listeners/darwin.rb +26 -7
- data/lib/guard/listeners/linux.rb +32 -8
- data/lib/guard/listeners/polling.rb +23 -5
- data/lib/guard/listeners/windows.rb +25 -6
- data/lib/guard/notifier.rb +78 -3
- data/lib/guard/ui.rb +125 -47
- data/lib/guard/version.rb +4 -1
- data/lib/guard/watcher.rb +48 -4
- data/man/guard.1 +1 -1
- data/man/guard.1.html +1 -1
- metadata +36 -13
data/lib/guard/dsl.rb
CHANGED
@@ -1,62 +1,160 @@
|
|
1
1
|
module Guard
|
2
|
+
|
3
|
+
# The DSL class provides the methods that are used in each `Guardfile` to describe
|
4
|
+
# the behaviour of Guard.
|
5
|
+
#
|
6
|
+
# The main keywords of the DSL are `guard` and `watch`. These are necessary to define
|
7
|
+
# the used Guards and the file changes they are watching.
|
8
|
+
#
|
9
|
+
# You can optionally group the Guards with the `group` keyword and ignore certain paths
|
10
|
+
# with the `ignore_paths` keyword.
|
11
|
+
#
|
12
|
+
# A more advanced DSL use is the `callback` keyword that allows you to execute arbitrary
|
13
|
+
# code before or after any of the `start`, `stop`, `reload`, `run_all` and `run_on_change`
|
14
|
+
# Guards' method. You can even insert more hooks inside these methods.
|
15
|
+
# Please [checkout the Wiki page](https://github.com/guard/guard/wiki/Hooks-and-callbacks) for more details.
|
16
|
+
#
|
17
|
+
# The DSL will also evaluate normal Ruby code.
|
18
|
+
#
|
19
|
+
# There are two possible locations for the `Guardfile`:
|
20
|
+
# - The `Guardfile` in the current directory where Guard has been started
|
21
|
+
# - The `.Guardfile` in your home directory.
|
22
|
+
#
|
23
|
+
# In addition, if a user configuration `.guard.rb` in your home directory is found, it will
|
24
|
+
# be appended to the current project `Guardfile`.
|
25
|
+
#
|
26
|
+
# @example A sample of a complex Guardfile
|
27
|
+
#
|
28
|
+
# group 'frontend' do
|
29
|
+
# guard 'passenger', :ping => true do
|
30
|
+
# watch('config/application.rb')
|
31
|
+
# watch('config/environment.rb')
|
32
|
+
# watch(%r{^config/environments/.+\.rb})
|
33
|
+
# watch(%r{^config/initializers/.+\.rb})
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# guard 'livereload', :apply_js_live => false do
|
37
|
+
# watch(%r{^app/.+\.(erb|haml)})
|
38
|
+
# watch(%r{^app/helpers/.+\.rb})
|
39
|
+
# watch(%r{^public/javascripts/.+\.js})
|
40
|
+
# watch(%r{^public/stylesheets/.+\.css})
|
41
|
+
# watch(%r{^public/.+\.html})
|
42
|
+
# watch(%r{^config/locales/.+\.yml})
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# group 'backend' do
|
47
|
+
# # Reload the bundle when the Gemfile is modified
|
48
|
+
# guard 'bundler' do
|
49
|
+
# watch('Gemfile')
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# # for big project you can fine tune the "timeout" before Spork's launch is considered failed
|
53
|
+
# guard 'spork', :wait => 40 do
|
54
|
+
# watch('Gemfile')
|
55
|
+
# watch('config/application.rb')
|
56
|
+
# watch('config/environment.rb')
|
57
|
+
# watch(%r{^config/environments/.+\.rb})
|
58
|
+
# watch(%r{^config/initializers/.+\.rb})
|
59
|
+
# watch('spec/spec_helper.rb')
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# # use RSpec 2, from the system's gem and with some direct RSpec CLI options
|
63
|
+
# guard 'rspec', :version => 2, :cli => "--color --drb -f doc", :bundler => false do
|
64
|
+
# watch('spec/spec_helper.rb') { "spec" }
|
65
|
+
# watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
66
|
+
# watch('config/routes.rb') { "spec/routing" }
|
67
|
+
# watch(%r{^spec/support/(controllers|acceptance)_helpers\.rb}) { |m| "spec/#{m[1]}" }
|
68
|
+
# watch(%r{^spec/.+_spec\.rb})
|
69
|
+
#
|
70
|
+
# watch(%r{^app/controllers/(.+)_(controller)\.rb}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
71
|
+
#
|
72
|
+
# watch(%r{^app/(.+)\.rb}) { |m| "spec/#{m[1]}_spec.rb" }
|
73
|
+
# watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
#
|
2
77
|
class Dsl
|
3
78
|
class << self
|
79
|
+
|
4
80
|
@@options = nil
|
5
81
|
|
82
|
+
# Evaluate the DSL methods in the `Guardfile`.
|
83
|
+
#
|
84
|
+
# @option options [Array<Symbol,String>] groups the groups to evaluate
|
85
|
+
# @option options [String] guardfile the path to a valid Guardfile
|
86
|
+
# @option options [String] guardfile_contents a string representing the content of a valid Guardfile
|
87
|
+
# @raise [ArgumentError] when options are not a Hash
|
88
|
+
#
|
6
89
|
def evaluate_guardfile(options = {})
|
7
|
-
|
90
|
+
raise ArgumentError.new('No option hash passed to evaluate_guardfile!') unless options.is_a?(Hash)
|
8
91
|
|
9
92
|
@@options = options.dup
|
93
|
+
|
10
94
|
fetch_guardfile_contents
|
11
95
|
instance_eval_guardfile(guardfile_contents_with_user_config)
|
12
96
|
|
13
|
-
UI.error
|
97
|
+
UI.error 'No guards found in Guardfile, please add at least one.' if !::Guard.guards.nil? && ::Guard.guards.empty?
|
14
98
|
end
|
15
99
|
|
100
|
+
# Re-evaluate the `Guardfile` to update the current Guard configuration.
|
101
|
+
#
|
16
102
|
def reevaluate_guardfile
|
17
103
|
::Guard.guards.clear
|
104
|
+
::Guard.groups.clear
|
18
105
|
@@options.delete(:guardfile_contents)
|
19
106
|
Dsl.evaluate_guardfile(@@options)
|
20
|
-
msg =
|
107
|
+
msg = 'Guardfile has been re-evaluated.'
|
21
108
|
UI.info(msg)
|
22
109
|
Notifier.notify(msg)
|
23
110
|
end
|
24
111
|
|
112
|
+
# Evaluate the content of the `Guardfile`.
|
113
|
+
#
|
114
|
+
# @param [String] contents the content to evaluate.
|
115
|
+
#
|
25
116
|
def instance_eval_guardfile(contents)
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
exit 1
|
31
|
-
end
|
117
|
+
new.instance_eval(contents, @@options[:guardfile_path], 1)
|
118
|
+
rescue
|
119
|
+
UI.error "Invalid Guardfile, original error is:\n#{ $! }"
|
120
|
+
exit 1
|
32
121
|
end
|
33
122
|
|
123
|
+
# Test if the current `Guardfile` contains a specific Guard.
|
124
|
+
#
|
125
|
+
# @param [String] guard_name the name of the Guard
|
126
|
+
# @return [Boolean] whether the Guard has been declared
|
127
|
+
#
|
34
128
|
def guardfile_include?(guard_name)
|
35
|
-
guardfile_contents.match(/^guard\s*\(?\s*['":]#{guard_name}['"]?/)
|
129
|
+
guardfile_contents.match(/^guard\s*\(?\s*['":]#{ guard_name }['"]?/)
|
36
130
|
end
|
37
131
|
|
132
|
+
# Read the current `Guardfile` content.
|
133
|
+
#
|
134
|
+
# @param [String] the path to the Guardfile
|
135
|
+
#
|
38
136
|
def read_guardfile(guardfile_path)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
exit 1
|
45
|
-
end
|
137
|
+
@@options[:guardfile_path] = guardfile_path
|
138
|
+
@@options[:guardfile_contents] = File.read(guardfile_path)
|
139
|
+
rescue
|
140
|
+
UI.error("Error reading file #{ guardfile_path }")
|
141
|
+
exit 1
|
46
142
|
end
|
47
143
|
|
144
|
+
# Get the content to evaluate and stores it into
|
145
|
+
# the options as `:guardfile_contents`.
|
146
|
+
#
|
48
147
|
def fetch_guardfile_contents
|
49
|
-
# TODO: do we need .rc file interaction?
|
50
148
|
if @@options[:guardfile_contents]
|
51
|
-
UI.info
|
149
|
+
UI.info 'Using inline Guardfile.'
|
52
150
|
@@options[:guardfile_path] = 'Inline Guardfile'
|
53
151
|
|
54
152
|
elsif @@options[:guardfile]
|
55
153
|
if File.exist?(@@options[:guardfile])
|
56
154
|
read_guardfile(@@options[:guardfile])
|
57
|
-
UI.info "Using Guardfile at #{@@options[:guardfile]}."
|
155
|
+
UI.info "Using Guardfile at #{ @@options[:guardfile] }."
|
58
156
|
else
|
59
|
-
UI.error "No Guardfile exists at #{@@options[:guardfile]}."
|
157
|
+
UI.error "No Guardfile exists at #{ @@options[:guardfile] }."
|
60
158
|
exit 1
|
61
159
|
end
|
62
160
|
|
@@ -64,85 +162,209 @@ module Guard
|
|
64
162
|
if File.exist?(guardfile_default_path)
|
65
163
|
read_guardfile(guardfile_default_path)
|
66
164
|
else
|
67
|
-
UI.error
|
165
|
+
UI.error 'No Guardfile found, please create one with `guard init`.'
|
68
166
|
exit 1
|
69
167
|
end
|
70
168
|
end
|
71
169
|
|
72
170
|
unless guardfile_contents_usable?
|
73
|
-
UI.error "The command file(#{@@options[:guardfile]}) seems to be empty."
|
171
|
+
UI.error "The command file(#{ @@options[:guardfile] }) seems to be empty."
|
74
172
|
exit 1
|
75
173
|
end
|
76
174
|
end
|
77
175
|
|
176
|
+
# Get the content of the `Guardfile`.
|
177
|
+
#
|
178
|
+
# @return [String] the Guardfile content
|
179
|
+
#
|
78
180
|
def guardfile_contents
|
79
|
-
@@options ? @@options[:guardfile_contents] :
|
181
|
+
@@options ? @@options[:guardfile_contents] : ''
|
80
182
|
end
|
81
183
|
|
184
|
+
# Get the content of the `Guardfile` and the global
|
185
|
+
# user configuration file.
|
186
|
+
#
|
187
|
+
# @see #user_config_path
|
188
|
+
#
|
189
|
+
# @return [String] the Guardfile content
|
190
|
+
#
|
82
191
|
def guardfile_contents_with_user_config
|
83
192
|
config = File.read(user_config_path) if File.exist?(user_config_path)
|
84
193
|
[guardfile_contents, config].join("\n")
|
85
194
|
end
|
86
195
|
|
196
|
+
# Get the file path to the project `Guardfile`.
|
197
|
+
#
|
198
|
+
# @return [String] the path to the Guardfile
|
199
|
+
#
|
87
200
|
def guardfile_path
|
88
|
-
@@options ? @@options[:guardfile_path] :
|
201
|
+
@@options ? @@options[:guardfile_path] : ''
|
89
202
|
end
|
90
203
|
|
204
|
+
# Tests if the current `Guardfile` content is usable.
|
205
|
+
#
|
206
|
+
# @return [Boolean] if the Guardfile is usable
|
207
|
+
#
|
91
208
|
def guardfile_contents_usable?
|
92
|
-
guardfile_contents && guardfile_contents.size >= 'guard :a'.size #
|
209
|
+
guardfile_contents && guardfile_contents.size >= 'guard :a'.size # Smallest Guard definition
|
93
210
|
end
|
94
211
|
|
212
|
+
# Gets the default path of the `Guardfile`. This returns the `Guardfile`
|
213
|
+
# from the current directory when existing, or the global `.Guardfile`
|
214
|
+
# at the home directory.
|
215
|
+
#
|
216
|
+
# @return [String] the path to the Guardfile
|
217
|
+
#
|
95
218
|
def guardfile_default_path
|
96
219
|
File.exist?(local_guardfile_path) ? local_guardfile_path : home_guardfile_path
|
97
220
|
end
|
98
221
|
|
99
|
-
|
222
|
+
private
|
100
223
|
|
224
|
+
# The path to the `Guardfile` that is located at
|
225
|
+
# the directory, where Guard has been started from.
|
226
|
+
#
|
227
|
+
# @param [String] the path to the local Guardfile
|
228
|
+
#
|
101
229
|
def local_guardfile_path
|
102
|
-
File.join(Dir.pwd,
|
230
|
+
File.join(Dir.pwd, 'Guardfile')
|
103
231
|
end
|
104
232
|
|
233
|
+
# The path to the `.Guardfile` that is located at
|
234
|
+
# the users home directory.
|
235
|
+
#
|
236
|
+
# @param [String] the path to ~/.Guardfile
|
237
|
+
#
|
105
238
|
def home_guardfile_path
|
106
|
-
File.expand_path(File.join(
|
239
|
+
File.expand_path(File.join('~', '.Guardfile'))
|
107
240
|
end
|
108
241
|
|
242
|
+
# The path to the user configuration `.guard.rb`
|
243
|
+
# that is located at the users home directory.
|
244
|
+
#
|
245
|
+
# @param [String] the path to ~/.guard.rb
|
246
|
+
#
|
109
247
|
def user_config_path
|
110
|
-
File.expand_path(File.join(
|
248
|
+
File.expand_path(File.join('~', '.guard.rb'))
|
111
249
|
end
|
112
250
|
|
113
251
|
end
|
114
252
|
|
115
|
-
|
253
|
+
# Declares a group of guards to be run with `guard start --group group_name`.
|
254
|
+
#
|
255
|
+
# @example Declare two groups of Guards
|
256
|
+
#
|
257
|
+
# group 'backend' do
|
258
|
+
# guard 'spork'
|
259
|
+
# guard 'rspec'
|
260
|
+
# end
|
261
|
+
#
|
262
|
+
# group 'frontend' do
|
263
|
+
# guard 'passenger'
|
264
|
+
# guard 'livereload'
|
265
|
+
# end
|
266
|
+
#
|
267
|
+
# @param [Symbol, String] name the group's name called from the CLI
|
268
|
+
# @param [Hash] options the options accepted by the group
|
269
|
+
# @yield a block where you can declare several guards
|
270
|
+
#
|
271
|
+
# @see Guard.add_group
|
272
|
+
# @see Dsl#guard
|
273
|
+
# @see Guard::DslDescriber
|
274
|
+
#
|
275
|
+
def group(name, options = {})
|
116
276
|
@groups = @@options[:group] || []
|
117
|
-
name
|
277
|
+
name = name.to_sym
|
118
278
|
|
119
|
-
if
|
279
|
+
if block_given? && (@groups.empty? || @groups.map(&:to_sym).include?(name))
|
280
|
+
::Guard.add_group(name.to_s.downcase, options)
|
120
281
|
@current_group = name
|
121
|
-
|
282
|
+
|
283
|
+
yield if block_given?
|
284
|
+
|
122
285
|
@current_group = nil
|
123
286
|
end
|
124
287
|
end
|
125
288
|
|
126
|
-
|
289
|
+
# Declare a guard to be used when running `guard start`.
|
290
|
+
#
|
291
|
+
# The name parameter is usually the name of the gem without
|
292
|
+
# the 'guard-' prefix.
|
293
|
+
#
|
294
|
+
# The available options are different for each Guard implementation.
|
295
|
+
#
|
296
|
+
# @example Declare a Guard
|
297
|
+
#
|
298
|
+
# guard 'rspec' do
|
299
|
+
# end
|
300
|
+
#
|
301
|
+
# @param [String] name the Guard name
|
302
|
+
# @param [Hash] options the options accepted by the Guard
|
303
|
+
# @yield a block where you can declare several watch patterns and actions
|
304
|
+
#
|
305
|
+
# @see Guard.add_guard
|
306
|
+
# @see Dsl#group
|
307
|
+
# @see Dsl#watch
|
308
|
+
# @see Guard::DslDescriber
|
309
|
+
#
|
310
|
+
def guard(name, options = {})
|
127
311
|
@watchers = []
|
128
312
|
@callbacks = []
|
129
|
-
|
313
|
+
|
314
|
+
yield if block_given?
|
315
|
+
|
130
316
|
options.update(:group => (@current_group || :default))
|
131
|
-
::Guard.add_guard(name.to_s.downcase
|
317
|
+
::Guard.add_guard(name.to_s.downcase, @watchers, @callbacks, options)
|
132
318
|
end
|
133
319
|
|
320
|
+
# Define a pattern to be watched in order to run actions on file modification.
|
321
|
+
#
|
322
|
+
# @example Declare watchers for a Guard
|
323
|
+
#
|
324
|
+
# guard 'rspec' do
|
325
|
+
# watch('spec/spec_helper.rb')
|
326
|
+
# watch(%r{^.+_spec.rb})
|
327
|
+
# watch(%r{^app/controllers/(.+).rb}) { |m| 'spec/acceptance/#{m[1]}s_spec.rb' }
|
328
|
+
# end
|
329
|
+
#
|
330
|
+
# @param [String, Regexp] pattern the pattern to be watched by the guard
|
331
|
+
# @yield a block to be run when the pattern is matched
|
332
|
+
# @yieldparam [MatchData] m matches of the pattern
|
333
|
+
# @yieldreturn a directory, a filename, an array of directories / filenames, or nothing (can be an arbitrary command)
|
334
|
+
#
|
335
|
+
# @see Guard::Watcher
|
336
|
+
# @see Dsl#guard
|
337
|
+
#
|
134
338
|
def watch(pattern, &action)
|
135
339
|
@watchers << ::Guard::Watcher.new(pattern, action)
|
136
340
|
end
|
137
341
|
|
342
|
+
# Define a callback to execute arbitrary code before or after any of
|
343
|
+
# the `start`, `stop`, `reload`, `run_all` and `run_on_change` guards' method.
|
344
|
+
#
|
345
|
+
# @param [Array] args the callback arguments
|
346
|
+
# @yield a block with listeners
|
347
|
+
#
|
348
|
+
# @see Guard::Hook
|
349
|
+
#
|
138
350
|
def callback(*args, &listener)
|
139
351
|
listener, events = args.size > 1 ? args : [listener, args[0]]
|
140
352
|
@callbacks << { :events => events, :listener => listener }
|
141
353
|
end
|
142
354
|
|
355
|
+
# Ignore certain paths globally.
|
356
|
+
#
|
357
|
+
# @example Ignore some paths
|
358
|
+
# ignore_paths ".git", ".svn"
|
359
|
+
#
|
360
|
+
# @param [Array] paths the list of paths to ignore
|
361
|
+
#
|
362
|
+
# @see Guard::Listener
|
363
|
+
#
|
143
364
|
def ignore_paths(*paths)
|
144
|
-
UI.info "Ignoring paths: #{paths.join(', ')}"
|
365
|
+
UI.info "Ignoring paths: #{ paths.join(', ') }"
|
145
366
|
::Guard.listener.ignore_paths.push(*paths)
|
146
367
|
end
|
368
|
+
|
147
369
|
end
|
148
370
|
end
|
data/lib/guard/dsl_describer.rb
CHANGED
@@ -1,28 +1,60 @@
|
|
1
1
|
require 'guard/dsl'
|
2
2
|
|
3
3
|
module Guard
|
4
|
+
|
5
|
+
# The DslDescriber overrides methods to create an internal structure
|
6
|
+
# of the Guardfile that is used in some inspection utility methods
|
7
|
+
# like the CLI commands `show` and `list`.
|
8
|
+
#
|
9
|
+
# @see Guard::Dsl
|
10
|
+
# @see Guard::CLI
|
11
|
+
#
|
4
12
|
class DslDescriber < Dsl
|
13
|
+
|
5
14
|
@@guardfile_structure = [ { :guards => [] } ]
|
6
15
|
|
7
16
|
class << self
|
17
|
+
|
18
|
+
# Get the Guardfile structure.
|
19
|
+
#
|
20
|
+
# @return [Array<Hash>] the structure
|
21
|
+
#
|
8
22
|
def guardfile_structure
|
9
23
|
@@guardfile_structure
|
10
24
|
end
|
11
25
|
end
|
12
26
|
|
13
27
|
private
|
14
|
-
def group(name, &guard_definition)
|
15
|
-
@@guardfile_structure << { :group => name.to_sym, :guards => [] }
|
16
28
|
|
29
|
+
# Declares a group of guards.
|
30
|
+
#
|
31
|
+
# @param [String] name the group's name called from the CLI
|
32
|
+
# @yield a block where you can declare several guards
|
33
|
+
#
|
34
|
+
# @see Guard::Dsl#group
|
35
|
+
#
|
36
|
+
def group(name)
|
37
|
+
@@guardfile_structure << { :group => name.to_sym, :guards => [] }
|
17
38
|
@group = true
|
18
|
-
|
39
|
+
|
40
|
+
yield if block_given?
|
41
|
+
|
19
42
|
@group = false
|
20
43
|
end
|
21
44
|
|
22
|
-
|
45
|
+
# Declares a Guard.
|
46
|
+
#
|
47
|
+
# @param [String] name the Guard name
|
48
|
+
# @param [Hash] options the options accepted by the Guard
|
49
|
+
# @yield a block where you can declare several watch patterns and actions
|
50
|
+
#
|
51
|
+
# @see Guard::Dsl#guard
|
52
|
+
#
|
53
|
+
def guard(name, options = {})
|
23
54
|
node = (@group ? @@guardfile_structure.last : @@guardfile_structure.first)
|
24
55
|
|
25
56
|
node[:guards] << { :name => name, :options => options }
|
26
57
|
end
|
58
|
+
|
27
59
|
end
|
28
60
|
end
|