Skip to content

Commit

Permalink
major rework everywhere
Browse files Browse the repository at this point in the history
  • Loading branch information
e2 committed Nov 27, 2014
1 parent b636019 commit 8976db6
Show file tree
Hide file tree
Showing 55 changed files with 1,693 additions and 1,878 deletions.
145 changes: 27 additions & 118 deletions lib/guard.rb
Expand Up @@ -8,9 +8,10 @@
require "guard/internals/traps"
require "guard/internals/helpers"

require "guard/metadata"
require "guard/options"
require "guard/internals/queue"
require "guard/internals/state"

require "guard/options"
require "guard/commander"
require "guard/dsl"
require "guard/group"
Expand All @@ -32,6 +33,9 @@ module Guard
class << self
attr_reader :listener

# @private api
attr_reader :queue

include Internals::Helpers

# Initializes the Guard singleton:
Expand All @@ -48,74 +52,37 @@ class << self
# @option options [String] guardfile the path to the Guardfile
#
# @return [Guard] the Guard singleton
#

# TODO: this method has too many instance variables
# and some are mock and leak between tests,
# so ideally there should be a guard "instance"
# object that can be created anew between tests
def setup(cmdline_options = {})
init(cmdline_options)

@queue = Queue.new
self.watchdirs = Array(options[:watchdir])
@queue = Internals::Queue.new(Guard)

UI.reset_and_clear

reset_plugins
reset_scope
_evaluate(state.session.evaluator_options)

_evaluate
setup_scope
# NOTE: this should be *after* evaluate so :directories can work
@listener = Listen.send(*state.session.listener_args, &_listener_callback)


@listener = _setup_listener

Notifier.connect(notify: options[:notify])
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(options[:no_interactions])
@interactor = Interactor.new(state.session.interactor_name == :sleep)
traps.handle("INT") { @interactor.handle_interrupt }

self
end

attr_reader :interactor

# Used only by tests (for all I know...)
def clear_options
@options = nil
end

# Initializes the plugins array to an empty array.
#
# @see Guard.plugins
#
def reset_plugins
@plugins = []
def init(cmdline_options)
@state = Internals::State.new(cmdline_options)
end

attr_reader :watchdirs
attr_reader :state

# Stores the scopes defined by the user via the `--group` / `-g` option (to
# run only a specific group) or the `--plugin` / `-P` option (to run only a
# specific plugin).
#
# @see CLI#start
# @see Dsl#scope
#
def setup_scope(new_scope = {})
# TODO: there should be a special Scope class instead
new_scope = _prepare_scope(new_scope)

Guard.scope = {
groups: new_scope[:groups].map { |item| add_group(item) },
plugins: new_scope[:plugins].map { |item| plugin(item) },
}
end
attr_reader :interactor

# Asynchronously trigger changes
#
Expand All @@ -133,80 +100,24 @@ def async_queue_add(changes)
Thread.new { interactor.background }
end

def pending_changes?
! @queue.empty?
end

def watchdirs=(dirs)
dirs = [Dir.pwd] if dirs.empty?
@watchdirs = dirs.map { |dir| File.expand_path dir }
end

private

# Initializes the listener and registers a callback for changes.
#
def _setup_listener
if options[:listen_on]
Listen.on(options[:listen_on], &_listener_callback)
else
listener_options = {}
[:latency, :force_polling, :wait_for_delay].each do |option|
listener_options[option] = options[option] if options[option]
end
listen_args = watchdirs + [listener_options]
Listen.to(*listen_args, &_listener_callback)
end
end

# Process the change queue, running tasks within the main Guard thread
def _process_queue
actions, changes = [], { modified: [], added: [], removed: [] }

while pending_changes?
if (item = @queue.pop).first.is_a?(Symbol)
actions << item
else
item.each { |key, value| changes[key] += value }
end
end

_run_actions(actions)
return if changes.values.all?(&:empty?)
Runner.new.run_on_changes(*changes.values)
end

# TODO: Guard::Watch or Guard::Scope should provide this
def _scoped_watchers
watchers = []
Runner.new.send(:_scoped_plugins) { |guard| watchers += guard.watchers }
watchers
end

# 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)
watchers = _scoped_watchers
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 _run_actions(actions)
actions.each do |action_args|
args = action_args.dup
namespaced_action = args.shift
action = namespaced_action.to_s.sub(/^guard_/, "")
if ::Guard.respond_to?(action)
::Guard.send(action, *args)
else
fail "Unknown action: #{action.inspect}"
end
end
end

