guard 2.7.0 → 2.7.1

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.
@@ -2,6 +2,7 @@ require "guard/ui"
2
2
 
3
3
  require "guard/jobs/sleep"
4
4
  require "guard/jobs/pry_wrapper"
5
+ require "guard/jobs/stdin"
5
6
 
6
7
  module Guard
7
8
  class Interactor
@@ -12,7 +13,11 @@ module Guard
12
13
  def initialize(no_interaction = false)
13
14
  @interactive = !no_interaction && self.class.enabled?
14
15
 
15
- job_klass = interactive? ? Jobs::PryWrapper : Jobs::Sleep
16
+
17
+ # TODO: restore
18
+ # job_klass = interactive? ? Jobs::PryWrapper : Jobs::Sleep
19
+ job_klass = Jobs::StdinJob
20
+
16
21
  @idle_job = job_klass.new(self.class.options)
17
22
  end
18
23
 
@@ -74,6 +74,7 @@ module Guard
74
74
  _start_pry
75
75
  @thread.join
76
76
  thread = @thread
77
+ # TODO: rename :stopped to continue
77
78
  thread.nil? ? :stopped : :exit
78
79
  ensure
79
80
  ::Guard::UI.reset_line
@@ -0,0 +1,46 @@
1
+ require "guard/jobs/base"
2
+
3
+ module Guard
4
+ module Jobs
5
+ class StdinJob
6
+ def initialize(options)
7
+ @mode = :stopped
8
+ @sleeping = false
9
+ end
10
+
11
+ def foreground
12
+ output "Guard is idle" # needed for child-process cucumber tests
13
+
14
+ line = $stdin.readline.chomp
15
+ return :exit if line == "exit"
16
+
17
+ m = /^sleep (?<seconds>\d+)$/.match(line)
18
+ return @mode unless m
19
+
20
+ seconds = Integer(m[:seconds][/\d+/])
21
+ @sleeping = true
22
+ sleep seconds
23
+ @sleeping = false
24
+ @mode
25
+ rescue EOFError, Interrupt
26
+ @sleeping = false
27
+ :exit
28
+ end
29
+
30
+ def background
31
+ Thread.main.wakeup if @sleeping
32
+ end
33
+
34
+ def handle_interrupt
35
+ @mode = :exit
36
+ end
37
+
38
+ private
39
+
40
+ def output(text)
41
+ $stdout.puts text
42
+ $stdout.flush
43
+ end
44
+ end
45
+ end
46
+ end
@@ -18,18 +18,13 @@ module Guard
18
18
  }
19
19
 
20
20
  def self.available?(opts = {})
21
- super && _emacs_client_available?(opts)
22
- end
21
+ return false unless super
23
22
 
24
- # @private
25
- #
26
- # @return [Boolean] whether or not the emacs client is available
27
- #
28
- def self._emacs_client_available?(opts)
29
23
  client_name = opts.fetch(:client, DEFAULTS[:client])
30
24
  cmd = "#{client_name} --eval '1' 2> #{DEV_NULL} || echo 'N/A'"
31
25
  stdout = Sheller.stdout(cmd)
32
- !%w(N/A 'N/A').include?(stdout.chomp!)
26
+ return false if stdout.nil?
27
+ !%w(N/A 'N/A').include?(stdout.chomp)
33
28
  end
34
29
 
35
30
  # Shows a system notification.
