Skip to content

Commit

Permalink
Merge 6e60c0a into 280a02c
Browse files Browse the repository at this point in the history
  • Loading branch information
e2 committed Dec 1, 2014
2 parents 280a02c + 6e60c0a commit 12ff328
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 70 deletions.
35 changes: 35 additions & 0 deletions features/ignores.feature
@@ -0,0 +1,35 @@
Feature: ignore files and directories

In order to receive only relevant changes
As a user
I want to specify which files and directories to ignore globally

Background: Guard is installed through bundler
Given Guard is bundled using source

@spawn
Scenario: Ignore events matching ignore regexp
Given my Guardfile contains:
"""
require 'guard/plugin'
ignore /bar/
module ::Guard
class Myplugin < Plugin
def run_on_additions(files)
$stdout.puts "Files added: #{files.inspect}"
$stdout.flush
end
end
end
guard(:myplugin) { watch(/ba/) }
"""
Given I start `bundle exec guard -n f`
And I create a file "baz"
And I create a file "bar"
And I wait for Guard to become idle
And I stop guard
Then the output should match /Files added: \["baz"\]/
7 changes: 7 additions & 0 deletions lib/guard.rb
Expand Up @@ -60,8 +60,15 @@ def setup(cmdline_options = {})
_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
Expand Down
16 changes: 11 additions & 5 deletions lib/guard/dsl.rb
Expand Up @@ -5,8 +5,6 @@
require "guard/watcher"

require "guard/deprecated/dsl" unless Guard::Config.new.strict?

# TODO: only for listener
require "guard"

module Guard
Expand Down Expand Up @@ -75,6 +73,7 @@ class Error < RuntimeError
# @see Guard::Notifier for available notifier and its options.
#
def notification(notifier, options = {})
# TODO: remove dependency on Notifier (let session handle this)
Notifier.add(notifier.to_sym, options.merge(silent: false))
end

Expand All @@ -90,6 +89,7 @@ def notification(notifier, options = {})
# options
#
def interactor(options)
# TODO: remove dependency on Interactor (let session handle this)
case options
when :off
Interactor.enabled = false
Expand Down Expand Up @@ -132,6 +132,7 @@ def group(*args)

if block_given?
groups.each do |group|
# TODO: let groups be added *after* evaluation
Guard.state.session.groups.add(group, options)
end

Expand Down Expand Up @@ -181,6 +182,7 @@ def guard(name, options = {})
groups = @current_groups && @current_groups.last || [:default]
groups.each do |group|
opts = @plugin_options.merge(group: group)
# TODO: let plugins be added *after* evaluation
Guard.state.session.plugins.add(name, opts)
end

Expand Down Expand Up @@ -263,8 +265,8 @@ def callback(*args, &block)
# @param [Regexp] regexps a pattern (or list of patterns) for ignoring paths
#
def ignore(*regexps)
# TODO: instead, use Guard.reconfigure(ignore: regexps) or something
Guard.listener.ignore(regexps) if Guard.listener
# TODO: use guardfile results class
Guard.state.session.guardfile_ignore = regexps
end

# TODO: deprecate
Expand All @@ -280,8 +282,11 @@ def ignore(*regexps)
def ignore!(*regexps)
@ignore_regexps ||= []
@ignore_regexps << regexps
Guard.listener.ignore!(@ignore_regexps) if Guard.listener
# TODO: use guardfile results class
Guard.state.session.guardfile_ignore_bang = @ignore_regexps
end

# TODO: deprecate
alias filter! ignore!

# Configures the Guard logger.
Expand Down Expand Up @@ -365,6 +370,7 @@ def logger(options)
# @param [Hash] scopes the scope for the groups and plugins
#
def scope(scope = {})
# TODO: use a Guardfile::Results class
Guard.state.session.guardfile_scope(scope)
end

Expand Down
5 changes: 5 additions & 0 deletions lib/guard/internals/session.rb
Expand Up @@ -69,6 +69,8 @@ def initialize(new_options)

@guardfile_plugin_scope = []
@guardfile_group_scope = []
@guardfile_ignore = []
@guardfile_ignore_bang = []
end

def guardfile_scope(scope)
Expand All @@ -78,8 +80,11 @@ def guardfile_scope(scope)
fail "Unknown options: #{opts.inspect}" unless opts.empty?
end

# TODO: create a EvaluatorResult class?
attr_reader :guardfile_group_scope
attr_reader :guardfile_plugin_scope
attr_accessor :guardfile_ignore
attr_accessor :guardfile_ignore_bang

