guard 2.7.1 → 2.7.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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