guard 2.7.2 → 2.7.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,42 @@
1
+ require "guard/plugin/base"
2
+
3
+ module Guard
4
+ # @deprecated Inheriting from `Guard::Guard` is deprecated, please inherit
5
+ # from {Plugin} instead. Please note that the constructor signature has
6
+ # changed from `Guard::Guard#initialize(watchers = [], options = {})` to
7
+ # `Guard::Plugin#initialize(options = {})`.
8
+ #
9
+ # @see https://github.com/guard/guard/wiki/Upgrading-to-Guard-2.0 How to
10
+ # upgrade for Guard 2.0
11
+ #
12
+ class Guard
13
+ include ::Guard::Plugin::Base
14
+
15
+ # @deprecated Inheriting from `Guard::Guard` is deprecated, please inherit
16
+ # from {Plugin} instead. Please note that the constructor signature
17
+ # has changed from `Guard::Guard#initialize(watchers = [], options = {})`
18
+ # to `Guard::Plugin#initialize(options = {})`.
19
+ #
20
+ # Initializes a Guard plugin. Don't do any work here,
21
+ # especially as Guard plugins get initialized even if they are not in an
22
+ # active group!
23
+ #
24
+ # @see https://github.com/guard/guard/wiki/Upgrading-to-Guard-2.0 How to
25
+ # upgrade for Guard 2.0
26
+ #
27
+ # @param [Array<Guard::Watcher>] watchers the Guard plugin file watchers
28
+ # @param [Hash] options the custom Guard plugin options
29
+ # @option options [Symbol] group the group this Guard plugin belongs to
30
+ # @option options [Boolean] any_return allow any object to be returned from
31
+ # a watcher
32
+ # @option options [Boolean] first_match stop after the first watcher that
33
+ # returns a valid result
34
+ #
35
+ def initialize(watchers = [], options = {})
36
+ UI.deprecation(Deprecator::GUARD_GUARD_DEPRECATION % title)
37
+
38
+ _set_instance_variables_from_options(options.merge(watchers: watchers))
39
+ _register_callbacks
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,43 @@
1
+ require "guard/guardfile/evaluator"
2
+ require "guard/guardfile/generator"
3
+ require "guard/ui"
4
+
5
+ module Guard
6
+ # @deprecated Use instance methods of {Guardfile::Evaluator} and
7
+ # {Guardfile::Generator} instead.
8
+ #
9
+ # @see Guardfile::Evaluator
10
+ # @see Guardfile::Generator
11
+ #
12
+ module Guardfile
13
+ # @deprecated Use {Guardfile::Generator#create_guardfile} instead.
14
+ #
15
+ # @see https://github.com/guard/guard/wiki/Upgrading-to-Guard-2.0 How to
16
+ # upgrade for Guard 2.0
17
+ #
18
+ def self.create_guardfile(options = {})
19
+ UI.deprecation(Deprecator::CREATE_GUARDFILE_DEPRECATION)
20
+ Generator.new(options).create_guardfile
21
+ end
22
+
23
+ # @deprecated Use {Guardfile::Generator#initialize_template} instead.
24
+ #
25
+ # @see https://github.com/guard/guard/wiki/Upgrading-to-Guard-2.0 How to
26
+ # upgrade for Guard 2.0
27
+ #
28
+ def self.initialize_template(plugin_name)
29
+ UI.deprecation(Deprecator::INITIALIZE_TEMPLATE_DEPRECATION)
30
+ Generator.new.initialize_template(plugin_name)
31
+ end
32
+
33
+ # @deprecated Use {Guardfile::Generator#initialize_all_templates} instead.
34
+ #
35
+ # @see https://github.com/guard/guard/wiki/Upgrading-to-Guard-2.0 How to
36
+ # upgrade for Guard 2.0
37
+ #
38
+ def self.initialize_all_templates
39
+ UI.deprecation(Deprecator::INITIALIZE_ALL_TEMPLATES_DEPRECATION)
40
+ Generator.new.initialize_all_templates
41
+ end
42
+ end
43
+ end
@@ -22,6 +22,7 @@ module Guard
22
22
  # content of a valid Guardfile
