-
Notifications
You must be signed in to change notification settings - Fork 483
/
guard.rb
169 lines (134 loc) · 5.08 KB
/
guard.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
require "thread"
require "listen"
require "guard/config"
require "guard/deprecated/guard" unless Guard::Config.new.strict?
require "guard/internals/helpers"
require "guard/internals/debugging"
require "guard/internals/traps"
require "guard/internals/queue"
# TODO: remove this class altogether
require "guard/interactor"
# Guard is the main module for all Guard related modules and classes.
# Also Guard plugins should use this namespace.
module Guard
Deprecated::Guard.add_deprecated(self) unless Config.new.strict?
class << self
attr_reader :state
attr_reader :queue
attr_reader :listener
attr_reader :interactor
# @private api
include Internals::Helpers
# Initializes the Guard singleton:
#
# * Initialize the internal Guard state;
# * Create the interactor
# * Select and initialize the file change listener.
#
# @option options [Boolean] clear if auto clear the UI should be done
# @option options [Boolean] notify if system notifications should be shown
# @option options [Boolean] debug if debug output should be shown
# @option options [Array<String>] group the list of groups to start
# @option options [Array<String>] watchdir the directories to watch
# @option options [String] guardfile the path to the Guardfile
#
# @return [Guard] the Guard singleton
def setup(cmdline_options = {})
init(cmdline_options)
@queue = Internals::Queue.new(Guard)
_evaluate(state.session.evaluator_options)
# NOTE: this should be *after* evaluate so :directories can work
# TODO: move listener setup to session?
@listener = Listen.send(*state.session.listener_args, &_listener_callback)
ignores = state.session.guardfile_ignore
@listener.ignore(ignores) unless ignores.empty?
ignores = state.session.guardfile_ignore_bang
@listener.ignore!(ignores) unless ignores.empty?
Notifier.connect(state.session.notify_options)
traps = Internals::Traps
traps.handle("USR1") { async_queue_add([:guard_pause, :paused]) }
traps.handle("USR2") { async_queue_add([:guard_pause, :unpaused]) }
@interactor = Interactor.new(state.session.interactor_name == :sleep)
traps.handle("INT") { @interactor.handle_interrupt }
self
end
def init(cmdline_options)
@state = Internals::State.new(cmdline_options)
end
# Asynchronously trigger changes
#
# Currently supported args:
#
# @example Old style hash:
# async_queue_add(modified: ['foo'], added: ['bar'], removed: [])
#
# @example New style signals with args:
# async_queue_add([:guard_pause, :unpaused ])
#
def async_queue_add(changes)
@queue << changes
# Putting interactor in background puts guard into foreground
# so it can handle change notifications
Thread.new { interactor.background }
end
private
# Check if any of the changes are actually watched for
# TODO: why iterate twice? reuse this info when running tasks
def _relevant_changes?(changes)
# TODO: no coverage!
files = changes.values.flatten(1)
scope = Guard.state.scope
watchers = scope.grouped_plugins.map do |_group, plugins|
plugins.map(&:watchers).flatten
end.flatten
watchers.any? { |watcher| files.any? { |file| watcher.match(file) } }
end
def _relative_pathnames(paths)
paths.map { |path| _relative_pathname(path) }
end
def _listener_callback
lambda do |modified, added, removed|
relative_paths = {
modified: _relative_pathnames(modified),
added: _relative_pathnames(added),
removed: _relative_pathnames(removed)
}
_guardfile_deprecated_check(relative_paths[:modified])
async_queue_add(relative_paths) if _relevant_changes?(relative_paths)
end
end
# TODO: obsoleted? (move to Dsl?)
def _pluginless_guardfile?
state.session.plugins.all.empty?
end
def _evaluate(options)
evaluator = Guardfile::Evaluator.new(options)
evaluator.evaluate
UI.reset_and_clear
msg = "No plugins found in Guardfile, please add at least one."
UI.error msg if _pluginless_guardfile?
if evaluator.inline?
UI.info("Using inline Guardfile.")
elsif evaluator.custom?
UI.info("Using Guardfile at #{ evaluator.path }.")
end
rescue Guardfile::Evaluator::NoPluginsError => e
UI.error(e.message)
end
# TODO: remove at some point
# TODO: not tested because collides with ongoing refactoring
def _guardfile_deprecated_check(modified)
modified.map!(&:to_s)
regexp = %r{^(?:.+/)?Guardfile$}
guardfiles = modified.select { |path| regexp.match(path) }
return if guardfiles.empty?
guardfile = Pathname("Guardfile").realpath
real_guardfiles = guardfiles.detect do |path|
/^Guardfile$/.match(path) || Pathname(path).expand_path == guardfile
end
return unless real_guardfiles
UI.warning "Guardfile changed -- _guard-core will exit.\n"
exit 2 # nonzero to break any while loop
end
end
end