guard 1.4.0 → 2.18.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.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -677
  3. data/LICENSE +4 -2
  4. data/README.md +91 -753
  5. data/bin/_guard-core +11 -0
  6. data/bin/guard +108 -3
  7. data/lib/guard/aruba_adapter.rb +59 -0
  8. data/lib/guard/cli/environments/bundler.rb +22 -0
  9. data/lib/guard/cli/environments/evaluate_only.rb +35 -0
  10. data/lib/guard/cli/environments/valid.rb +69 -0
  11. data/lib/guard/cli.rb +129 -128
  12. data/lib/guard/commander.rb +104 -0
  13. data/lib/guard/commands/all.rb +37 -0
  14. data/lib/guard/commands/change.rb +31 -0
  15. data/lib/guard/commands/notification.rb +26 -0
  16. data/lib/guard/commands/pause.rb +29 -0
  17. data/lib/guard/commands/reload.rb +36 -0
  18. data/lib/guard/commands/scope.rb +38 -0
  19. data/lib/guard/commands/show.rb +24 -0
  20. data/lib/guard/config.rb +18 -0
  21. data/lib/guard/deprecated/dsl.rb +45 -0
  22. data/lib/guard/deprecated/evaluator.rb +39 -0
  23. data/lib/guard/deprecated/guard.rb +328 -0
  24. data/lib/guard/deprecated/guardfile.rb +84 -0
  25. data/lib/guard/deprecated/watcher.rb +27 -0
  26. data/lib/guard/dsl.rb +332 -363
  27. data/lib/guard/dsl_describer.rb +132 -122
  28. data/lib/guard/dsl_reader.rb +51 -0
  29. data/lib/guard/group.rb +34 -14
  30. data/lib/guard/guardfile/evaluator.rb +232 -0
  31. data/lib/guard/guardfile/generator.rb +128 -0
  32. data/lib/guard/guardfile.rb +24 -60
  33. data/lib/guard/interactor.rb +31 -255
  34. data/lib/guard/internals/debugging.rb +68 -0
  35. data/lib/guard/internals/groups.rb +40 -0
  36. data/lib/guard/internals/helpers.rb +13 -0
  37. data/lib/guard/internals/plugins.rb +53 -0
  38. data/lib/guard/internals/queue.rb +51 -0
  39. data/lib/guard/internals/scope.rb +121 -0
  40. data/lib/guard/internals/session.rb +180 -0
  41. data/lib/guard/internals/state.rb +25 -0
  42. data/lib/guard/internals/tracing.rb +33 -0
  43. data/lib/guard/internals/traps.rb +10 -0
  44. data/lib/guard/jobs/base.rb +21 -0
  45. data/lib/guard/jobs/pry_wrapper.rb +336 -0
  46. data/lib/guard/jobs/sleep.rb +26 -0
  47. data/lib/guard/notifier.rb +46 -212
  48. data/lib/guard/options.rb +22 -0
  49. data/lib/guard/plugin.rb +303 -0
  50. data/lib/guard/plugin_util.rb +191 -0
  51. data/lib/guard/rake_task.rb +42 -0
  52. data/lib/guard/runner.rb +80 -140
  53. data/lib/guard/templates/Guardfile +14 -0
  54. data/lib/guard/terminal.rb +13 -0
  55. data/lib/guard/ui/colors.rb +56 -0
  56. data/lib/guard/ui/config.rb +70 -0
  57. data/lib/guard/ui/logger.rb +30 -0
  58. data/lib/guard/ui.rb +163 -128
  59. data/lib/guard/version.rb +1 -2
  60. data/lib/guard/watcher/pattern/deprecated_regexp.rb +45 -0
  61. data/lib/guard/watcher/pattern/match_result.rb +18 -0
  62. data/lib/guard/watcher/pattern/matcher.rb +33 -0
  63. data/lib/guard/watcher/pattern/pathname_path.rb +15 -0
  64. data/lib/guard/watcher/pattern/simple_path.rb +23 -0
  65. data/lib/guard/watcher/pattern.rb +24 -0
  66. data/lib/guard/watcher.rb +52 -95
  67. data/lib/guard.rb +108 -376
  68. data/lib/tasks/releaser.rb +116 -0
  69. data/man/guard.1 +12 -9
  70. data/man/guard.1.html +18 -12
  71. metadata +148 -77
  72. data/images/guard.png +0 -0
  73. data/lib/guard/guard.rb +0 -156
  74. data/lib/guard/hook.rb +0 -120
  75. data/lib/guard/interactors/coolline.rb +0 -64
  76. data/lib/guard/interactors/helpers/completion.rb +0 -32
  77. data/lib/guard/interactors/helpers/terminal.rb +0 -46
  78. data/lib/guard/interactors/readline.rb +0 -94
  79. data/lib/guard/interactors/simple.rb +0 -19
  80. data/lib/guard/notifiers/emacs.rb +0 -69
  81. data/lib/guard/notifiers/gntp.rb +0 -118
  82. data/lib/guard/notifiers/growl.rb +0 -99
  83. data/lib/guard/notifiers/growl_notify.rb +0 -92
  84. data/lib/guard/notifiers/libnotify.rb +0 -96
  85. data/lib/guard/notifiers/notifysend.rb +0 -84
  86. data/lib/guard/notifiers/rb_notifu.rb +0 -102
  87. data/lib/guard/notifiers/terminal_notifier.rb +0 -66
  88. data/lib/guard/notifiers/tmux.rb +0 -69
  89. data/lib/guard/version.rbc +0 -130