@@ -0,0 +1,103 @@
1
+ require "guard/notifiers/base"
2
+
3
+ module Guard
4
+ module Notifier
5
+ # Send a notification to Emacs with emacsclient
6
+ # (http://www.emacswiki.org/emacs/EmacsClient).
7
+ #
8
+ # @example Add the `:emacs` notifier to your `Guardfile`
9
+ # notification :emacs
10
+ #
11
+ class Emacs < Base
12
+ DEFAULTS = {
13
+ client: "emacsclient",
14
+ success: "ForestGreen",
15
+ failed: "Firebrick",
16
+ default: "Black",
17
+ fontcolor: "White",
18
+ }
19
+
20
+ def self.available?(opts = {})
21
+ super && _emacs_client_available?(opts)
22
+ end
23
+
24
+ # @private
25
+ #
26
+ # @return [Boolean] whether or not the emacs client is available
27
+ #
28
+ def self._emacs_client_available?(opts)
29
+ client_name = opts.fetch(:client, DEFAULTS[:client])
30
+ cmd = "#{client_name} --eval '1' 2> #{DEV_NULL} || echo 'N/A'"
31
+ stdout = Sheller.stdout(cmd)
32
+ !%w(N/A 'N/A').include?(stdout.chomp!)
33
+ end
34
+
35
+ # Shows a system notification.
36
+ #
37
+ # @param [String] type the notification type. Either 'success',
38
+ # 'pending', 'failed' or 'notify'
39
+ # @param [String] title the notification title
40
+ # @param [String] message the notification message body
41
+ # @param [String] image the path to the notification image
42
+ # @param [Hash] opts additional notification library options
43
+ # @option opts [String] success the color to use for success
44
+ # notifications (default is 'ForestGreen')
45
+ # @option opts [String] failed the color to use for failure
46
+ # notifications (default is 'Firebrick')
47
+ # @option opts [String] pending the color to use for pending
48
+ # notifications
49
+ # @option opts [String] default the default color to use (default is
50
+ # 'Black')
51
+ # @option opts [String] client the client to use for notification
52
+ # (default is 'emacsclient')
53
+ # @option opts [String, Integer] priority specify an int or named key
54
+ # (default is 0)
55
+ #
56
+ def notify(message, opts = {})
57
+ super
58
+
59
+ opts = DEFAULTS.merge(opts)
60
+ color = emacs_color(opts[:type], opts)
61
+ fontcolor = emacs_color(:fontcolor, opts)
62
+ elisp = <<-EOF.gsub(/\s+/, " ").strip
63
+ (set-face-attribute 'mode-line nil
64
+ :background "#{color}"
65
+ :foreground "#{fontcolor}")
66
+ EOF
67
+
68
+ _run_cmd(opts[:client], "--eval", elisp)
69
+ end
70
+
71
+ # Get the Emacs color for the notification type.
72
+ # You can configure your own color by overwrite the defaults.
73
+ #
74
+ # @param [String] type the notification type
75
+ # @param [Hash] options aditional notification options
76
+ #
77
+ # @option options [String] success the color to use for success
78
+ # notifications (default is 'ForestGreen')
79
+ #
80
+ # @option options [String] failed the color to use for failure
81
+ # notifications (default is 'Firebrick')
82
+ #
83
+ # @option options [String] pending the color to use for pending
84
+ # notifications
85
+ #
86
+ # @option options [String] default the default color to use (default is
87
+ # 'Black')
88
+ #
89
+ # @return [String] the name of the emacs color
90
+ #
91
+ def emacs_color(type, options = {})
92
+ default = options.fetch(:default, DEFAULTS[:default])
93
+ options.fetch(type.to_sym, default)
94
+ end
95
+
96
+ private
97
+
98
+ def _run_cmd(cmd, *args)
99
+ Sheller.run(cmd, *args)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -69,10 +69,12 @@ module Guard
69
69
  # https://github.com/guard/guard/wiki/Upgrading-to-Guard-2.0
70
70
  #
71
71
  def initialize_plugin(options)
72
- if plugin_class.superclass.to_s == "Guard::Guard"
73
- plugin_class.new(options.delete(:watchers), options)
72
+ klass = plugin_class
73
+ fail "Could not load class: #{_constant_name.inspect}" unless klass
74
+ if klass.superclass.to_s == "Guard::Guard"
75
+ klass.new(options.delete(:watchers), options)
74
76
  else
75
- plugin_class.new(options)
77
+ klass.new(options)
76
78
  end
77
79
  end
78
80
 
