guard 2.9.0 → 2.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,13 +5,12 @@ module Guard
5
5
  module Jobs
6
6
  class Sleep < Base
7
7
  def foreground
8
- # TODO: cleanup the namespace
9
- ::Guard::UI.debug "Guards jobs done. Sleeping..."
8
+ UI.debug "Guards jobs done. Sleeping..."
10
9
  sleep
11
- ::Guard::UI.debug "Sleep interrupted by events."
10
+ UI.debug "Sleep interrupted by events."
12
11
  :stopped
13
12
  rescue Interrupt
14
- ::Guard::UI.debug "Sleep interrupted by user."
13
+ UI.debug "Sleep interrupted by user."
15
14
  :exit
16
15
  end
17
16
 
@@ -85,6 +85,8 @@ module Guard
85
85
 
86
86
  return unless enabled? && options[:notify]
87
87
 
88
+ @detected.detect
89
+
88
90
  turn_on
89
91
  rescue Detected::NoneAvailableError => e
90
92
  ::Guard::UI.info e.to_s
@@ -0,0 +1,83 @@
1
+ require "guard/internals/environment"
2
+
3
+ require_relative "../notifiers/emacs"
4
+ require_relative "../notifiers/file_notifier"
5
+ require_relative "../notifiers/gntp"
6
+ require_relative "../notifiers/growl"
7
+ require_relative "../notifiers/libnotify"
8
+ require_relative "../notifiers/notifysend"
9
+ require_relative "../notifiers/rb_notifu"
10
+ require_relative "../notifiers/terminal_notifier"
11
+ require_relative "../notifiers/terminal_title"
12
+ require_relative "../notifiers/tmux"
13
+
14
+ module Guard
15
+ module Notifier
16
+ # @private api
17
+ class Detected
18
+ NO_SUPPORTED_NOTIFIERS = "Guard could not detect any of the supported" +
19
+ " notification libraries."
20
+
21
+ class NoneAvailableError < RuntimeError
22
+ end
23
+
24
+ def initialize(supported)
25
+ @supported = supported
26
+ @environment = Internals::Environment.new("GUARD").tap do |env|
27
+ env.create_method(:notifiers=) { |data| YAML::dump(data) }
28
+ env.create_method(:notifiers) { |data| data ? YAML::load(data) : [] }
29
+ end
30
+ end
31
+
32
+ def reset
33
+ @environment.notifiers = nil
34
+ end
35
+
36
+ def detect
37
+ return unless _data.empty?
38
+ @supported.each do |group|
39
+ group.detect { |name, _| add(name, silent: true) }
40
+ end
41
+
42
+ fail NoneAvailableError, NO_SUPPORTED_NOTIFIERS if _data.empty?
43
+ end
44
+
45
+ def available
46
+ _data.map { |entry| [_to_module(entry[:name]), entry[:options]] }
47
+ end
48
+
49
+ def add(name, opts)
50
+ klass = _to_module(name)
51
+ return false unless klass
52
+
53
+ all = @environment.notifiers
54
+
55
+ # Silently skip if it's already available, because otherwise
56
+ # we'd have to do :turn_off, then configure, then :turn_on
57
+ names = all.map(&:first).map(&:last)
58
+ unless names.include?(name)
59
+ return false unless klass.available?(opts)
60
+ @environment.notifiers = all << { name: name, options: opts }
61
+ true
62
+ end
63
+
64
+ # Just overwrite the options (without turning the notifier off or on),
65
+ # so those options will be passed in next calls to notify()
66
+ all.each { |item| item[:options] = opts if item[:name] == name }
67
+ true
68
+ end
69
+
70
+ def _to_module(name)
71
+ @supported.each do |group|
72
+ next unless (notifier = group.detect { |n, _| n == name })
73
+ return notifier.last
74
+ end
75
+ nil
76
+ end
77
+
78
+ def _data
79
+ @environment.notifiers || []
80
+ end
81
+ end
82
+ end
83
+ end
@@ -1,5 +1,3 @@
1
- require "guard/plugin/base"
2
-
3
1
  module Guard
4
2
  # Base class from which every Guard plugin implementation must inherit.
5
3
  #
@@ -19,8 +17,6 @@ module Guard
19
17
  # your Guard plugin method was not successful, and successive Guard plugin
