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:
commit
11495687f4
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,5 +1,8 @@
|
||||
pkg/*
|
||||
*.gem
|
||||
*.rbc
|
||||
.*.swp
|
||||
*.bak
|
||||
.bundle
|
||||
Gemfile.lock
|
||||
|
||||
|
@ -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"
|
||||
irc: "irc.freenode.org#guard"
|
||||
|
52
CHANGELOG.md
52
CHANGELOG.md
@ -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
|
||||
|
2
Gemfile
2
Gemfile
@ -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
|
||||
|
@ -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" }
|
||||
|
79
README.md
79
README.md
@ -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
|
||||
------------------
|
||||
|
||||
|
90
lib/guard.rb
90
lib/guard.rb
@ -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 = []
|
||||
@options = options
|
||||
@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,37 +29,74 @@ 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)
|
||||
Dsl.reevaluate_guardfile if Watcher.match_guardfile?(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)
|
||||
guards.each do |guard|
|
||||
paths = Watcher.match_files(guard, files)
|
||||
unless paths.empty?
|
||||
UI.debug "#{guard.class.name}#run_on_change with #{paths.inspect}"
|
||||
supervised_task(guard, :run_on_change, paths)
|
||||
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?
|
||||
UI.debug "#{guard.class.name}#run_on_change with #{paths.inspect}"
|
||||
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) }
|
||||
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
|
||||
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
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
|
||||
::Guard.run_all
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
UI.info "Your system doesn't support QUIT signal, so Ctrl-\\ (Run all) won't work"
|
||||
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"
|
||||
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"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def lock
|
||||
@locked = true
|
||||
end
|
||||
|
||||
def unlock
|
||||
@locked = false
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -9,8 +9,10 @@ module Guard
|
||||
autoload :Polling, 'guard/listeners/polling'
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -3,7 +3,6 @@ module Guard
|
||||
|
||||
def initialize(*)
|
||||
super
|
||||
|
||||
@fchange = FChange::Notifier.new
|
||||
end
|
||||
|
||||
|
@ -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,17 +102,21 @@ module Guard
|
||||
end
|
||||
|
||||
def self.require_growl
|
||||
require 'growl_notify'
|
||||
begin
|
||||
require 'growl_notify'
|
||||
|
||||
if GrowlNotify.application_name != APPLICATION_NAME
|
||||
GrowlNotify.config do |c|
|
||||
c.notifications = c.default_notifications = [ APPLICATION_NAME ]
|
||||
c.application_name = c.notifications.first
|
||||
if GrowlNotify.application_name != APPLICATION_NAME
|
||||
GrowlNotify.config do |c|
|
||||
c.notifications = c.default_notifications = [ APPLICATION_NAME ]
|
||||
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
|
||||
|
@ -1,3 +1,3 @@
|
||||
module Guard
|
||||
VERSION = "0.6.1" unless defined? Guard::VERSION
|
||||
VERSION = "0.6.3" unless defined? Guard::VERSION
|
||||
end
|
||||
|
@ -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 }
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -55,10 +55,10 @@ describe Guard::Listener do
|
||||
it "should relativize paths to the configured directory" do
|
||||
subject.relativize_paths(@paths).should =~ %w( a a/b a.b/c.d )
|
||||
end
|
||||
|
||||
|
||||
context "when set to false" do
|
||||
subject { described_class.new('/tmp', :relativize_paths => false) }
|
||||
|
||||
|
||||
it "can be disabled" do
|
||||
subject.relativize_paths(@paths).should eql @paths
|
||||
end
|
||||
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user