Merge branch 'master' into guard_dependencies

Conflicts:
	lib/guard.rb
	lib/guard/dsl.rb
	man/guard
	man/guard.html
This commit is contained in:
Rémy Coutable 2011-09-22 22:44:15 +02:00
commit 4c1cf825e5
28 changed files with 1466 additions and 823 deletions

4
.gitignore vendored
View File

@ -1,9 +1,11 @@
pkg/* pkg/*
doc/*
*.gem *.gem
*.rbc *.rbc
.*.swp .*.swp
*.bak *.bak
.bundle .bundle
.yardoc
Gemfile.lock Gemfile.lock
## MAC OS ## MAC OS
@ -12,4 +14,4 @@ Gemfile.lock
.com.apple.timemachine.supported .com.apple.timemachine.supported
.fseventsd .fseventsd
Desktop DB Desktop DB
Desktop DF Desktop DF

11
.yardopts Normal file
View File

@ -0,0 +1,11 @@
--title 'Guard Documentation'
--readme README.md
--markup markdown
--markup-provider kramdown
--private
--protected
--output-dir ./doc
lib/**/*.rb
-
CHANGELOG.md
LICENSE

View File

@ -1,3 +1,7 @@
## Master
- Pull request [#137](https://github.com/guard/guard/pull/137): Fix interacting with tools like ruby-debug. ([@hron][] & [@netzpirat][])
## 0.7.0 - September 14, 2011 ## 0.7.0 - September 14, 2011
## 0.7.0.rc1 - September 5, 2011 ## 0.7.0.rc1 - September 5, 2011
@ -250,6 +254,7 @@
[@fnichol]: https://github.com/fnichol [@fnichol]: https://github.com/fnichol
[@Gazer]: https://github.com/Gazer [@Gazer]: https://github.com/Gazer
[@gix]: https://github.com/gix [@gix]: https://github.com/gix
[@hron]: https://github.com/hron
[@hashrocketeer]: https://github.com/hashrocketeer [@hashrocketeer]: https://github.com/hashrocketeer
[@ianwhite]: https://github.com/ianwhite [@ianwhite]: https://github.com/ianwhite
[@indirect]: https://github.com/indirect [@indirect]: https://github.com/indirect

373
README.md
View File

@ -26,27 +26,19 @@ Install
Install the gem: Install the gem:
``` bash $ gem install guard
$ gem install guard
```
Or add it to your Gemfile (inside the `development` group): Or add it to your Gemfile (inside the `development` group):
``` ruby gem 'guard'
gem 'guard'
```
and install it via Bundler: and install it via Bundler:
``` bash $ bundle install
$ bundle install
```
Generate an empty Guardfile with: Generate an empty Guardfile with:
``` bash $ guard init
$ 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. Also note that if a `.guard.rb` is found in your home directory, it will be appended to the Guardfile.
@ -57,31 +49,23 @@ Add the guards you need to your Guardfile (see the existing guards below).
Install the rb-fsevent gem for [FSEvent](http://en.wikipedia.org/wiki/FSEvents) support: Install the rb-fsevent gem for [FSEvent](http://en.wikipedia.org/wiki/FSEvents) support:
``` bash $ gem install rb-fsevent
$ gem install rb-fsevent
```
You have two possibilities: You have two possibilities:
Use the [growl_notify gem](https://rubygems.org/gems/growl_notify) (recommended): Use the [growl_notify gem](https://rubygems.org/gems/growl_notify) (recommended):
``` bash $ gem install growl_notify
$ gem install growl_notify
```
Use the [growlnotify](http://growl.info/extras.php#growlnotify) (cli tool for growl) + the [growl gem](https://rubygems.org/gems/growl). 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
$ brew install growlnotify $ gem install growl
$ gem install growl
```
And add them to your Gemfile: And add them to your Gemfile:
``` ruby gem 'rb-fsevent'
gem 'rb-fsevent' gem 'growl_notify' # or gem 'growl'
gem 'growl_notify' # or gem 'growl'
```
The difference between growl and growl_notify is that growl_notify uses AppleScript to 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 display a message, whereas growl uses the `growlnotify` command. In general the AppleScript
@ -92,65 +76,47 @@ approach is preferred, but you may also use the older growl gem. Have a look at
Install the [rb-inotify gem](https://rubygems.org/gems/rb-inotify) 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
$ gem install rb-inotify
```
Install the [libnotify gem](https://rubygems.org/gems/libnotify) if you want visual notification support: Install the [libnotify gem](https://rubygems.org/gems/libnotify) if you want visual notification support:
``` bash $ gem install libnotify
$ gem install libnotify
```
And add them to your Gemfile: And add them to your Gemfile:
``` ruby gem 'rb-inotify'
gem 'rb-inotify' gem 'libnotify'
gem 'libnotify'
```
### On Windows ### On Windows
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: 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
$ gem install rb-fchange
```
Install the [win32console gem](https://rubygems.org/gems/win32console) 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
$ gem install win32console
```
Install the [rb-notifu gem](https://rubygems.org/gems/rb-notifu) if you want visual notification support: Install the [rb-notifu gem](https://rubygems.org/gems/rb-notifu) if you want visual notification support:
``` bash $ gem install rb-notifu
$ gem install rb-notifu
```
And add them to your Gemfile: And add them to your Gemfile:
``` ruby gem 'rb-fchange'
gem 'rb-fchange' gem 'rb-notifu'
gem 'rb-notifu' gem 'win32console'
gem 'win32console'
```
Usage Usage
----- -----
Just launch Guard inside your Ruby / Rails project with: Just launch Guard inside your Ruby / Rails project with:
``` bash $ guard [start]
$ guard [start]
```
or if you use Bundler, to run the Guard executable specific to your bundle: or if you use Bundler, to run the Guard executable specific to your bundle:
``` bash $ bundle exec guard [start]
$ bundle exec guard [start]
```
Guard will look for a Guardfile in your current directory. If it does not find one, it will look in your `$HOME` directory for a .Guardfile. Guard will look for a Guardfile in your current directory. If it does not find one, it will look in your `$HOME` directory for a .Guardfile.
@ -161,19 +127,15 @@ Command line options
Shell can be cleared after each change: Shell can be cleared after each change:
``` bash $ guard --clear
$ guard --clear $ guard -c # shortcut
$ guard -c # shortcut
```
### `-n`/`--notify` option ### `-n`/`--notify` option
Notifications (growl/libnotify) can be disabled: Notifications (growl/libnotify) can be disabled:
``` bash $ guard --notify false
$ guard --notify false $ guard -n f # shortcut
$ guard -n f # shortcut
```
Notifications can also be disabled globally by setting a `GUARD_NOTIFY` environment variable to `false` Notifications can also be disabled globally by setting a `GUARD_NOTIFY` environment variable to `false`
@ -181,43 +143,33 @@ Notifications can also be disabled globally by setting a `GUARD_NOTIFY` environm
Only certain guards groups can be run (see the Guardfile DSL below for creating groups): Only certain guards groups can be run (see the Guardfile DSL below for creating groups):
``` bash $ guard --group group_name another_group_name
$ guard --group group_name another_group_name $ guard -g group_name another_group_name # shortcut
$ guard -g group_name another_group_name # shortcut
```
### `-d`/`--debug` option ### `-d`/`--debug` option
Guard can be run in debug mode: Guard can be run in debug mode:
``` bash $ guard --debug
$ guard --debug $ guard -d # shortcut
$ guard -d # shortcut
```
### `-w`/`--watchdir` option ### `-w`/`--watchdir` option
Guard can watch in any directory (instead of the current directory): Guard can watch in any directory (instead of the current directory):
``` bash $ guard --watchdir ~/your/fancy/project
$ guard --watchdir ~/your/fancy/project $ guard -w ~/your/fancy/project # shortcut
$ guard -w ~/your/fancy/project # shortcut
```
### `-G`/`--guardfile` option ### `-G`/`--guardfile` option
Guard can use a Guardfile not located in the current directory: Guard can use a Guardfile not located in the current directory:
``` bash $ guard --guardfile ~/.your_global_guardfile
$ guard --guardfile ~/.your_global_guardfile $ guard -G ~/.your_global_guardfile # shortcut
$ guard -G ~/.your_global_guardfile # shortcut
```
An exhaustive list of options is available with: An exhaustive list of options is available with:
``` bash $ guard help [TASK]
$ guard help [TASK]
```
Interactions Interactions
------------ ------------
@ -240,21 +192,15 @@ A list of the available guards is present [in the wiki](https://github.com/guard
Add it to your Gemfile (inside the `development` group): Add it to your Gemfile (inside the `development` group):
``` ruby gem '<guard-name>'
gem '<guard-name>'
```
You can list all guards installed on your system with: You can list all guards installed on your system with:
``` bash $ guard list
$ guard list
```
Insert default guard's definition to your Guardfile by running this command: Insert default guard's definition to your Guardfile by running this command:
``` bash $ guard init <guard-name>
$ guard init <guard-name>
```
You are good to go, or you can modify your guards' definition to suit your needs. You are good to go, or you can modify your guards' definition to suit your needs.
@ -271,36 +217,34 @@ The Guardfile DSL consists of the following methods:
Example: Example:
``` ruby ignore_paths 'foo', 'bar'
ignore_paths 'foo', 'bar'
group 'backend' do group 'backend' do
guard 'bundler' do guard 'bundler' do
watch('Gemfile') watch('Gemfile')
end end
guard 'rspec', :cli => '--color --format doc' do guard 'rspec', :cli => '--color --format doc' do
# Regexp watch patterns are matched with Regexp#match # Regexp watch patterns are matched with Regexp#match
watch(%r{^spec/.+_spec\.rb$}) watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
watch(%r{^spec/models/.+\.rb$}) { ["spec/models", "spec/acceptance"] } watch(%r{^spec/models/.+\.rb$}) { ["spec/models", "spec/acceptance"] }
watch(%r{^spec/.+\.rb$}) { `say hello` } watch(%r{^spec/.+\.rb$}) { `say hello` }
# String watch patterns are matched with simple '==' # String watch patterns are matched with simple '=='
watch('spec/spec_helper.rb') { "spec" } watch('spec/spec_helper.rb') { "spec" }
end end
end end
group 'frontend' do group 'frontend' do
guard 'coffeescript', :output => 'public/javascripts/compiled' do guard 'coffeescript', :output => 'public/javascripts/compiled' do
watch(%r{^app/coffeescripts/.+\.coffee$}) watch(%r{^app/coffeescripts/.+\.coffee$})
end end
guard 'livereload' do guard 'livereload' do
watch(%r{^app/.+\.(erb|haml)$}) watch(%r{^app/.+\.(erb|haml)$})
end end
end end
```
Using a Guardfile without the `guard` binary Using a Guardfile without the `guard` binary
-------------------------------------------- --------------------------------------------
@ -315,37 +259,33 @@ Remember, without any options given, Guard will look for a Guardfile in your cur
For instance, you could use it as follow: For instance, you could use it as follow:
``` ruby gem 'guard'
gem 'guard' require 'guard'
require 'guard'
Guard.setup Guard.setup
Guard::Dsl.evaluate_guardfile(:guardfile => '/your/custom/path/to/a/valid/Guardfile') Guard::Dsl.evaluate_guardfile(:guardfile => '/your/custom/path/to/a/valid/Guardfile')
# or # or
Guard::Dsl.evaluate_guardfile(:guardfile_contents => " Guard::Dsl.evaluate_guardfile(:guardfile_contents => "
guard 'rspec' do guard 'rspec' do
watch(%r{^spec/.+_spec\.rb$}) watch(%r{^spec/.+_spec\.rb$})
end end
") ")
```
### Listing defined guards/groups for the current project ### Listing defined guards/groups for the current project
You can list the defined groups and guards for the current Guardfile from the command line using `guard show` or `guard -T`: You can list the defined groups and guards for the current Guardfile from the command line using `guard show` or `guard -T`:
``` bash $ guard -T
# guard -T
(global): (global):
shell shell
Group backend: Group backend:
bundler bundler
rspec: cli => "--color --format doc" rspec: cli => "--color --format doc"
Group frontend: Group frontend:
coffeescript: output => "public/javascripts/compiled" coffeescript: output => "public/javascripts/compiled"
livereload livereload
```
User config file User config file
---------------- ----------------
@ -355,114 +295,107 @@ 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 other users probably don't. For example, indexing your source tree with
[Ctags](http://ctags.sourceforge.net): [Ctags](http://ctags.sourceforge.net):
``` ruby guard 'shell' do
guard 'shell' do watch(%r{^(?:app|lib)/.+\.rb$}) { `ctags -R` }
watch(%r{^(?:app|lib)/.+\.rb$}) { `ctags -R` } end
end
```
Create a new guard Create a new guard
------------------ ------------------
Creating a new guard is very easy, just create a new gem (`bundle gem` if you use Bundler) with this basic structure: Creating a new guard is very easy, just create a new gem (`bundle gem` if you use Bundler) with this basic structure:
``` .travis.yml # bonus point!
.travis.yml # bonus point! CHANGELOG.md # bonus point!
CHANGELOG.md # bonus point! Gemfile
Gemfile guard-name.gemspec
guard-name.gemspec Guardfile
Guardfile lib/
lib/ guard/
guard/ guard-name/
guard-name/ templates/
templates/ Guardfile # needed for `guard init <guard-name>`
Guardfile # needed for `guard init <guard-name>` version.rb
version.rb guard-name.rb
guard-name.rb test/ # or spec/
test/ # or spec/ README.md
README.md
```
`Guard::GuardName` (in `lib/guard/guard-name.rb`) must inherit from `Guard::Guard` and should overwrite at least one of the five basic `Guard::Guard` instance methods. `Guard::GuardName` (in `lib/guard/guard-name.rb`) must inherit from `Guard::Guard` and should overwrite at least one of the five basic `Guard::Guard` instance methods.
Here is an example scaffold for `lib/guard/guard-name.rb`: Here is an example scaffold for `lib/guard/guard-name.rb`:
``` ruby require 'guard'
require 'guard' require 'guard/guard'
require 'guard/guard'
module Guard module Guard
class GuardName < Guard class GuardName < Guard
def initialize(watchers=[], options={}) def initialize(watchers=[], options={})
super super
# init stuff here, thx! # init stuff here, thx!
end
# =================
# = Guard methods =
# =================
# If one of those methods raise an exception, the Guard::GuardName instance
# will be removed from the active guards.
# Called once when Guard starts
# Please override initialize method to init stuff
def start
true
end
# Called when `stop|quit|exit|s|q|e + enter` is pressed (when Guard quits)
def stop
true
end
# Called when `reload|r|z + enter` is pressed
# This method should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/...
def reload
true
end
# Called when just `enter` is pressed
# This method should be principally used for long action like running all specs/tests/...
def run_all
true
end
# Called on file(s) modifications
def run_on_change(paths)
true
end
end
end end
# =================
# = Guard methods =
# =================
# If one of those methods raise an exception, the Guard::GuardName instance
# will be removed from the active guards.
# Called once when Guard starts
# Please override initialize method to init stuff
def start
true
end
# Called on Ctrl-C signal (when Guard quits)
def stop
true
end
# Called on Ctrl-Z signal
# This method should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/...
def reload
true
end
# Called on Ctrl-\ signal
# This method should be principally used for long action like running all specs/tests/...
def run_all
true
end
# Called on file(s) modifications
def run_on_change(paths)
true
end
end
end
```
Please take a look at the [existing guards' source code](https://github.com/guard/guard/wiki/List-of-available-Guards) for more concrete example and inspiration. Please take a look at the [existing guards' source code](https://github.com/guard/guard/wiki/List-of-available-Guards) for more concrete example and inspiration.
Alternatively, a new guard can be added inline to a Guardfile with this basic structure: Alternatively, a new guard can be added inline to a Guardfile with this basic structure:
``` ruby require 'guard/guard'
require 'guard/guard'
module ::Guard module ::Guard
class InlineGuard < ::Guard::Guard class InlineGuard < ::Guard::Guard
def run_all def run_all
true true
end end
def run_on_change(paths) def run_on_change(paths)
true true
end
end
end end
end
end
```
Here is a very cool example by [@avdi](https://github.com/avdi) : http://avdi.org/devblog/2011/06/15/a-guardfile-for-redis Here is a very cool example by [@avdi](https://github.com/avdi) : http://avdi.org/devblog/2011/06/15/a-guardfile-for-redis
Development Development
----------- -----------
* Documentation hosted at [RubyDoc](http://rubydoc.info/github/guard/guard/master/frames).
* Source hosted at [GitHub](https://github.com/guard/guard). * Source hosted at [GitHub](https://github.com/guard/guard).
* Report issues and feature requests to [GitHub Issues](https://github.com/guard/guard/issues). * Report issues and feature requests to [GitHub Issues](https://github.com/guard/guard/issues).

View File

@ -14,11 +14,13 @@ Gem::Specification.new do |s|
s.required_rubygems_version = '>= 1.3.6' s.required_rubygems_version = '>= 1.3.6'
s.rubyforge_project = 'guard' s.rubyforge_project = 'guard'
s.add_dependency 'thor', '~> 0.14.6'
s.add_development_dependency 'bundler' s.add_development_dependency 'bundler'
s.add_development_dependency 'rspec', '~> 2.6.0' s.add_development_dependency 'rspec', '~> 2.6.0'
s.add_development_dependency 'guard-rspec', '~> 0.3.1' s.add_development_dependency 'guard-rspec', '~> 0.3.1'
s.add_development_dependency 'yard', '~> 0.7.2'
s.add_dependency 'thor', '~> 0.14.6' s.add_development_dependency 'kramdown', '~> 0.13.3'
s.files = Dir.glob('{bin,images,lib}/**/*') + %w[CHANGELOG.md LICENSE man/guard.1 man/guard.1.html README.md] s.files = Dir.glob('{bin,images,lib}/**/*') + %w[CHANGELOG.md LICENSE man/guard.1 man/guard.1.html README.md]
s.executable = 'guard' s.executable = 'guard'

View File

@ -1,3 +1,6 @@
# Guard is the main module for all Guard related modules and classes.
# Also other Guard implementation should use this namespace.
#
module Guard module Guard
autoload :UI, 'guard/ui' autoload :UI, 'guard/ui'
@ -12,7 +15,16 @@ module Guard
class << self class << self
attr_accessor :options, :guards, :groups, :interactor, :listener attr_accessor :options, :guards, :groups, :interactor, :listener
# initialize this singleton # Initialize the Guard singleton.
#
# @param [Hash] options the Guard options.
# @option options [Boolean] clear if auto clear the UI should be done
# @option options [Boolean] notify if system notifications should be shown
# @option options [Boolean] debug if debug output should be shown
# @option options [Array<String>] group the list of groups to start
# @option options [String] watchdir the director to watch
# @option options [String] guardfile the path to the Guardfile
#
def setup(options = {}) def setup(options = {})
@options = options @options = options
@guards = [] @guards = []
@ -20,7 +32,7 @@ module Guard
@interactor = Interactor.new @interactor = Interactor.new
@listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd) @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
UI.clear if @options[:clear] UI.clear if @options[:clear]
debug_command_execution if @options[:debug] debug_command_execution if @options[:debug]
@ -28,6 +40,17 @@ module Guard
self self
end end
# Start Guard by evaluate the `Guardfile`, initialize the declared Guards
# and start the available file change listener.
#
# @param [Hash] options the Guard options.
# @option options [Boolean] clear if auto clear the UI should be done
# @option options [Boolean] notify if system notifications should be shown
# @option options [Boolean] debug if debug output should be shown
# @option options [Array<String>] group the list of groups to start
# @option options [String] watchdir the director to watch
# @option options [String] guardfile the path to the Guardfile
#
def start(options = {}) def start(options = {})
setup(options) setup(options)
@ -38,7 +61,7 @@ module Guard
listener.changed_files += files if Watcher.match_files?(guards, files) listener.changed_files += 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 }'"
execute_supervised_task_for_all_guards(:start) execute_supervised_task_for_all_guards(:start)
@ -46,42 +69,57 @@ module Guard
listener.start listener.start
end end
# Stop Guard listening to file changes
#
def stop def stop
UI.info "Bye bye...", :reset => true UI.info 'Bye bye...', :reset => true
listener.stop listener.stop
execute_supervised_task_for_all_guards(:stop) execute_supervised_task_for_all_guards(:stop)
abort abort
end end
# Reload all Guards currently enabled.
#
def reload def reload
run do run do
execute_supervised_task_for_all_guards(:reload) execute_supervised_task_for_all_guards(:reload)
end end
end end
# Trigger `run_all` on all Guards currently enabled.
#
def run_all def run_all
run do run do
execute_supervised_task_for_all_guards(:run_all) execute_supervised_task_for_all_guards(:run_all)
end end
end end
# Pause Guard listening to file changes.
#
def pause def pause
if listener.locked if listener.locked
UI.info "Un-paused files modification listening", :reset => true UI.info 'Un-paused files modification listening', :reset => true
listener.clear_changed_files listener.clear_changed_files
listener.unlock listener.unlock
else else
UI.info "Paused files modification listening", :reset => true UI.info 'Paused files modification listening', :reset => true
listener.lock listener.lock
end end
end end
def run_on_change(files) # Trigger `run_on_change` on all Guards currently enabled.
#
def run_on_change(paths)
run do run do
execute_supervised_task_for_all_guards(:run_on_change, files) execute_supervised_task_for_all_guards(:run_on_change, paths)
end end
end end
# Run a block where the listener and the interactor is
# blocked.
#
# @yield the block to run
#
def run def run
listener.lock listener.lock
interactor.lock interactor.lock
@ -94,12 +132,19 @@ module Guard
listener.unlock listener.unlock
end end
# Loop through all groups and execute the given task for each Guard in it,
# but halt the task execution for the all Guards within a group if one Guard
# throws `:task_has_failed` and the group has its `:halt_on_fail` option to `true`.
#
# @param [Symbol] task the task to run
# @param [Array] files the list of files to pass to the task
#
def execute_supervised_task_for_all_guards(task, files = nil) def execute_supervised_task_for_all_guards(task, files = nil)
groups.each do |group_hash| groups.each do |group_hash|
catch group_hash[:options][:halt_on_fail] == true ? :task_has_failed : :no_catch do catch group_hash[:options][:halt_on_fail] == true ? :task_has_failed : :no_catch do
guards.find_all { |guard| guard.group == group_hash[:name] }.each do |guard| guards.find_all { |guard| guard.group == group_hash[:name] }.each do |guard|
paths = Watcher.match_files(guard, files) if files if task == :run_on_change
if paths && !paths.empty? paths = Watcher.match_files(guard, files)
UI.debug "#{guard.class.name}##{task} with #{paths.inspect}" UI.debug "#{guard.class.name}##{task} with #{paths.inspect}"
supervised_task(guard, task, paths) supervised_task(guard, task, paths)
else else
@ -110,25 +155,40 @@ module Guard
end end
end end
# Let a guard execute its task but # Let a Guard execute its task, but fire it
# fire it if his work leads to a system failure # if his work leads to a system failure.
#
# @param [Guard::Guard] guard the Guard to execute
# @param [Symbol] task_to_supervise the task to run
# @param [Array] args the arguments for the task
# @return [Boolean, Exception] the result of the Guard
#
def supervised_task(guard, task_to_supervise, *args) def supervised_task(guard, task_to_supervise, *args)
guard.hook("#{task_to_supervise}_begin", *args) guard.hook("#{ task_to_supervise }_begin", *args)
result = guard.send(task_to_supervise, *args) result = guard.send(task_to_supervise, *args)
guard.hook("#{task_to_supervise}_end", result) guard.hook("#{ task_to_supervise }_end", result)
result result
rescue Exception => ex rescue Exception => ex
UI.error("#{guard.class.name} failed to achieve its <#{task_to_supervise.to_s}>, exception was:" + UI.error("#{ guard.class.name } failed to achieve its <#{ task_to_supervise.to_s }>, exception was:" +
"\n#{ex.class}: #{ex.message}\n#{ex.backtrace.join("\n")}") "\n#{ ex.class }: #{ ex.message }\n#{ ex.backtrace.join("\n") }")
guards.delete guard guards.delete guard
UI.info("\n#{guard.class.name} has just been fired") UI.info("\n#{ guard.class.name } has just been fired")
return ex
ex
end end
# Add a Guard to use.
#
# @param [String] name the Guard name
# @param [Array<Watcher>] watchers the list of declared watchers
# @param [Array<Hash>] callbacks the list of callbacks
# @param [Hash] options the Guard options
#
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| Hook.add_callback(callback[:listener], guard_class, callback[:events]) } callbacks.each { |callback| Hook.add_callback(callback[:listener], guard_class, callback[:events]) }
@ -136,42 +196,60 @@ module Guard
end end
end end
# Add a Guard group.
#
# @param [String] name the group name
# @param [Hash] options the group options
# @option options [Boolean] halt_on_fail if a task execution should be halted for all Guards in this group if one Guard throws `:task_has_failed`
#
def add_group(name, options = {}) def add_group(name, options = {})
@groups << { :name => name.to_sym, :options => options } unless name.nil? || @groups.find { |group| group[:name] == name } @groups << { :name => name.to_sym, :options => options } unless name.nil? || @groups.find { |group| group[:name] == name }
end end
# Tries to load the Guard main class.
#
# @param [String] name the name of the Guard
# @return [Class, nil] the loaded class
#
def get_guard_class(name) def get_guard_class(name)
name = name.to_s name = name.to_s
try_require = false try_require = false
const_name = name.downcase.gsub('-', '') const_name = name.downcase.gsub('-', '')
begin begin
require "guard/#{name.downcase}" if try_require require "guard/#{ name.downcase }" if try_require
self.const_get(self.constants.find { |c| c.to_s.downcase == const_name }) self.const_get(self.constants.find { |c| c.to_s.downcase == const_name })
rescue TypeError rescue TypeError
unless try_require unless try_require
try_require = true try_require = true
retry retry
else else
UI.error "Could not find class Guard::#{const_name.capitalize}" UI.error "Could not find class Guard::#{ const_name.capitalize }"
end end
rescue LoadError => loadError rescue LoadError => loadError
UI.error "Could not load 'guard/#{name.downcase}' or find class Guard::#{const_name.capitalize}" UI.error "Could not load 'guard/#{ name.downcase }' or find class Guard::#{ const_name.capitalize }"
UI.error loadError.to_s UI.error loadError.to_s
end end
end end
# Locate a path to a Guard gem.
#
# @param [String] name the name of the Guard without the prefix `guard-`
# @return [String] the full path to the Guard gem
#
def locate_guard(name) def locate_guard(name)
if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0') if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
Gem::Specification.find_by_name("guard-#{name}").full_gem_path Gem::Specification.find_by_name("guard-#{ name }").full_gem_path
else else
Gem.source_index.find_name("guard-#{name}").last.full_gem_path Gem.source_index.find_name("guard-#{ name }").last.full_gem_path
end end
rescue rescue
UI.error "Could not find 'guard-#{name}' gem path." UI.error "Could not find 'guard-#{ name }' gem path."
end end
##
# Returns a list of guard Gem names installed locally. # Returns a list of guard Gem names installed locally.
#
# @return [Array<String>] a list of guard gem names
#
def guard_gem_names def guard_gem_names
if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0') if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
Gem::Specification.find_all.select { |x| x.name =~ /^guard-/ } Gem::Specification.find_all.select { |x| x.name =~ /^guard-/ }
@ -180,16 +258,19 @@ module Guard
end.map { |x| x.name.sub /^guard-/, '' } end.map { |x| x.name.sub /^guard-/, '' }
end end
# Adds a command logger in debug mode. This wraps common command
# execution functions and logs the executed command before execution.
#
def debug_command_execution def debug_command_execution
Kernel.send(:alias_method, :original_system, :system) Kernel.send(:alias_method, :original_system, :system)
Kernel.send(:define_method, :system) do |command, *args| Kernel.send(:define_method, :system) do |command, *args|
::Guard::UI.debug "Command execution: #{command} #{args.join(' ')}" ::Guard::UI.debug "Command execution: #{ command } #{ args.join(' ') }"
original_system command, *args original_system command, *args
end end
Kernel.send(:alias_method, :original_backtick, :"`") Kernel.send(:alias_method, :original_backtick, :'`')
Kernel.send(:define_method, :"`") do |command| Kernel.send(:define_method, :'`') do |command|
::Guard::UI.debug "Command execution: #{command}" ::Guard::UI.debug "Command execution: #{ command }"
original_backtick command original_backtick command
end end
end end

View File

@ -2,89 +2,172 @@ require 'thor'
require 'guard/version' require 'guard/version'
module Guard module Guard
# Guard command line interface managed by [Thor](https://github.com/wycats/thor).
# This is the main interface to Guard that is called by the Guard binary `bin/guard`.
#
class CLI < Thor class CLI < Thor
default_task :start default_task :start
method_option :clear, :type => :boolean, :default => false, :aliases => '-c', :banner => "Auto clear shell before each change/run_all/reload" desc 'start', 'Starts Guard'
method_option :notify, :type => :boolean, :default => true, :aliases => '-n', :banner => "Notifications feature (growl/libnotify)"
method_option :debug, :type => :boolean, :default => false, :aliases => '-d', :banner => "Print debug messages"
method_option :group, :type => :array, :default => [], :aliases => '-g', :banner => "Run only the passed groups"
method_option :watchdir, :type => :string, :aliases => '-w', :banner => "Specify the directory to watch"
method_option :guardfile, :type => :string, :aliases => '-G', :banner => "Specify a Guardfile"
desc "start", "Starts Guard" method_option :clear,
:type => :boolean,
:default => false,
:aliases => '-c',
:banner => 'Auto clear shell before each change/run_all/reload'
method_option :notify,
:type => :boolean,
:default => true,
:aliases => '-n',
:banner => 'Notifications feature (growl/libnotify)'
method_option :debug,
:type => :boolean,
:default => false,
:aliases => '-d',
:banner => 'Print debug messages'
method_option :group,
:type => :array,
:default => [],
:aliases => '-g',
:banner => 'Run only the passed groups'
method_option :watchdir,
:type => :string,
:aliases => '-w',
:banner => 'Specify the directory to watch'
method_option :guardfile,
:type => :string,
:aliases => '-G',
:banner => 'Specify a Guardfile'
# Start Guard by initialize the defined Guards and watch the file system.
# This is the default task, so calling `guard` is the same as calling `guard start`.
#
# @see Guard.start
#
def start def start
::Guard.start(options) ::Guard.start(options)
end end
desc "list", "Lists guards that can be used with init" desc 'list', 'Lists guards that can be used with init'
# List the Guards that are available for use in your system and marks
# those that are currently used in your `Guardfile`.
#
# @example Guard list output
#
# Available guards:
# bundler *
# livereload
# ronn
# rspec *
# spork
#
# See also https://github.com/guard/guard/wiki/List-of-available-Guards
# * denotes ones already in your Guardfile
#
# @see Guard::DslDescriber
#
def list def list
::Guard::DslDescriber.evaluate_guardfile(options) Guard::DslDescriber.evaluate_guardfile(options)
installed = []
::Guard::DslDescriber.guardfile_structure.each do |group| installed = Guard::DslDescriber.guardfile_structure.inject([]) do |installed, group|
group[:guards].each {|x| installed << x[:name]} if group[:guards] group[:guards].each { |guard| installed << guard[:name] } if group[:guards]
installed
end end
::Guard::UI.info "Available guards:" Guard::UI.info 'Available guards:'
::Guard::guard_gem_names.sort.each do |name|
if installed.include? name Guard::guard_gem_names.sort.uniq.each do |name|
::Guard::UI.info " #{name} *" Guard::UI.info " #{ name } #{ installed.include?(name) ? '*' : '' }"
else
::Guard::UI.info " #{name}"
end
end end
::Guard::UI.info ' '
::Guard::UI.info "See also https://github.com/guard/guard/wiki/List-of-available-Guards" Guard::UI.info ' '
::Guard::UI.info "* denotes ones already in your Guardfile" Guard::UI.info 'See also https://github.com/guard/guard/wiki/List-of-available-Guards'
Guard::UI.info '* denotes ones already in your Guardfile'
end end
desc "version", "Prints Guard's version" desc 'version', 'Show the Guard version'
def version
::Guard::UI.info "Guard version #{Guard::VERSION}"
end
map %w(-v --version) => :version map %w(-v --version) => :version
desc "init [GUARD]", "Generates a Guardfile into the current working directory, or insert the given GUARD in an existing Guardfile" # Shows the current version of Guard.
def init(guard_name = nil) #
if !File.exist?("Guardfile") # @see Guard::VERSION
puts "Writing new Guardfile to #{Dir.pwd}/Guardfile" #
FileUtils.cp(File.expand_path('../templates/Guardfile', __FILE__), 'Guardfile') def version
elsif guard_name.nil? Guard::UI.info "Guard version #{ Guard::VERSION }"
::Guard::UI.error "Guardfile already exists at #{Dir.pwd}/Guardfile" end
exit 1
end
desc 'init [GUARD]', 'Generates a Guardfile at the current working directory, or insert the given GUARD to an existing Guardfile'
# Appends the Guard template to the `Guardfile`, or creates an initial
# `Guardfile` when no Guard name is passed.
#
# @param [String] guard_name the name of the Guard to initialize
#
def init(guard_name = nil)
if guard_name if guard_name
guard_class = ::Guard.get_guard_class(guard_name) guard_class = ::Guard.get_guard_class(guard_name)
guard_class.init(guard_name) guard_class.init(guard_name)
else
if File.exist?('Guardfile')
puts 'Writing new Guardfile to #{Dir.pwd}/Guardfile'
FileUtils.cp(File.expand_path('../templates/Guardfile', __FILE__), 'Guardfile')
else
Guard::UI.error "Guardfile already exists at #{ Dir.pwd }/Guardfile"
exit 1
end
end end
end end
desc "show", "Show all defined Guards and their options" desc 'show', 'Show all defined Guards and their options'
def show map %w(-T) => :show
::Guard::DslDescriber.evaluate_guardfile(options)
::Guard::DslDescriber.guardfile_structure.each do |group| # Shows all Guards and their options that are defined in
if !group[:guards].empty? # the `Guardfile`.
#
# @example guard show output
#
# (global):
# bundler
# coffeescript: input => "app/assets/javascripts", noop => true
# jasmine
# rspec: cli => "--fail-fast --format Fuubar
#
# @see Guard::DslDescriber
#
def show
Guard::DslDescriber.evaluate_guardfile(options)
Guard::DslDescriber.guardfile_structure.each do |group|
unless group[:guards].empty?
if group[:group] if group[:group]
::Guard::UI.info "Group #{group[:group]}:" Guard::UI.info "Group #{ group[:group] }:"
else else
::Guard::UI.info "(global):" Guard::UI.info '(global):'
end end
group[:guards].each do |guard| group[:guards].each do |guard|
line = " #{guard[:name]}" line = " #{ guard[:name] }"
if !guard[:options].empty? unless guard[:options].empty?
line += ": #{guard[:options].collect { |k, v| "#{k} => #{v.inspect}" }.join(", ")}" line += ": #{ guard[:options].collect { |k, v| "#{ k } => #{ v.inspect }" }.join(', ') }"
end end
::Guard::UI.info line
Guard::UI.info line
end end
end end
end end
::Guard::UI.info '' Guard::UI.info ''
end end
map %w(-T) => :show
end end
end end

View File

@ -1,63 +1,161 @@
module Guard module Guard
# The DSL class provides the methods that are used in each `Guardfile` to describe
# the behaviour of Guard.
#
# The main keywords of the DSL are `guard` and `watch`. These are necessary to define
# the used Guards and the file changes they are watching.
#
# You can optionally group the Guards with the `group` keyword and ignore certain paths
# with the `ignore_paths` keyword.
#
# A more advanced DSL use is the `callback` keyword that allows you to execute arbitrary
# code before or after any of the `start`, `stop`, `reload`, `run_all` and `run_on_change`
# Guards' method. You can even insert more hooks inside these methods.
# Please [checkout the Wiki page](https://github.com/guard/guard/wiki/Hooks-and-callbacks) for more details.
#
# The DSL will also evaluate normal Ruby code.
#
# There are two possible locations for the `Guardfile`:
# - The `Guardfile` in the current directory where Guard has been started
# - The `.Guardfile` in your home directory.
#
# In addition, if a user configuration `.guard.rb` in your home directory is found, it will
# be appended to the current project `Guardfile`.
#
# @example A sample of a complex Guardfile
#
# group 'frontend' do
# guard 'passenger', :ping => true do
# watch('config/application.rb')
# watch('config/environment.rb')
# watch(%r{^config/environments/.+\.rb})
# watch(%r{^config/initializers/.+\.rb})
# end
#
# guard 'livereload', :apply_js_live => false do
# watch(%r{^app/.+\.(erb|haml)})
# watch(%r{^app/helpers/.+\.rb})
# watch(%r{^public/javascripts/.+\.js})
# watch(%r{^public/stylesheets/.+\.css})
# watch(%r{^public/.+\.html})
# watch(%r{^config/locales/.+\.yml})
# end
# end
#
# group 'backend' do
# # Reload the bundle when the Gemfile is modified
# guard 'bundler' do
# watch('Gemfile')
# end
#
# # for big project you can fine tune the "timeout" before Spork's launch is considered failed
# guard 'spork', :wait => 40 do
# watch('Gemfile')
# watch('config/application.rb')
# watch('config/environment.rb')
# watch(%r{^config/environments/.+\.rb})
# watch(%r{^config/initializers/.+\.rb})
# watch('spec/spec_helper.rb')
# end
#
# # use RSpec 2, from the system's gem and with some direct RSpec CLI options
# guard 'rspec', :version => 2, :cli => "--color --drb -f doc", :bundler => false do
# watch('spec/spec_helper.rb') { "spec" }
# watch('app/controllers/application_controller.rb') { "spec/controllers" }
# watch('config/routes.rb') { "spec/routing" }
# watch(%r{^spec/support/(controllers|acceptance)_helpers\.rb}) { |m| "spec/#{m[1]}" }
# watch(%r{^spec/.+_spec\.rb})
#
# watch(%r{^app/controllers/(.+)_(controller)\.rb}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
#
# watch(%r{^app/(.+)\.rb}) { |m| "spec/#{m[1]}_spec.rb" }
# watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
# end
# end
#
class Dsl class Dsl
class << self class << self
@@options = nil @@options = nil
# Evaluate the DSL methods in the `Guardfile`.
#
# @param [Hash] options the Guard options
# @option options [Array<Symbol,String>] groups the groups to evaluate
# @option options [String] guardfile the path to a valid Guardfile
# @option options [String] guardfile_contents a string representing the content of a valid Guardfile
# @raise [ArgumentError] when options are not a Hash
#
def evaluate_guardfile(options = {}) def evaluate_guardfile(options = {})
options.is_a?(Hash) or raise ArgumentError.new("evaluate_guardfile not passed a Hash!") raise ArgumentError.new('No option hash passed to evaluate_guardfile!') unless options.is_a?(Hash)
@@options = options.dup @@options = options.dup
fetch_guardfile_contents fetch_guardfile_contents
instance_eval_guardfile(guardfile_contents_with_user_config) 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
# Re-evaluate the `Guardfile` to update the current Guard configuration.
#
def reevaluate_guardfile def reevaluate_guardfile
::Guard.guards.clear ::Guard.guards.clear
::Guard.groups.clear ::Guard.groups.clear
@@options.delete(:guardfile_contents) @@options.delete(:guardfile_contents)
Dsl.evaluate_guardfile(@@options) Dsl.evaluate_guardfile(@@options)
msg = "Guardfile has been re-evaluated." msg = 'Guardfile has been re-evaluated.'
UI.info(msg) UI.info(msg)
Notifier.notify(msg) Notifier.notify(msg)
end end
# Evaluate the content of the `Guardfile`.
#
# @param [String] contents the content to evaluate.
#
def instance_eval_guardfile(contents) def instance_eval_guardfile(contents)
begin new.instance_eval(contents, @@options[:guardfile_path], 1)
new.instance_eval(contents, @@options[:guardfile_path], 1) rescue
rescue UI.error "Invalid Guardfile, original error is:\n#{ $! }"
UI.error "Invalid Guardfile, original error is:\n#{$!}" exit 1
exit 1
end
end end
# Test if the current `Guardfile` contains a specific Guard.
#
# @param [String] guard_name the name of the Guard
# @return [Boolean] whether the Guard has been declared
#
def guardfile_include?(guard_name) def guardfile_include?(guard_name)
guardfile_contents.match(/^guard\s*\(?\s*['":]#{guard_name}['"]?/) guardfile_contents.match(/^guard\s*\(?\s*['":]#{ guard_name }['"]?/)
end end
# Read the current `Guardfile` content.
#
# @param [String] the path to the Guardfile
#
def read_guardfile(guardfile_path) def read_guardfile(guardfile_path)
begin @@options[:guardfile_path] = guardfile_path
@@options[:guardfile_path] = guardfile_path @@options[:guardfile_contents] = File.read(guardfile_path)
@@options[:guardfile_contents] = File.read(guardfile_path) rescue
rescue UI.error("Error reading file #{ guardfile_path }")
UI.error("Error reading file #{guardfile_path}") exit 1
exit 1
end
end end
# Get the content to evaluate and stores it into
# the options as `:guardfile_contents`.
#
def fetch_guardfile_contents def fetch_guardfile_contents
# TODO: do we need .rc file interaction?
if @@options[:guardfile_contents] if @@options[:guardfile_contents]
UI.info "Using inline Guardfile." UI.info 'Using inline Guardfile.'
@@options[:guardfile_path] = 'Inline Guardfile' @@options[:guardfile_path] = 'Inline Guardfile'
elsif @@options[:guardfile] elsif @@options[:guardfile]
if File.exist?(@@options[:guardfile]) if File.exist?(@@options[:guardfile])
read_guardfile(@@options[:guardfile]) read_guardfile(@@options[:guardfile])
UI.info "Using Guardfile at #{@@options[:guardfile]}." UI.info "Using Guardfile at #{ @@options[:guardfile] }."
else else
UI.error "No Guardfile exists at #{@@options[:guardfile]}." UI.error "No Guardfile exists at #{ @@options[:guardfile] }."
exit 1 exit 1
end end
@ -65,86 +163,209 @@ module Guard
if File.exist?(guardfile_default_path) if File.exist?(guardfile_default_path)
read_guardfile(guardfile_default_path) read_guardfile(guardfile_default_path)
else else
UI.error "No Guardfile found, please create one with `guard init`." UI.error 'No Guardfile found, please create one with `guard init`.'
exit 1 exit 1
end end
end end
unless guardfile_contents_usable? unless guardfile_contents_usable?
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
end end
# Get the content of the `Guardfile`.
#
# @return [String] the Guardfile content
#
def guardfile_contents def guardfile_contents
@@options ? @@options[:guardfile_contents] : "" @@options ? @@options[:guardfile_contents] : ''
end end
# Get the content of the `Guardfile` and the global
# user configuration file.
#
# @see #user_config_path
#
# @return [String] the Guardfile content
#
def guardfile_contents_with_user_config def guardfile_contents_with_user_config
config = File.read(user_config_path) if File.exist?(user_config_path) config = File.read(user_config_path) if File.exist?(user_config_path)
[guardfile_contents, config].join("\n") [guardfile_contents, config].join("\n")
end end
# Get the file path to the project `Guardfile`.
#
# @return [String] the path to the Guardfile
#
def guardfile_path def guardfile_path
@@options ? @@options[:guardfile_path] : "" @@options ? @@options[:guardfile_path] : ''
end end
# Tests if the current `Guardfile` content is usable.
#
# @return [Boolean] if the Guardfile is usable
#
def guardfile_contents_usable? def guardfile_contents_usable?
guardfile_contents && guardfile_contents.size >= 'guard :a'.size # smallest guard-definition guardfile_contents && guardfile_contents.size >= 'guard :a'.size # Smallest Guard definition
end end
# Gets the default path of the `Guardfile`. This returns the `Guardfile`
# from the current directory when existing, or the global `.Guardfile`
# at the home directory.
#
# @return [String] the path to the Guardfile
#
def guardfile_default_path def guardfile_default_path
File.exist?(local_guardfile_path) ? local_guardfile_path : home_guardfile_path File.exist?(local_guardfile_path) ? local_guardfile_path : home_guardfile_path
end end
private private
# The path to the `Guardfile` that is located at
# the directory, where Guard has been started from.
#
# @param [String] the path to the local Guardfile
#
def local_guardfile_path def local_guardfile_path
File.join(Dir.pwd, "Guardfile") File.join(Dir.pwd, 'Guardfile')
end end
# The path to the `.Guardfile` that is located at
# the users home directory.
#
# @param [String] the path to ~/.Guardfile
#
def home_guardfile_path def home_guardfile_path
File.expand_path(File.join("~", ".Guardfile")) File.expand_path(File.join('~', '.Guardfile'))
end end
# The path to the user configuration `.guard.rb`
# that is located at the users home directory.
#
# @param [String] the path to ~/.guard.rb
#
def user_config_path def user_config_path
File.expand_path(File.join("~", ".guard.rb")) File.expand_path(File.join('~', '.guard.rb'))
end end
end end
def group(name, options = {}, &guard_definition) # Declares a group of guards to be run with `guard start --group group_name`.
#
# @example Declare two groups of Guards
#
# group 'backend' do
# guard 'spork'
# guard 'rspec'
# end
#
# group 'frontend' do
# guard 'passenger'
# guard 'livereload'
# end
#
# @param [Symbol, String] name the group's name called from the CLI
# @param [Hash] options the options accepted by the group
# @yield a block where you can declare several guards
#
# @see Guard.add_group
# @see Dsl#guard
# @see Guard::DslDescriber
#
def group(name, options = {})
@groups = @@options[:group] || [] @groups = @@options[:group] || []
name = name.to_sym name = name.to_sym
if guard_definition && (@groups.empty? || @groups.map(&:to_sym).include?(name)) if block_given? && (@groups.empty? || @groups.map(&:to_sym).include?(name))
::Guard.add_group(name.to_s.downcase.to_sym, options) ::Guard.add_group(name.to_s.downcase, options)
@current_group = name @current_group = name
guard_definition.call
yield if block_given?
@current_group = nil @current_group = nil
end end
end end
def guard(name, options = {}, &watch_and_callback_definition) # Declare a guard to be used when running `guard start`.
#
# The name parameter is usually the name of the gem without
# the 'guard-' prefix.
#
# The available options are different for each Guard implementation.
#
# @example Declare a Guard
#
# guard 'rspec' do
# end
#
# @param [String] name the Guard name
# @param [Hash] options the options accepted by the Guard
# @yield a block where you can declare several watch patterns and actions
#
# @see Guard.add_guard
# @see Dsl#group
# @see Dsl#watch
# @see Guard::DslDescriber
#
def guard(name, options = {})
@watchers = [] @watchers = []
@callbacks = [] @callbacks = []
watch_and_callback_definition.call if watch_and_callback_definition
yield if block_given?
options.update(:group => (@current_group || :default)) options.update(:group => (@current_group || :default))
::Guard.add_guard(name.to_s.downcase.to_sym, @watchers, @callbacks, options) ::Guard.add_guard(name.to_s.downcase, @watchers, @callbacks, options)
end end
# Define a pattern to be watched in order to run actions on file modification.
#
# @example Declare watchers for a Guard
#
# guard 'rspec' do
# watch('spec/spec_helper.rb')
# watch(%r{^.+_spec.rb})
# watch(%r{^app/controllers/(.+).rb}) { |m| 'spec/acceptance/#{m[1]}s_spec.rb' }
# end
#
# @param [String, Regexp] pattern the pattern to be watched by the guard
# @yield a block to be run when the pattern is matched
# @yieldparam [MatchData] m matches of the pattern
# @yieldreturn a directory, a filename, an array of directories / filenames, or nothing (can be an arbitrary command)
#
# @see Guard::Watcher
# @see Dsl#guard
#
def watch(pattern, &action) def watch(pattern, &action)
@watchers << ::Guard::Watcher.new(pattern, action) @watchers << ::Guard::Watcher.new(pattern, action)
end end
# Define a callback to execute arbitrary code before or after any of
# the `start`, `stop`, `reload`, `run_all` and `run_on_change` guards' method.
#
# @param [Array] args the callback arguments
# @yield a block with listeners
#
# @see Guard::Hook
#
def callback(*args, &listener) def callback(*args, &listener)
listener, events = args.size > 1 ? args : [listener, args[0]] listener, events = args.size > 1 ? args : [listener, args[0]]
@callbacks << { :events => events, :listener => listener } @callbacks << { :events => events, :listener => listener }
end end
# Ignore certain paths globally.
#
# @example Ignore some paths
# ignore_paths ".git", ".svn"
#
# @param [Array] paths the list of paths to ignore
#
# @see Guard::Listener
#
def ignore_paths(*paths) def ignore_paths(*paths)
UI.info "Ignoring paths: #{paths.join(', ')}" UI.info "Ignoring paths: #{ paths.join(', ') }"
::Guard.listener.ignore_paths.push(*paths) ::Guard.listener.ignore_paths.push(*paths)
end end
end end
end end

View File

@ -1,28 +1,60 @@
require 'guard/dsl' require 'guard/dsl'
module Guard module Guard
# The DslDescriber overrides methods to create an internal structure
# of the Guardfile that is used in some inspection utility methods
# like the CLI commands `show` and `list`.
#
# @see Guard::Dsl
# @see Guard::CLI
#
class DslDescriber < Dsl class DslDescriber < Dsl
@@guardfile_structure = [ { :guards => [] } ] @@guardfile_structure = [ { :guards => [] } ]
class << self class << self
# Get the Guardfile structure.
#
# @return [Array<Hash>] the structure
#
def guardfile_structure def guardfile_structure
@@guardfile_structure @@guardfile_structure
end end
end end
private private
def group(name, &guard_definition)
@@guardfile_structure << { :group => name.to_sym, :guards => [] }
# Declares a group of guards.
#
# @param [String] name the group's name called from the CLI
# @yield a block where you can declare several guards
#
# @see Guard::Dsl#group
#
def group(name)
@@guardfile_structure << { :group => name.to_sym, :guards => [] }
@group = true @group = true
guard_definition.call
yield if block_given?
@group = false @group = false
end end
def guard(name, options = {}, &watch_definition) # Declares a Guard.
#
# @param [String] name the Guard name
# @param [Hash] options the options accepted by the Guard
# @yield a block where you can declare several watch patterns and actions
#
# @see Guard::Dsl#guard
#
def guard(name, options = {})
node = (@group ? @@guardfile_structure.last : @@guardfile_structure.first) node = (@group ? @@guardfile_structure.last : @@guardfile_structure.first)
node[:guards] << { :name => name, :options => options } node[:guards] << { :name => name, :options => options }
end end
end end
end end

View File

@ -1,21 +1,39 @@
module Guard module Guard
# Main class that every Guard implementation must subclass.
#
# Guard will trigger the `start`, `stop`, `reload`, `run_all` and `run_on_change`
# methods depending on user interaction and file modification.
#
# Each Guard should provide a template Guardfile located within the Gem
# at `lib/guard/guard-name/templates/Guardfile`.
#
class Guard class Guard
include Hook include Hook
attr_accessor :watchers, :options, :group attr_accessor :watchers, :options, :group
# Initialize a Guard.
#
# @param [Array<Guard::Watcher>] watchers the Guard file watchers
# @param [Hash] options the custom Guard options.
#
def initialize(watchers = [], options = {}) def initialize(watchers = [], options = {})
@group = options.delete(:group) || :default @group = options.delete(:group) || :default
@watchers, @options = watchers, options @watchers, @options = watchers, options
end end
# Guardfile template needed inside guard gem # Initialize the Guard. This will copy the Guardfile template inside the Guard gem.
# The template Guardfile must be located within the Gem at `lib/guard/guard-name/templates/Guardfile`.
#
# @param [String] name the name of the Guard
#
def self.init(name) def self.init(name)
if ::Guard::Dsl.guardfile_include?(name) if ::Guard::Dsl.guardfile_include?(name)
::Guard::UI.info "Guardfile already includes #{name} guard" ::Guard::UI.info "Guardfile already includes #{ name } guard"
else else
content = File.read('Guardfile') content = File.read('Guardfile')
guard = File.read("#{::Guard.locate_guard(name)}/lib/guard/#{name}/templates/Guardfile") guard = File.read("#{ ::Guard.locate_guard(name) }/lib/guard/#{ name }/templates/Guardfile")
File.open('Guardfile', 'wb') do |f| File.open('Guardfile', 'wb') do |f|
f.puts(content) f.puts(content)
f.puts("") f.puts("")
@ -25,31 +43,43 @@ module Guard
end end
end end
# ================ # Call once when Guard starts. Please override initialize method to init stuff.
# = Guard method = #
# ================ # @return [Boolean] Whether the start action was successful or not
#
# Call once when guard starts
# Please override initialize method to init stuff
def start def start
true true
end end
# Call once when guard quit # Call once when Guard quit.
#
# @return [Boolean] Whether the stop action was successful or not
#
def stop def stop
true true
end end
# Should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/... # Should be used for "reload" (really!) actions like reloading passenger/spork/bundler/...
#
# @return [Boolean] Whether the reload action was successful or not
#
def reload def reload
true true
end end
# Should be principally used for long action like running all specs/tests/... # Should be used for long action like running all specs/tests/...
#
# @return [Boolean] Whether the run_all action was successful or not
#
def run_all def run_all
true true
end end
# Will be triggered when a file change matched a watcher.
#
# @param [Array<String>] paths the changes files or paths
# @return [Boolean] Whether the run_all action was successful or not
#
def run_on_change(paths) def run_on_change(paths)
true true
end end

View File

@ -1,51 +1,82 @@
module Guard module Guard
# Guard has a hook mechanism that allows you to insert callbacks for individual Guards.
# By default, each of the Guard instance methods has a "_begin" and an "_end" hook.
# For example, the Guard::Guard#start method has a :start_begin hook that is runs immediately
# before Guard::Guard#start, and a :start_end hook that runs immediately after Guard::Guard#start.
#
# Read more about [hooks and callbacks on the wiki](https://github.com/guard/guard/wiki/Hooks-and-callbacks).
#
module Hook module Hook
# The Hook module gets included.
#
# @param [Class] base the class that includes the module
#
def self.included(base) def self.included(base)
base.send :include, InstanceMethods base.send :include, InstanceMethods
end end
# Instance methods that gets included in the base class.
#
module InstanceMethods module InstanceMethods
# When +event+ is a Symbol, #hook will generate a hook name
# by concatenating the method name from where #hook is called # When event is a Symbol, {#hook} will generate a hook name
# by concatenating the method name from where {#hook} is called
# with the given Symbol. # with the given Symbol.
# Example: #
# @example Add a hook with a Symbol
#
# def run_all # def run_all
# hook :foo # hook :foo
# end # end
# Here, when #run_all is called, #hook will notify callbacks #
# Here, when {Guard::Guard#run_all} is called, {#hook} will notify callbacks
# registered for the "run_all_foo" event. # registered for the "run_all_foo" event.
# #
# When +event+ is a String, #hook will directly turn the String # When event is a String, {#hook} will directly turn the String
# into a Symbol. # into a Symbol.
# Example: #
# @example Add a hook with a String
#
# def run_all # def run_all
# hook "foo_bar" # hook "foo_bar"
# end # end
# Here, when #run_all is called, #hook will notify callbacks #
# When {Guard::Guard#run_all} is called, {#hook} will notify callbacks
# registered for the "foo_bar" event. # registered for the "foo_bar" event.
# #
# +args+ parameter is passed as is to the callbacks registered # @param [Symbol, String] event the name of the Guard event
# for the given event. # @param [Array] args the parameters are passed as is to the callbacks registered for the given event.
#
def hook(event, *args) def hook(event, *args)
hook_name = if event.is_a? Symbol hook_name = if event.is_a? Symbol
calling_method = caller[0][/`([^']*)'/, 1] calling_method = caller[0][/`([^']*)'/, 1]
"#{calling_method}_#{event}" "#{ calling_method }_#{ event }"
else else
event event
end.to_sym end.to_sym
UI.debug "Hook :#{hook_name} executed for #{self.class}" UI.debug "Hook :#{ hook_name } executed for #{ self.class }"
Hook.notify(self.class, hook_name, *args) Hook.notify(self.class, hook_name, *args)
end end
end end
class << self class << self
# Get all callbacks.
#
def callbacks def callbacks
@callbacks ||= Hash.new { |hash, key| hash[key] = [] } @callbacks ||= Hash.new { |hash, key| hash[key] = [] }
end end
# Add a callback.
#
# @param [Block] listener the listener to notify
# @param [Guard::Guard] guard_class the Guard class to add the callback
# @param [Array<Symbol>] events the events to register
#
def add_callback(listener, guard_class, events) def add_callback(listener, guard_class, events)
_events = events.is_a?(Array) ? events : [events] _events = events.is_a?(Array) ? events : [events]
_events.each do |event| _events.each do |event|
@ -53,19 +84,34 @@ module Guard
end end
end end
# Checks if a callback has been registered.
#
# @param [Block] listener the listener to notify
# @param [Guard::Guard] guard_class the Guard class to add the callback
# @param [Symbol] event the event to look for
#
def has_callback?(listener, guard_class, event) def has_callback?(listener, guard_class, event)
callbacks[[guard_class, event]].include?(listener) callbacks[[guard_class, event]].include?(listener)
end end
# Notify a callback.
#
# @param [Guard::Guard] guard_class the Guard class to add the callback
# @param [Symbol] event the event to trigger
# @param [Array] args the arguments for the listener
#
def notify(guard_class, event, *args) def notify(guard_class, event, *args)
callbacks[[guard_class, event]].each do |listener| callbacks[[guard_class, event]].each do |listener|
listener.call(guard_class, event, *args) listener.call(guard_class, event, *args)
end end
end end
# Reset all callbacks.
#
def reset_callbacks! def reset_callbacks!
@callbacks = nil @callbacks = nil
end end
end end
end end

View File

@ -1,39 +1,76 @@
module Guard module Guard
# The interactor reads user input and triggers
# specific action upon them unless its locked.
#
# Currently the following actions are implemented:
#
# - stop, quit, exit, s, q, e => Exit Guard
# - reload, r, z => Reload Guard
# - pause, p => Pause Guard
# - Everything else => Run all
#
class Interactor class Interactor
class LockException < Exception; end
class UnlockException < Exception; end
attr_reader :locked attr_reader :locked
# Initialize the interactor in unlocked state.
#
def initialize def initialize
@locked = false @locked = false
end end
# Start the interactor in its own thread.
#
def start def start
return if ENV["GUARD_ENV"] == 'test' return if ENV["GUARD_ENV"] == 'test'
Thread.new do
@thread = Thread.new do
loop do loop do
if (entry = $stdin.gets) && !@locked begin
entry.gsub! /\n/, '' if !@locked && (entry = $stdin.gets)
case entry entry.gsub! /\n/, ''
when 'stop', 'quit', 'exit', 's', 'q', 'e' case entry
::Guard.stop when 'stop', 'quit', 'exit', 's', 'q', 'e'
when 'reload', 'r', 'z' ::Guard.stop
::Guard.reload when 'reload', 'r', 'z'
when 'pause', 'p' ::Guard.reload
::Guard.pause when 'pause', 'p'
else ::Guard.pause
::Guard.run_all else
::Guard.run_all
end
end end
rescue LockException
lock
rescue UnlockException
unlock
end end
end end
end end
end end
# Lock the interactor.
#
def lock def lock
@locked = true if !@thread || @thread == Thread.current
@locked = true
else
@thread.raise(LockException)
end
end end
# Unlock the interactor.
#
def unlock def unlock
@locked = false if !@thread || @thread == Thread.current
@locked = false
else
@thread.raise(UnlockException)
end
end end
end end

View File

@ -8,40 +8,63 @@ module Guard
autoload :Windows, 'guard/listeners/windows' autoload :Windows, 'guard/listeners/windows'
autoload :Polling, 'guard/listeners/polling' autoload :Polling, 'guard/listeners/polling'
# The Listener is the base class for all listener
# implementations.
#
# @abstract
#
class Listener class Listener
DefaultIgnorePaths = %w[. .. .bundle .git log tmp vendor] # Default paths that gets ignored by the listener
DEFAULT_IGNORE_PATHS = %w[. .. .bundle .git log tmp vendor]
attr_accessor :changed_files attr_accessor :changed_files
attr_reader :directory, :ignore_paths, :locked attr_reader :directory, :ignore_paths, :locked
def self.select_and_init(*a) # Select the appropriate listener implementation for the
# current OS and initializes it.
#
# @param [Array] args the arguments for the listener
# @return [Guard::Listener] the chosen listener
#
def self.select_and_init(*args)
if mac? && Darwin.usable? if mac? && Darwin.usable?
Darwin.new(*a) Darwin.new(*args)
elsif linux? && Linux.usable? elsif linux? && Linux.usable?
Linux.new(*a) Linux.new(*args)
elsif windows? && Windows.usable? elsif windows? && Windows.usable?
Windows.new(*a) Windows.new(*args)
else else
UI.info "Using polling (Please help us to support your system better than that.)" UI.info 'Using polling (Please help us to support your system better than that).'
Polling.new(*a) Polling.new(*args)
end end
end end
# Initialize the listener.
#
# @param [String] directory the root directory to listen to
# @param [Hash] options the listener options
# @option options [Boolean] relativize_paths use only relative paths
# @option options [Array<String>] ignore_paths the paths to ignore by the listener
#
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 = [] @changed_files = []
@locked = false @locked = false
@ignore_paths = DefaultIgnorePaths @ignore_paths = DEFAULT_IGNORE_PATHS
@ignore_paths |= options[:ignore_paths] if options[:ignore_paths] @ignore_paths |= options[:ignore_paths] if options[:ignore_paths]
update_last_event update_last_event
start_reactor start_reactor
end end
# Start the listener thread.
#
def start_reactor def start_reactor
return if ENV["GUARD_ENV"] == 'test' return if ENV["GUARD_ENV"] == 'test'
Thread.new do Thread.new do
loop do loop do
if @changed_files != [] && !@locked if @changed_files != [] && !@locked
@ -55,33 +78,54 @@ module Guard
end end
end end
# Start watching the root directory.
#
def start def start
watch(@directory) watch(@directory)
end end
# Stop listening for events.
#
def stop def stop
end end
# Lock the listener to ignore change events.
#
def lock def lock
@locked = true @locked = true
end end
# Unlock the listener to listen again to change events.
#
def unlock def unlock
@locked = false @locked = false
end end
# Clear the list of changed files.
#
def clear_changed_files def clear_changed_files
@changed_files.clear @changed_files.clear
end end
# Store a listener callback.
#
# @param [Block] callback the callback to store
#
def on_change(&callback) def on_change(&callback)
@callback = callback @callback = callback
end end
# Updates the timestamp of the last event.
#
def update_last_event def update_last_event
@last_event = Time.now @last_event = Time.now
end end
# Get the modified files.
#
# @param [Array<String>] dirs the watched directories
# @param [Hash] options the listener options
#
def modified_files(dirs, options = {}) def modified_files(dirs, options = {})
last_event = @last_event last_event = @last_event
update_last_event update_last_event
@ -89,41 +133,64 @@ module Guard
relativize_paths(files) relativize_paths(files)
end end
def worker # Register a directory to watch.
raise NotImplementedError, "should respond to #watch" # Must be implemented by the subclasses.
end #
# @param [String] directory the directory to watch
# register a directory to watch. must be implemented by the subclasses #
def watch(directory) def watch(directory)
raise NotImplementedError, "do whatever you want here, given the directory as only argument" raise NotImplementedError, "do whatever you want here, given the directory as only argument"
end end
# Get all files that are in the watched directory.
#
# @return [Array<String>] the list of files
#
def all_files def all_files
potentially_modified_files([@directory], :all => true) potentially_modified_files([@directory], :all => true)
end end
# scopes all given paths to the current #directory # Scopes all given paths to the current directory.
#
# @param [Array<String>] paths the paths to change
# @return [Array<String>] all paths now relative to the current dir
#
def relativize_paths(paths) def relativize_paths(paths)
return paths unless relativize_paths? return paths unless relativize_paths?
paths.map do |path| paths.map do |path|
path.gsub(%r{^#{@directory}/}, '') path.gsub(%r{^#{ @directory }/}, '')
end end
end end
# Use paths relative to the current directory.
#
# @return [Boolean] whether to use relative or absolute paths
#
def relativize_paths? def relativize_paths?
!!@relativize_paths !!@relativize_paths
end end
# return children of the passed dirs that are not in the ignore_paths list # Removes the ignored paths from the directory list.
#
# @param [Array<String>] dirs the directory to listen to
# @param [Array<String>] ignore_paths the paths to ignore
# @return children of the passed dirs that are not in the ignore_paths list
#
def exclude_ignored_paths(dirs, ignore_paths = self.ignore_paths) def exclude_ignored_paths(dirs, ignore_paths = self.ignore_paths)
Dir.glob(dirs.map { |d| "#{d.sub(%r{/+$}, '')}/*" }, File::FNM_DOTMATCH).reject do |path| Dir.glob(dirs.map { |d| "#{d.sub(%r{/+$}, '')}/*" }, File::FNM_DOTMATCH).reject do |path|
ignore_paths.include?(File.basename(path)) ignore_paths.include?(File.basename(path))
end end
end end
private private
def potentially_modified_files(dirs, options={}) # Gets a list of files that are in the modified directories.
#
# @param [Array<String>] dirs the list of directories
# @param [Hash] options the options
# @option options [Symbol] all whether to include all files
#
def potentially_modified_files(dirs, options = {})
paths = exclude_ignored_paths(dirs) paths = exclude_ignored_paths(dirs)
if options[:all] if options[:all]
@ -131,7 +198,7 @@ module Guard
if File.file?(path) if File.file?(path)
array << path array << path
else else
array += Dir.glob("#{path}/**/*", File::FNM_DOTMATCH).select { |p| File.file?(p) } array += Dir.glob("#{ path }/**/*", File::FNM_DOTMATCH).select { |p| File.file?(p) }
end end
array array
end end
@ -140,9 +207,17 @@ module Guard
end end
end end
# Test if the file content has changed.
#
# Depending on the filesystem, mtime/ctime 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.
#
# ctime is used only on == comparison to always catches Rails 3.1 Assets pipelined on Mac OSX # ctime is used only on == comparison to always catches Rails 3.1 Assets pipelined on Mac OSX
#
# @param [String] path the file path
# @param [Time] last_event the time of the last event
# @return [Boolean] Whether the file content has changed or not.
#
def file_modified?(path, last_event) def file_modified?(path, last_event)
ctime = File.ctime(path).to_i ctime = File.ctime(path).to_i
mtime = File.mtime(path).to_i mtime = File.mtime(path).to_i
@ -158,6 +233,12 @@ module Guard
false false
end end
# Tests if the file content has been modified by
# comparing the SHA1 checksum.
#
# @param [String] path the file path
# @param [String] sha1_checksum the checksum of the file
#
def file_content_modified?(path, sha1_checksum) def file_content_modified?(path, sha1_checksum)
if @sha1_checksums_hash[path] != sha1_checksum if @sha1_checksums_hash[path] != sha1_checksum
set_sha1_checksums_hash(path, sha1_checksum) set_sha1_checksums_hash(path, sha1_checksum)
@ -167,22 +248,44 @@ module Guard
end end
end end
# Set the current checksum of a file.
#
# @param [String] path the file path
# @param [String] sha1_checksum the checksum of the file
#
def set_sha1_checksums_hash(path, sha1_checksum) def set_sha1_checksums_hash(path, sha1_checksum)
@sha1_checksums_hash[path] = sha1_checksum @sha1_checksums_hash[path] = sha1_checksum
end end
# Calculates the SHA1 checksum of a file.
#
# @param [String] path the path to the file
# @return [String] the SHA1 checksum
#
def sha1_checksum(path) def sha1_checksum(path)
Digest::SHA1.file(path).to_s Digest::SHA1.file(path).to_s
end end
# Test if the OS is Mac OS X.
#
# @return [Boolean] Whether the OS is Mac OS X
#
def self.mac? def self.mac?
RbConfig::CONFIG['target_os'] =~ /darwin/i RbConfig::CONFIG['target_os'] =~ /darwin/i
end end
# Test if the OS is Linux.
#
# @return [Boolean] Whether the OS is Linux
#
def self.linux? def self.linux?
RbConfig::CONFIG['target_os'] =~ /linux/i RbConfig::CONFIG['target_os'] =~ /linux/i
end end
# Test if the OS is Windows.
#
# @return [Boolean] Whether the OS is Windows
#
def self.windows? def self.windows?
RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
end end

View File

@ -1,41 +1,60 @@
module Guard module Guard
# Listener implementation for Mac OS X `FSEvents`.
#
class Darwin < Listener class Darwin < Listener
# Initialize the Listener.
#
def initialize(*) def initialize(*)
super super
@fsevent = FSEvent.new @fsevent = FSEvent.new
end end
def worker # Start the listener.
@fsevent #
end
def start def start
super super
worker.run worker.run
end end
# Stop the listener.
#
def stop def stop
super super
worker.stop worker.stop
end end
# Check if the listener is usable on the current OS.
#
# @return [Boolean] whether usable or not
#
def self.usable? def self.usable?
require 'rb-fsevent' require 'rb-fsevent'
if !defined?(FSEvent::VERSION) || (defined?(Gem::Version) && if !defined?(FSEvent::VERSION) || (defined?(Gem::Version) &&
Gem::Version.new(FSEvent::VERSION) < Gem::Version.new('0.4.0')) Gem::Version.new(FSEvent::VERSION) < Gem::Version.new('0.4.0'))
UI.info "Please update rb-fsevent (>= 0.4.0)" UI.info 'Please update rb-fsevent (>= 0.4.0)'
false false
else else
true true
end end
rescue LoadError rescue LoadError
UI.info "Please install rb-fsevent gem for Mac OSX FSEvents support" UI.info 'Please install rb-fsevent gem for Mac OSX FSEvents support'
false false
end end
private private
# Get the listener worker.
#
def worker
@fsevent
end
# Watch the given directory for file changes.
#
# @param [String] directory the directory to watch
#
def watch(directory) def watch(directory)
worker.watch(directory) do |modified_dirs| worker.watch(directory) do |modified_dirs|
files = modified_files(modified_dirs) files = modified_files(modified_dirs)

View File

@ -1,6 +1,11 @@
module Guard module Guard
# Listener implementation for Linux `inotify`.
#
class Linux < Listener class Linux < Listener
# Initialize the Listener.
#
def initialize(*) def initialize(*)
super super
@inotify = INotify::Notifier.new @inotify = INotify::Notifier.new
@ -8,44 +13,53 @@ module Guard
@latency = 0.5 @latency = 0.5
end end
# Start the listener.
#
def start def start
@stop = false @stop = false
super super
watch_change unless watch_change? watch_change unless watch_change?
end end
# Stop the listener.
#
def stop def stop
super super
@stop = true @stop = true
sleep(@latency) sleep(@latency)
end end
# Check if the listener is usable on the current OS.
#
# @return [Boolean] whether usable or not
#
def self.usable? def self.usable?
require 'rb-inotify' require 'rb-inotify'
if !defined?(INotify::VERSION) || (defined?(Gem::Version) && if !defined?(INotify::VERSION) || (defined?(Gem::Version) &&
Gem::Version.new(INotify::VERSION.join('.')) < Gem::Version.new('0.8.5')) Gem::Version.new(INotify::VERSION.join('.')) < Gem::Version.new('0.8.5'))
UI.info "Please update rb-inotify (>= 0.8.5)" UI.info 'Please update rb-inotify (>= 0.8.5)'
false false
else else
true true
end end
rescue LoadError rescue LoadError
UI.info "Please install rb-inotify gem for Linux inotify support" UI.info 'Please install rb-inotify gem for Linux inotify support'
false false
end end
def watch_change? private
!!@watch_change
end
private
# Get the listener worker.
#
def worker def worker
@inotify @inotify
end end
# Watch the given directory for file changes.
#
# @param [String] directory the directory to watch
#
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
worker.watch(directory, :recursive, :attrib, :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
@ -54,6 +68,16 @@ module Guard
rescue Interrupt rescue Interrupt
end end
# Test if inotify is watching for changes.
#
# @return [Boolean] whether inotify is active or not
#
def watch_change?
!!@watch_change
end
# Watch for file system changes.
#
def watch_change def watch_change
@watch_change = true @watch_change = true
until @stop until @stop

View File

@ -1,24 +1,46 @@
module Guard module Guard
# Polling listener that works cross-platform and
# has no dependencies. This is the listener that
# uses the most CPU processing power and has higher
# file IO that the other implementations.
#
class Polling < Listener class Polling < Listener
# Initialize the Listener.
#
def initialize(*) def initialize(*)
super super
@latency = 1.5 @latency = 1.5
end end
# Start the listener.
#
def start def start
@stop = false @stop = false
super super
watch_change watch_change
end end
# Stop the listener.
#
def stop def stop
super super
@stop = true @stop = true
end end
private # Watch the given directory for file changes.
#
# @param [String] directory the directory to watch
#
def watch(directory)
@existing = all_files
end
private
# Watch for file system changes.
#
def watch_change def watch_change
until @stop until @stop
start = Time.now.to_f start = Time.now.to_f
@ -29,9 +51,5 @@ module Guard
end end
end end
def watch(directory)
@existing = all_files
end
end end
end end

View File

@ -1,35 +1,48 @@
module Guard module Guard
# Listener implementation for Windows `fchange`.
#
class Windows < Listener class Windows < Listener
# Initialize the Listener.
#
def initialize(*) def initialize(*)
super super
@fchange = FChange::Notifier.new @fchange = FChange::Notifier.new
end end
# Start the listener.
#
def start def start
super super
worker.run worker.run
end end
# Stop the listener.
#
def stop def stop
super super
worker.stop worker.stop
end end
# Check if the listener is usable on the current OS.
#
# @return [Boolean] whether usable or not
#
def self.usable? def self.usable?
require 'rb-fchange' require 'rb-fchange'
true true
rescue LoadError rescue LoadError
UI.info "Please install rb-fchange gem for Windows file events support" UI.info 'Please install rb-fchange gem for Windows file events support'
false false
end end
private private
def worker
@fchange
end
# Watch the given directory for file changes.
#
# @param [String] directory the directory to watch
#
def watch(directory) def watch(directory)
worker.watch(directory, :all_events, :recursive) do |event| worker.watch(directory, :all_events, :recursive) do |event|
paths = [File.expand_path(event.watcher.path)] paths = [File.expand_path(event.watcher.path)]
@ -38,5 +51,11 @@ module Guard
end end
end end
# Get the listener worker.
#
def worker
@fchange
end
end end
end end

View File

@ -3,13 +3,29 @@ require 'pathname'
require 'guard/ui' require 'guard/ui'
module Guard module Guard
# The notifier class handles cross-platform system notifications that supports:
#
# - Growl on Mac OS X
# - Libnotify on Linux
# - Notifu on Windows
#
module Notifier module Notifier
# Application name as shown in the specific notification settings
APPLICATION_NAME = "Guard" APPLICATION_NAME = "Guard"
# Turn notifications off.
#
def self.turn_off def self.turn_off
ENV["GUARD_NOTIFY"] = 'false' ENV["GUARD_NOTIFY"] = 'false'
end end
# Turn notifications on. This tries to load the platform
# specific notification library.
#
# @return [Boolean] whether the notification could be enabled.
#
def self.turn_on def self.turn_on
ENV["GUARD_NOTIFY"] = 'true' ENV["GUARD_NOTIFY"] = 'true'
case RbConfig::CONFIG['target_os'] case RbConfig::CONFIG['target_os']
@ -22,6 +38,15 @@ module Guard
end end
end end
# Show a message with the system notification.
#
# @see .image_path
#
# @param [String] the message to show
# @param [Hash] options the notification options
# @option options [Symbol, String] image the image symbol or path to an image
# @option options [String] title the notification title
#
def self.notify(message, options = {}) def self.notify(message, options = {})
if enabled? if enabled?
image = options.delete(:image) || :success image = options.delete(:image) || :success
@ -38,12 +63,23 @@ module Guard
end end
end end
# Test if the notifications are enabled and available.
#
# @return [Boolean] whether the notifications are available
#
def self.enabled? def self.enabled?
ENV["GUARD_NOTIFY"] == 'true' ENV["GUARD_NOTIFY"] == 'true'
end end
private private
# Send a message to Growl either with the `growl` gem or the `growl_notify` gem.
#
# @param [String] title the notification title
# @param [String] message the message to show
# @param [Symbol, String] the image to user
# @param [Hash] options the growl options
#
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
@ -61,18 +97,43 @@ module Guard
end end
end end
# Send a message to libnotify.
#
# @param [String] title the notification title
# @param [String] message the message to show
# @param [Symbol, String] the image to user
# @param [Hash] options the libnotify options
#
def self.notify_linux(title, message, image, options) def self.notify_linux(title, message, image, options)
require_libnotify # need for guard-rspec formatter that is called out of guard scope require_libnotify # need for guard-rspec formatter that is called out of guard scope
default_options = { :body => message, :summary => title, :icon_path => image_path(image), :transient => true } default_options = { :body => message, :summary => title, :icon_path => image_path(image), :transient => true }
Libnotify.show default_options.merge(options) if enabled? Libnotify.show default_options.merge(options) if enabled?
end end
# Send a message to notifu.
#
# @param [String] title the notification title
# @param [String] message the message to show
# @param [Symbol, String] the image to user
# @param [Hash] options the notifu options
#
def self.notify_windows(title, message, image, options) def self.notify_windows(title, message, image, options)
require_rbnotifu # need for guard-rspec formatter that is called out of guard scope require_rbnotifu # need for guard-rspec formatter that is called out of guard scope
default_options = { :message => message, :title => title, :type => image_level(image), :time => 3 } default_options = { :message => message, :title => title, :type => image_level(image), :time => 3 }
Notifu.show default_options.merge(options) if enabled? Notifu.show default_options.merge(options) if enabled?
end end
# Get the image path for an image symbol.
#
# Known symbols are:
#
# - failed
# - pending
# - success
#
# @param [Symbol] image the image name
# @return [String] the image path
#
def self.image_path(image) def self.image_path(image)
images_path = Pathname.new(File.dirname(__FILE__)).join('../../images') images_path = Pathname.new(File.dirname(__FILE__)).join('../../images')
case image case image
@ -88,6 +149,11 @@ module Guard
end end
end end
# The notification level type for the given image.
#
# @param [Symbol] image the image
# @return [Symbol] the level
#
def self.image_level(image) def self.image_level(image)
case image case image
when :failed when :failed
@ -101,6 +167,9 @@ module Guard
end end
end end
# Try to safely load growl and turns notifications
# off on load failure.
#
def self.require_growl def self.require_growl
begin begin
require 'growl_notify' require 'growl_notify'
@ -119,6 +188,9 @@ module Guard
UI.info "Please install growl_notify or growl 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
# Try to safely load libnotify and turns notifications
# off on load failure.
#
def self.require_libnotify def self.require_libnotify
require 'libnotify' require 'libnotify'
rescue LoadError rescue LoadError
@ -126,11 +198,15 @@ module Guard
UI.info "Please install libnotify gem for Linux notification support and add it to your Gemfile" UI.info "Please install libnotify gem for Linux notification support and add it to your Gemfile"
end end
# Try to safely load rb-notifu and turns notifications
# off on load failure.
#
def self.require_rbnotifu def self.require_rbnotifu
require 'rb-notifu' require 'rb-notifu'
rescue LoadError rescue LoadError
turn_off turn_off
UI.info "Please install rb-notifu gem for Windows notification support and add it to your Gemfile" UI.info "Please install rb-notifu gem for Windows notification support and add it to your Gemfile"
end end
end end
end end

View File

@ -1,88 +1,92 @@
module Guard module Guard
# The UI class helps to format messages for the user.
#
module UI module UI
ANSI_ESCAPE_BRIGHT = "1"
ANSI_ESCAPE_BLACK = "30"
ANSI_ESCAPE_RED = "31"
ANSI_ESCAPE_GREEN = "32"
ANSI_ESCAPE_YELLOW = "33"
ANSI_ESCAPE_BLUE = "34"
ANSI_ESCAPE_MAGENTA = "35"
ANSI_ESCAPE_CYAN = "36"
ANSI_ESCAPE_WHITE = "37"
ANSI_ESCAPE_BGBLACK = "40"
ANSI_ESCAPE_BGRED = "41"
ANSI_ESCAPE_BGGREEN = "42"
ANSI_ESCAPE_BGYELLOW = "43"
ANSI_ESCAPE_BGBLUE = "44"
ANSI_ESCAPE_BGMAGENTA = "45"
ANSI_ESCAPE_BGCYAN = "46"
ANSI_ESCAPE_BGWHITE = "47"
class << self class << self
color_enabled = nil color_enabled = nil
def info(message, options = {}) # Show an info message.
unless ENV["GUARD_ENV"] == "test" #
# @param [String] message the message to show
# @param [Hash] options the options
# @option options [Boolean] reset whether to clean the output before
#
def info(message, options = { })
unless ENV['GUARD_ENV'] == 'test'
reset_line if options[:reset] reset_line if options[:reset]
puts color(message) if message != '' puts color(message) if message != ''
end end
end end
def error(message, options={}) # Show a red error message that is prefixed with ERROR.
unless ENV["GUARD_ENV"] == "test" #
# @param [String] message the message to show
# @param [Hash] options the options
# @option options [Boolean] reset whether to clean the output before
#
def error(message, options = { })
unless ENV['GUARD_ENV'] == 'test'
reset_line if options[:reset] reset_line if options[:reset]
puts color('ERROR: ', :red) + message puts color('ERROR: ', :red) + message
end end
end end
def deprecation(message, options = {}) # Show a red deprecation message that is prefixed with DEPRECATION.
unless ENV["GUARD_ENV"] == "test" #
# @param [String] message the message to show
# @param [Hash] options the options
# @option options [Boolean] reset whether to clean the output before
#
def deprecation(message, options = { })
unless ENV['GUARD_ENV'] == 'test'
reset_line if options[:reset] reset_line if options[:reset]
puts color('DEPRECATION: ', :red) + message puts color('DEPRECATION: ', :red) + message
end end
end end
def debug(message, options={}) # Show a debug message that is prefixed with DEBUG and a timestamp.
unless ENV["GUARD_ENV"] == "test" #
# @param [String] message the message to show
# @param [Hash] options the options
# @option options [Boolean] reset whether to clean the output before
#
def debug(message, options = { })
unless ENV['GUARD_ENV'] == 'test'
reset_line if options[:reset] reset_line if options[:reset]
puts color("DEBUG (#{Time.now.strftime('%T')}): ", :yellow) + message if ::Guard.options && ::Guard.options[:debug] puts color("DEBUG (#{Time.now.strftime('%T')}): ", :yellow) + message if ::Guard.options && ::Guard.options[:debug]
end end
end end
# Reset a line.
#
def reset_line def reset_line
print(color_enabled? ? "\r\e[0m" : "\r\n") print(color_enabled? ? "\r\e[0m" : "\r\n")
end end
# Clear the output.
#
def clear def clear
system("clear;") system('clear;')
end end
private private
# Reset a color sequence.
#
# @deprecated # @deprecated
# @param [String] text the text
#
def reset_color(text) def reset_color(text)
deprecation('UI.reset_color(text) is deprecated, please use color(text, "") instead.') deprecation('UI.reset_color(text) is deprecated, please use color(text, ' ') instead.')
color(text, "") color(text, '')
end
def color(text, *color_options)
color_code = ""
color_options.each do |color_option|
color_option = color_option.to_s
if color_option != ""
if !(color_option =~ /\d+/)
color_option = const_get("ANSI_ESCAPE_#{color_option.upcase}")
end
color_code += ";" + color_option
end
end
color_enabled? ? "\e[0#{color_code}m#{text}\e[0m" : text
end end
# Checks if color output can be enabled.
#
# @return [Boolean] whether color is enabled or not
#
def color_enabled? def color_enabled?
if @color_enabled.nil? if @color_enabled.nil?
if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
@ -102,9 +106,87 @@ module Guard
@color_enabled = true @color_enabled = true
end end
end end
@color_enabled @color_enabled
end end
# Colorizes a text message. See the constant in the UI class for possible
# color_options parameters. You can pass optionally :bright, a foreground
# color and a background color.
#
# @example
#
# color('Hello World', :red, :bright)
#
# @param [String] the text to colorize
# @param [Array] color_options the color options
#
def color(text, *color_options)
color_code = ''
color_options.each do |color_option|
color_option = color_option.to_s
if color_option != ''
if !(color_option =~ /\d+/)
color_option = const_get("ANSI_ESCAPE_#{ color_option.upcase }")
end
color_code += ';' + color_option
end
end
color_enabled? ? "\e[0#{ color_code }m#{ text }\e[0m" : text
end
end end
# Brighten the color
ANSI_ESCAPE_BRIGHT = '1'
# Black foreground color
ANSI_ESCAPE_BLACK = '30'
# Red foreground color
ANSI_ESCAPE_RED = '31'
# Green foreground color
ANSI_ESCAPE_GREEN = '32'
# Yellow foreground color
ANSI_ESCAPE_YELLOW = '33'
# Blue foreground color
ANSI_ESCAPE_BLUE = '34'
# Magenta foreground color
ANSI_ESCAPE_MAGENTA = '35'
# Cyan foreground color
ANSI_ESCAPE_CYAN = '36'
# White foreground color
ANSI_ESCAPE_WHITE = '37'
# Black background color
ANSI_ESCAPE_BGBLACK = '40'
# Red background color
ANSI_ESCAPE_BGRED = '41'
# Green background color
ANSI_ESCAPE_BGGREEN = '42'
# Yellow background color
ANSI_ESCAPE_BGYELLOW = '43'
# Blue background color
ANSI_ESCAPE_BGBLUE = '44'
# Magenta background color
ANSI_ESCAPE_BGMAGENTA = '45'
# Cyan background color
ANSI_ESCAPE_BGCYAN = '46'
# White background color
ANSI_ESCAPE_BGWHITE = '47'
end end
end end

View File

@ -1,3 +1,6 @@
module Guard module Guard
VERSION = "0.7.0" unless defined? Guard::VERSION unless defined? Guard::VERSION
# The current gem version of Guard
VERSION = '0.7.0'
end
end end

View File

@ -1,7 +1,18 @@
module Guard module Guard
# The watcher defines a RegExp that will be matched against file system modifications.
# When a watcher matches a change, an optional action block is executed to enable
# processing the file system change result.
#
class Watcher class Watcher
attr_accessor :pattern, :action attr_accessor :pattern, :action
# Initialize a file watcher.
#
# @param [String, Regexp] pattern the pattern to be watched by the guard
# @param [Block] action the action to execute before passing the result to the Guard
#
def initialize(pattern, action = nil) def initialize(pattern, action = nil)
@pattern, @action = pattern, action @pattern, @action = pattern, action
@@warning_printed ||= false @@warning_printed ||= false
@ -10,14 +21,25 @@ module Guard
if @pattern.is_a?(String) && @pattern =~ /(^(\^))|(>?(\\\.)|(\.\*))|(\(.*\))|(\[.*\])|(\$$)/ if @pattern.is_a?(String) && @pattern =~ /(^(\^))|(>?(\\\.)|(\.\*))|(\(.*\))|(\[.*\])|(\$$)/
unless @@warning_printed unless @@warning_printed
UI.info "*"*20 + "\nDEPRECATION WARNING!\n" + "*"*20 UI.info "*"*20 + "\nDEPRECATION WARNING!\n" + "*"*20
UI.info "You have strings in your Guardfile's watch patterns that seem to represent regexps.\nGuard matchs String with == and Regexp with Regexp#match.\nYou should either use plain String (without Regexp special characters) or real Regexp.\n" UI.info <<-MSG
You have a string in your Guardfile watch patterns that seem to represent a Regexp.
Guard matches String with == and Regexp with Regexp#match.
You should either use plain String (without Regexp special characters) or real Regexp.
MSG
@@warning_printed = true @@warning_printed = true
end end
UI.info "\"#{@pattern}\" has been converted to #{Regexp.new(@pattern).inspect}\n"
UI.info "\"#{@pattern}\" has been converted to #{ Regexp.new(@pattern).inspect }\n"
@pattern = Regexp.new(@pattern) @pattern = Regexp.new(@pattern)
end end
end end
# Finds the files that matches a Guard.
#
# @param [Guard::Guard] guard the guard which watchers are used
# @param [Array<String>] files the changed files
# @return [Array<String>] the matched files
#
def self.match_files(guard, files) def self.match_files(guard, files)
guard.watchers.inject([]) do |paths, watcher| guard.watchers.inject([]) do |paths, watcher|
files.each do |file| files.each do |file|
@ -30,10 +52,17 @@ module Guard
end end
end end
end end
paths.flatten.map { |p| p.to_s } paths.flatten.map { |p| p.to_s }
end end
end end
# Test if a file would be matched by any of the Guards watchers.
#
# @param [Array<Guard::Guard>] guards the guards to use the watchers from
# @param [Array<String>] files the files to test
# @return [Boolean] Whether a file matches
#
def self.match_files?(guards, files) def self.match_files?(guards, files)
guards.any? do |guard| guards.any? do |guard|
guard.watchers.any? do |watcher| guard.watchers.any? do |watcher|
@ -42,6 +71,11 @@ module Guard
end end
end end
# Test the watchers pattern against a file.
#
# @param [String] file the file to test
# @return [Boolean] whether the given file is matched
#
def match_file?(file) def match_file?(file)
if @pattern.is_a?(Regexp) if @pattern.is_a?(Regexp)
file.match(@pattern) file.match(@pattern)
@ -50,15 +84,25 @@ module Guard
end end
end end
# Test if any of the files is the Guardfile.
#
# @param [Array<String>] the files to test
# @return [Boolean] whether one of these files is the Guardfile
#
def self.match_guardfile?(files) def self.match_guardfile?(files)
files.any? { |file| "#{Dir.pwd}/#{file}" == Dsl.guardfile_path } files.any? { |file| "#{ Dir.pwd }/#{ file }" == Dsl.guardfile_path }
end end
# Executes a watcher action.
#
# @param [String, MatchData] the matched path or the match from the Regex
# @return [String] the final paths
#
def call_action(matches) def call_action(matches)
begin begin
@action.arity > 0 ? @action.call(matches) : @action.call @action.arity > 0 ? @action.call(matches) : @action.call
rescue Exception => e rescue Exception => e
UI.error "Problem with watch action!\n#{e.message}\n\n#{e.backtrace.join("\n")}" UI.error "Problem with watch action!\n#{ e.message }\n\n#{ e.backtrace.join("\n") }"
end end
end end

188
man/guard
View File

@ -1,188 +0,0 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "GUARD" "" "September 2011" "" ""
.
.SH "NAME"
\fBguard\fR
.
.P
\fI!DOCTYPE html\fR
.
.P
.
.P
.
.IP "" 4
.
.nf
<a href="#NAME">NAME</a>
<a href="#NAME">NAME</a>
<a href="#NAME">NAME</a>
.
.fi
.
.IP "" 0
.
.P
.
.P
.
.IP "" 4
.
.nf
<li class=\'tl\'>guard</li>
<li class=\'tc\'></li>
<li class=\'tr\'>guard</li>
.
.fi
.
.IP "" 0
.
.P
.
.P
.
.P
\fBguard\fR
.
.P
\fI!DOCTYPE html\fR
.
.P
.
.P
.
.IP "" 4
.
.nf
<a href="#NAME">NAME</a>
<a href="#NAME">NAME</a>
.
.fi
.
.IP "" 0
.
.P
.
.P
.
.IP "" 4
.
.nf
<li class=\'tl\'>guard</li>
<li class=\'tc\'></li>
<li class=\'tr\'>guard</li>
.
.fi
.
.IP "" 0
.
.P
.
.P
.
.P
\fBguard\fR
.
.P
\fI!DOCTYPE html\fR
.
.P
.
.P
.
.IP "" 4
.
.nf
<a href="#NAME">NAME</a>
.
.fi
.
.IP "" 0
.
.P
.
.P
.
.IP "" 4
.
.nf
<li class=\'tl\'>guard</li>
<li class=\'tc\'></li>
<li class=\'tr\'>guard</li>
.
.fi
.
.IP "" 0
.
.P
.
.P
.
.P
\fBguard\fR
.
.P
\&\.\e" generated with Ronn/v0\.7\.3 \.\e" http://github\.com/rtomayko/ronn/tree/0\.7\.3 \. \.TH "GUARD" "1" "August 2011" "" "" \. \.SH "NAME" \efBguard\efR \- Guard keeps an eye on your file modifications\. \. \.SH "DESCRIPTION" Guard is a command line tool that easily handle events on files modifications\. \. \.SH "SYNOPSIS" \efBguard \fICOMMAND\fR \fIOPTIONS\fR\efR \. \.SH "COMMANDS" \. \.SS "start" Starts Guard\. This is the default command if none is provided\. \. \.P The following options are available: \. \.P \efB\-c\efR, \efB\-\-clear\efR Clears the Shell after each change\. \. \.P \efB\-n\efR, \efB\-\-notify\efR \efIFLAG\efR Disable notifications (Growl or Libnotify depending on your system)\. Notifications can be disabled globally by setting a GUARD_NOTIFY environment variable to false\. FLAG can be \efBtrue\efR/\efBfalse\efR or \efBt\efR/\efBf\efR\. \. \.P \efB\-d\efR, \efB\-\-debug\efR Runs Guard in debug mode\. \. \.P \efB\-g\efR, \efB\-\-group\efR \efIGROUP1\efR \efIGROUP2\efR\.\.\. Runs only the groups specified by GROUP1, GROUP2 etc\. Groups name should be separated by spaces\. Guards that don\e\'t belong to a group are considered global and are always run\. \. \.P \efB\-w\efR, \efB\-\-watchdir\efR \efIPATH\efR \. \.P Tells Guard to watch PATH instead of \efB\./\efR\. \. \.P \efB\-G\efR, \efB\-\-guardfile\efR \efIFILE\efR Tells Guard to use FILE as its Guardfile instead of \efB\./Guardfile\efR or \efB~/\.Guardfile\efR\. \. \.SS "init [GUARD]" If no Guardfile is present in the current directory, creates an empty Guardfile\. \. \.P If \efIGUARD\efR is present, add its default Guardfile configuration to the current Guardfile\. Note that \efIGUARD\efR is the guard\e\'s name without the \efBguard\-\efR prefix\. For instance to initialize guard\-rspec, run \efBguard init rspec\efR\. \. \.SS "list" Lists guards that can be used with the \efBinit\efR command\. \. \.SS "\-T, show" List defined groups and guards for the current Guardfile\. \. \.SS "\-h, help [COMMAND]" List all of Guard\e\'s available commands\. \. \.P If \efICOMMAND\efR is given, displays a specific help for \efITASK\efR\. \. \.SH "EXAMPLES" Initialize Guard and a specific guard at the same time: \. \.P \efB[bundle exec] guard init [rspec]\efR \. \.P Run Guard: \. \.P \efB[bundle exec] guard [start] \-\-watchdir ~/dev \-\-guardfile ~/env/Guardfile \-\-clear \-\-group backend frontend \-\-notify false \-\-debug\efR \. \.P or in a more concise way: \. \.P \efB[bundle exec] guard [start] \-w ~/dev \-G ~/env/Guardfile \-c \-g backend frontend \-n f \-d\efR \. \.SH "AUTHORS / CONTRIBUTORS" Thibaud Guillaume\-Gentil is the main author\. \. \.P A list of contributors based on all commits can be found here: https://github\.com/guard/guard/contributors \. \.P For an exhaustive list of all the contributors, please see the CHANGELOG: https://github\.com/guard/guard/blob/master/CHANGELOG\.md \. \.P This manual has been written by Remy Coutable\. \. \.SH "WWW" https://github\.com/guard/guard
.
.P
.
.IP "" 4
.
.nf
<li class=\'tl\'></li>
<li class=\'tc\'>September 2011</li>
<li class=\'tr\'>guard</li>
.
.fi
.
.IP "" 0
.
.P
.
.P
.
.P
.
.IP "" 4
.
.nf
<li class=\'tl\'></li>
<li class=\'tc\'>September 2011</li>
<li class=\'tr\'>guard</li>
.
.fi
.
.IP "" 0
.
.P
.
.P
.
.P
.
.IP "" 4
.
.nf
<li class=\'tl\'></li>
<li class=\'tc\'>September 2011</li>
<li class=\'tr\'>guard</li>
.
.fi
.
.IP "" 0
.
.P
.
.P

View File

@ -41,7 +41,7 @@ Tells Guard to watch PATH instead of \fB\./\fR\.
.P .P
\fB\-G\fR, \fB\-\-guardfile\fR \fIFILE\fR Tells Guard to use FILE as its Guardfile instead of \fB\./Guardfile\fR or \fB~/\.Guardfile\fR\. \fB\-G\fR, \fB\-\-guardfile\fR \fIFILE\fR Tells Guard to use FILE as its Guardfile instead of \fB\./Guardfile\fR or \fB~/\.Guardfile\fR\.
. .
.SS "init <a href=\"guard\.html\">GUARD</a>" .SS "init [GUARD]"
If no Guardfile is present in the current directory, creates an empty Guardfile\. If no Guardfile is present in the current directory, creates an empty Guardfile\.
. .
.P .P

View File

@ -112,7 +112,7 @@
<p><code>-G</code>, <code>--guardfile</code> <var>FILE</var> <p><code>-G</code>, <code>--guardfile</code> <var>FILE</var>
Tells Guard to use FILE as its Guardfile instead of <code>./Guardfile</code> or <code>~/.Guardfile</code>.</p> Tells Guard to use FILE as its Guardfile instead of <code>./Guardfile</code> or <code>~/.Guardfile</code>.</p>
<h3 id="init-GUARD">init <a href="guard.html">GUARD</a></h3> <h3 id="init-GUARD-">init [GUARD]</h3>
<p>If no Guardfile is present in the current directory, creates an empty Guardfile.</p> <p>If no Guardfile is present in the current directory, creates an empty Guardfile.</p>

View File

@ -56,7 +56,6 @@
<a href="#NAME">NAME</a> <a href="#NAME">NAME</a>
<a href="#NAME">NAME</a> <a href="#NAME">NAME</a>
<a href="#NAME">NAME</a> <a href="#NAME">NAME</a>
<a href="#NAME">NAME</a>
</div> </div>
<ol class='man-decor man-head man head'> <ol class='man-decor man-head man head'>
@ -130,7 +129,6 @@
<pre><code>&lt;a href="#NAME"&gt;NAME&lt;/a&gt; <pre><code>&lt;a href="#NAME"&gt;NAME&lt;/a&gt;
&lt;a href="#NAME"&gt;NAME&lt;/a&gt; &lt;a href="#NAME"&gt;NAME&lt;/a&gt;
&lt;a href="#NAME"&gt;NAME&lt;/a&gt;
</code></pre> </code></pre>
<p> </p> <p> </p>
@ -151,125 +149,15 @@
</p> </p>
<p><var>!DOCTYPE html</var>
<p>&lt;p<var>!DOCTYPE html</var>
<html> <html>
<head> <head>
<meta http-equiv="content-type" value="text/html;charset=utf8" /> <meta value="text/html;charset=utf8" http-equiv="content-type" />
<meta name="generator" value="Ronn/v0.7.3 (http://github.com/rtomayko/ronn/tree/0.7.3)" /> <meta name="generator" value="Ronn/v0.7.3 (http://github.com/rtomayko/ronn/tree/0.7.3)" />
<title>guard</title> <title>guard</title>
<style type="text/css" media="all"> <style media="all" type="text/css">
/<em> style: man </em>/
body#manpage {margin:0}
.mp {max-width:100ex;padding:0 9ex 1ex 4ex}
.mp p,.mp pre,.mp ul,.mp ol,.mp dl {margin:0 0 20px 0}
.mp h2 {margin:10px 0 0 0}
.mp > p,.mp > pre,.mp > ul,.mp > ol,.mp > dl {margin-left:8ex}
.mp h3 {margin:0 0 0 4ex}
.mp dt {margin:0;clear:left}
.mp dt.flush {float:left;width:8ex}
.mp dd {margin:0 0 0 9ex}
.mp h1,.mp h2,.mp h3,.mp h4 {clear:left}
.mp pre {margin-bottom:20px}
.mp pre+h2,.mp pre+h3 {margin-top:22px}
.mp h2+pre,.mp h3+pre {margin-top:5px}
.mp img {display:block;margin:auto}
.mp h1.man-title {display:none}
.mp,.mp code,.mp pre,.mp tt,.mp kbd,.mp samp,.mp h3,.mp h4 {font-family:monospace;font-size:14px;line-height:1.42857142857143}
.mp h2 {font-size:16px;line-height:1.25}
.mp h1 {font-size:20px;line-height:2}
.mp {text-align:justify;background:#fff}
.mp,.mp code,.mp pre,.mp pre code,.mp tt,.mp kbd,.mp samp {color:#131211}
.mp h1,.mp h2,.mp h3,.mp h4 {color:#030201}
.mp u {text-decoration:underline}
.mp code,.mp strong,.mp b {font-weight:bold;color:#131211}
.mp em,.mp var {font-style:italic;color:#232221;text-decoration:none}
.mp a,.mp a:link,.mp a:hover,.mp a code,.mp a pre,.mp a tt,.mp a kbd,.mp a samp {color:#0000ff}
.mp b.man-ref {font-weight:normal;color:#434241}
.mp pre {padding:0 4ex}
.mp pre code {font-weight:normal;color:#434241}
.mp h2+pre,h3+pre {padding-left:0}
ol.man-decor,ol.man-decor li {margin:3px 0 10px 0;padding:0;float:left;width:33%;list-style-type:none;text-transform:uppercase;color:#999;letter-spacing:1px}
ol.man-decor {width:100%}
ol.man-decor li.tl {text-align:left}
ol.man-decor li.tc {text-align:center;letter-spacing:4px}
ol.man-decor li.tr {text-align:right;float:right}
</style>
</head></html></p>
<!--
The following styles are deprecated and will be removed at some point:
div#man, div#man ol.man, div#man ol.head, div#man ol.man.
The .man-page, .man-decor, .man-head, .man-foot, .man-title, and
.man-navigation should be used instead.
-->
<p><body id="manpage">
<div class="mp" id="man" /></body></p>
<p> <div class="man-navigation" style="display:none" /></p>
<pre><code>&lt;a href="#NAME"&gt;NAME&lt;/a&gt;
&lt;a href="#NAME"&gt;NAME&lt;/a&gt;
</code></pre>
<p> </p>
<p> <ol class="man-decor man-head man head" /></p>
<pre><code>&lt;li class='tl'&gt;guard&lt;/li&gt;
&lt;li class='tc'&gt;&lt;/li&gt;
&lt;li class='tr'&gt;guard&lt;/li&gt;
</code></pre>
<p> </p>
<p> <h2 id="NAME">NAME</h2></p>
<p class="man-name">
<code>guard</code>
</p>
<p><var>!DOCTYPE html</var>
<html>
<head>
<meta http-equiv="content-type" value="text/html;charset=utf8" />
<meta name="generator" value="Ronn/v0.7.3 (http://github.com/rtomayko/ronn/tree/0.7.3)" />
<title>guard</title>
<style type="text/css" media="all">
/<em> style: man </em>/ /<em> style: man </em>/
body#manpage {margin:0} body#manpage {margin:0}
.mp {max-width:100ex;padding:0 9ex 1ex 4ex} .mp {max-width:100ex;padding:0 9ex 1ex 4ex}
@ -374,6 +262,11 @@
<blockquote><blockquote><blockquote><blockquote><blockquote><blockquote><blockquote><p>master</p></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote>
<p>.\" generated with Ronn/v0.7.3 <p>.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3
. .
@ -496,6 +389,71 @@ https://github.com/guard/guard</p>
<p><var>&lt;&lt;&lt;&lt;&lt;&lt; HEAD</var></p>
<p>&lt;p <ol class="man-decor man-foot man foot" /></p>
<pre><code>&lt;li class='tl'&gt;&lt;/li&gt;
&lt;li class='tc'&gt;September 2011&lt;/li&gt;
&lt;li class='tr'&gt;guard&lt;/li&gt;
</code></pre>
<p> </p>
<p>
</p>
<p>=======</p>
<blockquote><blockquote><blockquote><blockquote><blockquote><blockquote><blockquote><p>master</p></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote></blockquote>
<p> <ol class="man-decor man-foot man foot" /></p>
<pre><code>&lt;li class='tl'&gt;&lt;/li&gt;
&lt;li class='tc'&gt;September 2011&lt;/li&gt;
&lt;li class='tr'&gt;guard&lt;/li&gt;
</code></pre>
<p> </p>
<p>
</p>
<p> <ol class="man-decor man-foot man foot" /></p> <p> <ol class="man-decor man-foot man foot" /></p>

View File

@ -235,17 +235,17 @@ describe Guard::Dsl do
disable_user_config 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 })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => ['w']) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
::Guard.groups.should eql [{ :name => :default, :options => {} }, { :name => :w, :options => {} }] ::Guard.groups.should eql [{ :name => :default, :options => {} }, { :name => :w, :options => {} }]
end end
it "evaluates only the specified symbol group" do it "evaluates only the specified symbol 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 })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w]) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
@ -253,10 +253,10 @@ describe Guard::Dsl do
end end
it "evaluates only the specified groups (with their options)" do it "evaluates only the specified groups (with their options)" 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(:rspec, [], [], { :group => :x }) ::Guard.should_receive(:add_guard).with('rspec', [], [], { :group => :x })
::Guard.should_receive(:add_guard).with(:ronn, [], [], { :group => :x }) ::Guard.should_receive(:add_guard).with('ronn', [], [], { :group => :x })
::Guard.should_receive(:add_guard).with(:less, [], [], { :group => :y }) ::Guard.should_receive(:add_guard).with('less', [], [], { :group => :y })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:x, :y]) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:x, :y])
@ -264,8 +264,8 @@ describe Guard::Dsl do
end end
it "evaluates always guard outside any group (even when a group is given)" do it "evaluates always guard outside any group (even when a group is given)" do
::Guard.should_receive(:add_guard).with(:pow, [], [], { :group => :default }) ::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :w }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w]) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
@ -273,11 +273,11 @@ describe Guard::Dsl do
end end
it "evaluates all groups when no group option is specified (with their options)" do it "evaluates all groups when no group option is specified (with their options)" 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 })
::Guard.should_receive(:add_guard).with(:rspec, [], [], { :group => :x }) ::Guard.should_receive(:add_guard).with('rspec', [], [], { :group => :x })
::Guard.should_receive(:add_guard).with(:ronn, [], [], { :group => :x }) ::Guard.should_receive(:add_guard).with('ronn', [], [], { :group => :x })
::Guard.should_receive(:add_guard).with(:less, [], [], { :group => :y }) ::Guard.should_receive(:add_guard).with('less', [], [], { :group => :y })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string)
@ -290,31 +290,31 @@ describe Guard::Dsl do
disable_user_config 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 })
subject.evaluate_guardfile(:guardfile_contents => "guard 'test'") subject.evaluate_guardfile(:guardfile_contents => "guard 'test'")
end end
it "loads a guard specified as a double quoted string from the DSL" do it "loads a guard specified as a double quoted string from the DSL" do
::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :default }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default })
subject.evaluate_guardfile(:guardfile_contents => 'guard "test"') subject.evaluate_guardfile(:guardfile_contents => 'guard "test"')
end end
it "loads a guard specified as a symbol from the DSL" do it "loads a guard specified as a symbol from the DSL" do
::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :default }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default })
subject.evaluate_guardfile(:guardfile_contents => "guard :test") subject.evaluate_guardfile(:guardfile_contents => "guard :test")
end end
it "loads a guard specified as a symbol and called with parens from the DSL" do it "loads a guard specified as a symbol and called with parens from the DSL" do
::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :default }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default })
subject.evaluate_guardfile(:guardfile_contents => "guard(:test)") subject.evaluate_guardfile(:guardfile_contents => "guard(:test)")
end end
it "receives options when specified, from normal arg" do it "receives options when specified, from normal arg" do
::Guard.should_receive(:add_guard).with(:test, [], [], { :opt_a => 1, :opt_b => 'fancy', :group => :default }) ::Guard.should_receive(:add_guard).with('test', [], [], { :opt_a => 1, :opt_b => 'fancy', :group => :default })
subject.evaluate_guardfile(:guardfile_contents => "guard 'test', :opt_a => 1, :opt_b => 'fancy'") subject.evaluate_guardfile(:guardfile_contents => "guard 'test', :opt_a => 1, :opt_b => 'fancy'")
end end
@ -324,7 +324,7 @@ describe Guard::Dsl do
disable_user_config 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
watchers[0].pattern.should == 'a' watchers[0].pattern.should == 'a'
watchers[0].action.call.should == proc { 'b' }.call watchers[0].action.call.should == proc { 'b' }.call
@ -347,7 +347,7 @@ describe Guard::Dsl do
end end
end end
::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|
callbacks.should have(2).items callbacks.should have(2).items
callbacks[0][:events].should == :start_end callbacks[0][:events].should == :start_end
callbacks[0][:listener].call(Guard::Dummy, :start_end, 'foo').should == "Guard::Dummy executed 'start_end' hook with foo!" callbacks[0][:listener].call(Guard::Dummy, :start_end, 'foo').should == "Guard::Dummy executed 'start_end' hook with foo!"
@ -373,17 +373,17 @@ private
" "
guard :pow guard :pow
group 'w' do group :w do
guard 'test' guard :test
end end
group :x, :halt_on_fail => true do group :x, :halt_on_fail => true do
guard 'rspec' guard :rspec
guard :ronn guard :ronn
end end
group 'y' do group :y do
guard 'less' guard :less
end end
" "
end end

View File

@ -4,20 +4,22 @@ describe Guard::Interactor do
subject { Guard::Interactor.new } subject { Guard::Interactor.new }
describe "#initialize" do describe "#initialize" do
it "un-lock by default" do it "unlocks the interactor by default" do
subject.locked.should be_false subject.locked.should be_false
end end
end end
describe "#lock" do describe "#lock" do
it "locks" do it "locks the interactor" do
subject.start
subject.lock subject.lock
subject.locked.should be_true subject.locked.should be_true
end end
end end
describe "#unlock" do describe "#unlock" do
it "unlocks" do it "unlocks the interactor" do
subject.start
subject.unlock subject.unlock
subject.locked.should be_false subject.locked.should be_false
end end

View File

@ -172,7 +172,7 @@ describe Guard::Listener do
describe "#ignore_paths" do describe "#ignore_paths" do
it "defaults to the default ignore paths" do it "defaults to the default ignore paths" do
subject.new.ignore_paths.should == Guard::Listener::DefaultIgnorePaths subject.new.ignore_paths.should == Guard::Listener::DEFAULT_IGNORE_PATHS
end end
it "can be added to via :ignore_paths option" do it "can be added to via :ignore_paths option" do