20
18
  # tasks will be aborted when the group has set the `:halt_on_fail` option.
21
19
  #
22
- # @see Guard::Base
23
- # @see Guard::Hooker
24
20
  # @see Guard::Group
25
21
  #
26
22
  # @example Throw :task_has_failed
@@ -43,7 +39,234 @@ module Guard
43
39
  # plugins.
44
40
  #
45
41
  class Plugin
46
- include Base
42
+ TEMPLATE_FORMAT = "%s/lib/guard/%s/templates/Guardfile"
43
+
44
+ require "guard/ui"
45
+
46
+ # Get all callbacks registered for all Guard plugins present in the
47
+ # Guardfile.
48
+ #
49
+ def self.callbacks
50
+ @callbacks ||= Hash.new { |hash, key| hash[key] = [] }
51
+ end
52
+
53
+ # Add a callback.
54
+ #
55
+ # @param [Block] listener the listener to notify
56
+ # @param [Guard::Plugin] guard_plugin the Guard plugin to add the callback
57
+ # @param [Array<Symbol>] events the events to register
58
+ #
59
+ def self.add_callback(listener, guard_plugin, events)
60
+ Array(events).each do |event|
61
+ callbacks[[guard_plugin, event]] << listener
62
+ end
63
+ end
64
+
65
+ # Notify a callback.
66
+ #
67
+ # @param [Guard::Plugin] guard_plugin the Guard plugin to add the callback
68
+ # @param [Symbol] event the event to trigger
69
+ # @param [Array] args the arguments for the listener
70
+ #
71
+ def self.notify(guard_plugin, event, *args)
72
+ callbacks[[guard_plugin, event]].each do |listener|
73
+ listener.call(guard_plugin, event, *args)
74
+ end
75
+ end
76
+
77
+ # Reset all callbacks.
78
+ #
79
+ # TODO: remove (not used anywhere)
80
+ def self.reset_callbacks!
81
+ @callbacks = nil
82
+ end
83
+
84
+ # When event is a Symbol, {#hook} will generate a hook name
85
+ # by concatenating the method name from where {#hook} is called
86
+ # with the given Symbol.
87
+ #
88
+ # @example Add a hook with a Symbol
89
+ #
90
+ # def run_all
91
+ # hook :foo
92
+ # end
93
+ #
94
+ # Here, when {Guard::Plugin::Base#run_all} is called, {#hook} will notify
95
+ # callbacks registered for the "run_all_foo" event.
96
+ #
97
+ # When event is a String, {#hook} will directly turn the String
98
+ # into a Symbol.
99
+ #
100
+ # @example Add a hook with a String
101
+ #
102
+ # def run_all
103
+ # hook "foo_bar"
104
+ # end
105
+ #
106
+ # When {Guard::Plugin::Base#run_all} is called, {#hook} will notify
107
+ # callbacks registered for the "foo_bar" event.
108
+ #
109
+ # @param [Symbol, String] event the name of the Guard event
110
+ # @param [Array] args the parameters are passed as is to the callbacks
111
+ # registered for the given event.
112
+ #
113
+ def hook(event, *args)
114
+ hook_name = if event.is_a? Symbol
115
+ calling_method = caller[0][/`([^']*)'/, 1]
116
+ "#{ calling_method }_#{ event }"
117
+ else
118
+ event
119
+ end
120
+
121
+ UI.debug "Hook :#{ hook_name } executed for #{ self.class }"
122
+
123
+ self.class.notify(self, hook_name.to_sym, *args)
124
+ end
125
+
126
+ attr_accessor :group, :watchers, :callbacks, :options
127
+
128
+ # Returns the non-namespaced class name of the plugin
129
+ #
130
+ #
131
+ # @example Non-namespaced class name for Guard::RSpec
132
+ # Guard::RSpec.non_namespaced_classname
133
+ # #=> "RSpec"
134
+ #
135
+ # @return [String]
136
+ #
137
+ def self.non_namespaced_classname
138
+ to_s.sub("Guard::", "")
139
+ end
140
+
141
+ # Returns the non-namespaced name of the plugin
142
+ #
143
+ #
144
+ # @example Non-namespaced name for Guard::RSpec
145
+ # Guard::RSpec.non_namespaced_name
146
+ # #=> "rspec"
147
+ #
148
+ # @return [String]
149
+ #
150
+ def self.non_namespaced_name
151
+ non_namespaced_classname.downcase
152
+ end
153
+
154
+ # Specify the source for the Guardfile template.
155
+ # Each Guard plugin can redefine this method to add its own logic.
156
+ #
157
+ # @param [String] plugin_location the plugin location
158
+ #
159
+ def self.template(plugin_location)
160
+ File.read TEMPLATE_FORMAT % [plugin_location, non_namespaced_name]
161
+ end
162
+
163
+ # Called once when Guard starts. Please override initialize method to
164
+ # init stuff.
165
+ #
166
+ # @raise [:task_has_failed] when start has failed
167
+ # @return [Object] the task result
168
+ #
169
+ # @!method start
170
+
171
+ # Called when `stop|quit|exit|s|q|e + enter` is pressed (when Guard
172
+ # quits).
173
+ #
174
+ # @raise [:task_has_failed] when stop has failed
175
+ # @return [Object] the task result
176
+ #
177
+ # @!method stop
178
+
179
+ # Called when `reload|r|z + enter` is pressed.
180
+ # This method should be mainly used for "reload" (really!) actions like
181
+ # reloading passenger/spork/bundler/...
182
+ #
183
+ # @raise [:task_has_failed] when reload has failed
184
+ # @return [Object] the task result
185
+ #
186
+ # @!method reload
187
+
188
+ # Called when just `enter` is pressed
189
+ # This method should be principally used for long action like running all
190
+ # specs/tests/...
191
+ #
192
+ # @raise [:task_has_failed] when run_all has failed
193
+ # @return [Object] the task result
194
+ #
195
+ # @!method run_all
196
+
197
+ # Default behaviour on file(s) changes that the Guard plugin watches.
198
+ #
199
+ # @param [Array<String>] paths the changes files or paths
200
+ # @raise [:task_has_failed] when run_on_changes has failed
201
+ # @return [Object] the task result
202
+ #
203
+ # @!method run_on_changes(paths)
204
+
205
+ # Called on file(s) additions that the Guard plugin watches.
206
+ #
207
+ # @param [Array<String>] paths the changes files or paths
208
+ # @raise [:task_has_failed] when run_on_additions has failed
209
+ # @return [Object] the task result
210
+ #
211
+ # @!method run_on_additions(paths)
212
+
213
+ # Called on file(s) modifications that the Guard plugin watches.
214
+ #
215
+ # @param [Array<String>] paths the changes files or paths
216
+ # @raise [:task_has_failed] when run_on_modifications has failed
217
+ # @return [Object] the task result
218
+ #
219
+ # @!method run_on_modifications(paths)
220
+
221
+ # Called on file(s) removals that the Guard plugin watches.
222
+ #
223
+ # @param [Array<String>] paths the changes files or paths
224
+ # @raise [:task_has_failed] when run_on_removals has failed
225
+ # @return [Object] the task result
226
+ #
227
+ # @!method run_on_removals(paths)
228
+
229
+ # Returns the plugin's name (without "guard-").
230
+ #
231
+ # @example Name for Guard::RSpec
232
+ # Guard::RSpec.new.name
233
+ # #=> "rspec"
234
+ #
235
+ # @return [String]
236
+ #
237
+ def name
238
+ @name ||= self.class.non_namespaced_name
239
+ end
240
+
241
+ # Returns the plugin's class name without the Guard:: namespace.
242
+ #
243
+ # @example Title for Guard::RSpec
244
+ # Guard::RSpec.new.title
245
+ # #=> "RSpec"
246
+ #
247
+ # @return [String]
248
+ #
249
+ def title
250
+ @title ||= self.class.non_namespaced_classname
251
+ end
252
+
253
+ # String representation of the plugin.
254
+ #
255
+ # @example String representation of an instance of the Guard::RSpec plugin
256
+ #
257
+ # Guard::RSpec.new.title
258
+ # #=> "#<Guard::RSpec @name=rspec @group=#<Guard::Group @name=default
259
+ # @options={}> @watchers=[] @callbacks=[] @options={all_after_pass:
260
+ # true}>"
261
+ #
262
+ # @return [String] the string representation
263
+ #
264
+ def to_s
265
+ "#<#{self.class} @name=#{name} @group=#{group} @watchers=#{watchers}"\
266
+ " @callbacks=#{callbacks} @options=#{options}>"
267
+ end
268
+
269
+ private
47
270
 
48
271
  # Initializes a Guard plugin.
49
272
  # Don't do any work here, especially as Guard plugins get initialized even
@@ -57,8 +280,21 @@ module Guard
57
280
  # a watcher
58
281
  #
59
282
  def initialize(options = {})
60
- _set_instance_variables_from_options(options)
283
+ group_name = options.delete(:group) { :default }
284
+ @group = Guard.state.session.groups.add(group_name)
285
+ @watchers = options.delete(:watchers) { [] }
286
+ @callbacks = options.delete(:callbacks) { [] }
287
+ @options = options
61
288
  _register_callbacks
62
289
  end
290
+
291
+ # Add all the Guard::Plugin's callbacks to the global @callbacks array
292
+ # that's used by Guard to know which callbacks to notify.
293
+ #
294
+ def _register_callbacks
295
+ callbacks.each do |callback|
296
+ Hooker.add_callback(callback[:listener], self, callback[:events])
297
+ end
298
+ end
63
299
  end
64
300
  end
@@ -96,21 +96,19 @@ module Guard
96
96
  def plugin_class(options = {})
97
97
  options = { fail_gracefully: false }.merge(options)
98
98
 
99
- try_require = false
99
+ const = _plugin_constant
100
+ fail TypeError, "no constant: #{_constant_name}" unless const
101
+ @plugin_class ||= Guard.const_get(const)
102
+
103
+ rescue TypeError
100
104
  begin
101
- if try_require
102
- require "guard/#{name.downcase}"
103
- end
105
+ require "guard/#{ name.downcase }"
106
+ const = _plugin_constant
107
+ @plugin_class ||= Guard.const_get(const)
104
108
 
105
- @plugin_class ||= ::Guard.const_get(_plugin_constant)
106
109
  rescue TypeError => error
107
- if try_require
108
- ::Guard::UI.error "Could not find class Guard::#{ _constant_name }"
109
- ::Guard::UI.error error.backtrace.join("\n")
110
- else
111
- try_require = true
112
- retry
113
- end
110
+ UI.error "Could not find class Guard::#{ _constant_name }"
111
+ UI.error error.backtrace.join("\n")
114
112
  rescue LoadError => error
115
113
  unless options[:fail_gracefully]
116
114
  UI.error ERROR_NO_GUARD_OR_CLASS % [name.downcase, _constant_name]
@@ -123,18 +121,21 @@ module Guard
123
121
  # Adds a plugin's template to the Guardfile.
124
122
  #
125
123
  def add_to_guardfile
124
+ klass = plugin_class # call here to avoid failing later
125
+
126
126
  require "guard/guardfile/evaluator"
127
- # TODO: move this method to Generator?
128
- evaluator = Guard::Guardfile::Evaluator.new
129
- evaluator.evaluate_guardfile
127
+ # TODO: move this to Generator?
128
+ options = Guard.state.session.evaluator_options
129
+ evaluator = Guardfile::Evaluator.new(options)
130
+ evaluator.evaluate
130
131
  if evaluator.guardfile_include?(name)
131
- ::Guard::UI.info "Guardfile already includes #{ name } guard"
132
+ UI.info "Guardfile already includes #{ name } guard"
132
133
  else
133
134
  content = File.read("Guardfile")
134
135
  File.open("Guardfile", "wb") do |f|
135
136
  f.puts(content)
136
137
  f.puts("")
137
- f.puts(plugin_class.template(plugin_location))
138
+ f.puts(klass.template(plugin_location))
138
139
  end
139
140
 
140
141
  UI.info INFO_ADDED_GUARD_TO_GUARDFILE % name
@@ -150,7 +151,7 @@ module Guard
150
151
  # => Guard::RSpec
151
152
  #
152
153
  def _plugin_constant
153
- @_plugin_constant ||= ::Guard.constants.detect do |c|
154
+ @_plugin_constant ||= Guard.constants.detect do |c|
154
155
  c.to_s.downcase == _constant_name.downcase
155
156
  end
156
157
  end