Merge branch 'master' into stdin

Conflicts:
	Guardfile
	lib/guard.rb
This commit is contained in:
Thibaud Guillaume-Gentil 2011-08-30 21:16:30 +02:00
commit 5de94ccbcb
14 changed files with 305 additions and 99 deletions

View File

@ -1,3 +1,19 @@
## 0.6.2 - August 17, 2011
### Bugs fixes:
- Re-add the possibility to use the `growl` gem since the `growl_notify` gem this is currently known to not work in conjunction with Spork. ([@netzpirat][])
- Ensure that scoped groups and group name are symbolized before checking for inclusion. ([@rymai][])
### New features:
- Groups are now stored in a @groups variable (will be used for future features). ([@rymai][])
- Guards will now receive their group in the options hash at initialization (will be used for future features). ([@rymai][])
### Improvement:
- Explain the growl/growl_notify differences in the README. ([@netzpirat][])
## 0.6.1 - August 15, 2011
### Bugs fixes:
@ -12,11 +28,11 @@
- Pull request [#107](https://github.com/guard/guard/pull/107): Small spelling fix. ([@dnagir][])
- Dir.glob now ignores files that don't need to be watched. ([@rymai][])
### New features
### New features:
- Pull request [#112](https://github.com/guard/guard/pull/112): Add `list` command to CLI. ([@docwhat][])
### Improvements
### Improvements:
- Pull request [#99](https://github.com/guard/guard/pull/99): [OS X] Switch from growl gem to growl_notify gem. ([@johnbintz][])
- Pull request [#115](https://github.com/guard/guard/pull/115): [Linux] Add ':transient => true' to default libnotify options. ([@zonque][])
@ -35,12 +51,12 @@
## 0.5.0 - July 2, 2011
### New features
### New features:
- Guard::Ego is now part of Guard, so Guardfile is automagically re-evaluated when modified. ([@thibaudgg][])
- Pull request [#91](https://github.com/guard/guard/pull/91): Show Guards in Guardfile with the `guard -T`. ([@johnbintz][])
### Improvements
### Improvements:
- Issue [#98](https://github.com/guard/guard/issues/98): Multiple calls per watch event on linux with rb-inotify. ([@jeffutter][] & [@netzpirat][])
- Pull request [#94](https://github.com/guard/guard/pull/94): Show backtrace in terminal when a problem with a watch action occurs. ([@capotej][])
@ -62,7 +78,7 @@
## 0.4.1 - June 7, 2011
### Improvements
### Improvements:
- Pull request [#77](https://github.com/guard/guard/pull/77): Refactor `get_guard_class` to first try the constant and fallback to require + various tweaks. ([@mislav][])
- Notifier improvement, don't use system notification library if could not be required. ([@yannlugrin][])
@ -78,7 +94,7 @@
- Pull request [#73](https://github.com/guard/guard/pull/73): Allow DSL's `group` method to accept a Symbol as group name. ([@johnbintz][])
- Pull request [#51](https://github.com/guard/guard/pull/51): Allow options (like `:priority`) to be passed through to the Notifier. ([@indirect][] & [@netzpirat][])
### Improvements
### Improvements:
- Pull request [#74](https://github.com/guard/guard/pull/74): Added link definitions to make the CHANGELOG more DRY! That's for sure now, we have the cleanest CHANGELOG ever! (even the link definitions are sorted alphabetically!) ([@pcreux][])

View File

@ -8,15 +8,11 @@ group :guard do
gem 'guard-ronn'
end
group :test do
gem 'fuubar'
end
require 'rbconfig'
if RbConfig::CONFIG['target_os'] =~ /darwin/i
gem 'rb-fsevent', '>= 0.4.0', :require => false
gem 'growl_notify', :require => false
gem 'growl', '~> 1.0.3', :require => false
end
if RbConfig::CONFIG['target_os'] =~ /linux/i
gem 'rb-inotify', '>= 0.8.5', :require => false

View File

@ -13,7 +13,7 @@ Features
* [Directory Change Notification](http://msdn.microsoft.com/en-us/library/aa365261\(VS.85\).aspx) support on Windows ([rb-fchange, >= 0.0.2](https://rubygems.org/gems/rb-fchange) required).
* Polling on the other operating systems (help us to support more OS).
* Automatic & Super fast (when polling is not used) files modifications detection (even new files are detected).
* Growl notifications ([growlnotify](http://growl.info/documentation/growlnotify.php) & [growl gem](https://rubygems.org/gems/growl) required).
* Growl notifications ([growl_notify gem](https://rubygems.org/gems/growl_notify) or [growlnotify](http://growl.info/documentation/growlnotify.php) & [growl gem](https://rubygems.org/gems/growl) required).
* Libnotify notifications ([libnotify gem](https://rubygems.org/gems/libnotify) required).
* Tested against Ruby 1.8.7, 1.9.2 and REE.
@ -55,19 +55,25 @@ Install the rb-fsevent gem for [FSEvent](http://en.wikipedia.org/wiki/FSEvents)
$ gem install rb-fsevent
```
Install the growl_notify gem if you want notification support:
Install either the growl_notify or the growl gem if you want notification support:
``` bash
$ gem install growl_notify
$ # or
$ gem install growl
```
And add it to your Gemfile:
And add them to your Gemfile:
``` ruby
gem 'rb-fsevent'
gem 'growl_notify'
gem 'growl'
```
The difference between growl and growl_notify is that growl_notify uses AppleScript to
display a message, whereas growl uses the `growlnotify` command. In general the AppleScript
approach is preferred, but this is currently known to not work in conjunction with Spork.
### On Linux
Install the rb-inotify gem for [inotify](http://en.wikipedia.org/wiki/Inotify) support:

View File

@ -9,12 +9,13 @@ module Guard
autoload :Notifier, 'guard/notifier'
class << self
attr_accessor :options, :guards, :interactor, :listener
attr_accessor :options, :guards, :groups, :interactor, :listener
# initialize this singleton
def setup(options = {})
@options = options
@guards = []
@groups = [:default]
@interactor = Interactor.new
@listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd)
@ -105,19 +106,24 @@ module Guard
end
def add_guard(name, watchers = [], options = {})
if name.downcase == 'ego'
if name.to_sym == :ego
UI.deprecation("Guard::Ego is now part of Guard. You can remove it from your Guardfile.")
else
guard_class = get_guard_class(name)
@guards << guard_class.new(watchers, options)
guard = get_guard_class(name).new(watchers, options)
@guards << guard
end
end
def add_group(name)
@groups << name.to_sym unless name.nil?
end
def get_guard_class(name)
name = name.to_s
try_require = false
const_name = name.to_s.downcase.gsub('-', '')
const_name = name.downcase.gsub('-', '')
begin
require "guard/#{name.to_s.downcase}" if try_require
require "guard/#{name.downcase}" if try_require
self.const_get(self.constants.find { |c| c.to_s.downcase == const_name })
rescue TypeError
unless try_require

View File

@ -106,13 +106,20 @@ module Guard
def group(name, &guard_definition)
@groups = @@options[:group] || []
guard_definition.call if guard_definition && (@groups.empty? || @groups.include?(name.to_s))
name = name.to_sym
if guard_definition && (@groups.empty? || @groups.map(&:to_sym).include?(name))
@current_group = name
guard_definition.call
@current_group = nil
end
end
def guard(name, options = {}, &watch_definition)
@watchers = []
watch_definition.call if watch_definition
::Guard.add_guard(name, @watchers, options)
options.update(:group => (@current_group || :default))
::Guard.add_guard(name.to_s.downcase.to_sym, @watchers, options)
end
def watch(pattern, &action)

View File

@ -1,9 +1,10 @@
module Guard
class Guard
attr_accessor :watchers, :options
attr_accessor :watchers, :options, :group
def initialize(watchers = [], options = {})
@group = options.delete(:group) || :default
@watchers, @options = watchers, options
end

View File

@ -47,10 +47,18 @@ module Guard
def self.notify_mac(title, message, image, options)
require_growl # need for guard-rspec formatter that is called out of guard scope
options = { :description => message, :title => title, :icon => image_path(image), :application_name => APPLICATION_NAME }.merge(options)
options.delete(:name)
default_options = { :title => title, :icon => image_path(image), :name => APPLICATION_NAME }
default_options.merge!(options)
GrowlNotify.send_notification(options) if enabled?
if defined?(GrowlNotify)
default_options[:description] = message
default_options[:application_name] = APPLICATION_NAME
default_options.delete(:name)
GrowlNotify.send_notification(default_options) if enabled?
else
Growl.notify message, default_options.merge(options) if enabled?
end
end
def self.notify_linux(title, message, image, options)
@ -94,6 +102,7 @@ module Guard
end
def self.require_growl
begin
require 'growl_notify'
if GrowlNotify.application_name != APPLICATION_NAME
@ -102,9 +111,12 @@ module Guard
c.application_name = c.notifications.first
end
end
rescue LoadError
require 'growl'
end
rescue LoadError
turn_off
UI.info "Please install growl_notify gem for Mac OS X notification support and add it to your Gemfile"
UI.info "Please install growl or growl_notify gem for Mac OS X notification support and add it to your Gemfile"
end
def self.require_libnotify

View File

@ -1,3 +1,3 @@
module Guard
VERSION = "0.6.1" unless defined? Guard::VERSION
VERSION = "0.6.2" unless defined? Guard::VERSION
end

View File

@ -18,7 +18,7 @@ describe Guard::Dsl do
subject.guardfile_contents.should == valid_guardfile_string
end
it "should use a -command file over the default loc" do
it "should use a given file over the default loc" do
fake_guardfile('/abc/Guardfile', "guard :foo")
Guard::UI.should_not_receive(:error)
@ -66,22 +66,22 @@ describe Guard::Dsl do
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string)
end
describe "it should correctly read data from its valid data source" do
describe "correctly reads data from its valid data source" do
before(:each) { ::Guard::Dsl.stub!(:instance_eval_guardfile) }
it "should read correctly from a string" do
it "reads correctly from a string" do
lambda { subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
subject.guardfile_contents.should == valid_guardfile_string
end
it "should read correctly from a Guardfile" do
it "reads correctly from a Guardfile" do
fake_guardfile('/abc/Guardfile', "guard :foo" )
lambda { subject.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
subject.guardfile_contents.should == "guard :foo"
end
it "should read correctly from a Guardfile" do
it "reads correctly from a Guardfile" do
fake_guardfile(File.join(Dir.pwd, 'Guardfile'), valid_guardfile_string)
lambda { subject.evaluate_guardfile }.should_not raise_error
@ -89,10 +89,10 @@ describe Guard::Dsl do
end
end
describe "It should correctly throw errors when initializing with invalid data" do
describe "correctly throws errors when initializing with invalid data" do
before(:each) { ::Guard::Dsl.stub!(:instance_eval_guardfile) }
it "should raise error when there's a problem reading a file" do
it "raises error when there's a problem reading a file" do
File.stub!(:exist?).with('/def/Guardfile') { true }
File.stub!(:read).with('/def/Guardfile') { raise Errno::EACCES.new("permission error") }
@ -100,14 +100,14 @@ describe Guard::Dsl do
lambda { subject.evaluate_guardfile(:guardfile => '/def/Guardfile') }.should raise_error
end
it "should raise error when -guardfile doesn't exist" do
it "raises error when given Guardfile doesn't exist" do
File.stub!(:exist?).with('/def/Guardfile') { false }
Guard::UI.should_receive(:error).with(/No Guardfile exists at/)
lambda { subject.evaluate_guardfile(:guardfile => '/def/Guardfile') }.should raise_error
end
it "should raise error when resorting to use default, finds no default" do
it "raises error when resorting to use default, finds no default" do
File.stub!(:exist?).with(@local_guardfile_path) { false }
File.stub!(:exist?).with(@home_guardfile_path) { false }
@ -115,12 +115,12 @@ describe Guard::Dsl do
lambda { subject.evaluate_guardfile }.should raise_error
end
it "should raise error when guardfile_content ends up empty or nil" do
it "raises error when guardfile_content ends up empty or nil" do
Guard::UI.should_receive(:error).with(/The command file/)
lambda { subject.evaluate_guardfile(:guardfile_contents => "") }.should raise_error
end
it "should not raise error when guardfile_content is nil (skipped)" do
it "doesn't raise error when guardfile_content is nil (skipped)" do
Guard::UI.should_not_receive(:error)
lambda { subject.evaluate_guardfile(:guardfile_contents => nil) }.should_not raise_error
end
@ -200,48 +200,74 @@ describe Guard::Dsl do
end
describe "#group" do
it "should evaluates only the specified string group" do
::Guard.should_receive(:add_guard).with('test', anything, {})
lambda { subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => ['w']) }.should_not raise_error
it "evaluates only the specified string group" do
::Guard.should_receive(:add_guard).with(:pow, [], { :group => :default })
::Guard.should_receive(:add_guard).with(:test, [], { :group => :w })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => ['w'])
end
it "should evaluates only the specified symbol group" do
::Guard.should_receive(:add_guard).with('test', anything, {})
lambda { subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => ['x']) }.should_not raise_error
it "evaluates only the specified symbol group" do
::Guard.should_receive(:add_guard).with(:pow, [], { :group => :default })
::Guard.should_receive(:add_guard).with(:test, [], { :group => :w })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
end
it "should evaluates only the specified groups" do
::Guard.should_receive(:add_guard).with('test', anything, {})
::Guard.should_receive(:add_guard).with('another', anything, {})
lambda { subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => ['x','y']) }.should_not raise_error
it "evaluates only the specified groups" do
::Guard.should_receive(:add_guard).with(:pow, [], { :group => :default })
::Guard.should_receive(:add_guard).with(:rspec, [], { :group => :x })
::Guard.should_receive(:add_guard).with(:ronn, [], { :group => :x })
::Guard.should_receive(:add_guard).with(:less, [], { :group => :y })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:x, :y])
end
it "should evaluate all groups when no group option is specified" do
::Guard.should_receive(:add_guard).with('test', anything, {}).twice
::Guard.should_receive(:add_guard).with('another', anything, {}).twice
lambda { subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
it "evaluates always guard outside any group (even when a group is given)" do
::Guard.should_receive(:add_guard).with(:pow, [], { :group => :default })
::Guard.should_receive(:add_guard).with(:test, [], { :group => :w })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
end
it "evaluates all groups when no group option is specified" do
::Guard.should_receive(:add_guard).with(:pow, [], { :group => :default })
::Guard.should_receive(:add_guard).with(:test, [], { :group => :w })
::Guard.should_receive(:add_guard).with(:rspec, [], { :group => :x })
::Guard.should_receive(:add_guard).with(:ronn, [], { :group => :x })
::Guard.should_receive(:add_guard).with(:less, [], { :group => :y })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string)
end
end
# TODO: not sure if each seperate quoting/call type needs its own test
describe "#guard" do
it "should load a guard specified as a quoted string from the DSL" do
::Guard.should_receive(:add_guard).with('test', [], {})
it "loads a guard specified as a quoted string from the DSL" do
::Guard.should_receive(:add_guard).with(:test, [], { :group => :default })
subject.evaluate_guardfile(:guardfile_contents => "guard 'test'")
end
it "should load a guard specified as a symbol from the DSL" do
::Guard.should_receive(:add_guard).with(:test, [], {})
it "loads a guard specified as a double quoted string from the DSL" do
::Guard.should_receive(:add_guard).with(:test, [], { :group => :default })
subject.evaluate_guardfile(:guardfile_contents => 'guard "test"')
end
it "loads a guard specified as a symbol from the DSL" do
::Guard.should_receive(:add_guard).with(:test, [], { :group => :default })
subject.evaluate_guardfile(:guardfile_contents => "guard :test")
end
it "should load a guard specified as a symbol and called with parens from the DSL" do
::Guard.should_receive(:add_guard).with(:test, [], {})
it "loads a guard specified as a symbol and called with parens from the DSL" do
::Guard.should_receive(:add_guard).with(:test, [], { :group => :default })
subject.evaluate_guardfile(:guardfile_contents => "guard(:test)")
end
it "should receive options when specified" do
::Guard.should_receive(:add_guard).with('test', anything, { :opt_a => 1, :opt_b => 'fancy' })
it "receives options when specified, from normal arg" do
::Guard.should_receive(:add_guard).with(:test, [], { :opt_a => 1, :opt_b => 'fancy', :group => :default })
subject.evaluate_guardfile(:guardfile_contents => "guard 'test', :opt_a => 1, :opt_b => 'fancy'")
end
@ -254,7 +280,7 @@ describe Guard::Dsl do
watch('c')
end"
::Guard.should_receive(:add_guard).with('test', anything, {}) do |name, watchers, options|
::Guard.should_receive(:add_guard).with(:test, anything, { :group => :default }) do |name, watchers, options|
watchers.size.should == 2
watchers[0].pattern.should == 'a'
watchers[0].action.call.should == proc { 'b' }.call
@ -273,29 +299,22 @@ private
end
def valid_guardfile_string
"group 'w' do
guard 'test' do
watch('c')
end
"
guard :pow
group 'w' do
guard 'test'
end
group :x do
guard 'test' do
watch('c')
end
guard 'rspec'
guard :ronn
end
group 'y' do
guard 'another' do
watch('c')
guard 'less'
end
end
group 'z' do
guard 'another' do
watch('c')
end
end"
"
end
def mock_guardfile_content(content)

View File

@ -21,7 +21,7 @@ describe Guard::Darwin do
subject.should be_usable
end
it_should_behave_like "a listener that reacts to #on_change"
it_should_behave_like "a listener scoped to a specific directory"
it_should_behave_like "a listener that reacts to #on_change", 0.4
it_should_behave_like "a listener scoped to a specific directory", 0.4
end
end

View File

@ -39,9 +39,19 @@ describe Guard::Notifier do
end
end
context "without the GrowlNofity library available" do
context "with the Growl library available" do
it "loads the library and enables the notifications" do
subject.should_receive(:require).with('growl_notify').and_raise LoadError
subject.should_receive(:require).with('growl').and_return true
subject.turn_on
subject.should be_enabled
end
end
context "without the Growl library available" do
it "disables the notifications" do
subject.should_receive(:require).with('growl_notify').and_raise LoadError
subject.should_receive(:require).with('growl').and_raise LoadError
subject.turn_on
subject.should_not be_enabled
end
@ -102,6 +112,51 @@ describe Guard::Notifier do
subject.stub(:require_growl)
end
context 'with growl gem' do
before do
Object.send(:remove_const, :Growl) if defined?(Growl)
Growl = Object.new
end
after do
Object.send(:remove_const, :Growl)
end
it "passes the notification to Growl" do
Growl.should_receive(:notify).with("great",
:title => "Guard",
:icon => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
:name => "Guard"
)
subject.notify 'great', :title => 'Guard'
end
it "don't passes the notification to Growl if library is not available" do
Growl.should_not_receive(:notify)
subject.should_receive(:enabled?).and_return(true, false)
subject.notify 'great', :title => 'Guard'
end
it "allows additional notification options" do
Growl.should_receive(:notify).with("great",
:title => "Guard",
:icon => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
:name => "Guard",
:priority => 1
)
subject.notify 'great', :title => 'Guard', :priority => 1
end
it "allows to overwrite a default notification option" do
Growl.should_receive(:notify).with("great",
:title => "Guard",
:icon => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
:name => "Guard-Cucumber"
)
subject.notify 'great', :title => 'Guard', :name => "Guard-Cucumber"
end
end
context 'with growl_notify gem' do
before do
Object.send(:remove_const, :GrowlNotify) if defined?(GrowlNotify)

View File

@ -10,13 +10,17 @@ describe Guard do
subject.should be ::Guard
end
it "initializes the Guards" do
::Guard.guards.should be_kind_of(Array)
it "initializes @guards" do
subject.guards.should eql []
end
it "initializes @groups" do
Guard.groups.should eql [:default]
end
it "initializes the options" do
opts = { :my_opts => true }
::Guard.setup(opts).options.should include(:my_opts)
Guard.setup(opts).options.should include(:my_opts)
end
it "initializes the listener" do
@ -62,6 +66,88 @@ describe Guard do
end
end
describe ".add_guard" do
before(:each) do
@guard_rspec_class = double('Guard::RSpec')
@guard_rspec = double('Guard::RSpec')
Guard.stub!(:get_guard_class) { @guard_rspec_class }
Guard.setup
end
it "accepts guard name as string" do
@guard_rspec_class.should_receive(:new).and_return(@guard_rspec)
Guard.add_guard('rspec')
end
it "accepts guard name as symbol" do
@guard_rspec_class.should_receive(:new).and_return(@guard_rspec)
Guard.add_guard(:rspec)
end
it "adds guard to the @guards array" do
@guard_rspec_class.should_receive(:new).and_return(@guard_rspec)
Guard.add_guard(:rspec)
Guard.guards.should eql [@guard_rspec]
end
context "with no watchers given" do
it "gives an empty array of watchers" do
@guard_rspec_class.should_receive(:new).with([], {}).and_return(@guard_rspec)
Guard.add_guard(:rspec, [])
end
end
context "with watchers given" do
it "give the watchers array" do
@guard_rspec_class.should_receive(:new).with([:foo], {}).and_return(@guard_rspec)
Guard.add_guard(:rspec, [:foo])
end
end
context "with no options given" do
it "gives an empty hash of options" do
@guard_rspec_class.should_receive(:new).with([], {}).and_return(@guard_rspec)
Guard.add_guard(:rspec, [], {})
end
end
context "with options given" do
it "give the options hash" do
@guard_rspec_class.should_receive(:new).with([], { :foo => true, :group => :backend }).and_return(@guard_rspec)
Guard.add_guard(:rspec, [], { :foo => true, :group => :backend })
end
end
end
describe ".add_group" do
before(:each) do
Guard.setup
end
it "accepts group name as string" do
Guard.add_group('backend')
Guard.groups.should eql [:default, :backend]
end
it "accepts group name as symbol" do
Guard.add_group(:backend)
Guard.groups.should eql [:default, :backend]
end
end
describe ".get_guard_class" do
after do
[:Classname, :DashedClassName, :Inline].each do |const|

View File

@ -1,10 +1,10 @@
private
def start
sleep 1
sleep(@rest_delay || 1)
@listener.update_last_event
Thread.new { @listener.start }
sleep 1
sleep(@rest_delay || 1)
end
def record_results
@ -17,17 +17,18 @@ private
end
def stop
sleep 1
sleep(@rest_delay || 1)
@listener.stop
sleep 1
sleep(@rest_delay || 1)
end
def results
@results.flatten
end
shared_examples_for 'a listener that reacts to #on_change' do
shared_examples_for 'a listener that reacts to #on_change' do |rest_delay|
before(:each) do
@rest_delay = rest_delay
@listener = described_class.new
record_results
end
@ -105,8 +106,9 @@ shared_examples_for 'a listener that reacts to #on_change' do
end
shared_examples_for "a listener scoped to a specific directory" do
shared_examples_for "a listener scoped to a specific directory" do |rest_delay|
before :each do
@rest_delay = rest_delay
@wd = @fixture_path.join("folder1")
@listener = described_class.new @wd
end