data/lib/guard/dsl.rb CHANGED
@@ -1,469 +1,438 @@
1
- module Guard
1
+ require "guard/guardfile/evaluator"
2
+ require "guard/interactor"
3
+ require "guard/notifier"
4
+ require "guard/ui"
5
+ require "guard/watcher"
6
+
7
+ require "guard/deprecated/dsl" unless Guard::Config.new.strict?
8
+ require "guard"
2
9
 
3
- # The DSL class provides the methods that are used in each `Guardfile` to describe
4
- # the behaviour of Guard.
10
+ module Guard
11
+ # The Dsl class provides the methods that are used in each `Guardfile` to
12
+ # describe the behaviour of Guard.
5
13
  #
6
- # The main keywords of the DSL are `guard` and `watch`. These are necessary to define
7
- # the used Guard plugins and the file changes they are watching.
14
+ # The main keywords of the DSL are {#guard} and {#watch}. These are necessary
15
+ # to define the used Guard plugins and the file changes they are watching.
8
16
  #
9
- # You can optionally group the Guard plugins with the `group` keyword and ignore and filter certain paths
10
- # with the `ignore` and `filter` keywords.
17
+ # You can optionally group the Guard plugins with the {#group} keyword and
18
+ # ignore and filter certain paths with the {#ignore} and {#filter} keywords.
11
19
  #
12
- # You can set your preferred system notification library with `notification` and pass
13
- # some optional configuration options for the library. If you don't configure a library,
14
- # Guard will automatically pick one with default options (if you don't want notifications,
15
- # specify `:off` as library). @see ::Guard::Notifier for more information about the supported libraries.
20
+ # You can set your preferred system notification library with {#notification}
21
+ # and pass some optional configuration options for the library. If you don't
22
+ # configure a library, Guard will automatically pick one with default options
23
+ # (if you don't want notifications, specify `:off` as library). Please see
24
+ # {Notifier} for more information about the supported libraries.
16
25
  #
17
- # A more advanced DSL use is the `callback` keyword that allows you to execute arbitrary
18
- # code before or after any of the `start`, `stop`, `reload`, `run_all`, `run_on_changes`,
19
- # `run_on_additions`, `run_on_modifications` and `run_on_removals` Guard plugins method.
20
- # You can even insert more hooks inside these methods.
21
- # Please [checkout the Wiki page](https://github.com/guard/guard/wiki/Hooks-and-callbacks) for more details.
26
+ # A more advanced DSL use is the {#callback} keyword that allows you to
27
+ # execute arbitrary code before or after any of the {Plugin#start},
28
+ # {Plugin#stop}, {Plugin#reload}, {Plugin#run_all},
29
+ # {Plugin#run_on_changes}, {Plugin#run_on_additions},
30
+ # {Plugin#run_on_modifications} and {Plugin#run_on_removals}
31
+ # Guard plugins method.
32
+ # You can even insert more hooks inside these methods. Please [checkout the
33
+ # Wiki page](https://github.com/guard/guard/wiki/Hooks-and-callbacks) for
34
+ # more details.
22
35
  #
23
36
  # The DSL will also evaluate normal Ruby code.
24
37
  #
25
38
  # There are two possible locations for the `Guardfile`:
26
- # - The `Guardfile` in the current directory where Guard has been started
27
- # - The `.Guardfile` in your home directory.
28
- #
29
- # In addition, if a user configuration `.guard.rb` in your home directory is found, it will
30
- # be appended to the current project `Guardfile`.
31
- #
32
- # @example A sample of a complex Guardfile
33
- #
34
- # notification :growl
35
- #
36
- # group 'frontend' do
37
- # guard 'passenger', :ping => true do
38
- # watch('config/application.rb')
39
- # watch('config/environment.rb')
40
- # watch(%r{^config/environments/.+\.rb})
41
- # watch(%r{^config/initializers/.+\.rb})
42
- # end
43
- #
44
- # guard 'livereload', :apply_js_live => false do
45
- # watch(%r{^app/.+\.(erb|haml)})
46
- # watch(%r{^app/helpers/.+\.rb})
47
- # watch(%r{^public/javascripts/.+\.js})
48
- # watch(%r{^public/stylesheets/.+\.css})
49
- # watch(%r{^public/.+\.html})
50
- # watch(%r{^config/locales/.+\.yml})
51
- # end
52
- # end
53
- #
54
- # group 'backend' do
55
- # # Reload the bundle when the Gemfile is modified
56
- # guard 'bundler' do
57
- # watch('Gemfile')
58
- # end
59
39
  #
