Compare commits

..

3 Commits

55 changed files with 1288 additions and 6273 deletions

9
.gitignore vendored
View File

@ -1,13 +1,6 @@
pkg/* pkg/*
doc/*
*.gem *.gem
*.rbc
.*.swp
*.bak
.bundle .bundle
.yardoc
.rbx
.rvmrc
Gemfile.lock Gemfile.lock
## MAC OS ## MAC OS
@ -16,4 +9,4 @@ Gemfile.lock
.com.apple.timemachine.supported .com.apple.timemachine.supported
.fseventsd .fseventsd
Desktop DB Desktop DB
Desktop DF Desktop DF

View File

@ -1,19 +0,0 @@
rvm:
- 1.8.7
- 1.9.2
- ree
- jruby
- rbx
branches:
only:
- master
- guard_dependencies
env:
- GUARD_SLEEP=1
notifications:
recipients:
- thibaud@thibaud.me
- rymai@rymai.me
- michi@netzpiraten.ch
- yann.lugrin@sans-savoir.net
irc: "irc.freenode.org#guard"

View File

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

View File

@ -1,347 +0,0 @@
## Master
### Improvements
- Add cli option (-i / --no-interactions) to turn off Guard terminal interactions. ([@thibaudgg][])
- Add support for Growl Notification Transport Protocol. ([@netzpirat][])
- [#157](https://github.com/guard/guard/pull/157): Allow any return from the Guard watchers. ([@earlonrails][])
- [#156](https://github.com/guard/guard/pull/156): Log error and diagnostic messages to STDERR. ([@sunaku][])
- [#152](https://github.com/guard/guard/pull/152): Growl Notify API update for a graceful fail. ([@scottdavis][])
### Bug fix
- [#149](https://github.com/guard/guard/issues/160): Avoid `Guard is not missing constant ...` exceptions. (reported by [@earlonrails][], fixed by [@netzpirat][])
## 0.8.4 - October 3, 2011
### Bug fix
- [#149](https://github.com/guard/guard/issues/149) & [#150](https://github.com/guard/guard/pull/150): Fix issue where interator thread was continuing to capture input from stdin while a guard is being executed. (reported by [@hardipe][], fixed by [@f1sherman][])
## 0.8.3 - October 1, 2011
### Bug fix
- [#145](https://github.com/guard/guard/pull/145): Fix over-utilization of CPU in Interactor. ([@johnbintz][])
### Improvements
- [#146](https://github.com/guard/guard/pull/146): Use a mutex instead of a lock for more efficient/simple locking. ([@f1sherman][])
- Make Guard implementation of `:task_has_failed` simple. ([@netzpirat][])
## 0.8.2 - September 30, 2011
### Bug fix
- Fixed guard stop to prevent run_guard_task(:stop) to be skipped [guard-spork issue #28](https://github.com/guard/guard-spork/issues/28). ([@thibaudgg][])
### Improvement
- Update docs regarding :task_has_failed. ([@netzpirat][])
## 0.8.1 - September 29, 2011
### Bug fix
- [#144](https://github.com/guard/guard/pull/144): Fix `guard init`. (reported by [@fabioyamate][], fixed by [@rymai][])
## 0.8.0 - September 28, 2011
### Bug fixes
- [#137](https://github.com/guard/guard/pull/137): Fix interacting with tools like ruby-debug. ([@hron][] & [@netzpirat][])
- [#138](https://github.com/guard/guard/pull/138): Fixed comments in example scaffold to reference interactions. ([@rmm5t][] & [@netzpirat][])
### New feature
- [#136](https://github.com/guard/guard/pull/136): New CLI `:watch_all_modifications`/`-A` option to watch for deleted and moved files too. ([@limeyd][] & [@netzpirat][])
- [#97](https://github.com/guard/guard/issues/97): Guard dependencies. Task execution can now be halted if a Guard throws `:task_has_failed` and `Guard::Dsl#group` options include `:halt_on_fail => true`. ([@rymai][])
- [#121](https://github.com/guard/guard/issues/121): `Guard.guards` and `Guard.groups` are now smart accessors. Filters can be passed to find a specific Guard/group or several Guards/groups that match (see YARDoc). ([@rymai][] & [@ches][])
- New `Guard::Group` class to store groups defined in Guardfile (with `Guard::Dsl#group`). ([@rymai][])
### Improvements
- Specs refactoring. ([@netzpirat][])
- Full YARD documentation. ([@netzpirat][] & a little of [@rymai][])
## 0.7.0 - September 14, 2011
## 0.7.0.rc1 - September 5, 2011
### Major Changes
- Posix Signals handlers (`Ctrl-C`, `Ctrl-\` and `Ctrl-Z`) are no more supported and replaced by `$stdin.gets`. Please refer to the "Interactions" section in the README for more information. ([@thibaudgg][])
- JRuby & Rubinius support (beta). ([@thibaudgg][] & [@netzpirat][])
### New features
- [#42](https://github.com/guard/guard/pull/42): New DSL method: `callback` allows you to execute arbitrary code before or after any of the `start`, `stop`, `reload`, `run_all` and `run_on_change` guards' method. New [Wiki page](https://github.com/guard/guard/wiki/Hooks-and-callbacks) for documenting it. ([@monocle][] & [@rymai][])
- Ability to 'pause' files modification listening. Please refer to the "Interactions" section in the README for more information. ([@thibaudgg][])
### Improvement
- Remove the need to scan the whole directory after guard's `run_on_change` method. ([@thibaudgg][])
## 0.6.3 - September 1, 2011
### New features
- [#130](https://github.com/guard/guard/pull/130): Adds `ignore_paths` method to DSL. ([@ianwhite][])
- [#128](https://github.com/guard/guard/pull/128): Users can add additional settings to `~/.guard.rb` that augment the existing Guardfile. ([@tpope][])
## 0.6.2 - August 17, 2011
### Bug fixes
- Re-add the possibility to use the `growl` gem since the `growl_notify` gem this is currently known to not work in conjunction with Spork. ([@netzpirat][])
- Ensure that scoped groups and group name are symbolized before checking for inclusion. ([@rymai][])
### New features
- Groups are now stored in a `@groups` variable (will be used for future features). ([@rymai][])
- Guards will now receive their group in the options hash at initialization (will be used for future features). ([@rymai][])
### Improvement
- Explain the growl/growl_notify differences in the README. ([@netzpirat][])
## 0.6.1 - August 15, 2011
### Bug fixes
- [#120](https://github.com/guard/guard/pull/120): remove `guardfile_contents` when re-evaluating so that the Guardfile gets reloaded correctly. ([@mordaroso][])
- [#119](https://github.com/guard/guard/pull/119): `Dsl.evaluate_guardfile` uses all groups if none specified. ([@ches][])
## 0.6.0 - August 13, 2011
### Bug fixes
- Pull request [#107](https://github.com/guard/guard/pull/107): Small spelling fix. ([@dnagir][])
- `Dir.glob` now ignores files that don't need to be watched. ([@rymai][])
### New feature
- Pull request [#112](https://github.com/guard/guard/pull/112): Add `list` command to CLI. ([@docwhat][])
### Improvements
- [#99](https://github.com/guard/guard/pull/99): [OS X] Switch from growl gem to growl_notify gem. ([@johnbintz][])
- [#115](https://github.com/guard/guard/pull/115): [Linux] Add `:transient => true` to default libnotify options. ([@zonque][])
- [#95](https://github.com/guard/guard/pull/95): Output system commands and options to be executed when in debug mode. ([@uk-ar][] and [@netzpirat][])
- `Guard::Dsl.revaluate_guardfile` has been renamed to `Guard::Dsl.reevaluate_guardfile`. ([@rymai][])
- New CLI options: ([@nestegg][])
- `watchdir`/`-w` to specify the directory in which Guard should watch for changes,
- `guardfile`/`-G` to specify an alternate location for the Guardfile to use.
- [#90](https://github.com/guard/guard/pull/90): Refactoring of color handling in the `Guard::UI`. ([@stereobooster][])
## 0.5.1 - July 2, 2011
### Bug fix
- Fixed `guard show` command. ([@bronson][] & [@thibaudgg][])
## 0.5.0 - July 2, 2011
### New features
- Guard::Ego is now part of Guard, so Guardfile is automagically re-evaluated when modified. ([@thibaudgg][])
- [#91](https://github.com/guard/guard/pull/91): Show Guards in Guardfile with the `guard -T`. ([@johnbintz][])
### Improvements
- [#98](https://github.com/guard/guard/issues/98): Multiple calls per watch event on linux with rb-inotify. ([@jeffutter][] & [@netzpirat][])
- [#94](https://github.com/guard/guard/pull/94): Show backtrace in terminal when a problem with a watch action occurs. ([@capotej][])
- [#88](https://github.com/guard/guard/pull/88): Write exception trace in the terminal when a supervised task fail. ([@mcmire][])
- Color in red the "ERROR:" flag when using `UI.error`. ([@rymai][])
- [#79](https://github.com/guard/guard/issues/79) and Pull request [#82](https://github.com/guard/guard/pull/82): Improve INotify support on Linux. ([@Gazer][] & [@yannlugrin][])
- [#12](https://github.com/guard/guard/issues/12) and Pull request [#86](https://github.com/guard/guard/pull/86): Eventually exits with SystemStackError. ([@stereobooster][])
- [#84](https://github.com/guard/guard/pull/84): Use RbConfig instead of obsolete and deprecated Config. ([@etehtsea][])
- [#80](https://github.com/guard/guard/pull/80): Watching dotfile (hidden files under unix). (reported by [@chrisberkhout][], fixed by [@yannlugrin][])
- Clear the terminal on start when the `:clear` option is given. ([@rymai][])
- Rename home directory Guardfile to `.Guardfile`. ([@tpope][])
## 0.4.2 - June 7, 2011
### Bug fixes
- Fixed Guard::Version in ruby 1.8.7 ([@thibaudgg][])
- Fix ([@mislav][]) link in CHANGELOG (Note: this is a recursive CHANGELOG item). ([@fnichol][])
## 0.4.1 - June 7, 2011
### Improvements
- [#77](https://github.com/guard/guard/pull/77): Refactor `get_guard_class` to first try the constant and fallback to require + various tweaks. ([@mislav][])
- Notifier improvement, don't use system notification library if could not be required. ([@yannlugrin][])
## 0.4.0 - June 5, 2011
### Bug fix
- In Ruby < 1.9, `Symbol#downcase` doesn't exist! ([@rymai][])
### New features
- [#73](https://github.com/guard/guard/pull/73): Allow DSL's `group` method to accept a Symbol as group name. ([@johnbintz][])
- [#51](https://github.com/guard/guard/pull/51): Allow options (like `:priority`) to be passed through to the Notifier. ([@indirect][] & [@netzpirat][])
### Improvement
- [#74](https://github.com/guard/guard/pull/74): Added link definitions to make the CHANGELOG more DRY! That's for sure now, we have the cleanest CHANGELOG ever! (even the link definitions are sorted alphabetically!) ([@pcreux][])
## 0.4.0.rc - May 28, 2011
### Bug fixes
- [#69](https://github.com/guard/guard/pull/69): Fixed typo in README: `Ctr-/` => `Ctr-\`. ([@tinogomes][])
- [#66](https://github.com/guard/guard/pull/66): Support for dashes in guard names. ([@johnbintz][])
- Require `guard/ui` because `Guard::Notifier` can be required without full Guard. ([@yannlugrin][])
- Handled quick file (<1s) modification. Avoid to catch modified files without content modification (sha1 checksum). ([@thibaudgg][] & [@netzpirat][])
- Fixed `Guard::Notifier` (when growl/libnotify not present). ([@thibaudgg][])
- Fixed Rubygems deprecation messages. ([@thibaudgg][])
### New features
- [#67](https://github.com/guard/guard/pull/67): Allow Guardfile in `$HOME` folder. ([@hashrocketeer][])
- [#64](https://github.com/guard/guard/pull/64): Windows notifications support. ([@stereobooster][])
- [#63](https://github.com/guard/guard/pull/63): Refactor listeners to work as a library. ([@niklas][])
- Use `ENV["GUARD_NOTIFY"]` to disable notifications. ([@thibaudgg][])
- Cleaning up all specs. ([@netzpirat][])
- [#60](https://github.com/guard/guard/pull/60): Added Windows support. ([@stereobooster][])
- [#58](https://github.com/guard/guard/pull/58): Extract code from signal handlers into methods. ([@nicksieger][])
- [#55](https://github.com/guard/guard/pull/55): It is now possible to pass `:guardfile` (a Guardfile path) or `:guardfile_contents` (the content of a Guardfile) to `Guard::Dsl.evaluate_guardfile`. Hence this allows the use of `Guard::Dsl.evaluate_guardfile` in a programmatic manner. ([@anithri][], improved by [@rymai][])
## 0.3.4 - April 24, 2011
### Bug fix
- [#41](https://github.com/guard/guard/issues/41): Removed useless Bundler requirement. ([@thibaudgg][])
### New features
- Changed CHANGELOG from RDOC to Markdown and cleaned it! Let's celebrate! ([@rymai][])
- Changed README from RDOC to Markdown! Let's celebrate! ([@thibaudgg][])
- [#48](https://github.com/guard/guard/issues/48): Adding support for inline Guard classes rather than requiring a gem. ([@jrsacks][])
## 0.3.3 - April 18, 2011
### Bug fix
- Fixed `new_modified_files` rerun conditions on `Guard.run_on_change_for_all_guards`. ([@thibaudgg][])
## 0.3.2 - April 17, 2011
### Bug fixe
- [#43](https://github.com/guard/guard/pull/43): Fixed `guard init` command. ([@brainopia][])
## 0.3.1 - April 14, 2011
### Bug fixes
- Return unique filenames from Linux listener. (Marian Schubert)
- `Guard.get_guard_class` return wrong class when loaded nested class. ([@koshigoe][])
- [#35](https://github.com/guard/guard/issues/35): Fixed open-gem/gem_open dependency problem by using `gem which` to locate guards gem path. (reported by [@thierryhenrio][], fixed by [@thibaudgg][])
- [#38](https://github.com/guard/guard/issues/38) & Pull request [#39](https://github.com/guard/guard/issues/39): Fixed an invalid ANSI escape code in `Guard::UI.reset_line`. ([@gix][])
### New feature
- [#28](https://github.com/guard/guard/issues/28): New `-n` command line option to disable notifications (Growl / Libnotify). ([@thibaudgg][])
## 0.3.0 - January 19, 2011
### Bug fix
- Avoid launching `run_on_change` guards method when no files matched. `--clear` guard argument is now usable. ([@thibaudgg][])
### New features
- The whole directory is now watched during `run_on_change` to detect new files modifications. ([@thibaudgg][])
- [#26](https://github.com/guard/guard/pull/26): New DSL method: `group` allows you to group several guards. New CLI option: `--group group_name` to specify certain groups of guards to start. ([@netzpirat][])
- `watch` patterns are now more strict: strings are matched with `String#==`, `Regexp` are matched with `Regexp#match`. ([@rymai][])
- A deprecation warning is displayed if your `Guardfile` contains `String` that look like `Regexp` (bad!). ([@rymai][])
- It's now possible to return an `Enumerable` in the `watch` optional blocks in the `Guardfile`. ([@rymai][])
### New specs
- `Guard::Watcher`. ([@rymai][])
- [#13](https://github.com/guard/guard/pull/13): `Guard::Dsl`. ([@oliamb][])
## 0.2.2 - October 25, 2010
### Bug fix
- [#5](https://github.com/guard/guard/issues/5): avoid creating new copy of `fsevent_watch` every time a file is changed. (reported by [@stouset][], fixed by [@thibaudgg][])
## 0.2.1 - October 24, 2010
### Bug fixes
- [#7](https://github.com/guard/guard/pull/7): Fixes for Linux support. ([@yannlugrin][])
- [#6](https://github.com/guard/guard/pull/6): Locate guard now chomp newline in result path. ([@yannlugrin][])
## 0.2.0 - October 21, 2010
### Bug fixes
- [#3](https://github.com/guard/guard/issues/3): `guard init <guard-name>` no more need `Gemfile` but `open_gem` is required now. (reported by [@wereHamster][], fixed by [@thibaudgg][])
- [#2](https://github.com/guard/guard/issues/2): 1.8.6 compatibility. (reported by [@veged][], fixed by [@thibaudgg][])
- Removes Growl & Libnotify dependencies. ([@thibaudgg][])
## 0.2.0.beta.1 - October 17, 2010
### New features
- Improved listeners support (`rb-fsevent` & `rb-inotify`). ([@thibaudgg][])
- Added polling listening fallback. ([@thibaudgg][])
[@anithri]: https://github.com/anithri
[@brainopia]: https://github.com/brainopia
[@bronson]: https://github.com/bronson
[@capotej]: https://github.com/capotej
[@ches]: https://github.com/ches
[@chrisberkhout]: https://github.com/chrisberkhout
[@dnagir]: https://github.com/dnagir
[@docwhat]: https://github.com/docwhat
[@earlonrails]: https://github.com/earlonrails
[@etehtsea]: https://github.com/etehtsea
[@f1sherman]: https://github.com/f1sherman
[@fabioyamate]: https://github.com/fabioyamate
[@fnichol]: https://github.com/fnichol
[@Gazer]: https://github.com/Gazer
[@gix]: https://github.com/gix
[@hron]: https://github.com/hron
[@hardipe]: https://github.com/hardipe
[@hashrocketeer]: https://github.com/hashrocketeer
[@ianwhite]: https://github.com/ianwhite
[@indirect]: https://github.com/indirect
[@jeffutter]: https://github.com/jeffutter
[@johnbintz]: https://github.com/johnbintz
[@jrsacks]: https://github.com/jrsacks
[@koshigoe]: https://github.com/koshigoe
[@limeyd]: https://github.com/limeyd
[@mcmire]: https://github.com/mcmire
[@mislav]: https://github.com/mislav
[@monocle]: https://github.com/monocle
[@mordaroso]: https://github.com/mordaroso
[@nestegg]: https://github.com/nestegg
[@netzpirat]: https://github.com/netzpirat
[@nicksieger]: https://github.com/nicksieger
[@niklas]: https://github.com/niklas
[@oliamb]: https://github.com/oliamb
[@pcreux]: https://github.com/pcreux
[@rmm5t]: https://github.com/rmm5t
[@rymai]: https://github.com/rymai
[@scottdavis]: https://github.com/scottdavis
[@stereobooster]: https://github.com/stereobooster
[@stouset]: https://github.com/stouset
[@sunaku]: https://github.com/sunaku
[@thibaudgg]: https://github.com/thibaudgg
[@thierryhenrio]: https://github.com/thierryhenrio
[@tinogomes]: https://github.com/tinogomes
[@tpope]: https://github.com/tpope
[@uk-ar]: https://github.com/uk-ar
[@veged]: https://github.com/veged
[@wereHamster]: https://github.com/wereHamster
[@yannlugrin]: https://github.com/yannlugrin
[@zonque]: https://github.com/zonque

26
CHANGELOG.rdoc Normal file
View File

@ -0,0 +1,26 @@
== 0.2.2 (Oct 25, 2010)
Bugs fixes:
- Avoid creating new copy of fsevent_watch every time a file is changed. (issue #5)
== 0.2.1 (Oct 24, 2010)
Bugs fixes:
- Fixes for Linux support
== 0.2.0 (Oct 21, 2010)
Bugs fixes:
- Fixes for 1.8.6 compatibility (issue #2)
- guard init <guard-name> no more need Gemfile presence but open_gem is required now (issue #3)
- Removes growl & libnotify dependencies
== 0.2.0.beta.1 (Oct 17, 2010)
Features:
- Improved listeners support (rb-fsevent & rb-inotify)
- Added polling listening fallback

25
Gemfile
View File

@ -1,23 +1,14 @@
source :rubygems source "http://rubygems.org"
gemspec gemspec
gem 'rake'
group :guard do
gem 'guard-ronn'
end
require 'rbconfig' require 'rbconfig'
if RbConfig::CONFIG['target_os'] =~ /darwin/i if Config::CONFIG['target_os'] =~ /darwin/i
gem 'rb-fsevent', '>= 0.4.0', :require => false gem 'rb-fsevent', '>= 0.3.5'
gem 'growl', '~> 1.0.3', :require => false gem 'growl', '~> 1.0.3'
elsif RbConfig::CONFIG['target_os'] =~ /linux/i end
gem 'rb-inotify', '>= 0.8.5', :require => false if Config::CONFIG['target_os'] =~ /linux/i
gem 'libnotify', '~> 0.1.3', :require => false gem 'rb-inotify', '>= 0.5.1'
elsif RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i gem 'libnotify', '~> 0.1.3'
gem 'win32console', :require => false
gem 'rb-fchange', '>= 0.0.2', :require => false
gem 'rb-notifu', '>= 0.0.4', :require => false
end end

View File

@ -1,28 +1,5 @@
group :specs do guard 'rspec', :version => 2 do
guard :rspec, :all_on_start => false, :all_after_pass => false, :cli => '--fail-fast --format doc' do watch('^spec/(.*)_spec.rb')
watch(%r{^spec/.+_spec\.rb$}) watch('^lib/(.*).rb') { |m| "spec/#{m[1]}_spec.rb" }
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } watch('^spec/spec_helper.rb') { "spec" }
watch('spec/support/listener_helper.rb') { Dir.glob("spec/guard/listeners/*") } end
watch('spec/spec_helper.rb') { "spec" }
end
end
group :docs do
guard :ronn do
watch(%r{^man/.+\.ronn?$})
end
end
# require 'guard/guard'
#
# module ::Guard
# class Breaking < ::Guard::Guard
# def run_all
# raise "Fool !"
# end
# end
# end
#
# group "exceptional" do
# guard :breaking
# end

View File

@ -1,4 +1,4 @@
Copyright (c) 2011 Thibaud Guillaume-Gentil Copyright (c) 2010 Thibaud Guillaume-Gentil
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the

459
README.md
View File

@ -1,459 +0,0 @@
Guard [![Build Status](https://secure.travis-ci.org/guard/guard.png)](http://travis-ci.org/guard/guard)
=====
Guard is a command line tool that easily handle events on files modifications.
If you have any questions please join us on our [Google group](http://groups.google.com/group/guard-dev) or on `#guard` (irc.freenode.net).
Features
--------
* [FSEvent](http://en.wikipedia.org/wiki/FSEvents) support on Mac OS X 10.5+ (without RubyCocoa!, [rb-fsevent gem, >= 0.3.5](https://rubygems.org/gems/rb-fsevent) required).
* [Inotify](http://en.wikipedia.org/wiki/Inotify) support on Linux ([rb-inotify gem, >= 0.5.1](https://rubygems.org/gems/rb-inotify) required).
* [Directory Change Notification](http://msdn.microsoft.com/en-us/library/aa365261\(VS.85\).aspx) support on Windows ([rb-fchange, >= 0.0.2](https://rubygems.org/gems/rb-fchange) required).
* Polling on the other operating systems (help us to support more OS).
* Automatic & Super fast (when polling is not used) files modifications detection (even new files are detected).
* Visual notifications on Mac OSX ([Growl](http://growl.info)), Linux ([Libnotify](http://developer.gnome.org/libnotify)) and Windows ([Notifu](http://www.paralint.com/projects/notifu)).
* Tested against Ruby 1.8.7, 1.9.2, REE and the latest versions of JRuby & Rubinius.
Screencast
----------
Ryan Bates made a Railscast on Guard, you can view it here: [http://railscasts.com/episodes/264-guard](http://railscasts.com/episodes/264-guard)
Install
-------
Install the gem:
$ gem install guard
Or add it to your Gemfile (inside the `development` group):
gem 'guard'
and install it via Bundler:
$ bundle install
Generate an empty Guardfile with:
$ guard init
You may optionally place a .Guardfile in your home directory to use it across multiple projects.
Also note that if a `.guard.rb` is found in your home directory, it will be appended to the Guardfile.
Add the guards you need to your Guardfile (see the existing guards below).
Now, be sure to read the particular instructions for your operating system: [Mac OS X](#mac) | [Linux](#linux) | [Windows](#win)
<a name="mac" />
### On Mac OS X
Install the rb-fsevent gem for [FSEvent](http://en.wikipedia.org/wiki/FSEvents) support:
$ gem install rb-fsevent
You have three possibilities for getting Growl support:
Use the [growl_notify gem](https://rubygems.org/gems/growl_notify):
$ gem install growl_notify
The `growl_notify` gem is compatible with Growl >= 1.3 and uses AppleScript to send Growl notifications.
The gem needs a native C extension to make use of AppleScript and does not run on JRuby and MacRuby.
Use the [ruby_gntp gem](https://github.com/snaka/ruby_gntp):
$ gem install ruby_gntp
The `ruby_gntp` gem is compatible with Growl >= 1.3 and uses the Growl Notification Transport Protocol to send Growl
notifications. Guard supports multiple notification channels for customizing each notification type, but it's limited
to the local host currently.
Use the [growl gem](https://rubygems.org/gems/growl):
$ gem install growl
The `growl` gem is compatible with all versions of Growl and uses a command line tool [growlnotify](http://growl.info/extras.php#growlnotify)
that must be separately downloaded and installed. You can also install it with HomeBrew:
$ brew install growlnotify
Finally you have to add your Growl library of choice to your Gemfile:
gem 'rb-fsevent'
gem 'growl_notify' # or gem 'ruby_gntp' or gem 'growl'
Have a look at the [Guard Wiki](https://github.com/guard/guard/wiki/Which-Growl-library-should-I-use) for more information.
<a name="linux" />
### On Linux
Install the [rb-inotify gem](https://rubygems.org/gems/rb-inotify) for [inotify](http://en.wikipedia.org/wiki/Inotify) support:
$ gem install rb-inotify
Install the [libnotify gem](https://rubygems.org/gems/libnotify) if you want visual notification support:
$ gem install libnotify
And add them to your Gemfile:
gem 'rb-inotify'
gem 'libnotify'
<a name="win" />
### 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:
$ gem install rb-fchange
Install the [win32console gem](https://rubygems.org/gems/win32console) if you want colors in your terminal:
$ gem install win32console
Install the [rb-notifu gem](https://rubygems.org/gems/rb-notifu) if you want visual notification support:
$ gem install rb-notifu
And add them to your Gemfile:
gem 'rb-fchange'
gem 'rb-notifu'
gem 'win32console'
Usage
-----
Just launch Guard inside your Ruby / Rails project with:
$ guard [start]
or if you use Bundler, to run the Guard executable specific to your bundle:
$ 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.
Command line options
--------------------
### `-c`/`--clear` option
Shell can be cleared after each change:
$ guard --clear
$ guard -c # shortcut
### `-n`/`--notify` option
Notifications (growl/libnotify) can be disabled:
$ guard --notify false
$ guard -n f # shortcut
Notifications can also be disabled globally by setting a `GUARD_NOTIFY` environment variable to `false`
### `-g`/`--group` option
Only certain guards groups can be run (see the Guardfile DSL below for creating groups):
$ 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:
$ guard --debug
$ guard -d # shortcut
### `-w`/`--watchdir` option
Guard can watch in any directory (instead of the current directory):
$ 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:
$ guard --guardfile ~/.your_global_guardfile
$ guard -G ~/.your_global_guardfile # shortcut
### `-A`/`--watch-all-modifications` option
Guard can optionally watch all file modifications like moves or deletions with:
$ guard start -A
$ guard start --watch-all-modifications
### `-i`/`--no-interactions` option
Turn off completely any Guard terminal [interactions](#interactions) with:
$ guard start -A
$ guard start --watch-all-modifications
An exhaustive list of options is available with:
$ guard help [TASK]
<a name="interactions" />
Interactions
------------
**From version >= 0.7.0 Posix Signal handlers are no more used to interact with Guard. If you're using a version < 0.7, please refer to the [README in the v0.6 branch](https://github.com/guard/guard/blob/v0.6/README.md).**
When Guard do nothing you can interact with by entering a command + hitting enter:
* `stop|quit|exit|s|q|e + enter` - Calls each guard's `#stop` method, in the same order they are declared in the Guardfile, and then quits Guard itself.
* `reload|r|z + enter` - Calls each guard's `#reload` method, in the same order they are declared in the Guardfile.
* `pause|p + enter` - Toggle files modification listening. Useful when switching git branches.
* `just enter (no commands)` - Calls each guard's `#run_all` method, in the same order they are declared in the Guardfile.
Available Guards
----------------
A list of the available guards is present [in the wiki](https://github.com/guard/guard/wiki/List-of-available-Guards).
### Add a guard to your Guardfile
Add it to your Gemfile (inside the `development` group):
gem '<guard-name>'
You can list all guards installed on your system with:
$ guard list
Insert default guard's definition to your Guardfile by running this command:
$ guard init <guard-name>
You are good to go, or you can modify your guards' definition to suit your needs.
Guardfile DSL
-------------
The Guardfile DSL consists of the following methods:
* `#guard` - Allows you to add a guard with an optional hash of options.
* `#watch` - Allows you to define which files are supervised by a guard. An optional block can be added to overwrite the paths sent to the guard's `#run_on_change` method or to launch any arbitrary command.
* `#group` - Allows you to group several guards together. Groups to be run can be specified with the Guard DSL option `--group` (or `-g`). This comes in handy especially when you have a huge Guardfile and want to focus your development on a certain part. Guards that don't belong to a group are considered global and are always run.
* `#callback` - 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.
* `#ignore_paths` - Allows you to ignore top level directories altogether. This comes is handy when you have large amounts of non-source data in you project. By default .bundle, .git, log, tmp, and vendor are ignored. Currently it is only possible to ignore the immediate descendants of the watched directory.
Example:
ignore_paths 'foo', 'bar'
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` }
# 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
guard 'livereload' do
watch(%r{^app/.+\.(erb|haml)$})
end
end
Using a Guardfile without the `guard` binary
--------------------------------------------
The Guardfile DSL can also be used in a programmatic fashion by calling directly `Guard::Dsl.evaluate_guardfile`.
Available options are as follow:
* `:guardfile` - The path to a valid Guardfile.
* `:guardfile_contents` - A string representing the content of a valid Guardfile
Remember, without any options given, Guard will look for a Guardfile in your current directory and if it does not find one, it will look for it in your `$HOME` directory.
For instance, you could use it as follow:
gem 'guard'
require 'guard'
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
")
### 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`:
$ guard -T
(global):
shell
Group backend:
bundler
rspec: cli => "--color --format doc"
Group frontend:
coffeescript: output => "public/javascripts/compiled"
livereload
User config file
----------------
If a `.guard.rb` is found in your home directory, it will be appended to
the Guardfile. This can be used for tasks you want guard to handle but
other users probably don't. For example, indexing your source tree with
[Ctags](http://ctags.sourceforge.net):
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
`Guard::GuardName` (in `lib/guard/guard-name.rb`) must inherit from
[Guard::Guard](http://rubydoc.info/github/guard/guard/master/Guard/Guard) and should overwrite at least one of
the basic `Guard::Guard` task methods.
Here is an example scaffold for `lib/guard/guard-name.rb`:
require 'guard'
require 'guard/guard'
module Guard
class GuardName < Guard
# Initialize a Guard.
# @param [Array<Guard::Watcher>] watchers the Guard file watchers
# @param [Hash] options the custom Guard options
def initialize(watchers = [], options = {})
super
end
# Call once when Guard starts. Please override initialize method to init stuff.
# @raise [:task_has_failed] when start has failed
def start
end
# Called when `stop|quit|exit|s|q|e + enter` is pressed (when Guard quits).
# @raise [:task_has_failed] when stop has failed
def stop
end
# Called when `reload|r|z + enter` is pressed.
# This method should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/...
# @raise [:task_has_failed] when reload has failed
def reload
end
# Called when just `enter` is pressed
# This method should be principally used for long action like running all specs/tests/...
# @raise [:task_has_failed] when run_all has failed
def run_all
end
# Called on file(s) modifications that the Guard watches.
# @param [Array<String>] paths the changes files or paths
# @raise [:task_has_failed] when run_on_change has failed
def run_on_change(paths)
end
# Called on file(s) deletions that the Guard watches.
# @param [Array<String>] paths the deleted files or paths
# @raise [:task_has_failed] when run_on_change has failed
def run_on_deletion(paths)
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:
require 'guard/guard'
module ::Guard
class InlineGuard < ::Guard::Guard
def run_all
end
def run_on_change(paths)
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](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).
Pull requests are very welcome! Please try to follow these simple "rules", though:
- Please create a topic branch for every separate change you make;
- Make sure your patches are well tested;
- Update the README (if applicable);
- Update the CHANGELOG (maybe not for a typo but don't hesitate!);
- Please **do not change** the version number.
For questions please join us on our [Google group](http://groups.google.com/group/guard-dev) or on `#guard` (irc.freenode.net).
Author
------
[Thibaud Guillaume-Gentil](https://github.com/thibaudgg)
Contributors
------------
[https://github.com/guard/guard/contributors](https://github.com/guard/guard/contributors)

204
README.rdoc Normal file
View File

@ -0,0 +1,204 @@
= Guard
Guard is a command line tool to easly handle events on files modifications.
== Features
- {FSEvent}[http://en.wikipedia.org/wiki/FSEvents] support on Mac OS X 10.5+ (without RubyCocoa!, {rb-fsevent gem, >= 0.3.5}[https://rubygems.org/gems/rb-fsevent] required)
- {Inotify}[http://en.wikipedia.org/wiki/Inotify] support on Linux ({rb-inotify gem, >= 0.5.1}[https://rubygems.org/gems/rb-inotify] required)
- Polling for others (help us to support more systems)
- Super fast change detection (when polling not used)
- Automatic files modifications detection (even new files are detected)
- Growl notification ({growlnotify}[http://growl.info/documentation/growlnotify.php] & {growl gem}[https://rubygems.org/gems/growl] required)
- Libnotify notification ({libnotify gem}[https://rubygems.org/gems/libnotify] required)
- Tested on Ruby 1.8.6, 1.8.7 & 1.9.2
== Install
Install the gem:
gem install guard
Add it to your Gemfile (inside test group):
gem 'guard'
Generate an empty Guardfile with:
guard init
Add guard(s) you need (see available guards below)
=== On Mac OS X
Install rb-fsevent for {FSEvent}[http://en.wikipedia.org/wiki/FSEvents] support
gem install rb-fsevent
Install growl for Growl notification support
gem install growl
And add it to you Gemfile
gem 'growl'
=== On Linux
Install rb-inotify for {inotify}[http://en.wikipedia.org/wiki/Inotify] support
gem install rb-inotify
Install libnotify for libonity notification support
gem install libnotify
And add it to you Gemfile
gem 'libnotify'
== Usage
Just launch Guard inside your ruby/rails project with:
guard
Shell can be cleared after each change with:
guard -c
Options list is available with:
guard help [TASK]
Signal handlers are used to interact with Guard:
- Ctrl-C - Quit Guard (call stop guard(s) method before)
- Ctrl-\ - Call run_all guard(s) method
- Ctrl-Z - Call reload guard(s) method
== Available Guards
- {guard-bundler}[http://github.com/guard/guard-bundler] by {Yann Lugrin}[http://github.com/yannlugrin]
- {guard-coffeescript}[http://github.com/guard/guard-coffeescript] by {Michael Kessler}[http://github.com/netzpirat]
- {guard-compass}[http://github.com/guard/guard-compass] by {Olivier Amblet}[http://github.com/oliamb]
- {guard-cucumber}[http://github.com/guard/guard-cucumber] by {Michael Kessler}[http://github.com/netzpirat]
- {guard-livereload}[http://github.com/guard/guard-livereload] by {Thibaud Guillaume-Gentil}[http://github.com/thibaudgg]
- {guard-minitest}[http://github.com/guard/guard-minitest] by {Yann Lugrin}[http://github.com/yannlugrin]
- {guard-nanoc}[http://github.com/guard/guard-nanoc] by {Yann Lugrin}[http://github.com/yannlugrin]
- {guard-passenger}[http://github.com/guard/guard-passenger] by {Fabio Kuhn}[http://github.com/mordaroso]
- {guard-rspec}[http://github.com/guard/guard-rspec] by {Thibaud Guillaume-Gentil}[http://github.com/thibaudgg]
- {guard-sass}[http://github.com/guard/guard-sass] by {Joshua Hawxwell}[http://github.com/hawx]
- {guard-shell}[http://github.com/guard/guard-shell] by {Joshua Hawxwell}[http://github.com/hawx]
- {guard-test}[http://github.com/guard/guard-test] by {Rémy Coutable}[http://github.com/rymai]
guard ideas:
- guard-spork
- others ideas?
=== Add a guard to your Guardfile
Add it to your Gemfile (inside test group):
gem '<guard-name>'
Add guard definition to your Guardfile by running this command:
guard init <guard-name>
You are good to go!
== Create a guard
Create a new guard is very easy, just create a new gem with this basic structure:
lib/
guard/
guard-name/
templates/
Guardfile (needed for guard init <guard-name>)
guard-name.rb
lib/guard/guard-name.rb inherit from guard/guard and should overwrite at least one of the five guard methods. Example:
require 'guard'
require 'guard/guard'
module Guard
class GuardName < Guard
def initialize(watchers = [], options = {})
super
# init stuff here, thx!
end
# ================
# = Guard method =
# ================
# If one of those methods raise an exception, the Guard instance
# will be removed from the active guard.
# Call once when guard starts
# Please override initialize method to init stuff
def start
true
end
# Call with Ctrl-C signal (when Guard quit)
def stop
true
end
# Call with Ctrl-Z signal
# This method should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/...
def reload
true
end
# Call with Ctrl-/ signal
# This method should be principally used for long action like running all specs/tests/...
def run_all
true
end
# Call on file(s) modifications
def run_on_change(paths)
true
end
end
end
Looks at available guards code for more concrete example.
== Guardfile DSL
Guardfile DSL consists of just two simple methods: guard & watch. Example:
guard 'rspec', :version => 2 do
watch('^spec/(.*)_spec.rb')
watch('^lib/(.*)\.rb') { |m| "spec/lib/#{m[1]}_spec.rb" }
watch('^spec/spec_helper.rb') { "spec" }
watch('^spec/spec_helper.rb') { `say hello` }
end
- "guard" method allow to add a guard with an optional options hash
- "watch" method allow to define which files are supervised per this guard. A optional block can be added to overwrite path sending to run_on_change guard method or launch simple command.
== TODO
- Add more specs! Shame on me :)
== Development
- Source hosted at {GitHub}[http://github.com/guard/guard]
- Report issues/Questions/Feature requests on {GitHub Issues}[http://github.com/guard/guard/issues]
Pull requests are very welcome! Make sure your patches are well tested. Please create a topic branch for every separate change
you make.
== Authors
{Thibaud Guillaume-Gentil}[http://github.com/thibaudgg]

View File

@ -5,44 +5,17 @@ require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec) RSpec::Core::RakeTask.new(:spec)
task :default => :spec task :default => :spec
require 'rbconfig'
namespace(:spec) do namespace(:spec) do
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/i desc "Run all specs on multiple ruby versions (requires rvm)"
desc "Run all specs on multiple ruby versions (requires pik)" task(:portability) do
task(:portability) do %w[1.8.6 1.8.7 1.9.2].each do |version|
%w[187 192 161].each do |version| system <<-BASH
system "cmd /c echo -----------#{version}------------ & " + bash -c 'source ~/.rvm/scripts/rvm;
"pik use #{version} & " + rvm #{version};
"bundle install & " + echo "--------- version #{version} ----------\n";
"bundle exec rspec spec" bundle install;
end rake spec'
end BASH
else
desc "Run all specs on multiple ruby versions (requires rvm)"
task(:portability) do
travis_config_file = File.expand_path("../.travis.yml", __FILE__)
begin
travis_options ||= YAML::load_file(travis_config_file)
rescue => ex
puts "Travis config file '#{travis_config_file}' could not be found: #{ex.message}"
return
end
travis_options['rvm'].each do |version|
system <<-BASH
bash -c 'source ~/.rvm/scripts/rvm;
rvm #{version};
ruby_version_string_size=`ruby -v | wc -m`
echo;
for ((c=1; c<$ruby_version_string_size; c++)); do echo -n "="; done
echo;
echo "`ruby -v`";
for ((c=1; c<$ruby_version_string_size; c++)); do echo -n "="; done
echo;
RBXOPT="-Xrbc.db" bundle install;
RBXOPT="-Xrbc.db" bundle exec rspec spec -f doc 2>&1;'
BASH
end
end end
end end
end end

View File

@ -3,4 +3,4 @@
require 'guard' require 'guard'
require 'guard/cli' require 'guard/cli'
Guard::CLI.start Guard::CLI.start

View File

@ -1,5 +1,6 @@
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
Kernel.load File.expand_path('../lib/guard/version.rb', __FILE__) $:.push File.expand_path('../lib', __FILE__)
require 'guard/version'
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = 'guard' s.name = 'guard'
@ -7,22 +8,21 @@ Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY s.platform = Gem::Platform::RUBY
s.authors = ['Thibaud Guillaume-Gentil'] s.authors = ['Thibaud Guillaume-Gentil']
s.email = ['thibaud@thibaud.me'] s.email = ['thibaud@thibaud.me']
s.homepage = 'https://github.com/guard/guard' s.homepage = 'http://rubygems.org/gems/guard'
s.summary = 'Guard keeps an eye on your file modifications' s.summary = 'Guard keep an eye on your files modifications.'
s.description = 'Guard is a command line tool to easily handle events on file system modifications.' s.description = 'Guard is a command line tool to easily handle events on files modifications.'
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', '~> 1.0.3'
s.add_development_dependency 'rspec', '~> 2.0.1'
s.add_development_dependency 'bundler' s.add_development_dependency 'guard-rspec', '~> 0.1.4'
s.add_development_dependency 'rspec', '~> 2.6.0'
s.add_development_dependency 'guard-rspec', '~> 0.3.1' s.add_dependency 'thor', '~> 0.14.3'
s.add_development_dependency 'yard', '~> 0.7.2' s.add_dependency 'open_gem', '~> 1.4.2'
s.add_development_dependency 'kramdown', '~> 0.13.3'
s.files = Dir.glob('{bin,images,lib}/**/*') + %w[LICENSE README.rdoc]
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'
s.require_path = 'lib' s.require_path = 'lib'
end end

