Merge branch 'master' into hook

Conflicts:
	Guardfile
	lib/guard.rb
	lib/guard/dsl.rb
	spec/guard/interactor_spec.rb
	spec/guard/listeners/darwin_spec.rb
This commit is contained in:
Rémy Coutable 2011-09-04 18:00:29 +02:00
commit 11495687f4
21 changed files with 449 additions and 165 deletions

3
.gitignore vendored
View File

@ -1,5 +1,8 @@
pkg/*
*.gem
*.rbc
.*.swp
*.bak
.bundle
Gemfile.lock

View File

@ -2,9 +2,13 @@ rvm:
- 1.8.7
- 1.9.2
- ree
- jruby
- rbx
branches:
only:
- master
- hook
- stdin
- jruby_on_travis
notifications:
irc: "irc.freenode.org#guard"

View File

@ -1,3 +1,42 @@
## Master
### Major Changes
- Posix Signals handlers (`Ctrl-C`, `Ctrl-\` and `Ctrl-Z`) are no more supported and replaced by `$stdin.gets`. Please refer to the "Interactions" section in the README for more information. ([@thibaudgg][])
- JRuby support (beta). ([@thibaudgg][])
- Rubinius support (beta). ([@thibaudgg][])
### New feature:
- Ability to 'pause' files modification listening. Please refer to the "Interactions" section in the README for more information. ([@thibaudgg][])
### Improvement:
- Remove the need to scan the whole directory after guard's `run_on_change` method. ([@thibaudgg][])
## 0.6.3 - September 1, 2011
### New features:
- Pull request [#130](https://github.com/guard/guard/pull/130): Adds ignore_paths option to DSL. ([@ianwhite][])
- Pull request [#128](https://github.com/guard/guard/pull/128): Users can add additional settings to ~/.guard.rb that augment the existing Guardfile. ([@tpope][])
## 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 +51,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 +74,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 +101,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 +117,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][])
@ -210,6 +249,7 @@
[@Gazer]: https://github.com/Gazer
[@gix]: https://github.com/gix
[@hashrocketeer]: https://github.com/hashrocketeer
[@ianwhite]: https://github.com/ianwhite
[@indirect]: https://github.com/indirect
[@jeffutter]: https://github.com/jeffutter
[@johnbintz]: https://github.com/johnbintz

View File

@ -12,7 +12,7 @@ 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

@ -1,4 +1,4 @@
guard :rspec, :version => 2, :keep_failed => false, :cli => '-f doc' do
guard :rspec, :version => 2, :all_on_start => false, :all_after_pass => false, :keep_failed => false, :cli => '--format doc' do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch('spec/spec_helper.rb') { "spec" }

View File

@ -1,4 +1,4 @@
Guard [![Build Status](https://secure.travis-ci.org/guard/guard.png)](http://travis-ci.org/guard/guard)
Guard [![Build Status](http://travis-ci.org/guard/guard.png)](http://travis-ci.org/guard/guard)
=====
Guard is a command line tool that easily handle events on files modifications.
@ -13,14 +13,13 @@ 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).
* Libnotify notifications ([libnotify gem](https://rubygems.org/gems/libnotify) required).
* Visual notifications on Mac OSX ([Growl](http://growl.info)), Linux ([Libnotify](http://developer.gnome.org/libnotify)) and Windows ([Notifu](http://www.paralint.com/projects/notifu)).
* Tested against Ruby 1.8.7, 1.9.2 and REE.
Screencast
----------
Ryan Bates made a screencast on Guard, you can view it here: http://railscasts.com/episodes/264-guard
Ryan Bates made a Railscast on Guard, you can view it here: http://railscasts.com/episodes/264-guard
Install
-------
@ -31,12 +30,18 @@ Install the gem:
$ gem install guard
```
Add it to your Gemfile (inside the `development` group):
Or add it to your Gemfile (inside the `development` group):
``` ruby
gem 'guard'
```
and install it via Bundler:
``` bash
$ bundle install
```
Generate an empty Guardfile with:
``` bash
@ -44,6 +49,7 @@ $ guard init
```
You may optionally place a .Guardfile in your home directory to use it across multiple projects.
Also note that if a `.guard.rb` is found in your home directory, it will be appended to the Guardfile.
Add the guards you need to your Guardfile (see the existing guards below).
@ -55,28 +61,42 @@ 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:
You have two possibilities:
Use the [growl_notify gem](https://rubygems.org/gems/growl_notify) (recommended):
``` bash
$ gem install growl_notify
```
And add it to your Gemfile:
Use the [growlnotify](http://growl.info/extras.php#growlnotify) (cli tool for growl) + the [growl gem](https://rubygems.org/gems/growl).
``` bash
$ brew install growlnotify
$ gem install growl
```
And add them to your Gemfile:
``` ruby
gem 'rb-fsevent'
gem 'growl_notify'
gem 'growl_notify' # or 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 you may also use the older growl gem. Have a look at the
[Guard Wiki](https://github.com/guard/guard/wiki/Use-growl_notify-or-growl-gem) for more information.
### On Linux
Install the rb-inotify gem for [inotify](http://en.wikipedia.org/wiki/Inotify) support:
Install the [rb-inotify gem](https://rubygems.org/gems/rb-inotify) for [inotify](http://en.wikipedia.org/wiki/Inotify) support:
``` bash
$ gem install rb-inotify
```
Install the Libnotify gem if you want notification support:
Install the [libnotify gem](https://rubygems.org/gems/libnotify) if you want visual notification support:
``` bash
$ gem install libnotify
@ -91,19 +111,19 @@ gem 'libnotify'
### On Windows
Install the rb-fchange gem for [Directory Change Notification](http://msdn.microsoft.com/en-us/library/aa365261\(VS.85\).aspx) support:
Install the [rb-fchange gem](https://rubygems.org/gems/rb-fchange) for [Directory Change Notification](http://msdn.microsoft.com/en-us/library/aa365261\(VS.85\).aspx) support:
``` bash
$ gem install rb-fchange
```
Install the win32console gem if you want colors in your terminal:
Install the [win32console gem](https://rubygems.org/gems/win32console) if you want colors in your terminal:
``` bash
$ gem install win32console
```
Install the Notifu gem if you want notification support:
Install the [rb-notifu gem](https://rubygems.org/gems/rb-notifu) if you want visual notification support:
``` bash
$ gem install rb-notifu
@ -114,6 +134,7 @@ And add them to your Gemfile:
``` ruby
gem 'rb-fchange'
gem 'rb-notifu'
gem 'win32console'
```
Usage
@ -198,16 +219,17 @@ An exhaustive list of options is available with:
$ guard help [TASK]
```
Signal handlers
---------------
Interactions
------------
Signal handlers are used to interact with Guard:
**From version >= 0.7.0 Posix Signal handlers are no more used to interact with Guard.**
* `Ctrl-C` - Calls each guard's `#stop` method, in the same order they are declared in the Guardfile, and then quits Guard itself.
* `Ctrl-\` - Calls each guard's `#run_all` method, in the same order they are declared in the Guardfile.
* `Ctrl-Z` - Calls each guard's `#reload` method, in the same order they are declared in the Guardfile.
When Guard do nothing you can interact with by entering a command + hitting enter:
You can read more about [configure the signal keyboard shortcuts](https://github.com/guard/guard/wiki/Configure-keyboard-shortcuts) in the wiki.
* `stop|quit|exit|s|q|e + enter` - Calls each guard's `#stop` method, in the same order they are declared in the Guardfile, and then quits Guard itself.
* `reload|r|z + enter` - Calls each guard's `#reload` method, in the same order they are declared in the Guardfile.
* `pause|p + enter` - Toggle files modification listening. Useful when switching git branches.
* `just enter (no commands)` - Calls each guard's `#run_all` method, in the same order they are declared in the Guardfile.
Available Guards
----------------
@ -249,10 +271,13 @@ Optional:
* The `#watch` method allows you to define which files are supervised by this guard. An optional block can be added to overwrite the paths sent to the guard's `#run_on_change` method or to launch any arbitrary command.
* The `#group` method allows you to group several guards together. Groups to be run can be specified with the Guard DSL option `--group` (or `-g`). This comes in handy especially when you have a huge Guardfile and want to focus your development on a certain part. Guards that don't belong to a group are considered global and are always run.
* The `#ignore_paths` method allows you to ignore top level directories altogether. This comes is handy when you have large amounts of non-source data in you project. By default .bundle, .git, log, tmp, and vendor are ignored. Currently it is only possible to ignore the immediate descendants of the watched directory.
Example:
``` ruby
ignore_paths 'foo', 'bar'
group 'backend' do
guard 'bundler' do
watch('Gemfile')
@ -325,6 +350,20 @@ Group frontend:
livereload
```
User config file
----------------
If a `.guard.rb` is found in your home directory, it will be appended to
the Guardfile. This can be used for tasks you want guard to handle but
other users probably don't. For example, indexing your source tree with
[Ctags](http://ctags.sourceforge.net):
``` ruby
guard 'shell' do
watch(%r{^(?:app|lib)/.+\.rb$}) { `ctags -R` }
end
```
Create a new guard
------------------

View File

@ -10,14 +10,15 @@ module Guard
autoload :Hook, 'guard/hook'
class << self
attr_accessor :options, :guards, :groups, :listener
attr_accessor :options, :guards, :groups, :interactor, :listener
# initialize this singleton
def setup(options = {})
@options = options
@listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd)
@groups = [:default]
@guards = []
@groups = [:default]
@interactor = Interactor.new
@listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd)
@options[:notify] && ENV["GUARD_NOTIFY"] != 'false' ? Notifier.turn_on : Notifier.turn_off
@ -28,24 +29,54 @@ module Guard
end
def start(options = {})
Interactor.init_signal_traps
setup(options)
Dsl.evaluate_guardfile(options)
listener.on_change do |files|
Dsl.reevaluate_guardfile if Watcher.match_guardfile?(files)
run { run_on_change_for_all_guards(files) } if Watcher.match_files?(guards, files)
listener.changed_files += files if Watcher.match_files?(guards, files)
end
UI.info "Guard is now watching at '#{listener.directory}'"
guards.each { |guard| supervised_task(guard, :start) }
interactor.start
listener.start
end
def run_on_change_for_all_guards(files)
def stop
UI.info "Bye bye...", :reset => true
listener.stop
guards.each { |guard| supervised_task(guard, :stop) }
abort
end
def reload
run do
guards.each { |guard| supervised_task(guard, :reload) }
end
end
def run_all
run do
guards.each { |guard| supervised_task(guard, :run_all) }
end
end
def pause
if listener.locked
UI.info "Un-paused files modification listening", :reset => true
listener.clear_changed_files
listener.unlock
else
UI.info "Paused files modification listening", :reset => true
listener.lock
end
end
def run_on_change(files)
run do
guards.each do |guard|
paths = Watcher.match_files(guard, files)
unless paths.empty?
@ -53,14 +84,21 @@ module Guard
supervised_task(guard, :run_on_change, paths)
end
end
# Reparse the whole directory to catch new files modified during the guards run
new_modified_files = listener.modified_files([listener.directory], :all => true)
if !new_modified_files.empty? && Watcher.match_files?(guards, new_modified_files)
run { run_on_change_for_all_guards(new_modified_files) }
end
end
def run
listener.lock
interactor.lock
UI.clear if options[:clear]
begin
yield
rescue Interrupt
end
interactor.unlock
listener.unlock
end
# Let a guard execute its task but
# fire it if his work leads to a system failure
def supervised_task(guard, task_to_supervise, *args)
@ -76,22 +114,12 @@ module Guard
return ex
end
def run
listener.stop
UI.clear if options[:clear]
begin
yield
rescue Interrupt
end
listener.start
end
def add_guard(name, watchers = [], callbacks = [], options = {})
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)
callbacks.each { |callback| ::Guard::Hook.add_callback(callback[:listener], guard_class, callback[:events]) }
callbacks.each { |callback| Hook.add_callback(callback[:listener], guard_class, callback[:events]) }
@guards << guard_class.new(watchers, options)
end
end

View File

@ -7,7 +7,8 @@ module Guard
options.is_a?(Hash) or raise ArgumentError.new("evaluate_guardfile not passed a Hash!")
@@options = options.dup
instance_eval_guardfile(fetch_guardfile_contents)
fetch_guardfile_contents
instance_eval_guardfile(guardfile_contents_with_user_config)
UI.error "No guards found in Guardfile, please add at least one." if !::Guard.guards.nil? && ::Guard.guards.empty?
end
@ -72,14 +73,17 @@ module Guard
UI.error "The command file(#{@@options[:guardfile]}) seems to be empty."
exit 1
end
guardfile_contents
end
def guardfile_contents
@@options ? @@options[:guardfile_contents] : ""
end
def guardfile_contents_with_user_config
config = File.read(user_config_path) if File.exist?(user_config_path)
[guardfile_contents, config].join("\n")
end
def guardfile_path
@@options ? @@options[:guardfile_path] : ""
end
@ -102,6 +106,10 @@ module Guard
File.expand_path(File.join("~", ".Guardfile"))
end
def user_config_path
File.expand_path(File.join("~", ".guard.rb"))
end
end
def group(name, &guard_definition)
@ -132,5 +140,9 @@ module Guard
@callbacks << { :events => events, :listener => listener }
end
def ignore_paths(*paths)
UI.info "Ignoring paths: #{paths.join(', ')}"
::Guard.listener.ignore_paths.push(*paths)
end
end
end

View File

@ -1,54 +1,40 @@
module Guard
module Interactor
extend self
class Interactor
def run_all
::Guard.run do
::Guard.guards.each { |guard| ::Guard.supervised_task(guard, :run_all) }
end
attr_reader :locked
def initialize
@locked = false
end
def stop
UI.info "Bye bye...", :reset => true
::Guard.listener.stop
::Guard.guards.each { |guard| ::Guard.supervised_task(guard, :stop) }
abort
end
def reload
::Guard.run do
::Guard.guards.each { |guard| ::Guard.supervised_task(guard, :reload) }
end
end
def self.init_signal_traps
# Run all (Ctrl-\)
if Signal.list.has_key?('QUIT')
Signal.trap('QUIT') do
run_all
end
def start
return if ENV["GUARD_ENV"] == 'test'
Thread.new do
loop do
if (entry = $stdin.gets) && !@locked
entry.gsub! /\n/, ''
case entry
when 'stop', 'quit', 'exit', 's', 'q', 'e'
::Guard.stop
when 'reload', 'r', 'z'
::Guard.reload
when 'pause', 'p'
::Guard.pause
else
UI.info "Your system doesn't support QUIT signal, so Ctrl-\\ (Run all) won't work"
::Guard.run_all
end
end
end
end
end
# Stop (Ctrl-C)
if Signal.list.has_key?('INT')
Signal.trap('INT') do
stop
end
else
UI.info "Your system doesn't support INT signal, so Ctrl-C (Stop) won't work"
def lock
@locked = true
end
# Reload (Ctrl-Z)
if Signal.list.has_key?('TSTP')
Signal.trap('TSTP') do
reload
end
else
UI.info "Your system doesn't support TSTP signal, so Ctrl-Z (Reload) won't work"
def unlock
@locked = false
end
end
end
end

View File

@ -10,7 +10,9 @@ module Guard
class Listener
attr_reader :directory
DefaultIgnorePaths = %w[. .. .bundle .git log tmp vendor]
attr_accessor :changed_files
attr_reader :directory, :ignore_paths, :locked
def self.select_and_init(*a)
if mac? && Darwin.usable?
@ -25,11 +27,32 @@ module Guard
end
end
def initialize(directory=Dir.pwd, options={})
def initialize(directory = Dir.pwd, options = {})
@directory = directory.to_s
@sha1_checksums_hash = {}
@relativize_paths = options.fetch(:relativize_paths, true)
@changed_files = []
@locked = false
@ignore_paths = DefaultIgnorePaths
@ignore_paths |= options[:ignore_paths] if options[:ignore_paths]
update_last_event
start_reactor
end
def start_reactor
return if ENV["GUARD_ENV"] == 'test'
Thread.new do
loop do
if @changed_files != [] && !@locked
changed_files = @changed_files.dup
clear_changed_files
::Guard.run_on_change(changed_files)
else
sleep 0.1
end
end
end
end
def start
@ -39,6 +62,18 @@ module Guard
def stop
end
def lock
@locked = true
end
def unlock
@locked = false
end
def clear_changed_files
@changed_files.clear
end
def on_change(&callback)
@callback = callback
end
@ -47,9 +82,10 @@ module Guard
@last_event = Time.now
end
def modified_files(dirs, options={})
files = potentially_modified_files(dirs, options).select { |path| file_modified?(path) }
def modified_files(dirs, options = {})
last_event = @last_event
update_last_event
files = potentially_modified_files(dirs, options).select { |path| file_modified?(path, last_event) }
relativize_paths(files)
end
@ -78,12 +114,17 @@ module Guard
!!@relativize_paths
end
# return children of the passed dirs that are not in the ignore_paths list
def exclude_ignored_paths(dirs, ignore_paths = self.ignore_paths)
Dir.glob(dirs.map { |d| "#{d.sub(%r{/+$}, '')}/*" }, File::FNM_DOTMATCH).reject do |path|
ignore_paths.include?(File.basename(path))
end
end
private
def potentially_modified_files(dirs, options={})
paths = Dir.glob(dirs.map { |d| "#{d.sub(%r{/+$}, '')}/*" }, File::FNM_DOTMATCH).reject do |path|
%w[. .. .bundle .git log tmp vendor].include?(File.basename(path))
end
paths = exclude_ignored_paths(dirs)
if options[:all]
paths.inject([]) do |array, path|
@ -99,14 +140,17 @@ module Guard
end
end
# Depending on the filesystem, mtime is probably only precise to the second, so round
# Depending on the filesystem, mtime/ctime is probably only precise to the second, so round
# both values down to the second for the comparison.
def file_modified?(path)
if File.mtime(path).to_i == @last_event.to_i
# ctime is used only on == comparaison to always catches Rails 3.1 Assets pipelined on Mac OSX
def file_modified?(path, last_event)
if File.ctime(path).to_i == last_event.to_i
file_content_modified?(path, sha1_checksum(path))
elsif File.mtime(path).to_i > @last_event.to_i
elsif File.mtime(path).to_i > last_event.to_i
set_sha1_checksums_hash(path, sha1_checksum(path))
true
else
false
end
rescue
false

View File

@ -3,7 +3,6 @@ module Guard
def initialize(*)
super
@inotify = INotify::Notifier.new
@files = []
@latency = 0.5
@ -47,7 +46,7 @@ module Guard
def watch(directory)
# The event selection is based on https://github.com/guard/guard/wiki/Analysis-of-inotify-events-for-different-editors
worker.watch(directory, :recursive, :create, :move_self, :close_write) do |event|
worker.watch(directory, :recursive, :attrib, :create, :move_self, :close_write) do |event|
unless event.name == "" # Event on root directory
@files << event.absolute_name
end

View File

@ -3,7 +3,6 @@ module Guard
def initialize(*)
super
@fchange = FChange::Notifier.new
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_notify or growl 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.3" unless defined? Guard::VERSION
end

View File

@ -1,7 +1,11 @@
require 'spec_helper'
describe Guard::DslDescriber do
before(:each) { ::Guard.stub!(:guards).and_return([mock('Guard')]) }
before(:each) do
::Guard.stub!(:guards).and_return([mock('Guard')])
user_config_path = File.expand_path(File.join('~', '.guard.rb'))
File.stub(:exist?).with(user_config_path) { false }
end
subject { described_class }

View File

@ -8,12 +8,18 @@ describe Guard::Dsl do
before(:each) do
@local_guardfile_path = File.join(Dir.pwd, 'Guardfile')
@home_guardfile_path = File.expand_path(File.join("~", ".Guardfile"))
@user_config_path = File.expand_path(File.join("~", ".guard.rb"))
::Guard.stub!(:options).and_return(:debug => true)
::Guard.stub!(:guards).and_return([mock('Guard')])
end
def self.disable_user_config
before(:each) { File.stub(:exist?).with(@user_config_path) { false } }
end
describe "it should select the correct data source for Guardfile" do
before(:each) { ::Guard::Dsl.stub!(:instance_eval_guardfile) }
disable_user_config
it "should use a string for initializing" do
Guard::UI.should_not_receive(:error)
@ -54,6 +60,15 @@ describe Guard::Dsl do
lambda { subject.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
subject.guardfile_contents.should == "guard :foo"
end
it 'should append the user config file if present' do
fake_guardfile('/abc/Guardfile', "guard :foo")
fake_guardfile(@user_config_path, "guard :bar")
Guard::UI.should_not_receive(:error)
lambda { subject.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
subject.guardfile_contents_with_user_config.should == "guard :foo\nguard :bar"
end
end
it "displays an error message when no Guardfile is found" do
@ -71,6 +86,7 @@ describe Guard::Dsl do
describe "correctly reads data from its valid data source" do
before(:each) { ::Guard::Dsl.stub!(:instance_eval_guardfile) }
disable_user_config
it "reads correctly from a string" do
lambda { subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
@ -202,7 +218,21 @@ describe Guard::Dsl do
end
end
describe "#ignore_paths" do
disable_user_config
it "adds the paths to the listener's ignore_paths" do
::Guard.stub!(:listener).and_return(mock('Listener'))
::Guard.listener.should_receive(:ignore_paths).and_return(ignore_paths = ['faz'])
subject.evaluate_guardfile(:guardfile_contents => "ignore_paths 'foo', 'bar'")
ignore_paths.should == ['faz', 'foo', 'bar']
end
end
describe "#group" do
disable_user_config
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 })
@ -245,6 +275,8 @@ describe Guard::Dsl do
end
describe "#guard" do
disable_user_config
it "loads a guard specified as a quoted string from the DSL" do
::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :default })
@ -277,6 +309,8 @@ describe Guard::Dsl do
end
describe "#watch" do
disable_user_config
it "should receive watchers when specified" do
::Guard.should_receive(:add_guard).with(:dummy, anything, anything, { :group => :default }) do |name, watchers, callbacks, options|
watchers.size.should == 2

View File

@ -1,35 +1,26 @@
require 'spec_helper'
describe Guard::Interactor do
subject { Guard::Interactor }
subject { Guard::Interactor.new }
let(:guard) { mock "guard" }
before :each do
Guard.stub!(:guards).and_return([guard])
Guard.stub!(:options).and_return({})
Guard.stub!(:listener).and_return(mock(:start => nil, :stop => nil))
guard.should_receive(:hook).twice
end
describe ".run_all" do
it "sends :run_all to all guards" do
guard.should_receive(:run_all)
subject.run_all
describe "#initialize" do
it "un-lock by default" do
subject.locked.should be_false
end
end
describe ".stop" do
it "sends :stop to all guards" do
guard.should_receive(:stop)
lambda { subject.stop }.should raise_error(SystemExit)
describe "#lock" do
it "locks" do
subject.lock
subject.locked.should be_true
end
end
describe ".reload" do
it "sends :reload to all guards" do
guard.should_receive(:reload)
subject.reload
describe "#unlock" do
it "unlocks" do
subject.unlock
subject.locked.should be_false
end
end
end

View File

@ -128,7 +128,6 @@ describe Guard::Listener do
end
describe "working directory" do
context "unspecified" do
subject { described_class.new }
it "defaults to Dir.pwd" do
@ -158,7 +157,33 @@ describe Guard::Listener do
stop
end
end
end
describe "#ignore_paths" do
it "defaults to the default ignore paths" do
subject.new.ignore_paths.should == Guard::Listener::DefaultIgnorePaths
end
it "can be added to via :ignore_paths option" do
listener = subject.new 'path', :ignore_paths => ['foo', 'bar']
listener.ignore_paths.should include('foo', 'bar')
end
end
describe "#exclude_ignored_paths [<dirs>]" do
let(:ignore_paths) { nil }
subject { described_class.new(@fixture_path, {:ignore_paths => ignore_paths}) }
it "returns children of <dirs>" do
subject.exclude_ignored_paths(["spec/fixtures"]).should =~ ["spec/fixtures/.dotfile", "spec/fixtures/folder1", "spec/fixtures/Guardfile"]
end
describe "when ignore_paths set to some of <dirs> children" do
let(:ignore_paths) { ['Guardfile', '.dotfile'] }
it "excludes the ignored paths" do
subject.exclude_ignored_paths(["spec/fixtures"]).should =~ ["spec/fixtures/folder1"]
end
end
end
end

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", 0.7
it_should_behave_like "a listener scoped to a specific directory", 0.7
it_should_behave_like "a listener that reacts to #on_change"
it_should_behave_like "a listener scoped to a specific directory"
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

@ -28,7 +28,7 @@ private
shared_examples_for 'a listener that reacts to #on_change' do |rest_delay|
before(:each) do
@rest_delay = rest_delay
@rest_delay = rest_delay if rest_delay.is_a?(Integer) || rest_delay.is_a?(Float) # jruby workaround
@listener = described_class.new
record_results
end
@ -61,6 +61,15 @@ shared_examples_for 'a listener that reacts to #on_change' do |rest_delay|
results.should =~ ['spec/fixtures/folder1/file1.txt']
end
it "not catches a single file chmod update" do
file = @fixture_path.join("folder1/file1.txt")
File.exists?(file).should be_true
start
File.chmod(0777, file)
stop
results.should =~ []
end
it "catches a dotfile update" do
file = @fixture_path.join(".dotfile")
File.exists?(file).should be_true
@ -108,7 +117,7 @@ end
shared_examples_for "a listener scoped to a specific directory" do |rest_delay|
before :each do
@rest_delay = rest_delay
@rest_delay = rest_delay if rest_delay.is_a?(Integer) || rest_delay.is_a?(Float) # jruby workaround
@wd = @fixture_path.join("folder1")
@listener = described_class.new @wd
end