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/* pkg/*
*.gem *.gem
*.rbc
.*.swp
*.bak
.bundle .bundle
Gemfile.lock Gemfile.lock

View File

@ -2,9 +2,13 @@ rvm:
- 1.8.7 - 1.8.7
- 1.9.2 - 1.9.2
- ree - ree
- jruby
- rbx
branches: branches:
only: only:
- master - master
- hook - hook
- stdin
- jruby_on_travis
notifications: notifications:
irc: "irc.freenode.org#guard" 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 ## 0.6.1 - August 15, 2011
### Bugs fixes: ### Bugs fixes:
@ -12,11 +51,11 @@
- Pull request [#107](https://github.com/guard/guard/pull/107): Small spelling fix. ([@dnagir][]) - 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][]) - 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][]) - 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 [#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][]) - 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 ## 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][]) - 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][]) - 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][]) - 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][]) - 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 ## 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][]) - 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][]) - 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 [#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][]) - 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][]) - 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 [@Gazer]: https://github.com/Gazer
[@gix]: https://github.com/gix [@gix]: https://github.com/gix
[@hashrocketeer]: https://github.com/hashrocketeer [@hashrocketeer]: https://github.com/hashrocketeer
[@ianwhite]: https://github.com/ianwhite
[@indirect]: https://github.com/indirect [@indirect]: https://github.com/indirect
[@jeffutter]: https://github.com/jeffutter [@jeffutter]: https://github.com/jeffutter
[@johnbintz]: https://github.com/johnbintz [@johnbintz]: https://github.com/johnbintz

View File

@ -12,7 +12,7 @@ require 'rbconfig'
if RbConfig::CONFIG['target_os'] =~ /darwin/i if RbConfig::CONFIG['target_os'] =~ /darwin/i
gem 'rb-fsevent', '>= 0.4.0', :require => false gem 'rb-fsevent', '>= 0.4.0', :require => false
gem 'growl_notify', :require => false gem 'growl', '~> 1.0.3', :require => false
end end
if RbConfig::CONFIG['target_os'] =~ /linux/i if RbConfig::CONFIG['target_os'] =~ /linux/i
gem 'rb-inotify', '>= 0.8.5', :require => false 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{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch('spec/spec_helper.rb') { "spec" } 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. 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). * [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). * 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). * 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). * 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)).
* Libnotify notifications ([libnotify gem](https://rubygems.org/gems/libnotify) required).
* Tested against Ruby 1.8.7, 1.9.2 and REE. * Tested against Ruby 1.8.7, 1.9.2 and REE.
Screencast 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 Install
------- -------
@ -31,12 +30,18 @@ Install the gem:
$ gem install guard $ gem install guard
``` ```
Add it to your Gemfile (inside the `development` group): Or add it to your Gemfile (inside the `development` group):
``` ruby ``` ruby
gem 'guard' gem 'guard'
``` ```
and install it via Bundler:
``` bash
$ bundle install
```
Generate an empty Guardfile with: Generate an empty Guardfile with:
``` bash ``` bash
@ -44,6 +49,7 @@ $ guard init
``` ```
You may optionally place a .Guardfile in your home directory to use it across multiple projects. 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). 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 $ 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 ``` bash
$ gem install growl_notify $ 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 ``` ruby
gem 'rb-fsevent' 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 ### 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 ``` bash
$ gem install rb-inotify $ 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 ``` bash
$ gem install libnotify $ gem install libnotify
@ -91,19 +111,19 @@ gem 'libnotify'
### On Windows ### 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 ``` bash
$ gem install rb-fchange $ 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 ``` bash
$ gem install win32console $ 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 ``` bash
$ gem install rb-notifu $ gem install rb-notifu
@ -114,6 +134,7 @@ And add them to your Gemfile:
``` ruby ``` ruby
gem 'rb-fchange' gem 'rb-fchange'
gem 'rb-notifu' gem 'rb-notifu'
gem 'win32console'
``` ```
Usage Usage
@ -198,16 +219,17 @@ An exhaustive list of options is available with:
$ guard help [TASK] $ 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. When Guard do nothing you can interact with by entering a command + hitting enter:
* `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.
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 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 `#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 `#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: Example:
``` ruby ``` ruby
ignore_paths 'foo', 'bar'
group 'backend' do group 'backend' do
guard 'bundler' do guard 'bundler' do
watch('Gemfile') watch('Gemfile')
@ -325,6 +350,20 @@ Group frontend:
livereload 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 Create a new guard
------------------ ------------------

View File

@ -10,14 +10,15 @@ module Guard
autoload :Hook, 'guard/hook' autoload :Hook, 'guard/hook'
class << self class << self
attr_accessor :options, :guards, :groups, :listener attr_accessor :options, :guards, :groups, :interactor, :listener
# initialize this singleton # initialize this singleton
def setup(options = {}) def setup(options = {})
@options = options @options = options
@listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd)
@groups = [:default]
@guards = [] @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 @options[:notify] && ENV["GUARD_NOTIFY"] != 'false' ? Notifier.turn_on : Notifier.turn_off
@ -28,24 +29,54 @@ module Guard
end end
def start(options = {}) def start(options = {})
Interactor.init_signal_traps
setup(options) setup(options)
Dsl.evaluate_guardfile(options) Dsl.evaluate_guardfile(options)
listener.on_change do |files| listener.on_change do |files|
Dsl.reevaluate_guardfile if Watcher.match_guardfile?(files) Dsl.reevaluate_guardfile if Watcher.match_guardfile?(files)
listener.changed_files += files if Watcher.match_files?(guards, files)
run { run_on_change_for_all_guards(files) } if Watcher.match_files?(guards, files)
end end
UI.info "Guard is now watching at '#{listener.directory}'" UI.info "Guard is now watching at '#{listener.directory}'"
guards.each { |guard| supervised_task(guard, :start) } guards.each { |guard| supervised_task(guard, :start) }
interactor.start
listener.start listener.start
end 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| guards.each do |guard|
paths = Watcher.match_files(guard, files) paths = Watcher.match_files(guard, files)
unless paths.empty? unless paths.empty?
@ -53,14 +84,21 @@ module Guard
supervised_task(guard, :run_on_change, paths) supervised_task(guard, :run_on_change, paths)
end end
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
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 # Let a guard execute its task but
# fire it if his work leads to a system failure # fire it if his work leads to a system failure
def supervised_task(guard, task_to_supervise, *args) def supervised_task(guard, task_to_supervise, *args)
@ -76,22 +114,12 @@ module Guard
return ex return ex
end 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 = {}) def add_guard(name, watchers = [], callbacks = [], options = {})
if name.to_sym == :ego if name.to_sym == :ego
UI.deprecation("Guard::Ego is now part of Guard. You can remove it from your Guardfile.") UI.deprecation("Guard::Ego is now part of Guard. You can remove it from your Guardfile.")
else else
guard_class = get_guard_class(name) 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) @guards << guard_class.new(watchers, options)
end end
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.is_a?(Hash) or raise ArgumentError.new("evaluate_guardfile not passed a Hash!")
@@options = options.dup @@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? UI.error "No guards found in Guardfile, please add at least one." if !::Guard.guards.nil? && ::Guard.guards.empty?
end end
@ -72,14 +73,17 @@ module Guard
UI.error "The command file(#{@@options[:guardfile]}) seems to be empty." UI.error "The command file(#{@@options[:guardfile]}) seems to be empty."
exit 1 exit 1
end end
guardfile_contents
end end
def guardfile_contents def guardfile_contents
@@options ? @@options[:guardfile_contents] : "" @@options ? @@options[:guardfile_contents] : ""
end 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 def guardfile_path
@@options ? @@options[:guardfile_path] : "" @@options ? @@options[:guardfile_path] : ""
end end
@ -102,6 +106,10 @@ module Guard
File.expand_path(File.join("~", ".Guardfile")) File.expand_path(File.join("~", ".Guardfile"))
end end
def user_config_path
File.expand_path(File.join("~", ".guard.rb"))
end
end end
def group(name, &guard_definition) def group(name, &guard_definition)
@ -132,5 +140,9 @@ module Guard
@callbacks << { :events => events, :listener => listener } @callbacks << { :events => events, :listener => listener }
end end
def ignore_paths(*paths)
UI.info "Ignoring paths: #{paths.join(', ')}"
::Guard.listener.ignore_paths.push(*paths)
end
end end
end end

View File

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

View File

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

View File

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

View File

@ -3,7 +3,6 @@ module Guard
def initialize(*) def initialize(*)
super super
@fchange = FChange::Notifier.new @fchange = FChange::Notifier.new
end end

View File

@ -47,10 +47,18 @@ module Guard
def self.notify_mac(title, message, image, options) def self.notify_mac(title, message, image, options)
require_growl # need for guard-rspec formatter that is called out of guard scope 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) default_options = { :title => title, :icon => image_path(image), :name => APPLICATION_NAME }
options.delete(: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 end
def self.notify_linux(title, message, image, options) def self.notify_linux(title, message, image, options)
@ -94,6 +102,7 @@ module Guard
end end
def self.require_growl def self.require_growl
begin
require 'growl_notify' require 'growl_notify'
if GrowlNotify.application_name != APPLICATION_NAME if GrowlNotify.application_name != APPLICATION_NAME
@ -102,9 +111,12 @@ module Guard
c.application_name = c.notifications.first c.application_name = c.notifications.first
end end
end end
rescue LoadError
require 'growl'
end
rescue LoadError rescue LoadError
turn_off 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 end
def self.require_libnotify def self.require_libnotify

View File

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

View File

@ -1,7 +1,11 @@
require 'spec_helper' require 'spec_helper'
describe Guard::DslDescriber do 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 } subject { described_class }

View File

@ -8,12 +8,18 @@ describe Guard::Dsl do
before(:each) do before(:each) do
@local_guardfile_path = File.join(Dir.pwd, 'Guardfile') @local_guardfile_path = File.join(Dir.pwd, 'Guardfile')
@home_guardfile_path = File.expand_path(File.join("~", ".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!(:options).and_return(:debug => true)
::Guard.stub!(:guards).and_return([mock('Guard')]) ::Guard.stub!(:guards).and_return([mock('Guard')])
end 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 describe "it should select the correct data source for Guardfile" do
before(:each) { ::Guard::Dsl.stub!(:instance_eval_guardfile) } before(:each) { ::Guard::Dsl.stub!(:instance_eval_guardfile) }
disable_user_config
it "should use a string for initializing" do it "should use a string for initializing" do
Guard::UI.should_not_receive(:error) 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 lambda { subject.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
subject.guardfile_contents.should == "guard :foo" subject.guardfile_contents.should == "guard :foo"
end 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 end
it "displays an error message when no Guardfile is found" do 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 describe "correctly reads data from its valid data source" do
before(:each) { ::Guard::Dsl.stub!(:instance_eval_guardfile) } before(:each) { ::Guard::Dsl.stub!(:instance_eval_guardfile) }
disable_user_config
it "reads 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 lambda { subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
@ -202,7 +218,21 @@ describe Guard::Dsl do
end end
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 describe "#group" do
disable_user_config
it "evaluates only the specified string group" do it "evaluates only the specified string group" do
::Guard.should_receive(:add_guard).with(:pow, [], [], { :group => :default }) ::Guard.should_receive(:add_guard).with(:pow, [], [], { :group => :default })
::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :w }) ::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :w })
@ -245,6 +275,8 @@ describe Guard::Dsl do
end end
describe "#guard" do describe "#guard" do
disable_user_config
it "loads a guard specified as a quoted string from the DSL" do it "loads a guard specified as a quoted string from the DSL" do
::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :default }) ::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :default })
@ -277,6 +309,8 @@ describe Guard::Dsl do
end end
describe "#watch" do describe "#watch" do
disable_user_config
it "should receive watchers when specified" do it "should receive watchers when specified" do
::Guard.should_receive(:add_guard).with(:dummy, anything, anything, { :group => :default }) do |name, watchers, callbacks, options| ::Guard.should_receive(:add_guard).with(:dummy, anything, anything, { :group => :default }) do |name, watchers, callbacks, options|
watchers.size.should == 2 watchers.size.should == 2

View File

@ -1,35 +1,26 @@
require 'spec_helper' require 'spec_helper'
describe Guard::Interactor do describe Guard::Interactor do
subject { Guard::Interactor } subject { Guard::Interactor.new }
let(:guard) { mock "guard" } describe "#initialize" do
it "un-lock by default" do
before :each do subject.locked.should be_false
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
end end
end end
describe ".stop" do describe "#lock" do
it "sends :stop to all guards" do it "locks" do
guard.should_receive(:stop) subject.lock
lambda { subject.stop }.should raise_error(SystemExit) subject.locked.should be_true
end end
end end
describe ".reload" do describe "#unlock" do
it "sends :reload to all guards" do it "unlocks" do
guard.should_receive(:reload) subject.unlock
subject.reload subject.locked.should be_false
end end
end end
end end

View File

@ -128,7 +128,6 @@ describe Guard::Listener do
end end
describe "working directory" do describe "working directory" do
context "unspecified" do context "unspecified" do
subject { described_class.new } subject { described_class.new }
it "defaults to Dir.pwd" do it "defaults to Dir.pwd" do
@ -158,7 +157,33 @@ describe Guard::Listener do
stop stop
end end
end 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 end

View File

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

View File

@ -39,9 +39,19 @@ describe Guard::Notifier do
end end
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 it "disables the notifications" do
subject.should_receive(:require).with('growl_notify').and_raise LoadError subject.should_receive(:require).with('growl_notify').and_raise LoadError
subject.should_receive(:require).with('growl').and_raise LoadError
subject.turn_on subject.turn_on
subject.should_not be_enabled subject.should_not be_enabled
end end
@ -102,6 +112,51 @@ describe Guard::Notifier do
subject.stub(:require_growl) subject.stub(:require_growl)
end 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 context 'with growl_notify gem' do
before do before do
Object.send(:remove_const, :GrowlNotify) if defined?(GrowlNotify) 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| shared_examples_for 'a listener that reacts to #on_change' do |rest_delay|
before(:each) do 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 @listener = described_class.new
record_results record_results
end 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'] results.should =~ ['spec/fixtures/folder1/file1.txt']
end 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 it "catches a dotfile update" do
file = @fixture_path.join(".dotfile") file = @fixture_path.join(".dotfile")
File.exists?(file).should be_true File.exists?(file).should be_true
@ -108,7 +117,7 @@ end
shared_examples_for "a listener scoped to a specific directory" do |rest_delay| shared_examples_for "a listener scoped to a specific directory" do |rest_delay|
before :each do 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") @wd = @fixture_path.join("folder1")
@listener = described_class.new @wd @listener = described_class.new @wd
end end