View File

@ -1,436 +1,101 @@
require 'thread' require 'bundler'
# 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'
autoload :Dsl, 'guard/dsl' autoload :Dsl, 'guard/dsl'
autoload :DslDescriber, 'guard/dsl_describer' autoload :Interactor, 'guard/interactor'
autoload :Group, 'guard/group' autoload :Listener, 'guard/listener'
autoload :Interactor, 'guard/interactor' autoload :Watcher, 'guard/watcher'
autoload :Listener, 'guard/listener' autoload :Notifier, 'guard/notifier'
autoload :Watcher, 'guard/watcher'
autoload :Notifier, 'guard/notifier'
autoload :Hook, 'guard/hook'
class << self class << self
attr_accessor :options, :interactor, :listener, :lock attr_accessor :options, :guards, :listener
# Creates the initial Guardfile template or add a Guard implementation # initialize this singleton
# Guardfile template to an existing Guardfile. def init(options = {})
# @options = options
# @see Guard::Guard.init @listener = Listener.init
# @guards = []
# @param [String] guard_name the name of the Guard to initialize return self
#
def initialize_template(guard_name = nil)
if guard_name
guard_class = ::Guard.get_guard_class(guard_name)
guard_class.init(guard_name)
else
if !File.exist?('Guardfile')
::Guard::UI.info "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
# Initialize the Guard singleton.
#
# @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
# @option options [Boolean] watch_all_modifications watches all file modifications if true
#
def setup(options = {})
@lock = Mutex.new
@options = options
@guards = []
@groups = [Group.new(:default)]
@interactor = Interactor.new unless @options[:no_interactions]
@listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd, options)
@options[:notify] && ENV['GUARD_NOTIFY'] != 'false' ? Notifier.turn_on : Notifier.turn_off
UI.clear if @options[:clear]
debug_command_execution if @options[:debug]
self
end
# Smart accessor for retrieving a specific guard or several guards at once.
#
# @param [String, Symbol] filter return the guard with the given name, or nil if not found
# @param [Regexp] filter returns all guards matching the Regexp, or [] if no guard found
# @param [Hash] filter returns all guards matching the given Hash.
# Example: `{ :name => 'rspec', :group => 'backend' }`, or [] if no guard found
# @param [NilClass] filter returns all guards
#
# @see Guard.groups
#
def guards(filter = nil)
case filter
when String, Symbol
@guards.find { |guard| guard.class.to_s.downcase.sub('guard::', '') == filter.to_s.downcase.gsub('-', '') }
when Regexp
@guards.find_all { |guard| guard.class.to_s.downcase.sub('guard::', '') =~ filter }
when Hash
filter.inject(@guards) do |matches, (k, v)|
if k.to_sym == :name
matches.find_all { |guard| guard.class.to_s.downcase.sub('guard::', '') == v.to_s.downcase.gsub('-', '') }
else
matches.find_all { |guard| guard.send(k).to_sym == v.to_sym }
end
end
else
@guards
end
end
# Smart accessor for retrieving a specific group or several groups at once.
#
# @param [NilClass] filter returns all groups
# @param [String, Symbol] filter return the group with the given name, or nil if not found
# @param [Regexp] filter returns all groups matching the Regexp, or [] if no group found
#
# @see Guard.guards
#
def groups(filter = nil)
case filter
when String, Symbol
@groups.find { |group| group.name == filter.to_sym }
when Regexp
@groups.find_all { |group| group.name.to_s =~ filter }
else
@groups
end
end
# Start Guard by evaluate the `Guardfile`, initialize the declared Guards
# and start the available file change listener.
#
# @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) init options
Dsl.evaluate_guardfile(options) Dsl.evaluate_guardfile
if guards.empty?
listener.on_change do |files| UI.error "No guards found in Guardfile, please add at least one."
Dsl.reevaluate_guardfile if Watcher.match_guardfile?(files)
listener.changed_files += files if Watcher.match_files?(guards, files)
end
UI.info "Guard is now watching at '#{ listener.directory }'"
run_guard_task(:start)
interactor.start if interactor
listener.start
end
# Stop Guard listening to file changes
#
def stop
UI.info 'Bye bye...', :reset => true
run_guard_task(:stop)
listener.stop
abort
end
# Reload all Guards currently enabled.
#
def reload
run do
run_guard_task(:reload)
end
end
# Trigger `run_all` on all Guards currently enabled.
#
def run_all
run do
run_guard_task(:run_all)
end
end
# Pause Guard listening to file changes.
#
def pause
if listener.paused?
UI.info 'Un-paused files modification listening', :reset => true
listener.clear_changed_files
listener.run
else else
UI.info 'Paused files modification listening', :reset => true Interactor.init_signal_traps
listener.pause
end listener.on_change do |files|
end run do
guards.each do |guard|
# Trigger `run_on_change` on all Guards currently enabled. paths = Watcher.match_files(guard, files)
# supervised_task(guard, :run_on_change, paths) unless paths.empty?
def run_on_change(paths)
run do
run_guard_task(:run_on_change, paths)
end
end
# Run a block where the listener and the interactor is
# blocked.
#
# @yield the block to run
#
def run
UI.clear if options[:clear]
lock.synchronize do
begin
interactor.stop_if_not_current if interactor
yield
rescue Interrupt
end
interactor.start if interactor
end
end
# Loop through all groups and run the given task for each Guard.
#
# Stop the task run for the all Guards within a group if one Guard
# throws `:task_has_failed`.
#
# @param [Symbol] task the task to run
# @param [Array<String>] files the list of files to pass to the task
#
def run_guard_task(task, files = nil)
groups.each do |group|
catch :task_has_failed do
guards(:group => group.name).each do |guard|
if task == :run_on_change
run_on_change_task(files, guard, task)
else
run_supervised_task(guard, task)
end end
end end
end end
end
end UI.info "Guard is now watching at '#{Dir.pwd}'"
guards.each do |guard|
# Run the `:run_on_change` task. When the option `:watch_all_modifications` is set, if supervised_task(guard, :start)
# the task is split to run changed paths on {Guard::Guard#run_on_change}, whereas %w[reload run_all].each do |m|
# deleted paths run on {Guard::Guard#run_on_deletion}. guard.send(m) if guard.respond_to?(:"#{m}_at_start?") && guard.send(:"#{m}_at_start?")
# end
# @param [Array<String>] files the list of files to pass to the task end
# @param [Guard::Guard] guard the guard to run
# @param [Symbol] task the task to run
# @raise [:task_has_failed] when task has failed
#
def run_on_change_task(files, guard, task)
paths = Watcher.match_files(guard, files)
changes = changed_paths(paths)
deletions = deleted_paths(paths)
unless changes.empty?
UI.debug "#{ guard.class.name }##{ task } with #{ changes.inspect }"
run_supervised_task(guard, task, changes)
end
unless deletions.empty?
UI.debug "#{ guard.class.name }#run_on_deletion with #{ deletions.inspect }"
run_supervised_task(guard, :run_on_deletion, deletions)
end
end
# Detects the paths that have changed.
#
# Deleted paths are prefixed by an exclamation point.
# @see Guard::Listener#modified_files
#
# @param [Array<String>] paths the watched paths
# @return [Array<String>] the changed paths
#
def changed_paths(paths)
paths.select { |f| !f.start_with?('!') }
end
# Detects the paths that have been deleted.
#
# Deleted paths are prefixed by an exclamation point.
# @see Guard::Listener#modified_files
#
# @param [Array<String>] paths the watched paths
# @return [Array<String>] the deleted paths
#
def deleted_paths(paths)
paths.select { |f| f.start_with?('!') }.map { |f| f.slice(1..-1) }
end
# Run a Guard task, but remove the Guard when his work leads to a system failure.
#
# When the Group has `:halt_on_fail` disabled, we've to catch `:task_has_failed`
# here in order to avoid an uncaught throw error.
#
# @param [Guard::Guard] guard the Guard to execute
# @param [Symbol] task the task to run
# @param [Array] args the arguments for the task
# @raise [:task_has_failed] when task has failed
#
def run_supervised_task(guard, task, *args)
catch guard_symbol(guard) do
guard.hook("#{ task }_begin", *args)
result = guard.send(task, *args)
guard.hook("#{ task }_end", result)
result
end
rescue Exception => ex
UI.error("#{ guard.class.name } failed to achieve its <#{ task.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")
ex
end
# Get the symbol we have to catch when running a supervised task.
# If we are within a Guard group that has the `:halt_on_fail`
# option set, we do NOT catch it here, it will be catched at the
# group level.
#
# @see .run_guard_task
#
# @param [Guard::Guard] guard the Guard to execute
# @return [Symbol] the symbol to catch
#
def guard_symbol(guard)
if guard.group.class == Symbol
group = groups(guard.group)
group.options[:halt_on_fail] ? :no_catch : :task_has_failed
else
:task_has_failed
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 (see the given Guard documentation)
#
def add_guard(name, watchers = [], callbacks = [], options = {})
if name.to_sym == :ego
UI.deprecation('Guard::Ego is now part of Guard. You can remove it from your Guardfile.')
else
guard_class = get_guard_class(name)
callbacks.each { |callback| Hook.add_callback(callback[:listener], guard_class, callback[:events]) }
@guards << guard_class.new(watchers, options)
end
end
# Add a Guard group.
#
# @param [String] name the group name
# @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`
# @return [Guard::Group] the group added (or retrieved from the `@groups` variable if already present)
#
def add_group(name, options = {})
group = groups(name)
if group.nil?
group = Group.new(name, options)
@groups << group
end
group
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
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 }"
end end
rescue LoadError => loadError listener.start
UI.error "Could not load 'guard/#{ name.downcase }' or find class Guard::#{ const_name.capitalize }"
UI.error loadError.to_s
end end
end end
# Locate a path to a Guard gem. def add_guard(name, watchers = [], options = {})
# guard_class = get_guard_class(name)
# @param [String] name the name of the Guard without the prefix `guard-` @guards << guard_class.new(watchers, options)
# @return [String] the full path to the Guard gem end
#
def get_guard_class(name)
require "guard/#{name.downcase}"
klasses = []
ObjectSpace.each_object(Class) do |klass|
klasses << klass if klass.to_s.downcase.match "^guard::#{name.downcase}"
end
klasses.first
rescue LoadError
UI.error "Could not find gem 'guard-#{name}' in the current Gemfile."
end
# Let a guard execute his task but
# fire it if his work lead to system failure
def supervised_task(guard, task_to_supervise, *args)
if !guard.respond_to(:"#{task_to_supervise}?") || guard.send(:"#{task_to_supervise}?")
guard.send(task_to_supervise, *args)
else
false
end
rescue Exception
UI.error("#{guard.class.name} guard failed to achieve its <#{task_to_supervise.to_s}> command: #{$!}")
::Guard.guards.delete guard
UI.info("Guard #{guard.class.name} has just been fired")
return $!
end
def locate_guard(name) def locate_guard(name)
if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0') `gem open guard-#{name} --latest --command echo`.chomp
Gem::Specification.find_by_name("guard-#{ name }").full_gem_path
else
Gem.source_index.find_name("guard-#{ name }").last.full_gem_path
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. def run
# listener.stop
# @return [Array<String>] a list of guard gem names UI.clear if options[:clear]
# begin
def guard_gem_names yield
if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0') rescue Interrupt
Gem::Specification.find_all.select { |x| x.name =~ /^guard-/ }
else
Gem.source_index.find_name(/^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(' ') }"
original_system command, *args
end
Kernel.send(:alias_method, :original_backtick, :'`')
Kernel.send(:define_method, :'`') do |command|
::Guard::UI.debug "Command execution: #{ command }"
original_backtick command
end end
listener.start
end end
end end
end end

