Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
173 lines (138 sloc) 5.404 kb
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)
guardfiles = modified.select { |path| /^(?:.+\/)?Guardfile$/.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
if real_guardfiles
UI.warning "Guardfile changed -- _guard-core will exit.\n"
exit 2 # nonzero to break any while loop
else # e.g. templates/Guardfile
msg = "Config changed: %s - Guard will exit so it can be restarted."
UI.info format(msg, guardfiles.inspect)
exit 0 # 0 so any shell while loop can continue
end
end
end
end
Jump to Line
Something went wrong with that request. Please try again.