Compare commits

..

No commits in common. "master" and "guard_dependencies" have entirely different histories.

36 changed files with 1552 additions and 2224 deletions

2
.gitignore vendored
View File

@ -6,8 +6,6 @@ doc/*
*.bak *.bak
.bundle .bundle
.yardoc .yardoc
.rbx
.rvmrc
Gemfile.lock Gemfile.lock
## MAC OS ## MAC OS

View File

@ -8,8 +8,6 @@ branches:
only: only:
- master - master
- guard_dependencies - guard_dependencies
env:
- GUARD_SLEEP=1
notifications: notifications:
recipients: recipients:
- thibaud@thibaud.me - thibaud@thibaud.me

View File

@ -1,68 +1,6 @@
## Master ## Master
### Improvements - Pull request [#137](https://github.com/guard/guard/pull/137): Fix interacting with tools like ruby-debug. ([@hron][] & [@netzpirat][])
- 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 - September 14, 2011
@ -71,226 +9,235 @@
### Major Changes ### 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][]) - 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][]) - JRuby & Rubinius support (beta). ([@thibaudgg][] and [@netzpirat][])
### New features ### New feature:
- [#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][]) - Pull request [#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][]) - Ability to 'pause' files modification listening. Please refer to the "Interactions" section in the README for more information. ([@thibaudgg][])
### Improvement ### Improvement:
- Remove the need to scan the whole directory after guard's `run_on_change` method. ([@thibaudgg][]) - Remove the need to scan the whole directory after guard's `run_on_change` method. ([@thibaudgg][])
## 0.6.3 - September 1, 2011 ## 0.6.3 - September 1, 2011
### New features ### New features:
- [#130](https://github.com/guard/guard/pull/130): Adds `ignore_paths` method to DSL. ([@ianwhite][]) - Pull request [#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][]) - Pull request [#128](https://github.com/guard/guard/pull/128): Users can add additional settings to `~/.guard.rb` that augment the existing Guardfile. ([@tpope][])
## 0.6.2 - August 17, 2011 ## 0.6.2 - August 17, 2011
### Bug fixes ### Bugs fixes:
- Re-add the possibility to use the `growl` gem since the `growl_notify` gem this is currently known to not work in conjunction with Spork. ([@netzpirat][]) - 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][]) - Ensure that scoped groups and group name are symbolized before checking for inclusion. ([@rymai][])
### New features ### New features:
- Groups are now stored in a `@groups` variable (will be used for future features). ([@rymai][]) - 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][]) - Guards will now receive their group in the options hash at initialization (will be used for future features). ([@rymai][])
### Improvement ### Improvement:
- Explain the growl/growl_notify differences in the README. ([@netzpirat][]) - Explain the growl/growl_notify differences in the README. ([@netzpirat][])
## 0.6.1 - August 15, 2011 ## 0.6.1 - August 15, 2011
### Bug fixes ### Bugs fixes:
- [#120](https://github.com/guard/guard/pull/120): remove `guardfile_contents` when re-evaluating so that the Guardfile gets reloaded correctly. ([@mordaroso][]) - Pull request [#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][]) - Pull request [#119](https://github.com/guard/guard/pull/119): `Dsl.evaluate_guardfile` uses all groups if none specified. ([@ches][])
## 0.6.0 - August 13, 2011 ## 0.6.0 - August 13, 2011
### Bug fixes ### Bugs fixes:
- Pull request [#107](https://github.com/guard/guard/pull/107): Small spelling fix. ([@dnagir][]) - Pull request [#107](https://github.com/guard/guard/pull/107): Small spelling fix. ([@dnagir][])
- `Dir.glob` now ignores files that don't need to be watched. ([@rymai][]) - `Dir.glob` now ignores files that don't need to be watched. ([@rymai][])
### New feature ### New features:
- Pull request [#112](https://github.com/guard/guard/pull/112): Add `list` command to CLI. ([@docwhat][]) - Pull request [#112](https://github.com/guard/guard/pull/112): Add `list` command to CLI. ([@docwhat][])
### Improvements ### Improvements:
- [#99](https://github.com/guard/guard/pull/99): [OS X] Switch from growl gem to growl_notify gem. ([@johnbintz][]) - Pull request [#99](https://github.com/guard/guard/pull/99): [OS X] Switch from growl gem to growl_notify gem. ([@johnbintz][])
- [#115](https://github.com/guard/guard/pull/115): [Linux] Add `:transient => true` to default libnotify options. ([@zonque][]) - Pull request [#115](https://github.com/guard/guard/pull/115): [Linux] Add `:transient => true` to default libnotify options. ([@zonque][])
- [#95](https://github.com/guard/guard/pull/95): Output system commands and options to be executed when in debug mode. ([@uk-ar][] and [@netzpirat][]) - Pull request [#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][]) - `Guard::Dsl.revaluate_guardfile` has been renamed to `Guard::Dsl.reevaluate_guardfile`. ([@rymai][])
- New CLI options: ([@nestegg][]) - New CLI options: ([@nestegg][])
- `watchdir`/`-w` to specify the directory in which Guard should watch for changes, - `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. - `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][]) - Pull request [#90](https://github.com/guard/guard/pull/90): Refactoring of color handling in the `Guard::UI`. ([@stereobooster][])
## 0.5.1 - July 2, 2011 ## 0.5.1 - July 2, 2011
### Bug fix ### Bugs fixes:
- Fixed `guard show` command. ([@bronson][] & [@thibaudgg][]) - Fixed `guard show` command. ([@bronson][] & [@thibaudgg][])
## 0.5.0 - July 2, 2011 ## 0.5.0 - July 2, 2011
### New features ### New features:
- Guard::Ego is now part of Guard, so Guardfile is automagically re-evaluated when modified. ([@thibaudgg][]) - Guard::Ego is now part of Guard, so Guardfile is automagically re-evaluated when modified. ([@thibaudgg][])
- [#91](https://github.com/guard/guard/pull/91): Show Guards in Guardfile with the `guard -T`. ([@johnbintz][]) - Pull request [#91](https://github.com/guard/guard/pull/91): Show Guards in Guardfile with the `guard -T`. ([@johnbintz][])
### Improvements ### Improvements:
- [#98](https://github.com/guard/guard/issues/98): Multiple calls per watch event on linux with rb-inotify. ([@jeffutter][] & [@netzpirat][]) - Issue [#98](https://github.com/guard/guard/issues/98): Multiple calls per watch event on linux with rb-inotify. ([@jeffutter][] & [@netzpirat][])
- [#94](https://github.com/guard/guard/pull/94): Show backtrace in terminal when a problem with a watch action occurs. ([@capotej][]) - Pull request [#94](https://github.com/guard/guard/pull/94): Show backtrace in terminal when a problem with a watch action occurs. ([@capotej][])
- [#88](https://github.com/guard/guard/pull/88): Write exception trace in the terminal when a supervised task fail. ([@mcmire][]) - Pull request [#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][]) - 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][]) - Issue [#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][]) - Issue [#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][]) - Pull request [#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][]) - Pull request [#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][]) - Clear the terminal on start when the `:clear` option is given. ([@rymai][])
- Rename home directory Guardfile to `.Guardfile`. ([@tpope][]) - Rename home directory Guardfile to `.Guardfile`. ([@tpope][])
## 0.4.2 - June 7, 2011 ## 0.4.2 - June 7, 2011
### Bug fixes ### Bugs fixes:
- Fixed Guard::Version in ruby 1.8.7 ([@thibaudgg][]) - Fixed Guard::Version in ruby 1.8.7 ([@thibaudgg][])
- Fix ([@mislav][]) link in CHANGELOG (Note: this is a recursive CHANGELOG item). ([@fnichol][]) - Fix ([@mislav][]) link in CHANGELOG (Note: this is a recursive CHANGELOG item). ([@fnichol][])
## 0.4.1 - June 7, 2011 ## 0.4.1 - June 7, 2011
### Improvements ### 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][]) - Pull request [#77](https://github.com/guard/guard/pull/77): Refactor `get_guard_class` to first try the constant and fallback to require + various tweaks. ([@mislav][])
- Notifier improvement, don't use system notification library if could not be required. ([@yannlugrin][]) - Notifier improvement, don't use system notification library if could not be required. ([@yannlugrin][])
## 0.4.0 - June 5, 2011 ## 0.4.0 - June 5, 2011
### Bug fix ### Bugs fixes:
- In Ruby < 1.9, `Symbol#downcase` doesn't exist! ([@rymai][]) - In Ruby < 1.9, `Symbol#downcase` doesn't exist! ([@rymai][])
### New features ### New features:
- [#73](https://github.com/guard/guard/pull/73): Allow DSL's `group` method to accept a Symbol as group name. ([@johnbintz][]) - Pull request [#73](https://github.com/guard/guard/pull/73): Allow DSL's `group` method to accept a Symbol as group name. ([@johnbintz][])
- [#51](https://github.com/guard/guard/pull/51): Allow options (like `:priority`) to be passed through to the Notifier. ([@indirect][] & [@netzpirat][]) - Pull request [#51](https://github.com/guard/guard/pull/51): Allow options (like `:priority`) to be passed through to the Notifier. ([@indirect][] & [@netzpirat][])
### Improvement ### Improvements:
- [#74](https://github.com/guard/guard/pull/74): Added link definitions to make the CHANGELOG more DRY! That's for sure now, we have the cleanest CHANGELOG ever! (even the link definitions are sorted alphabetically!) ([@pcreux][]) - Pull request [#74](https://github.com/guard/guard/pull/74): Added link definitions to make the CHANGELOG more DRY! That's for sure now, we have the cleanest CHANGELOG ever! (even the link definitions are sorted alphabetically!) ([@pcreux][])
## 0.4.0.rc - May 28, 2011 ## 0.4.0.rc - May 28, 2011
### Bug fixes ### Bugs fixes:
- [#69](https://github.com/guard/guard/pull/69): Fixed typo in README: `Ctr-/` => `Ctr-\`. ([@tinogomes][]) - Pull request [#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][]) - Pull request [#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][]) - 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][]) - 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 `Guard::Notifier` (when growl/libnotify not present). ([@thibaudgg][])
- Fixed Rubygems deprecation messages. ([@thibaudgg][]) - Fixed Rubygems deprecation messages. ([@thibaudgg][])
### New features ### New features:
- [#67](https://github.com/guard/guard/pull/67): Allow Guardfile in `$HOME` folder. ([@hashrocketeer][]) - Pull request [#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][]) - Pull request [#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][]) - Pull request [#63](https://github.com/guard/guard/pull/63): Refactor listeners to work as a library. ([@niklas][])
- Use `ENV["GUARD_NOTIFY"]` to disable notifications. ([@thibaudgg][]) - Use `ENV["GUARD_NOTIFY"]` to disable notifications. ([@thibaudgg][])
- Cleaning up all specs. ([@netzpirat][]) - Cleaning up all specs. ([@netzpirat][])
- [#60](https://github.com/guard/guard/pull/60): Added Windows support. ([@stereobooster][]) - Pull request [#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][]) - Pull request [#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][]) - Pull request [#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 ## 0.3.4 - April 24, 2011
### Bug fix ### Bugs fixes:
- [#41](https://github.com/guard/guard/issues/41): Removed useless Bundler requirement. ([@thibaudgg][]) - Issue [#41](https://github.com/guard/guard/issues/41): Removed useless Bundler requirement. ([@thibaudgg][])
### New features ### New features:
- Changed CHANGELOG from RDOC to Markdown and cleaned it! Let's celebrate! ([@rymai][]) - Changed CHANGELOG from RDOC to Markdown and cleaned it! Let's celebrate! ([@rymai][])
- Changed README from RDOC to Markdown! Let's celebrate! ([@thibaudgg][]) - 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][]) - Issue [#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 ## 0.3.3 - April 18, 2011
### Bug fix ### Bugs fixes:
- Fixed `new_modified_files` rerun conditions on `Guard.run_on_change_for_all_guards`. ([@thibaudgg][]) - Fixed `new_modified_files` rerun conditions on `Guard.run_on_change_for_all_guards`. ([@thibaudgg][])
## 0.3.2 - April 17, 2011 ## 0.3.2 - April 17, 2011
### Bug fixe ### Bugs fixes:
- Pull request [#43](https://github.com/guard/guard/pull/43): Fixed `guard init` command. ([@brainopia][])
- [#43](https://github.com/guard/guard/pull/43): Fixed `guard init` command. ([@brainopia][])
## 0.3.1 - April 14, 2011 ## 0.3.1 - April 14, 2011
### Bug fixes ### Bugs fixes:
- Return unique filenames from Linux listener. (Marian Schubert) - Return unique filenames from Linux listener. (Marian Schubert)
- `Guard.get_guard_class` return wrong class when loaded nested class. ([@koshigoe][]) - `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][]) - Issue [#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][]) - Issue [#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 ### New features:
- Issue [#28](https://github.com/guard/guard/issues/28): New `-n` command line option to disable notifications (Growl / Libnotify). ([@thibaudgg][])
- [#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 ## 0.3.0 - January 19, 2011
### Bug fix ### Bugs fixes:
- Avoid launching `run_on_change` guards method when no files matched. `--clear` guard argument is now usable. ([@thibaudgg][]) - Avoid launching `run_on_change` guards method when no files matched. `--clear` guard argument is now usable. ([@thibaudgg][])
### New features ### New features:
- The whole directory is now watched during `run_on_change` to detect new files modifications. ([@thibaudgg][]) - 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][]) - Pull request [#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][]) - `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][]) - 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][]) - It's now possible to return an `Enumerable` in the `watch` optional blocks in the `Guardfile`. ([@rymai][])
### New specs ### New specs:
- `Guard::Watcher`. ([@rymai][]) - `Guard::Watcher`. ([@rymai][])
- [#13](https://github.com/guard/guard/pull/13): `Guard::Dsl`. ([@oliamb][]) - Pull request [#13](https://github.com/guard/guard/pull/13): `Guard::Dsl`. ([@oliamb][])
## 0.2.2 - October 25, 2010 ## 0.2.2 - October 25, 2010
### Bug fix ### Bugs fixes:
- Issue [#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][])
- [#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 ## 0.2.1 - October 24, 2010
### Bug fixes ### Bugs fixes:
- Pull request [#7](https://github.com/guard/guard/pull/7): Fixes for Linux support. ([@yannlugrin][])
- Pull request [#6](https://github.com/guard/guard/pull/6): Locate guard now chomp newline in result path. ([@yannlugrin][])
- [#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 ## 0.2.0 - October 21, 2010
### Bug fixes ### Bugs 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][]) - Issue [#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][]) - Issue [#2](https://github.com/guard/guard/issues/2): 1.8.6 compatibility. (reported by [@veged][], fixed by [@thibaudgg][])
- Removes Growl & Libnotify dependencies. ([@thibaudgg][]) - Removes Growl & Libnotify dependencies. ([@thibaudgg][])
## 0.2.0.beta.1 - October 17, 2010 ## 0.2.0.beta.1 - October 17, 2010
### New features ### New features:
- Improved listeners support (`rb-fsevent` & `rb-inotify`). ([@thibaudgg][]) - Improved listeners support (`rb-fsevent` & `rb-inotify`). ([@thibaudgg][])
- Added polling listening fallback. ([@thibaudgg][]) - Added polling listening fallback. ([@thibaudgg][])
@ -303,15 +250,11 @@
[@chrisberkhout]: https://github.com/chrisberkhout [@chrisberkhout]: https://github.com/chrisberkhout
[@dnagir]: https://github.com/dnagir [@dnagir]: https://github.com/dnagir
[@docwhat]: https://github.com/docwhat [@docwhat]: https://github.com/docwhat
[@earlonrails]: https://github.com/earlonrails
[@etehtsea]: https://github.com/etehtsea [@etehtsea]: https://github.com/etehtsea
[@f1sherman]: https://github.com/f1sherman
[@fabioyamate]: https://github.com/fabioyamate
[@fnichol]: https://github.com/fnichol [@fnichol]: https://github.com/fnichol
[@Gazer]: https://github.com/Gazer [@Gazer]: https://github.com/Gazer
[@gix]: https://github.com/gix [@gix]: https://github.com/gix
[@hron]: https://github.com/hron [@hron]: https://github.com/hron
[@hardipe]: https://github.com/hardipe
[@hashrocketeer]: https://github.com/hashrocketeer [@hashrocketeer]: https://github.com/hashrocketeer
[@ianwhite]: https://github.com/ianwhite [@ianwhite]: https://github.com/ianwhite
[@indirect]: https://github.com/indirect [@indirect]: https://github.com/indirect
@ -319,7 +262,6 @@
[@johnbintz]: https://github.com/johnbintz [@johnbintz]: https://github.com/johnbintz
[@jrsacks]: https://github.com/jrsacks [@jrsacks]: https://github.com/jrsacks
[@koshigoe]: https://github.com/koshigoe [@koshigoe]: https://github.com/koshigoe
[@limeyd]: https://github.com/limeyd
[@mcmire]: https://github.com/mcmire [@mcmire]: https://github.com/mcmire
[@mislav]: https://github.com/mislav [@mislav]: https://github.com/mislav
[@monocle]: https://github.com/monocle [@monocle]: https://github.com/monocle
@ -330,12 +272,9 @@
[@niklas]: https://github.com/niklas [@niklas]: https://github.com/niklas
[@oliamb]: https://github.com/oliamb [@oliamb]: https://github.com/oliamb
[@pcreux]: https://github.com/pcreux [@pcreux]: https://github.com/pcreux
[@rmm5t]: https://github.com/rmm5t
[@rymai]: https://github.com/rymai [@rymai]: https://github.com/rymai
[@scottdavis]: https://github.com/scottdavis
[@stereobooster]: https://github.com/stereobooster [@stereobooster]: https://github.com/stereobooster
[@stouset]: https://github.com/stouset [@stouset]: https://github.com/stouset
[@sunaku]: https://github.com/sunaku
[@thibaudgg]: https://github.com/thibaudgg [@thibaudgg]: https://github.com/thibaudgg
[@thierryhenrio]: https://github.com/thierryhenrio [@thierryhenrio]: https://github.com/thierryhenrio
[@tinogomes]: https://github.com/tinogomes [@tinogomes]: https://github.com/tinogomes

View File

@ -1,16 +1,11 @@
group :specs do guard :rspec, :version => 2, :all_on_start => false, :all_after_pass => false, :keep_failed => false, :cli => '--format doc' do
guard :rspec, :all_on_start => false, :all_after_pass => false, :cli => '--fail-fast --format doc' do watch(%r{^spec/.+_spec\.rb$})
watch(%r{^spec/.+_spec\.rb$}) watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } watch('spec/spec_helper.rb') { "spec" }
watch('spec/support/listener_helper.rb') { Dir.glob("spec/guard/listeners/*") }
watch('spec/spec_helper.rb') { "spec" }
end
end end
group :docs do guard :ronn do
guard :ronn do watch(%r{^man/.+\.ronn?$})
watch(%r{^man/.+\.ronn?$})
end
end end
# require 'guard/guard' # require 'guard/guard'

121
README.md
View File

@ -19,12 +19,11 @@ Features
Screencast 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) Ryan Bates made a Railscast on Guard, you can view it here: http://railscasts.com/episodes/264-guard
Install Install
------- -------
Install the gem: Install the gem:
$ gem install guard $ gem install guard
@ -46,50 +45,32 @@ Also note that if a `.guard.rb` is found in your home directory, it will be appe
Add the guards you need to your Guardfile (see the existing guards below). Add the guards you need to your Guardfile (see the existing guards below).
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 ### On Mac OS X
Install the rb-fsevent gem for [FSEvent](http://en.wikipedia.org/wiki/FSEvents) support: Install the rb-fsevent gem for [FSEvent](http://en.wikipedia.org/wiki/FSEvents) support:
$ gem install rb-fsevent $ gem install rb-fsevent
You have three possibilities for getting Growl support: You have two possibilities:
Use the [growl_notify gem](https://rubygems.org/gems/growl_notify): Use the [growl_notify gem](https://rubygems.org/gems/growl_notify) (recommended):
$ gem install growl_notify $ gem install growl_notify
The `growl_notify` gem is compatible with Growl >= 1.3 and uses AppleScript to send Growl notifications. Use the [growlnotify](http://growl.info/extras.php#growlnotify) (cli tool for growl) + the [growl gem](https://rubygems.org/gems/growl).
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 $ brew install growlnotify
$ gem install growl
Finally you have to add your Growl library of choice to your Gemfile: And add them to your Gemfile:
gem 'rb-fsevent' gem 'rb-fsevent'
gem 'growl_notify' # or gem 'ruby_gntp' or gem 'growl' gem 'growl_notify' # 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. The difference between growl and growl_notify is that growl_notify uses AppleScript to
display a message, whereas growl uses the `growlnotify` command. In general the AppleScript
<a name="linux" /> approach is preferred, but you may also use the older growl gem. Have a look at the
[Guard Wiki](https://github.com/guard/guard/wiki/Use-growl_notify-or-growl-gem) for more information.
### On Linux ### On Linux
@ -106,8 +87,6 @@ And add them to your Gemfile:
gem 'rb-inotify' gem 'rb-inotify'
gem 'libnotify' gem 'libnotify'
<a name="win" />
### On Windows ### On Windows
Install the [rb-fchange gem](https://rubygems.org/gems/rb-fchange) for [Directory Change Notification](http://msdn.microsoft.com/en-us/library/aa365261\(VS.85\).aspx) support: Install the [rb-fchange gem](https://rubygems.org/gems/rb-fchange) for [Directory Change Notification](http://msdn.microsoft.com/en-us/library/aa365261\(VS.85\).aspx) support:
@ -188,26 +167,10 @@ Guard can use a Guardfile not located in the current directory:
$ guard --guardfile ~/.your_global_guardfile $ guard --guardfile ~/.your_global_guardfile
$ guard -G ~/.your_global_guardfile # shortcut $ 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: An exhaustive list of options is available with:
$ guard help [TASK] $ guard help [TASK]
<a name="interactions" />
Interactions Interactions
------------ ------------
@ -246,11 +209,11 @@ Guardfile DSL
The Guardfile DSL consists of the following methods: The Guardfile DSL consists of the following methods:
* `#guard` - Allows you to add a guard with an optional hash of options. * `#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. * `#watch`: allows you to define which files are supervised by this guard. An optional block can be added to overwrite the paths sent to the guard's `#run_on_change` method or to launch any arbitrary command.
* `#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. * `#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. * `#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. * `#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: Example:
@ -356,9 +319,7 @@ Creating a new guard is very easy, just create a new gem (`bundle gem` if you us
test/ # or spec/ test/ # or spec/
README.md README.md
`Guard::GuardName` (in `lib/guard/guard-name.rb`) must inherit from `Guard::GuardName` (in `lib/guard/guard-name.rb`) must inherit from `Guard::Guard` and should overwrite at least one of the five basic `Guard::Guard` instance methods.
[Guard::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`: Here is an example scaffold for `lib/guard/guard-name.rb`:
@ -368,52 +329,50 @@ Here is an example scaffold for `lib/guard/guard-name.rb`:
module Guard module Guard
class GuardName < Guard class GuardName < Guard
# Initialize a Guard. def initialize(watchers=[], options={})
# @param [Array<Guard::Watcher>] watchers the Guard file watchers
# @param [Hash] options the custom Guard options
def initialize(watchers = [], options = {})
super super
# init stuff here, thx!
end end
# Call once when Guard starts. Please override initialize method to init stuff. # =================
# @raise [:task_has_failed] when start has failed # = Guard methods =
# =================
# If one of those methods raise an exception, the Guard::GuardName instance
# will be removed from the active guards.
# Called once when Guard starts
# Please override initialize method to init stuff
def start def start
true
end end
# Called when `stop|quit|exit|s|q|e + enter` is pressed (when Guard quits). # Called when `stop|quit|exit|s|q|e + enter` is pressed (when Guard quits)
# @raise [:task_has_failed] when stop has failed
def stop def stop
true
end end
# Called when `reload|r|z + enter` is pressed. # Called when `reload|r|z + enter` is pressed
# This method 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
def reload def reload
true
end end
# Called when just `enter` is pressed # Called when just `enter` is pressed
# This method should be principally used for long action like running all specs/tests/... # 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 def run_all
true
end end
# Called on file(s) modifications that the Guard watches. # Called on file(s) modifications
# @param [Array<String>] paths the changes files or paths
# @raise [:task_has_failed] when run_on_change has failed
def run_on_change(paths) def run_on_change(paths)
end true
# 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 end
end end
Please take a look at the [existing guards' source code](https://github.com/guard/guard/wiki/List-of-available-Guards) 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.
for more concrete example and inspiration.
Alternatively, a new guard can be added inline to a Guardfile with this basic structure: Alternatively, a new guard can be added inline to a Guardfile with this basic structure:
@ -422,14 +381,16 @@ Alternatively, a new guard can be added inline to a Guardfile with this basic st
module ::Guard module ::Guard
class InlineGuard < ::Guard::Guard class InlineGuard < ::Guard::Guard
def run_all def run_all
true
end end
def run_on_change(paths) def run_on_change(paths)
true
end end
end end
end end
Here is a very cool example by [@avdi](https://github.com/avdi) : [http://avdi.org/devblog/2011/06/15/a-guardfile-for-redis](http://avdi.org/devblog/2011/06/15/a-guardfile-for-redis) Here is a very cool example by [@avdi](https://github.com/avdi) : http://avdi.org/devblog/2011/06/15/a-guardfile-for-redis
Development Development
----------- -----------
@ -456,4 +417,4 @@ Author
Contributors Contributors
------------ ------------
[https://github.com/guard/guard/contributors](https://github.com/guard/guard/contributors) https://github.com/guard/guard/contributors

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,3 @@
require 'thread'
# Guard is the main module for all Guard related modules and classes. # Guard is the main module for all Guard related modules and classes.
# Also other Guard implementation should use this namespace. # Also other Guard implementation should use this namespace.
# #
@ -8,7 +6,6 @@ module Guard
autoload :UI, 'guard/ui' autoload :UI, 'guard/ui'
autoload :Dsl, 'guard/dsl' autoload :Dsl, 'guard/dsl'
autoload :DslDescriber, 'guard/dsl_describer' autoload :DslDescriber, 'guard/dsl_describer'
autoload :Group, 'guard/group'
autoload :Interactor, 'guard/interactor' autoload :Interactor, 'guard/interactor'
autoload :Listener, 'guard/listener' autoload :Listener, 'guard/listener'
autoload :Watcher, 'guard/watcher' autoload :Watcher, 'guard/watcher'
@ -16,109 +13,37 @@ module Guard
autoload :Hook, 'guard/hook' autoload :Hook, 'guard/hook'
class << self class << self
attr_accessor :options, :interactor, :listener, :lock attr_accessor :options, :guards, :groups, :interactor, :listener
# Creates the initial Guardfile template or add a Guard implementation
# Guardfile template to an existing Guardfile.
#
# @see Guard::Guard.init
#
# @param [String] guard_name the name of the Guard to initialize
#
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
# Initialize the Guard singleton. # Initialize the Guard singleton.
# #
# @param [Hash] options the Guard options.
# @option options [Boolean] clear if auto clear the UI should be done # @option options [Boolean] clear if auto clear the UI should be done
# @option options [Boolean] notify if system notifications should be shown # @option options [Boolean] notify if system notifications should be shown
# @option options [Boolean] debug if debug output 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 [Array<String>] group the list of groups to start
# @option options [String] watchdir the director to watch # @option options [String] watchdir the director to watch
# @option options [String] guardfile the path to the Guardfile # @option options [String] guardfile the path to the Guardfile
# @option options [Boolean] watch_all_modifications watches all file modifications if true
# #
def setup(options = {}) def setup(options = {})
@lock = Mutex.new
@options = options @options = options
@guards = [] @guards = []
@groups = [Group.new(:default)] @groups = [{ :name => :default, :options => {} }]
@interactor = Interactor.new unless @options[:no_interactions] @interactor = Interactor.new
@listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd, options) @listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd)
@options[:notify] && ENV['GUARD_NOTIFY'] != 'false' ? Notifier.turn_on : Notifier.turn_off @options[:notify] && ENV['GUARD_NOTIFY'] != 'false' ? Notifier.turn_on : Notifier.turn_off
UI.clear if @options[:clear] UI.clear if @options[:clear]
debug_command_execution if @options[:debug] debug_command_execution if @options[:debug]
self self
end 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 # Start Guard by evaluate the `Guardfile`, initialize the declared Guards
# and start the available file change listener. # and start the available file change listener.
# #
# @param [Hash] options the Guard options.
# @option options [Boolean] clear if auto clear the UI should be done # @option options [Boolean] clear if auto clear the UI should be done
# @option options [Boolean] notify if system notifications should be shown # @option options [Boolean] notify if system notifications should be shown
# @option options [Boolean] debug if debug output should be shown # @option options [Boolean] debug if debug output should be shown
@ -138,9 +63,9 @@ module Guard
UI.info "Guard is now watching at '#{ listener.directory }'" UI.info "Guard is now watching at '#{ listener.directory }'"
run_guard_task(:start) execute_supervised_task_for_all_guards(:start)
interactor.start if interactor interactor.start
listener.start listener.start
end end
@ -148,10 +73,8 @@ module Guard
# #
def stop def stop
UI.info 'Bye bye...', :reset => true UI.info 'Bye bye...', :reset => true
run_guard_task(:stop)
listener.stop listener.stop
execute_supervised_task_for_all_guards(:stop)
abort abort
end end
@ -159,7 +82,7 @@ module Guard
# #
def reload def reload
run do run do
run_guard_task(:reload) execute_supervised_task_for_all_guards(:reload)
end end
end end
@ -167,20 +90,20 @@ module Guard
# #
def run_all def run_all
run do run do
run_guard_task(:run_all) execute_supervised_task_for_all_guards(:run_all)
end end
end end
# Pause Guard listening to file changes. # Pause Guard listening to file changes.
# #
def pause def pause
if listener.paused? if listener.locked
UI.info 'Un-paused files modification listening', :reset => true UI.info 'Un-paused files modification listening', :reset => true
listener.clear_changed_files listener.clear_changed_files
listener.run listener.unlock
else else
UI.info 'Paused files modification listening', :reset => true UI.info 'Paused files modification listening', :reset => true
listener.pause listener.lock
end end
end end
@ -188,7 +111,7 @@ module Guard
# #
def run_on_change(paths) def run_on_change(paths)
run do run do
run_guard_task(:run_on_change, paths) execute_supervised_task_for_all_guards(:run_on_change, paths)
end end
end end
@ -198,144 +121,70 @@ module Guard
# @yield the block to run # @yield the block to run
# #
def run def run
listener.lock
interactor.lock
UI.clear if options[:clear] UI.clear if options[:clear]
begin
lock.synchronize do yield
begin rescue Interrupt
interactor.stop_if_not_current if interactor
yield
rescue Interrupt
end
interactor.start if interactor
end end
interactor.unlock
listener.unlock
end end
# Loop through all groups and run the given task for each Guard. # Loop through all groups and execute the given task for each Guard in it,
# # but halt the task execution for the all Guards within a group if one Guard
# Stop the task run for the all Guards within a group if one Guard # throws `:task_has_failed` and the group has its `:halt_on_fail` option to `true`.
# throws `:task_has_failed`.
# #
# @param [Symbol] task the task to run # @param [Symbol] task the task to run
# @param [Array<String>] files the list of files to pass to the task # @param [Array] files the list of files to pass to the task
# #
def run_guard_task(task, files = nil) def execute_supervised_task_for_all_guards(task, files = nil)
groups.each do |group| groups.each do |group_hash|
catch :task_has_failed do catch group_hash[:options][:halt_on_fail] == true ? :task_has_failed : :no_catch do
guards(:group => group.name).each do |guard| guards.find_all { |guard| guard.group == group_hash[:name] }.each do |guard|
if task == :run_on_change if task == :run_on_change
run_on_change_task(files, guard, task) paths = Watcher.match_files(guard, files)
UI.debug "#{guard.class.name}##{task} with #{paths.inspect}"
supervised_task(guard, task, paths)
else else
run_supervised_task(guard, task) supervised_task(guard, task)
end end
end end
end end
end end
end end
# Run the `:run_on_change` task. When the option `:watch_all_modifications` is set, # Let a Guard execute its task, but fire it
# the task is split to run changed paths on {Guard::Guard#run_on_change}, whereas # if his work leads to a system failure.
# deleted paths run on {Guard::Guard#run_on_deletion}.
#
# @param [Array<String>] files the list of files to pass to the task
# @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 [Guard::Guard] guard the Guard to execute
# @param [Symbol] task the task to run # @param [Symbol] task_to_supervise the task to run
# @param [Array] args the arguments for the task # @param [Array] args the arguments for the task
# @raise [:task_has_failed] when task has failed # @return [Boolean, Exception] the result of the Guard
# #
def run_supervised_task(guard, task, *args) def supervised_task(guard, task_to_supervise, *args)
catch guard_symbol(guard) do guard.hook("#{ task_to_supervise }_begin", *args)
guard.hook("#{ task }_begin", *args) result = guard.send(task_to_supervise, *args)
result = guard.send(task, *args) guard.hook("#{ task_to_supervise }_end", result)
guard.hook("#{ task }_end", result)
result result
end
rescue Exception => ex rescue Exception => ex
UI.error("#{ guard.class.name } failed to achieve its <#{ task.to_s }>, exception was:" + UI.error("#{ guard.class.name } failed to achieve its <#{ task_to_supervise.to_s }>, exception was:" +
"\n#{ ex.class }: #{ ex.message }\n#{ ex.backtrace.join("\n") }") "\n#{ ex.class }: #{ ex.message }\n#{ ex.backtrace.join("\n") }")
guards.delete guard guards.delete guard
UI.info("\n#{ guard.class.name } has just been fired") UI.info("\n#{ guard.class.name } has just been fired")
ex ex
end 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. # Add a Guard to use.
# #
# @param [String] name the Guard name # @param [String] name the Guard name
# @param [Array<Watcher>] watchers the list of declared watchers # @param [Array<Watcher>] watchers the list of declared watchers
# @param [Array<Hash>] callbacks the list of callbacks # @param [Array<Hash>] callbacks the list of callbacks
# @param [Hash] options the Guard options (see the given Guard documentation) # @param [Hash] options the Guard options
# #
def add_guard(name, watchers = [], callbacks = [], options = {}) def add_guard(name, watchers = [], callbacks = [], options = {})
if name.to_sym == :ego if name.to_sym == :ego
@ -350,17 +199,11 @@ module Guard
# Add a Guard group. # Add a Guard group.
# #
# @param [String] name the group name # @param [String] name the group name
# @option options [Boolean] halt_on_fail if a task execution # @param [Hash] options the group options
# should be halted for all Guards in this group if one Guard throws `:task_has_failed` # @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 = {}) def add_group(name, options = {})
group = groups(name) @groups << { :name => name.to_sym, :options => options } unless name.nil? || @groups.find { |group| group[:name] == name }
if group.nil?
group = Group.new(name, options)
@groups << group
end
group
end end
# Tries to load the Guard main class. # Tries to load the Guard main class.

View File

@ -3,9 +3,8 @@ require 'guard/version'
module Guard module Guard
# Facade for the Guard command line interface managed by [Thor](https://github.com/wycats/thor). # 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`. # 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
@ -47,18 +46,6 @@ module Guard
:aliases => '-G', :aliases => '-G',
:banner => 'Specify a Guardfile' :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. # 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`. # This is the default task, so calling `guard` is the same as calling `guard start`.
# #
@ -73,10 +60,37 @@ module Guard
# List the Guards that are available for use in your system and marks # List the Guards that are available for use in your system and marks
# those that are currently used in your `Guardfile`. # those that are currently used in your `Guardfile`.
# #
# @see Guard::DslDescriber.list # @example Guard list output
#
# Available guards:
# bundler *
# livereload
# ronn
# rspec *
# spork
#
# See also https://github.com/guard/guard/wiki/List-of-available-Guards
# * denotes ones already in your Guardfile
#
# @see Guard::DslDescriber
# #
def list def list
::Guard::DslDescriber.list(options) Guard::DslDescriber.evaluate_guardfile(options)
installed = Guard::DslDescriber.guardfile_structure.inject([]) do |installed, group|
group[:guards].each { |guard| installed << guard[:name] } if group[:guards]
installed
end
Guard::UI.info 'Available guards:'
Guard::guard_gem_names.sort.uniq.each do |name|
Guard::UI.info " #{ name } #{ installed.include?(name) ? '*' : '' }"
end
Guard::UI.info ' '
Guard::UI.info 'See also https://github.com/guard/guard/wiki/List-of-available-Guards'
Guard::UI.info '* denotes ones already in your Guardfile'
end end
desc 'version', 'Show the Guard version' desc 'version', 'Show the Guard version'
@ -87,7 +101,7 @@ module Guard
# @see Guard::VERSION # @see Guard::VERSION
# #
def version def version
::Guard::UI.info "Guard version #{ Guard::VERSION }" Guard::UI.info "Guard version #{ Guard::VERSION }"
end end
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 at the current working directory, or insert the given GUARD to an existing Guardfile'
@ -95,12 +109,22 @@ module Guard
# Appends the Guard template to the `Guardfile`, or creates an initial # Appends the Guard template to the `Guardfile`, or creates an initial
# `Guardfile` when no Guard name is passed. # `Guardfile` when no Guard name is passed.
# #
# @see Guard.initialize_template
#
# @param [String] guard_name the name of the Guard to initialize # @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 guard_name
guard_class = ::Guard.get_guard_class(guard_name)
guard_class.init(guard_name)
else
if File.exist?('Guardfile')
puts 'Writing new Guardfile to #{Dir.pwd}/Guardfile'
FileUtils.cp(File.expand_path('../templates/Guardfile', __FILE__), 'Guardfile')
else
Guard::UI.error "Guardfile already exists at #{ Dir.pwd }/Guardfile"
exit 1
end
end
end end
desc 'show', 'Show all defined Guards and their options' desc 'show', 'Show all defined Guards and their options'
@ -109,10 +133,40 @@ module Guard
# Shows all Guards and their options that are defined in # Shows all Guards and their options that are defined in
# the `Guardfile`. # the `Guardfile`.
# #
# @see Guard::DslDescriber.show # @example guard show output
#
# (global):
# bundler
# coffeescript: input => "app/assets/javascripts", noop => true
# jasmine
# rspec: cli => "--fail-fast --format Fuubar
#
# @see Guard::DslDescriber
# #
def show def show
::Guard::DslDescriber.show(options) Guard::DslDescriber.evaluate_guardfile(options)
Guard::DslDescriber.guardfile_structure.each do |group|
unless group[:guards].empty?
if group[:group]
Guard::UI.info "Group #{ group[:group] }:"
else
Guard::UI.info '(global):'
end
group[:guards].each do |guard|
line = " #{ guard[:name] }"
unless guard[:options].empty?
line += ": #{ guard[:options].collect { |k, v| "#{ k } => #{ v.inspect }" }.join(', ') }"
end
Guard::UI.info line
end
end
end
Guard::UI.info ''
end end
end end

View File

@ -81,6 +81,7 @@ module Guard
# Evaluate the DSL methods in the `Guardfile`. # Evaluate the DSL methods in the `Guardfile`.
# #
# @param [Hash] options the Guard options
# @option options [Array<Symbol,String>] groups the groups to evaluate # @option options [Array<Symbol,String>] groups the groups to evaluate
# @option options [String] guardfile the path to a valid Guardfile # @option options [String] guardfile the path to a valid Guardfile
# @option options [String] guardfile_contents a string representing the content of a valid Guardfile # @option options [String] guardfile_contents a string representing the content of a valid Guardfile

View File

@ -2,8 +2,6 @@ require 'guard/dsl'
module Guard module Guard
autoload :UI, 'guard/ui'
# The DslDescriber overrides methods to create an internal structure # The DslDescriber overrides methods to create an internal structure
# of the Guardfile that is used in some inspection utility methods # of the Guardfile that is used in some inspection utility methods
# like the CLI commands `show` and `list`. # like the CLI commands `show` and `list`.
@ -13,97 +11,10 @@ module Guard
# #
class DslDescriber < Dsl class DslDescriber < Dsl
@@guardfile_structure = [ { :guards => [] } ]
class << self 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. # Get the Guardfile structure.
# #
# @return [Array<Hash>] the structure # @return [Array<Hash>] the structure
@ -111,7 +22,6 @@ module Guard
def guardfile_structure def guardfile_structure
@@guardfile_structure @@guardfile_structure
end end
end end
private private
@ -140,7 +50,7 @@ module Guard
# #
# @see Guard::Dsl#guard # @see Guard::Dsl#guard
# #
def guard(name, options = { }) def guard(name, options = {})
node = (@group ? @@guardfile_structure.last : @@guardfile_structure.first) node = (@group ? @@guardfile_structure.last : @@guardfile_structure.first)
node[:guards] << { :name => name, :options => options } node[:guards] << { :name => name, :options => options }

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

@ -2,37 +2,12 @@ module Guard
# Main class that every Guard implementation must subclass. # Main class that every Guard implementation must subclass.
# #
# Guard will trigger the `start`, `stop`, `reload`, `run_all`, `run_on_change` and # Guard will trigger the `start`, `stop`, `reload`, `run_all` and `run_on_change`
# `run_on_deletion` task methods depending on user interaction and file modification. # 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 # Each Guard should provide a template Guardfile located within the Gem
# at `lib/guard/guard-name/templates/Guardfile`. # 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 include Hook
@ -41,12 +16,10 @@ module Guard
# Initialize a Guard. # Initialize a Guard.
# #
# @param [Array<Guard::Watcher>] watchers the Guard file watchers # @param [Array<Guard::Watcher>] watchers the Guard file watchers
# @param [Hash] options the custom Guard options # @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 @group = options.delete(:group) || :default
@watchers, @options = watchers, options @watchers, @options = watchers, options
end end
@ -61,69 +34,55 @@ module 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. # Call once when Guard starts. Please override initialize method to init stuff.
# #
# @raise [:task_has_failed] when start has failed # @return [Boolean] Whether the start action was successful or not
# @return [Object] the task result
# #
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 [Boolean] Whether the stop action was successful or not
# @return [Object] the task result
# #
def stop def stop
true
end end
# Called when `reload|r|z + enter` is pressed. # Should be 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 [Boolean] Whether the reload action was successful or not
# @return [Object] the task result
# #
def reload def reload
true
end end
# Called when just `enter` is pressed # Should be used for long action like running all specs/tests/...
# This method should be principally used for long action like running all specs/tests/...
# #
# @raise [:task_has_failed] when run_all has failed # @return [Boolean] Whether the run_all action was successful or not
# @return [Object] the task result
# #
def run_all def run_all
true
end end
# Called on file(s) modifications that the Guard watches. # Will be triggered when a file change matched a watcher.
# #
# @param [Array<String>] paths the changes files or paths # @param [Array<String>] paths the changes files or paths
# @raise [:task_has_failed] when run_on_change has failed # @return [Boolean] Whether the run_all action was successful or not
# @return [Object] the task result
# #
def run_on_change(paths) def run_on_change(paths)
end true
# 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 end

View File

@ -11,34 +11,67 @@ module Guard
# - Everything else => Run all # - Everything else => Run all
# #
class Interactor class Interactor
class LockException < Exception; end
class UnlockException < Exception; end
attr_reader :locked
# Initialize the interactor in unlocked state.
#
def initialize
@locked = false
end
# Start the interactor in its own thread. # Start the interactor in its own thread.
# #
def start def start
return if ENV["GUARD_ENV"] == 'test' return if ENV["GUARD_ENV"] == 'test'
if !@thread || @thread.stop? @thread = Thread.new do
@thread = Thread.new do loop do
while entry = $stdin.gets.chomp begin
case entry if !@locked && (entry = $stdin.gets)
when 'stop', 'quit', 'exit', 's', 'q', 'e' entry.gsub! /\n/, ''
::Guard.stop case entry
when 'reload', 'r', 'z' when 'stop', 'quit', 'exit', 's', 'q', 'e'
::Guard::Dsl.reevaluate_guardfile ::Guard.stop
::Guard.reload when 'reload', 'r', 'z'
when 'pause', 'p' ::Guard.reload
::Guard.pause when 'pause', 'p'
else ::Guard.pause
::Guard.run_all else
::Guard.run_all
end
end end
rescue LockException
lock
rescue UnlockException
unlock
end end
end end
end end
end end
def stop_if_not_current # Lock the interactor.
unless Thread.current == @thread #
@thread.kill def lock
if !@thread || @thread == Thread.current
@locked = true
else
@thread.raise(LockException)
end end
end end
# Unlock the interactor.
#
def unlock
if !@thread || @thread == Thread.current
@locked = false
else
@thread.raise(UnlockException)
end
end
end end
end end

View File

@ -19,11 +19,7 @@ module Guard
DEFAULT_IGNORE_PATHS = %w[. .. .bundle .git log tmp vendor] DEFAULT_IGNORE_PATHS = %w[. .. .bundle .git log tmp vendor]
attr_accessor :changed_files attr_accessor :changed_files
attr_reader :directory, :ignore_paths attr_reader :directory, :ignore_paths, :locked
def paused?
@paused
end
# Select the appropriate listener implementation for the # Select the appropriate listener implementation for the
# current OS and initializes it. # current OS and initializes it.
@ -47,19 +43,18 @@ module Guard
# Initialize the listener. # Initialize the listener.
# #
# @param [String] directory the root directory to listen to # @param [String] directory the root directory to listen to
# @param [Hash] options the listener options
# @option options [Boolean] relativize_paths use only relative paths # @option options [Boolean] relativize_paths use only relative paths
# @option options [Array<String>] ignore_paths the paths to ignore by the listener # @option options [Array<String>] ignore_paths the paths to ignore by the listener
# #
def initialize(directory = Dir.pwd, options = {}) def initialize(directory = Dir.pwd, options = {})
@directory = directory.to_s @directory = directory.to_s
@sha1_checksums_hash = {} @sha1_checksums_hash = { }
@file_timestamp_hash = {} @relativize_paths = options.fetch(:relativize_paths, true)
@relativize_paths = options.fetch(:relativize_paths, true) @changed_files = []
@changed_files = [] @locked = false
@paused = false @ignore_paths = DEFAULT_IGNORE_PATHS
@ignore_paths = DEFAULT_IGNORE_PATHS @ignore_paths |= options[:ignore_paths] if options[: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 start_reactor
@ -72,7 +67,7 @@ module Guard
Thread.new do Thread.new do
loop do loop do
if @changed_files != [] && !@paused if @changed_files != [] && !@locked
changed_files = @changed_files.dup changed_files = @changed_files.dup
clear_changed_files clear_changed_files
::Guard.run_on_change(changed_files) ::Guard.run_on_change(changed_files)
@ -87,7 +82,6 @@ module Guard
# #
def start def start
watch(@directory) watch(@directory)
timestamp_files
end end
# Stop listening for events. # Stop listening for events.
@ -95,16 +89,16 @@ module Guard
def stop def stop
end end
# Pause the listener to ignore change events. # Lock the listener to ignore change events.
# #
def pause def lock
@paused = true @locked = true
end end
# Unpause the listener to listen again to change events. # Unlock the listener to listen again to change events.
# #
def run def unlock
@paused = false @locked = false
end end
# Clear the list of changed files. # Clear the list of changed files.
@ -129,33 +123,13 @@ module Guard
# Get the modified files. # 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 [Array<String>] dirs the watched directories
# @param [Hash] options the listener options # @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 last_event = @last_event
files = []
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 update_last_event
files.concat(potentially_modified_files(dirs, options).select { |path| file_modified?(path, last_event) }) files = potentially_modified_files(dirs, options).select { |path| file_modified?(path, last_event) }
relativize_paths(files) relativize_paths(files)
end end
@ -184,7 +158,7 @@ module Guard
def relativize_paths(paths) def relativize_paths(paths)
return paths unless relativize_paths? return paths unless relativize_paths?
paths.map do |path| paths.map do |path|
path.gsub(%r{^(!)?#{ @directory }/},'\1') path.gsub(%r{^#{ @directory }/}, '')
end end
end end
@ -196,12 +170,6 @@ module Guard
!!@relativize_paths !!@relativize_paths
end 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. # Removes the ignored paths from the directory list.
# #
# @param [Array<String>] dirs the directory to listen to # @param [Array<String>] dirs the directory to listen to
@ -219,8 +187,8 @@ module Guard
# Gets a list of files that are in the modified directories. # Gets a list of files that are in the modified directories.
# #
# @param [Array<String>] dirs the list of directories # @param [Array<String>] dirs the list of directories
# @param [Hash] options the find file option # @param [Hash] options the options
# @option options [Symbol] all whether to files in sub directories # @option options [Symbol] all whether to include all files
# #
def potentially_modified_files(dirs, options = {}) def potentially_modified_files(dirs, options = {})
paths = exclude_ignored_paths(dirs) paths = exclude_ignored_paths(dirs)
@ -258,12 +226,6 @@ module Guard
elsif mtime > last_event.to_i elsif mtime > last_event.to_i
set_sha1_checksums_hash(path, sha1_checksum(path)) set_sha1_checksums_hash(path, sha1_checksum(path))
true true
elsif @watch_all_modifications
ts = file_timestamp(path)
if ts != @file_timestamp_hash[path]
set_file_timestamp_hash(path, ts)
true
end
else else
false false
end end
@ -286,15 +248,6 @@ module Guard
end 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. # Set the current checksum of a file.
# #
# @param [String] path the file path # @param [String] path the file path
@ -304,15 +257,6 @@ module Guard
@sha1_checksums_hash[path] = sha1_checksum @sha1_checksums_hash[path] = sha1_checksum
end 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. # Calculates the SHA1 checksum of a file.
# #
# @param [String] path the path to the file # @param [String] path the path to the file

View File

@ -26,6 +26,7 @@ module Guard
def stop def stop
super super
@stop = true @stop = true
sleep(@latency)
end end
# Check if the listener is usable on the current OS. # Check if the listener is usable on the current OS.

View File

@ -15,276 +15,198 @@ module Guard
# Application name as shown in the specific notification settings # Application name as shown in the specific notification settings
APPLICATION_NAME = "Guard" APPLICATION_NAME = "Guard"
class << self # Turn notifications off.
#
def self.turn_off
ENV["GUARD_NOTIFY"] = 'false'
end
attr_accessor :growl_library, :gntp # Turn notifications on. This tries to load the platform
# specific notification library.
# Turn notifications off. #
# # @return [Boolean] whether the notification could be enabled.
def turn_off #
ENV["GUARD_NOTIFY"] = 'false' def self.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
end
# Show a message with the system notification.
#
# @see .image_path
#
# @param [String] the message to show
# @param [Hash] options the notification options
# @option options [Symbol, String] image the image symbol or path to an image
# @option options [String] title the notification title
#
def self.notify(message, options = {})
if enabled?
image = options.delete(:image) || :success
title = options.delete(:title) || "Guard"
# 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'] case RbConfig::CONFIG['target_os']
when /darwin/i when /darwin/i
require_growl notify_mac(title, message, image, options)
when /linux/i when /linux/i
require_libnotify notify_linux(title, message, image, options)
when /mswin|mingw/i when /mswin|mingw/i
require_rbnotifu notify_windows(title, message, image, options)
end end
end end
end
# Show a message with the system notification. # Test if the notifications are enabled and available.
# #
# @see .image_path # @return [Boolean] whether the notifications are available
# #
# @param [String] the message to show def self.enabled?
# @option options [Symbol, String] image the image symbol or path to an image ENV["GUARD_NOTIFY"] == 'true'
# @option options [String] title the notification title end
#
def notify(message, options = { })
if enabled?
image = options.delete(:image) || :success
title = options.delete(:title) || "Guard"
case RbConfig::CONFIG['target_os'] private
when /darwin/i
notify_mac(title, message, image, options) # Send a message to Growl either with the `growl` gem or the `growl_notify` gem.
when /linux/i #
notify_linux(title, message, image, options) # @param [String] title the notification title
when /mswin|mingw/i # @param [String] message the message to show
notify_windows(title, message, image, options) # @param [Symbol, String] the image to user
end # @param [Hash] options the growl options
end #
def self.notify_mac(title, message, image, options)
require_growl # need for guard-rspec formatter that is called out of guard scope
default_options = { :title => title, :icon => image_path(image), :name => APPLICATION_NAME }
default_options.merge!(options)
if defined?(GrowlNotify)
default_options[:description] = message
default_options[:application_name] = APPLICATION_NAME
default_options.delete(:name)
GrowlNotify.send_notification(default_options) if enabled?
else
Growl.notify message, default_options.merge(options) if enabled?
end end
end
# Test if the notifications are enabled and available. # Send a message to libnotify.
# #
# @return [Boolean] whether the notifications are available # @param [String] title the notification title
# # @param [String] message the message to show
def enabled? # @param [Symbol, String] the image to user
ENV["GUARD_NOTIFY"] == 'true' # @param [Hash] options the libnotify options
#
def self.notify_linux(title, message, image, options)
require_libnotify # need for guard-rspec formatter that is called out of guard scope
default_options = { :body => message, :summary => title, :icon_path => image_path(image), :transient => true }
Libnotify.show default_options.merge(options) if enabled?
end
# Send a message to notifu.
#
# @param [String] title the notification title
# @param [String] message the message to show
# @param [Symbol, String] the image to user
# @param [Hash] options the notifu options
#
def self.notify_windows(title, message, image, options)
require_rbnotifu # need for guard-rspec formatter that is called out of guard scope
default_options = { :message => message, :title => title, :type => image_level(image), :time => 3 }
Notifu.show default_options.merge(options) if enabled?
end
# Get the image path for an image symbol.
#
# Known symbols are:
#
# - failed
# - pending
# - success
#
# @param [Symbol] image the image name
# @return [String] the image path
#
def self.image_path(image)
images_path = Pathname.new(File.dirname(__FILE__)).join('../../images')
case image
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
end
private # The notification level type for the given image.
#
# Send a message to Growl either with the `growl` gem or the `growl_notify` gem. # @param [Symbol] image the image
# # @return [Symbol] the level
# @param [String] title the notification title #
# @param [String] message the message to show def self.image_level(image)
# @param [Symbol, String] the image to user case image
# @param [Hash] options the growl options when :failed
# :error
def notify_mac(title, message, image, options = { }) when :pending
require_growl # need for guard-rspec formatter that is called out of guard scope :warn
when :success
notification = { :title => title, :icon => image_path(image) }.merge(options) :info
else
case self.growl_library :info
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 end
end
# Send a message to libnotify. # Try to safely load growl and turns notifications
# # off on load failure.
# @param [String] title the notification title #
# @param [String] message the message to show def self.require_growl
# @param [Symbol, String] the image to user begin
# @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' require 'growl_notify'
begin if GrowlNotify.application_name != APPLICATION_NAME
if GrowlNotify.application_name != APPLICATION_NAME GrowlNotify.config do |c|
GrowlNotify.config do |c| c.notifications = c.default_notifications = [ APPLICATION_NAME ]
c.notifications = c.default_notifications = [APPLICATION_NAME] c.application_name = c.notifications.first
c.application_name = c.notifications.first
end
end end
rescue ::GrowlNotify::GrowlNotFound
turn_off
UI.info "Please install Growl from http://growl.info"
end end
:growl_notify
rescue LoadError 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' require 'growl'
:growl
rescue LoadError
end end
rescue LoadError
# Try to safely load libnotify and turns notifications turn_off
# off on load failure. UI.info "Please install growl_notify or growl gem for Mac OS X notification support and add it to your Gemfile"
#
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
# Try to safely load libnotify and turns notifications
# off on load failure.
#
def self.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 self.require_rbnotifu
require 'rb-notifu'
rescue LoadError
turn_off
UI.info "Please install rb-notifu gem for Windows notification support and add it to your Gemfile"
end
end end
end end

View File

@ -1,11 +1,6 @@
module Guard module Guard
# The UI class helps to format messages for the user. Everything that is logged # The UI class helps to format messages for the user.
# 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
@ -15,55 +10,59 @@ module Guard
# Show an info message. # Show an info message.
# #
# @param [String] message the message to show # @param [String] message the message to show
# @param [Hash] options the options
# @option options [Boolean] reset whether to clean the output before # @option options [Boolean] reset whether to clean the output before
# #
def info(message, options = { }) def info(message, options = { })
unless ENV['GUARD_ENV'] == 'test' unless ENV['GUARD_ENV'] == 'test'
reset_line if options[:reset] reset_line if options[:reset]
STDERR.puts color(message) if message != '' puts color(message) if message != ''
end end
end end
# Show a red error message that is prefixed with ERROR. # Show a red error message that is prefixed with ERROR.
# #
# @param [String] message the message to show # @param [String] message the message to show
# @param [Hash] options the options
# @option options [Boolean] reset whether to clean the output before # @option options [Boolean] reset whether to clean the output before
# #
def error(message, options = { }) def error(message, options = { })
unless ENV['GUARD_ENV'] == 'test' unless ENV['GUARD_ENV'] == 'test'
reset_line if options[:reset] reset_line if options[:reset]
STDERR.puts color('ERROR: ', :red) + message puts color('ERROR: ', :red) + message
end end
end end
# Show a red deprecation message that is prefixed with DEPRECATION. # Show a red deprecation message that is prefixed with DEPRECATION.
# #
# @param [String] message the message to show # @param [String] message the message to show
# @param [Hash] options the options
# @option options [Boolean] reset whether to clean the output before # @option options [Boolean] reset whether to clean the output before
# #
def deprecation(message, options = { }) def deprecation(message, options = { })
unless ENV['GUARD_ENV'] == 'test' unless ENV['GUARD_ENV'] == 'test'
reset_line if options[:reset] reset_line if options[:reset]
STDERR.puts color('DEPRECATION: ', :red) + message puts color('DEPRECATION: ', :red) + message
end end
end end
# Show a debug message that is prefixed with DEBUG and a timestamp. # Show a debug message that is prefixed with DEBUG and a timestamp.
# #
# @param [String] message the message to show # @param [String] message the message to show
# @param [Hash] options the options
# @option options [Boolean] reset whether to clean the output before # @option options [Boolean] reset whether to clean the output before
# #
def debug(message, options = { }) def debug(message, options = { })
unless ENV['GUARD_ENV'] == 'test' unless ENV['GUARD_ENV'] == 'test'
reset_line if options[:reset] reset_line if options[:reset]
STDERR.puts color("DEBUG (#{Time.now.strftime('%T')}): ", :yellow) + message if ::Guard.options && ::Guard.options[:debug] puts color("DEBUG (#{Time.now.strftime('%T')}): ", :yellow) + message if ::Guard.options && ::Guard.options[:debug]
end end
end end
# Reset a line. # Reset a line.
# #
def reset_line def reset_line
STDERR.print(color_enabled? ? "\r\e[0m" : "\r\n") print(color_enabled? ? "\r\e[0m" : "\r\n")
end end
# Clear the output. # Clear the output.

View File

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

View File

@ -38,7 +38,7 @@ module Guard
# #
# @param [Guard::Guard] guard the guard which watchers are used # @param [Guard::Guard] guard the guard which watchers are used
# @param [Array<String>] files the changed files # @param [Array<String>] files the changed files
# @return [Array<Object>] the matched watcher response # @return [Array<String>] the matched files
# #
def self.match_files(guard, files) def self.match_files(guard, files)
guard.watchers.inject([]) do |paths, watcher| guard.watchers.inject([]) do |paths, watcher|
@ -46,18 +46,14 @@ module Guard
if matches = watcher.match_file?(file) if matches = watcher.match_file?(file)
if watcher.action if watcher.action
result = watcher.call_action(matches) result = watcher.call_action(matches)
if guard.options[:any_return] paths << Array(result) if result.respond_to?(:empty?) && !result.empty?
paths << result
elsif result.respond_to?(:empty?) && !result.empty?
paths << Array(result)
end
else else
paths << matches[0] paths << matches[0]
end end
end end
end end
guard.options[:any_return] ? paths : paths.flatten.map { |p| p.to_s } paths.flatten.map { |p| p.to_s }
end end
end end

502
man/guard.html Normal file
View File

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

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

View File

@ -1,70 +1,40 @@
require 'spec_helper' require 'spec_helper'
describe Guard::DslDescriber do describe Guard::DslDescriber do
before(:each) do
let(:describer) { ::Guard::DslDescriber } ::Guard.stub!(:guards).and_return([mock('Guard')])
user_config_path = File.expand_path(File.join('~', '.guard.rb'))
let(:guardfile) do File.stub(:exist?).with(user_config_path) { false }
<<-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 end
subject { described_class }
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
it 'should evaluate a Guardfile and create the right structure' do
mixed_guardfile_string = <<-GUARD
guard 'test', :a => :b do
watch('c')
end
group :a do
guard 'test' do
watch('c')
end
end
group "b" do
guard 'another' do
watch('c')
end
end
GUARD
subject.evaluate_guardfile(:guardfile_contents => mixed_guardfile_string)
subject.guardfile_structure.should == [
{ :guards => [ { :name => 'test', :options => { :a => :b } } ] },
{ :group => :a, :guards => [ { :name => 'test', :options => {} } ] },
{ :group => :b, :guards => [ { :name => 'another', :options => {} } ] }
]
end
end end

View File

@ -2,7 +2,7 @@ require 'spec_helper'
require 'guard/guard' require 'guard/guard'
describe Guard::Dsl do describe Guard::Dsl do
subject { described_class }
class Guard::Dummy < Guard::Guard; end class Guard::Dummy < Guard::Guard; end
before(:each) do before(:each) do
@ -24,24 +24,24 @@ describe Guard::Dsl do
it "should use a string for initializing" do it "should use a string for initializing" do
Guard::UI.should_not_receive(:error) Guard::UI.should_not_receive(:error)
lambda { described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error lambda { subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
described_class.guardfile_contents.should == valid_guardfile_string subject.guardfile_contents.should == valid_guardfile_string
end end
it "should use a given file over the default loc" do it "should use a given file over the default loc" do
fake_guardfile('/abc/Guardfile', "guard :foo") fake_guardfile('/abc/Guardfile', "guard :foo")
Guard::UI.should_not_receive(:error) Guard::UI.should_not_receive(:error)
lambda { described_class.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error lambda { subject.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
described_class.guardfile_contents.should == "guard :foo" subject.guardfile_contents.should == "guard :foo"
end end
it "should use a default file if no other options are given" do it "should use a default file if no other options are given" do
fake_guardfile(@local_guardfile_path, "guard :bar") fake_guardfile(@local_guardfile_path, "guard :bar")
Guard::UI.should_not_receive(:error) Guard::UI.should_not_receive(:error)
lambda { described_class.evaluate_guardfile }.should_not raise_error lambda { subject.evaluate_guardfile }.should_not raise_error
described_class.guardfile_contents.should == "guard :bar" subject.guardfile_contents.should == "guard :bar"
end end
it "should use a string over any other method" do it "should use a string over any other method" do
@ -49,8 +49,8 @@ describe Guard::Dsl do
fake_guardfile(@local_guardfile_path, "guard :bar") fake_guardfile(@local_guardfile_path, "guard :bar")
Guard::UI.should_not_receive(:error) Guard::UI.should_not_receive(:error)
lambda { described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error lambda { subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
described_class.guardfile_contents.should == valid_guardfile_string subject.guardfile_contents.should == valid_guardfile_string
end end
it "should use the given Guardfile over default Guardfile" do it "should use the given Guardfile over default Guardfile" do
@ -58,31 +58,31 @@ describe Guard::Dsl do
fake_guardfile(@local_guardfile_path, "guard :bar") fake_guardfile(@local_guardfile_path, "guard :bar")
Guard::UI.should_not_receive(:error) Guard::UI.should_not_receive(:error)
lambda { described_class.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error lambda { subject.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
described_class.guardfile_contents.should == "guard :foo" subject.guardfile_contents.should == "guard :foo"
end end
it 'should append the user config file if present' do it 'should append the user config file if present' do
fake_guardfile('/abc/Guardfile', "guard :foo") fake_guardfile('/abc/Guardfile', "guard :foo")
fake_guardfile(@user_config_path, "guard :bar") fake_guardfile(@user_config_path, "guard :bar")
Guard::UI.should_not_receive(:error) Guard::UI.should_not_receive(:error)
lambda { described_class.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error lambda { subject.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
described_class.guardfile_contents_with_user_config.should == "guard :foo\nguard :bar" subject.guardfile_contents_with_user_config.should == "guard :foo\nguard :bar"
end end
end end
it "displays an error message when no Guardfile is found" do it "displays an error message when no Guardfile is found" do
described_class.stub(:guardfile_default_path).and_return("no_guardfile_here") subject.stub(:guardfile_default_path).and_return("no_guardfile_here")
Guard::UI.should_receive(:error).with("No Guardfile found, please create one with `guard init`.") Guard::UI.should_receive(:error).with("No Guardfile found, please create one with `guard init`.")
lambda { described_class.evaluate_guardfile }.should raise_error lambda { subject.evaluate_guardfile }.should raise_error
end end
it "displays an error message when no guard are defined in Guardfile" do it "displays an error message when no guard are defined in Guardfile" do
::Guard::Dsl.stub!(:instance_eval_guardfile) ::Guard::Dsl.stub!(:instance_eval_guardfile)
::Guard.stub!(:guards).and_return([]) ::Guard.stub!(:guards).and_return([])
Guard::UI.should_receive(:error) Guard::UI.should_receive(:error)
described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string)
end end
describe "correctly reads data from its valid data source" do describe "correctly reads data from its valid data source" do
@ -90,22 +90,22 @@ describe Guard::Dsl do
disable_user_config disable_user_config
it "reads correctly from a string" do it "reads correctly from a string" do
lambda { described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error lambda { subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
described_class.guardfile_contents.should == valid_guardfile_string subject.guardfile_contents.should == valid_guardfile_string
end end
it "reads correctly from a Guardfile" do it "reads correctly from a Guardfile" do
fake_guardfile('/abc/Guardfile', "guard :foo" ) fake_guardfile('/abc/Guardfile', "guard :foo" )
lambda { described_class.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error lambda { subject.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
described_class.guardfile_contents.should == "guard :foo" subject.guardfile_contents.should == "guard :foo"
end end
it "reads correctly from a Guardfile" do it "reads correctly from a Guardfile" do
fake_guardfile(File.join(Dir.pwd, 'Guardfile'), valid_guardfile_string) fake_guardfile(File.join(Dir.pwd, 'Guardfile'), valid_guardfile_string)
lambda { described_class.evaluate_guardfile }.should_not raise_error lambda { subject.evaluate_guardfile }.should_not raise_error
described_class.guardfile_contents.should == valid_guardfile_string subject.guardfile_contents.should == valid_guardfile_string
end end
end end
@ -117,14 +117,14 @@ describe Guard::Dsl do
File.stub!(:read).with('/def/Guardfile') { raise Errno::EACCES.new("permission error") } File.stub!(:read).with('/def/Guardfile') { raise Errno::EACCES.new("permission error") }
Guard::UI.should_receive(:error).with(/^Error reading file/) Guard::UI.should_receive(:error).with(/^Error reading file/)
lambda { described_class.evaluate_guardfile(:guardfile => '/def/Guardfile') }.should raise_error lambda { subject.evaluate_guardfile(:guardfile => '/def/Guardfile') }.should raise_error
end end
it "raises error when given Guardfile doesn't exist" do it "raises error when given Guardfile doesn't exist" do
File.stub!(:exist?).with('/def/Guardfile') { false } File.stub!(:exist?).with('/def/Guardfile') { false }
Guard::UI.should_receive(:error).with(/No Guardfile exists at/) Guard::UI.should_receive(:error).with(/No Guardfile exists at/)
lambda { described_class.evaluate_guardfile(:guardfile => '/def/Guardfile') }.should raise_error lambda { subject.evaluate_guardfile(:guardfile => '/def/Guardfile') }.should raise_error
end end
it "raises error when resorting to use default, finds no default" do it "raises error when resorting to use default, finds no default" do
@ -132,24 +132,24 @@ describe Guard::Dsl do
File.stub!(:exist?).with(@home_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`.") Guard::UI.should_receive(:error).with("No Guardfile found, please create one with `guard init`.")
lambda { described_class.evaluate_guardfile }.should raise_error lambda { subject.evaluate_guardfile }.should raise_error
end end
it "raises error when guardfile_content ends up empty or nil" do it "raises error when guardfile_content ends up empty or nil" do
Guard::UI.should_receive(:error).with(/The command file/) Guard::UI.should_receive(:error).with(/The command file/)
lambda { described_class.evaluate_guardfile(:guardfile_contents => "") }.should raise_error lambda { subject.evaluate_guardfile(:guardfile_contents => "") }.should raise_error
end end
it "doesn't raise error when guardfile_content is nil (skipped)" do it "doesn't raise error when guardfile_content is nil (skipped)" do
Guard::UI.should_not_receive(:error) Guard::UI.should_not_receive(:error)
lambda { described_class.evaluate_guardfile(:guardfile_contents => nil) }.should_not raise_error lambda { subject.evaluate_guardfile(:guardfile_contents => nil) }.should_not raise_error
end end
end end
it "displays an error message when Guardfile is not valid" do it "displays an error message when Guardfile is not valid" do
Guard::UI.should_receive(:error).with(/Invalid Guardfile, original error is:/) Guard::UI.should_receive(:error).with(/Invalid Guardfile, original error is:/)
lambda { described_class.evaluate_guardfile(:guardfile_contents => invalid_guardfile_string ) }.should raise_error lambda { subject.evaluate_guardfile(:guardfile_contents => invalid_guardfile_string ) }.should raise_error
end end
describe ".reevaluate_guardfile" do describe ".reevaluate_guardfile" do
@ -157,10 +157,10 @@ describe Guard::Dsl do
it "resets already definded guards before calling evaluate_guardfile" do it "resets already definded guards before calling evaluate_guardfile" do
Guard::Notifier.turn_off Guard::Notifier.turn_off
described_class.evaluate_guardfile(:guardfile_contents => invalid_guardfile_string) subject.evaluate_guardfile(:guardfile_contents => invalid_guardfile_string)
::Guard.guards.should_not be_empty ::Guard.guards.should_not be_empty
::Guard::Dsl.should_receive(:evaluate_guardfile) ::Guard::Dsl.should_receive(:evaluate_guardfile)
described_class.reevaluate_guardfile subject.reevaluate_guardfile
::Guard.guards.should be_empty ::Guard.guards.should be_empty
end end
end end
@ -173,14 +173,14 @@ describe Guard::Dsl do
context "when there is a local Guardfile" do context "when there is a local Guardfile" do
it "returns the path to the local Guardfile" do it "returns the path to the local Guardfile" do
File.stub(:exist?).with(local_path).and_return(true) File.stub(:exist?).with(local_path).and_return(true)
described_class.guardfile_default_path.should == local_path subject.guardfile_default_path.should == local_path
end end
end end
context "when there is a Guardfile in the user's home directory" do context "when there is a Guardfile in the user's home directory" do
it "returns the path to the user Guardfile" do it "returns the path to the user Guardfile" do
File.stub(:exist?).with(user_path).and_return(true) File.stub(:exist?).with(user_path).and_return(true)
described_class.guardfile_default_path.should == user_path subject.guardfile_default_path.should == user_path
end end
end end
@ -188,34 +188,34 @@ describe Guard::Dsl do
it "returns the path to the local Guardfile" do it "returns the path to the local Guardfile" do
File.stub(:exist?).with(local_path).and_return(true) File.stub(:exist?).with(local_path).and_return(true)
File.stub(:exist?).with(user_path).and_return(true) File.stub(:exist?).with(user_path).and_return(true)
described_class.guardfile_default_path.should == local_path subject.guardfile_default_path.should == local_path
end end
end end
end end
describe ".guardfile_include?" do describe ".guardfile_include?" do
it "detects a guard specified by a string with double quotes" do it "detects a guard specified by a string with double quotes" do
described_class.stub(:guardfile_contents => 'guard "test" {watch("c")}') subject.stub(:guardfile_contents => 'guard "test" {watch("c")}')
described_class.guardfile_include?('test').should be_true subject.guardfile_include?('test').should be_true
end end
it "detects a guard specified by a string with single quote" do it "detects a guard specified by a string with single quote" do
described_class.stub(:guardfile_contents => 'guard \'test\' {watch("c")}') subject.stub(:guardfile_contents => 'guard \'test\' {watch("c")}')
described_class.guardfile_include?('test').should be_true subject.guardfile_include?('test').should be_true
end end
it "detects a guard specified by a symbol" do it "detects a guard specified by a symbol" do
described_class.stub(:guardfile_contents => 'guard :test {watch("c")}') subject.stub(:guardfile_contents => 'guard :test {watch("c")}')
described_class.guardfile_include?('test').should be_true subject.guardfile_include?('test').should be_true
end end
it "detects a guard wrapped in parentheses" do it "detects a guard wrapped in parentheses" do
described_class.stub(:guardfile_contents => 'guard(:test) {watch("c")}') subject.stub(:guardfile_contents => 'guard(:test) {watch("c")}')
described_class.guardfile_include?('test').should be_true subject.guardfile_include?('test').should be_true
end end
end end
@ -226,7 +226,7 @@ describe Guard::Dsl do
::Guard.stub!(:listener).and_return(mock('Listener')) ::Guard.stub!(:listener).and_return(mock('Listener'))
::Guard.listener.should_receive(:ignore_paths).and_return(ignore_paths = ['faz']) ::Guard.listener.should_receive(:ignore_paths).and_return(ignore_paths = ['faz'])
described_class.evaluate_guardfile(:guardfile_contents => "ignore_paths 'foo', 'bar'") subject.evaluate_guardfile(:guardfile_contents => "ignore_paths 'foo', 'bar'")
ignore_paths.should == ['faz', 'foo', 'bar'] ignore_paths.should == ['faz', 'foo', 'bar']
end end
end end
@ -238,14 +238,18 @@ describe Guard::Dsl do
::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default }) ::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w })
described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w]) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
::Guard.groups.should eql [{ :name => :default, :options => {} }, { :name => :w, :options => {} }]
end end
it "evaluates only the specified symbol group" do it "evaluates only the specified symbol group" do
::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default }) ::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w })
described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w]) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
::Guard.groups.should eql [{ :name => :default, :options => {} }, { :name => :w, :options => {} }]
end end
it "evaluates only the specified groups (with their options)" do it "evaluates only the specified groups (with their options)" do
@ -254,14 +258,18 @@ describe Guard::Dsl do
::Guard.should_receive(:add_guard).with('ronn', [], [], { :group => :x }) ::Guard.should_receive(:add_guard).with('ronn', [], [], { :group => :x })
::Guard.should_receive(:add_guard).with('less', [], [], { :group => :y }) ::Guard.should_receive(:add_guard).with('less', [], [], { :group => :y })
described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:x, :y]) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:x, :y])
::Guard.groups.should eql [{ :name => :default, :options => {} }, { :name => :x, :options => { :halt_on_fail => true } }, { :name => :y, :options => {} }]
end end
it "evaluates always guard outside any group (even when a group is given)" do it "evaluates always guard outside any group (even when a group is given)" do
::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default }) ::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w })
described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w]) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
::Guard.groups.should eql [{ :name => :default, :options => {} }, { :name => :w, :options => {} }]
end end
it "evaluates all groups when no group option is specified (with their options)" do it "evaluates all groups when no group option is specified (with their options)" do
@ -271,7 +279,10 @@ describe Guard::Dsl do
::Guard.should_receive(:add_guard).with('ronn', [], [], { :group => :x }) ::Guard.should_receive(:add_guard).with('ronn', [], [], { :group => :x })
::Guard.should_receive(:add_guard).with('less', [], [], { :group => :y }) ::Guard.should_receive(:add_guard).with('less', [], [], { :group => :y })
described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string)
::Guard.groups.should eql [{ :name => :default, :options => {} }, { :name => :w, :options => {} }, { :name => :x, :options => { :halt_on_fail => true } }, { :name => :y, :options => {} }]
end end
end end
@ -281,31 +292,31 @@ describe Guard::Dsl do
it "loads a guard specified as a quoted string from the DSL" do it "loads a guard specified as a quoted string from the DSL" do
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default })
described_class.evaluate_guardfile(:guardfile_contents => "guard 'test'") subject.evaluate_guardfile(:guardfile_contents => "guard 'test'")
end end
it "loads a guard specified as a double quoted string from the DSL" do it "loads a guard specified as a double quoted string from the DSL" do
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default })
described_class.evaluate_guardfile(:guardfile_contents => 'guard "test"') subject.evaluate_guardfile(:guardfile_contents => 'guard "test"')
end end
it "loads a guard specified as a symbol from the DSL" do it "loads a guard specified as a symbol from the DSL" do
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default })
described_class.evaluate_guardfile(:guardfile_contents => "guard :test") subject.evaluate_guardfile(:guardfile_contents => "guard :test")
end end
it "loads a guard specified as a symbol and called with parens from the DSL" do it "loads a guard specified as a symbol and called with parens from the DSL" do
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default })
described_class.evaluate_guardfile(:guardfile_contents => "guard(:test)") subject.evaluate_guardfile(:guardfile_contents => "guard(:test)")
end end
it "receives options when specified, from normal arg" do it "receives options when specified, from normal arg" do
::Guard.should_receive(:add_guard).with('test', [], [], { :opt_a => 1, :opt_b => 'fancy', :group => :default }) ::Guard.should_receive(:add_guard).with('test', [], [], { :opt_a => 1, :opt_b => 'fancy', :group => :default })
described_class.evaluate_guardfile(:guardfile_contents => "guard 'test', :opt_a => 1, :opt_b => 'fancy'") subject.evaluate_guardfile(:guardfile_contents => "guard 'test', :opt_a => 1, :opt_b => 'fancy'")
end end
end end
@ -320,7 +331,7 @@ describe Guard::Dsl do
watchers[1].pattern.should == 'c' watchers[1].pattern.should == 'c'
watchers[1].action.should == nil watchers[1].action.should == nil
end end
described_class.evaluate_guardfile(:guardfile_contents => " subject.evaluate_guardfile(:guardfile_contents => "
guard :dummy do guard :dummy do
watch('a') { 'b' } watch('a') { 'b' }
watch('c') watch('c')
@ -343,7 +354,7 @@ describe Guard::Dsl do
callbacks[1][:events].should == [:start_begin, :run_all_begin] callbacks[1][:events].should == [:start_begin, :run_all_begin]
callbacks[1][:listener].should == MyCustomCallback callbacks[1][:listener].should == MyCustomCallback
end end
described_class.evaluate_guardfile(:guardfile_contents => ' subject.evaluate_guardfile(:guardfile_contents => '
guard :dummy do guard :dummy do
callback(:start_end) { |guard_class, event, args| "#{guard_class} executed \'#{event}\' hook with #{args}!" } callback(:start_end) { |guard_class, event, args| "#{guard_class} executed \'#{event}\' hook with #{args}!" }
callback(MyCustomCallback, [:start_begin, :run_all_begin]) callback(MyCustomCallback, [:start_begin, :run_all_begin])

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 +0,0 @@
require 'spec_helper'
describe Guard::Guard do
describe '#initialize' do
it 'assigns the defined watchers' do
watchers = [ Guard::Watcher.new('*') ]
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
describe '#init' do
context 'when the Guard is already in the Guardfile' do
before { ::Guard::Dsl.stub(:guardfile_include?).and_return true }
it 'shows an info message' do
::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

View File

@ -2,47 +2,48 @@ require 'spec_helper'
require 'guard/guard' require 'guard/guard'
describe Guard::Hook do describe Guard::Hook do
subject { Guard::Hook }
class Guard::Dummy < Guard::Guard; end class Guard::Dummy < Guard::Guard; end
let(:guard_class) { ::Guard::Dummy } let(:guard_class) { ::Guard::Dummy }
let(:listener) { double('listener').as_null_object } let(:listener) { double('listener').as_null_object }
after { described_class.reset_callbacks! } after { subject.reset_callbacks! }
describe "--module methods--" do context "--module methods--" do
before { described_class.add_callback(listener, guard_class, :start_begin) } before { subject.add_callback(listener, guard_class, :start_begin) }
describe ".add_callback" do describe ".add_callback" do
it "can add a single callback" do it "can add a single callback" do
described_class.has_callback?(listener, guard_class, :start_begin).should be_true subject.has_callback?(listener, guard_class, :start_begin).should be_true
end end
it "can add multiple callbacks" do it "can add multiple callbacks" do
described_class.add_callback(listener, guard_class, [:event1, :event2]) subject.add_callback(listener, guard_class, [:event1, :event2])
described_class.has_callback?(listener, guard_class, :event1).should be_true subject.has_callback?(listener, guard_class, :event1).should be_true
described_class.has_callback?(listener, guard_class, :event2).should be_true subject.has_callback?(listener, guard_class, :event2).should be_true
end end
end end
describe ".notify" do describe ".notify" do
it "sends :call to the given Guard class's callbacks" do it "sends :call to the given Guard class's callbacks" do
listener.should_receive(:call).with(guard_class, :start_begin, "args") listener.should_receive(:call).with(guard_class, :start_begin, "args")
described_class.notify(guard_class, :start_begin, "args") subject.notify(guard_class, :start_begin, "args")
end end
it "runs only the given callbacks" do it "runs only the given callbacks" do
listener2 = double('listener2') listener2 = double('listener2')
described_class.add_callback(listener2, guard_class, :start_end) subject.add_callback(listener2, guard_class, :start_end)
listener2.should_not_receive(:call).with(guard_class, :start_end) listener2.should_not_receive(:call).with(guard_class, :start_end)
described_class.notify(guard_class, :start_begin) subject.notify(guard_class, :start_begin)
end end
it "runs callbacks only for the guard given" do it "runs callbacks only for the guard given" do
guard2_class = double('Guard::Dummy2').class guard2_class = double('Guard::Dummy2').class
described_class.add_callback(listener, guard2_class, :start_begin) subject.add_callback(listener, guard2_class, :start_begin)
listener.should_not_receive(:call).with(guard2_class, :start_begin) listener.should_not_receive(:call).with(guard2_class, :start_begin)
described_class.notify(guard_class, :start_begin) subject.notify(guard_class, :start_begin)
end end
end end
end end

View File

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

View File

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

View File

@ -2,26 +2,26 @@ require 'spec_helper'
require 'guard/listeners/darwin' require 'guard/listeners/darwin'
describe Guard::Darwin do describe Guard::Darwin do
subject { Guard::Darwin }
if windows? if windows?
it "isn't usable on windows" do it "isn't usable on windows" do
described_class.should_not be_usable subject.should_not be_usable
end end
end end
if linux? if linux?
it "isn't usable on linux" do it "isn't 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? && Guard::Darwin.usable?
it "is usable on 10.6" do it "is usable on 10.6" do
described_class.should be_usable subject.should be_usable
end end
it_should_behave_like "a listener that reacts to #on_change" it_should_behave_like "a listener that reacts to #on_change"
it_should_behave_like "a listener scoped to a specific directory" it_should_behave_like "a listener scoped to a specific directory"
end end
end end

View File

@ -3,22 +3,23 @@ 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 "isn't usable on 10.6" do
described_class.should_not be_usable subject.should_not be_usable
end end
end end
if windows? if windows?
it "isn't usable on windows" do it "isn't usable on windows" do
described_class.should_not be_usable subject.should_not be_usable
end end
end end
if linux? && Guard::Linux.usable? if linux? && Guard::Linux.usable?
it "is usable on linux" do it "is usable on linux" do
described_class.should be_usable subject.should be_usable
end end
describe "#start", :long_running => true do describe "#start", :long_running => true do
@ -71,6 +72,6 @@ describe Guard::Linux do
stop stop
File.open(file, 'w') {|f| f.write('') } File.open(file, 'w') {|f| f.write('') }
end end
end
end
end end

View File

@ -2,8 +2,8 @@ require 'spec_helper'
require 'guard/listeners/polling' require 'guard/listeners/polling'
describe Guard::Polling do describe Guard::Polling do
subject { Guard::Polling }
it_should_behave_like "a listener that reacts to #on_change" it_should_behave_like "a listener that reacts to #on_change"
it_should_behave_like "a listener scoped to a specific directory" it_should_behave_like "a listener scoped to a specific directory"
end end

View File

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

View File

@ -1,11 +1,12 @@
require 'spec_helper' require 'spec_helper'
describe Guard::Notifier do describe Guard::Notifier do
subject { Guard::Notifier }
describe ".turn_off" do describe ".turn_off" do
before do before do
ENV["GUARD_NOTIFY"] = 'true' ENV["GUARD_NOTIFY"] = 'true'
described_class.turn_off subject.turn_off
end end
it "disables the notifications" do it "disables the notifications" do
@ -21,27 +22,16 @@ describe Guard::Notifier do
context "with the GrowlNotify library available" do context "with the GrowlNotify library available" do
before do before do
class ::GrowlNotify module ::GrowlNotify
class GrowlNotFound < Exception; end
def self.config ; end def self.config ; end
end end
end end
it "loads the library and enables the notifications" do it "loads the library and enables the notifications" do
described_class.should_receive(:require).with('growl_notify').and_return true subject.should_receive(:require).with('growl_notify').and_return true
GrowlNotify.should_receive(:application_name).and_return '' GrowlNotify.should_receive(:application_name).and_return ''
described_class.turn_on subject.turn_on
described_class.should be_enabled subject.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 end
after do after do
@ -49,46 +39,21 @@ describe Guard::Notifier do
end end
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 context "with the Growl library available" do
it "loads the library and enables the notifications" do it "loads the library and enables the notifications" do
described_class.should_receive(:require).with('growl_notify').and_raise LoadError subject.should_receive(:require).with('growl_notify').and_raise LoadError
described_class.should_receive(:require).with('ruby_gntp').and_raise LoadError subject.should_receive(:require).with('growl').and_return true
described_class.should_receive(:require).with('growl').and_return true subject.turn_on
described_class.turn_on subject.should be_enabled
described_class.should be_enabled
described_class.growl_library.should eql :growl
end end
end end
context "without a Growl library available" do context "without the Growl library available" do
it "disables the notifications" do it "disables the notifications" do
described_class.should_receive(:require).with('growl_notify').and_raise LoadError subject.should_receive(:require).with('growl_notify').and_raise LoadError
described_class.should_receive(:require).with('ruby_gntp').and_raise LoadError subject.should_receive(:require).with('growl').and_raise LoadError
described_class.should_receive(:require).with('growl').and_raise LoadError subject.turn_on
described_class.turn_on subject.should_not be_enabled
described_class.should_not be_enabled
described_class.growl_library.should be nil
end end
end end
end end
@ -100,17 +65,17 @@ describe Guard::Notifier do
context "with the Libnotify library available" do context "with the Libnotify library available" do
it "loads the library and enables the notifications" do it "loads the library and enables the notifications" do
described_class.should_receive(:require).with('libnotify').and_return true subject.should_receive(:require).with('libnotify').and_return true
described_class.turn_on subject.turn_on
described_class.should be_enabled subject.should be_enabled
end end
end end
context "without the Libnotify library available" do context "without the Libnotify library available" do
it "disables the notifications" do it "disables the notifications" do
described_class.should_receive(:require).with('libnotify').and_raise LoadError subject.should_receive(:require).with('libnotify').and_raise LoadError
described_class.turn_on subject.turn_on
described_class.should_not be_enabled subject.should_not be_enabled
end end
end end
end end
@ -122,36 +87,35 @@ describe Guard::Notifier do
context "with the rb-notifu library available" do context "with the rb-notifu library available" do
it "loads the library and enables the notifications" do it "loads the library and enables the notifications" do
described_class.should_receive(:require).with('rb-notifu').and_return true subject.should_receive(:require).with('rb-notifu').and_return true
described_class.turn_on subject.turn_on
described_class.should be_enabled subject.should be_enabled
end end
end end
context "without the rb-notify library available" do context "without the rb-notify library available" do
it "disables the notifications" do it "disables the notifications" do
described_class.should_receive(:require).with('rb-notifu').and_raise LoadError subject.should_receive(:require).with('rb-notifu').and_raise LoadError
described_class.turn_on subject.turn_on
described_class.should_not be_enabled subject.should_not be_enabled
end end
end end
end end
end end
describe ".notify" do describe ".notify" do
before { described_class.stub(:enabled?).and_return(true) } before { subject.stub(:enabled?).and_return(true) }
context "on Mac OS" do context "on Mac OS" do
before do before do
RbConfig::CONFIG.stub(:[]).and_return 'darwin' RbConfig::CONFIG.should_receive(:[]).with('target_os').and_return 'darwin'
described_class.stub(:require_growl) subject.stub(:require_growl)
end end
context 'with growl gem' do context 'with growl gem' do
before do before do
Object.send(:remove_const, :Growl) if defined?(Growl) Object.send(:remove_const, :Growl) if defined?(Growl)
Growl = Object.new Growl = Object.new
described_class.growl_library = :growl
end end
after do after do
@ -164,14 +128,13 @@ describe Guard::Notifier do
:icon => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s, :icon => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
:name => "Guard" :name => "Guard"
) )
described_class.notify 'great', :title => 'Guard' subject.notify 'great', :title => 'Guard'
end end
it "don't passes the notification to Growl if library is not available" do it "don't passes the notification to Growl if library is not available" do
Growl.should_not_receive(:notify) Growl.should_not_receive(:notify)
described_class.growl_library = nil subject.should_receive(:enabled?).and_return(true, false)
described_class.should_receive(:enabled?).and_return(false) subject.notify 'great', :title => 'Guard'
described_class.notify 'great', :title => 'Guard'
end end
it "allows additional notification options" do it "allows additional notification options" do
@ -181,7 +144,7 @@ describe Guard::Notifier do
:name => "Guard", :name => "Guard",
:priority => 1 :priority => 1
) )
described_class.notify 'great', :title => 'Guard', :priority => 1 subject.notify 'great', :title => 'Guard', :priority => 1
end end
it "allows to overwrite a default notification option" do it "allows to overwrite a default notification option" do
@ -190,7 +153,7 @@ describe Guard::Notifier do
:icon => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s, :icon => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
:name => "Guard-Cucumber" :name => "Guard-Cucumber"
) )
described_class.notify 'great', :title => 'Guard', :name => "Guard-Cucumber" subject.notify 'great', :title => 'Guard', :name => "Guard-Cucumber"
end end
end end
@ -198,7 +161,6 @@ describe Guard::Notifier do
before do before do
Object.send(:remove_const, :GrowlNotify) if defined?(GrowlNotify) Object.send(:remove_const, :GrowlNotify) if defined?(GrowlNotify)
GrowlNotify = Object.new GrowlNotify = Object.new
described_class.growl_library = :growl_notify
end end
after do after do
@ -212,14 +174,13 @@ describe Guard::Notifier do
:application_name => "Guard", :application_name => "Guard",
:description => 'great' :description => 'great'
) )
described_class.notify 'great', :title => 'Guard' subject.notify 'great', :title => 'Guard'
end end
it "don't passes the notification to Growl if library is not available" do it "don't passes the notification to Growl if library is not available" do
GrowlNotify.should_not_receive(:send_notification) GrowlNotify.should_not_receive(:send_notification)
described_class.growl_library = nil subject.should_receive(:enabled?).and_return(true, false)
described_class.should_receive(:enabled?).and_return(false) subject.notify 'great', :title => 'Guard'
described_class.notify 'great', :title => 'Guard'
end end
it "allows additional notification options" do it "allows additional notification options" do
@ -230,7 +191,7 @@ describe Guard::Notifier do
:description => 'great', :description => 'great',
:priority => 1 :priority => 1
) )
described_class.notify 'great', :title => 'Guard', :priority => 1 subject.notify 'great', :title => 'Guard', :priority => 1
end end
it "throws out the application name since Guard should only use one Growl App Name while running" do it "throws out the application name since Guard should only use one Growl App Name while running" do
@ -240,80 +201,15 @@ describe Guard::Notifier do
:application_name => "Guard", :application_name => "Guard",
:description => 'great' :description => 'great'
) )
described_class.notify 'great', :title => 'Guard', :name => "Guard-Cucumber" subject.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 end
end end
context "on Linux" do context "on Linux" do
before do before do
RbConfig::CONFIG.stub(:[]).and_return 'linux' RbConfig::CONFIG.should_receive(:[]).with('target_os').and_return 'linux'
described_class.stub(:require_libnotify) subject.stub(:require_libnotify)
Object.send(:remove_const, :Libnotify) if defined?(Libnotify) Object.send(:remove_const, :Libnotify) if defined?(Libnotify)
Libnotify = Object.new Libnotify = Object.new
end end
@ -329,13 +225,13 @@ describe Guard::Notifier do
: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 :transient => true
) )
described_class.notify 'great', :title => 'Guard' subject.notify 'great', :title => 'Guard'
end end
it "don't passes the notification to Libnotify if library is not available" do it "don't passes the notification to Libnotify if library is not available" do
Libnotify.should_not_receive(:show) Libnotify.should_not_receive(:show)
described_class.should_receive(:enabled?).and_return(false) subject.should_receive(:enabled?).and_return(true, false)
described_class.notify 'great', :title => 'Guard' subject.notify 'great', :title => 'Guard'
end end
it "allows additional notification options" do it "allows additional notification options" do
@ -346,7 +242,7 @@ describe Guard::Notifier do
:transient => true, :transient => true,
:urgency => :critical :urgency => :critical
) )
described_class.notify 'great', :title => 'Guard', :urgency => :critical subject.notify 'great', :title => 'Guard', :urgency => :critical
end end
it "allows to overwrite a default notification option" do it "allows to overwrite a default notification option" do
@ -356,14 +252,14 @@ describe Guard::Notifier do
:icon_path => '~/.guard/success.png', :icon_path => '~/.guard/success.png',
:transient => true :transient => true
) )
described_class.notify 'great', :title => 'Guard', :icon_path => '~/.guard/success.png' subject.notify 'great', :title => 'Guard', :icon_path => '~/.guard/success.png'
end end
end end
context "on Windows" do context "on Windows" do
before do before do
RbConfig::CONFIG.stub(:[]).and_return 'mswin' RbConfig::CONFIG.should_receive(:[]).with('target_os').and_return 'mswin'
described_class.stub(:require_rbnotifu) subject.stub(:require_rbnotifu)
Object.send(:remove_const, :Notifu) if defined?(Notifu) Object.send(:remove_const, :Notifu) if defined?(Notifu)
Notifu = Object.new Notifu = Object.new
end end
@ -379,13 +275,13 @@ describe Guard::Notifier do
:type => :info, :type => :info,
:time => 3 :time => 3
) )
described_class.notify 'great', :title => 'Guard' subject.notify 'great', :title => 'Guard'
end end
it "don't passes the notification to rb-notifu if library is not available" do it "don't passes the notification to rb-notifu if library is not available" do
Notifu.should_not_receive(:show) Notifu.should_not_receive(:show)
described_class.should_receive(:enabled?).and_return(false) subject.should_receive(:enabled?).and_return(true, false)
described_class.notify 'great', :title => 'Guard' subject.notify 'great', :title => 'Guard'
end end
it "allows additional notification options" do it "allows additional notification options" do
@ -396,7 +292,7 @@ describe Guard::Notifier do
:time => 3, :time => 3,
:nosound => true :nosound => true
) )
described_class.notify 'great', :title => 'Guard', :nosound => true subject.notify 'great', :title => 'Guard', :nosound => true
end end
it "allows to overwrite a default notification option" do it "allows to overwrite a default notification option" do
@ -406,7 +302,7 @@ describe Guard::Notifier do
:type => :info, :type => :info,
:time => 10 :time => 10
) )
described_class.notify 'great', :title => 'Guard', :time => 10 subject.notify 'great', :title => 'Guard', :time => 10
end end
end end
end end
@ -424,5 +320,4 @@ describe Guard::Notifier do
it { should_not be_enabled } it { should_not be_enabled }
end end
end end
end end

View File

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

View File

@ -3,45 +3,6 @@ require 'guard/guard'
describe Guard do describe Guard do
describe ".initialize_template" do
context "with a Guard name" do
it "initializes a the Guard" do
class Guard::TestGuard < Guard::Guard
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
describe ".setup" do describe ".setup" do
subject { ::Guard.setup } subject { ::Guard.setup }
@ -54,8 +15,7 @@ describe Guard do
end end
it "initializes @groups" do it "initializes @groups" do
described_class.groups[0].name.should eql :default Guard.groups.should eql [{ :name => :default, :options => {} }]
described_class.groups[0].options.should == {}
end end
it "initializes the options" do it "initializes the options" do
@ -93,129 +53,6 @@ describe Guard do
::Guard.should_receive(:debug_command_execution) ::Guard.should_receive(:debug_command_execution)
::Guard.setup(:debug => true) ::Guard.setup(:debug => true)
end 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
describe ".guards" 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 end
describe ".start" do describe ".start" do
@ -298,22 +135,19 @@ describe Guard do
it "accepts group name as string" do it "accepts group name as string" do
subject.add_group('backend') subject.add_group('backend')
subject.groups[0].name.should eql :default subject.groups.should eql [{ :name => :default, :options => {} }, { :name => :backend, :options => {} }]
subject.groups[1].name.should eql :backend
end end
it "accepts group name as symbol" do it "accepts group name as symbol" do
subject.add_group(:backend) subject.add_group(:backend)
subject.groups[0].name.should eql :default subject.groups.should eql [{ :name => :default, :options => {} }, { :name => :backend, :options => {} }]
subject.groups[1].name.should eql :backend
end end
it "accepts options" do it "accepts options" do
subject.add_group(:backend, { :halt_on_fail => true }) subject.add_group(:backend, { :halt_on_fail => true })
subject.groups[0].options.should == {} subject.groups.should eql [{ :name => :default, :options => {} }, { :name => :backend, :options => { :halt_on_fail => true } }]
subject.groups[1].options.should == { :halt_on_fail => true }
end end
end end
@ -392,7 +226,7 @@ describe Guard do
end end
end end
describe ".run_guard_task" do describe ".execute_supervised_task_for_all_guards" do
subject { ::Guard.setup } subject { ::Guard.setup }
before do before do
@ -413,13 +247,13 @@ describe Guard do
end end
it "executes the task for each guard in each group" do it "executes the task for each guard in each group" do
subject.run_guard_task(:task) subject.execute_supervised_task_for_all_guards(:task)
@sum.all? { |k, v| v == 2 }.should be_true @sum.all? { |k, v| v == 2 }.should be_true
end end
end end
context "one guard fails" do context "one guard fails (by returning false)" do
before do before do
subject.guards.each_with_index do |g, i| subject.guards.each_with_index do |g, i|
g.stub!(:task) do g.stub!(:task) do
@ -434,7 +268,7 @@ describe Guard do
end end
it "executes the task only for guards that didn't fail for group with :halt_on_fail == true" do it "executes the task only for guards that didn't fail for group with :halt_on_fail == true" do
subject.run_guard_task(:task) subject.execute_supervised_task_for_all_guards(:task)
@sum[:foo].should eql 1 @sum[:foo].should eql 1
@sum[:bar].should eql 7 @sum[:bar].should eql 7
@ -442,52 +276,13 @@ describe Guard do
end end
end end
describe ".run_on_change_task" do describe ".supervised_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 } subject { ::Guard.setup }
before do before do
@g = mock(Guard::Guard).as_null_object @g = mock(Guard::Guard).as_null_object
subject.guards.push(@g) subject.guards.push(@g)
subject.add_group(:foo, { :halt_on_fail => true }) subject.groups.push({ :name => :foo, :options => { :halt_on_fail => true } })
subject.add_group(:bar, { :halt_on_fail => false })
end end
context "with a task that succeed" do context "with a task that succeed" do
@ -497,22 +292,22 @@ describe Guard do
end end
it "doesn't fire the Guard" do it "doesn't fire the Guard" do
lambda { subject.run_supervised_task(@g, :regular_without_arg) }.should_not change(subject.guards, :size) lambda { subject.supervised_task(@g, :regular_without_arg) }.should_not change(subject.guards, :size)
end end
it "returns the result of the task" do it "returns the result of the task" do
::Guard.run_supervised_task(@g, :regular_without_arg).should be_true ::Guard.supervised_task(@g, :regular_without_arg).should be_true
end end
it "passes the args to the :begin hook" do it "passes the args to the :begin hook" do
@g.should_receive(:hook).with("regular_without_arg_begin", "given_path") @g.should_receive(:hook).with("regular_without_arg_begin", "given_path")
::Guard.run_supervised_task(@g, :regular_without_arg, "given_path") ::Guard.supervised_task(@g, :regular_without_arg, "given_path")
end end
it "passes the result of the supervised method to the :end hook" do 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_begin", "given_path")
@g.should_receive(:hook).with("regular_without_arg_end", true) @g.should_receive(:hook).with("regular_without_arg_end", true)
::Guard.run_supervised_task(@g, :regular_without_arg, "given_path") ::Guard.supervised_task(@g, :regular_without_arg, "given_path")
end end
end end
@ -522,91 +317,45 @@ describe Guard do
end end
it "doesn't fire the Guard" do it "doesn't fire the Guard" do
lambda { subject.run_supervised_task(@g, :regular_with_arg, "given_path") }.should_not change(subject.guards, :size) lambda { subject.supervised_task(@g, :regular_with_arg, "given_path") }.should_not change(subject.guards, :size)
end end
it "returns the result of the task" do it "returns the result of the task" do
::Guard.run_supervised_task(@g, :regular_with_arg, "given_path").should eql "I'm a success" ::Guard.supervised_task(@g, :regular_with_arg, "given_path").should eql "I'm a success"
end end
it "calls the default begin hook but not the default end hook" do it "calls the default begin hook but not the default end hook" do
@g.should_receive(:hook).with("failing_begin") @g.should_receive(:hook).with("failing_begin")
@g.should_not_receive(:hook).with("failing_end") @g.should_not_receive(:hook).with("failing_end")
::Guard.run_supervised_task(@g, :failing) ::Guard.supervised_task(@g, :failing)
end end
end end
end end
context "with a task that throw :task_has_failed" do context "with a task that return false and guard's group has the :halt_on_fail option == true" 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 } }
before(:each) { @g.stub!(:group) { :foo }; @g.stub!(:failing) { throw :task_has_failed } }
it "throws :task_has_failed" do it "throws :task_has_failed" do
expect { subject.run_supervised_task(@g, :failing) }.to throw_symbol(:task_has_failed) expect { subject.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
end end
context "with a task that raises an exception" do context "with a task that raises an exception" do
before(:each) { @g.stub!(:group) { :foo }; @g.stub!(:failing) { raise "I break your system" } } before(:each) { @g.stub!(:failing) { raise "I break your system" } }
it "fires the Guard" do it "fires the Guard" do
lambda { subject.run_supervised_task(@g, :failing) }.should change(subject.guards, :size).by(-1) lambda { subject.supervised_task(@g, :failing) }.should change(subject.guards, :size).by(-1)
subject.guards.should_not include(@g) subject.guards.should_not include(@g)
end end
it "returns the exception" do it "returns the exception" do
failing_result = ::Guard.run_supervised_task(@g, :failing) failing_result = ::Guard.supervised_task(@g, :failing)
failing_result.should be_kind_of(Exception) failing_result.should be_kind_of(Exception)
failing_result.message.should == 'I break your system' failing_result.message.should == 'I break your system'
end end
end 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 describe ".debug_command_execution" do
subject { ::Guard.setup } subject { ::Guard.setup }

View File

@ -1,225 +1,139 @@
private 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 def start
sleep(sleep_time) sleep(@rest_delay || 1)
@listener.update_last_event @listener.update_last_event
Thread.new { @listener.start } Thread.new { @listener.start }
sleep(sleep_time) sleep(@rest_delay || 1)
end 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 def record_results
# Don't fail specs due to editor swap files, etc. noise = %r|\.sw.$| # don't fail specs due to editor swap files, etc.
noise = %r|\.sw.$|
@results = []
@results = []
@listener.on_change do |files| @listener.on_change do |files|
@results += files.reject { |f| f =~ noise } @results += files.reject { |f| f =~ noise }
end end
end end
# Get the recorded result from the listener. def stop
# sleep(@rest_delay || 1)
# @return [Array<String>] the result files @listener.stop
# sleep(@rest_delay || 1)
end
def results def results
@results.flatten @results.flatten
end end
# Define a file absolute to the fixture path. shared_examples_for 'a listener that reacts to #on_change' do |rest_delay|
# before(:each) do
# @param [String, Array<String>] file the relative file name, separated by segment @rest_delay = rest_delay if rest_delay.is_a?(Integer) || rest_delay.is_a?(Float) # jruby workaround
# @return [String] the absolute file @listener = described_class.new
# record_results
def fixture(*file)
@fixture_path.join(*file)
end end
shared_examples_for 'a listener that reacts to #on_change' do it "catches a new file" do
before do file = @fixture_path.join("newfile.rb")
listen_to described_class.new if File.exists?(file)
end begin
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 File.delete file
rescue
end end
results.should =~ []
end end
File.exists?(file).should be_false
start
FileUtils.touch file
stop
begin
File.delete file
rescue
end
results.should =~ ['spec/fixtures/newfile.rb']
end end
context 'for a moved file' do it "catches a single file update" do
let(:file1) { fixture('folder1', 'file1.txt') } file = @fixture_path.join("folder1/file1.txt")
let(:file2) { fixture('folder1', 'movedfile1.txt') } File.exists?(file).should be_true
start
after { FileUtils.mv file2, file1 } File.open(file, 'w') { |f| f.write('') }
stop
it 'does not catch the move' do results.should =~ ['spec/fixtures/folder1/file1.txt']
File.exists?(file1).should be_true
File.exists?(file2).should be_false
watch do
FileUtils.mv file1, file2
end
results.should =~ []
end
end end
it "not catches a single file chmod update" do
file = @fixture_path.join("folder1/file1.txt")
File.exists?(file).should be_true
start
File.chmod(0777, file)
stop
results.should =~ []
end
it "catches a dotfile update" do
file = @fixture_path.join(".dotfile")
File.exists?(file).should be_true
start
File.open(file, 'w') { |f| f.write('') }
stop
results.should =~ ['spec/fixtures/.dotfile']
end
it "catches multiple file updates" 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 "not catches a 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 =~ []
end
it "not catches a 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 =~ []
end
end end
shared_examples_for "a listener scoped to a specific directory" do shared_examples_for "a listener scoped to a specific directory" do |rest_delay|
before :each do
@rest_delay = rest_delay if rest_delay.is_a?(Integer) || rest_delay.is_a?(Float) # jruby workaround
@wd = @fixture_path.join("folder1")
@listener = described_class.new @wd
end
let(:work_directory) { fixture('folder1') } it "should base paths within this directory" do
record_results
let(:new_file) { work_directory.join('folder2', 'newfile.rb') } new_file = @wd.join("folder2/newfile.rb")
let(:modified) { work_directory.join('file1.txt') } modified = @wd.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?(modified).should be_true
File.exists?(new_file).should be_false File.exists?(new_file).should be_false
start
watch do FileUtils.touch new_file
FileUtils.touch new_file File.open(modified, 'w') { |f| f.write('') }
File.open(modified, 'w') { |f| f.write('') } stop
end File.delete new_file
results.should =~ ["folder2/newfile.rb", 'file1.txt']
results.should =~ ['folder2/newfile.rb', 'file1.txt']
end end
end end