23
23
  #
24
24
  def initialize(opts = {})
25
+ @evaluated = false
25
26
  @source = nil
26
27
  @guardfile_path = nil
27
28
 
@@ -81,8 +82,8 @@ module Guard
81
82
  # @return [Boolean] whether the Guard plugin has been declared
82
83
  #
83
84
  def guardfile_include?(plugin_name)
84
- regexp = /^guard\s*\(?\s*['":]#{ plugin_name }['"]?/
85
- _guardfile_contents_without_user_config.match(regexp)
85
+ /^\s*guard\s*\(?\s*['":]#{ plugin_name }['"]?/.
86
+ match _guardfile_contents_without_user_config
86
87
  end
87
88
 
88
89
  # Gets the content of the `Guardfile` concatenated with the global
@@ -106,6 +107,7 @@ module Guard
106
107
  # @return [String] the Guardfile content
107
108
  #
108
109
  def _guardfile_contents_without_user_config
110
+ fail "BUG: no data - Guardfile wasn't evaluated" unless @evaluated
109
111
  @guardfile_contents || ""
110
112
  end
111
113
 
@@ -124,6 +126,7 @@ module Guard
124
126
  #
125
127
  def _fetch_guardfile_contents
126
128
  _use_inline || _use_provided || _use_default
129
+ @evaluated = true
127
130
 
128
131
  return if _guardfile_contents_usable?
129
132
  ::Guard::UI.error "No Guard plugins found in Guardfile,"\
@@ -0,0 +1,275 @@
1
+ require "guard/options"
2
+ require "guard/plugin"
3
+
4
+ module Guard
5
+ module Guardfile
6
+ # This class is responsible for evaluating the Guardfile. It delegates to
7
+ # Guard::Dsl for the actual objects generation from the Guardfile content.
8
+ #
9
+ # @see Guard::Dsl
10
+ #
11
+ class Evaluator
12
+ attr_reader :options, :guardfile_path
13
+
14
+ def guardfile_source
15
+ @source
16
+ end
17
+
18
+ # Initializes a new Guard::Guardfile::Evaluator object.
19
+ #
20
+ # @option opts [String] guardfile the path to a valid Guardfile
21
+ # @option opts [String] guardfile_contents a string representing the
22
+ # content of a valid Guardfile
23
+ #
24
+ def initialize(opts = {})
25
+ @source = nil
26
+ @guardfile_path = nil
27
+
28
+ valid_options = opts.select do |k, _|
29
+ [:guardfile, :guardfile_contents].include?(k.to_sym)
30
+ end
31
+
32
+ @options = ::Guard::Options.new(valid_options)
33
+ end
34
+
35
+ # Evaluates the DSL methods in the `Guardfile`.
36
+ #
37
+ # @example Programmatically evaluate a Guardfile
38
+ # Guard::Guardfile::Evaluator.new.evaluate_guardfile
39
+ #
40
+ # @example Programmatically evaluate a Guardfile with a custom Guardfile
41
+ # path
42
+ #
43
+ # options = { guardfile: '/Users/guardfile/MyAwesomeGuardfile' }
44
+ # Guard::Guardfile::Evaluator.new(options).evaluate_guardfile
45
+ #
46
+ # @example Programmatically evaluate a Guardfile with an inline Guardfile
47
+ #
48
+ # options = { guardfile_contents: 'guard :rspec' }
49
+ # Guard::Guardfile::Evaluator.new(options).evaluate_guardfile
50
+ #
51
+ def evaluate_guardfile
52
+ _fetch_guardfile_contents
53
+ _instance_eval_guardfile(guardfile_contents)
54
+ ::Guard.add_builtin_plugins(guardfile_path)
55
+ end
56
+
57
+ # Re-evaluates the `Guardfile` to update
58
+ # the current Guard configuration.
59
+ #
60
+ def reevaluate_guardfile
61
+ # Don't re-evaluate inline Guardfile
62
+ return if @source == :inline
63
+
64
+ _before_reevaluate_guardfile
65
+ evaluate_guardfile
66
+ _after_reevaluate_guardfile
67
+ end
68
+
69
+ # Tests if the current `Guardfile` contains a specific Guard plugin.
70
+ #
71
+ # @example Programmatically test if a Guardfile contains a specific Guard
72
+ # plugin
73
+ #
74
+ # File.read('Guardfile')
75
+ # => "guard :rspec"
76
+ #
77
+ # Guard::Guardfile::Evaluator.new.guardfile_include?('rspec)
78
+ # => true
79
+ #
80
+ # @param [String] plugin_name the name of the Guard
81
+ # @return [Boolean] whether the Guard plugin has been declared
82
+ #
83
+ def guardfile_include?(plugin_name)
84
+ regexp = /^guard\s*\(?\s*['":]#{ plugin_name }['"]?/
85
+ _guardfile_contents_without_user_config.match(regexp)
86
+ end
87
+
88
+ # Gets the content of the `Guardfile` concatenated with the global
89
+ # user configuration file.
90
+ #
91
+ # @example Programmatically get the content of the current Guardfile
92
+ # Guard::Guardfile::Evaluator.new.guardfile_contents
93
+ # => "guard :rspec"
94
+ #
95
+ # @return [String] the Guardfile content
96
+ #
97
+ def guardfile_contents
98
+ config = File.read(_user_config_path) if File.exist?(_user_config_path)
99
+ [_guardfile_contents_without_user_config, config].compact.join("\n")
100
+ end
101
+
102
+ private
103
+
104
+ # Gets the content of the `Guardfile`.
105
+ #
106
+ # @return [String] the Guardfile content
107
+ #
108
+ def _guardfile_contents_without_user_config
109
+ @guardfile_contents || ""
110
+ end
111
+
112
+ # Evaluates the content of the `Guardfile`.
113
+ #
114
+ # @param [String] contents the content to evaluate.
115
+ #
116
+ def _instance_eval_guardfile(contents)
117
+ ::Guard::Dsl.new.instance_eval(contents, @guardfile_path || "", 1)
118
+ rescue => ex
119
+ ::Guard::UI.error "Invalid Guardfile, original error is:\n#{ $! }"
120
+ raise ex
121
+ end
122
+
123
+ # Gets the content to evaluate and stores it into @guardfile_contents.
124
+ #
125
+ def _fetch_guardfile_contents
126
+ _use_inline || _use_provided || _use_default
127
+
128
+ return if _guardfile_contents_usable?
129
+ ::Guard::UI.error "No Guard plugins found in Guardfile,"\
130
+ " please add at least one."
131
+ end
132
+
133
+ # Use the provided inline Guardfile if provided.
134
+ #
135
+ def _use_inline
136
+ source_from_option = @source.nil? && options[:guardfile_contents]
137
+ inline = @source == :inline
138
+
139
+ return false unless (source_from_option) || inline
140
+
141
+ @source = :inline
142
+ @guardfile_contents = options[:guardfile_contents]
143
+
144
+ ::Guard::UI.info "Using inline Guardfile."
145
+ true
146
+ end
147
+
148
+ # Try to use the provided Guardfile. Exits Guard if the Guardfile cannot
149
+ # be found.
150
+ #
151
+ def _use_provided
152
+ source_from_file = @source.nil? && options[:guardfile]
153
+ return false unless source_from_file || (@source == :custom)
154
+
155
+ @source = :custom
156
+
157
+ options[:guardfile] = File.expand_path(options[:guardfile])
158
+ if File.exist?(options[:guardfile])
159
+ _read_guardfile(options[:guardfile])
160
+ ::Guard::UI.info "Using Guardfile at #{ options[:guardfile] }."
161
+ true
162
+ else
163
+ ::Guard::UI.error "No Guardfile exists at #{ options[:guardfile] }."
164
+ exit 1
165
+ end
166
+
167
+ true
168
+ end
169
+
170
+ # Try to use one of the default Guardfiles (local or home Guardfile).
171
+ # Exits Guard if no Guardfile is found.
172
+ #
173
+ def _use_default
174
+ if guardfile_path = _find_default_guardfile
175
+ @source = :default
176
+ _read_guardfile(guardfile_path)
177
+ else
178
+ ::Guard::UI.error \
179
+ "No Guardfile found, please create one with `guard init`."
180
+ exit 1
181
+ end
182
+ end
183
+
184
+ # Returns the first default Guardfile (either local or home Guardfile)
185
+ # or nil otherwise.
186
+ #
187
+ def _find_default_guardfile
188
+ [_local_guardfile_path, _home_guardfile_path].detect do |path|
189
+ File.exist?(path)
190
+ end
191
+ end
192
+
193
+ # Reads the current `Guardfile` content.
194
+ #
195
+ # @param [String] guardfile_path the path to the Guardfile
196
+ #
197
+ def _read_guardfile(guardfile_path)
198
+ @guardfile_path = guardfile_path
199
+ @guardfile_contents = File.read(guardfile_path)
200
+ rescue => ex
201
+ ::Guard::UI.error "Error reading file #{ guardfile_path }:"
202
+ ::Guard::UI.error ex.inspect
203
+ ::Guard::UI.error ex.backtrace
204
+ exit 1
205
+ end
206
+
207
+ # Stops Guard and clear internal state
208
+ # before the Guardfile will be re-evaluated.
209
+ #
210
+ def _before_reevaluate_guardfile
211
+ ::Guard.runner.run(:stop)
212
+ ::Guard.reset_groups
213
+ ::Guard.reset_plugins
214
+ ::Guard.reset_scope
215
+ ::Guard::Notifier.clear_notifiers
216
+ end
217
+
218
+ # Starts Guard and notification and show a message
219
+ # after the Guardfile has been re-evaluated.
220
+ #
221
+ def _after_reevaluate_guardfile
222
+ ::Guard::Notifier.turn_on if ::Guard::Notifier.enabled?
223
+
224
+ if !::Guard.send(:_pluginless_guardfile?)
225
+ ::Guard::Notifier.notify(
226
+ "No plugins found in Guardfile, please add at least one.",
227
+ title: "Guard re-evaluate",
228
+ image: :failed)
229
+ else
230
+ msg = "Guardfile has been re-evaluated."
231
+ ::Guard::UI.info(msg)
232
+ ::Guard::Notifier.notify(msg, title: "Guard re-evaluate")
233
+
234
+ ::Guard.setup_scope
235
+ ::Guard.runner.run(:start)
236
+ end
237
+ end
238
+
239
+ # Tests if the current `Guardfile` content is usable.
240
+ #
241
+ # @return [Boolean] if the Guardfile is usable
242
+ #
243
+ def _guardfile_contents_usable?
244
+ guardfile_contents && guardfile_contents =~ /guard/m
245
+ end
246
+
247
+ # The path to the `Guardfile` that is located at
248
+ # the directory, where Guard has been started from.
249
+ #
250
+ # @return [String] the path to the local Guardfile
251
+ #
252
+ def _local_guardfile_path
253
+ File.expand_path(File.join(Dir.pwd, "Guardfile"))
254
+ end
255
+
256
+ # The path to the `.Guardfile` that is located at
257
+ # the users home directory.
258
+ #
259
+ # @return [String] the path to `~/.Guardfile`
260
+ #
261
+ def _home_guardfile_path
262
+ File.expand_path(File.join("~", ".Guardfile"))
263
+ end
264
+
265
+ # The path to the user configuration `.guard.rb`
266
+ # that is located at the users home directory.
267
+ #
268
+ # @return [String] the path to `~/.guard.rb`
269
+ #
270
+ def _user_config_path
271
+ File.expand_path(File.join("~", ".guard.rb"))
272
+ end
273
+ end
274
+ end
275
+ end
@@ -0,0 +1,221 @@
1
+ require "rbconfig"
2
+ require "guard/ui"
3
+
4
+ module Guard
5
+ module Notifier
6
+ # Base class for all notifiers.
7
+ #
8
+ class Base
9
+ HOSTS = {
10
+ darwin: "Mac OS X",
11
+ linux: "Linux",
12
+ freebsd: "FreeBSD",
13
+ openbsd: "OpenBSD",
14
+ sunos: "SunOS",
15
+ solaris: "Solaris",
16
+ mswin: "Windows",
17
+ mingw: "Windows",
18
+ cygwin: "Windows"
19
+ }
20
+
21
+ ERROR_ADD_GEM_AND_RUN_BUNDLE = "Please add \"gem '%s'\" to your Gemfile "\
22
+ "and run Guard with \"bundle exec\"."
23
+
24
+ attr_reader :options
25
+
26
+ def initialize(opts = {})
27
+ @options = opts
28
+ end
29
+
30
+ # This method should be overriden by subclasses and return an array of
31
+ # OSes the notifier supports. By default, it returns :all which mean
32
+ # there's no check against the current OS.
33
+ #
34
+ # @see HOSTS for the list of possible OSes
35
+ #
36
+ def self.supported_hosts
37
+ :all
38
+ end
39
+
40
+ # Test if the notifier can be used.
41
+ #
42
+ # @param [Hash] opts notifier options
43
+ # @option opts [Boolean] silent true if no error messages should be shown
44
+ # @return [Boolean] the availability status
45
+ #
46
+ def self.available?(opts = {})
47
+ if _supported_host?
48
+ true
49
+ else
50
+ hosts = supported_hosts.map { |host| HOSTS[host.to_sym] }.join(", ")
51
+ unless opts.fetch(:silent) { false }
52
+ ::Guard::UI.error "The :#{name} notifier runs only on #{hosts}."
53
+ end
54
+ false
55
+ end
56
+ end
57
+
58
+ # This method must be overriden.
59
+ #
60
+ def notify(_message, opts = {})
61
+ options.delete(:silent)
62
+ opts.replace(options.merge(opts))
63
+ normalize_standard_options!(opts)
64
+ end
65
+
66
+ # Returns the title of the notifier.
67
+ #
68
+ # @example Un-modulize the class name
69
+ # Guard::Notifier::FileNotifier.title
70
+ # #=> 'FileNotifier'
71
+ #
72
+ # @return [String] the title of the notifier
73
+ #
74
+ def self.title
75
+ to_s.sub(/.+::(\w+)$/, '\1')
76
+ end
77
+
78
+ # Returns the name of the notifier.
79
+ #
80
+ # @example Un-modulize, underscorize and downcase the class name
81
+ # Guard::Notifier::FileNotifier.name
82
+ # #=> 'file_notifier'
83
+ #
84
+ # @return [String] the name of the notifier
85
+ #
86
+ def self.name
87
+ title.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
88
+ end
89
+
90
+ # Returns the name of the notifier's gem. By default it returns the
91
+ # notifier name. This method can be overriden by subclasses.
92
+ #
93
+ # @example Un-modulize, underscorize and downcase the class name
94
+ # Guard::Notifier::FileNotifier.gem_name
95
+ # #=> 'file_notifier'
96
+ #
97
+ # @return [String] the name of the notifier's gem
98
+ #
99
+ def self.gem_name
100
+ name
101
+ end
102
+
103
+ # This method tries to require the gem whose name is returned by
104
+ # `.gem_name`. If a LoadError or NameError occurs, it displays an error
105
+ # message (unless opts[:silent] is true) and returns false.
106
+ #
107
+ # @param [Hash] opts some options
108
+ # @option opts [Boolean] silent true if no error messages should be shown
109
+ #
110
+ # @return [Boolean] whether or not the gem is loaded
111
+ #
112
+ def self.require_gem_safely(opts = {})
113
+ require gem_name
114
+ true
115
+ rescue LoadError, NameError
116
+ unless opts[:silent]
117
+ UI.error ERROR_ADD_GEM_AND_RUN_BUNDLE % [gem_name]
118
+ end
119
+ false
120
+ end
121
+
122
+ # Returns the title of the notifier.
123
+ #
124
+ # @example Un-modulize the class name
125
+ # Guard::Notifier::FileNotifier.new.title
126
+ # #=> 'FileNotifier'
127
+ #
128
+ # @return [String] the title of the notifier
129
+ #
130
+ def title
131
+ self.class.title
132
+ end
133
+
134
+ # Returns the name of the notifier.
135
+ #
136
+ # @example Un-modulize, underscorize and downcase the class name
137
+ # Guard::Notifier::FileNotifier.new.name
138
+ # #=> 'file_notifier'
139
+ #
140
+ # @return [String] the name of the notifier
141
+ #
142
+ def name
143
+ self.class.name
144
+ end
145
+
146
+ # Paths where all Guard images are located
147
+ #
148
+ # @return [Pathname] the path to the images directory
149
+ #
150
+ def images_path
151
+ @images_path ||= Pathname.new(__FILE__).dirname + "../../../images"
152
+ end
153
+
154
+ # @private
155
+ #
156
+ # Checks if the current OS is supported by the notifier.
157
+ #
158
+ # @see .supported_hosts
159
+ #
160
+ def self._supported_host?
161
+ supported_hosts == :all ||
162
+ RbConfig::CONFIG["host_os"] =~ /#{supported_hosts.join('|')}/
163
+ end
164
+
165
+ # Set or modify the `:title`, `:type` and `:image` options for a
166
+ # notification. Should be used in `#notify`.
167
+ #
168
+ # @param [Hash] opts additional notification library options
169
+ # @option opts [String] type the notification type. Either 'success',
170
+ # 'pending', 'failed' or 'notify'
171
+ # @option opts [String] title the notification title
172
+ # @option opts [String] image the path to the notification image
173
+ #
174
+ def normalize_standard_options!(opts)
175
+ opts[:title] ||= "Guard"
176
+ opts[:type] ||= _notification_type(opts.fetch(:image, :success))
177
+ opts[:image] = _image_path(opts.delete(:image) { :success })
178
+ end
179
+
180
+ private
181
+
182
+ # Get the image path for an image symbol for the following
183
+ # known image types:
184
+ #
185
+ # - failed
186
+ # - pending
187
+ # - success
188
+ #
189
+ # If the image is not a known symbol, it will be returned unmodified.
190
+ #
191
+ # @param [Symbol, String] image the image symbol or path to an image
192
+ #
193
+ # @return [String] the image path
194
+ #
195
+ def _image_path(image)
196
+ case image
197
+ when :failed, :pending, :success
198
+ images_path.join("#{image}.png").to_s
199
+ else
200
+ image
201
+ end
202
+ end
203
+
204
+ # Get the notification type depending on the
205
+ # image that has been selected for the notification.
206
+ #
207
+ # @param [Symbol, String] image the image symbol or path to an image
208
+ #
209
+ # @return [String] the notification type
210
+ #
211
+ def _notification_type(image)
212
+ case image
213
+ when :failed, :pending, :success
214
+ image
215
+ else
216
+ :notify
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end