data/lib/guard/runner.rb CHANGED
@@ -14,12 +14,10 @@ module Guard
14
14
  # @param [Hash] scopes either the Guard plugin or the group to run the task
15
15
  # on
16
16
  #
17
- # @see self.run_supervised_task
18
- #
19
- def run(task, scope = {})
17
+ def run(task, scope_hash = {})
20
18
  Lumberjack.unit_of_work do
21
- _scoped_plugins(scope) do |guard|
22
- run_supervised_task(guard, task) if guard.respond_to?(task)
19
+ _scoped_plugins(scope_hash || {}) do |plugin|
20
+ _supervise(plugin, task) if plugin.respond_to?(task)
23
21
  end
24
22
  end
25
23
  end
@@ -47,15 +45,16 @@ module Guard
47
45
 
48
46
  ::Guard::UI.clearable
49
47
 
50
- _scoped_plugins do |guard|
48
+ _scoped_plugins do |plugin|
51
49
  ::Guard::UI.clear
52
50
 
53
51
  types.each do |tasks, unmatched_paths|
54
- paths = ::Guard::Watcher.match_files(guard, unmatched_paths)
55
- next if paths.empty?
52
+ next if unmatched_paths.empty?
53
+ match_result = ::Guard::Watcher.match_files(plugin, unmatched_paths)
54
+ next if match_result.empty?
56
55
 
57
- next unless (task = tasks.detect { |meth| guard.respond_to?(meth) })
58
- run_supervised_task(guard, task, paths)
56
+ next unless (task = tasks.detect { |meth| plugin.respond_to?(meth) })
57
+ _supervise(plugin, task, match_result)
59
58
  end
60
59
  end
61
60
  end
@@ -71,24 +70,24 @@ module Guard
71
70
  # @param [Array] args the arguments for the task
72
71
  # @raise [:task_has_failed] when task has failed
73
72
  #
74
- def run_supervised_task(guard, task, *args)
75
- catch self.class.stopping_symbol_for(guard) do
76
- guard.hook("#{ task }_begin", *args)
73
+ def _supervise(plugin, task, *args)
74
+ catch self.class.stopping_symbol_for(plugin) do
75
+ plugin.hook("#{ task }_begin", *args)
77
76
  begin
78
- result = guard.send(task, *args)
77
+ result = plugin.send(task, *args)
79
78
  rescue Interrupt
80
79
  throw(:task_has_failed)
81
80
  end
82
- guard.hook("#{ task }_end", result)
81
+ plugin.hook("#{ task }_end", result)
83
82
  result
84
83
  end
85
84
  rescue ScriptError, StandardError, RuntimeError
