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
|