guard 0.7.0 → 0.8.0
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/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
|