86
- ::Guard::UI.error("#{ guard.class.name } failed to achieve its"\
85
+ ::Guard::UI.error("#{ plugin.class.name } failed to achieve its"\
87
86
  " <#{ task }>, exception was:" \
88
87
  "\n#{ $!.class }: #{ $!.message }" \
89
88
  "\n#{ $!.backtrace.join("\n") }")
90
- ::Guard.plugins.delete guard
91
- ::Guard::UI.info("\n#{ guard.class.name } has just been fired")
89
+ ::Guard.remove_plugin(plugin)
90
+ ::Guard::UI.info("\n#{ plugin.class.name } has just been fired")
92
91
  $!
93
92
  end
94
93
 
@@ -121,12 +120,14 @@ module Guard
121
120
  # @yield the task to run
122
121
  #
123
122
  def _scoped_plugins(scopes = {})
123
+ fail "NO PLUGIN SCOPE" if scopes.nil?
124
124
  if plugins = _current_plugins_scope(scopes)
125
125
  plugins.each do |guard|
126
126
  yield(guard)
127
127
  end
128
128
  else
129
129
  _current_groups_scope(scopes).each do |group|
130
+ fail "Invalid group: #{group.inspect}" unless group.respond_to?(:name)
130
131
  current_plugin = nil
131
132
  block_return = catch :task_has_failed do
132
133
  ::Guard.plugins(group: group.name).each do |guard|
@@ -151,13 +152,9 @@ module Guard
151
152
  # @return [Array<Guard::Plugin>] the plugins to scope to
152
153
  #
153
154
  def _current_plugins_scope(scope)
154
- if plugins = _find_non_empty_plugins_scope(scope)
155
- Array(plugins).map do |plugin|
156
- plugin.is_a?(Symbol) ? ::Guard.plugin(plugin) : plugin
157
- end
158
- else
159
- nil
160
- end
155
+ return nil unless (plugins = _find_non_empty_plugins_scope(scope))
156
+
157
+ Array(plugins).map { |plugin| _instantiate(:plugin, plugin) }
161
158
  end
162
159
 
163
160
  # Returns the current groups scope.
@@ -168,9 +165,14 @@ module Guard
168
165
  # @return [Array<Guard::Group>] the groups to scope to
169
166
  #
170
167
  def _current_groups_scope(scope)
171
- Array(_find_non_empty_groups_scope(scope)).map do |group|
172
- group.is_a?(Symbol) ? ::Guard.group(group) : group
173
- end
168
+ found = _find_non_empty_groups_scope(scope)
169
+ groups = Array(found).map { |group| _instantiate(:group, group) }
170
+ return groups if groups.any? { |g| g.name == :common }
171
+ ([_instantiate(:group, :common)] + Array(found)).compact
172
+ end
173
+
174
+ def _instantiate(meth, obj)
175
+ (obj.is_a?(Symbol) || obj.is_a?(String)) ? ::Guard.send(meth, obj) : obj
174
176
  end
175
177
 
176
178
  # Find the first non empty element in the given possibilities
@@ -178,16 +180,17 @@ module Guard
178
180
  def _find_non_empty_scope(type, local_scope, *additional_possibilities)
179
181
  found = [
180
182
  local_scope[:"#{type}s"],
181
- local_scope[type.to_sym],
183
+ [local_scope[type.to_sym]],
182
184
  ::Guard.scope[:"#{type}s"],
183
185
  additional_possibilities.flatten
184
- ].compact.detect { |a| !Array(a).empty? }
185
- found ? [::Guard.group(:common)] + Array(found) : found
186
+ ]
187
+ found.compact.detect { |a| !Array(a).compact.empty? }
186
188
  end
187
189
 
188
190
  # Find the first non empty plugins scope
189
191
  #
190
192
  def _find_non_empty_plugins_scope(scope)
193
+ fail "NO PLUGIN SCOPE" if scope.nil?
191
194
  _find_non_empty_scope(:plugin, scope)
192
195
  end
193
196
 
data/lib/guard/setuper.rb CHANGED
@@ -51,7 +51,7 @@ module Guard
51
51
  @runner = ::Guard::Runner.new
52
52
  @watchdirs = _setup_watchdirs
53
53
 
54
- ::Guard::UI.clear(force: true)
54
+ ::Guard::UI.setup(options)
55
55
 
56
56
  _setup_debug if options[:debug]
57
57
  @listener = _setup_listener
@@ -62,7 +62,7 @@ module Guard
62
62
  self
63
63
  end
64
64
 
65
- attr_reader :options, :evaluator, :interactor
65
+ attr_reader :options, :interactor
66
66
 
67
67
  # Used only by tests (for all I know...)
68
68
  def clear_options
@@ -127,12 +127,11 @@ module Guard
127
127
  def setup_scope(scope = {})
128
128
  # TODO: there should be a special Scope class instead
129
129
  scope = _prepare_scope(scope)
130
- { groups: :add_group, plugins: :plugin }.each do |type, meth|
131
- next unless scope[type].any?
132
- ::Guard.scope[type] = scope[type].map do |item|
133
- ::Guard.send(meth, item)
134
- end
135
- end
130
+
131
+ ::Guard.scope = {
132
+ groups: scope[:groups].map { |item| ::Guard.add_group(item) },
133
+ plugins: scope[:plugins].map { |item| ::Guard.plugin(item) },
134
+ }
136
135
  end
