guard 2.7.2 → 2.7.3

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.
@@ -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