View File

@ -2,118 +2,37 @@ require 'thor'
require 'guard/version' require 'guard/version'
module Guard module Guard
# Facade for the 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`.
# Do not put any logic in here, create a class and delegate instead.
#
class CLI < Thor class CLI < Thor
default_task :start default_task :start
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 :clear, method_option :debug, :type => :boolean, :default => false, :aliases => '-d', :banner => "Print debug messages"
: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'
method_option :watch_all_modifications,
:type => :boolean,
:default => false,
:aliases => '-A',
:banner => 'Watch for all file modifications including moves and deletions'
method_option :no_interactions,
:type => :boolean,
:default => false,
:aliases => '-i',
:banner => 'Turn off completely any guard terminal interactions'
# 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 "version", "Prints the guard's version information"
# List the Guards that are available for use in your system and marks
# those that are currently used in your `Guardfile`.
#
# @see Guard::DslDescriber.list
#
def list
::Guard::DslDescriber.list(options)
end
desc 'version', 'Show the Guard version'
map %w(-v --version) => :version
# Shows the current version of Guard.
#
# @see Guard::VERSION
#
def version def version
::Guard::UI.info "Guard version #{ Guard::VERSION }" ::Guard::UI.info "Guard version #{Guard::VERSION}"
end end
map %w(-v --version) => :version
desc 'init [GUARD]', 'Generates a Guardfile at the current working directory, or insert the given GUARD to an existing Guardfile'
desc "init [GUARD]", "Generates a Guardfile into the current working directory, or add it given guard"
# Appends the Guard template to the `Guardfile`, or creates an initial
# `Guardfile` when no Guard name is passed.
#
# @see Guard.initialize_template
#
# @param [String] guard_name the name of the Guard to initialize
#
def init(guard_name = nil) def init(guard_name = nil)
::Guard.initialize_template(guard_name) 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
if guard_name
guard_class = ::Guard.get_guard_class(guard_name)
guard_class.init(guard_name)
end
end end
desc 'show', 'Show all defined Guards and their options'
map %w(-T) => :show
# Shows all Guards and their options that are defined in
# the `Guardfile`.
#
# @see Guard::DslDescriber.show
#
def show
::Guard::DslDescriber.show(options)
end
end end
end end

View File

@ -1,370 +1,35 @@
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
def self.evaluate_guardfile
@@options = nil guardfile = "#{Dir.pwd}/Guardfile"
if File.exists? guardfile
# Evaluate the DSL methods in the `Guardfile`. begin
# dsl = new
# @option options [Array<Symbol,String>] groups the groups to evaluate dsl.instance_eval(File.read(guardfile.to_s), guardfile.to_s, 1)
# @option options [String] guardfile the path to a valid Guardfile rescue
# @option options [String] guardfile_contents a string representing the content of a valid Guardfile UI.error "Invalid Guardfile, original error is:\n#{$!}"
# @raise [ArgumentError] when options are not a Hash
#
def evaluate_guardfile(options = {})
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?
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.'
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)
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 }['"]?/)
end
# Read the current `Guardfile` content.
#
# @param [String] the path to the Guardfile
#
def read_guardfile(guardfile_path)
@@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
if @@options[:guardfile_contents]
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] }."
else
UI.error "No Guardfile exists at #{ @@options[:guardfile] }."
exit 1
end
else
if File.exist?(guardfile_default_path)
read_guardfile(guardfile_default_path)
else
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."
exit 1 exit 1
end end
end else
UI.error "No Guardfile in current folder, please create one."
# Get the content of the `Guardfile`. exit 1
#
# @return [String] the Guardfile content
#
def 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] : ''
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
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
# 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')
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'))
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'))
end
end
# 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
if block_given? && (@groups.empty? || @groups.map(&:to_sym).include?(name))
::Guard.add_group(name.to_s.downcase, options)
@current_group = name
yield if block_given?
@current_group = nil
end end
end end
# Declare a guard to be used when running `guard start`. def self.guardfile_included?(guard_name)
# File.read('Guardfile').include?("guard '#{guard_name}'")
# 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 = []
yield if block_given?
options.update(:group => (@current_group || :default))
::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. def guard(name, options = {}, &definition)
# @watchers = []
# @example Declare watchers for a Guard definition.call if definition
# ::Guard.add_guard(name, @watchers, options)
# guard 'rspec' do end
# 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)
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(', ') }"
::Guard.listener.ignore_paths.push(*paths)
end
end end
end end

View File

@ -1,150 +0,0 @@
require 'guard/dsl'
module Guard
autoload :UI, 'guard/ui'
# 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 << self
# Evaluate the DSL methods in the `Guardfile`.
#
# @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 = {})
@@guardfile_structure = [{ :guards => [] }]
super options
end
# 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
#
# @param [Hash] options the Guard options
#
def list(options)
evaluate_guardfile(options)
installed = guardfile_structure.inject([]) do |installed, group|
group[:guards].each { |guard| installed << guard[:name] } if group[:guards]
installed
end
UI.info 'Available guards:'
::Guard.guard_gem_names.sort.uniq.each do |name|
UI.info " #{ name }#{ installed.include?(name) ? '*' : '' }"
end
UI.info ''
UI.info 'See also https://github.com/guard/guard/wiki/List-of-available-Guards'
UI.info '* denotes ones already in your Guardfile'
end
# 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
#
# @param [Hash] options the Guard options
#
def show(options)
evaluate_guardfile(options)
guardfile_structure.each do |group|
unless group[:guards].empty?
if group[:group]
UI.info "Group #{ group[:group] }:"
else
UI.info '(global):'
end
group[:guards].each do |guard|
line = " #{ guard[:name] }"
unless guard[:options].empty?
line += ": #{ guard[:options].sort.collect { |k, v| "#{ k } => #{ v.inspect }" }.join(', ') }"
end
UI.info line
end
end
end
UI.info ''
end
private
# Get the Guardfile structure.
#
# @return [Array<Hash>] the structure
#
def guardfile_structure
@@guardfile_structure
end
end
private
# 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
yield if block_given?
@group = false
end
# 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,37 +0,0 @@
module Guard
# A group of Guards. There are two reasons why you want to group your guards:
#
# - You can start only certain Groups from the command line by passing the `--group` option.
# - Abort task execution chain on failure within a group.
#
# @example Group that aborts on failure
#
# group :frontend, :halt_on_fail => true do
# guard 'coffeescript', :input => 'spec/coffeescripts', :output => 'spec/javascripts'
# guard 'jasmine-headless-webkit' do
# watch(%r{^spec/javascripts/(.*)\..*}) { |m| newest_js_file("spec/javascripts/#{m[1]}_spec") }
# end
# end
#
# @see Guard::CLI
#
class Group
attr_accessor :name, :options
# Initialize a Group.
#
# @param [String] name the name of the group
# @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 initialize(name, options = {})
@name = name.to_sym
@options = options
end
end
end

View File

@ -1,129 +1,80 @@
module Guard module Guard
# Main class that every Guard implementation must subclass.
#
# Guard will trigger the `start`, `stop`, `reload`, `run_all`, `run_on_change` and
# `run_on_deletion` task methods depending on user interaction and file modification.
#
# In each of these Guard task methods you have to implement some work when you want to
# support this kind of task. The return value of each Guard task method is not evaluated
# by Guard, but I'll be passed to the "_end" hook for further evaluation. You can
# throw `:task_has_failed` to indicate that your Guard method was not successful,
# and successive guard tasks will be aborted when the group has set the `:halt_on_fail`
# option.
#
# @see Guard::Hook
# @see Guard::Group
#
# @example Throw :task_has_failed
#
# def run_all
# if !runner.run(['all'])
# throw :task_has_failed
# end
# end
#
# Each Guard should provide a template Guardfile located within the Gem
# at `lib/guard/guard-name/templates/Guardfile`.
#
# By default all watchers for a Guard are returning strings of paths to the
# Guard, but if your Guard want to allow any return value from a watcher,
# you can set the `any_return` option to true.
#
# If one of those methods raise an exception other than `:task_has_failed`,
# the Guard::GuardName instance will be removed from the active guards.
#
class Guard class Guard
include Hook attr_accessor :watchers, :options
attr_accessor :watchers, :options, :group DEFAULT_OPTIONS = {
:reload => {:at_start => false, :disable => false},
# Initialize a Guard. :run_all => {:at_start => false, :disable => false}
# }.freeze
# @param [Array<Guard::Watcher>] watchers the Guard file watchers
# @param [Hash] options the custom Guard options
# @options [Symbol] group the group this Guard belongs to
# @options [Boolean] any_return allow any object to be returned from a watcher
#
def initialize(watchers = [], options = {}) def initialize(watchers = [], options = {})
@group = options[:group] ? options.delete(:group).to_sym : :default @watchers, @options = watchers, DEFAULT_OPTIONS.merge(options)
@watchers, @options = watchers, options
end end
# Initialize the Guard. This will copy the Guardfile template inside the Guard gem. # Guardfile template needed inside 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_included?(name)
::Guard::UI.info "Guardfile already includes #{ name } guard" ::Guard::UI.info "Guardfile already include #{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 ""
f.puts(guard) f.puts guard
end end
::Guard::UI.info "#{name} guard added to Guardfile, feel free to edit it"
::Guard::UI.info "#{ name } guard added to Guardfile, feel free to edit it"
end end
end end
# Call once when Guard starts. Please override initialize method to init stuff. # ================
# # = Guard method =
# @raise [:task_has_failed] when start has failed # ================
# @return [Object] the task result
# # Call once when guard starts
# Please override initialize method to init stuff
def start def start
true
end end
# Called when `stop|quit|exit|s|q|e + enter` is pressed (when Guard quits). # Call once when guard quit
#
# @raise [:task_has_failed] when stop has failed
# @return [Object] the task result
#
def stop def stop
true
end end
# Called when `reload|r|z + enter` is pressed. # Should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/...
# This method should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/...
#
# @raise [:task_has_failed] when reload has failed
# @return [Object] the task result
#
def reload def reload
true
end end
# Called when just `enter` is pressed # disable reload method if return true. When is overwriten, must return false if `super' return false
# This method should be principally used for long action like running all specs/tests/... def reload?
# options[:reload] == true || (options[:reload].is_a?(Hash) && !options[:reload][:disable])
# @raise [:task_has_failed] when run_all has failed end
# @return [Object] the task result
# # enable call of reload method after start method if return true. When is overwriten, must return false if `super' return false
def reload_at_start?
options[:reload].is_a?(Hash) && !!options[:reload][:at_start]
end
# Should be principally used for long action like running all specs/tests/...
def run_all def run_all
true
end end
# Called on file(s) modifications that the Guard watches. # disable run_all method if return true. When is overwriten, must return false if `super' return false
# def run_all?
# @param [Array<String>] paths the changes files or paths options[:run_all] == true || (options[:run_all].is_a?(Hash) && !options[:run_all][:disable])
# @raise [:task_has_failed] when run_on_change has failed end
# @return [Object] the task result
# # enable call of run_all method after start method if return true. When is overwriten, must return false if `super' return false
def run_all_at_start?
options[:run_all].is_a?(Hash) && !!options[:run_all][:at_start]
end
def run_on_change(paths) def run_on_change(paths)
true
end end
# Called on file(s) deletions that the Guard watches.
#
# @param [Array<String>] paths the deleted files or paths
# @raise [:task_has_failed] when run_on_change has failed
# @return [Object] the task result
#
def run_on_deletion(paths)
end
end end
end
end

View File

@ -1,118 +0,0 @@
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
# with the given Symbol.
#
# @example Add a hook with a Symbol
#
# def run_all
# hook :foo
# end
#
# 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
# into a Symbol.
#
# @example Add a hook with a String
#
# def run_all
# hook "foo_bar"
# end
#
# When {Guard::Guard#run_all} is called, {#hook} will notify callbacks
# registered for the "foo_bar" 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
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|
callbacks[[guard_class, event]] << listener
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
end

View File

@ -1,44 +1,29 @@
module Guard module Guard
module Interactor
# The interactor reads user input and triggers
# specific action upon them unless its locked. def self.init_signal_traps
# # Run all (Ctrl-\)
# Currently the following actions are implemented: Signal.trap('QUIT') do
# ::Guard.run do
# - stop, quit, exit, s, q, e => Exit Guard ::Guard.guards.each { |g| ::Guard.supervised_task g, :run_all }
# - reload, r, z => Reload Guard end
# - pause, p => Pause Guard end
# - Everything else => Run all
# # Stop (Ctrl-C)
class Interactor Signal.trap('INT') do
# Start the interactor in its own thread. ::Guard.listener.stop
# ::Guard.guards.each { |g| ::Guard.supervised_task g, :stop }
def start UI.info "Bye bye...", :reset => true
return if ENV["GUARD_ENV"] == 'test' abort("\n")
end
if !@thread || @thread.stop?
@thread = Thread.new do # Reload (Ctrl-Z)
while entry = $stdin.gets.chomp Signal.trap('TSTP') do
case entry ::Guard.run do
when 'stop', 'quit', 'exit', 's', 'q', 'e' ::Guard.guards.each { |g| ::Guard.supervised_task g, :reload }
::Guard.stop
when 'reload', 'r', 'z'
::Guard::Dsl.reevaluate_guardfile
::Guard.reload
when 'pause', 'p'
::Guard.pause
else
::Guard.run_all
end
end
end end
end end
end end
def stop_if_not_current
unless Thread.current == @thread
@thread.kill
end
end
end end
end end

View File

@ -1,350 +1,58 @@
require 'rbconfig' require 'rbconfig'
require 'digest/sha1'
module Guard module Guard
autoload :Darwin, 'guard/listeners/darwin' autoload :Darwin, 'guard/listeners/darwin'
autoload :Linux, 'guard/listeners/linux' autoload :Linux, 'guard/listeners/linux'
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
attr_reader :last_event
# Default paths that gets ignored by the listener
DEFAULT_IGNORE_PATHS = %w[. .. .bundle .git log tmp vendor] def self.init
attr_accessor :changed_files
attr_reader :directory, :ignore_paths
def paused?
@paused
end
# 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(*args) Darwin.new
elsif linux? && Linux.usable? elsif linux? && Linux.usable?
Linux.new(*args) Linux.new
elsif windows? && Windows.usable?
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(*args) Polling.new
end end
end end
# Initialize the listener. def initialize
#
# @param [String] directory the root directory to listen to
# @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 = {}
@file_timestamp_hash = {}
@relativize_paths = options.fetch(:relativize_paths, true)
@changed_files = []
@paused = false
@ignore_paths = DEFAULT_IGNORE_PATHS
@ignore_paths |= options[:ignore_paths] if options[:ignore_paths]
@watch_all_modifications = options.fetch(:watch_all_modifications, false)
update_last_event update_last_event
start_reactor
end end
# Start the listener thread. private
#
def start_reactor
return if ENV["GUARD_ENV"] == 'test'
Thread.new do
loop do
if @changed_files != [] && !@paused
changed_files = @changed_files.dup
clear_changed_files
::Guard.run_on_change(changed_files)
else
sleep 0.1
end
end
end
end
# Start watching the root directory.
#
def start
watch(@directory)
timestamp_files
end
# Stop listening for events.
#
def stop
end
# Pause the listener to ignore change events.
#
def pause
@paused = true
end
# Unpause the listener to listen again to change events.
#
def run
@paused = 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.
#
# If the `:watch_all_modifications` option is true, then moved and
# deleted files are also reported, but prefixed by an exclamation point.
#
# @example Deleted or moved file
# !/home/user/dir/file.rb
#
# @param [Array<String>] dirs the watched directories
# @param [Hash] options the listener options
# @option options [Symbol] all whether to files in sub directories
# @return [Array<String>] paths of files that have been modified
#
def modified_files(dirs, options = {}) def modified_files(dirs, options = {})
last_event = @last_event files = potentially_modified_files(dirs, options).select { |path| File.file?(path) && recent_file?(path) }
files = [] files.map! { |file| file.gsub("#{Dir.pwd}/", '') }
if @watch_all_modifications
deleted_files = @file_timestamp_hash.collect do |path, ts|
unless File.exists?(path)
@sha1_checksums_hash.delete(path)
@file_timestamp_hash.delete(path)
"!#{path}"
end
end
files.concat(deleted_files.compact)
end
update_last_event
files.concat(potentially_modified_files(dirs, options).select { |path| file_modified?(path, last_event) })
relativize_paths(files)
end end
# 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.
#
# @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 }/},'\1')
end
end
# Use paths relative to the current directory.
#
# @return [Boolean] whether to use relative or absolute paths
#
def relativize_paths?
!!@relativize_paths
end
# Populate initial timestamp file hash to watch for deleted or moved files.
#
def timestamp_files
all_files.each {|path| set_file_timestamp_hash(path, file_timestamp(path)) } if @watch_all_modifications
end
# 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
# Gets a list of files that are in the modified directories.
#
# @param [Array<String>] dirs the list of directories
# @param [Hash] options the find file option
# @option options [Symbol] all whether to files in sub directories
#
def potentially_modified_files(dirs, options = {}) def potentially_modified_files(dirs, options = {})
paths = exclude_ignored_paths(dirs) match = options[:all] ? "**/*" : "*"
Dir.glob(dirs.map { |dir| "#{dir}#{match}" })
if options[:all]
paths.inject([]) do |array, path|
if File.file?(path)
array << path
else
array += Dir.glob("#{ path }/**/*", File::FNM_DOTMATCH).select { |p| File.file?(p) }
end
array
end
else
paths.select { |path| File.file?(path) }
end
end end
# Test if the file content has changed. def recent_file?(file)
# File.mtime(file) >= last_event
# 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
if [mtime, ctime].max == last_event.to_i
file_content_modified?(path, sha1_checksum(path))
elsif mtime > last_event.to_i
set_sha1_checksums_hash(path, sha1_checksum(path))
true
elsif @watch_all_modifications
ts = file_timestamp(path)
if ts != @file_timestamp_hash[path]
set_file_timestamp_hash(path, ts)
true
end
else
false
end
rescue rescue
false false
end end
# Tests if the file content has been modified by def update_last_event
# comparing the SHA1 checksum. @last_event = Time.now
#
# @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)
true
else
false
end
end end
# Set save a files current timestamp
#
# @param [String] path the file path
# @param [Int] file_timestamp the files modified timestamp
#
def set_file_timestamp_hash(path, file_timestamp)
@file_timestamp_hash[path] = file_timestamp
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
# Gets a files modified timestamp
#
# @path [String] path the file path
# @return [Int] file modified timestamp
#
def file_timestamp(path)
File.mtime(path).to_i
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? def self.mac?
RbConfig::CONFIG['target_os'] =~ /darwin/i Config::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 Config::CONFIG['target_os'] =~ /linux/i
end 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
end end
end end

View File

@ -1,66 +1,40 @@
module Guard module Guard
# Listener implementation for Mac OS X `FSEvents`.
#
class Darwin < Listener class Darwin < Listener
attr_reader :fsevent
# Initialize the Listener.
# def initialize
def initialize(*)
super super
@fsevent = FSEvent.new @fsevent = FSEvent.new
end end
# Start the listener. def on_change(&callback)
# @fsevent.watch Dir.pwd do |modified_dirs|
files = modified_files(modified_dirs)
update_last_event
callback.call(files)
end
end
def start def start
super @fsevent.run
worker.run
end end
# Stop the listener.
#
def stop def stop
super @fsevent.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) || Gem::Version.new(FSEvent::VERSION) < Gem::Version.new('0.3.5')
Gem::Version.new(FSEvent::VERSION) < Gem::Version.new('0.4.0')) UI.info "Please update rb-fsevent (>= 0.3.5)"
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
# 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)
@callback.call(files) unless files.empty?
end
end
end end
end end