60
- # # for big project you can fine tune the "timeout" before Spork's launch is considered failed
61
- # guard 'spork', :wait => 40 do
62
- # watch('Gemfile')
63
- # watch('config/application.rb')
64
- # watch('config/environment.rb')
65
- # watch(%r{^config/environments/.+\.rb})
66
- # watch(%r{^config/initializers/.+\.rb})
67
- # watch('spec/spec_helper.rb')
68
- # end
40
+ # * The `Guardfile` or `guardfile.rb` in the current directory where Guard
41
+ # has been started
42
+ # * The `.Guardfile` in your home directory.
69
43
  #
70
- # # use RSpec 2, from the system's gem and with some direct RSpec CLI options
71
- # guard 'rspec', :version => 2, :cli => "--color --drb -f doc", :bundler => false do
72
- # watch('spec/spec_helper.rb') { "spec" }
73
- # watch('app/controllers/application_controller.rb') { "spec/controllers" }
74
- # watch('config/routes.rb') { "spec/routing" }
75
- # watch(%r{^spec/support/(controllers|acceptance)_helpers\.rb}) { |m| "spec/#{m[1]}" }
76
- # watch(%r{^spec/.+_spec\.rb})
44
+ # In addition, if a user configuration `.guard.rb` in your home directory is
45
+ # found, it will be appended to the current project `Guardfile`.
77
46
  #
78
- # 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"] }
79
- #
80
- # watch(%r{^app/(.+)\.rb}) { |m| "spec/#{m[1]}_spec.rb" }
81
- # watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
82
- # end
83
- # end
47
+ # @see https://github.com/guard/guard/wiki/Guardfile-examples
84
48
  #
85
49
  class Dsl
50
+ Deprecated::Dsl.add_deprecated(self) unless Config.new.strict?
86
51
 