137
136
 
138
137
  # Evaluates the Guardfile content. It displays an error message if no
@@ -143,7 +142,7 @@ module Guard
143
142
  def evaluate_guardfile
144
143
  evaluator.evaluate_guardfile
145
144
  msg = "No plugins found in Guardfile, please add at least one."
146
- ::Guard::UI.error msg unless _non_builtin_plugins?
145
+ ::Guard::UI.error msg unless _pluginless_guardfile?
147
146
  end
148
147
 
149
148
  # Asynchronously trigger changes
@@ -343,16 +342,30 @@ module Guard
343
342
  end
344
343
 
345
344
  def _prepare_scope(scope)
345
+ fail "Guard::setup() not called!" if options.nil?
346
346
  plugins = Array(options[:plugin])
347
347
  plugins = Array(scope[:plugins] || scope[:plugin]) if plugins.empty?
348
348
 
349
+ # Convert objects to names
350
+ plugins.map! { |p| p.respond_to?(:name) ? p.name : p }
351
+
349
352
  groups = Array(options[:group])
350
353
  groups = Array(scope[:groups] || scope[:group]) if groups.empty?
351
354
 
355
+ # Convert objects to names
356
+ groups.map! { |g| g.respond_to?(:name) ? g.name : g }
357
+
352
358
  { plugins: plugins, groups: groups }
353
359
  end
354
360
 
355
- def _non_builtin_plugins?
361
+ def _pluginless_guardfile?
362
+ # no Reevaluator means there was no Guardfile configured that could be
363
+ # reevaluated, so we don't have a pluginless guardfile, because we don't
364
+ # have a Guardfile to begin with...
365
+ #
366
+ # But, if we have a Guardfile, we'll at least have the built-in
367
+ # Reevaluator, so the following will work:
368
+
356
369
  plugins.map(&:name) != ["reevaluator"]
357
370
  end
358
371
 
@@ -365,7 +378,7 @@ module Guard
365
378
  @watchdirs = nil
366
379
  @listener = nil
367
380
  @interactor = nil
368
- ::Guard.scope = nil
381
+ @scope = nil
369
382
  end
370
383
  end
371
384
  end
@@ -38,8 +38,18 @@ module Guard
38
38
  #
39
39
  # @return [Guard] the Guard singleton
40
40
  #
41
+
42
+ # TODO: this method has too many instance variables
43
+ # and some are mock and leak between tests,
44
+ # so ideally there should be a guard "instance"
45
+ # object that can be created anew between tests
41
46
  def setup(opts = {})
42
- _init_options(opts)
47
+ reset_options(opts)
48
+ reset_evaluator(opts)
49
+
50
+ @queue = Queue.new
51
+ @runner = ::Guard::Runner.new
52
+ @watchdirs = _setup_watchdirs
43
53
 
44
54
  ::Guard::UI.clear(force: true)
45
55
 
@@ -52,7 +62,7 @@ module Guard
52
62
  self
53
63
  end
54
64
 
55
- attr_reader :options, :evaluator, :interactor
65
+ attr_reader :options, :interactor
56
66
 
57
67
  # Used only by tests (for all I know...)
58
68
  def clear_options
@@ -85,6 +95,16 @@ module Guard
85
95
  ::Guard.scope = { groups: [], plugins: [] }
86
96
  end
87
97
 
98
+ # Used to merge CLI options with Setuper defaults
99
+ def reset_options(new_options)
100
+ @options = ::Guard::Options.new(new_options, DEFAULT_OPTIONS)
101
+ end
102
+
103
+ # TODO: code smell - too many reset_* methods
104
+ def reset_evaluator(new_options)
105
+ @evaluator = ::Guard::Guardfile::Evaluator.new(new_options)
106
+ end
107
+
88
108
  def save_scope
89
109
  # This actually replaces scope from command line,