def clearing(on)
@clear = on
Expand Down
71 changes: 22 additions & 49 deletions spec/lib/guard/dsl_spec.rb
Expand Up @@ -26,9 +26,7 @@
stub_const "Guard::Bar", instance_double(Guard::Plugin)
stub_const "Guard::Baz", instance_double(Guard::Plugin)
allow(Guard::Notifier).to receive(:turn_on)
allow(Listen).to receive(:to).with(Dir.pwd, {})
allow(Guard::Interactor).to receive(:new).and_return(interactor)
allow(Guard).to receive(:listener) { listener }

allow(state).to receive(:scope).and_return(scope)
allow(session).to receive(:plugins).and_return(plugins)
Expand All @@ -46,9 +44,7 @@
let(:contents) { "ignore %r{^foo}, /bar/" }

it "adds ignored regexps to the listener" do
expect(listener).to receive(:ignore).
with([/^foo/, /bar/]).and_return(listener)

expect(session).to receive(:guardfile_ignore=).with([/^foo/, /bar/])
evaluator.call(contents)
end
end
Expand All @@ -59,66 +55,43 @@
let(:contents) { "ignore! %r{^foo}, /bar/" }

it "replaces listener regexps" do
expect(listener).to receive(:ignore!).
with([[/^foo/, /bar/]]).and_return(listener)
expect(session).to receive(:guardfile_ignore_bang=).
with([[/^foo/, /bar/]])

evaluator.call(contents)
end
end

context "when filtering *.txt and *.zip and ignoring only foo*" do
let(:contents) { "filter! %r{.txt$}, /.*\\.zip/\n ignore! %r{^foo}" }
context "when ignoring *.txt and *.zip and ignoring! only foo*" do
let(:contents) { "ignore! %r{.txt$}, /.*\\.zip/\n ignore! %r{^foo}" }

it "replaces listener ignores, but keeps filter! ignores" do
allow(listener).to receive(:ignore!).
with([[/.txt$/, /.*\.zip/]]).and_return(listener)
it "replaces listener ignores, but keeps ignore! ignores" do
allow(session).to receive(:guardfile_ignore_bang=).
with([[/.txt$/, /.*\.zip/]])

expect(listener).to receive(:ignore!).
with([[/.txt$/, /.*\.zip/], [/^foo/]]).and_return(listener)
expect(session).to receive(:guardfile_ignore_bang=).
with([[/.txt$/, /.*\.zip/], [/^foo/]])

evaluator.call(contents)
end
end
end

describe "#filter" do
context "with filter regexp" do
let(:contents) { "filter %r{.txt$}, /.*.zip/" }

it "adds ignored regexps to the listener" do
expect(listener).to receive(:ignore).
with([/.txt$/, /.*.zip/]).and_return(listener)

evaluator.call(contents)
end
end
# TODO: remove this hack (after deprecating filter)
def method_for(klass, meth)
klass.instance_method(meth)
end

describe "#filter!" do
context "when filter!" do
let(:contents) { "filter! %r{.txt$}, /.*.zip/" }

it "replaces ignored regexps in the listener" do
expect(listener).to receive(:ignore!).
with([[/.txt$/, /.*.zip/]]).and_return(listener)

evaluator.call(contents)
end
end

context "with ignore! and filter!" do
let(:contents) { "ignore! %r{^foo}\n filter! %r{.txt$}, /.*.zip/" }

it "replaces listener ignores, but keeps guardfile ignore!" do
expect(listener).to receive(:ignore!).
with([[/^foo/]]).and_return(listener)

expect(listener).to receive(:ignore!).
with([[/^foo/], [/.txt$/, /.*.zip/]]).and_return(listener)
# TODO: deprecated #filter
describe "#filter alias method" do
subject { method_for(described_class, :filter) }
it { is_expected.to eq(method_for(described_class, :ignore)) }
end

evaluator.call(contents)
end
end
# TODO: deprecated #filter
describe "#filter! alias method" do
subject { method_for(described_class, :filter!) }
it { is_expected.to eq(method_for(described_class, :ignore!)) }
end

describe "#notification" do
Expand Down
41 changes: 28 additions & 13 deletions spec/lib/guard/internals/session_spec.rb
@@ -1,18 +1,18 @@
require "guard/internals/session"

RSpec.describe Guard::Internals::Session do
let(:options) { {} }
subject { described_class.new(options) }