def _listener_callback
lambda do |modified, added, removed|
relative_paths = {
Expand All @@ -219,7 +130,7 @@ def _listener_callback
end
end

# TODO: obsoleted? (by Dsl errors)
# TODO: obsoleted? (move to Dsl?)
def _pluginless_guardfile?
# no Reevaluator means there was no Guardfile configured that could be
# reevaluated, so we don't have a pluginless guardfile, because we don't
Expand All @@ -228,14 +139,12 @@ def _pluginless_guardfile?
# But, if we have a Guardfile, we'll at least have the built-in
# Reevaluator, so the following will work:

# TODO: this is a workaround for tests
return true if plugins.empty?

plugins.map(&:name) == ["reevaluator"]
plugins = state.session.plugins.all
plugins.empty? || plugins.map(&:name) == ["reevaluator"]
end

def _evaluate
evaluator = Guardfile::Evaluator.new(Guard.options)
def _evaluate(options)
evaluator = Guardfile::Evaluator.new(options)
evaluator.evaluate
msg = "No plugins found in Guardfile, please add at least one."
UI.error msg if _pluginless_guardfile?
Expand Down
13 changes: 8 additions & 5 deletions lib/guard/cli.rb
Expand Up @@ -48,6 +48,7 @@ class CLI < Thor
aliases: "-P",
banner: "Run only the passed plugins"

# TODO: make it plural
method_option :watchdir,
type: :array,
aliases: "-w",
Expand Down Expand Up @@ -167,10 +168,11 @@ def init(*plugin_names)
bare = options[:bare]

generator = Guardfile::Generator.new
Guard.init(options)
session = Guard.state.session

begin
Guard.init(options)
Guardfile::Evaluator.new(Guard.options).evaluate
Guardfile::Evaluator.new(session.evaluator_options).evaluate
rescue Guardfile::Evaluator::NoGuardfileError
generator.create_guardfile
end
Expand All @@ -179,7 +181,7 @@ def init(*plugin_names)

# Evaluate because it might have existed and creating was skipped
# FIXME: still, I don't know why this is needed
Guardfile::Evaluator.new(Guard.options).evaluate
Guardfile::Evaluator.new(session.evaluator_options).evaluate

if plugin_names.empty?
generator.initialize_all_templates
Expand Down Expand Up @@ -225,8 +227,9 @@ def _verify_bundler_presence
end

def _require_guardfile(options)
Guard.init(options)
Guardfile::Evaluator.new(Guard.options).evaluate
Guard.init(options) # to setup metadata
session = Guard.state.session
Guardfile::Evaluator.new(session.evaluator_options).evaluate
rescue Dsl::Error,
Guardfile::Evaluator::NoPluginsError,
Guardfile::Evaluator::NoGuardfileError,
Expand Down
29 changes: 14 additions & 15 deletions lib/guard/commander.rb
Expand Up @@ -30,31 +30,30 @@ module Commander
#
def start(options = {})
setup(options)
::Guard::UI.debug "Guard starts all plugins"
Guard::Runner.new.run(:start)
UI.debug "Guard starts all plugins"
Runner.new.run(:start)
listener.start

watched = ::Guard.watchdirs.join("', '")
::Guard::UI.info "Guard is now watching at '#{ watched }'"
watched = Guard.state.session.watchdirs.join("', '")
UI.info "Guard is now watching at '#{ watched }'"

begin
while interactor.foreground != :exit
_process_queue while pending_changes?
Guard.queue.process while Guard.queue.pending?
end
rescue Interrupt
end

stop
end

# TODO: refactor (left to avoid breaking too many specs)
def stop
listener.stop
interactor.background
::Guard::UI.debug "Guard stops all plugins"
Guard::Runner.new.run(:stop)
::Guard::Notifier.disconnect
::Guard::UI.info "Bye bye...", reset: true
UI.debug "Guard stops all plugins"
Runner.new.run(:stop)
Notifier.disconnect
UI.info "Bye bye...", reset: true
end

# Reload Guardfile and all Guard plugins currently enabled.
Expand All @@ -65,13 +64,13 @@ def stop
#
def reload(scopes = {})
# TODO: guard reevaluator should probably handle all this
::Guard::UI.clear(force: true)
::Guard::UI.action_with_scopes("Reload", scopes)
UI.clear(force: true)
UI.action_with_scopes("Reload", scopes)

if scopes.empty?
Reevaluator.new.reevaluate
else
Guard::Runner.new.run(:reload, scopes)
Runner.new.run(:reload, scopes)
end
end

Expand All @@ -80,8 +79,8 @@ def reload(scopes = {})
# @param [Hash] scopes hash with a Guard plugin or a group scope
#
def run_all(scopes = {})
::Guard::UI.clear(force: true)
::Guard::UI.action_with_scopes("Run", scopes)
UI.clear(force: true)
UI.action_with_scopes("Run", scopes)
Guard::Runner.new.run(:run_all, scopes)
end

Expand Down
3 changes: 1 addition & 2 deletions lib/guard/commands/all.rb
@@ -1,7 +1,6 @@
# required for async_queue_add
require "pry"

# TODO: remove this dependency
require "guard/interactor"
require "guard"

Expand All @@ -23,7 +22,7 @@ def self.import
BANNER

def process(*entries)
scopes, unknown = ::Guard::Interactor.convert_scope(entries)
scopes, unknown = Interactor.convert_scope(entries)

unless unknown.empty?
output.puts "Unknown scopes: #{ unknown.join(", ") }"
Expand Down
3 changes: 1 addition & 2 deletions lib/guard/commands/change.rb
@@ -1,6 +1,5 @@
require "pry"

# TODO: remove
require "guard"

module Guard
Expand All @@ -23,7 +22,7 @@ def process(*files)
return
end

::Guard.async_queue_add(modified: files, added: [], removed: [])
Guard.async_queue_add(modified: files, added: [], removed: [])
end
end
end
Expand Down
3 changes: 1 addition & 2 deletions lib/guard/commands/reload.rb
@@ -1,6 +1,5 @@
require "pry"

# TODO: should not be necessary!
require "guard/interactor"
require "guard"

Expand All @@ -22,7 +21,7 @@ def self.import
BANNER

def process(*entries)
scopes, unknown = ::Guard::Interactor.convert_scope(entries)
scopes, unknown = Interactor.convert_scope(entries)

unless unknown.empty?
output.puts "Unknown scopes: #{ unknown.join(", ") }"
Expand Down
4 changes: 1 addition & 3 deletions lib/guard/commands/scope.rb
Expand Up @@ -29,9 +29,7 @@ def process(*entries)
return
end

# TODO: provide a way for guard to know this was called from Pry
::Guard.setup_scope(scope)
#::Guard.add_scope(:interactor, scope)
Guard.state.scope.from_interactor(scope)
end
end
end
Expand Down

0 comments on commit 8976db6

Please sign in to comment.