87
- require 'guard'
88
- require 'guard/dsl'
89
- require 'guard/interactor'
90
- require 'guard/notifier'
91
- require 'guard/ui'
92
- require 'guard/watcher'
93
-
94
- # Deprecation message for the `ignore_paths` method
95
- IGNORE_PATHS_DEPRECATION = <<-EOS.gsub(/^\s*/, '')
96
- Starting with Guard v1.1 the use of the 'ignore_paths' Guardfile dsl method is deprecated.
97
-
98
- Please replace that method with the better 'ignore' or/and 'filter' methods.
99
- Documentation on the README: https://github.com/guard/guard#guardfile-dsl-ignore
100
- EOS
101
-
102
- class << self
103
-
104
- @@options = nil
105
-
106
- # Evaluate the DSL methods in the `Guardfile`.
107
- #
108
- # @option options [Array<Symbol,String>] groups the groups to evaluate
109
- # @option options [String] guardfile the path to a valid Guardfile
110
- # @option options [String] guardfile_contents a string representing the content of a valid Guardfile
111
- # @raise [ArgumentError] when options are not a Hash
112
- #
113
- def evaluate_guardfile(options = {})
114
- raise ArgumentError.new('No option hash passed to evaluate_guardfile!') unless options.is_a?(Hash)
115
-
116
- @@options = options.dup
117
-
118
- fetch_guardfile_contents
119
- instance_eval_guardfile(guardfile_contents_with_user_config)
120
- end
121
-
122
- # Re-evaluate the `Guardfile` to update the current Guard configuration.
123
- #
124
- def reevaluate_guardfile
125
- before_reevaluate_guardfile
126
- ::Guard::Dsl.evaluate_guardfile(@@options)
127
- after_reevaluate_guardfile
128
- end
129
-
130
- # Stop Guard and clear internal state
131
- # before the Guardfile will be re-evaluated.
132
- #
133
- def before_reevaluate_guardfile
134
- ::Guard.runner.run(:stop)
135
- ::Guard.guards.clear
136
- ::Guard.setup_groups
137
- ::Guard::Notifier.clear_notifications
138
-
139
- @@options.delete(:guardfile_contents)
140
- end
141
-
142
- # Start Guard and notification and show a message
143
- # after the Guardfile has been re-evaluated.
144
- #
145
- def after_reevaluate_guardfile
146
- ::Guard::Notifier.turn_on if ::Guard::Notifier.enabled?
147
-
148
- if ::Guard.guards.empty?
149
- ::Guard::Notifier.notify('No guards found in Guardfile, please add at least one.', :title => 'Guard re-evaluate', :image => :failed)
150
- else
151
- msg = 'Guardfile has been re-evaluated.'
152
- ::Guard::UI.info(msg)
153
- ::Guard::Notifier.notify(msg, :title => 'Guard re-evaluate')
154
-
155
- ::Guard.runner.run(:start)
156
- end
157
- end
158
-
159
- # Evaluate the content of the `Guardfile`.
160
- #
161
- # @param [String] contents the content to evaluate.
162
- #
163
- def instance_eval_guardfile(contents)
164
- new.instance_eval(contents, @@options[:guardfile_path], 1)
165
- rescue
166
- ::Guard::UI.error "Invalid Guardfile, original error is:\n#{ $! }"
167
- end
168
-
169
- # Test if the current `Guardfile` contains a specific Guard plugin.
170
- #
171
- # @param [String] guard_name the name of the Guard
172
- # @return [Boolean] whether the Guard has been declared
173
- #
174
- def guardfile_include?(guard_name)
175
- guardfile_contents.match(/^guard\s*\(?\s*['":]#{ guard_name }['"]?/)
176
- end
177
-
178
- # Read the current `Guardfile` content.
179
- #
180
- # @param [String] guardfile_path the path to the Guardfile
181
- #
182
- def read_guardfile(guardfile_path)
183
- @@options[:guardfile_path] = guardfile_path
184
- @@options[:guardfile_contents] = File.read(guardfile_path)
185
- rescue
186
- ::Guard::UI.error("Error reading file #{ guardfile_path }")
187
- exit 1
188
- end
189
-
190
- # Get the content to evaluate and stores it into
191
- # the options as `:guardfile_contents`.
192
- #
193
- def fetch_guardfile_contents
194
- if @@options[:guardfile_contents]
195
- ::Guard::UI.info 'Using inline Guardfile.'
196
- @@options[:guardfile_path] = 'Inline Guardfile'
197
-
198
- elsif @@options[:guardfile]
199
- if File.exist?(@@options[:guardfile])
200
- read_guardfile(@@options[:guardfile])
201
- ::Guard::UI.info "Using Guardfile at #{ @@options[:guardfile] }."
202
- else
203
- ::Guard::UI.error "No Guardfile exists at #{ @@options[:guardfile] }."
204
- exit 1
205
- end
206
-
207
- else
208
- if File.exist?(guardfile_default_path)
209
- read_guardfile(guardfile_default_path)
210
- else
211
- ::Guard::UI.error 'No Guardfile found, please create one with `guard init`.'
212
- exit 1
213
- end
214
- end
215
-
216
- unless guardfile_contents_usable?
217
- ::Guard::UI.error 'No Guard plugins found in Guardfile, please add at least one.'
218
- end
219
- end
220
-
221
- # Get the content of the `Guardfile`.
222
- #
223
- # @return [String] the Guardfile content
224
- #
225
- def guardfile_contents
226
- @@options ? @@options[:guardfile_contents] : ''
227
- end
228
-
229
- # Get the content of the `Guardfile` and the global
230
- # user configuration file.
231
- #
232
- # @see #user_config_path
233
- #
234
- # @return [String] the Guardfile content
235
- #
236
- def guardfile_contents_with_user_config
237
- config = File.read(user_config_path) if File.exist?(user_config_path)
238
- [guardfile_contents, config].join("\n")
239
- end
240
-
241
- # Get the file path to the project `Guardfile`.
242
- #
243
- # @return [String] the path to the Guardfile
244
- #
245
- def guardfile_path
246
- @@options ? @@options[:guardfile_path] : ''
247
- end
248
-
249
- # Tests if the current `Guardfile` content is usable.
250
- #
251
- # @return [Boolean] if the Guardfile is usable
252
- #
253
- def guardfile_contents_usable?
254
- guardfile_contents && guardfile_contents.size >= 'guard :a'.size # Smallest Guard definition
255
- end
256
-
257
- # Gets the default path of the `Guardfile`. This returns the `Guardfile`
258
- # from the current directory when existing, or the global `.Guardfile`
259
- # at the home directory.
260
- #
261
- # @return [String] the path to the Guardfile
262
- #
263
- def guardfile_default_path
264
- File.exist?(local_guardfile_path) ? local_guardfile_path : home_guardfile_path
265
- end
266
-
267
- private
268
-
269
- # The path to the `Guardfile` that is located at
270
- # the directory, where Guard has been started from.
271
- #
272
- # @return [String] the path to the local Guardfile
273
- #
274
- def local_guardfile_path
275
- File.join(Dir.pwd, 'Guardfile')
276
- end
277
-
278
- # The path to the `.Guardfile` that is located at
279
- # the users home directory.
280
- #
281
- # @return [String] the path to ~/.Guardfile
282
- #
283
- def home_guardfile_path
284
- File.expand_path(File.join('~', '.Guardfile'))
285
- end
52
+ # Wrap exceptions during parsing Guardfile
53
+ class Error < RuntimeError
54
+ end
286
55
 
287
- # The path to the user configuration `.guard.rb`
288
- # that is located at the users home directory.
289
- #
290
- # @return [String] the path to ~/.guard.rb
291
- #
292
- def user_config_path
293
- File.expand_path(File.join('~', '.guard.rb'))
294
- end
56
+ WARN_INVALID_LOG_LEVEL = "Invalid log level `%s` ignored. "\
57
+ "Please use either :debug, :info, :warn or :error."
295
58
 
296
- end
59
+ WARN_INVALID_LOG_OPTIONS = "You cannot specify the logger options"\
60
+ " :only and :except at the same time."
297
61
 
298
62
  # Set notification options for the system notifications.
299
- # You can set multiple notification, which allows you to show local
63
+ # You can set multiple notifications, which allows you to show local
300
64
  # system notifications and remote notifications with separate libraries.
301
65
  # You can also pass `:off` as library to turn off notifications.
302
66
  #
303
67
  # @example Define multiple notifications
304
- # notification :growl_notify
305
- # notification :ruby_gntp, :host => '192.168.1.5'
306
- #
307
- # @see Guard::Notifier for available notifier and its options.
68
+ # notification :ruby_gntp
69
+ # notification :ruby_gntp, host: '192.168.1.5'
308
70
  #
309
71
  # @param [Symbol, String] notifier the name of the notifier to use
310
- # @param [Hash] options the notification library options
72
+ # @param [Hash] opts the notification library options
311
73
  #
312
- def notification(notifier, options = {})
313
- ::Guard::Notifier.add_notification(notifier.to_sym, options, false)
74
+ # @see Guard::Notifier for available notifier and its options.
75
+ #
76
+ def notification(notifier, opts = {})
77
+ Guard.state.session.guardfile_notification = { notifier.to_sym => opts }
314
78
  end
315
79
 
316
- # Sets the interactor to use.
80
+ # Sets the interactor options or disable the interactor.
317
81
  #
318
- # @example Use the readline interactor
319
- # interactor :readline
320
- #
321
- # @example Use the gets interactor
322
- # interactor :gets
82
+ # @example Pass options to the interactor
83
+ # interactor option1: 'value1', option2: 'value2'
323
84
  #
324
85
  # @example Turn off interactions
325
86
  # interactor :off
326
87
  #
327
- def interactor(interactor)
328
- ::Guard::Interactor.interactor = interactor.to_sym
88
+ # @param [Symbol, Hash] options either `:off` or a Hash with interactor
89
+ # options
90
+ #
91
+ def interactor(options)
92
+ # TODO: remove dependency on Interactor (let session handle this)
93
+ case options
94
+ when :off
95
+ Interactor.enabled = false
96
+ when Hash
97
+ Interactor.options = options
98
+ end
329
99
  end
330
100
 
331
- # Declares a group of Guard plugins to be run with `guard start --group group_name`.
101
+ # Declares a group of Guard plugins to be run with `guard start --group
102
+ # group_name`.
332
103
  #
333
104
  # @example Declare two groups of Guard plugins
334
- #
335
- # group 'backend' do
336
- # guard 'spork'
337
- # guard 'rspec'
105
+ # group :backend do
106
+ # guard :spork
107
+ # guard :rspec
338
108
  # end
339
109
  #
340
- # group 'frontend' do
341
- # guard 'passenger'
342
- # guard 'livereload'
110
+ # group :frontend do
111
+ # guard :passenger
112
+ # guard :livereload
343
113
  # end
344
114
  #
345
- # @param [Symbol, String] name the group name called from the CLI
115
+ # @param [Symbol, String, Array<Symbol, String>] name the group name called
116
+ # from the CLI
346
117
  # @param [Hash] options the options accepted by the group
347
- # @yield a block where you can declare several guards
118
+ # @yield a block where you can declare several Guard plugins
348
119
  #
120
+ # @see Group
349
121
  # @see Guard.add_group
350
- # @see Dsl#guard
351
- # @see Guard::DslDescriber
122
+ # @see #guard
352
123
  #
353
- def group(name, options = {})
354
- @groups = @@options[:group] || []
355
- name = name.to_sym
124
+ def group(*args)
125
+ options = args.last.is_a?(Hash) ? args.pop : {}
126
+ groups = args
356
127
 
357
- if block_given? && (@groups.empty? || @groups.map(&:to_sym).include?(name))
358
- ::Guard.add_group(name.to_s.downcase, options)
359
- @current_group = name
128
+ groups.each do |group|
129
+ next unless group.to_sym == :all
130
+ fail ArgumentError, "'all' is not an allowed group name!"
131
+ end
360
132
 
361
- yield if block_given?
133
+ if block_given?
134
+ groups.each do |group|
135
+ # TODO: let groups be added *after* evaluation
136
+ Guard.state.session.groups.add(group, options)
137
+ end
362
138
 
363
- @current_group = nil
139
+ @current_groups ||= []
140
+ @current_groups.push(groups)
141
+
142
+ yield
143
+
144
+ @current_groups.pop
145
+ else
146
+ UI.error \
147
+ "No Guard plugins found in the group '#{ groups.join(', ') }',"\
148
+ " please add at least one."
364
149
  end
365
150
  end
366
151
 
367
- # Declare a Guard plugin to be used when running `guard start`.
152
+ # Declares a Guard plugin to be used when running `guard start`.
368
153
  #
369
154
  # The name parameter is usually the name of the gem without
370
155
  # the 'guard-' prefix.
371
156
  #
372
157
  # The available options are different for each Guard implementation.
373
158
  #
374
- # @example Declare a Guard
159
+ # @example Declare a Guard without `watch` patterns
160
+ # guard :rspec
375
161
  #
376
- # guard 'rspec' do
162
+ # @example Declare a Guard with a `watch` pattern
163
+ # guard :rspec do
164
+ # watch %r{.*_spec.rb}
377
165
  # end
378
166
  #
379
- # @param [String] name the Guard name
380
- # @param [Hash] options the options accepted by the Guard
167
+ # @param [String] name the Guard plugin name
168
+ # @param [Hash] options the options accepted by the Guard plugin
381
169
  # @yield a block where you can declare several watch patterns and actions
382
170
  #
383
- # @see Guard.add_guard
384
- # @see Dsl#group
385
- # @see Dsl#watch
386
- # @see Guard::DslDescriber
171
+ # @see Plugin
172
+ # @see Guard.add_plugin
173
+ # @see #watch
174
+ # @see #group
387
175
  #
388
176
  def guard(name, options = {})
389
- @watchers = []
390
- @callbacks = []
391
- @current_group ||= :default
177
+ @plugin_options = options.merge(watchers: [], callbacks: [])
392
178
 
393
179
  yield if block_given?
394
180
 
395
- options.update(:group => @current_group)
396
- ::Guard.add_guard(name.to_s.downcase, @watchers, @callbacks, options)
181
+ @current_groups ||= []
182
+ groups = @current_groups && @current_groups.last || [:default]
183
+ groups.each do |group|
184
+ opts = @plugin_options.merge(group: group)
185
+ # TODO: let plugins be added *after* evaluation
186
+ Guard.state.session.plugins.add(name, opts)
187
+ end
188
+
189
+ @plugin_options = nil
397
190
  end
398
191
 
399
- # Define a pattern to be watched in order to run actions on file modification.
192
+ # Defines a pattern to be watched in order to run actions on file
193
+ # modification.
400
194
  #
401
195
  # @example Declare watchers for a Guard
402
- #
403
- # guard 'rspec' do
196
+ # guard :rspec do
404
197
  # watch('spec/spec_helper.rb')
405
198
  # watch(%r{^.+_spec.rb})
406
- # watch(%r{^app/controllers/(.+).rb}) { |m| 'spec/acceptance/#{m[1]}s_spec.rb' }
199
+ # watch(%r{^app/controllers/(.+).rb}) do |m|
200
+ # 'spec/acceptance/#{m[1]}s_spec.rb'
201
+ # end
407
202
  # end
408
203
  #
409
- # @param [String, Regexp] pattern the pattern to be watched by the guard
204
+ # @example Declare global watchers outside of a Guard
205
+ # watch(%r{^(.+)$}) { |m| puts "#{m[1]} changed." }
206
+ #
207
+ # @param [String, Regexp] pattern the pattern that Guard must watch for
208
+ # modification
209
+ #
410
210
  # @yield a block to be run when the pattern is matched
411
211
  # @yieldparam [MatchData] m matches of the pattern
412
- # @yieldreturn a directory, a filename, an array of directories / filenames, or nothing (can be an arbitrary command)
212
+ # @yieldreturn a directory, a filename, an array of
213
+ # directories / filenames, or nothing (can be an arbitrary command)
413
214
  #
414
215
  # @see Guard::Watcher
415
- # @see Dsl#guard
216
+ # @see #guard
416
217
  #
417
218
  def watch(pattern, &action)
418
- @watchers << ::Guard::Watcher.new(pattern, action)
219
+ # Allow watches in the global scope (to execute arbitrary commands) by
220
+ # building a generic Guard::Plugin.
221
+ @plugin_options ||= nil
222
+ return guard(:plugin) { watch(pattern, &action) } unless @plugin_options
223
+
224
+ @plugin_options[:watchers] << Watcher.new(pattern, action)
419
225
  end
420
226
 
421
- # Define a callback to execute arbitrary code before or after any of
422
- # the `start`, `stop`, `reload`, `run_all`, `run_on_changes` `run_on_additions`, `run_on_modifications`
423
- # and `run_on_removals` plugin method.
227
+ # Defines a callback to execute arbitrary code before or after any of
228
+ # the `start`, `stop`, `reload`, `run_all`, `run_on_changes`,
229
+ # `run_on_additions`, `run_on_modifications` and `run_on_removals` plugin
230
+ # method.
424
231
  #
425
- # @param [Array] args the callback arguments
426
- # @yield a block with listeners
232
+ # @example Add callback before the `reload` action.
233
+ # callback(:reload_begin) { puts "Let's reload!" }
427
234
  #
428
- # @see Guard::Hook
235
+ # @example Add callback before the `start` and `stop` actions.
429
236
  #
430
- def callback(*args, &listener)
431
- listener, events = args.size > 1 ? args : [listener, args[0]]
432
- @callbacks << { :events => events, :listener => listener }
237
+ # my_lambda = lambda do |plugin, event, *args|
238
+ # puts "Let's #{event} #{plugin} with #{args}!"
239
+ # end
240
+ #
241
+ # callback(my_lambda, [:start_begin, :start_end])
242
+ #
243
+ # @param [Array] args the callback arguments
244
+ # @yield a callback block
245
+ #
246
+ def callback(*args, &block)
247
+ @plugin_options ||= nil
248
+ fail "callback must be called within a guard block" unless @plugin_options
249
+
250
+ block, events = if args.size > 1
251
+ # block must be the first argument in that case, the
252
+ # yielded block is ignored
253
+ args
254
+ else
255
+ [block, args[0]]
256
+ end
257
+ @plugin_options[:callbacks] << { events: events, listener: block }
433
258
  end
434
259
 
435
- # @deprecated Ignore certain paths globally.
260
+ # Ignores certain paths globally.
436
261
  #
437
262
  # @example Ignore some paths
438
- # ignore_paths ".git", ".svn"
263
+ # ignore %r{^ignored/path/}, /man/
439
264
  #
440
- # @param [Array] paths the list of paths to ignore
265
+ # @param [Regexp] regexps a pattern (or list of patterns) for ignoring paths
441
266
  #
442
- def ignore_paths(*paths)
443
- ::Guard::UI.deprecation(IGNORE_PATHS_DEPRECATION)
267
+ def ignore(*regexps)
268
+ # TODO: use guardfile results class
269
+ Guard.state.session.guardfile_ignore = regexps
444
270
  end
445
271
 
446
- # Ignore certain patterns paths globally.
272
+ # TODO: deprecate
273
+ alias filter ignore
274
+
275
+ # Replaces ignored paths globally
447
276
  #
448
- # @example Ignore some paths
449
- # ignore %r{^ignored/path/}, /man/
277
+ # @example Ignore only these paths
278
+ # ignore! %r{^ignored/path/}, /man/
450
279
  #
451
- # @param [Regexp] regexps a pattern for ignoring paths
280
+ # @param [Regexp] regexps a pattern (or list of patterns) for ignoring paths
452
281
  #
453
- def ignore(*regexps)
454
- ::Guard.listener = ::Guard.listener.ignore(*regexps)
282
+ def ignore!(*regexps)
283
+ @ignore_regexps ||= []
284
+ @ignore_regexps << regexps
285
+ # TODO: use guardfile results class
286
+ Guard.state.session.guardfile_ignore_bang = @ignore_regexps
287
+ end
288
+
289
+ # TODO: deprecate
290
+ alias filter! ignore!
291
+
292
+ # Configures the Guard logger.
293
+ #
294
+ # * Log level must be either `:debug`, `:info`, `:warn` or `:error`.
295
+ # * Template supports the following placeholders: `:time`, `:severity`,
296
+ # `:progname`, `:pid`, `:unit_of_work_id` and `:message`.
297
+ # * Time format directives are the same as `Time#strftime` or
298
+ # `:milliseconds`.
299
+ # * The `:only` and `:except` options must be a `RegExp`.
300
+ #
301
+ # @example Set the log level
302
+ # logger level: :warn
303
+ #
304
+ # @example Set a custom log template
305
+ # logger template: '[Guard - :severity - :progname - :time] :message'
306
+ #
307
+ # @example Set a custom time format
308
+ # logger time_format: '%h'
309
+ #
310
+ # @example Limit logging to a Guard plugin
311
+ # logger only: :jasmine
312
+ #
313
+ # @example Log all but not the messages from a specific Guard plugin
314
+ # logger except: :jasmine
315
+ #
316
+ # @param [Hash] options the log options
317
+ # @option options [String, Symbol] level the log level
318
+ # @option options [String] template the logger template
319
+ # @option options [String, Symbol] time_format the time format
320
+ # @option options [Regexp] only show only messages from the matching Guard
321
+ # plugin
322
+ # @option options [Regexp] except does not show messages from the matching
323
+ # Guard plugin
324
+ #
325
+ def logger(options)
326
+ if options[:level]
327
+ options[:level] = options[:level].to_sym
328
+
329
+ unless [:debug, :info, :warn, :error].include? options[:level]
330
+ UI.warning(format(WARN_INVALID_LOG_LEVEL, options[:level]))
331
+ options.delete :level
332
+ end
333
+ end
334
+
335
+ if options[:only] && options[:except]
336
+ UI.warning WARN_INVALID_LOG_OPTIONS
337
+
338
+ options.delete :only
339
+ options.delete :except
340
+ end
341
+
342
+ # Convert the :only and :except options to a regular expression
343
+ [:only, :except].each do |name|
344
+ next unless options[name]
345
+
346
+ list = [].push(options[name]).flatten.map do |plugin|
347
+ Regexp.escape(plugin.to_s)
348
+ end
349
+
350
+ options[name] = Regexp.new(list.join("|"), Regexp::IGNORECASE)
351
+ end
352
+
353
+ UI.options = UI.options.merge(options)
455
354
  end
456
355
 
457
- # Filter certain patterns paths globally.
356
+ # Sets the default scope on startup
458
357
  #
459
- # @example Filter some files
460
- # ignore /\.txt$/, /.*\.zip/
358
+ # @example Scope Guard to a single group
359
+ # scope group: :frontend
461
360
  #
462
- # @param [Regexp] regexps a pattern for filtering paths
361
+ # @example Scope Guard to multiple groups
362
+ # scope groups: [:specs, :docs]
463
363
  #
464
- def filter(*regexps)
465
- ::Guard.listener = ::Guard.listener.filter(*regexps)
364
+ # @example Scope Guard to a single plugin
365
+ # scope plugin: :test
366
+ #
367
+ # @example Scope Guard to multiple plugins
368
+ # scope plugins: [:jasmine, :rspec]
369
+ #
370
+ # @param [Hash] scope the scope for the groups and plugins
371
+ #
372
+ def scope(scope = {})
373
+ # TODO: use a Guardfile::Results class
374
+ Guard.state.session.guardfile_scope(scope)
375
+ end
376
+
377
+ def evaluate(contents, filename, lineno) # :nodoc
378
+ instance_eval(contents, filename.to_s, lineno)
379
+ rescue StandardError, ScriptError => e
380
+ prefix = "\n\t(dsl)> "
381
+ cleaned_backtrace = _cleanup_backtrace(e.backtrace)
382
+ backtrace = "#{prefix}#{cleaned_backtrace.join(prefix)}"
383
+ msg = "Invalid Guardfile, original error is: \n\n%s, \nbacktrace: %s"
384
+ raise Error, format(msg, e, backtrace)
466
385
  end
467
386
 
387
+ # Sets the directories to pass to Listen
388
+ #
389
+ # @example watch only given directories
390
+ # directories %w(lib specs)
391
+ #
392
+ # @param [Array] directories directories for Listen to watch
393
+ #
394
+ def directories(directories)
395
+ directories.each do |dir|
396
+ fail "Directory #{dir.inspect} does not exist!" unless Dir.exist?(dir)
397
+ end
398
+ Guard.state.session.watchdirs = directories
399
+ end
400
+
401
+ # Sets Guard to clear the screen before every task is run
402
+ #
403
+ # @example switching clearing the screen on
404
+ # clearing(:on)
405
+ #
406
+ # @param [Symbol] on ':on' to turn on, ':off' (default) to turn off
407
+ #
408
+ def clearing(on)
409
+ Guard.state.session.clearing(on == :on)
410
+ end
411
+
412
+ private
413
+
414
+ def _cleanup_backtrace(backtrace)
415
+ dirs = { File.realpath(Dir.pwd) => ".", }
416
+
417
+ gem_env = ENV["GEM_HOME"] || ""
418
+ dirs[gem_env] = "$GEM_HOME" unless gem_env.empty?
419
+
420
+ gem_paths = (ENV["GEM_PATH"] || "").split(File::PATH_SEPARATOR)
421
+ gem_paths.each_with_index do |path, index|
422
+ dirs[path] = "$GEM_PATH[#{index}]"
423
+ end
424
+
425
+ backtrace.dup.map do |raw_line|
426
+ path = nil
427
+ symlinked_path = raw_line.split(":").first
428
+ begin
429
+ path = raw_line.sub(symlinked_path, File.realpath(symlinked_path))
430
+ dirs.detect { |dir, name| path.sub!(File.realpath(dir), name) }
431
+ path
432
+ rescue Errno::ENOENT
433
+ path || symlinked_path
434
+ end
435
+ end
436
+ end
468
437
  end
469
438
  end