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/*
doc/*
*.gem
*.rbc
.*.swp
*.bak
.bundle
.yardoc
Gemfile.lock
## MAC OS
@ -12,4 +14,4 @@ Gemfile.lock
.com.apple.timemachine.supported
.fseventsd
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.rc1 - September 5, 2011
@ -250,6 +254,7 @@
[@fnichol]: https://github.com/fnichol
[@Gazer]: https://github.com/Gazer
[@gix]: https://github.com/gix
[@hron]: https://github.com/hron
[@hashrocketeer]: https://github.com/hashrocketeer
[@ianwhite]: https://github.com/ianwhite
[@indirect]: https://github.com/indirect

373
README.md
View File

@ -26,27 +26,19 @@ Install
Install the gem:
``` bash
$ gem install guard
```
$ gem install guard
Or add it to your Gemfile (inside the `development` group):
``` ruby
gem 'guard'
```
gem 'guard'
and install it via Bundler:
``` bash
$ bundle install
```
$ bundle install
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.
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:
``` bash
$ gem install rb-fsevent
```
$ gem install rb-fsevent
You have two possibilities:
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).
``` bash
$ brew install growlnotify
$ gem install growl
```
$ brew install growlnotify
$ gem install growl
And add them to your Gemfile:
``` ruby
gem 'rb-fsevent'
gem 'growl_notify' # or gem 'growl'
```
gem 'rb-fsevent'
gem 'growl_notify' # or gem 'growl'
The difference between growl and growl_notify is that growl_notify uses AppleScript to
display a message, whereas growl uses the `growlnotify` command. In general the AppleScript
@ -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:
``` bash
$ gem install rb-inotify
```
$ gem install rb-inotify
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:
``` ruby
gem 'rb-inotify'
gem 'libnotify'
```
gem 'rb-inotify'
gem 'libnotify'
### 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:
``` 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:
``` bash
$ gem install win32console
```
$ gem install win32console
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:
``` ruby
gem 'rb-fchange'
gem 'rb-notifu'
gem 'win32console'
```
gem 'rb-fchange'
gem 'rb-notifu'
gem 'win32console'
Usage
-----
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:
``` 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.
@ -161,19 +127,15 @@ Command line options
Shell can be cleared after each change:
``` bash
$ guard --clear
$ guard -c # shortcut
```
$ guard --clear
$ guard -c # shortcut
### `-n`/`--notify` option
Notifications (growl/libnotify) can be disabled:
``` bash
$ guard --notify false
$ guard -n f # shortcut
```
$ guard --notify false
$ guard -n f # shortcut
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):
``` bash
$ guard --group group_name another_group_name
$ guard -g group_name another_group_name # shortcut
```
$ guard --group group_name another_group_name
$ guard -g group_name another_group_name # shortcut
### `-d`/`--debug` option
Guard can be run in debug mode:
``` bash
$ guard --debug
$ guard -d # shortcut
```
$ guard --debug
$ guard -d # shortcut
### `-w`/`--watchdir` option
Guard can watch in any directory (instead of the current directory):
``` bash
$ guard --watchdir ~/your/fancy/project
$ guard -w ~/your/fancy/project # shortcut
```
$ guard --watchdir ~/your/fancy/project
$ guard -w ~/your/fancy/project # shortcut
### `-G`/`--guardfile` option
Guard can use a Guardfile not located in the current directory:
``` bash
$ guard --guardfile ~/.your_global_guardfile
$ guard -G ~/.your_global_guardfile # shortcut
```
$ guard --guardfile ~/.your_global_guardfile
$ guard -G ~/.your_global_guardfile # shortcut
An exhaustive list of options is available with:
``` bash
$ guard help [TASK]
```
$ guard help [TASK]
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):
``` ruby
gem '<guard-name>'
```
gem '<guard-name>'
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:
``` 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.
@ -271,36 +217,34 @@ The Guardfile DSL consists of the following methods:
Example:
``` ruby
ignore_paths 'foo', 'bar'
ignore_paths 'foo', 'bar'
group 'backend' do
guard 'bundler' do
watch('Gemfile')
end
group 'backend' do
guard 'bundler' do
watch('Gemfile')
end
guard 'rspec', :cli => '--color --format doc' do
# Regexp watch patterns are matched with Regexp#match
watch(%r{^spec/.+_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/.+\.rb$}) { `say hello` }
guard 'rspec', :cli => '--color --format doc' do
# Regexp watch patterns are matched with Regexp#match
watch(%r{^spec/.+_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/.+\.rb$}) { `say hello` }
# String watch patterns are matched with simple '=='
watch('spec/spec_helper.rb') { "spec" }
end
end
# String watch patterns are matched with simple '=='
watch('spec/spec_helper.rb') { "spec" }
end
end
group 'frontend' do
guard 'coffeescript', :output => 'public/javascripts/compiled' do
watch(%r{^app/coffeescripts/.+\.coffee$})
end
group 'frontend' do
guard 'coffeescript', :output => 'public/javascripts/compiled' do
watch(%r{^app/coffeescripts/.+\.coffee$})
end
guard 'livereload' do
watch(%r{^app/.+\.(erb|haml)$})
end
end
```
guard 'livereload' do
watch(%r{^app/.+\.(erb|haml)$})
end
end
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:
``` ruby
gem 'guard'
require 'guard'
gem 'guard'
require 'guard'
Guard.setup
Guard.setup
Guard::Dsl.evaluate_guardfile(:guardfile => '/your/custom/path/to/a/valid/Guardfile')
# or
Guard::Dsl.evaluate_guardfile(:guardfile_contents => "
guard 'rspec' do
watch(%r{^spec/.+_spec\.rb$})
end
")
```
Guard::Dsl.evaluate_guardfile(:guardfile => '/your/custom/path/to/a/valid/Guardfile')
# or
Guard::Dsl.evaluate_guardfile(:guardfile_contents => "
guard 'rspec' do
watch(%r{^spec/.+_spec\.rb$})
end
")
### 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`:
``` bash
# guard -T
$ guard -T
(global):
shell
Group backend:
bundler
rspec: cli => "--color --format doc"
Group frontend:
coffeescript: output => "public/javascripts/compiled"
livereload
```
(global):
shell
Group backend:
bundler
rspec: cli => "--color --format doc"
Group frontend:
coffeescript: output => "public/javascripts/compiled"
livereload
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
[Ctags](http://ctags.sourceforge.net):
``` ruby
guard 'shell' do
watch(%r{^(?:app|lib)/.+\.rb$}) { `ctags -R` }
end
```
guard 'shell' do
watch(%r{^(?:app|lib)/.+\.rb$}) { `ctags -R` }
end
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:
```
.travis.yml # bonus point!
CHANGELOG.md # bonus point!
Gemfile
guard-name.gemspec
Guardfile
lib/
guard/
guard-name/
templates/
Guardfile # needed for `guard init <guard-name>`
version.rb
guard-name.rb
test/ # or spec/
README.md
```
.travis.yml # bonus point!
CHANGELOG.md # bonus point!
Gemfile
guard-name.gemspec
Guardfile
lib/
guard/
guard-name/
templates/
Guardfile # needed for `guard init <guard-name>`
version.rb
guard-name.rb
test/ # or spec/
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.
Here is an example scaffold for `lib/guard/guard-name.rb`:
``` ruby
require 'guard'
require 'guard/guard'
require 'guard'
require 'guard/guard'
module Guard
class GuardName < Guard
module Guard
class GuardName < Guard
def initialize(watchers=[], options={})
super
# init stuff here, thx!
def initialize(watchers=[], options={})
super
# 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
# =================
# = 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.
Alternatively, a new guard can be added inline to a Guardfile with this basic structure:
``` ruby
require 'guard/guard'
require 'guard/guard'
module ::Guard
class InlineGuard < ::Guard::Guard
def run_all
true
end
module ::Guard
class InlineGuard < ::Guard::Guard
def run_all
true
end
def run_on_change(paths)
true
def run_on_change(paths)
true
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
Development
-----------
* Documentation hosted at [RubyDoc](http://rubydoc.info/github/guard/guard/master/frames).
* Source hosted at [GitHub](https://github.com/guard/guard).
* 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.rubyforge_project = 'guard'
s.add_dependency 'thor', '~> 0.14.6'
s.add_development_dependency 'bundler'
s.add_development_dependency 'rspec', '~> 2.6.0'
s.add_development_dependency 'guard-rspec', '~> 0.3.1'
s.add_dependency 'thor', '~> 0.14.6'
s.add_development_dependency 'yard', '~> 0.7.2'
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.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
autoload :UI, 'guard/ui'
@ -12,7 +15,16 @@ module Guard
class << self
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 = {})
@options = options
@guards = []
@ -20,7 +32,7 @@ module Guard
@interactor = Interactor.new
@listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd)
@options[:notify] && ENV["GUARD_NOTIFY"] != 'false' ? Notifier.turn_on : Notifier.turn_off
@options[:notify] && ENV['GUARD_NOTIFY'] != 'false' ? Notifier.turn_on : Notifier.turn_off
UI.clear if @options[:clear]
debug_command_execution if @options[:debug]
@ -28,6 +40,17 @@ module Guard
self
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 = {})
setup(options)
@ -38,7 +61,7 @@ module Guard
listener.changed_files += files if Watcher.match_files?(guards, files)
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)
@ -46,42 +69,57 @@ module Guard
listener.start
end
# Stop Guard listening to file changes
#
def stop
UI.info "Bye bye...", :reset => true
UI.info 'Bye bye...', :reset => true
listener.stop
execute_supervised_task_for_all_guards(:stop)
abort
end
# Reload all Guards currently enabled.
#
def reload
run do
execute_supervised_task_for_all_guards(:reload)
end
end
# Trigger `run_all` on all Guards currently enabled.
#
def run_all
run do
execute_supervised_task_for_all_guards(:run_all)
end
end
# Pause Guard listening to file changes.
#
def pause
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.unlock
else
UI.info "Paused files modification listening", :reset => true
UI.info 'Paused files modification listening', :reset => true
listener.lock
end
end
def run_on_change(files)
# Trigger `run_on_change` on all Guards currently enabled.
#
def run_on_change(paths)
run do
execute_supervised_task_for_all_guards(:run_on_change, files)
execute_supervised_task_for_all_guards(:run_on_change, paths)
end
end
# Run a block where the listener and the interactor is
# blocked.
#
# @yield the block to run
#
def run
listener.lock
interactor.lock
@ -94,12 +132,19 @@ module Guard
listener.unlock
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)
groups.each do |group_hash|
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|
paths = Watcher.match_files(guard, files) if files
if paths && !paths.empty?
if task == :run_on_change
paths = Watcher.match_files(guard, files)
UI.debug "#{guard.class.name}##{task} with #{paths.inspect}"
supervised_task(guard, task, paths)
else
@ -110,25 +155,40 @@ module Guard
end
end
# Let a guard execute its task but
# fire it if his work leads to a system failure
# Let a Guard execute its task, but fire it
# 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)
guard.hook("#{task_to_supervise}_begin", *args)
guard.hook("#{ task_to_supervise }_begin", *args)
result = guard.send(task_to_supervise, *args)
guard.hook("#{task_to_supervise}_end", result)
guard.hook("#{ task_to_supervise }_end", result)
result
rescue Exception => ex
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")}")
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") }")
guards.delete guard
UI.info("\n#{guard.class.name} has just been fired")
return ex
UI.info("\n#{ guard.class.name } has just been fired")
ex
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 = {})
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
guard_class = get_guard_class(name)
callbacks.each { |callback| Hook.add_callback(callback[:listener], guard_class, callback[:events]) }
@ -136,42 +196,60 @@ module Guard
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 = {})
@groups << { :name => name.to_sym, :options => options } unless name.nil? || @groups.find { |group| group[:name] == name }
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)
name = name.to_s
try_require = false
const_name = name.downcase.gsub('-', '')
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 })
rescue TypeError
unless try_require
try_require = true
retry
else
UI.error "Could not find class Guard::#{const_name.capitalize}"
UI.error "Could not find class Guard::#{ const_name.capitalize }"
end
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
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)
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
Gem.source_index.find_name("guard-#{name}").last.full_gem_path
Gem.source_index.find_name("guard-#{ name }").last.full_gem_path
end
rescue
UI.error "Could not find 'guard-#{name}' gem path."
UI.error "Could not find 'guard-#{ name }' gem path."
end
##
# Returns a list of guard Gem names installed locally.
#
# @return [Array<String>] a list of guard gem names
#
def guard_gem_names
if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
Gem::Specification.find_all.select { |x| x.name =~ /^guard-/ }
@ -180,16 +258,19 @@ module Guard
end.map { |x| x.name.sub /^guard-/, '' }
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
Kernel.send(:alias_method, :original_system, :system)
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
end
Kernel.send(:alias_method, :original_backtick, :"`")
Kernel.send(:define_method, :"`") do |command|
::Guard::UI.debug "Command execution: #{command}"
Kernel.send(:alias_method, :original_backtick, :'`')
Kernel.send(:define_method, :'`') do |command|
::Guard::UI.debug "Command execution: #{ command }"
original_backtick command
end
end

View File

@ -2,89 +2,172 @@ require 'thor'
require 'guard/version'
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
default_task :start
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"
desc 'start', 'Starts Guard'
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
::Guard.start(options)
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
::Guard::DslDescriber.evaluate_guardfile(options)
installed = []
::Guard::DslDescriber.guardfile_structure.each do |group|
group[:guards].each {|x| installed << x[:name]} if group[:guards]
Guard::DslDescriber.evaluate_guardfile(options)
installed = Guard::DslDescriber.guardfile_structure.inject([]) do |installed, group|
group[:guards].each { |guard| installed << guard[:name] } if group[:guards]
installed
end
::Guard::UI.info "Available guards:"
::Guard::guard_gem_names.sort.each do |name|
if installed.include? name
::Guard::UI.info " #{name} *"
else
::Guard::UI.info " #{name}"
end
Guard::UI.info 'Available guards:'
Guard::guard_gem_names.sort.uniq.each do |name|
Guard::UI.info " #{ name } #{ installed.include?(name) ? '*' : '' }"
end
::Guard::UI.info ' '
::Guard::UI.info "See also https://github.com/guard/guard/wiki/List-of-available-Guards"
::Guard::UI.info "* denotes ones already in your Guardfile"
Guard::UI.info ' '
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
desc "version", "Prints Guard's version"
def version
::Guard::UI.info "Guard version #{Guard::VERSION}"
end
desc 'version', 'Show the Guard 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"
def init(guard_name = nil)
if !File.exist?("Guardfile")
puts "Writing new Guardfile to #{Dir.pwd}/Guardfile"
FileUtils.cp(File.expand_path('../templates/Guardfile', __FILE__), 'Guardfile')
elsif guard_name.nil?
::Guard::UI.error "Guardfile already exists at #{Dir.pwd}/Guardfile"
exit 1
end
# Shows the current version of Guard.
#
# @see Guard::VERSION
#
def version
Guard::UI.info "Guard version #{ Guard::VERSION }"
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
guard_class = ::Guard.get_guard_class(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
desc "show", "Show all defined Guards and their options"
def show
::Guard::DslDescriber.evaluate_guardfile(options)
desc 'show', 'Show all defined Guards and their options'
map %w(-T) => :show
::Guard::DslDescriber.guardfile_structure.each do |group|
if !group[:guards].empty?
# Shows all Guards and their options that are defined in
# 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]
::Guard::UI.info "Group #{group[:group]}:"
Guard::UI.info "Group #{ group[:group] }:"
else
::Guard::UI.info "(global):"
Guard::UI.info '(global):'
end
group[:guards].each do |guard|
line = " #{guard[:name]}"
line = " #{ guard[:name] }"
if !guard[:options].empty?
line += ": #{guard[:options].collect { |k, v| "#{k} => #{v.inspect}" }.join(", ")}"
unless guard[:options].empty?
line += ": #{ guard[:options].collect { |k, v| "#{ k } => #{ v.inspect }" }.join(', ') }"
end
::Guard::UI.info line
Guard::UI.info line
end
end
end
::Guard::UI.info ''
Guard::UI.info ''
end
map %w(-T) => :show
end
end

View File

@ -1,63 +1,161 @@
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 << self
@@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 = {})
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
fetch_guardfile_contents
instance_eval_guardfile(guardfile_contents_with_user_config)
UI.error "No guards found in Guardfile, please add at least one." if !::Guard.guards.nil? && ::Guard.guards.empty?
UI.error 'No guards found in Guardfile, please add at least one.' if !::Guard.guards.nil? && ::Guard.guards.empty?
end
# Re-evaluate the `Guardfile` to update the current Guard configuration.
#
def reevaluate_guardfile
::Guard.guards.clear
::Guard.groups.clear
@@options.delete(:guardfile_contents)
Dsl.evaluate_guardfile(@@options)
msg = "Guardfile has been re-evaluated."
msg = 'Guardfile has been re-evaluated.'
UI.info(msg)
Notifier.notify(msg)
end
# Evaluate the content of the `Guardfile`.
#
# @param [String] contents the content to evaluate.
#
def instance_eval_guardfile(contents)
begin
new.instance_eval(contents, @@options[:guardfile_path], 1)
rescue
UI.error "Invalid Guardfile, original error is:\n#{$!}"
exit 1
end
new.instance_eval(contents, @@options[:guardfile_path], 1)
rescue
UI.error "Invalid Guardfile, original error is:\n#{ $! }"
exit 1
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)
guardfile_contents.match(/^guard\s*\(?\s*['":]#{guard_name}['"]?/)
guardfile_contents.match(/^guard\s*\(?\s*['":]#{ guard_name }['"]?/)
end
# Read the current `Guardfile` content.
#
# @param [String] the path to the Guardfile
#
def read_guardfile(guardfile_path)
begin
@@options[:guardfile_path] = guardfile_path
@@options[:guardfile_contents] = File.read(guardfile_path)
rescue
UI.error("Error reading file #{guardfile_path}")
exit 1
end
@@options[:guardfile_path] = guardfile_path
@@options[:guardfile_contents] = File.read(guardfile_path)
rescue
UI.error("Error reading file #{ guardfile_path }")
exit 1
end
# Get the content to evaluate and stores it into
# the options as `:guardfile_contents`.
#
def fetch_guardfile_contents
# TODO: do we need .rc file interaction?
if @@options[:guardfile_contents]
UI.info "Using inline Guardfile."
UI.info 'Using inline Guardfile.'
@@options[:guardfile_path] = 'Inline Guardfile'
elsif @@options[:guardfile]
if File.exist?(@@options[:guardfile])
read_guardfile(@@options[:guardfile])
UI.info "Using Guardfile at #{@@options[:guardfile]}."
UI.info "Using Guardfile at #{ @@options[:guardfile] }."
else
UI.error "No Guardfile exists at #{@@options[:guardfile]}."
UI.error "No Guardfile exists at #{ @@options[:guardfile] }."
exit 1
end
@ -65,86 +163,209 @@ module Guard
if File.exist?(guardfile_default_path)
read_guardfile(guardfile_default_path)
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
end
end
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
end
end
# Get the content of the `Guardfile`.
#
# @return [String] the Guardfile content
#
def guardfile_contents
@@options ? @@options[:guardfile_contents] : ""
@@options ? @@options[:guardfile_contents] : ''
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
config = File.read(user_config_path) if File.exist?(user_config_path)
[guardfile_contents, config].join("\n")
end
# Get the file path to the project `Guardfile`.
#
# @return [String] the path to the Guardfile
#
def guardfile_path
@@options ? @@options[:guardfile_path] : ""
@@options ? @@options[:guardfile_path] : ''
end
# Tests if the current `Guardfile` content is usable.
#
# @return [Boolean] if the Guardfile is 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
# 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
File.exist?(local_guardfile_path) ? local_guardfile_path : home_guardfile_path
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
File.join(Dir.pwd, "Guardfile")
File.join(Dir.pwd, 'Guardfile')
end
# The path to the `.Guardfile` that is located at
# the users home directory.
#
# @param [String] the path to ~/.Guardfile
#
def home_guardfile_path
File.expand_path(File.join("~", ".Guardfile"))
File.expand_path(File.join('~', '.Guardfile'))
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
File.expand_path(File.join("~", ".guard.rb"))
File.expand_path(File.join('~', '.guard.rb'))
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] || []
name = name.to_sym
name = name.to_sym
if guard_definition && (@groups.empty? || @groups.map(&:to_sym).include?(name))
::Guard.add_group(name.to_s.downcase.to_sym, options)
if block_given? && (@groups.empty? || @groups.map(&:to_sym).include?(name))
::Guard.add_group(name.to_s.downcase, options)
@current_group = name
guard_definition.call
yield if block_given?
@current_group = nil
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 = []
@callbacks = []
watch_and_callback_definition.call if watch_and_callback_definition
yield if block_given?
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
# 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)
@watchers << ::Guard::Watcher.new(pattern, action)
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)
listener, events = args.size > 1 ? args : [listener, args[0]]
@callbacks << { :events => events, :listener => listener }
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)
UI.info "Ignoring paths: #{paths.join(', ')}"
UI.info "Ignoring paths: #{ paths.join(', ') }"
::Guard.listener.ignore_paths.push(*paths)
end
end
end

View File

@ -1,28 +1,60 @@
require 'guard/dsl'
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
@@guardfile_structure = [ { :guards => [] } ]
class << self
# Get the Guardfile structure.
#
# @return [Array<Hash>] the structure
#
def guardfile_structure
@@guardfile_structure
end
end
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
guard_definition.call
yield if block_given?
@group = false
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[:guards] << { :name => name, :options => options }
end
end
end

View File

@ -1,21 +1,39 @@
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
include Hook
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 = {})
@group = options.delete(:group) || :default
@watchers, @options = watchers, options
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)
if ::Guard::Dsl.guardfile_include?(name)
::Guard::UI.info "Guardfile already includes #{name} guard"
::Guard::UI.info "Guardfile already includes #{ name } guard"
else
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|
f.puts(content)
f.puts("")
@ -25,31 +43,43 @@ module Guard
end
end
# ================
# = Guard method =
# ================
# Call once when guard starts
# Please override initialize method to init stuff
# Call once when Guard starts. Please override initialize method to init stuff.
#
# @return [Boolean] Whether the start action was successful or not
#
def start
true
end
# Call once when guard quit
# Call once when Guard quit.
#
# @return [Boolean] Whether the stop action was successful or not
#
def stop
true
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
true
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
true
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)
true
end

View File

@ -1,51 +1,82 @@
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
# The Hook module gets included.
#
# @param [Class] base the class that includes the module
#
def self.included(base)
base.send :include, InstanceMethods
end
# Instance methods that gets included in the base class.
#
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.
# Example:
#
# @example Add a hook with a Symbol
#
# def run_all
# hook :foo
# 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.
#
# 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.
# Example:
#
# @example Add a hook with a String
#
# def run_all
# hook "foo_bar"
# 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.
#
# +args+ parameter is passed as is to the callbacks registered
# for the given event.
# @param [Symbol, String] event the name of the Guard event
# @param [Array] args the parameters are passed as is to the callbacks registered for the given event.
#
def hook(event, *args)
hook_name = if event.is_a? Symbol
calling_method = caller[0][/`([^']*)'/, 1]
"#{calling_method}_#{event}"
else
event
end.to_sym
calling_method = caller[0][/`([^']*)'/, 1]
"#{ calling_method }_#{ event }"
else
event
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)
end
end
class << self
# Get all callbacks.
#
def callbacks
@callbacks ||= Hash.new { |hash, key| hash[key] = [] }
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)
_events = events.is_a?(Array) ? events : [events]
_events.each do |event|
@ -53,19 +84,34 @@ module Guard
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)
callbacks[[guard_class, event]].include?(listener)
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)
callbacks[[guard_class, event]].each do |listener|
listener.call(guard_class, event, *args)
end
end
# Reset all callbacks.
#
def reset_callbacks!
@callbacks = nil
end
end
end

View File

@ -1,39 +1,76 @@
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 LockException < Exception; end
class UnlockException < Exception; end
attr_reader :locked
# Initialize the interactor in unlocked state.
#
def initialize
@locked = false
end
# Start the interactor in its own thread.
#
def start
return if ENV["GUARD_ENV"] == 'test'
Thread.new do
@thread = Thread.new do
loop do
if (entry = $stdin.gets) && !@locked
entry.gsub! /\n/, ''
case entry
when 'stop', 'quit', 'exit', 's', 'q', 'e'
::Guard.stop
when 'reload', 'r', 'z'
::Guard.reload
when 'pause', 'p'
::Guard.pause
else
::Guard.run_all
begin
if !@locked && (entry = $stdin.gets)
entry.gsub! /\n/, ''
case entry
when 'stop', 'quit', 'exit', 's', 'q', 'e'
::Guard.stop
when 'reload', 'r', 'z'
::Guard.reload
when 'pause', 'p'
::Guard.pause
else
::Guard.run_all
end
end
rescue LockException
lock
rescue UnlockException
unlock
end
end
end
end
# Lock the interactor.
#
def lock
@locked = true
if !@thread || @thread == Thread.current
@locked = true
else
@thread.raise(LockException)
end
end
# Unlock the interactor.
#
def unlock
@locked = false
if !@thread || @thread == Thread.current
@locked = false
else
@thread.raise(UnlockException)
end
end
end

View File

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

View File

@ -1,41 +1,60 @@
module Guard
# Listener implementation for Mac OS X `FSEvents`.
#
class Darwin < Listener
# Initialize the Listener.
#
def initialize(*)
super
@fsevent = FSEvent.new
end
def worker
@fsevent
end
# Start the listener.
#
def start
super
worker.run
end
# Stop the listener.
#
def stop
super
worker.stop
end
# Check if the listener is usable on the current OS.
#
# @return [Boolean] whether usable or not
#
def self.usable?
require 'rb-fsevent'
if !defined?(FSEvent::VERSION) || (defined?(Gem::Version) &&
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
else
true
end
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
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)
worker.watch(directory) do |modified_dirs|
files = modified_files(modified_dirs)

View File

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

View File

@ -1,24 +1,46 @@
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
# Initialize the Listener.
#
def initialize(*)
super
@latency = 1.5
end
# Start the listener.
#
def start
@stop = false
super
watch_change
end
# Stop the listener.
#
def stop
super
@stop = true
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
until @stop
start = Time.now.to_f
@ -29,9 +51,5 @@ module Guard
end
end
def watch(directory)
@existing = all_files
end
end
end

View File

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

View File

@ -3,13 +3,29 @@ require 'pathname'
require 'guard/ui'
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
# Application name as shown in the specific notification settings
APPLICATION_NAME = "Guard"
# Turn notifications off.
#
def self.turn_off
ENV["GUARD_NOTIFY"] = 'false'
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
ENV["GUARD_NOTIFY"] = 'true'
case RbConfig::CONFIG['target_os']
@ -22,6 +38,15 @@ module Guard
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 = {})
if enabled?
image = options.delete(:image) || :success
@ -38,12 +63,23 @@ module Guard
end
end
# Test if the notifications are enabled and available.
#
# @return [Boolean] whether the notifications are available
#
def self.enabled?
ENV["GUARD_NOTIFY"] == 'true'
end
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)
require_growl # need for guard-rspec formatter that is called out of guard scope
@ -61,18 +97,43 @@ module Guard
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)
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 }
Libnotify.show default_options.merge(options) if enabled?
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)
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 }
Notifu.show default_options.merge(options) if enabled?
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)
images_path = Pathname.new(File.dirname(__FILE__)).join('../../images')
case image
@ -88,6 +149,11 @@ module Guard
end
end
# The notification level type for the given image.
#
# @param [Symbol] image the image
# @return [Symbol] the level
#
def self.image_level(image)
case image
when :failed
@ -101,6 +167,9 @@ module Guard
end
end
# Try to safely load growl and turns notifications
# off on load failure.
#
def self.require_growl
begin
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"
end
# Try to safely load libnotify and turns notifications
# off on load failure.
#
def self.require_libnotify
require 'libnotify'
rescue LoadError
@ -126,11 +198,15 @@ module Guard
UI.info "Please install libnotify gem for Linux notification support and add it to your Gemfile"
end
# Try to safely load rb-notifu and turns notifications
# off on load failure.
#
def self.require_rbnotifu
require 'rb-notifu'
rescue LoadError
turn_off
UI.info "Please install rb-notifu gem for Windows notification support and add it to your Gemfile"
end
end
end

View File

@ -1,88 +1,92 @@
module Guard
# The UI class helps to format messages for the user.
#
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
color_enabled = nil
def info(message, options = {})
unless ENV["GUARD_ENV"] == "test"
# Show an info message.
#
# @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]
puts color(message) if message != ''
end
end
def error(message, options={})
unless ENV["GUARD_ENV"] == "test"
# Show a red error message that is prefixed with ERROR.
#
# @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]
puts color('ERROR: ', :red) + message
end
end
def deprecation(message, options = {})
unless ENV["GUARD_ENV"] == "test"
# Show a red deprecation message that is prefixed with DEPRECATION.
#
# @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]
puts color('DEPRECATION: ', :red) + message
end
end
def debug(message, options={})
unless ENV["GUARD_ENV"] == "test"
# Show a debug message that is prefixed with DEBUG and a timestamp.
#
# @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]
puts color("DEBUG (#{Time.now.strftime('%T')}): ", :yellow) + message if ::Guard.options && ::Guard.options[:debug]
end
end
# Reset a line.
#
def reset_line
print(color_enabled? ? "\r\e[0m" : "\r\n")
end
# Clear the output.
#
def clear
system("clear;")
system('clear;')
end
private
private
# Reset a color sequence.
#
# @deprecated
# @param [String] text the text
#
def reset_color(text)
deprecation('UI.reset_color(text) is deprecated, please use color(text, "") instead.')
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
deprecation('UI.reset_color(text) is deprecated, please use color(text, ' ') instead.')
color(text, '')
end
# Checks if color output can be enabled.
#
# @return [Boolean] whether color is enabled or not
#
def color_enabled?
if @color_enabled.nil?
if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
@ -102,9 +106,87 @@ module Guard
@color_enabled = true
end
end
@color_enabled
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
# 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

View File

@ -1,3 +1,6 @@
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

View File

@ -1,7 +1,18 @@
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
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)
@pattern, @action = pattern, action
@@warning_printed ||= false
@ -10,14 +21,25 @@ module Guard
if @pattern.is_a?(String) && @pattern =~ /(^(\^))|(>?(\\\.)|(\.\*))|(\(.*\))|(\[.*\])|(\$$)/
unless @@warning_printed
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
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)
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)
guard.watchers.inject([]) do |paths, watcher|
files.each do |file|
@ -30,10 +52,17 @@ module Guard
end
end
end
paths.flatten.map { |p| p.to_s }
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)
guards.any? do |guard|
guard.watchers.any? do |watcher|
@ -42,6 +71,11 @@ module Guard
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)
if @pattern.is_a?(Regexp)
file.match(@pattern)
@ -50,15 +84,25 @@ module Guard
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)
files.any? { |file| "#{Dir.pwd}/#{file}" == Dsl.guardfile_path }
files.any? { |file| "#{ Dir.pwd }/#{ file }" == Dsl.guardfile_path }
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)
begin
@action.arity > 0 ? @action.call(matches) : @action.call
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

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
\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\.
.
.P

View File

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

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>
</div>
<ol class='man-decor man-head man head'>
@ -130,7 +129,6 @@
<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;
</code></pre>
<p> </p>
@ -151,125 +149,15 @@
</p>
<p><var>!DOCTYPE html</var>
<p>&lt;p<var>!DOCTYPE html</var>
<html>
<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)" />
<title>guard</title>
<style type="text/css" media="all">
/<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">
<style media="all" type="text/css">
/<em> style: man </em>/
body#manpage {margin:0}
.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
.\" 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>

View File

@ -235,17 +235,17 @@ describe Guard::Dsl do
disable_user_config
it "evaluates only the specified string group" do
::Guard.should_receive(:add_guard).with(:pow, [], [], { :group => :default })
::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :w })
::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => ['w'])
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
::Guard.groups.should eql [{ :name => :default, :options => {} }, { :name => :w, :options => {} }]
end
it "evaluates only the specified symbol group" do
::Guard.should_receive(:add_guard).with(:pow, [], [], { :group => :default })
::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :w })
::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
@ -253,10 +253,10 @@ describe Guard::Dsl do
end
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(:rspec, [], [], { :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('pow', [], [], { :group => :default })
::Guard.should_receive(:add_guard).with('rspec', [], [], { :group => :x })
::Guard.should_receive(:add_guard).with('ronn', [], [], { :group => :x })
::Guard.should_receive(:add_guard).with('less', [], [], { :group => :y })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:x, :y])
@ -264,8 +264,8 @@ describe Guard::Dsl do
end
it "evaluates always guard outside any group (even when a group is given)" do
::Guard.should_receive(:add_guard).with(:pow, [], [], { :group => :default })
::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :w })
::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
@ -273,11 +273,11 @@ describe Guard::Dsl do
end
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(:test, [], [], { :group => :w })
::Guard.should_receive(:add_guard).with(:rspec, [], [], { :group => :x })
::Guard.should_receive(:add_guard).with(:ronn, [], [], { :group => :x })
::Guard.should_receive(:add_guard).with(:less, [], [], { :group => :y })
::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w })
::Guard.should_receive(:add_guard).with('rspec', [], [], { :group => :x })
::Guard.should_receive(:add_guard).with('ronn', [], [], { :group => :x })
::Guard.should_receive(:add_guard).with('less', [], [], { :group => :y })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string)
@ -290,31 +290,31 @@ describe Guard::Dsl do
disable_user_config
it "loads a guard specified as a quoted string from the DSL" do
::Guard.should_receive(:add_guard).with(:test, [], [], { :group => :default })
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default })
subject.evaluate_guardfile(:guardfile_contents => "guard 'test'")
end
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"')
end
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")
end
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)")
end
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'")
end
@ -324,7 +324,7 @@ describe Guard::Dsl do
disable_user_config
it "should receive watchers when specified" do
::Guard.should_receive(:add_guard).with(:dummy, anything, anything, { :group => :default }) do |name, watchers, callbacks, options|
::Guard.should_receive(:add_guard).with('dummy', anything, anything, { :group => :default }) do |name, watchers, callbacks, options|
watchers.size.should == 2
watchers[0].pattern.should == 'a'
watchers[0].action.call.should == proc { 'b' }.call
@ -347,7 +347,7 @@ describe Guard::Dsl do
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[0][:events].should == :start_end
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
group 'w' do
guard 'test'
group :w do
guard :test
end
group :x, :halt_on_fail => true do
guard 'rspec'
guard :rspec
guard :ronn
end
group 'y' do
guard 'less'
group :y do
guard :less
end
"
end

View File

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

View File

@ -172,7 +172,7 @@ describe Guard::Listener do
describe "#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
it "can be added to via :ignore_paths option" do