View File

@ -1,65 +1,28 @@
module Guard module Guard
# Listener implementation for Linux `inotify`.
#
class Linux < Listener class Linux < Listener
attr_reader :inotify, :files, :latency, :callback
# Initialize the Listener. def initialize
#
def initialize(*)
super super
@inotify = INotify::Notifier.new @inotify = INotify::Notifier.new
@files = [] @files = []
@latency = 0.5 @latency = 0.5
end end
# Start the listener.
#
def start def start
@stop = false @stop = false
super
watch_change unless watch_change? watch_change unless watch_change?
end end
# Stop the listener.
#
def stop def stop
super
@stop = true @stop = true
sleep latency
end end
# Check if the listener is usable on the current OS. def on_change(&callback)
# @callback = callback
# @return [Boolean] whether usable or not inotify.watch(Dir.pwd, :recursive, :modify, :create, :delete, :move) do |event|
#
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)'
false
else
true
end
rescue LoadError
UI.info 'Please install rb-inotify gem for Linux inotify support'
false
end
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)
worker.watch(directory, :recursive, :attrib, :create, :move_self, :close_write) do |event|
unless event.name == "" # Event on root directory unless event.name == "" # Event on root directory
@files << event.absolute_name @files << event.absolute_name
end end
@ -67,31 +30,44 @@ module Guard
rescue Interrupt rescue Interrupt
end end
# Test if inotify is watching for changes. def self.usable?
# require 'rb-inotify'
# @return [Boolean] whether inotify is active or not if !defined?(INotify::VERSION) || Gem::Version.new(INotify::VERSION.join('.')) < Gem::Version.new('0.5.1')
# UI.info "Please update rb-inotify (>= 0.5.1)"
false
else
true
end
rescue LoadError
UI.info "Please install rb-inotify gem for Linux inotify support"
false
end
def watch_change? def watch_change?
!!@watch_change !!@watch_change
end end
# Watch for file system changes. private
#
def watch_change def watch_change
@watch_change = true @watch_change = true
until @stop while !@stop
if RbConfig::CONFIG['build'] =~ /java/ || IO.select([worker.to_io], [], [], @latency) if Config::CONFIG['build'] =~ /java/ || IO.select([inotify.to_io], [], [], latency)
break if @stop break if @stop
sleep(@latency) sleep latency
worker.process inotify.process
update_last_event
files = modified_files(@files.shift(@files.size).map { |f| File.dirname(f) }.uniq) unless files.empty?
@callback.call(files) unless files.empty? files.map! { |file| file.gsub("#{Dir.pwd}/", '') }
callback.call(files)
files.clear
end
end end
end end
@watch_change = false @watch_change = false
end end
end end
end end

View File

@ -1,55 +1,37 @@
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
attr_reader :callback, :latency
# Initialize the Listener.
# def initialize
def initialize(*)
super super
@latency = 1.5 @latency = 1.5
end end
# Start the listener. def on_change(&callback)
# @callback = callback
end
def start def start
@stop = false @stop = false
super
watch_change watch_change
end end
# Stop the listener.
#
def stop def stop
super
@stop = true @stop = true
end end
# Watch the given directory for file changes. private
#
# @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 while !@stop
start = Time.now.to_f start = Time.now.to_f
files = modified_files([@directory], :all => true) files = modified_files([Dir.pwd + '/'], :all => true)
@callback.call(files) unless files.empty? update_last_event
nap_time = @latency - (Time.now.to_f - start) callback.call(files) unless files.empty?
nap_time = latency - (Time.now.to_f - start)
sleep(nap_time) if nap_time > 0 sleep(nap_time) if nap_time > 0
end end
end end
end end
end end

View File

@ -1,61 +0,0 @@
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'
false
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)]
files = modified_files(paths, :all => true)
@callback.call(files) unless files.empty?
end
end
# Get the listener worker.
#
def worker
@fchange
end
end
end

View File

@ -1,290 +1,62 @@
require 'rbconfig' require 'rbconfig'
require 'pathname' require 'pathname'
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 def self.notify(message, options = {})
APPLICATION_NAME = "Guard" unless ENV["GUARD_ENV"] == "test"
image = options[:image] || :success
class << self title = options[:title] || "Guard"
case Config::CONFIG['target_os']
attr_accessor :growl_library, :gntp when /darwin/i
if growl_installed?
# Turn notifications off. Growl.notify message, :title => title, :icon => image_path(image), :name => "Guard"
# end
def turn_off when /linux/i
ENV["GUARD_NOTIFY"] = 'false' if libnotify_installed?
end Libnotify.show :body => message, :summary => title, :icon_path => image_path(image)
# Turn notifications on. This tries to load the platform
# specific notification library.
#
# @return [Boolean] whether the notification could be enabled.
#
def turn_on
ENV["GUARD_NOTIFY"] = 'true'
case RbConfig::CONFIG['target_os']
when /darwin/i
require_growl
when /linux/i
require_libnotify
when /mswin|mingw/i
require_rbnotifu
end
end
# Show a message with the system notification.
#
# @see .image_path
#
# @param [String] the message to show
# @option options [Symbol, String] image the image symbol or path to an image
# @option options [String] title the notification title
#
def notify(message, options = { })
if enabled?
image = options.delete(:image) || :success
title = options.delete(:title) || "Guard"
case RbConfig::CONFIG['target_os']
when /darwin/i
notify_mac(title, message, image, options)
when /linux/i
notify_linux(title, message, image, options)
when /mswin|mingw/i
notify_windows(title, message, image, options)
end end
end end
end end
# Test if the notifications are enabled and available.
#
# @return [Boolean] whether the notifications are available
#
def 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 notify_mac(title, message, image, options = { })
require_growl # need for guard-rspec formatter that is called out of guard scope
notification = { :title => title, :icon => image_path(image) }.merge(options)
case self.growl_library
when :growl_notify
notification.delete(:name)
GrowlNotify.send_notification({
:description => message,
:application_name => APPLICATION_NAME
}.merge(notification))
when :ruby_gntp
icon = "file://#{ notification.delete(:icon) }"
self.gntp.notify({
:name => [:pending, :success, :failed].include?(image) ? image.to_s : 'notify',
:text => message,
:icon => icon
}.merge(notification))
when :growl
Growl.notify(message, {
:name => APPLICATION_NAME
}.merge(notification))
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 notify_linux(title, message, image, options = { })
require_libnotify # need for guard-rspec formatter that is called out of guard scope
notification = { :body => message, :summary => title, :icon_path => image_path(image), :transient => true }
Libnotify.show notification.merge(options)
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 notify_windows(title, message, image, options = { })
require_rbnotifu # need for guard-rspec formatter that is called out of guard scope
notification = { :message => message, :title => title, :type => image_level(image), :time => 3 }
Notifu.show notification.merge(options)
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 image_path(image)
images_path = Pathname.new(File.dirname(__FILE__)).join('../../images')
case image
when :failed
images_path.join("failed.png").to_s
when :pending
images_path.join("pending.png").to_s
when :success
images_path.join("success.png").to_s
else
# path given
image
end
end
# The notification level type for the given image.
#
# @param [Symbol] image the image
# @return [Symbol] the level
#
def image_level(image)
case image
when :failed
:error
when :pending
:warn
when :success
:info
else
:info
end
end
# Try to safely load growl and turns notifications off on load failure.
# The Guard notifier knows three different library to handle sending
# Growl messages and tries to loading them in the given order:
#
# - [Growl Notify](https://github.com/scottdavis/growl_notify)
# - [Ruby GNTP](https://github.com/snaka/ruby_gntp)
# - [Growl](https://github.com/visionmedia/growl)
#
# On successful loading of any of the libraries, the active library name is
# accessible through `.growl_library`.
#
def require_growl
self.growl_library = try_growl_notify || try_ruby_gntp || try_growl
unless self.growl_library
turn_off
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 load the `growl_notify` gem.
#
# @return [Symbol, nil] A symbol with the name of the loaded library
#
def try_growl_notify
require 'growl_notify'
begin
if GrowlNotify.application_name != APPLICATION_NAME
GrowlNotify.config do |c|
c.notifications = c.default_notifications = [APPLICATION_NAME]
c.application_name = c.notifications.first
end
end
rescue ::GrowlNotify::GrowlNotFound
turn_off
UI.info "Please install Growl from http://growl.info"
end
:growl_notify
rescue LoadError
end
# Try to load the `ruby_gntp` gem and register the available
# notification channels.
#
# @return [Symbol, nil] A symbol with the name of the loaded library
#
def try_ruby_gntp
require 'ruby_gntp'
self.gntp = GNTP.new(APPLICATION_NAME)
self.gntp.register(:notifications => [
{ :name => 'notify', :enabled => true },
{ :name => 'failed', :enabled => true },
{ :name => 'pending', :enabled => true },
{ :name => 'success', :enabled => true }
])
:ruby_gntp
rescue LoadError
end
# Try to load the `growl_notify` gem.
#
# @return [Symbol, nil] A symbol with the name of the loaded library
#
def try_growl
require 'growl'
:growl
rescue LoadError
end
# Try to safely load libnotify and turns notifications
# off on load failure.
#
def require_libnotify
require 'libnotify'
rescue LoadError
turn_off
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 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
private
def self.image_path(image)
images_path = Pathname.new(File.dirname(__FILE__)).join('../../images')
case image
when :failed
images_path.join("failed.png").to_s
when :pending
images_path.join("pending.png").to_s
when :success
images_path.join("success.png").to_s
else
# path given
image
end
end
def self.growl_installed?
@installed ||= begin
require 'growl'
true
rescue LoadError
UI.info "Please install growl gem for Mac OS X notification support and add it to your Gemfile"
false
end
end
def self.libnotify_installed?
@installed ||= begin
require 'libnotify'
true
rescue LoadError
UI.info "Please install libnotify gem for Linux notification support and add it to your Gemfile"
false
end
end
end end
end end

View File

@ -1,2 +1,2 @@
# A sample Guardfile # A sample Guardfile
# More info at https://github.com/guard/guard#readme # More info at http://github.com/guard/guard#readme

View File

@ -1,193 +1,42 @@
module Guard module Guard
# The UI class helps to format messages for the user. Everything that is logged
# through this class is considered either as an error message or a diagnostic
# message and is written to standard error (STDERR).
#
# If your Guard does some output that is piped into another process for further
# processing, please just write it to STDOUT with `puts`.
#
module UI module UI
class << self 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
# @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]
STDERR.puts color(message) if message != '' puts reset_color(message) if message != ''
end end
end end
# Show a red error message that is prefixed with ERROR. def error(message)
# puts "ERROR: #{message}"
# @param [String] message the message to show end
# @option options [Boolean] reset whether to clean the output before
# def debug(message)
def error(message, options = { }) unless ENV["GUARD_ENV"] == "test"
unless ENV['GUARD_ENV'] == 'test' puts "DEBUG: #{message}" if ::Guard.options && ::Guard.options[:debug]
reset_line if options[:reset]
STDERR.puts color('ERROR: ', :red) + message
end end
end end
# Show a red deprecation message that is prefixed with DEPRECATION.
#
# @param [String] message the message to show
# @option options [Boolean] reset whether to clean the output before
#
def deprecation(message, options = { })
unless ENV['GUARD_ENV'] == 'test'
reset_line if options[:reset]
STDERR.puts color('DEPRECATION: ', :red) + message
end
end
# Show a debug message that is prefixed with DEBUG and a timestamp.
#
# @param [String] message the message to show
# @option options [Boolean] reset whether to clean the output before
#
def debug(message, options = { })
unless ENV['GUARD_ENV'] == 'test'
reset_line if options[:reset]
STDERR.puts color("DEBUG (#{Time.now.strftime('%T')}): ", :yellow) + message if ::Guard.options && ::Guard.options[:debug]
end
end
# Reset a line.
#
def reset_line def reset_line
STDERR.print(color_enabled? ? "\r\e[0m" : "\r\n") print "\r\e "
end end
# Clear the output.
#
def clear def clear
system('clear;') system("clear;")
end end
private private
# Reset a color sequence.
#
# @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.') color(text, "\e[0m")
color(text, '')
end end
# Checks if color output can be enabled. def color(text, color_code)
# "#{color_code}#{text}\e[0m"
# @return [Boolean] whether color is enabled or not
#
def color_enabled?
if @color_enabled.nil?
if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
if ENV['ANSICON']
@color_enabled = true
else
begin
require 'rubygems' unless ENV['NO_RUBYGEMS']
require 'Win32/Console/ANSI'
@color_enabled = true
rescue LoadError
@color_enabled = false
info "You must 'gem install win32console' to use color on Windows"
end
end
else
@color_enabled = true
end
end
@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,6 +1,3 @@
module Guard module Guard
unless defined? Guard::VERSION VERSION = "0.2.2"
# The current gem version of Guard end
VERSION = '0.8.4'
end
end

View File

@ -1,114 +1,34 @@
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
# deprecation warning
if @pattern.is_a?(String) && @pattern =~ /(^(\^))|(>?(\\\.)|(\.\*))|(\(.*\))|(\[.*\])|(\$$)/
unless @@warning_printed
UI.info "*"*20 + "\nDEPRECATION WARNING!\n" + "*"*20
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"
@pattern = Regexp.new(@pattern)
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<Object>] the matched watcher response
#
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|
if matches = watcher.match_file?(file) if matches = file.match(watcher.pattern)
if watcher.action if watcher.action
result = watcher.call_action(matches) begin
if guard.options[:any_return] if watcher.action.arity == 1
paths << result result = watcher.action.call(matches)
elsif result.respond_to?(:empty?) && !result.empty? else
paths << Array(result) result = watcher.action.call
end
rescue
UI.info "Problem with watch action"
end end
paths << result if result.is_a?(String) && result != ''
else else
paths << matches[0] paths << matches[0]
end end
end end
end end
paths
guard.options[:any_return] ? paths : 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)
guards.any? do |guard|
guard.watchers.any? do |watcher|
files.any? { |file| watcher.match_file?(file) }
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)
if @pattern.is_a?(Regexp)
file.match(@pattern)
else
file == @pattern ? [file] : nil
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 }
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") }"
end
end
end end
end end

View File

@ -1,93 +0,0 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "GUARD" "1" "September 2011" "" ""
.
.SH "NAME"
\fBguard\fR \- 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"
\fBguard <COMMAND> <OPTIONS>\fR
.
.SH "COMMANDS"
.
.SS "start"
Starts Guard\. This is the default command if none is provided\.
.
.P
The following options are available:
.
.P
\fB\-c\fR, \fB\-\-clear\fR Clears the Shell after each change\.
.
.P
\fB\-n\fR, \fB\-\-notify\fR \fIFLAG\fR 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 \fBtrue\fR/\fBfalse\fR or \fBt\fR/\fBf\fR\.
.
.P
\fB\-d\fR, \fB\-\-debug\fR Runs Guard in debug mode\.
.
.P
\fB\-g\fR, \fB\-\-group\fR \fIGROUP1\fR \fIGROUP2\fR\.\.\. Runs only the groups specified by GROUP1, GROUP2 etc\. Groups name should be separated by spaces\. Guards that don\'t belong to a group are considered global and are always run\.
.
.P
\fB\-w\fR, \fB\-\-watchdir\fR \fIPATH\fR
.
.P
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 [GUARD]"
If no Guardfile is present in the current directory, creates an empty Guardfile\.
.
.P
If \fIGUARD\fR is present, add its default Guardfile configuration to the current Guardfile\. Note that \fIGUARD\fR is the guard\'s name without the \fBguard\-\fR prefix\. For instance to initialize guard\-rspec, run \fBguard init rspec\fR\.
.
.SS "list"
Lists guards that can be used with the \fBinit\fR command\.
.
.SS "\-T, show"
List defined groups and guards for the current Guardfile\.
.
.SS "\-h, help [COMMAND]"
List all of Guard\'s available commands\.
.
.P
If \fICOMMAND\fR is given, displays a specific help for \fITASK\fR\.
.
.SH "EXAMPLES"
Initialize Guard and a specific guard at the same time:
.
.P
\fB[bundle exec] guard init [rspec]\fR
.
.P
Run Guard:
.
.P
\fB[bundle exec] guard [start] \-\-watchdir ~/dev \-\-guardfile ~/env/Guardfile \-\-clear \-\-group backend frontend \-\-notify false \-\-debug\fR
.
.P
or in a more concise way:
.
.P
\fB[bundle exec] guard [start] \-w ~/dev \-G ~/env/Guardfile \-c \-g backend frontend \-n f \-d\fR
.
.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

View File

@ -1,176 +0,0 @@
<!DOCTYPE html>
<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(1) - Guard keeps an eye on your file modifications.</title>
<style type='text/css' media='all'>
/* style: man */
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>
<!--
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.
-->
<body id='manpage'>
<div class='mp' id='man'>
<div class='man-navigation' style='display:none'>
<a href="#NAME">NAME</a>
<a href="#DESCRIPTION">DESCRIPTION</a>
<a href="#SYNOPSIS">SYNOPSIS</a>
<a href="#COMMANDS">COMMANDS</a>
<a href="#EXAMPLES">EXAMPLES</a>
<a href="#AUTHORS-CONTRIBUTORS">AUTHORS / CONTRIBUTORS</a>
<a href="#WWW">WWW</a>
</div>
<ol class='man-decor man-head man head'>
<li class='tl'>guard(1)</li>
<li class='tc'></li>
<li class='tr'>guard(1)</li>
</ol>
<h2 id="NAME">NAME</h2>
<p class="man-name">
<code>guard</code> - <span class="man-whatis">Guard keeps an eye on your file modifications.</span>
</p>
<h2 id="DESCRIPTION">DESCRIPTION</h2>
<p>Guard is a command line tool that easily handle events on files modifications.</p>
<h2 id="SYNOPSIS">SYNOPSIS</h2>
<p><code>guard &lt;COMMAND> &lt;OPTIONS></code></p>
<h2 id="COMMANDS">COMMANDS</h2>
<h3 id="start">start</h3>
<p>Starts Guard. This is the default command if none is provided.</p>
<p>The following options are available:</p>
<p><code>-c</code>, <code>--clear</code>
Clears the Shell after each change.</p>
<p><code>-n</code>, <code>--notify</code> <var>FLAG</var>
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 <code>true</code>/<code>false</code> or <code>t</code>/<code>f</code>.</p>
<p><code>-d</code>, <code>--debug</code>
Runs Guard in debug mode.</p>
<p><code>-g</code>, <code>--group</code> <var>GROUP1</var> <var>GROUP2</var>...
Runs only the groups specified by GROUP1, GROUP2 etc.
Groups name should be separated by spaces.
Guards that don't belong to a group are considered global and are always run.</p>
<p><code>-w</code>, <code>--watchdir</code> <var>PATH</var></p>
<p>Tells Guard to watch PATH instead of <code>./</code>.</p>
<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 [GUARD]</h3>
<p>If no Guardfile is present in the current directory, creates an empty Guardfile.</p>
<p>If <var>GUARD</var> is present, add its default Guardfile configuration to the current Guardfile.
Note that <var>GUARD</var> is the guard's name without the <code>guard-</code> prefix.
For instance to initialize guard-rspec, run <code>guard init rspec</code>.</p>
<h3 id="list">list</h3>
<p>Lists guards that can be used with the <code>init</code> command.</p>
<h3 id="-T-show">-T, show</h3>
<p>List defined groups and guards for the current Guardfile.</p>
<h3 id="-h-help-COMMAND-">-h, help [COMMAND]</h3>
<p>List all of Guard's available commands.</p>
<p>If <var>COMMAND</var> is given, displays a specific help for <var>TASK</var>.</p>
<h2 id="EXAMPLES">EXAMPLES</h2>
<p>Initialize Guard and a specific guard at the same time:</p>
<p><code>[bundle exec] guard init [rspec]</code></p>
<p>Run Guard:</p>
<p><code>[bundle exec] guard [start] --watchdir ~/dev --guardfile ~/env/Guardfile --clear --group backend frontend --notify false --debug</code></p>
<p>or in a more concise way:</p>
<p><code>[bundle exec] guard [start] -w ~/dev -G ~/env/Guardfile -c -g backend frontend -n f -d</code></p>
<h2 id="AUTHORS-CONTRIBUTORS">AUTHORS / CONTRIBUTORS</h2>
<p>Thibaud Guillaume-Gentil is the main author.</p>
<p>A list of contributors based on all commits can be found here:
https://github.com/guard/guard/contributors</p>
<p>For an exhaustive list of all the contributors, please see the CHANGELOG:
https://github.com/guard/guard/blob/master/CHANGELOG.md</p>
<p>This manual has been written by Remy Coutable.</p>
<h2 id="WWW">WWW</h2>
<p>https://github.com/guard/guard</p>
<ol class='man-decor man-foot man foot'>
<li class='tl'></li>
<li class='tc'>September 2011</li>
<li class='tr'>guard(1)</li>
</ol>
</div>
</body>
</html>

