guard 2.7.1 → 2.7.2

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.
@@ -1,90 +0,0 @@
1
- require "guard/ui"
2
-
3
- require "guard/jobs/sleep"
4
- require "guard/jobs/pry_wrapper"
5
- require "guard/jobs/stdin"
6
-
7
- module Guard
8
- class Interactor
9
- # Initializes the interactor. This configures
10
- # Pry and creates some custom commands and aliases
11
- # for Guard.
12
- #
13
- def initialize(no_interaction = false)
14
- @interactive = !no_interaction && self.class.enabled?
15
-
16
-
17
- # TODO: restore
18
- # job_klass = interactive? ? Jobs::PryWrapper : Jobs::Sleep
19
- job_klass = Jobs::StdinJob
20
-
21
- @idle_job = job_klass.new(self.class.options)
22
- end
23
-
24
- def interactive?
25
- @interactive
26
- end
27
-
28
- # Run in foreground and wait until interrupted or closed
29
- def foreground
30
- idle_job.foreground
31
- end
32
-
33
- # Remove interactor so other tasks can run in foreground
34
- def background
35
- idle_job.background
36
- end
37
-
38
- def handle_interrupt
39
- idle_job.handle_interrupt
40
- end
41
-
42
- class << self
43
- def options
44
- @options ||= {}
45
- end
46
-
47
- # Pass options to interactor's job when it's created
48
- attr_writer :options
49
-
50
- # TODO: allow custom user idle jobs, e.g. [:pry, :sleep, :exit, ...]
51
- def enabled?
52
- @enabled || @enabled.nil?
53
- end
54
-
55
- alias_method :enabled, :enabled?
56
-
57
- # TODO: handle switching interactors during runtime?
58
- attr_writer :enabled
59
-
60
- # Converts and validates a plain text scope
61
- # to a valid plugin or group scope.
62
- #
63
- # @param [Array<String>] entries the text scope
64
- # @return [Hash, Array<String>] the plugin or group scope, the unknown
65
- # entries
66
- #
67
- # TODO: call this from within action, not within interactor command
68
- def convert_scope(entries)
69
- scopes = { plugins: [], groups: [] }
70
- unknown = []
71
-
72
- entries.each do |entry|
73
- if plugin = ::Guard.plugin(entry)
74
- scopes[:plugins] << plugin
75
- elsif group = ::Guard.group(entry)
76
- scopes[:groups] << group
77
- else
78
- unknown << entry
79
- end
80
- end
81
-
82
- [scopes, unknown]
83
- end
84
- end
85
-
86
- private
87
-
88
- attr_reader :idle_job
89
- end
90
- end
@@ -1,46 +0,0 @@
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
@@ -1,103 +0,0 @@
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
@@ -1,384 +0,0 @@
1
- require "thread"
2
- require "listen"
3
- require "guard/options"
4
-
5
- module Guard
6
- # Sets up initial variables and options
7
- module Setuper
8
- DEFAULT_OPTIONS = {
9
- clear: false,
10
- notify: true,
11
- debug: false,
12
- group: [],
13
- plugin: [],
14
- watchdir: nil,
15
- guardfile: nil,
16
- no_interactions: false,
17
- no_bundler_warning: false,
18
- show_deprecations: false,
19
- latency: nil,
20
- force_polling: false,
21
- wait_for_delay: nil,
22
- listen_on: nil
23
- }
24
- DEFAULT_GROUPS = [:default, :common]
25
-
26
- # Initializes the Guard singleton:
27
- #
28
- # * Initialize the internal Guard state;
29
- # * Create the interactor
30
- # * Select and initialize the file change listener.
31
- #
32
- # @option options [Boolean] clear if auto clear the UI should be done
33
- # @option options [Boolean] notify if system notifications should be shown
34
- # @option options [Boolean] debug if debug output should be shown
35
- # @option options [Array<String>] group the list of groups to start
36
- # @option options [Array<String>] watchdir the directories to watch
37
- # @option options [String] guardfile the path to the Guardfile
38
- #
39
- # @return [Guard] the Guard singleton
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
46
- def setup(opts = {})
47
- reset_options(opts)
48
- reset_evaluator(opts)
49
-
50
- @queue = Queue.new
51
- @runner = ::Guard::Runner.new
52
- @watchdirs = _setup_watchdirs
53
-
54
- ::Guard::UI.clear(force: true)
55
-
56
- _setup_debug if options[:debug]
57
- @listener = _setup_listener
58
- _setup_signal_traps
59
-
60
- _load_guardfile
61
- @interactor = _setup_interactor
62
- self
63
- end
64
-
65
- attr_reader :options, :interactor
66
-
67
- # Used only by tests (for all I know...)
68
- def clear_options
69
- @options = nil
70
- end
71
-
72
- # Initializes the groups array with the default group(s).
73
- #
74
- # @see DEFAULT_GROUPS
75
- #
76
- def reset_groups
77
- @groups = DEFAULT_GROUPS.map { |name| Group.new(name) }
78
- end
79
-
80
- # Initializes the plugins array to an empty array.
81
- #
82
- # @see Guard.plugins
83
- #
84
- def reset_plugins
85
- @plugins = []
86
- end
87
-
88
- # Initializes the scope hash to `{ groups: [], plugins: [] }`.
89
- #
90
- # @see Guard.setup_scope
91
- #
92
- def reset_scope
93
- # calls Guard.scope=() to set the instance variable directly, as opposed
94
- # to Guard.scope()
95
- ::Guard.scope = { groups: [], plugins: [] }
96
- end
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
-
108
- def save_scope
109
- # This actually replaces scope from command line,
110
- # so scope set by 'scope' Pry command will be reset
111
- @saved_scope = _prepare_scope(::Guard.scope)
112
- end
113
-
114
- def restore_scope
115
- ::Guard.setup_scope(@saved_scope)
116
- end
117
-
118
- attr_reader :watchdirs
119
-
120
- # Stores the scopes defined by the user via the `--group` / `-g` option (to
121
- # run only a specific group) or the `--plugin` / `-P` option (to run only a
122
- # specific plugin).
123
- #
124
- # @see CLI#start
125
- # @see Dsl#scope
126
- #
127
- def setup_scope(scope = {})
128
- # TODO: there should be a special Scope class instead
129
- scope = _prepare_scope(scope)
130
-
131
- ::Guard.scope = {
132
- groups: scope[:groups].map { |item| ::Guard.add_group(item) },
133
- plugins: scope[:plugins].map { |item| ::Guard.plugin(item) },
134
- }
135
- end
136
-
137
- # Evaluates the Guardfile content. It displays an error message if no
138
- # Guard plugins are instantiated after the Guardfile evaluation.
139
- #
140
- # @see Guard::Guardfile::Evaluator#evaluate_guardfile
141
- #
142
- def evaluate_guardfile
143
- evaluator.evaluate_guardfile
144
- msg = "No plugins found in Guardfile, please add at least one."
145
- ::Guard::UI.error msg unless _pluginless_guardfile?
146
- end
147
-
148
- # Asynchronously trigger changes
149
- #
150
- # Currently supported args:
151
- #
152
- # old style hash: {modified: ['foo'], added: ['bar'], removed: []}
153
- #
154
- # new style signals with args: [:guard_pause, :unpaused ]
155
- #
156
- def async_queue_add(changes)
157
- @queue << changes
158
-
159
- # Putting interactor in background puts guard into foreground
160
- # so it can handle change notifications
161
- Thread.new { interactor.background }
162
- end
163
-
164
- def pending_changes?
165
- ! @queue.empty?
166
- end
167
-
168
- def add_builtin_plugins(guardfile)
169
- return unless guardfile
170
-
171
- pattern = _relative_pathname(guardfile).to_s
172
- watcher = ::Guard::Watcher.new(pattern)
173
- ::Guard.add_plugin(:reevaluator, watchers: [watcher], group: :common)
174
- end
175
-
176
- private
177
-
178
- # Sets up various debug behaviors:
179
- #
180
- # * Abort threads on exception;
181
- # * Set the logging level to `:debug`;
182
- # * Modify the system and ` methods to log themselves before being executed
183
- #
184
- # @see #_debug_command_execution
185
- #
186
- def _setup_debug
187
- Thread.abort_on_exception = true
188
- ::Guard::UI.options[:level] = :debug
189
- _debug_command_execution
190
- end
191
-
192
- # Initializes the listener and registers a callback for changes.
193
- #
194
- def _setup_listener
195
- if options[:listen_on]
196
- Listen.on(options[:listen_on], &_listener_callback)
197
- else
198
- listener_options = {}
199
- [:latency, :force_polling, :wait_for_delay].each do |option|
200
- listener_options[option] = options[option] if options[option]
201
- end
202
- listen_args = watchdirs + [listener_options]
203
- Listen.to(*listen_args, &_listener_callback)
204
- end
205
- end
206
-
207
- # Process the change queue, running tasks within the main Guard thread
208
- def _process_queue
209
- actions, changes = [], { modified: [], added: [], removed: [] }
210
-
211
- while pending_changes?
212
- if (item = @queue.pop).first.is_a?(Symbol)
213
- actions << item
214
- else
215
- item.each { |key, value| changes[key] += value }
216
- end
217
- end
218
-
219
- _run_actions(actions)
220
- runner.run_on_changes(*changes.values)
221
- end
222
-
223
- # Sets up traps to catch signals used to control Guard.
224
- #
225
- # Currently two signals are caught:
226
- # - `USR1` which pauses listening to changes.
227
- # - `USR2` which resumes listening to changes.
228
- # - 'INT' which is delegated to Pry if active, otherwise stops Guard.
229
- #
230
- def _setup_signal_traps
231
- return if defined?(JRUBY_VERSION)
232
-
233
- if Signal.list.keys.include?("USR1")
234
- Signal.trap("USR1") { async_queue_add([:guard_pause, :paused]) }
235
- end
236
-
237
- if Signal.list.keys.include?("USR2")
238
- Signal.trap("USR2") { async_queue_add([:guard_pause, :unpaused]) }
239
- end
240
-
241
- return unless Signal.list.keys.include?("INT")
242
- Signal.trap("INT") { interactor.handle_interrupt }
243
- end
244
-
245
- # Enables or disables the notifier based on user's configurations.
246
- #
247
- def _setup_notifier
248
- if options[:notify] && ENV["GUARD_NOTIFY"] != "false"
249
- ::Guard::Notifier.turn_on
250
- else
251
- ::Guard::Notifier.turn_off
252
- end
253
- end
254
-
255
- # Adds a command logger in debug mode. This wraps common command
256
- # execution functions and logs the executed command before execution.
257
- #
258
- def _debug_command_execution
259
- Kernel.send(:alias_method, :original_system, :system)
260
- Kernel.send(:define_method, :system) do |command, *args|
261
- ::Guard::UI.debug "Command execution: #{ command } #{ args.join(" ") }"
262
- Kernel.send :original_system, command, *args
263
- end
264
-
265
- Kernel.send(:alias_method, :original_backtick, :'`')
266
- Kernel.send(:define_method, :'`') do |command|
267
- ::Guard::UI.debug "Command execution: #{ command }"
268
- Kernel.send :original_backtick, command
269
- end
270
- end
271
-
272
- # TODO: Guard::Watch or Guard::Scope should provide this
273
- def _scoped_watchers
274
- watchers = []
275
- runner.send(:_scoped_plugins) { |guard| watchers += guard.watchers }
276
- watchers
277
- end
278
-
279
- # Check if any of the changes are actually watched for
280
- def _relevant_changes?(changes)
281
- files = changes.values.flatten(1)
282
- watchers = _scoped_watchers
283
- watchers.any? { |watcher| files.any? { |file| watcher.match(file) } }
284
- end
285
-
286
- def _relative_pathname(path)
287
- full_path = Pathname(path)
288
- full_path.relative_path_from(Pathname.pwd)
289
- rescue ArgumentError
290
- full_path
291
- end
292
-
293
- def _relative_pathnames(paths)
294
- paths.map { |path| _relative_pathname(path) }
295
- end
296
-
297
- def _run_actions(actions)
298
- actions.each do |action_args|
299
- args = action_args.dup
300
- namespaced_action = args.shift
301
- action = namespaced_action.to_s.sub(/^guard_/, "")
302
- if ::Guard.respond_to?(action)
303
- ::Guard.send(action, *args)
304
- else
305
- fail "Unknown action: #{action.inspect}"
306
- end
307
- end
308
- end
309
-
310
- def _setup_watchdirs
311
- dirs = Array(options[:watchdir])
312
- dirs.empty? ? [Dir.pwd] : dirs.map { |dir| File.expand_path dir }
313
- end
314
-
315
- def _listener_callback
316
- lambda do |modified, added, removed|
317
- relative_paths = {
318
- modified: _relative_pathnames(modified),
319
- added: _relative_pathnames(added),
320
- removed: _relative_pathnames(removed)
321
- }
322
-
323
- async_queue_add(relative_paths) if _relevant_changes?(relative_paths)
324
- end
325
- end
326
-
327
- def _reset_all
328
- reset_groups
329
- reset_plugins
330
- reset_scope
331
- end
332
-
333
- def _setup_interactor
334
- ::Guard::Interactor.new(options[:no_interactions])
335
- end
336
-
337
- def _load_guardfile
338
- _reset_all
339
- evaluate_guardfile
340
- setup_scope
341
- _setup_notifier
342
- end
343
-
344
- def _prepare_scope(scope)
345
- fail "Guard::setup() not called!" if options.nil?
346
- plugins = Array(options[:plugin])
347
- plugins = Array(scope[:plugins] || scope[:plugin]) if plugins.empty?
348
-
349
- # Convert objects to names
350
- plugins.map! { |p| p.respond_to?(:name) ? p.name : p }
351
-
352
- groups = Array(options[:group])
353
- groups = Array(scope[:groups] || scope[:group]) if groups.empty?
354
-
355
- # Convert objects to names
356
- groups.map! { |g| g.respond_to?(:name) ? g.name : g }
357
-
358
- { plugins: plugins, groups: groups }
359
- end
360
-
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
-
369
- plugins.map(&:name) != ["reevaluator"]
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
383
- end
384
- end