let(:plugins) { instance_double("Guard::Internals::Plugins") }
let(:groups) { instance_double("Guard::Internals::Plugins") }
let(:scope) { instance_double("Guard::Internals::Scope") }

before do
allow(Guard::Internals::Plugins).to receive(:new).and_return(plugins)
allow(Guard::Internals::Groups).to receive(:new).and_return(groups)
end

describe "#initialize" do
before do
allow(Guard::Internals::Plugins).to receive(:new).and_return(plugins)
allow(Guard::Internals::Groups).to receive(:new).and_return(groups)
allow(Guard::Internals::Scope).to receive(:new).and_return(scope)
end

describe "#listener_args" do
subject { described_class.new(options).listener_args }
Expand Down Expand Up @@ -94,13 +94,6 @@
end

describe "#clearing" do
let(:options) { {} }

before do
allow(Guard::Internals::Plugins).to receive(:new).and_return(plugins)
allow(Guard::Internals::Groups).to receive(:new).and_return(groups)
end

context "when not set" do
context "when clearing is not set from commandline" do
it { is_expected.to_not be_clearing }
Expand All @@ -124,4 +117,26 @@
end
end
end

describe "#guardfile_ignore=" do
context "when set from guardfile" do
before { subject.guardfile_ignore = [/foo/] }
specify { expect(subject.guardfile_ignore).to eq([/foo/]) }
end

context "when unset" do
specify { expect(subject.guardfile_ignore).to eq([]) }
end
end

describe "#guardfile_ignore_bang=" do
context "when set from guardfile" do
before { subject.guardfile_ignore_bang = [/foo/] }
specify { expect(subject.guardfile_ignore_bang).to eq([/foo/]) }
end

context "when unset" do
specify { expect(subject.guardfile_ignore_bang).to eq([]) }
end
end
end
38 changes: 35 additions & 3 deletions spec/lib/guard_spec.rb
Expand Up @@ -42,7 +42,7 @@

let(:options) { { my_opts: true, guardfile: guardfile } }

let(:listener) { instance_double(Listen::Listener) }
let(:listener) { instance_double("Listen::Listener") }

before do
allow(Listen).to receive(:to).with(Dir.pwd, {}) { listener }
Expand All @@ -63,13 +63,19 @@
allow(Guard::Notifier).to receive(:connect)

allow(Guard::UI).to receive(:reset_and_clear)
allow(plugins).to receive(:all).and_return([])

allow(session).to receive(:listener_args).and_return([:to, Dir.pwd, {}])
allow(session).to receive(:evaluator_options).and_return({})
allow(session).to receive(:cmdline_groups).and_return({})
allow(session).to receive(:cmdline_plugins).and_return({})
allow(session).to receive(:notify_options).and_return(notify: true)
allow(session).to receive(:interactor_name).and_return(:foo)
allow(plugins).to receive(:all).and_return([])
allow(session).to receive(:guardfile_ignore).and_return([])
allow(session).to receive(:guardfile_ignore_bang).and_return([])

allow(listener).to receive(:ignore)
allow(listener).to receive(:ignore!)

allow(Guard::Internals::State).to receive(:new).and_return(state)
end
Expand All @@ -79,7 +85,9 @@
end

it "initializes the listener" do
allow(Listen).to receive(:to).with("/foo", latency: 2, wait_for_delay: 1)
allow(Listen).to receive(:to).
with("/foo", latency: 2, wait_for_delay: 1).and_return(listener)

allow(session).to receive(:listener_args).and_return(
[:to, "/foo", { latency: 2, wait_for_delay: 1 }]
)
Expand Down Expand Up @@ -124,6 +132,30 @@
subject
end

describe "listener" do
subject { listener }

context "with ignores 'ignore(/foo/)' and 'ignore!(/bar/)'" do
before do
allow(evaluator).to receive(:evaluate) do
allow(session).to receive(:guardfile_ignore).and_return([/foo/])
allow(session).to receive(:guardfile_ignore_bang).
and_return([/bar/])
end
Guard.setup(options)
end

it { is_expected.to have_received(:ignore).with([/foo/]) }
it { is_expected.to have_received(:ignore!).with([/bar/]) }
end

context "without ignores" do
before { Guard.setup(options) }
it { is_expected.to_not have_received(:ignore) }
it { is_expected.to_not have_received(:ignore!) }
end
end

it "displays an error message when no guard are defined in Guardfile" do
expect(Guard::UI).to receive(:error).
with("No plugins found in Guardfile, please add at least one.")
Expand Down

0 comments on commit 12ff328

Please sign in to comment.