View File

@ -1,93 +0,0 @@
guard(1) -- Guard keeps an eye on your file modifications.
========================================================
## DESCRIPTION
Guard is a command line tool that easily handle events on files modifications.
## SYNOPSIS
`guard <COMMAND> <OPTIONS>`
## COMMANDS
### start
Starts Guard. This is the default command if none is provided.
The following options are available:
`-c`, `--clear`
Clears the Shell after each change.
`-n`, `--notify` <FLAG>
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 `true`/`false` or `t`/`f`.
`-d`, `--debug`
Runs Guard in debug mode.
`-g`, `--group` <GROUP1> <GROUP2>...
Runs only the groups specified by GROUP1, GROUP2 etc.
Groups name should be separated by spaces.
Guards that don't belong to a group are considered global and are always run.
`-w`, `--watchdir` <PATH>
Tells Guard to watch PATH instead of `./`.
`-G`, `--guardfile` <FILE>
Tells Guard to use FILE as its Guardfile instead of `./Guardfile` or `~/.Guardfile`.
### init [GUARD]
If no Guardfile is present in the current directory, creates an empty Guardfile.
If <GUARD> is present, add its default Guardfile configuration to the current Guardfile.
Note that <GUARD> is the guard's name without the `guard-` prefix.
For instance to initialize guard-rspec, run `guard init rspec`.
### list
Lists guards that can be used with the `init` command.
### -T, show
List defined groups and guards for the current Guardfile.
### -h, help [COMMAND]
List all of Guard's available commands.
If <COMMAND> is given, displays a specific help for <TASK>.
## EXAMPLES
Initialize Guard and a specific guard at the same time:
`[bundle exec] guard init [rspec]`
Run Guard:
`[bundle exec] guard [start] --watchdir ~/dev --guardfile ~/env/Guardfile --clear --group backend frontend --notify false --debug`
or in a more concise way:
`[bundle exec] guard [start] -w ~/dev -G ~/env/Guardfile -c -g backend frontend -n f -d`
## AUTHORS / CONTRIBUTORS
Thibaud Guillaume-Gentil is the main author.
A list of contributors based on all commits can be found here:
https://github.com/guard/guard/contributors
For an exhaustive list of all the contributors, please see the CHANGELOG:
https://github.com/guard/guard/blob/master/CHANGELOG.md
This manual has been written by Remy Coutable.
## WWW
https://github.com/guard/guard

View File

View File

@ -1 +0,0 @@
# nothing here, it's just for feeding the specs! :)

0
spec/fixtures/folder1/file1.txt vendored Executable file → Normal file
View File

View File

@ -1,70 +0,0 @@
require 'spec_helper'
describe Guard::DslDescriber do
let(:describer) { ::Guard::DslDescriber }
let(:guardfile) do
<<-GUARD
guard 'test', :a => :b do
watch('c')
end
group :a do
guard 'test', :x => 1 do
watch('c')
end
end
group "b" do
guard 'another' do
watch('c')
end
end
GUARD
end
before do
@output = ''
Guard::UI.stub(:info) { |msg| @output << msg + "\n" }
end
after do
Guard::UI.unstub(:info)
end
describe '.list' do
it 'lists the available Guards' do
Guard.stub(:guard_gem_names).and_return ['test', 'another', 'even', 'more']
describer.list(:guardfile_contents => guardfile)
@output.should eql <<OUTPUT
Using inline Guardfile.
Available guards:
another*
even
more
test*
See also https://github.com/guard/guard/wiki/List-of-available-Guards
* denotes ones already in your Guardfile
OUTPUT
end
end
describe '.show' do
it 'shows the Guards and their options' do
describer.show(:guardfile_contents => guardfile)
@output.should eql <<OUTPUT
Using inline Guardfile.
(global):
test: a => :b
Group a:
test: x => 1
Group b:
another
OUTPUT
end
end
end

View File