90
110
  # so scope set by 'scope' Pry command will be reset
@@ -107,12 +127,11 @@ module Guard
107
127
  def setup_scope(scope = {})
108
128
  # TODO: there should be a special Scope class instead
109
129
  scope = _prepare_scope(scope)
110
- { groups: :add_group, plugins: :plugin }.each do |type, meth|
111
- next unless scope[type].any?
112
- ::Guard.scope[type] = scope[type].map do |item|
113
- ::Guard.send(meth, item)
114
- end
115
- end
130
+
131
+ ::Guard.scope = {
132
+ groups: scope[:groups].map { |item| ::Guard.add_group(item) },
133
+ plugins: scope[:plugins].map { |item| ::Guard.plugin(item) },
134
+ }
116
135
  end
117
136
 
118
137
  # Evaluates the Guardfile content. It displays an error message if no
@@ -123,7 +142,7 @@ module Guard
123
142
  def evaluate_guardfile
124
143
  evaluator.evaluate_guardfile
125
144
  msg = "No plugins found in Guardfile, please add at least one."
126
- ::Guard::UI.error msg unless _non_builtin_plugins?
145
+ ::Guard::UI.error msg unless _pluginless_guardfile?
127
146
  end
128
147
 
129
148
  # Asynchronously trigger changes
@@ -146,8 +165,7 @@ module Guard
146
165
  ! @queue.empty?
147
166
  end
148
167
 
149
- def add_builtin_plugins
150
- guardfile = ::Guard.evaluator.guardfile_path
168
+ def add_builtin_plugins(guardfile)
151
169
  return unless guardfile
152
170
 
153
171
  pattern = _relative_pathname(guardfile).to_s
@@ -306,14 +324,6 @@ module Guard
306
324
  end
307
325
  end
308
326
 
309
- def _init_options(opts)
310
- @queue = Queue.new
311
- @runner = ::Guard::Runner.new
312
- @evaluator = ::Guard::Guardfile::Evaluator.new(opts)
313
- @options = ::Guard::Options.new(opts, DEFAULT_OPTIONS)
314
- @watchdirs = _setup_watchdirs
315
- end
316
-
317
327
  def _reset_all
318
328
  reset_groups
319
329
  reset_plugins
@@ -332,17 +342,43 @@ module Guard
332
342
  end
333
343
 
334
344
  def _prepare_scope(scope)
345
+ fail "Guard::setup() not called!" if options.nil?
335
346
  plugins = Array(options[:plugin])
336
347
  plugins = Array(scope[:plugins] || scope[:plugin]) if plugins.empty?
337
348
 
349
+ # Convert objects to names
350
+ plugins.map! { |p| p.respond_to?(:name) ? p.name : p }
351
+
338
352
  groups = Array(options[:group])
339
353
  groups = Array(scope[:groups] || scope[:group]) if groups.empty?
340
354
 
355
+ # Convert objects to names
356
+ groups.map! { |g| g.respond_to?(:name) ? g.name : g }
357
+
341
358
  { plugins: plugins, groups: groups }
342
359
  end
343
360
 
344
- def _non_builtin_plugins?
361
+ def _pluginless_guardfile?
362
+ # no Reevaluator means there was no Guardfile configured that could be
363
+ # reevaluated, so we don't have a pluginless guardfile, because we don't
364
+ # have a Guardfile to begin with...
365
+ #
366
+ # But, if we have a Guardfile, we'll at least have the built-in
367
+ # Reevaluator, so the following will work:
368
+
345
369
  plugins.map(&:name) != ["reevaluator"]
346
370
  end
371
+
372
+ def _reset_for_tests
373
+ @options = nil
374
+ @queue = nil
375
+ @runner = nil
376
+ @evaluator = nil
377
+ @watchdirs = nil
378
+ @watchdirs = nil
379
+ @listener = nil
380
+ @interactor = nil
381
+ @scope = nil
382
+ end
347
383
  end
348
384
  end