@ -1,383 +1,62 @@
require 'spec_helper' require 'spec_helper'
require 'guard/guard' require 'guard/dsl'
describe Guard::Dsl do describe Guard::Dsl do
subject { Guard::Dsl }
class Guard::Dummy < Guard::Guard; end
before(:each) do before(:each) do
@local_guardfile_path = File.join(Dir.pwd, 'Guardfile') ::Guard.stub!(:add_guard)
@home_guardfile_path = File.expand_path(File.join("~", ".Guardfile"))
@user_config_path = File.expand_path(File.join("~", ".guard.rb"))
::Guard.setup
::Guard.stub!(:options).and_return(:debug => true)
::Guard.stub!(:guards).and_return([mock('Guard')])
end end
def self.disable_user_config it "write an error message when no Guardfile is found" do
before(:each) { File.stub(:exist?).with(@user_config_path) { false } } Dir.stub!(:pwd).and_return("no_guardfile_here")
Guard::UI.should_receive(:error).with("No Guardfile in current folder, please create one.")
lambda { subject.evaluate_guardfile }.should raise_error
end end
describe "it should select the correct data source for Guardfile" do it "write an error message when Guardfile is not valid" do
before(:each) { ::Guard::Dsl.stub!(:instance_eval_guardfile) } mock_guardfile_content("This Guardfile is invalid!")
disable_user_config
Guard::UI.should_receive(:error).with(/Invalid Guardfile, original error is:\n/)
it "should use a string for initializing" do lambda { subject.evaluate_guardfile }.should raise_error
Guard::UI.should_not_receive(:error)
lambda { described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
described_class.guardfile_contents.should == valid_guardfile_string
end
it "should use a given file over the default loc" do
fake_guardfile('/abc/Guardfile', "guard :foo")
Guard::UI.should_not_receive(:error)
lambda { described_class.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
described_class.guardfile_contents.should == "guard :foo"
end
it "should use a default file if no other options are given" do
fake_guardfile(@local_guardfile_path, "guard :bar")
Guard::UI.should_not_receive(:error)
lambda { described_class.evaluate_guardfile }.should_not raise_error
described_class.guardfile_contents.should == "guard :bar"
end
it "should use a string over any other method" do
fake_guardfile('/abc/Guardfile', "guard :foo")
fake_guardfile(@local_guardfile_path, "guard :bar")
Guard::UI.should_not_receive(:error)
lambda { described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
described_class.guardfile_contents.should == valid_guardfile_string
end
it "should use the given Guardfile over default Guardfile" do
fake_guardfile('/abc/Guardfile', "guard :foo")
fake_guardfile(@local_guardfile_path, "guard :bar")
Guard::UI.should_not_receive(:error)
lambda { described_class.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
described_class.guardfile_contents.should == "guard :foo"
end
it 'should append the user config file if present' do
fake_guardfile('/abc/Guardfile', "guard :foo")
fake_guardfile(@user_config_path, "guard :bar")
Guard::UI.should_not_receive(:error)
lambda { described_class.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
described_class.guardfile_contents_with_user_config.should == "guard :foo\nguard :bar"
end
end end
it "displays an error message when no Guardfile is found" do it "load a guard from the DSL" do
described_class.stub(:guardfile_default_path).and_return("no_guardfile_here") mock_guardfile_content("guard 'test'")
Guard::UI.should_receive(:error).with("No Guardfile found, please create one with `guard init`.")
lambda { described_class.evaluate_guardfile }.should raise_error ::Guard.should_receive(:add_guard).with('test', [], {})
subject.evaluate_guardfile
end end
it "displays an error message when no guard are defined in Guardfile" do it "receive watchers when specified" do
::Guard::Dsl.stub!(:instance_eval_guardfile) mock_guardfile_content("
::Guard.stub!(:guards).and_return([]) guard 'test' do
Guard::UI.should_receive(:error) watch('a') { 'b' }
described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) watch('c')
end
describe "correctly reads data from its valid data source" do
before(:each) { ::Guard::Dsl.stub!(:instance_eval_guardfile) }
disable_user_config
it "reads correctly from a string" do
lambda { described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
described_class.guardfile_contents.should == valid_guardfile_string
end
it "reads correctly from a Guardfile" do
fake_guardfile('/abc/Guardfile', "guard :foo" )
lambda { described_class.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
described_class.guardfile_contents.should == "guard :foo"
end
it "reads correctly from a Guardfile" do
fake_guardfile(File.join(Dir.pwd, 'Guardfile'), valid_guardfile_string)
lambda { described_class.evaluate_guardfile }.should_not raise_error
described_class.guardfile_contents.should == valid_guardfile_string
end
end
describe "correctly throws errors when initializing with invalid data" do
before(:each) { ::Guard::Dsl.stub!(:instance_eval_guardfile) }
it "raises error when there's a problem reading a file" do
File.stub!(:exist?).with('/def/Guardfile') { true }
File.stub!(:read).with('/def/Guardfile') { raise Errno::EACCES.new("permission error") }
Guard::UI.should_receive(:error).with(/^Error reading file/)
lambda { described_class.evaluate_guardfile(:guardfile => '/def/Guardfile') }.should raise_error
end
it "raises error when given Guardfile doesn't exist" do
File.stub!(:exist?).with('/def/Guardfile') { false }
Guard::UI.should_receive(:error).with(/No Guardfile exists at/)
lambda { described_class.evaluate_guardfile(:guardfile => '/def/Guardfile') }.should raise_error
end
it "raises error when resorting to use default, finds no default" do
File.stub!(:exist?).with(@local_guardfile_path) { false }
File.stub!(:exist?).with(@home_guardfile_path) { false }
Guard::UI.should_receive(:error).with("No Guardfile found, please create one with `guard init`.")
lambda { described_class.evaluate_guardfile }.should raise_error
end
it "raises error when guardfile_content ends up empty or nil" do
Guard::UI.should_receive(:error).with(/The command file/)
lambda { described_class.evaluate_guardfile(:guardfile_contents => "") }.should raise_error
end
it "doesn't raise error when guardfile_content is nil (skipped)" do
Guard::UI.should_not_receive(:error)
lambda { described_class.evaluate_guardfile(:guardfile_contents => nil) }.should_not raise_error
end
end
it "displays an error message when Guardfile is not valid" do
Guard::UI.should_receive(:error).with(/Invalid Guardfile, original error is:/)
lambda { described_class.evaluate_guardfile(:guardfile_contents => invalid_guardfile_string ) }.should raise_error
end
describe ".reevaluate_guardfile" do
before(:each) { ::Guard::Dsl.stub!(:instance_eval_guardfile) }
it "resets already definded guards before calling evaluate_guardfile" do
Guard::Notifier.turn_off
described_class.evaluate_guardfile(:guardfile_contents => invalid_guardfile_string)
::Guard.guards.should_not be_empty
::Guard::Dsl.should_receive(:evaluate_guardfile)
described_class.reevaluate_guardfile
::Guard.guards.should be_empty
end
end
describe ".guardfile_default_path" do
let(:local_path) { File.join(Dir.pwd, 'Guardfile') }
let(:user_path) { File.expand_path(File.join("~", '.Guardfile')) }
before(:each) { File.stub(:exist? => false) }
context "when there is a local Guardfile" do
it "returns the path to the local Guardfile" do
File.stub(:exist?).with(local_path).and_return(true)
described_class.guardfile_default_path.should == local_path
end
end
context "when there is a Guardfile in the user's home directory" do
it "returns the path to the user Guardfile" do
File.stub(:exist?).with(user_path).and_return(true)
described_class.guardfile_default_path.should == user_path
end
end
context "when there's both a local and user Guardfile" do
it "returns the path to the local Guardfile" do
File.stub(:exist?).with(local_path).and_return(true)
File.stub(:exist?).with(user_path).and_return(true)
described_class.guardfile_default_path.should == local_path
end
end
end
describe ".guardfile_include?" do
it "detects a guard specified by a string with double quotes" do
described_class.stub(:guardfile_contents => 'guard "test" {watch("c")}')
described_class.guardfile_include?('test').should be_true
end
it "detects a guard specified by a string with single quote" do
described_class.stub(:guardfile_contents => 'guard \'test\' {watch("c")}')
described_class.guardfile_include?('test').should be_true
end
it "detects a guard specified by a symbol" do
described_class.stub(:guardfile_contents => 'guard :test {watch("c")}')
described_class.guardfile_include?('test').should be_true
end
it "detects a guard wrapped in parentheses" do
described_class.stub(:guardfile_contents => 'guard(:test) {watch("c")}')
described_class.guardfile_include?('test').should be_true
end
end
describe "#ignore_paths" do
disable_user_config
it "adds the paths to the listener's ignore_paths" do
::Guard.stub!(:listener).and_return(mock('Listener'))
::Guard.listener.should_receive(:ignore_paths).and_return(ignore_paths = ['faz'])
described_class.evaluate_guardfile(:guardfile_contents => "ignore_paths 'foo', 'bar'")
ignore_paths.should == ['faz', 'foo', 'bar']
end
end
describe "#group" do
disable_user_config
it "evaluates only the specified string group" do
::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w })
described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
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 })
described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
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 })
described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:x, :y])
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 })
described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
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 })
described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string)
end
end
describe "#guard" do
disable_user_config
it "loads a guard specified as a quoted string from the DSL" do
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default })
described_class.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 })
described_class.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 })
described_class.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 })
described_class.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 })
described_class.evaluate_guardfile(:guardfile_contents => "guard 'test', :opt_a => 1, :opt_b => 'fancy'")
end
end
describe "#watch" do
disable_user_config
it "should receive watchers when specified" do
::Guard.should_receive(:add_guard).with('dummy', anything, anything, { :group => :default }) do |name, watchers, callbacks, options|
watchers.size.should == 2
watchers[0].pattern.should == 'a'
watchers[0].action.call.should == proc { 'b' }.call
watchers[1].pattern.should == 'c'
watchers[1].action.should == nil
end
described_class.evaluate_guardfile(:guardfile_contents => "
guard :dummy do
watch('a') { 'b' }
watch('c')
end") end")
::Guard.should_receive(:add_guard).with('test', anything, {}) do |name, watchers, options|
watchers.size.should == 2
watchers[0].pattern.should == 'a'
watchers[0].action.call.should == proc { 'b' }.call
watchers[1].pattern.should == 'c'
watchers[1].action.should be_nil
end end
subject.evaluate_guardfile
end end
describe "#callback" do it "receive options when specified" do
it "creates callbacks for the guard" do mock_guardfile_content("guard 'test', :opt_a => 1, :opt_b => 'fancy'")
class MyCustomCallback
def self.call(guard_class, event, args) ::Guard.should_receive(:add_guard).with('test', anything, { :opt_a => 1, :opt_b => 'fancy' })
# do nothing subject.evaluate_guardfile
end
end
::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!"
callbacks[1][:events].should == [:start_begin, :run_all_begin]
callbacks[1][:listener].should == MyCustomCallback
end
described_class.evaluate_guardfile(:guardfile_contents => '
guard :dummy do
callback(:start_end) { |guard_class, event, args| "#{guard_class} executed \'#{event}\' hook with #{args}!" }
callback(MyCustomCallback, [:start_begin, :run_all_begin])
end')
end
end end
private private
def fake_guardfile(name, contents) def mock_guardfile_content(content)
File.stub!(:exist?).with(name) { true } File.stub!(:read).with(File.expand_path('../../../Guardfile', __FILE__)) { content }
File.stub!(:read).with(name) { contents }
end
def valid_guardfile_string
"
guard :pow
group :w do
guard :test
end
group :x, :halt_on_fail => true do
guard :rspec
guard :ronn
end
group :y do
guard :less
end
"
end
def invalid_guardfile_string
"Bad Guardfile"
end end
end end

View File

@ -1,19 +0,0 @@
require 'spec_helper'
describe Guard::Group do
describe ".initialize" do
it "accepts a name as a string and provides an accessor for it (returning a symbol)" do
described_class.new('foo').name.should eql :foo
end
it "accepts a name as a symbol and provides an accessor for it (returning a symbol)" do
described_class.new(:foo).name.should eql :foo
end
it "accepts options and provides an accessor for it" do
described_class.new('foo', :halt_on_fail => true).options.should == { :halt_on_fail => true }
end
end
end

View File

@ -1,60 +1,152 @@
require 'spec_helper' require 'spec_helper'
require 'guard/guard'
describe Guard::Guard do describe Guard::Guard do
subject { Guard::Guard }
describe '#initialize' do it "should be initialized with watchers" do
watcher = mock(Guard::Watcher)
it 'assigns the defined watchers' do guard = subject.new([watcher])
watchers = [ Guard::Watcher.new('*') ] guard.watchers.should == [watcher]
guard = Guard::Guard.new(watchers)
guard.watchers.should eql watchers
end
it 'assigns the defined options' do
options = { :a => 1, :b => 2 }
guard = Guard::Guard.new([], options)
guard.options.should eql options
end
context 'with a group in the options' do
it 'assigns the given group' do
options = { :group => :test }
guard = Guard::Guard.new([], options)
guard.group.should eql :test
end
end
context 'without a group in the options' do
it 'assigns a default group' do
options = { }
guard = Guard::Guard.new([], options)
guard.group.should eql :default
end
end
end end
describe '#init' do it "should be initialized with options" do
context 'when the Guard is already in the Guardfile' do watcher = mock(Guard::Watcher)
before { ::Guard::Dsl.stub(:guardfile_include?).and_return true } guard = subject.new([], {:test => true})
guard.options[:test].should be_true
it 'shows an info message' do guard.options[:fake].should be_nil
::Guard::UI.should_receive(:info).with 'Guardfile already includes myguard guard'
Guard::Guard.init('myguard')
end
end
context 'when the Guard is not in the Guardfile' do
before { ::Guard::Dsl.stub(:guardfile_include?).and_return false }
it 'appends the template to the Guardfile' do
File.should_receive(:read).with('Guardfile').and_return 'Guardfile content'
::Guard.should_receive(:locate_guard).with('myguard').and_return '/Users/me/projects/guard-myguard'
File.should_receive(:read).with('/Users/me/projects/guard-myguard/lib/guard/myguard/templates/Guardfile').and_return('Template content')
io = StringIO.new
File.should_receive(:open).with('Guardfile', 'wb').and_yield io
Guard::Guard.init('myguard')
io.string.should eql "Guardfile content\n\nTemplate content\n"
end
end
end end
context "#reload?" do
it "should return true by default" do
subject.new.reload?.should be_true
end
it "should return true with option `:reload => {:disable => false}'" do
guard = subject.new([], :reload => {:disable => false})
guard.reload?.should be_true
end
it "should return true with option `:reload => {}'" do
guard = subject.new([], :reload => {})
guard.reload?.should be_true
end
it "should return true with option `:reload => true'" do
guard = subject.new([], :reload => true)
guard.reload?.should be_true
end
it "should return false with option `:reload => {:disable => true}'" do
guard = subject.new([], :reload => {:disable => true})
guard.reload?.should be_false
end
it "should return false with option `:reload => false'" do
guard = subject.new([], :reload => false)
guard.reload?.should be_false
end
end
context "#reload_at_start?" do
it "should return false by default" do
subject.new.reload_at_start?.should be_false
end
it "should return false with option `:reload => {:at_start => false}'" do
guard = subject.new([], :reload => {:at_start => false})
guard.reload_at_start?.should be_false
end
it "should return false with option `:reload => {}'" do
guard = subject.new([], :reload => {})
guard.reload_at_start?.should be_false
end
it "should return false with option `:reload => true'" do
guard = subject.new([], :reload => true)
guard.reload_at_start?.should be_false
end
it "should return false with option `:reload => false'" do
guard = subject.new([], :reload => false)
guard.reload_at_start?.should be_false
end
it "should return true with option `:reload => {:at_start => true}'" do
guard = subject.new([], :reload => {:at_start => true})
guard.reload_at_start?.should be_true
end
end
context "#run_all?" do
it "should return true by default" do
subject.new.run_all?.should be_true
end
it "should return true with option `:reload => {:disable => false}'" do
guard = subject.new([], :run_all => {:disable => false})
guard.run_all?.should be_true
end
it "should return true with option `:reload => {}'" do
guard = subject.new([], :run_all => {})
guard.run_all?.should be_true
end
it "should return true with option `:reload => true'" do
guard = subject.new([], :run_all => true)
guard.run_all?.should be_true
end
it "should return false with option `:reload => {:disable => true}'" do
guard = subject.new([], :run_all => {:disable => true})
guard.run_all?.should be_false
end
it "should return false with option `:reload => false'" do
guard = subject.new([], :run_all => false)
guard.run_all?.should be_false
end
end
context "#run_all_at_start?" do
it "should return false by default" do
subject.new.run_all_at_start?.should be_false
end
it "should return false with option `:reload => {:at_start => false}'" do
guard = subject.new([], :run_all => {:at_start => false})
guard.run_all_at_start?.should be_false
end
it "should return false with option `:reload => {}'" do
guard = subject.new([], :run_all => {})
guard.run_all_at_start?.should be_false
end
it "should return false with option `:reload => true'" do
guard = subject.new([], :run_all => true)
guard.run_all_at_start?.should be_false
end
it "should return false with option `:reload => false'" do
guard = subject.new([], :run_all => false)
guard.run_all_at_start?.should be_false
end
it "should return true with option `:reload => {:at_start => true}'" do
guard = subject.new([], :run_all => {:at_start => true})
guard.run_all_at_start?.should be_true
end
end
end end

View File

@ -1,89 +0,0 @@
require 'spec_helper'
require 'guard/guard'
describe Guard::Hook do
class Guard::Dummy < Guard::Guard; end
let(:guard_class) { ::Guard::Dummy }
let(:listener) { double('listener').as_null_object }
after { described_class.reset_callbacks! }
describe "--module methods--" do
before { described_class.add_callback(listener, guard_class, :start_begin) }
describe ".add_callback" do
it "can add a single callback" do
described_class.has_callback?(listener, guard_class, :start_begin).should be_true
end
it "can add multiple callbacks" do
described_class.add_callback(listener, guard_class, [:event1, :event2])
described_class.has_callback?(listener, guard_class, :event1).should be_true
described_class.has_callback?(listener, guard_class, :event2).should be_true
end
end
describe ".notify" do
it "sends :call to the given Guard class's callbacks" do
listener.should_receive(:call).with(guard_class, :start_begin, "args")
described_class.notify(guard_class, :start_begin, "args")
end
it "runs only the given callbacks" do
listener2 = double('listener2')
described_class.add_callback(listener2, guard_class, :start_end)
listener2.should_not_receive(:call).with(guard_class, :start_end)
described_class.notify(guard_class, :start_begin)
end
it "runs callbacks only for the guard given" do
guard2_class = double('Guard::Dummy2').class
described_class.add_callback(listener, guard2_class, :start_begin)
listener.should_not_receive(:call).with(guard2_class, :start_begin)
described_class.notify(guard_class, :start_begin)
end
end
end
describe "#hook" do
before(:all) do
guard_class.class_eval do
def start
hook "my_hook"
end
def run_all
hook :begin
hook :end
end
def stop
hook :begin, 'args'
hook 'special_sauce', 'first_arg', 'second_arg'
end
end
@guard = guard_class.new
end
it "calls Guard::Hook.notify" do
Guard::Hook.should_receive(:notify).with(guard_class, :run_all_begin)
Guard::Hook.should_receive(:notify).with(guard_class, :run_all_end)
@guard.run_all
end
it "if passed a string parameter, will use that for the hook name" do
Guard::Hook.should_receive(:notify).with(guard_class, :my_hook)
@guard.start
end
it "accepts extra args" do
Guard::Hook.should_receive(:notify).with(guard_class, :stop_begin, 'args')
Guard::Hook.should_receive(:notify).with(guard_class, :special_sauce, 'first_arg', 'second_arg')
@guard.stop
end
end
end

View File

@ -1,6 +0,0 @@
require 'spec_helper'
describe Guard::Interactor do
subject { Guard::Interactor.new }
end

View File

@ -1,291 +1,34 @@
require 'spec_helper' require 'spec_helper'
describe Guard::Listener do describe Guard::Listener do
subject { described_class }
describe '.select_and_init' do
before(:each) { @target_os = RbConfig::CONFIG['target_os'] } describe "init" do
after(:each) { RbConfig::CONFIG['target_os'] = @target_os }
before(:each) { @target_os = Config::CONFIG['target_os'] }
it 'uses the Darwin listener on Mac OS X' do after(:each) { Config::CONFIG['target_os'] = @target_os }
RbConfig::CONFIG['target_os'] = 'darwin10.4.0'
it "should use darwin listener on Mac OS X" do
Config::CONFIG['target_os'] = 'darwin10.4.0'
Guard::Darwin.stub(:usable?).and_return(true) Guard::Darwin.stub(:usable?).and_return(true)
Guard::Darwin.should_receive(:new) Guard::Darwin.should_receive(:new)
described_class.select_and_init subject.init
end end
it 'uses the Windows listener on Windows' do it "should use polling listener on Windows" do
RbConfig::CONFIG['target_os'] = 'mingw' Config::CONFIG['target_os'] = 'win32'
Guard::Windows.stub(:usable?).and_return(true) Guard::Polling.stub(:usable?).and_return(true)
Guard::Windows.should_receive(:new) Guard::Polling.should_receive(:new)
described_class.select_and_init subject.init
end end
it 'uses the Linux listener on Linux' do it "should use linux listener on Linux" do
RbConfig::CONFIG['target_os'] = 'linux' Config::CONFIG['target_os'] = 'linux'
Guard::Linux.stub(:usable?).and_return(true) Guard::Linux.stub(:usable?).and_return(true)
Guard::Linux.should_receive(:new) Guard::Linux.should_receive(:new)
described_class.select_and_init subject.init
end
it 'forwards its arguments to the constructor' do
described_class.stub!(:mac?).and_return(true)
Guard::Darwin.stub!(:usable?).and_return(true)
path, opts = 'path', { :foo => 23 }
Guard::Darwin.should_receive(:new).with(path, opts).and_return(true)
described_class.select_and_init(path, opts)
end end
end end
describe '#all_files' do end
subject { described_class.new(@fixture_path) }
it 'should return all files' do
subject.all_files.should =~
Dir.glob("#{ @fixture_path }/**/*", File::FNM_DOTMATCH).select { |file| File.file?(file) }
end
end
describe '#relativize_paths' do
subject { described_class.new('/tmp') }
let(:paths) { %w( /tmp/a /tmp/a/b /tmp/a.b/c.d ) }
it 'should relativize paths to the configured directory' do
subject.relativize_paths(paths).should =~ %w( a a/b a.b/c.d )
end
context 'when set to false' do
subject { described_class.new('/tmp', :relativize_paths => false) }
it 'can be disabled' do
subject.relativize_paths(paths).should eql paths
end
end
end
describe '#update_last_event' do
subject { described_class.new }
it 'updates the last event to the current time' do
time = Time.now
subject.update_last_event
subject.instance_variable_get(:@last_event).to_i.should >= time.to_i
end
end
describe '#modified_files' do
subject { described_class.new }
let(:file1) { fixture('folder1', 'file1.txt') }
let(:file2) { fixture('folder1', 'folder2', 'file2.txt') }
let(:file3) { fixture('folder1', 'deletedfile1.txt') }
let(:file4) { fixture('folder1', 'movedfile1.txt') }
let(:file5) { fixture('folder1', 'folder2', 'movedfile1.txt') }
before { listen_to subject }
context 'without the :all option' do
it 'finds modified files only in the directory supplied' do
watch do
FileUtils.touch([file1, file2, file3])
subject.modified_files([fixture('folder1')], {}).should =~
['spec/fixtures/folder1/deletedfile1.txt', 'spec/fixtures/folder1/file1.txt']
end
end
end
context 'with the :all options' do
it 'finds modified files within subdirectories' do
watch do
FileUtils.touch([file1, file2, file3])
subject.modified_files([fixture('folder1')], { :all => true }).should =~
['spec/fixtures/folder1/deletedfile1.txt',
'spec/fixtures/folder1/file1.txt',
'spec/fixtures/folder1/folder2/file2.txt']
end
end
end
context 'without updating the content' do
it 'ignores the files for the second time' do
watch do
FileUtils.touch([file1, file2, file3])
subject.modified_files([fixture('folder1')], {}).should =~
['spec/fixtures/folder1/deletedfile1.txt', 'spec/fixtures/folder1/file1.txt']
subject.update_last_event
FileUtils.touch([file1, file2, file3])
subject.modified_files([fixture('folder1')], {}).should be_empty
end
end
end
context 'with content that has changed' do
after { File.open(file1, 'w') { |f| f.write('') } }
it 'identifies the files for the second time' do
watch do
FileUtils.touch([file1, file2, file3])
subject.modified_files([fixture('folder1')], {}).should =~
['spec/fixtures/folder1/deletedfile1.txt', 'spec/fixtures/folder1/file1.txt']
subject.update_last_event
FileUtils.touch([file2, file3])
File.open(file1, 'w') { |f| f.write('changed content') }
subject.modified_files([fixture('folder1')], {}).should =~
['spec/fixtures/folder1/file1.txt']
end
end
end
context 'without the :watch_all_modifications option' do
after { FileUtils.touch(file3) }
it 'defaults to false' do
subject.instance_variable_get(:@watch_all_modifications).should eql false
end
context 'for a deleted file' do
after { FileUtils.touch(file3) }
it 'does not catch the deletion' do
File.exists?(file3).should be_true
watch do
FileUtils.remove_file(file3)
end
subject.modified_files([fixture('folder1')], {}).should =~ []
end
end
context 'for a moved file' do
after { FileUtils.move(file4, file1) }
it 'does not catch the move' do
File.exists?(file1).should be_true
File.exists?(file4).should be_false
watch do
FileUtils.move(file1, file4)
end
subject.modified_files([@fixture_path.join('folder1')], {}).should =~ []
end
end
end
context 'with the :watch_all_modifications option' do
subject { described_class.new(Dir.pwd, :watch_all_modifications => true) }
before do
subject.timestamp_files
subject.update_last_event
end
it 'should be true when set' do
subject.instance_variable_get(:@watch_all_modifications).should eql true
end
context 'for a deleted file' do
after { FileUtils.touch(file3) }
it 'catches the deletion' do
File.exists?(file3).should be_true
watch do
FileUtils.remove_file(file3)
end
subject.modified_files([fixture('folder1')], {}).should =~
['!spec/fixtures/folder1/deletedfile1.txt']
end
end
context 'for a moved file' do
after { FileUtils.move(file4, file1) }
it 'catches the move' do
File.exists?(file1).should be_true
File.exists?(file4).should be_false
watch do
FileUtils.move(file1, file4)
end
subject.modified_files([@fixture_path.join('folder1')], {}).should =~
['!spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/movedfile1.txt']
end
end
end
end
describe 'working directory' do
context 'unspecified' do
subject { described_class.new }
it 'defaults to Dir.pwd' do
subject.instance_variable_get(:@directory).should eql Dir.pwd
end
it 'can be not changed' do
subject.should_not respond_to(:directory=)
end
end
context 'specified as first argument to ::new' do
let(:working_directory) { fixture('folder1') }
subject { described_class.new working_directory }
before { listen_to subject }
it 'can be inspected' do
subject.instance_variable_get(:@directory).should eql working_directory.to_s
end
it 'can be not changed' do
subject.should_not respond_to(:directory=)
end
it 'will be used to watch' do
subject.should_receive(:watch).with(working_directory.to_s)
start
stop
end
end
end
describe '#ignore_paths' do
it 'defaults to the default ignore paths' do
described_class.new.ignore_paths.should == Guard::Listener::DEFAULT_IGNORE_PATHS
end
it 'can be added to via :ignore_paths option' do
listener = described_class.new 'path', :ignore_paths => ['foo', 'bar']
listener.ignore_paths.should include('foo', 'bar')
end
end
describe '#exclude_ignored_paths [<dirs>]' do
let(:ignore_paths) { nil }
subject { described_class.new(@fixture_path, { :ignore_paths => ignore_paths }) }
it 'returns children of <dirs>' do
subject.exclude_ignored_paths(['spec/fixtures']).should =~
['spec/fixtures/.dotfile', 'spec/fixtures/folder1', 'spec/fixtures/Guardfile']
end
describe 'when ignore_paths set to some of <dirs> children' do
let(:ignore_paths) { ['Guardfile', '.dotfile'] }
it 'excludes the ignored paths' do
subject.exclude_ignored_paths(['spec/fixtures']).should =~ ['spec/fixtures/folder1']
end
end
end
end

View File

@ -2,26 +2,72 @@ require 'spec_helper'
require 'guard/listeners/darwin' require 'guard/listeners/darwin'
describe Guard::Darwin do describe Guard::Darwin do
subject { Guard::Darwin }
if windows?
it "isn't usable on windows" do
described_class.should_not be_usable
end
end
if linux? if linux?
it "isn't usable on linux" do it "should not be usable on linux" do
described_class.should_not be_usable subject.should_not be_usable
end end
end end
if mac? && Guard::Darwin.usable? if mac?
it "is usable on 10.6" do it "should be usable on 10.6" do
described_class.should be_usable subject.should be_usable
end
describe "watch" do
before(:each) do
@results = []
@listener = Guard::Darwin.new
@listener.on_change do |files|
@results += files
end
end
it "should catch new file" do
file = @fixture_path.join("newfile.rb")
File.exists?(file).should be_false
start
FileUtils.touch file
stop
File.delete file
@results.should == ['spec/fixtures/newfile.rb']
end
it "should catch file update" do
file = @fixture_path.join("folder1/file1.txt")
File.exists?(file).should be_true
start
FileUtils.touch file
stop
@results.should == ['spec/fixtures/folder1/file1.txt']
end
it "should catch files update" do
file1 = @fixture_path.join("folder1/file1.txt")
file2 = @fixture_path.join("folder1/folder2/file2.txt")
File.exists?(file1).should be_true
File.exists?(file2).should be_true
start
FileUtils.touch file1
FileUtils.touch file2
stop
@results.should == ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/folder2/file2.txt']
end
end end
it_should_behave_like "a listener that reacts to #on_change"
it_should_behave_like "a listener scoped to a specific directory"
end end
private
def start
sleep 1
Thread.new { @listener.start }
sleep 1
end
def stop
sleep 1
@listener.stop
end
end end

View File

@ -3,35 +3,30 @@ require 'fileutils'
require 'guard/listeners/linux' require 'guard/listeners/linux'
describe Guard::Linux do describe Guard::Linux do
subject { Guard::Linux }
if mac? if mac?
it "isn't usable on 10.6" do it "should not be usable on 10.6" do
described_class.should_not be_usable subject.should_not be_usable
end end
end end
if windows? if linux?
it "isn't usable on windows" do it "should be usable on linux" do
described_class.should_not be_usable subject.should be_usable
end
end
if linux? && Guard::Linux.usable?
it "is usable on linux" do
described_class.should be_usable
end end
describe "#start", :long_running => true do describe "start" do
before(:each) do before(:each) do
@listener = Guard::Linux.new @listener = Guard::Linux.new
end end
it "calls watch_change on the first start" do it "should call watch_change if first start" do
@listener.should_receive(:watch_change) @listener.should_receive(:watch_change)
start start
end end
it "doesn't call watch_change on subsequent starts after a stop" do it "should not call watch_change if start after stop" do
@listener.stub!(:stop) @listener.stub!(:stop)
start start
stop stop
@ -42,35 +37,94 @@ describe Guard::Linux do
stop stop
@listener.should_not be_watch_change @listener.should_not be_watch_change
end end
end end
describe "watch" do
before(:each) do
@results = []
@listener = Guard::Linux.new
@listener.on_change do |files|
@results += files
end
end
it "should catch new file" do
file = @fixture_path.join("newfile.rb")
File.exists?(file).should be_false
start
FileUtils.touch file
stop
File.delete file
@results.should == ['spec/fixtures/newfile.rb']
end
it "should catch file update" do
file = @fixture_path.join("folder1/file1.txt")
File.exists?(file).should be_true
start
File.open(file, 'w') {|f| f.write('') }
stop
@results.should == ['spec/fixtures/folder1/file1.txt']
end
it "should catch files update" do
file1 = @fixture_path.join("folder1/file1.txt")
file2 = @fixture_path.join("folder1/folder2/file2.txt")
File.exists?(file1).should be_true
File.exists?(file2).should be_true
start
File.open(file1, 'w') {|f| f.write('') }
File.open(file2, 'w') {|f| f.write('') }
stop
@results.should == ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/folder2/file2.txt']
end
it "should catch deleted file" do
file = @fixture_path.join("folder1/file1.txt")
File.exists?(file).should be_true
start
File.delete file
stop
FileUtils.touch file
@results.should == ['spec/fixtures/folder1/file1.txt']
end
it "should catch moved file" do
file1 = @fixture_path.join("folder1/file1.txt")
file2 = @fixture_path.join("folder1/movedfile1.txt")
File.exists?(file1).should be_true
File.exists?(file2).should be_false
start
FileUtils.mv file1, file2
stop
FileUtils.mv file2, file1
@results.should == ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/movedfile1.txt']
end
it_should_behave_like "a listener that reacts to #on_change" it "should not process change if stopped" do
it_should_behave_like "a listener scoped to a specific directory" file = @fixture_path.join("folder1/file1.txt")
File.exists?(file).should be_true
# Fun fact: FileUtils.touch seems not to be enough on Linux to trigger a modify event start
@listener.inotify.should_not_receive(:process)
it "catches modified files with glib saving routine (like Vim, Emacs or Gedit)" do stop
@listener = described_class.new File.open(file, 'w') {|f| f.write('') }
record_results end
file = @fixture_path.join("folder1/file1.txt")
File.exists?(file).should be_true
start
File.open(file, 'r+').close
FileUtils.touch(file)
stop
results.should =~ ['spec/fixtures/folder1/file1.txt']
end
it "doesn't process a change when it is stopped" do
@listener = described_class.new
record_results
file = @fixture_path.join("folder1/file1.txt")
File.exists?(file).should be_true
start
@listener.instance_variable_get(:@inotify).should_not_receive(:process)
stop
File.open(file, 'w') {|f| f.write('') }
end end
end end
private
def start
sleep 1
Thread.new { @listener.start }
sleep 1
end
def stop
sleep 1
@listener.stop
sleep 1
end
end end

View File

@ -2,8 +2,56 @@ require 'spec_helper'
require 'guard/listeners/polling' require 'guard/listeners/polling'
describe Guard::Polling do describe Guard::Polling do
it_should_behave_like "a listener that reacts to #on_change" before(:each) do
it_should_behave_like "a listener scoped to a specific directory" @results = []
@listener = Guard::Polling.new
@listener.on_change do |files|
@results += files
end
end
it "should catch new file" do
file = @fixture_path.join("newfile.rb")
File.exists?(file).should be_false
start
FileUtils.touch file
stop
File.delete file
@results.should == ['spec/fixtures/newfile.rb']
end
it "should catch file update" do
file = @fixture_path.join("folder1/file1.txt")
File.exists?(file).should be_true
start
FileUtils.touch file
stop
@results.should == ['spec/fixtures/folder1/file1.txt']
end
it "should catch files update" do
file1 = @fixture_path.join("folder1/file1.txt")
file2 = @fixture_path.join("folder1/folder2/file2.txt")
File.exists?(file1).should be_true
File.exists?(file2).should be_true
start
FileUtils.touch file1
FileUtils.touch file2
stop
@results.sort.should == ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/folder2/file2.txt']
end
private
def start
Thread.new { @listener.start }
sleep 1
end
def stop
sleep 1
@listener.stop
end
end end

View File

@ -1,27 +0,0 @@
require 'spec_helper'
require 'guard/listeners/windows'
describe Guard::Windows do
if linux?
it "isn't usable on linux" do
described_class.should_not be_usable
end
end
if mac?
it "isn't usable on Mac" do
described_class.should_not be_usable
end
end
if windows?
it "is usable on Windows 2000 and later" do
described_class.should be_usable
end
it_should_behave_like "a listener that reacts to #on_change"
it_should_behave_like "a listener scoped to a specific directory"
end
end

View File

@ -1,428 +1,36 @@
require 'spec_helper' require 'spec_helper'
describe Guard::Notifier do describe Guard::Notifier do
subject { Guard::Notifier }
describe ".turn_off" do
before do describe "notify" do
ENV["GUARD_NOTIFY"] = 'true' before(:each) { ENV["GUARD_ENV"] = 'special_test' }
described_class.turn_off
end if mac?
require 'growl'
it "disables the notifications" do it "should use Growl on Mac OS X" do
ENV["GUARD_NOTIFY"].should eql 'false' Growl.should_receive(:notify).with("great",
end :title => "Guard",
end :icon => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
:name => "Guard"
describe ".turn_on" do )
context "on Mac OS" do subject.notify 'great', :title => 'Guard'
before do
RbConfig::CONFIG.should_receive(:[]).with('target_os').and_return 'darwin'
end
context "with the GrowlNotify library available" do
before do
class ::GrowlNotify
class GrowlNotFound < Exception; end
def self.config ; end
end
end
it "loads the library and enables the notifications" do
described_class.should_receive(:require).with('growl_notify').and_return true
GrowlNotify.should_receive(:application_name).and_return ''
described_class.turn_on
described_class.should be_enabled
end
it "should respond properly to a GrowlNotify exception" do
::GrowlNotify.should_receive(:config).and_raise ::GrowlNotify::GrowlNotFound
::GrowlNotify.should_receive(:application_name).and_return ''
::Guard::UI.should_receive(:info)
described_class.should_receive(:require).with('growl_notify').and_return true
described_class.turn_on
described_class.should_not be_enabled
described_class.growl_library.should eql :growl_notify
end
after do
Object.send(:remove_const, :GrowlNotify)
end
end
context "with the GNTP library available" do
before do
class ::GNTP
def initialize(app); end
def register(config) ; end
end
end
it "loads the library and enables the notifications" do
described_class.should_receive(:require).with('growl_notify').and_raise LoadError
described_class.should_receive(:require).with('ruby_gntp').and_return true
described_class.turn_on
described_class.should be_enabled
described_class.growl_library.should eql :ruby_gntp
end
after do
Object.send(:remove_const, :GNTP)
end
end
context "with the Growl library available" do
it "loads the library and enables the notifications" do
described_class.should_receive(:require).with('growl_notify').and_raise LoadError
described_class.should_receive(:require).with('ruby_gntp').and_raise LoadError
described_class.should_receive(:require).with('growl').and_return true
described_class.turn_on
described_class.should be_enabled
described_class.growl_library.should eql :growl
end
end
context "without a Growl library available" do
it "disables the notifications" do
described_class.should_receive(:require).with('growl_notify').and_raise LoadError
described_class.should_receive(:require).with('ruby_gntp').and_raise LoadError
described_class.should_receive(:require).with('growl').and_raise LoadError
described_class.turn_on
described_class.should_not be_enabled
described_class.growl_library.should be nil
end
end end
end end
context "on Linux" do if linux?
before do require 'libnotify'
RbConfig::CONFIG.should_receive(:[]).with('target_os').and_return 'linux' it "should use Libnotify on Linux" do
end
context "with the Libnotify library available" do
it "loads the library and enables the notifications" do
described_class.should_receive(:require).with('libnotify').and_return true
described_class.turn_on
described_class.should be_enabled
end
end
context "without the Libnotify library available" do
it "disables the notifications" do
described_class.should_receive(:require).with('libnotify').and_raise LoadError
described_class.turn_on
described_class.should_not be_enabled
end
end
end
context "on Windows" do
before do
RbConfig::CONFIG.should_receive(:[]).with('target_os').and_return 'mswin'
end
context "with the rb-notifu library available" do
it "loads the library and enables the notifications" do
described_class.should_receive(:require).with('rb-notifu').and_return true
described_class.turn_on
described_class.should be_enabled
end
end
context "without the rb-notify library available" do
it "disables the notifications" do
described_class.should_receive(:require).with('rb-notifu').and_raise LoadError
described_class.turn_on
described_class.should_not be_enabled
end
end
end
end
describe ".notify" do
before { described_class.stub(:enabled?).and_return(true) }
context "on Mac OS" do
before do
RbConfig::CONFIG.stub(:[]).and_return 'darwin'
described_class.stub(:require_growl)
end
context 'with growl gem' do
before do
Object.send(:remove_const, :Growl) if defined?(Growl)
Growl = Object.new
described_class.growl_library = :growl
end
after do
Object.send(:remove_const, :Growl)
end
it "passes the notification to Growl" do
Growl.should_receive(:notify).with("great",
:title => "Guard",
:icon => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
:name => "Guard"
)
described_class.notify 'great', :title => 'Guard'
end
it "don't passes the notification to Growl if library is not available" do
Growl.should_not_receive(:notify)
described_class.growl_library = nil
described_class.should_receive(:enabled?).and_return(false)
described_class.notify 'great', :title => 'Guard'
end
it "allows additional notification options" do
Growl.should_receive(:notify).with("great",
:title => "Guard",
:icon => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
:name => "Guard",
:priority => 1
)
described_class.notify 'great', :title => 'Guard', :priority => 1
end
it "allows to overwrite a default notification option" do
Growl.should_receive(:notify).with("great",
:title => "Guard",
:icon => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
:name => "Guard-Cucumber"
)
described_class.notify 'great', :title => 'Guard', :name => "Guard-Cucumber"
end
end
context 'with growl_notify gem' do
before do
Object.send(:remove_const, :GrowlNotify) if defined?(GrowlNotify)
GrowlNotify = Object.new
described_class.growl_library = :growl_notify
end
after do
Object.send(:remove_const, :GrowlNotify)
end
it "passes the notification to Growl" do
GrowlNotify.should_receive(:send_notification).with(
:title => "Guard",
:icon => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
:application_name => "Guard",
:description => 'great'
)
described_class.notify 'great', :title => 'Guard'
end
it "don't passes the notification to Growl if library is not available" do
GrowlNotify.should_not_receive(:send_notification)
described_class.growl_library = nil
described_class.should_receive(:enabled?).and_return(false)
described_class.notify 'great', :title => 'Guard'
end
it "allows additional notification options" do
GrowlNotify.should_receive(:send_notification).with(
:title => "Guard",
:icon => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
:application_name => "Guard",
:description => 'great',
:priority => 1
)
described_class.notify 'great', :title => 'Guard', :priority => 1
end
it "throws out the application name since Guard should only use one Growl App Name while running" do
GrowlNotify.should_receive(:send_notification).with(
:title => "Guard",
:icon => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
:application_name => "Guard",
:description => 'great'
)
described_class.notify 'great', :title => 'Guard', :name => "Guard-Cucumber"
end
end
context 'with ruby_gntp gem' do
before do
described_class.growl_library = :ruby_gntp
described_class.gntp = Object.new
end
it "passes a success notification to Ruby GNTP" do
described_class.gntp.should_receive(:notify).with(
:name => "success",
:text => 'great',
:title => "Guard",
:icon => 'file://' + Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s
)
described_class.notify 'great', :title => 'Guard'
end
it "passes a pending notification to Ruby GNTP" do
described_class.gntp.should_receive(:notify).with(
:name => "pending",
:text => 'great',
:title => "Guard",
:icon => 'file://' + Pathname.new(File.dirname(__FILE__)).join('../../images/pending.png').to_s
)
described_class.notify 'great', :title => 'Guard', :image => :pending
end
it "passes a failure notification to Ruby GNTP" do
described_class.gntp.should_receive(:notify).with(
:name => "failed",
:text => 'great',
:title => "Guard",
:icon => 'file://' + Pathname.new(File.dirname(__FILE__)).join('../../images/failed.png').to_s
)
described_class.notify 'great', :title => 'Guard', :image => :failed
end
it "passes a general notification to Ruby GNTP" do
described_class.gntp.should_receive(:notify).with(
:name => "notify",
:text => 'great',
:title => "Guard",
:icon => 'file:///path/to/custom.png'
)
described_class.notify 'great', :title => 'Guard', :image => '/path/to/custom.png'
end
it "don't passes the notification to Ruby GNTP if library is not available" do
described_class.gntp.should_not_receive(:notify)
described_class.growl_library = nil
described_class.should_receive(:enabled?).and_return(false)
described_class.notify 'great', :title => 'Guard'
end
it "allows additional notification options" do
described_class.gntp.should_receive(:notify).with(
:name => "success",
:text => 'great',
:title => "Guard",
:icon => 'file://' + Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
:sticky => true
)
described_class.notify 'great', :title => 'Guard', :sticky => true
end
end
end
context "on Linux" do
before do
RbConfig::CONFIG.stub(:[]).and_return 'linux'
described_class.stub(:require_libnotify)
Object.send(:remove_const, :Libnotify) if defined?(Libnotify)
Libnotify = Object.new
end
after do
Object.send(:remove_const, :Libnotify)
end
it "passes the notification to Libnotify" do
Libnotify.should_receive(:show).with( Libnotify.should_receive(:show).with(
:body => "great", :body => "great",
:summary => 'Guard', :summary => 'Guard',
:icon_path => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s, :icon_path => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s
:transient => true
) )
described_class.notify 'great', :title => 'Guard' subject.notify 'great', :title => 'Guard'
end
it "don't passes the notification to Libnotify if library is not available" do
Libnotify.should_not_receive(:show)
described_class.should_receive(:enabled?).and_return(false)
described_class.notify 'great', :title => 'Guard'
end
it "allows additional notification options" do
Libnotify.should_receive(:show).with(
:body => "great",
:summary => 'Guard',
:icon_path => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
:transient => true,
:urgency => :critical
)
described_class.notify 'great', :title => 'Guard', :urgency => :critical
end
it "allows to overwrite a default notification option" do
Libnotify.should_receive(:show).with(
:body => "great",
:summary => 'Guard',
:icon_path => '~/.guard/success.png',
:transient => true
)
described_class.notify 'great', :title => 'Guard', :icon_path => '~/.guard/success.png'
end
end
context "on Windows" do
before do
RbConfig::CONFIG.stub(:[]).and_return 'mswin'
described_class.stub(:require_rbnotifu)
Object.send(:remove_const, :Notifu) if defined?(Notifu)
Notifu = Object.new
end
after do
Object.send(:remove_const, :Notifu)
end
it "passes the notification to rb-notifu" do
Notifu.should_receive(:show).with(
:message => "great",
:title => 'Guard',
:type => :info,
:time => 3
)
described_class.notify 'great', :title => 'Guard'
end
it "don't passes the notification to rb-notifu if library is not available" do
Notifu.should_not_receive(:show)
described_class.should_receive(:enabled?).and_return(false)
described_class.notify 'great', :title => 'Guard'
end
it "allows additional notification options" do
Notifu.should_receive(:show).with(
:message => "great",
:title => 'Guard',
:type => :info,
:time => 3,
:nosound => true
)
described_class.notify 'great', :title => 'Guard', :nosound => true
end
it "allows to overwrite a default notification option" do
Notifu.should_receive(:show).with(
:message => "great",
:title => 'Guard',
:type => :info,
:time => 10
)
described_class.notify 'great', :title => 'Guard', :time => 10
end end
end end
after(:each) { ENV["GUARD_ENV"] = 'test' }
end end
describe ".enabled?" do end
context "when enabled" do
before { ENV["GUARD_NOTIFY"] = 'true' }
it { should be_enabled }
end
context "when disabled" do
before { ENV["GUARD_NOTIFY"] = 'false' }
it { should_not be_enabled }
end
end
end

View File

@ -1,309 +0,0 @@
require 'spec_helper'
require 'guard/guard'
describe Guard::Watcher do
describe "#initialize" do
it "requires a pattern parameter" do
expect { described_class.new }.to raise_error(ArgumentError)
end
context "with a pattern parameter" do
context "that is a string" do
it "keeps the string pattern unmodified" do
described_class.new('spec_helper.rb').pattern.should == 'spec_helper.rb'
end
end
context "that is a regexp" do
it "keeps the regex pattern unmodified" do
described_class.new(/spec_helper\.rb/).pattern.should == /spec_helper\.rb/
end
end
context "that is a string looking like a regex (deprecated)" do
before(:each) { Guard::UI.should_receive(:info).any_number_of_times }
it "converts the string automatically to a regex" do
described_class.new('^spec_helper.rb').pattern.should == /^spec_helper.rb/
described_class.new('spec_helper.rb$').pattern.should == /spec_helper.rb$/
described_class.new('spec_helper\.rb').pattern.should == /spec_helper\.rb/
described_class.new('.*_spec.rb').pattern.should == /.*_spec.rb/
end
end
end
end
describe "#action" do
it "sets the action to nothing by default" do
described_class.new(/spec_helper\.rb/).action.should be_nil
end
it "sets the action to the supplied block" do
action = lambda { |m| "spec/#{m[1]}_spec.rb" }
described_class.new(%r{^lib/(.*).rb}, action).action.should == action
end
end
describe ".match_files" do
before(:all) do
@guard = Guard::Guard.new
@guard_any_return = Guard::Guard.new
@guard_any_return.options[:any_return] = true
end
context "with a watcher without action" do
context "that is a regex pattern" do
before(:all) { @guard.watchers = [described_class.new(/.*_spec\.rb/)] }
it "returns the paths that matches the regex" do
described_class.match_files(@guard, ['guard_rocks_spec.rb', 'guard_rocks.rb']).should == ['guard_rocks_spec.rb']
end
end
context "that is a string pattern" do
before(:all) { @guard.watchers = [described_class.new('guard_rocks_spec.rb')] }
it "returns the path that matches the string" do
described_class.match_files(@guard, ['guard_rocks_spec.rb', 'guard_rocks.rb']).should == ['guard_rocks_spec.rb']
end
end
end
context "with a watcher action without parameter" do
context "for a watcher that matches file strings" do
before(:all) do
@guard.watchers = [
described_class.new('spec_helper.rb', lambda { 'spec' }),
described_class.new('addition.rb', lambda { 1 + 1 }),
described_class.new('hash.rb', lambda { Hash[:foo, 'bar'] }),
described_class.new('array.rb', lambda { ['foo', 'bar'] }),
described_class.new('blank.rb', lambda { '' }),
described_class.new(/^uptime\.rb/, lambda { `uptime > /dev/null` })
]
end
it "returns a single file specified within the action" do
described_class.match_files(@guard, ['spec_helper.rb']).should == ['spec']
end
it "returns multiple files specified within the action" do
described_class.match_files(@guard, ['hash.rb']).should == ['foo', 'bar']
end
it "returns multiple files by combining the results of different actions" do
described_class.match_files(@guard, ['spec_helper.rb', 'array.rb']).should == ['spec', 'foo', 'bar']
end
it "returns nothing if the action returns something other than a string or an array of strings" do
described_class.match_files(@guard, ['addition.rb']).should == []
end
it "returns nothing if the action response is empty" do
described_class.match_files(@guard, ['blank.rb']).should == []
end
it "returns nothing if the action returns nothing" do
described_class.match_files(@guard, ['uptime.rb']).should == []
end
end
context 'for a watcher that matches information objects' do
before(:all) do
@guard_any_return.watchers = [
described_class.new('spec_helper.rb', lambda { 'spec' }),
described_class.new('addition.rb', lambda { 1 + 1 }),
described_class.new('hash.rb', lambda { Hash[:foo, 'bar'] }),
described_class.new('array.rb', lambda { ['foo', 'bar'] }),
described_class.new('blank.rb', lambda { '' }),
described_class.new(/^uptime\.rb/, lambda { `uptime > /dev/null` })
]
end
it "returns a single file specified within the action" do
described_class.match_files(@guard_any_return, ['spec_helper.rb']).class.should == Array
described_class.match_files(@guard_any_return, ['spec_helper.rb']).empty?.should == false
end
it "returns multiple files specified within the action" do
described_class.match_files(@guard_any_return, ['hash.rb']).should == [{:foo => 'bar'}]
end
it "returns multiple files by combining the results of different actions" do
described_class.match_files(@guard_any_return, ['spec_helper.rb', 'array.rb']).should == ['spec', ['foo', 'bar']]
end
it "returns the evaluated addition argument in an array" do
described_class.match_files(@guard_any_return, ['addition.rb']).class.should == Array
described_class.match_files(@guard_any_return, ['addition.rb'])[0].should == 2
end
it "returns nothing if the action response is empty string" do
described_class.match_files(@guard_any_return, ['blank.rb']).should == ['']
end
it "returns nothing if the action returns empty string" do
described_class.match_files(@guard_any_return, ['uptime.rb']).should == ['']
end
end
end
context "with a watcher action that takes a parameter" do
context "for a watcher that matches file strings" do
before(:all) do
@guard.watchers = [
described_class.new(%r{lib/(.*)\.rb}, lambda { |m| "spec/#{m[1]}_spec.rb" }),
described_class.new(/addition(.*)\.rb/, lambda { |m| 1 + 1 }),
described_class.new('hash.rb', lambda { |m| Hash[:foo, 'bar'] }),
described_class.new(/array(.*)\.rb/, lambda { |m| ['foo', 'bar'] }),
described_class.new(/blank(.*)\.rb/, lambda { |m| '' }),
described_class.new(/uptime(.*)\.rb/, lambda { |m| `uptime > /dev/null` })
]
end
it "returns a substituted single file specified within the action" do
described_class.match_files(@guard, ['lib/my_wonderful_lib.rb']).should == ['spec/my_wonderful_lib_spec.rb']
end
it "returns multiple files specified within the action" do
described_class.match_files(@guard, ['hash.rb']).should == ['foo', 'bar']
end
it "returns multiple files by combining the results of different actions" do
described_class.match_files(@guard, ['lib/my_wonderful_lib.rb', 'array.rb']).should == ['spec/my_wonderful_lib_spec.rb', 'foo', 'bar']
end
it "returns nothing if the action returns something other than a string or an array of strings" do
described_class.match_files(@guard, ['addition.rb']).should == []
end
it "returns nothing if the action response is empty" do
described_class.match_files(@guard, ['blank.rb']).should == []
end
it "returns nothing if the action returns nothing" do
described_class.match_files(@guard, ['uptime.rb']).should == []
end
end
context "for a watcher that matches information objects" do
before(:all) do
@guard_any_return.watchers = [
described_class.new(%r{lib/(.*)\.rb}, lambda { |m| "spec/#{m[1]}_spec.rb" }),
described_class.new(/addition(.*)\.rb/, lambda { |m| (1 + 1).to_s + m[0] }),
described_class.new('hash.rb', lambda { |m| Hash[:foo, 'bar', :file_name, m[0]] }),
described_class.new(/array(.*)\.rb/, lambda { |m| ['foo', 'bar', m[0]] }),
described_class.new(/blank(.*)\.rb/, lambda { |m| '' }),
described_class.new(/uptime(.*)\.rb/, lambda { |m| `uptime > /dev/null` })
]
end
it "returns a substituted single file specified within the action" do
described_class.match_files(@guard_any_return, ['lib/my_wonderful_lib.rb']).should == ['spec/my_wonderful_lib_spec.rb']
end
it "returns a hash specified within the action" do
described_class.match_files(@guard_any_return, ['hash.rb']).should == [{:foo => 'bar', :file_name => 'hash.rb'}]
end
it "returns multiple files by combining the results of different actions" do
described_class.match_files(@guard_any_return, ['lib/my_wonderful_lib.rb', 'array.rb']).should == ['spec/my_wonderful_lib_spec.rb', ['foo', 'bar', "array.rb"]]
end
it "returns the evaluated addition argument + the path" do
described_class.match_files(@guard_any_return, ['addition.rb']).should == ["2addition.rb"]
end
it "returns nothing if the action response is empty string" do
described_class.match_files(@guard_any_return, ['blank.rb']).should == ['']
end
it "returns nothing if the action returns empty string" do
described_class.match_files(@guard_any_return, ['uptime.rb']).should == ['']
end
end
end
context "with an exception that is raised" do
before(:all) { @guard.watchers = [described_class.new('evil.rb', lambda { raise "EVIL" })] }
it "displays the error and backtrace" do
Guard::UI.should_receive(:error) do |msg|
msg.should include("Problem with watch action!")
msg.should include("EVIL")
end
described_class.match_files(@guard, ['evil.rb'])
end
end
end
describe ".match_files?" do
before(:all) do
@guard1 = Guard::Guard.new([described_class.new(/.*_spec\.rb/)])
@guard2 = Guard::Guard.new([described_class.new('spec_helper.rb', 'spec')])
@guards = [@guard1, @guard2]
end
context "with a watcher that matches a file" do
specify { described_class.match_files?(@guards, ['lib/my_wonderful_lib.rb', 'guard_rocks_spec.rb']).should be_true }
end
context "with no watcher that matches a file" do
specify { described_class.match_files?(@guards, ['lib/my_wonderful_lib.rb']).should be_false }
end
end
describe "#match_file?" do
context "with a string pattern" do
context "that is a normal string" do
subject { described_class.new('guard_rocks_spec.rb') }
context "with a watcher that matches a file" do
specify { subject.match_file?('guard_rocks_spec.rb').should be_true }
end
context "with no watcher that matches a file" do
specify { subject.match_file?('lib/my_wonderful_lib.rb').should be_false }
end
end
context "that is a string representing a regexp (deprecated)" do
subject { described_class.new('^guard_rocks_spec\.rb$') }
context "with a watcher that matches a file" do
specify { subject.match_file?('guard_rocks_spec.rb').should be_true }
end
context "with no watcher that matches a file" do
specify { subject.match_file?('lib/my_wonderful_lib.rb').should be_false }
end
end
end
context "that is a regexp pattern" do
subject { described_class.new(/.*_spec\.rb/) }
context "with a watcher that matches a file" do
specify { subject.match_file?('guard_rocks_spec.rb').should be_true }
end
context "with no watcher that matches a file" do
specify { subject.match_file?('lib/my_wonderful_lib.rb').should be_false }
end
end
end
describe ".match_guardfile?" do
before(:all) { Guard::Dsl.stub(:guardfile_path) { Dir.pwd + '/Guardfile' } }
context "with files that match the Guardfile" do
specify { described_class.match_guardfile?(['Guardfile', 'guard_rocks_spec.rb']).should be_true }
end
context "with no files that match the Guardfile" do
specify { described_class.match_guardfile?(['guard_rocks.rb', 'guard_rocks_spec.rb']).should be_false }
end
end
end

View File

@ -1,638 +1,191 @@
require 'spec_helper' require 'spec_helper'
require 'guard/guard'
# mute UI
module Guard::UI
class << self
def info(message, options = {})
end
def error(message)
end
def debug(message)
end
end
end
describe Guard do describe Guard do
describe ".initialize_template" do describe "get_guard_class" do
context "with a Guard name" do
it "initializes a the Guard" do it "should return Guard::RSpec" do
class Guard::TestGuard < Guard::Guard Guard.get_guard_class('rspec').should == Guard::RSpec
end
Guard::TestGuard.should_receive(:init)
Guard.initialize_template('test-guard')
end
end
context "without a Guard name" do
context "with an existing Guardfile" do
before do
File.stub(:exist?).and_return true
Dir.stub(:pwd).and_return "/home/user"
end
it "shows an error" do
Guard.should_receive(:exit).with 1
::Guard::UI.should_receive(:error).with("Guardfile already exists at /home/user/Guardfile")
Guard.initialize_template()
end
end
context "without an existing Guardfile" do
before do
File.stub(:exist?).and_return false
Dir.stub(:pwd).and_return "/home/user"
end
it "copies the Guardfile template" do
::Guard::UI.should_receive(:info).with("Writing new Guardfile to /home/user/Guardfile")
FileUtils.should_receive(:cp).with(an_instance_of(String), 'Guardfile')
Guard.initialize_template()
end
end
end end
end end
describe ".setup" do describe "locate_guard" do
subject { ::Guard.setup }
it "should return guard-rspec gem path" do
it "returns itself for chaining" do guard_path = Guard.locate_guard('rspec')
subject.should be ::Guard guard_path.should match(/^.*\/guard-rspec-.*$/)
guard_path.should == guard_path.chomp
end end
it "initializes @guards" do end
subject.guards.should eql []
describe "init" do
subject { ::Guard.init }
it "Should retrieve itself for chaining" do
subject.should be_kind_of(Module)
end end
it "initializes @groups" do it "Should init guards array" do
described_class.groups[0].name.should eql :default ::Guard.guards.should be_kind_of(Array)
described_class.groups[0].options.should == {}
end end
it "initializes the options" do it "Should init options" do
opts = { :my_opts => true } opts = {:my_opts => true}
Guard.setup(opts).options.should include(:my_opts) ::Guard.init(opts).options.should be_include(:my_opts)
end end
it "initializes the listener" do it "Should init listeners" do
::Guard.listener.should be_kind_of(Guard::Listener) ::Guard.listener.should be_kind_of(Guard::Listener)
end end
it "respect the watchdir option" do
::Guard.setup(:watchdir => "/foo/bar")
::Guard.listener.directory.should eql "/foo/bar"
end
it "turns on the notifier by default" do
ENV["GUARD_NOTIFY"] = nil
::Guard::Notifier.should_receive(:turn_on)
::Guard.setup(:notify => true)
end
it "turns off the notifier if the notify option is false" do
::Guard::Notifier.should_receive(:turn_off)
::Guard.setup(:notify => false)
end
it "turns off the notifier if environment variable GUARD_NOTIFY is false" do
ENV["GUARD_NOTIFY"] = 'false'
::Guard::Notifier.should_receive(:turn_off)
::Guard.setup(:notify => true)
end
it "logs command execution if the debug option is true" do
::Guard.should_receive(:debug_command_execution)
::Guard.setup(:debug => true)
end
it "initializes the interactor" do
::Guard.setup
::Guard.interactor.should be_kind_of(Guard::Interactor)
end
it "skips the interactor initalization if no-interactions is true" do
::Guard.interactor = nil
::Guard.setup(:no_interactions => true)
::Guard.interactor.should be_nil
end
end end
describe ".guards" do describe "start" do
class Guard::FooBar < Guard::Guard; end
class Guard::FooBaz < Guard::Guard; end
subject do
guard = ::Guard.setup
@guard_foo_bar_backend = Guard::FooBar.new([], { :group => 'backend' })
@guard_foo_bar_frontend = Guard::FooBar.new([], { :group => 'frontend' })
@guard_foo_baz_backend = Guard::FooBaz.new([], { :group => 'backend' })
@guard_foo_baz_frontend = Guard::FooBaz.new([], { :group => 'frontend' })
guard.instance_variable_get("@guards").push(@guard_foo_bar_backend)
guard.instance_variable_get("@guards").push(@guard_foo_bar_frontend)
guard.instance_variable_get("@guards").push(@guard_foo_baz_backend)
guard.instance_variable_get("@guards").push(@guard_foo_baz_frontend)
guard
end
it "return @guards without any argument" do
subject.guards.should eql subject.instance_variable_get("@guards")
end
describe "find a guard by as string/symbol" do
it "find a guard by a string" do
subject.guards('foo-bar').should eql @guard_foo_bar_backend
end
it "find a guard by a symbol" do
subject.guards(:'foo-bar').should eql @guard_foo_bar_backend
end
it "returns nil if guard is not found" do
subject.guards('foo-foo').should be_nil
end
end
describe "find guards matching a regexp" do
it "with matches" do
subject.guards(/^foobar/).should eql [@guard_foo_bar_backend, @guard_foo_bar_frontend]
end
it "without matches" do
subject.guards(/foo$/).should eql []
end
end
describe "find guards by their group" do
it "group name is a string" do
subject.guards(:group => 'backend').should eql [@guard_foo_bar_backend, @guard_foo_baz_backend]
end
it "group name is a symbol" do
subject.guards(:group => :frontend).should eql [@guard_foo_bar_frontend, @guard_foo_baz_frontend]
end
it "returns [] if guard is not found" do
subject.guards(:group => :unknown).should eql []
end
end
describe "find guards by their group & name" do
it "group name is a string" do
subject.guards(:group => 'backend', :name => 'foo-bar').should eql [@guard_foo_bar_backend]
end
it "group name is a symbol" do
subject.guards(:group => :frontend, :name => :'foo-baz').should eql [@guard_foo_baz_frontend]
end
it "returns [] if guard is not found" do
subject.guards(:group => :unknown, :name => :'foo-baz').should eql []
end
end
end
describe ".groups" do
subject do
guard = ::Guard.setup
@group_backend = guard.add_group(:backend)
@group_backflip = guard.add_group(:backflip)
guard
end
it "return @groups without any argument" do
subject.groups.should eql subject.instance_variable_get("@groups")
end
describe "find a group by as string/symbol" do
it "find a group by a string" do
subject.groups('backend').should eql @group_backend
end
it "find a group by a symbol" do
subject.groups(:backend).should eql @group_backend
end
it "returns nil if group is not found" do
subject.groups(:foo).should be_nil
end
end
describe "find groups matching a regexp" do
it "with matches" do
subject.groups(/^back/).should eql [@group_backend, @group_backflip]
end
it "without matches" do
subject.groups(/back$/).should eql []
end
end
end
describe ".start" do
it "basic check that core methods are called" do
opts = { :my_opts => true, :guardfile => File.join(@fixture_path, "Guardfile") }
::Guard.should_receive(:setup).with(opts)
::Guard::Dsl.should_receive(:evaluate_guardfile).with(opts)
::Guard.listener.should_receive(:start)
::Guard.start(opts)
end
end
describe ".add_guard" do
before(:each) do before(:each) do
@guard_rspec_class = double('Guard::RSpec') @guard = mock(::Guard::Guard)
@guard_rspec = double('Guard::RSpec') @guard.stub!(:reload_at_start?).and_return(false)
@guard.stub!(:run_all_at_start?).and_return(false)
@guard.stub!(:respond_to?)
Guard.stub!(:get_guard_class) { @guard_rspec_class } @listener = mock(::Guard::Polling)
@listener.stub!(:on_change)
@listener.stub!(:start).and_return(true)
Guard.setup ::Guard::Listener.stub!(:init).and_return(@listener)
::Guard::Dsl.stub!(:evaluate_guardfile)
::Guard::Interactor.stub!(:init_signal_traps)
subject.stub!(:guards).and_return([@guard])
subject.stub!(:supervised_task).with(anything(), :start).and_return(true)
end end
it "accepts guard name as string" do it 'should evaluate Guardfile' do
@guard_rspec_class.should_receive(:new).and_return(@guard_rspec) ::Guard::Dsl.should_receive(:evaluate_guardfile)
subject.start
Guard.add_guard('rspec')
end end
it "accepts guard name as symbol" do it 'should init signal traps' do
@guard_rspec_class.should_receive(:new).and_return(@guard_rspec) ::Guard::Interactor.should_receive(:init_signal_traps)
subject.start
Guard.add_guard(:rspec)
end end
it "adds guard to the @guards array" do it 'should define listener on_change' do
@guard_rspec_class.should_receive(:new).and_return(@guard_rspec) @listener.should_receive(:on_change)
subject.start
Guard.add_guard(:rspec)
Guard.guards.should eql [@guard_rspec]
end end
context "with no watchers given" do it 'should start guards' do
it "gives an empty array of watchers" do subject.should_receive(:supervised_task).with(@guard, :start).and_return(true)
@guard_rspec_class.should_receive(:new).with([], {}).and_return(@guard_rspec) subject.start
Guard.add_guard(:rspec, [])
end
end end
context "with watchers given" do it 'should call reload at start if needed' do
it "give the watchers array" do @guard.should_receive(:respond_to?).with(:reload_at_start?).and_return(true)
@guard_rspec_class.should_receive(:new).with([:foo], {}).and_return(@guard_rspec) @guard.should_receive(:reload_at_start?).and_return(true)
@guard.should_receive(:reload).and_return(true)
Guard.add_guard(:rspec, [:foo]) subject.start
end
end end
context "with no options given" do it 'should call run_all at start if needed' do
it "gives an empty hash of options" do @guard.should_receive(:respond_to?).with(:run_all_at_start?).and_return(true)
@guard_rspec_class.should_receive(:new).with([], {}).and_return(@guard_rspec) @guard.should_receive(:run_all_at_start?).and_return(true)
@guard.should_receive(:run_all).and_return(true)
Guard.add_guard(:rspec, [], [], {}) subject.start
end
end end
context "with options given" do it 'should not call reload and run_all at start if not needed' do
it "give the options hash" do @guard.should_receive(:respond_to?).with(:reload_at_start?).and_return(true)
@guard_rspec_class.should_receive(:new).with([], { :foo => true, :group => :backend }).and_return(@guard_rspec) @guard.should_receive(:reload_at_start?).and_return(false)
@guard.should_not_receive(:reload)
Guard.add_guard(:rspec, [], [], { :foo => true, :group => :backend }) @guard.should_receive(:respond_to?).with(:run_all_at_start?).and_return(true)
end @guard.should_receive(:run_all_at_start?).and_return(false)
@guard.should_not_receive(:run_all)
subject.start
end end
it 'should start listener' do
@listener.should_receive(:start)
subject.start
end
end end
describe ".add_group" do describe "supervised_task" do
subject { ::Guard.setup } subject { ::Guard.init }
it "accepts group name as string" do before :each do
subject.add_group('backend') @g = mock(Guard::Guard)
@g.stub!(:respond_to).and_return { false }
subject.groups[0].name.should eql :default @g.stub!(:regular).and_return { true }
subject.groups[1].name.should eql :backend @g.stub!(:spy).and_return { raise "I break your system" }
@g.stub!(:pirate).and_raise Exception.new("I blow your system up")
@g.stub!(:regular_arg).with("given_path").and_return { "given_path" }
@g.stub!(:enable_method).and_return { true }
@g.stub!(:enable_method?).and_return { true }
@g.stub!(:respond_to).with(:enable_method?).and_return { true }
@g.stub!(:disable_method).and_return { true }
@g.stub!(:disable_method?).and_return { false }
@g.stub!(:respond_to).with(:disable_method?).and_return { true }
subject.guards.push @g
end
it "should let it go when nothing special occurs" do
subject.guards.should be_include(@g)
subject.supervised_task(@g, :regular).should be_true
subject.guards.should be_include(@g)
end
it "should let it work with some tools" do
subject.guards.should be_include(@g)
subject.supervised_task(@g, :regular).should be_true
subject.guards.should be_include(@g)
end
it "should fire the guard on spy act discovery" do
subject.guards.should be_include(@g)
::Guard.supervised_task(@g, :spy).should be_kind_of(Exception)
subject.guards.should_not be_include(@g)
::Guard.supervised_task(@g, :spy).message.should == 'I break your system'
end
it "should fire the guard on pirate act discovery" do
subject.guards.should be_include(@g)
::Guard.supervised_task(@g, :regular_arg, "given_path").should be_kind_of(String)
subject.guards.should be_include(@g)
::Guard.supervised_task(@g, :regular_arg, "given_path").should == "given_path"
end end
it "accepts group name as symbol" do it 'should let it go when method is enable by guard options' do
subject.add_group(:backend) subject.guards.should be_include(@g)
subject.supervised_task(@g, :enable_method).should be_true
subject.groups[0].name.should eql :default subject.guards.should be_include(@g)
subject.groups[1].name.should eql :backend
end end
it "accepts options" do it 'should not let it go when method is disable by guard options' do
subject.add_group(:backend, { :halt_on_fail => true }) subject.guards.should be_include(@g)
subject.supervised_task(@g, :disable_method).should be_false
subject.groups[0].options.should == {} subject.guards.should be_include(@g)
subject.groups[1].options.should == { :halt_on_fail => true }
end
end
describe ".get_guard_class" do
after do
[:Classname, :DashedClassName, :Inline].each do |const|
Guard.send(:remove_const, const) rescue nil
end
end
it "reports an error if the class is not found" do
::Guard::UI.should_receive(:error).twice
Guard.get_guard_class('notAGuardClass')
end
context 'with a nested Guard class' do
it "resolves the Guard class from string" do
Guard.should_receive(:require) { |classname|
classname.should == 'guard/classname'
class Guard::Classname
end
}
Guard.get_guard_class('classname').should == Guard::Classname
end
it "resolves the Guard class from symbol" do
Guard.should_receive(:require) { |classname|
classname.should == 'guard/classname'
class Guard::Classname
end
}
Guard.get_guard_class(:classname).should == Guard::Classname
end
end
context 'with a name with dashes' do
it "returns the Guard class" do
Guard.should_receive(:require) { |classname|
classname.should == 'guard/dashed-class-name'
class Guard::DashedClassName
end
}
Guard.get_guard_class('dashed-class-name').should == Guard::DashedClassName
end
end
context 'with an inline Guard class' do
it 'returns the Guard class' do
module Guard
class Inline < Guard
end
end
Guard.should_not_receive(:require)
Guard.get_guard_class('inline').should == Guard::Inline
end
end
end
describe ".locate_guard" do
it "returns the path of a Guard gem" do
if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
gem_location = Gem::Specification.find_by_name("guard-rspec").full_gem_path
else
gem_location = Gem.source_index.find_name("guard-rspec").last.full_gem_path
end
Guard.locate_guard('rspec').should == gem_location
end
end
describe ".guard_gem_names" do
it "returns the list of guard gems" do
gems = Guard.guard_gem_names
gems.should include("rspec")
end
end
describe ".run_guard_task" do
subject { ::Guard.setup }
before do
class Guard::Dummy < Guard::Guard; end
subject.add_group(:foo, { :halt_on_fail => true })
subject.add_group(:bar)
subject.add_guard(:dummy, [], [], { :group => :foo })
subject.add_guard(:dummy, [], [], { :group => :foo })
subject.add_guard(:dummy, [], [], { :group => :bar })
subject.add_guard(:dummy, [], [], { :group => :bar })
@sum = { :foo => 0, :bar => 0}
end
context "all tasks succeed" do
before do
subject.guards.each { |guard| guard.stub!(:task) { @sum[guard.group] += 1; true } }
end
it "executes the task for each guard in each group" do
subject.run_guard_task(:task)
@sum.all? { |k, v| v == 2 }.should be_true
end
end
context "one guard fails" do
before do
subject.guards.each_with_index do |g, i|
g.stub!(:task) do
@sum[g.group] += i+1
if i % 2 == 0
throw :task_has_failed
else
true
end
end
end
end
it "executes the task only for guards that didn't fail for group with :halt_on_fail == true" do
subject.run_guard_task(:task)
@sum[:foo].should eql 1
@sum[:bar].should eql 7
end
end
end
describe ".run_on_change_task" do
let(:guard) do
class Guard::Dummy < Guard::Guard
def watchers
[Guard::Watcher.new(/.+\.rb/)]
end
end
Guard::Dummy.new
end
it 'runs the :run_on_change task with the watched file changes' do
Guard.should_receive(:run_supervised_task).with(guard, :run_on_change, ['a.rb', 'b.rb'])
Guard.run_on_change_task(['a.rb', 'b.rb', 'templates/d.haml'], guard, :run_on_change)
end
it 'runs the :run_on_deletion task with the watched file deletions' do
Guard.should_receive(:run_supervised_task).with(guard, :run_on_deletion, ['c.rb'])
Guard.run_on_change_task(['!c.rb', '!templates/e.haml'], guard, :run_on_change)
end
end
describe ".changed_paths" do
let(:paths) { ['a.rb', 'b.rb', '!c.rb', 'templates/d.haml', '!templates/e.haml'] }
it 'returns the changed paths' do
Guard.changed_paths(paths).should =~ ['a.rb', 'b.rb', 'templates/d.haml']
end
end
describe ".deleted_paths" do
let(:paths) { ['a.rb', 'b.rb', '!c.rb', 'templates/d.haml', '!templates/e.haml'] }
it 'returns the deleted paths' do
Guard.deleted_paths(paths).should =~ ['c.rb', 'templates/e.haml']
end
end
describe ".run_supervised_task" do
subject { ::Guard.setup }
before do
@g = mock(Guard::Guard).as_null_object
subject.guards.push(@g)
subject.add_group(:foo, { :halt_on_fail => true })
subject.add_group(:bar, { :halt_on_fail => false })
end
context "with a task that succeed" do
context 'without any arguments' do
before(:each) do
@g.stub!(:regular_without_arg) { true }
end
it "doesn't fire the Guard" do
lambda { subject.run_supervised_task(@g, :regular_without_arg) }.should_not change(subject.guards, :size)
end
it "returns the result of the task" do
::Guard.run_supervised_task(@g, :regular_without_arg).should be_true
end
it "passes the args to the :begin hook" do
@g.should_receive(:hook).with("regular_without_arg_begin", "given_path")
::Guard.run_supervised_task(@g, :regular_without_arg, "given_path")
end
it "passes the result of the supervised method to the :end hook" do
@g.should_receive(:hook).with("regular_without_arg_begin", "given_path")
@g.should_receive(:hook).with("regular_without_arg_end", true)
::Guard.run_supervised_task(@g, :regular_without_arg, "given_path")
end
end
context 'with arguments' do
before(:each) do
@g.stub!(:regular_with_arg).with("given_path") { "I'm a success" }
end
it "doesn't fire the Guard" do
lambda { subject.run_supervised_task(@g, :regular_with_arg, "given_path") }.should_not change(subject.guards, :size)
end
it "returns the result of the task" do
::Guard.run_supervised_task(@g, :regular_with_arg, "given_path").should eql "I'm a success"
end
it "calls the default begin hook but not the default end hook" do
@g.should_receive(:hook).with("failing_begin")
@g.should_not_receive(:hook).with("failing_end")
::Guard.run_supervised_task(@g, :failing)
end
end
end
context "with a task that throw :task_has_failed" do
context "for a guard's group has the :halt_on_fail option == true" do
before(:each) { @g.stub!(:group) { :foo }; @g.stub!(:failing) { throw :task_has_failed } }
it "throws :task_has_failed" do
expect { subject.run_supervised_task(@g, :failing) }.to throw_symbol(:task_has_failed)
end
end
context "for a guard's group has the :halt_on_fail option == false" do
before(:each) { @g.stub!(:group) { :bar }; @g.stub!(:failing) { throw :task_has_failed } }
it "catches :task_has_failed" do
expect { subject.run_supervised_task(@g, :failing) }.to_not throw_symbol(:task_has_failed)
end
end
end
context "with a task that raises an exception" do
before(:each) { @g.stub!(:group) { :foo }; @g.stub!(:failing) { raise "I break your system" } }
it "fires the Guard" do
lambda { subject.run_supervised_task(@g, :failing) }.should change(subject.guards, :size).by(-1)
subject.guards.should_not include(@g)
end
it "returns the exception" do
failing_result = ::Guard.run_supervised_task(@g, :failing)
failing_result.should be_kind_of(Exception)
failing_result.message.should == 'I break your system'
end
end
end
describe '.guard_symbol' do
let(:guard) { mock(Guard::Guard).as_null_object }
it 'returns :task_has_failed when the group is missing' do
subject.guard_symbol(guard).should eql :task_has_failed
end
context 'for a group with :halt_on_fail' do
let(:group) { mock(Guard::Group) }
before do
guard.stub(:group).and_return :foo
group.stub(:options).and_return({ :halt_on_fail => true })
end
it 'returns :no_catch' do
subject.should_receive(:groups).with(:foo).and_return group
subject.guard_symbol(guard).should eql :no_catch
end
end
context 'for a group without :halt_on_fail' do
let(:group) { mock(Guard::Group) }
before do
guard.stub(:group).and_return :foo
group.stub(:options).and_return({ :halt_on_fail => false })
end
it 'returns :task_has_failed' do
subject.should_receive(:groups).with(:foo).and_return group
subject.guard_symbol(guard).should eql :task_has_failed
end
end
end
describe ".debug_command_execution" do
subject { ::Guard.setup }
before do
@original_system = Kernel.method(:system)
@original_command = Kernel.method(:"`")
end
after do
Kernel.send(:define_method, :system, @original_system.to_proc )
Kernel.send(:define_method, :"`", @original_command.to_proc )
end
it "outputs Kernel.#system method parameters" do
::Guard.setup(:debug => true)
::Guard::UI.should_receive(:debug).with("Command execution: echo test")
system("echo", "test").should be_true
end
it "outputs Kernel.#` method parameters" do
::Guard.setup(:debug => true)
::Guard::UI.should_receive(:debug).twice.with("Command execution: echo test")
`echo test`.should eql "test\n"
%x{echo test}.should eql "test\n"
end end
end end
end end

View File

@ -2,21 +2,20 @@ require 'rubygems'
require 'guard' require 'guard'
require 'rspec' require 'rspec'
ENV["GUARD_ENV"] = 'test'
Dir["#{File.expand_path('..', __FILE__)}/support/**/*.rb"].each { |f| require f } Dir["#{File.expand_path('..', __FILE__)}/support/**/*.rb"].each { |f| require f }
puts "Please do not update/create files while tests are running." puts "Please do not update/create files while tests are running."
RSpec.configure do |config| RSpec.configure do |config|
config.color_enabled = true config.color_enabled = true
config.filter_run :focus => true
config.treat_symbols_as_metadata_keys_with_true_values = true
config.run_all_when_everything_filtered = true
config.before(:each) do config.before(:each) do
ENV["GUARD_ENV"] = 'test'
@fixture_path = Pathname.new(File.expand_path('../fixtures/', __FILE__)) @fixture_path = Pathname.new(File.expand_path('../fixtures/', __FILE__))
end end
end config.after(:each) do
ENV["GUARD_ENV"] = nil
end
end

View File

@ -1,20 +0,0 @@
def growl_installed?
require 'growl'
true
rescue LoadError
false
end
def libnotify_installed?
require 'libnotify'
true
rescue LoadError
false
end
def rbnotifu_installed?
require 'rb-notifu'
true
rescue LoadError
false
end

View File

@ -1,225 +0,0 @@
private
# Set the sleep time around start/stop the listener. This defaults
# to one second but can be overridden by setting the environment
# variable `GUARD_SLEEP`.
#
def sleep_time
@sleep_time ||= ENV['GUARD_SLEEP'] ? ENV['GUARD_SLEEP'].to_f : 1
end
# Make the spec listen to a specific listener.
# This automatically starts to record results for the supplied listener.
#
# @param [Guard::Listener] listener the Guard listener
#
def listen_to(listener)
@listener = listener
record_results
end
# Start the listener. Normally you use {#watch} to wrap
# the code block that should be listen to instead of starting
# it manually.
#
def start
sleep(sleep_time)
@listener.update_last_event
Thread.new { @listener.start }
sleep(sleep_time)
end
# Stop the listener. Normally you use {#watch} to wrap
# the code block that should be listen to instead of stopping
# it manually.
#
def stop
sleep(sleep_time)
@listener.stop
sleep(sleep_time)
end
# Watch file changes in a code block.
#
# @example Watch file changes
# watch do
# File.mv file1, file2
# end
#
# @yield The block to listen for file changes
#
def watch
start
yield if block_given?
stop
end
# Start recording results from the current listener.
# You may want to use {#listen_to} to set a listener
# instead of set it up manually.
#
def record_results
# Don't fail specs due to editor swap files, etc.
noise = %r|\.sw.$|
@results = []
@listener.on_change do |files|
@results += files.reject { |f| f =~ noise }
end
end
# Get the recorded result from the listener.
#
# @return [Array<String>] the result files
#
def results
@results.flatten
end
# Define a file absolute to the fixture path.
#
# @param [String, Array<String>] file the relative file name, separated by segment
# @return [String] the absolute file
#
def fixture(*file)
@fixture_path.join(*file)
end
shared_examples_for 'a listener that reacts to #on_change' do
before do
listen_to described_class.new
end
context 'for a new file' do
let(:file) { fixture('newfile.rb') }
before { File.delete(file) if File.exists?(file) }
after { File.delete file }
it 'catches the new file' do
File.exists?(file).should be_false
watch do
FileUtils.touch file
end
results.should =~ ['spec/fixtures/newfile.rb']
end
end
context 'for a single file update' do
let(:file) { fixture('folder1', 'file1.txt') }
it 'catches the update' do
File.exists?(file).should be_true
watch do
File.open(file, 'w') { |f| f.write('') }
end
results.should =~ ['spec/fixtures/folder1/file1.txt']
end
end
context 'for a single file chmod update' do
let(:file) { fixture('folder1/file1.txt') }
it 'does not catch the update' do
File.exists?(file).should be_true
watch do
File.chmod(0777, file)
end
results.should =~ []
end
end
context 'for a dotfile update' do
let(:file) { fixture('.dotfile') }
it "catches the update" do
File.exists?(file).should be_true
watch do
File.open(file, 'w') { |f| f.write('') }
end
results.should =~ ['spec/fixtures/.dotfile']
end
end
context 'for multiple file updates' do
let(:file1) { fixture('folder1', 'file1.txt') }
let(:file2) { fixture('folder1', 'folder2', 'file2.txt') }
it 'catches the updates' do
File.exists?(file1).should be_true
File.exists?(file2).should be_true
watch do
File.open(file1, 'w') { |f| f.write('') }
File.open(file2, 'w') { |f| f.write('') }
end
results.should =~ ['spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/folder2/file2.txt']
end
end
context 'for a deleted file' do
let(:file) { fixture('folder1', 'file1.txt') }
after { FileUtils.touch file }
it 'does not catch the deletion' do
File.exists?(file).should be_true
watch do
File.delete file
end
results.should =~ []
end
end
context 'for a moved file' do
let(:file1) { fixture('folder1', 'file1.txt') }
let(:file2) { fixture('folder1', 'movedfile1.txt') }
after { FileUtils.mv file2, file1 }
it 'does not catch the move' do
File.exists?(file1).should be_true
File.exists?(file2).should be_false
watch do
FileUtils.mv file1, file2
end
results.should =~ []
end
end
end
shared_examples_for "a listener scoped to a specific directory" do
let(:work_directory) { fixture('folder1') }
let(:new_file) { work_directory.join('folder2', 'newfile.rb') }
let(:modified) { work_directory.join('file1.txt') }
before { listen_to described_class.new(work_directory) }
after { File.delete new_file }
it 'should base paths within this directory' do
File.exists?(modified).should be_true
File.exists?(new_file).should be_false
watch do
FileUtils.touch new_file
File.open(modified, 'w') { |f| f.write('') }
end
results.should =~ ['folder2/newfile.rb', 'file1.txt']
end
end

View File

@ -1,11 +1,7 @@
def mac? def mac?
RbConfig::CONFIG['target_os'] =~ /darwin/i Config::CONFIG['target_os'] =~ /darwin/i
end end
def linux? def linux?
RbConfig::CONFIG['target_os'] =~ /linux/i Config::CONFIG['target_os'] =~ /linux/i
end end
def windows?
RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
end