Compare commits
244 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
95f895e1ed | ||
|
3584dbbfbb | ||
|
10f1542ac3 | ||
|
a5fb1aa601 | ||
|
50b35cc99c | ||
|
5111e8594e | ||
|
1a45a77969 | ||
|
67d73cf6a6 | ||
|
6094451cfb | ||
|
3b0e2ad305 | ||
|
83def5004f | ||
|
18e0f99a6d | ||
|
4ed28a3cbe | ||
|
7383efaf31 | ||
|
f6bea4da59 | ||
|
8527cf40e3 | ||
|
8891776e34 | ||
|
5429d10eb5 | ||
|
037b1e6feb | ||
|
bdfdf45325 | ||
|
0b837a20ec | ||
|
5c0105b2c1 | ||
|
6f1a0acb2e | ||
|
1845efb11e | ||
|
a274fb1ddf | ||
|
b2f282f1e8 | ||
|
31519887cd | ||
|
1c6ce380ea | ||
|
a7b6ebeef1 | ||
|
33542acec9 | ||
|
d6b47f6448 | ||
|
a5dd0c7847 | ||
|
bebaef1592 | ||
|
47f3d878f6 | ||
|
1181b8b234 | ||
|
8b6a035dfe | ||
|
a1e5723efe | ||
|
4451d73583 | ||
|
cdf0906614 | ||
|
cba4e4c163 | ||
|
eb7081e3d2 | ||
|
da866d3380 | ||
|
ac42712335 | ||
|
6ae9a83dbd | ||
|
910856362e | ||
|
cd545d5207 | ||
|
1090e0f797 | ||
|
b15638f13c | ||
|
64ac60a1fe | ||
|
e26c1d2179 | ||
|
7dcd7ca168 | ||
|
b3535b4a4e | ||
|
3c75205dd0 | ||
|
e33f5df518 | ||
|
b36bea28f3 | ||
|
776361d038 | ||
|
6ecc541bda | ||
|
e9ddb1c110 | ||
|
5bdb56caa0 | ||
|
56ebe9f9f4 | ||
|
aaa08d3a89 | ||
|
1607901e43 | ||
|
243ea157a9 | ||
|
b2488e7b9e | ||
|
309ecc7b4b | ||
|
db949bf9e4 | ||
|
40e033ce0f | ||
|
5d122466fa | ||
|
ca7b059c66 | ||
|
5325cbdea1 | ||
|
14150889c5 | ||
|
ec71eea227 | ||
|
81f713a9ae | ||
|
83b1c9f787 | ||
|
b00c850ef8 | ||
|
a91874a4df | ||
|
b64b7882f7 | ||
|
805fd174d9 | ||
|
479c6b1918 | ||
|
d857134446 | ||
|
7e6e52a2c9 | ||
|
87656c2065 | ||
|
4572cbed0c | ||
|
67882bcceb | ||
|
d9fc071492 | ||
|
a1f37f60d6 | ||
|
0936771a9e | ||
|
e9eaa39e4d | ||
|
1571292ba7 | ||
|
8365d7c429 | ||
|
5de287ba95 | ||
|
5c9ee2afdf | ||
|
a7a6c5c69e | ||
|
a326c35875 | ||
|
2f59a10558 | ||
|
41ada16595 | ||
|
0a60575dde | ||
|
dc526f2898 | ||
|
1cd669bf60 | ||
|
916613c027 | ||
|
12fcf15a95 | ||
|
ed97336c7d | ||
|
4c1cf825e5 | ||
|
ec463a271b | ||
|
0c9634a1c2 | ||
|
432d4a0991 | ||
|
bda57d1d8f | ||
|
507abce5b6 | ||
|
78448631ab | ||
|
072d5404ee | ||
|
9ebc59f533 | ||
|
d493e3c5d9 | ||
|
dd1a5cbb49 | ||
|
8c6a30795a | ||
|
c0dcef9dbc | ||
|
b944932f53 | ||
|
33b52d2955 | ||
|
53802ed355 | ||
|
ad6fe6f69b | ||
|
98ee450037 | ||
|
ab91117ed7 | ||
|
dddc2ad369 | ||
|
044100b7c3 | ||
|
2fa0f7255a | ||
|
0f5b2b764a | ||
|
dc009d445a | ||
|
d66a872f4a | ||
|
9df4b3c291 | ||
|
7099774e7c | ||
|
aa55d48b96 | ||
|
e126c7f609 | ||
|
48863fee0d | ||
|
42d4d94611 | ||
|
fc3e179a0c | ||
|
443f57efce | ||
|
f9521fe0fe | ||
|
1608b17501 | ||
|
078d55f13c | ||
|
b1b69924a7 | ||
|
22001c5ecd | ||
|
6993712c38 | ||
|
5a70faf974 | ||
|
fb2c320a2a | ||
|
879732fb43 | ||
|
34aaed9741 | ||
|
b74d09b9d6 | ||
|
423610f22d | ||
|
9ad866079f | ||
|
c704fe5d0b | ||
|
da46791204 | ||
|
84d6641925 | ||
|
2edf8bead6 | ||
|
b37928a540 | ||
|
b1bef902b1 | ||
|
16fe7441d4 | ||
|
58af2b2c1d | ||
|
330493a572 | ||
|
59103ffe09 | ||
|
d88ffec9a0 | ||
|
54689318cc | ||
|
dc9bddf02e | ||
|
771c9d08c0 | ||
|
d1c452bc1e | ||
|
bd67a69a8b | ||
|
3de4f505d6 | ||
|
596f92f7eb | ||
|
fe103ca9a4 | ||
|
b8beb3fb9e | ||
|
96604060ed | ||
|
3d9d1ad649 | ||
|
11495687f4 | ||
|
8255ae0be9 | ||
|
20305a59ba | ||
|
b8e43e7a7c | ||
|
69d308b505 | ||
|
8bb295f105 | ||
|
0a2a34e55f | ||
|
50d80d962b | ||
|
85f67f26ab | ||
|
60874a6991 | ||
|
1c5d1fd268 | ||
|
a288f51ec8 | ||
|
215af072a2 | ||
|
9fcd8621ad | ||
|
f91622adc5 | ||
|
e1472cb0c4 | ||
|
ee5468eeeb | ||
|
b0295c1437 | ||
|
8b66b71716 | ||
|
41127e2cbf | ||
|
5978e875df | ||
|
68efb0d52f | ||
|
667b248ad0 | ||
|
18cb3471af | ||
|
905c32dcc9 | ||
|
45ba095093 | ||
|
0c7b496296 | ||
|
f8b4f45737 | ||
|
47be15125b | ||
|
c4ddb29fc6 | ||
|
3b73ea77b7 | ||
|
5c1fb285e8 | ||
|
74b3445549 | ||
|
4ac556bfe3 | ||
|
5de94ccbcb | ||
|
63c3015532 | ||
|
3717179591 | ||
|
742b9fe3ff | ||
|
e752dbe1c1 | ||
|
02c4465940 | ||
|
f8960ec783 | ||
|
8da8f6a33d | ||
|
e795ab29f5 | ||
|
4fd1db3fb7 | ||
|
652c3d8661 | ||
|
af408ceb65 | ||
|
32904356cb | ||
|
eb347ee266 | ||
|
d6e27fe334 | ||
|
8144a43726 | ||
|
e7c23ff78d | ||
|
573ddf9d9d | ||
|
32cb5d7bf8 | ||
|
aeb2c67c17 | ||
|
efdacce491 | ||
|
d798bf05e2 | ||
|
20b8a9af69 | ||
|
e853009528 | ||
|
134cbdb007 | ||
|
b83653db2e | ||
|
b646ae53f6 | ||
|
386b0be53d | ||
|
7916139726 | ||
|
c82e1582f8 | ||
|
5b8ae609da | ||
|
44aed3264c | ||
|
d47aebd424 | ||
|
1d38c59200 | ||
|
bbc63abd5e | ||
|
1027e4b6b3 | ||
|
da0d059a43 | ||
|
154ef207ed | ||
|
7b559ce255 | ||
|
c4ce612bde |
9
.gitignore
vendored
9
.gitignore
vendored
@ -1,6 +1,13 @@
|
||||
pkg/*
|
||||
doc/*
|
||||
*.gem
|
||||
*.rbc
|
||||
.*.swp
|
||||
*.bak
|
||||
.bundle
|
||||
.yardoc
|
||||
.rbx
|
||||
.rvmrc
|
||||
Gemfile.lock
|
||||
|
||||
## MAC OS
|
||||
@ -9,4 +16,4 @@ Gemfile.lock
|
||||
.com.apple.timemachine.supported
|
||||
.fseventsd
|
||||
Desktop DB
|
||||
Desktop DF
|
||||
Desktop DF
|
||||
|
13
.travis.yml
13
.travis.yml
@ -2,9 +2,18 @@ rvm:
|
||||
- 1.8.7
|
||||
- 1.9.2
|
||||
- ree
|
||||
- jruby
|
||||
- rbx
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- hook
|
||||
- guard_dependencies
|
||||
env:
|
||||
- GUARD_SLEEP=1
|
||||
notifications:
|
||||
irc: "irc.freenode.org#guard"
|
||||
recipients:
|
||||
- thibaud@thibaud.me
|
||||
- rymai@rymai.me
|
||||
- michi@netzpiraten.ch
|
||||
- yann.lugrin@sans-savoir.net
|
||||
irc: "irc.freenode.org#guard"
|
||||
|
11
.yardopts
Normal file
11
.yardopts
Normal file
@ -0,0 +1,11 @@
|
||||
--title 'Guard Documentation'
|
||||
--readme README.md
|
||||
--markup markdown
|
||||
--markup-provider kramdown
|
||||
--private
|
||||
--protected
|
||||
--output-dir ./doc
|
||||
lib/**/*.rb
|
||||
-
|
||||
CHANGELOG.md
|
||||
LICENSE
|
252
CHANGELOG.md
252
CHANGELOG.md
@ -1,221 +1,296 @@
|
||||
## Master
|
||||
|
||||
### Improvements
|
||||
|
||||
- Add cli option (-i / --no-interactions) to turn off Guard terminal interactions. ([@thibaudgg][])
|
||||
- Add support for Growl Notification Transport Protocol. ([@netzpirat][])
|
||||
- [#157](https://github.com/guard/guard/pull/157): Allow any return from the Guard watchers. ([@earlonrails][])
|
||||
- [#156](https://github.com/guard/guard/pull/156): Log error and diagnostic messages to STDERR. ([@sunaku][])
|
||||
- [#152](https://github.com/guard/guard/pull/152): Growl Notify API update for a graceful fail. ([@scottdavis][])
|
||||
|
||||
### Bug fix
|
||||
|
||||
- [#149](https://github.com/guard/guard/issues/160): Avoid `Guard is not missing constant ...` exceptions. (reported by [@earlonrails][], fixed by [@netzpirat][])
|
||||
|
||||
## 0.8.4 - October 3, 2011
|
||||
|
||||
### Bug fix
|
||||
|
||||
- [#149](https://github.com/guard/guard/issues/149) & [#150](https://github.com/guard/guard/pull/150): Fix issue where interator thread was continuing to capture input from stdin while a guard is being executed. (reported by [@hardipe][], fixed by [@f1sherman][])
|
||||
|
||||
## 0.8.3 - October 1, 2011
|
||||
|
||||
### Bug fix
|
||||
|
||||
- [#145](https://github.com/guard/guard/pull/145): Fix over-utilization of CPU in Interactor. ([@johnbintz][])
|
||||
|
||||
### Improvements
|
||||
|
||||
- [#146](https://github.com/guard/guard/pull/146): Use a mutex instead of a lock for more efficient/simple locking. ([@f1sherman][])
|
||||
- Make Guard implementation of `:task_has_failed` simple. ([@netzpirat][])
|
||||
|
||||
## 0.8.2 - September 30, 2011
|
||||
|
||||
### Bug fix
|
||||
|
||||
- Fixed guard stop to prevent run_guard_task(:stop) to be skipped [guard-spork issue #28](https://github.com/guard/guard-spork/issues/28). ([@thibaudgg][])
|
||||
|
||||
### Improvement
|
||||
|
||||
- Update docs regarding :task_has_failed. ([@netzpirat][])
|
||||
|
||||
## 0.8.1 - September 29, 2011
|
||||
|
||||
### Bug fix
|
||||
|
||||
- [#144](https://github.com/guard/guard/pull/144): Fix `guard init`. (reported by [@fabioyamate][], fixed by [@rymai][])
|
||||
|
||||
## 0.8.0 - September 28, 2011
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- [#137](https://github.com/guard/guard/pull/137): Fix interacting with tools like ruby-debug. ([@hron][] & [@netzpirat][])
|
||||
- [#138](https://github.com/guard/guard/pull/138): Fixed comments in example scaffold to reference interactions. ([@rmm5t][] & [@netzpirat][])
|
||||
|
||||
### New feature
|
||||
|
||||
- [#136](https://github.com/guard/guard/pull/136): New CLI `:watch_all_modifications`/`-A` option to watch for deleted and moved files too. ([@limeyd][] & [@netzpirat][])
|
||||
- [#97](https://github.com/guard/guard/issues/97): Guard dependencies. Task execution can now be halted if a Guard throws `:task_has_failed` and `Guard::Dsl#group` options include `:halt_on_fail => true`. ([@rymai][])
|
||||
- [#121](https://github.com/guard/guard/issues/121): `Guard.guards` and `Guard.groups` are now smart accessors. Filters can be passed to find a specific Guard/group or several Guards/groups that match (see YARDoc). ([@rymai][] & [@ches][])
|
||||
- New `Guard::Group` class to store groups defined in Guardfile (with `Guard::Dsl#group`). ([@rymai][])
|
||||
|
||||
### Improvements
|
||||
|
||||
- Specs refactoring. ([@netzpirat][])
|
||||
- Full YARD documentation. ([@netzpirat][] & a little of [@rymai][])
|
||||
|
||||
## 0.7.0 - September 14, 2011
|
||||
|
||||
## 0.7.0.rc1 - September 5, 2011
|
||||
|
||||
### Major Changes
|
||||
|
||||
- Posix Signals handlers (`Ctrl-C`, `Ctrl-\` and `Ctrl-Z`) are no more supported and replaced by `$stdin.gets`. Please refer to the "Interactions" section in the README for more information. ([@thibaudgg][])
|
||||
- JRuby & Rubinius support (beta). ([@thibaudgg][] & [@netzpirat][])
|
||||
|
||||
### New features
|
||||
|
||||
- [#42](https://github.com/guard/guard/pull/42): New DSL method: `callback` allows you to execute arbitrary code before or after any of the `start`, `stop`, `reload`, `run_all` and `run_on_change` guards' method. New [Wiki page](https://github.com/guard/guard/wiki/Hooks-and-callbacks) for documenting it. ([@monocle][] & [@rymai][])
|
||||
- Ability to 'pause' files modification listening. Please refer to the "Interactions" section in the README for more information. ([@thibaudgg][])
|
||||
|
||||
### Improvement
|
||||
|
||||
- Remove the need to scan the whole directory after guard's `run_on_change` method. ([@thibaudgg][])
|
||||
|
||||
## 0.6.3 - September 1, 2011
|
||||
|
||||
### New features:
|
||||
### New features
|
||||
|
||||
- Pull request [#130](https://github.com/guard/guard/pull/130): Adds ignore_paths option to DSL. ([@ianwhite][])
|
||||
- Pull request [#128](https://github.com/guard/guard/pull/128): Users can add additional settings to ~/.guard.rb that augment the existing Guardfile. ([@tpope][])
|
||||
- [#130](https://github.com/guard/guard/pull/130): Adds `ignore_paths` method to DSL. ([@ianwhite][])
|
||||
- [#128](https://github.com/guard/guard/pull/128): Users can add additional settings to `~/.guard.rb` that augment the existing Guardfile. ([@tpope][])
|
||||
|
||||
## 0.6.2 - August 17, 2011
|
||||
|
||||
### Bugs fixes:
|
||||
### Bug fixes
|
||||
|
||||
- Re-add the possibility to use the `growl` gem since the `growl_notify` gem this is currently known to not work in conjunction with Spork. ([@netzpirat][])
|
||||
- Ensure that scoped groups and group name are symbolized before checking for inclusion. ([@rymai][])
|
||||
|
||||
### New features:
|
||||
### 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][])
|
||||
|
||||
### Improvement:
|
||||
### Improvement
|
||||
|
||||
- Explain the growl/growl_notify differences in the README. ([@netzpirat][])
|
||||
|
||||
## 0.6.1 - August 15, 2011
|
||||
|
||||
### Bugs fixes:
|
||||
### Bug fixes
|
||||
|
||||
- Pull request [#120](https://github.com/guard/guard/pull/120): remove guardfile_contents when re-evaluating so that the Guardfile gets reloaded correctly. ([@mordaroso][])
|
||||
- Pull request [#119](https://github.com/guard/guard/pull/119): Dsl.evaluate_guardfile uses all groups if none specified. ([@ches][])
|
||||
- [#120](https://github.com/guard/guard/pull/120): remove `guardfile_contents` when re-evaluating so that the Guardfile gets reloaded correctly. ([@mordaroso][])
|
||||
- [#119](https://github.com/guard/guard/pull/119): `Dsl.evaluate_guardfile` uses all groups if none specified. ([@ches][])
|
||||
|
||||
## 0.6.0 - August 13, 2011
|
||||
|
||||
### Bugs fixes:
|
||||
### Bug fixes
|
||||
|
||||
- Pull request [#107](https://github.com/guard/guard/pull/107): Small spelling fix. ([@dnagir][])
|
||||
- Dir.glob now ignores files that don't need to be watched. ([@rymai][])
|
||||
- `Dir.glob` now ignores files that don't need to be watched. ([@rymai][])
|
||||
|
||||
### New features:
|
||||
### New feature
|
||||
|
||||
- Pull request [#112](https://github.com/guard/guard/pull/112): Add `list` command to CLI. ([@docwhat][])
|
||||
|
||||
### Improvements:
|
||||
### Improvements
|
||||
|
||||
- Pull request [#99](https://github.com/guard/guard/pull/99): [OS X] Switch from growl gem to growl_notify gem. ([@johnbintz][])
|
||||
- Pull request [#115](https://github.com/guard/guard/pull/115): [Linux] Add ':transient => true' to default libnotify options. ([@zonque][])
|
||||
- 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][])
|
||||
- [#99](https://github.com/guard/guard/pull/99): [OS X] Switch from growl gem to growl_notify gem. ([@johnbintz][])
|
||||
- [#115](https://github.com/guard/guard/pull/115): [Linux] Add `:transient => true` to default libnotify options. ([@zonque][])
|
||||
- [#95](https://github.com/guard/guard/pull/95): Output system commands and options to be executed when in debug mode. ([@uk-ar][] and [@netzpirat][])
|
||||
- `Guard::Dsl.revaluate_guardfile` has been renamed to `Guard::Dsl.reevaluate_guardfile`. ([@rymai][])
|
||||
- New CLI options: ([@nestegg][])
|
||||
- `watchdir`/`-w` to specify the directory in which Guard should watch for changes,
|
||||
- `guardfile`/`-G` to specify an alternate location for the Guardfile to use.
|
||||
- Pull request [#90](https://github.com/guard/guard/pull/90): Refactoring of color handling in the `Guard::UI`. ([@stereobooster][])
|
||||
- [#90](https://github.com/guard/guard/pull/90): Refactoring of color handling in the `Guard::UI`. ([@stereobooster][])
|
||||
|
||||
## 0.5.1 - July 2, 2011
|
||||
|
||||
### Bugs fixes:
|
||||
### Bug fix
|
||||
|
||||
- Fixed `guard show` command. ([@bronson][] & [@thibaudgg][])
|
||||
|
||||
## 0.5.0 - July 2, 2011
|
||||
|
||||
### New features:
|
||||
### New features
|
||||
|
||||
- Guard::Ego is now part of Guard, so Guardfile is automagically re-evaluated when modified. ([@thibaudgg][])
|
||||
- Pull request [#91](https://github.com/guard/guard/pull/91): Show Guards in Guardfile with the `guard -T`. ([@johnbintz][])
|
||||
- [#91](https://github.com/guard/guard/pull/91): Show Guards in Guardfile with the `guard -T`. ([@johnbintz][])
|
||||
|
||||
### Improvements:
|
||||
### Improvements
|
||||
|
||||
- Issue [#98](https://github.com/guard/guard/issues/98): Multiple calls per watch event on linux with rb-inotify. ([@jeffutter][] & [@netzpirat][])
|
||||
- Pull request [#94](https://github.com/guard/guard/pull/94): Show backtrace in terminal when a problem with a watch action occurs. ([@capotej][])
|
||||
- Pull request [#88](https://github.com/guard/guard/pull/88): Write exception trace in the terminal when a supervised task fail. ([@mcmire][])
|
||||
- [#98](https://github.com/guard/guard/issues/98): Multiple calls per watch event on linux with rb-inotify. ([@jeffutter][] & [@netzpirat][])
|
||||
- [#94](https://github.com/guard/guard/pull/94): Show backtrace in terminal when a problem with a watch action occurs. ([@capotej][])
|
||||
- [#88](https://github.com/guard/guard/pull/88): Write exception trace in the terminal when a supervised task fail. ([@mcmire][])
|
||||
- Color in red the "ERROR:" flag when using `UI.error`. ([@rymai][])
|
||||
- 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][])
|
||||
- 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][])
|
||||
- Pull request [#84](https://github.com/guard/guard/pull/84): Use RbConfig instead of obsolete and deprecated Config. ([@etehtsea][])
|
||||
- Pull request [#80](https://github.com/guard/guard/pull/80): Watching dotfile (hidden files under unix). (reported by [@chrisberkhout][], fixed by [@yannlugrin][])
|
||||
- [#79](https://github.com/guard/guard/issues/79) and Pull request [#82](https://github.com/guard/guard/pull/82): Improve INotify support on Linux. ([@Gazer][] & [@yannlugrin][])
|
||||
- [#12](https://github.com/guard/guard/issues/12) and Pull request [#86](https://github.com/guard/guard/pull/86): Eventually exits with SystemStackError. ([@stereobooster][])
|
||||
- [#84](https://github.com/guard/guard/pull/84): Use RbConfig instead of obsolete and deprecated Config. ([@etehtsea][])
|
||||
- [#80](https://github.com/guard/guard/pull/80): Watching dotfile (hidden files under unix). (reported by [@chrisberkhout][], fixed by [@yannlugrin][])
|
||||
- Clear the terminal on start when the `:clear` option is given. ([@rymai][])
|
||||
- Rename home directory Guardfile to `.Guardfile`. ([@tpope][])
|
||||
|
||||
## 0.4.2 - June 7, 2011
|
||||
|
||||
### Bugs fixes:
|
||||
### Bug fixes
|
||||
|
||||
- Fixed Guard::Version in ruby 1.8.7 ([@thibaudgg][])
|
||||
- Fix ([@mislav][]) link in CHANGELOG (Note: this is a recursive CHANGELOG item). ([@fnichol][])
|
||||
|
||||
## 0.4.1 - June 7, 2011
|
||||
|
||||
### Improvements:
|
||||
### Improvements
|
||||
|
||||
- Pull request [#77](https://github.com/guard/guard/pull/77): Refactor `get_guard_class` to first try the constant and fallback to require + various tweaks. ([@mislav][])
|
||||
- [#77](https://github.com/guard/guard/pull/77): Refactor `get_guard_class` to first try the constant and fallback to require + various tweaks. ([@mislav][])
|
||||
- Notifier improvement, don't use system notification library if could not be required. ([@yannlugrin][])
|
||||
|
||||
## 0.4.0 - June 5, 2011
|
||||
|
||||
### Bugs fixes:
|
||||
### Bug fix
|
||||
|
||||
- In Ruby < 1.9, `Symbol#downcase` doesn't exist! ([@rymai][])
|
||||
|
||||
### New features:
|
||||
### New features
|
||||
|
||||
- Pull request [#73](https://github.com/guard/guard/pull/73): Allow DSL's `group` method to accept a Symbol as group name. ([@johnbintz][])
|
||||
- Pull request [#51](https://github.com/guard/guard/pull/51): Allow options (like `:priority`) to be passed through to the Notifier. ([@indirect][] & [@netzpirat][])
|
||||
- [#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][])
|
||||
|
||||
### Improvements:
|
||||
### Improvement
|
||||
|
||||
- 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][])
|
||||
- [#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
|
||||
|
||||
### Bugs fixes:
|
||||
### Bug fixes
|
||||
|
||||
- Pull request [#69](https://github.com/guard/guard/pull/69): Fixed typo in README: `Ctr-/` => `Ctr-\`. ([@tinogomes][])
|
||||
- Pull request [#66](https://github.com/guard/guard/pull/66): Support for dashes in guard names. ([@johnbintz][])
|
||||
- [#69](https://github.com/guard/guard/pull/69): Fixed typo in README: `Ctr-/` => `Ctr-\`. ([@tinogomes][])
|
||||
- [#66](https://github.com/guard/guard/pull/66): Support for dashes in guard names. ([@johnbintz][])
|
||||
- Require `guard/ui` because `Guard::Notifier` can be required without full Guard. ([@yannlugrin][])
|
||||
- Handled quick file (<1s) modification. Avoid to catch modified files without content modification (sha1 checksum). ([@thibaudgg][] & [@netzpirat][])
|
||||
- Fixed `Guard::Notifier` (when growl/libnotify not present). ([@thibaudgg][])
|
||||
- Fixed Rubygems deprecation messages. ([@thibaudgg][])
|
||||
|
||||
### New features:
|
||||
### New features
|
||||
|
||||
- Pull request [#67](https://github.com/guard/guard/pull/67): Allow Guardfile in `$HOME` folder. ([@hashrocketeer][])
|
||||
- Pull request [#64](https://github.com/guard/guard/pull/64): Windows notifications support. ([@stereobooster][])
|
||||
- Pull request [#63](https://github.com/guard/guard/pull/63): Refactor listeners to work as a library. ([@niklas][])
|
||||
- [#67](https://github.com/guard/guard/pull/67): Allow Guardfile in `$HOME` folder. ([@hashrocketeer][])
|
||||
- [#64](https://github.com/guard/guard/pull/64): Windows notifications support. ([@stereobooster][])
|
||||
- [#63](https://github.com/guard/guard/pull/63): Refactor listeners to work as a library. ([@niklas][])
|
||||
- Use `ENV["GUARD_NOTIFY"]` to disable notifications. ([@thibaudgg][])
|
||||
- Cleaning up all specs. ([@netzpirat][])
|
||||
- Pull request [#60](https://github.com/guard/guard/pull/60): Added Windows support. ([@stereobooster][])
|
||||
- Pull request [#58](https://github.com/guard/guard/pull/58): Extract code from signal handlers into methods. ([@nicksieger][])
|
||||
- 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][])
|
||||
|
||||
- [#60](https://github.com/guard/guard/pull/60): Added Windows support. ([@stereobooster][])
|
||||
- [#58](https://github.com/guard/guard/pull/58): Extract code from signal handlers into methods. ([@nicksieger][])
|
||||
- [#55](https://github.com/guard/guard/pull/55): It is now possible to pass `:guardfile` (a Guardfile path) or `:guardfile_contents` (the content of a Guardfile) to `Guard::Dsl.evaluate_guardfile`. Hence this allows the use of `Guard::Dsl.evaluate_guardfile` in a programmatic manner. ([@anithri][], improved by [@rymai][])
|
||||
|
||||
## 0.3.4 - April 24, 2011
|
||||
|
||||
### Bugs fixes:
|
||||
### Bug fix
|
||||
|
||||
- Issue [#41](https://github.com/guard/guard/issues/41): Removed useless Bundler requirement. ([@thibaudgg][])
|
||||
- [#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 README from RDOC to Markdown! Let's celebrate! ([@thibaudgg][])
|
||||
- Issue [#48](https://github.com/guard/guard/issues/48): Adding support for inline Guard classes rather than requiring a gem. ([@jrsacks][])
|
||||
|
||||
- [#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
|
||||
|
||||
### Bugs fixes:
|
||||
### Bug fix
|
||||
|
||||
- Fixed `new_modified_files` rerun conditions on `Guard.run_on_change_for_all_guards`. ([@thibaudgg][])
|
||||
|
||||
|
||||
## 0.3.2 - April 17, 2011
|
||||
|
||||
### Bugs fixes:
|
||||
|
||||
- Pull request [#43](https://github.com/guard/guard/pull/43): Fixed `guard init` command. ([@brainopia][])
|
||||
### Bug fixe
|
||||
|
||||
- [#43](https://github.com/guard/guard/pull/43): Fixed `guard init` command. ([@brainopia][])
|
||||
|
||||
## 0.3.1 - April 14, 2011
|
||||
|
||||
### Bugs fixes:
|
||||
### Bug fixes
|
||||
|
||||
- Return unique filenames from Linux listener. (Marian Schubert)
|
||||
- `Guard.get_guard_class` return wrong class when loaded nested class. ([@koshigoe][])
|
||||
- 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][])
|
||||
- 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][])
|
||||
- [#35](https://github.com/guard/guard/issues/35): Fixed open-gem/gem_open dependency problem by using `gem which` to locate guards gem path. (reported by [@thierryhenrio][], fixed by [@thibaudgg][])
|
||||
- [#38](https://github.com/guard/guard/issues/38) & Pull request [#39](https://github.com/guard/guard/issues/39): Fixed an invalid ANSI escape code in `Guard::UI.reset_line`. ([@gix][])
|
||||
|
||||
### New features:
|
||||
|
||||
- Issue [#28](https://github.com/guard/guard/issues/28): New `-n` command line option to disable notifications (Growl / Libnotify). ([@thibaudgg][])
|
||||
### New feature
|
||||
|
||||
- [#28](https://github.com/guard/guard/issues/28): New `-n` command line option to disable notifications (Growl / Libnotify). ([@thibaudgg][])
|
||||
|
||||
## 0.3.0 - January 19, 2011
|
||||
|
||||
### Bugs fixes:
|
||||
### Bug fix
|
||||
|
||||
- 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][])
|
||||
- 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][])
|
||||
- [#26](https://github.com/guard/guard/pull/26): New DSL method: `group` allows you to group several guards. New CLI option: `--group group_name` to specify certain groups of guards to start. ([@netzpirat][])
|
||||
- `watch` patterns are now more strict: strings are matched with `String#==`, `Regexp` are matched with `Regexp#match`. ([@rymai][])
|
||||
- A deprecation warning is displayed if your `Guardfile` contains `String` that look like `Regexp` (bad!). ([@rymai][])
|
||||
- It's now possible to return an `Enumerable` in the `watch` optional blocks in the `Guardfile`. ([@rymai][])
|
||||
|
||||
### New specs:
|
||||
### New specs
|
||||
|
||||
- `Guard::Watcher`. ([@rymai][])
|
||||
- Pull request [#13](https://github.com/guard/guard/pull/13): `Guard::Dsl`. ([@oliamb][])
|
||||
|
||||
- [#13](https://github.com/guard/guard/pull/13): `Guard::Dsl`. ([@oliamb][])
|
||||
|
||||
## 0.2.2 - October 25, 2010
|
||||
|
||||
### 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][])
|
||||
### Bug fix
|
||||
|
||||
- [#5](https://github.com/guard/guard/issues/5): avoid creating new copy of `fsevent_watch` every time a file is changed. (reported by [@stouset][], fixed by [@thibaudgg][])
|
||||
|
||||
## 0.2.1 - October 24, 2010
|
||||
|
||||
### 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][])
|
||||
### Bug fixes
|
||||
|
||||
- [#7](https://github.com/guard/guard/pull/7): Fixes for Linux support. ([@yannlugrin][])
|
||||
- [#6](https://github.com/guard/guard/pull/6): Locate guard now chomp newline in result path. ([@yannlugrin][])
|
||||
|
||||
## 0.2.0 - October 21, 2010
|
||||
|
||||
### Bugs fixes:
|
||||
### Bug fixes
|
||||
|
||||
- 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][])
|
||||
- Issue [#2](https://github.com/guard/guard/issues/2): 1.8.6 compatibility. (reported by [@veged][], fixed by [@thibaudgg][])
|
||||
- [#3](https://github.com/guard/guard/issues/3): `guard init <guard-name>` no more need `Gemfile` but `open_gem` is required now. (reported by [@wereHamster][], fixed by [@thibaudgg][])
|
||||
- [#2](https://github.com/guard/guard/issues/2): 1.8.6 compatibility. (reported by [@veged][], fixed by [@thibaudgg][])
|
||||
- Removes Growl & Libnotify dependencies. ([@thibaudgg][])
|
||||
|
||||
|
||||
## 0.2.0.beta.1 - October 17, 2010
|
||||
|
||||
### New features:
|
||||
### New features
|
||||
|
||||
- Improved listeners support (`rb-fsevent` & `rb-inotify`). ([@thibaudgg][])
|
||||
- Added polling listening fallback. ([@thibaudgg][])
|
||||
@ -228,18 +303,26 @@
|
||||
[@chrisberkhout]: https://github.com/chrisberkhout
|
||||
[@dnagir]: https://github.com/dnagir
|
||||
[@docwhat]: https://github.com/docwhat
|
||||
[@earlonrails]: https://github.com/earlonrails
|
||||
[@etehtsea]: https://github.com/etehtsea
|
||||
[@f1sherman]: https://github.com/f1sherman
|
||||
[@fabioyamate]: https://github.com/fabioyamate
|
||||
[@fnichol]: https://github.com/fnichol
|
||||
[@Gazer]: https://github.com/Gazer
|
||||
[@gix]: https://github.com/gix
|
||||
[@hron]: https://github.com/hron
|
||||
[@hardipe]: https://github.com/hardipe
|
||||
[@hashrocketeer]: https://github.com/hashrocketeer
|
||||
[@ianwhite]: https://github.com/ianwhite
|
||||
[@indirect]: https://github.com/indirect
|
||||
[@jeffutter]: https://github.com/jeffutter
|
||||
[@johnbintz]: https://github.com/johnbintz
|
||||
[@jrsacks]: https://github.com/jrsacks
|
||||
[@koshigoe]: https://github.com/koshigoe
|
||||
[@limeyd]: https://github.com/limeyd
|
||||
[@mcmire]: https://github.com/mcmire
|
||||
[@mislav]: https://github.com/mislav
|
||||
[@monocle]: https://github.com/monocle
|
||||
[@mordaroso]: https://github.com/mordaroso
|
||||
[@nestegg]: https://github.com/nestegg
|
||||
[@netzpirat]: https://github.com/netzpirat
|
||||
@ -247,9 +330,12 @@
|
||||
[@niklas]: https://github.com/niklas
|
||||
[@oliamb]: https://github.com/oliamb
|
||||
[@pcreux]: https://github.com/pcreux
|
||||
[@rmm5t]: https://github.com/rmm5t
|
||||
[@rymai]: https://github.com/rymai
|
||||
[@scottdavis]: https://github.com/scottdavis
|
||||
[@stereobooster]: https://github.com/stereobooster
|
||||
[@stouset]: https://github.com/stouset
|
||||
[@sunaku]: https://github.com/sunaku
|
||||
[@thibaudgg]: https://github.com/thibaudgg
|
||||
[@thierryhenrio]: https://github.com/thierryhenrio
|
||||
[@tinogomes]: https://github.com/tinogomes
|
||||
|
6
Gemfile
6
Gemfile
@ -13,12 +13,10 @@ require 'rbconfig'
|
||||
if RbConfig::CONFIG['target_os'] =~ /darwin/i
|
||||
gem 'rb-fsevent', '>= 0.4.0', :require => false
|
||||
gem 'growl', '~> 1.0.3', :require => false
|
||||
end
|
||||
if RbConfig::CONFIG['target_os'] =~ /linux/i
|
||||
elsif RbConfig::CONFIG['target_os'] =~ /linux/i
|
||||
gem 'rb-inotify', '>= 0.8.5', :require => false
|
||||
gem 'libnotify', '~> 0.1.3', :require => false
|
||||
end
|
||||
if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
|
||||
elsif RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
|
||||
gem 'win32console', :require => false
|
||||
gem 'rb-fchange', '>= 0.0.2', :require => false
|
||||
gem 'rb-notifu', '>= 0.0.4', :require => false
|
||||
|
17
Guardfile
17
Guardfile
@ -1,11 +1,16 @@
|
||||
guard :rspec, :version => 2, :keep_failed => false, :cli => '-f doc' do
|
||||
watch(%r{^spec/.+_spec\.rb$})
|
||||
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
||||
watch('spec/spec_helper.rb') { "spec" }
|
||||
group :specs do
|
||||
guard :rspec, :all_on_start => false, :all_after_pass => false, :cli => '--fail-fast --format doc' do
|
||||
watch(%r{^spec/.+_spec\.rb$})
|
||||
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
||||
watch('spec/support/listener_helper.rb') { Dir.glob("spec/guard/listeners/*") }
|
||||
watch('spec/spec_helper.rb') { "spec" }
|
||||
end
|
||||
end
|
||||
|
||||
guard :ronn do
|
||||
watch(%r{^man/.+\.ronn?$})
|
||||
group :docs do
|
||||
guard :ronn do
|
||||
watch(%r{^man/.+\.ronn?$})
|
||||
end
|
||||
end
|
||||
|
||||
# require 'guard/guard'
|
||||
|
486
README.md
486
README.md
@ -1,4 +1,4 @@
|
||||
Guard [![Build Status](https://travis-ci.org/guard/guard.png)](http://travis-ci.org/guard/guard)
|
||||
Guard [![Build Status](https://secure.travis-ci.org/guard/guard.png)](http://travis-ci.org/guard/guard)
|
||||
=====
|
||||
|
||||
Guard is a command line tool that easily handle events on files modifications.
|
||||
@ -14,141 +14,130 @@ Features
|
||||
* Polling on the other operating systems (help us to support more OS).
|
||||
* Automatic & Super fast (when polling is not used) files modifications detection (even new files are detected).
|
||||
* Visual notifications on Mac OSX ([Growl](http://growl.info)), Linux ([Libnotify](http://developer.gnome.org/libnotify)) and Windows ([Notifu](http://www.paralint.com/projects/notifu)).
|
||||
* Tested against Ruby 1.8.7, 1.9.2 and REE.
|
||||
* Tested against Ruby 1.8.7, 1.9.2, REE and the latest versions of JRuby & Rubinius.
|
||||
|
||||
Screencast
|
||||
----------
|
||||
|
||||
Ryan Bates made a Railscast on Guard, you can view it here: http://railscasts.com/episodes/264-guard
|
||||
Ryan Bates made a Railscast on Guard, you can view it here: [http://railscasts.com/episodes/264-guard](http://railscasts.com/episodes/264-guard)
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
|
||||
Install the gem:
|
||||
|
||||
``` bash
|
||||
$ gem install guard
|
||||
```
|
||||
$ gem install guard
|
||||
|
||||
Or add it to your Gemfile (inside the `development` group):
|
||||
|
||||
``` ruby
|
||||
gem 'guard'
|
||||
```
|
||||
gem 'guard'
|
||||
|
||||
and install it via Bundler:
|
||||
|
||||
``` bash
|
||||
$ bundle install
|
||||
```
|
||||
$ bundle install
|
||||
|
||||
Generate an empty Guardfile with:
|
||||
|
||||
``` bash
|
||||
$ guard init
|
||||
```
|
||||
$ guard init
|
||||
|
||||
You may optionally place a .Guardfile in your home directory to use it across multiple projects.
|
||||
Also note that if a `.guard.rb` is found in your home directory, it will be appended to the Guardfile.
|
||||
|
||||
Add the guards you need to your Guardfile (see the existing guards below).
|
||||
|
||||
Now, be sure to read the particular instructions for your operating system: [Mac OS X](#mac) | [Linux](#linux) | [Windows](#win)
|
||||
|
||||
<a name="mac" />
|
||||
|
||||
### On Mac OS X
|
||||
|
||||
Install the rb-fsevent gem for [FSEvent](http://en.wikipedia.org/wiki/FSEvents) support:
|
||||
|
||||
``` bash
|
||||
$ gem install rb-fsevent
|
||||
```
|
||||
$ gem install rb-fsevent
|
||||
|
||||
You have two possibilities:
|
||||
You have three possibilities for getting Growl support:
|
||||
|
||||
Use the [growl_notify gem](https://rubygems.org/gems/growl_notify) (recommended):
|
||||
Use the [growl_notify gem](https://rubygems.org/gems/growl_notify):
|
||||
|
||||
``` bash
|
||||
$ gem install growl_notify
|
||||
```
|
||||
$ gem install growl_notify
|
||||
|
||||
Use the [growlnotify](http://growl.info/extras.php#growlnotify) (cli tool for growl) + the [growl gem](https://rubygems.org/gems/growl).
|
||||
The `growl_notify` gem is compatible with Growl >= 1.3 and uses AppleScript to send Growl notifications.
|
||||
The gem needs a native C extension to make use of AppleScript and does not run on JRuby and MacRuby.
|
||||
|
||||
``` bash
|
||||
$ brew install growlnotify
|
||||
$ gem install growl
|
||||
```
|
||||
Use the [ruby_gntp gem](https://github.com/snaka/ruby_gntp):
|
||||
|
||||
And add them to your Gemfile:
|
||||
$ gem install ruby_gntp
|
||||
|
||||
``` ruby
|
||||
gem 'rb-fsevent'
|
||||
gem 'growl_notify' # or gem 'growl'
|
||||
```
|
||||
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.
|
||||
|
||||
The difference between growl and growl_notify is that growl_notify uses AppleScript to
|
||||
display a message, whereas growl uses the `growlnotify` command. In general the AppleScript
|
||||
approach is preferred, but you may also use the older growl gem.
|
||||
Use the [growl gem](https://rubygems.org/gems/growl):
|
||||
|
||||
$ gem install growl
|
||||
|
||||
The `growl` gem is compatible with all versions of Growl and uses a command line tool [growlnotify](http://growl.info/extras.php#growlnotify)
|
||||
that must be separately downloaded and installed. You can also install it with HomeBrew:
|
||||
|
||||
$ brew install growlnotify
|
||||
|
||||
Finally you have to add your Growl library of choice to your Gemfile:
|
||||
|
||||
gem 'rb-fsevent'
|
||||
gem 'growl_notify' # or gem 'ruby_gntp' or gem 'growl'
|
||||
|
||||
Have a look at the [Guard Wiki](https://github.com/guard/guard/wiki/Which-Growl-library-should-I-use) for more information.
|
||||
|
||||
<a name="linux" />
|
||||
|
||||
### On Linux
|
||||
|
||||
Install the [rb-inotify gem](https://rubygems.org/gems/rb-inotify) for [inotify](http://en.wikipedia.org/wiki/Inotify) support:
|
||||
|
||||
``` bash
|
||||
$ gem install rb-inotify
|
||||
```
|
||||
$ gem install rb-inotify
|
||||
|
||||
Install the [libnotify gem](https://rubygems.org/gems/libnotify) if you want visual notification support:
|
||||
|
||||
``` bash
|
||||
$ gem install libnotify
|
||||
```
|
||||
$ gem install libnotify
|
||||
|
||||
And add them to your Gemfile:
|
||||
|
||||
``` ruby
|
||||
gem 'rb-inotify'
|
||||
gem 'libnotify'
|
||||
```
|
||||
gem 'rb-inotify'
|
||||
gem 'libnotify'
|
||||
|
||||
<a name="win" />
|
||||
|
||||
### On Windows
|
||||
|
||||
Install the [rb-fchange gem](https://rubygems.org/gems/rb-fchange) for [Directory Change Notification](http://msdn.microsoft.com/en-us/library/aa365261\(VS.85\).aspx) support:
|
||||
|
||||
``` bash
|
||||
$ gem install rb-fchange
|
||||
```
|
||||
$ gem install rb-fchange
|
||||
|
||||
Install the [win32console gem](https://rubygems.org/gems/win32console) if you want colors in your terminal:
|
||||
|
||||
``` bash
|
||||
$ gem install win32console
|
||||
```
|
||||
$ gem install win32console
|
||||
|
||||
Install the [rb-notifu gem](https://rubygems.org/gems/rb-notifu) if you want visual notification support:
|
||||
|
||||
``` bash
|
||||
$ gem install rb-notifu
|
||||
```
|
||||
$ gem install rb-notifu
|
||||
|
||||
And add them to your Gemfile:
|
||||
|
||||
``` ruby
|
||||
gem 'rb-fchange'
|
||||
gem 'rb-notifu'
|
||||
```
|
||||
gem 'rb-fchange'
|
||||
gem 'rb-notifu'
|
||||
gem 'win32console'
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Just launch Guard inside your Ruby / Rails project with:
|
||||
|
||||
``` bash
|
||||
$ guard [start]
|
||||
```
|
||||
$ guard [start]
|
||||
|
||||
or if you use Bundler, to run the Guard executable specific to your bundle:
|
||||
|
||||
``` bash
|
||||
$ bundle exec guard [start]
|
||||
```
|
||||
$ bundle exec guard [start]
|
||||
|
||||
Guard will look for a Guardfile in your current directory. If it does not find one, it will look in your `$HOME` directory for a .Guardfile.
|
||||
|
||||
@ -159,19 +148,15 @@ Command line options
|
||||
|
||||
Shell can be cleared after each change:
|
||||
|
||||
``` bash
|
||||
$ guard --clear
|
||||
$ guard -c # shortcut
|
||||
```
|
||||
$ guard --clear
|
||||
$ guard -c # shortcut
|
||||
|
||||
### `-n`/`--notify` option
|
||||
|
||||
Notifications (growl/libnotify) can be disabled:
|
||||
|
||||
``` bash
|
||||
$ guard --notify false
|
||||
$ guard -n f # shortcut
|
||||
```
|
||||
$ guard --notify false
|
||||
$ guard -n f # shortcut
|
||||
|
||||
Notifications can also be disabled globally by setting a `GUARD_NOTIFY` environment variable to `false`
|
||||
|
||||
@ -179,54 +164,61 @@ Notifications can also be disabled globally by setting a `GUARD_NOTIFY` environm
|
||||
|
||||
Only certain guards groups can be run (see the Guardfile DSL below for creating groups):
|
||||
|
||||
``` bash
|
||||
$ guard --group group_name another_group_name
|
||||
$ guard -g group_name another_group_name # shortcut
|
||||
```
|
||||
$ guard --group group_name another_group_name
|
||||
$ guard -g group_name another_group_name # shortcut
|
||||
|
||||
### `-d`/`--debug` option
|
||||
|
||||
Guard can be run in debug mode:
|
||||
|
||||
``` bash
|
||||
$ guard --debug
|
||||
$ guard -d # shortcut
|
||||
```
|
||||
$ guard --debug
|
||||
$ guard -d # shortcut
|
||||
|
||||
### `-w`/`--watchdir` option
|
||||
|
||||
Guard can watch in any directory (instead of the current directory):
|
||||
|
||||
``` bash
|
||||
$ guard --watchdir ~/your/fancy/project
|
||||
$ guard -w ~/your/fancy/project # shortcut
|
||||
```
|
||||
$ guard --watchdir ~/your/fancy/project
|
||||
$ guard -w ~/your/fancy/project # shortcut
|
||||
|
||||
### `-G`/`--guardfile` option
|
||||
|
||||
Guard can use a Guardfile not located in the current directory:
|
||||
|
||||
``` bash
|
||||
$ guard --guardfile ~/.your_global_guardfile
|
||||
$ guard -G ~/.your_global_guardfile # shortcut
|
||||
```
|
||||
$ guard --guardfile ~/.your_global_guardfile
|
||||
$ guard -G ~/.your_global_guardfile # shortcut
|
||||
|
||||
### `-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:
|
||||
|
||||
``` bash
|
||||
$ guard help [TASK]
|
||||
```
|
||||
$ guard help [TASK]
|
||||
|
||||
Signal handlers
|
||||
---------------
|
||||
<a name="interactions" />
|
||||
|
||||
Signal handlers are used to interact with Guard:
|
||||
Interactions
|
||||
------------
|
||||
|
||||
* `Ctrl-C` - Calls each guard's `#stop` method, in the same order they are declared in the Guardfile, and then quits Guard itself.
|
||||
* `Ctrl-\` - Calls each guard's `#run_all` method, in the same order they are declared in the Guardfile.
|
||||
* `Ctrl-Z` - Calls each guard's `#reload` method, in the same order they are declared in the Guardfile.
|
||||
**From version >= 0.7.0 Posix Signal handlers are no more used to interact with Guard. If you're using a version < 0.7, please refer to the [README in the v0.6 branch](https://github.com/guard/guard/blob/v0.6/README.md).**
|
||||
|
||||
You can read more about [configure the signal keyboard shortcuts](https://github.com/guard/guard/wiki/Configure-keyboard-shortcuts) in the wiki.
|
||||
When Guard do nothing you can interact with by entering a command + hitting enter:
|
||||
|
||||
* `stop|quit|exit|s|q|e + enter` - Calls each guard's `#stop` method, in the same order they are declared in the Guardfile, and then quits Guard itself.
|
||||
* `reload|r|z + enter` - Calls each guard's `#reload` method, in the same order they are declared in the Guardfile.
|
||||
* `pause|p + enter` - Toggle files modification listening. Useful when switching git branches.
|
||||
* `just enter (no commands)` - Calls each guard's `#run_all` method, in the same order they are declared in the Guardfile.
|
||||
|
||||
Available Guards
|
||||
----------------
|
||||
@ -237,73 +229,62 @@ A list of the available guards is present [in the wiki](https://github.com/guard
|
||||
|
||||
Add it to your Gemfile (inside the `development` group):
|
||||
|
||||
``` ruby
|
||||
gem '<guard-name>'
|
||||
```
|
||||
gem '<guard-name>'
|
||||
|
||||
You can list all guards installed on your system with:
|
||||
|
||||
``` bash
|
||||
$ guard list
|
||||
```
|
||||
$ guard list
|
||||
|
||||
Insert default guard's definition to your Guardfile by running this command:
|
||||
|
||||
``` bash
|
||||
$ guard init <guard-name>
|
||||
```
|
||||
$ guard init <guard-name>
|
||||
|
||||
You are good to go, or you can modify your guards' definition to suit your needs.
|
||||
|
||||
Guardfile DSL
|
||||
-------------
|
||||
|
||||
The Guardfile DSL consists of just three simple methods: `#guard`, `#watch` & `#group`.
|
||||
The Guardfile DSL consists of the following methods:
|
||||
|
||||
Required:
|
||||
|
||||
* The `#guard` method allows you to add a guard with an optional hash of options.
|
||||
|
||||
Optional:
|
||||
|
||||
* The `#watch` method allows you to define which files are supervised by this guard. An optional block can be added to overwrite the paths sent to the guard's `#run_on_change` method or to launch any arbitrary command.
|
||||
* The `#group` method allows you to group several guards together. Groups to be run can be specified with the Guard DSL option `--group` (or `-g`). This comes in handy especially when you have a huge Guardfile and want to focus your development on a certain part. Guards that don't belong to a group are considered global and are always run.
|
||||
* The `#ignore_paths` method allows you to ignore top level directories altogether. This comes is handy when you have large amounts of non-source data in you project. By default .bundle, .git, log, tmp, and vendor are ignored. Currently it is only possible to ignore the immediate descendants of the watched directory.
|
||||
* `#guard` - Allows you to add a guard with an optional hash of options.
|
||||
* `#watch` - Allows you to define which files are supervised by a guard. An optional block can be added to overwrite the paths sent to the guard's `#run_on_change` method or to launch any arbitrary command.
|
||||
* `#group` - Allows you to group several guards together. Groups to be run can be specified with the Guard DSL option `--group` (or `-g`). This comes in handy especially when you have a huge Guardfile and want to focus your development on a certain part. Guards that don't belong to a group are considered global and are always run.
|
||||
* `#callback` - Allows you to execute arbitrary code before or after any of the `start`, `stop`, `reload`, `run_all` and `run_on_change` guards' method. You can even insert more hooks inside these methods. Please [checkout the Wiki page](https://github.com/guard/guard/wiki/Hooks-and-callbacks) for more details.
|
||||
* `#ignore_paths` - Allows you to ignore top level directories altogether. This comes is handy when you have large amounts of non-source data in you project. By default .bundle, .git, log, tmp, and vendor are ignored. Currently it is only possible to ignore the immediate descendants of the watched directory.
|
||||
|
||||
Example:
|
||||
|
||||
``` ruby
|
||||
ignore_paths 'foo', 'bar'
|
||||
ignore_paths 'foo', 'bar'
|
||||
|
||||
group 'backend' do
|
||||
guard 'bundler' do
|
||||
watch('Gemfile')
|
||||
end
|
||||
group 'backend' do
|
||||
guard 'bundler' do
|
||||
watch('Gemfile')
|
||||
end
|
||||
|
||||
guard 'rspec', :cli => '--color --format doc' do
|
||||
# Regexp watch patterns are matched with Regexp#match
|
||||
watch(%r{^spec/.+_spec\.rb$})
|
||||
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
||||
watch(%r{^spec/models/.+\.rb$}) { ["spec/models", "spec/acceptance"] }
|
||||
watch(%r{^spec/.+\.rb$}) { `say hello` }
|
||||
guard 'rspec', :cli => '--color --format doc' do
|
||||
# Regexp watch patterns are matched with Regexp#match
|
||||
watch(%r{^spec/.+_spec\.rb$})
|
||||
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
||||
watch(%r{^spec/models/.+\.rb$}) { ["spec/models", "spec/acceptance"] }
|
||||
watch(%r{^spec/.+\.rb$}) { `say hello` }
|
||||
|
||||
# String watch patterns are matched with simple '=='
|
||||
watch('spec/spec_helper.rb') { "spec" }
|
||||
end
|
||||
end
|
||||
# String watch patterns are matched with simple '=='
|
||||
watch('spec/spec_helper.rb') { "spec" }
|
||||
end
|
||||
end
|
||||
|
||||
group 'frontend' do
|
||||
guard 'coffeescript', :output => 'public/javascripts/compiled' do
|
||||
watch(%r{^app/coffeescripts/.+\.coffee$})
|
||||
end
|
||||
group 'frontend' do
|
||||
guard 'coffeescript', :output => 'public/javascripts/compiled' do
|
||||
watch(%r{^app/coffeescripts/.+\.coffee$})
|
||||
end
|
||||
|
||||
guard 'livereload' do
|
||||
watch(%r{^app/.+\.(erb|haml)$})
|
||||
end
|
||||
end
|
||||
```
|
||||
guard 'livereload' do
|
||||
watch(%r{^app/.+\.(erb|haml)$})
|
||||
end
|
||||
end
|
||||
|
||||
### Using a Guardfile without the `guard` binary
|
||||
Using a Guardfile without the `guard` binary
|
||||
--------------------------------------------
|
||||
|
||||
The Guardfile DSL can also be used in a programmatic fashion by calling directly `Guard::Dsl.evaluate_guardfile`.
|
||||
Available options are as follow:
|
||||
@ -315,37 +296,33 @@ Remember, without any options given, Guard will look for a Guardfile in your cur
|
||||
|
||||
For instance, you could use it as follow:
|
||||
|
||||
``` ruby
|
||||
gem 'guard'
|
||||
require 'guard'
|
||||
gem 'guard'
|
||||
require 'guard'
|
||||
|
||||
Guard.setup
|
||||
Guard.setup
|
||||
|
||||
Guard::Dsl.evaluate_guardfile(:guardfile => '/your/custom/path/to/a/valid/Guardfile')
|
||||
# or
|
||||
Guard::Dsl.evaluate_guardfile(:guardfile_contents => "
|
||||
guard 'rspec' do
|
||||
watch(%r{^spec/.+_spec\.rb$})
|
||||
end
|
||||
")
|
||||
```
|
||||
Guard::Dsl.evaluate_guardfile(:guardfile => '/your/custom/path/to/a/valid/Guardfile')
|
||||
# or
|
||||
Guard::Dsl.evaluate_guardfile(:guardfile_contents => "
|
||||
guard 'rspec' do
|
||||
watch(%r{^spec/.+_spec\.rb$})
|
||||
end
|
||||
")
|
||||
|
||||
### Listing defined guards/groups for the current project
|
||||
|
||||
You can list the defined groups and guards for the current Guardfile from the command line using `guard show` or `guard -T`:
|
||||
|
||||
``` bash
|
||||
# guard -T
|
||||
$ guard -T
|
||||
|
||||
(global):
|
||||
shell
|
||||
Group backend:
|
||||
bundler
|
||||
rspec: cli => "--color --format doc"
|
||||
Group frontend:
|
||||
coffeescript: output => "public/javascripts/compiled"
|
||||
livereload
|
||||
```
|
||||
(global):
|
||||
shell
|
||||
Group backend:
|
||||
bundler
|
||||
rspec: cli => "--color --format doc"
|
||||
Group frontend:
|
||||
coffeescript: output => "public/javascripts/compiled"
|
||||
livereload
|
||||
|
||||
User config file
|
||||
----------------
|
||||
@ -355,118 +332,119 @@ the Guardfile. This can be used for tasks you want guard to handle but
|
||||
other users probably don't. For example, indexing your source tree with
|
||||
[Ctags](http://ctags.sourceforge.net):
|
||||
|
||||
``` ruby
|
||||
guard 'shell' do
|
||||
watch(%r{^(?:app|lib)/.+\.rb$}) { `ctags -R` }
|
||||
end
|
||||
```
|
||||
guard 'shell' do
|
||||
watch(%r{^(?:app|lib)/.+\.rb$}) { `ctags -R` }
|
||||
end
|
||||
|
||||
Create a new guard
|
||||
------------------
|
||||
|
||||
Creating a new guard is very easy, just create a new gem (`bundle gem` if you use Bundler) with this basic structure:
|
||||
|
||||
```
|
||||
.travis.yml # bonus point!
|
||||
CHANGELOG.md # bonus point!
|
||||
Gemfile
|
||||
guard-name.gemspec
|
||||
Guardfile
|
||||
lib/
|
||||
guard/
|
||||
guard-name/
|
||||
templates/
|
||||
Guardfile # needed for `guard init <guard-name>`
|
||||
version.rb
|
||||
guard-name.rb
|
||||
test/ # or spec/
|
||||
README.md
|
||||
```
|
||||
.travis.yml # bonus point!
|
||||
CHANGELOG.md # bonus point!
|
||||
Gemfile
|
||||
guard-name.gemspec
|
||||
Guardfile
|
||||
lib/
|
||||
guard/
|
||||
guard-name/
|
||||
templates/
|
||||
Guardfile # needed for `guard init <guard-name>`
|
||||
version.rb
|
||||
guard-name.rb
|
||||
test/ # or spec/
|
||||
README.md
|
||||
|
||||
`Guard::GuardName` (in `lib/guard/guard-name.rb`) must inherit from `Guard::Guard` and should overwrite at least one of the five basic `Guard::Guard` instance methods.
|
||||
`Guard::GuardName` (in `lib/guard/guard-name.rb`) must inherit from
|
||||
[Guard::Guard](http://rubydoc.info/github/guard/guard/master/Guard/Guard) and should overwrite at least one of
|
||||
the basic `Guard::Guard` task methods.
|
||||
|
||||
Here is an example scaffold for `lib/guard/guard-name.rb`:
|
||||
|
||||
``` ruby
|
||||
require 'guard'
|
||||
require 'guard/guard'
|
||||
require 'guard'
|
||||
require 'guard/guard'
|
||||
|
||||
module Guard
|
||||
class GuardName < Guard
|
||||
module Guard
|
||||
class GuardName < Guard
|
||||
|
||||
def initialize(watchers=[], options={})
|
||||
super
|
||||
# init stuff here, thx!
|
||||
# Initialize a Guard.
|
||||
# @param [Array<Guard::Watcher>] watchers the Guard file watchers
|
||||
# @param [Hash] options the custom Guard options
|
||||
def initialize(watchers = [], options = {})
|
||||
super
|
||||
end
|
||||
|
||||
# Call once when Guard starts. Please override initialize method to init stuff.
|
||||
# @raise [:task_has_failed] when start has failed
|
||||
def start
|
||||
end
|
||||
|
||||
# Called when `stop|quit|exit|s|q|e + enter` is pressed (when Guard quits).
|
||||
# @raise [:task_has_failed] when stop has failed
|
||||
def stop
|
||||
end
|
||||
|
||||
# Called when `reload|r|z + enter` is pressed.
|
||||
# This method should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/...
|
||||
# @raise [:task_has_failed] when reload has failed
|
||||
def reload
|
||||
end
|
||||
|
||||
# Called when just `enter` is pressed
|
||||
# This method should be principally used for long action like running all specs/tests/...
|
||||
# @raise [:task_has_failed] when run_all has failed
|
||||
def run_all
|
||||
end
|
||||
|
||||
# Called on file(s) modifications that the Guard watches.
|
||||
# @param [Array<String>] paths the changes files or paths
|
||||
# @raise [:task_has_failed] when run_on_change has failed
|
||||
def run_on_change(paths)
|
||||
end
|
||||
|
||||
# Called on file(s) deletions that the Guard watches.
|
||||
# @param [Array<String>] paths the deleted files or paths
|
||||
# @raise [:task_has_failed] when run_on_change has failed
|
||||
def run_on_deletion(paths)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
# =================
|
||||
# = Guard methods =
|
||||
# =================
|
||||
|
||||
# If one of those methods raise an exception, the Guard::GuardName instance
|
||||
# will be removed from the active guards.
|
||||
|
||||
# Called once when Guard starts
|
||||
# Please override initialize method to init stuff
|
||||
def start
|
||||
true
|
||||
end
|
||||
|
||||
# Called on Ctrl-C signal (when Guard quits)
|
||||
def stop
|
||||
true
|
||||
end
|
||||
|
||||
# Called on Ctrl-Z signal
|
||||
# This method should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/...
|
||||
def reload
|
||||
true
|
||||
end
|
||||
|
||||
# Called on Ctrl-\ signal
|
||||
# This method should be principally used for long action like running all specs/tests/...
|
||||
def run_all
|
||||
true
|
||||
end
|
||||
|
||||
# Called on file(s) modifications
|
||||
def run_on_change(paths)
|
||||
true
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Please take a look at the [existing guards' source code](https://github.com/guard/guard/wiki/List-of-available-Guards) for more concrete example and inspiration.
|
||||
Please take a look at the [existing guards' source code](https://github.com/guard/guard/wiki/List-of-available-Guards)
|
||||
for more concrete example and inspiration.
|
||||
|
||||
Alternatively, a new guard can be added inline to a Guardfile with this basic structure:
|
||||
|
||||
``` ruby
|
||||
require 'guard/guard'
|
||||
require 'guard/guard'
|
||||
|
||||
module ::Guard
|
||||
class InlineGuard < ::Guard::Guard
|
||||
def run_all
|
||||
true
|
||||
module ::Guard
|
||||
class InlineGuard < ::Guard::Guard
|
||||
def run_all
|
||||
end
|
||||
|
||||
def run_on_change(paths)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def run_on_change(paths)
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Here is a very cool example by [@avdi](https://github.com/avdi) : http://avdi.org/devblog/2011/06/15/a-guardfile-for-redis
|
||||
Here is a very cool example by [@avdi](https://github.com/avdi) : [http://avdi.org/devblog/2011/06/15/a-guardfile-for-redis](http://avdi.org/devblog/2011/06/15/a-guardfile-for-redis)
|
||||
|
||||
Development
|
||||
-----------
|
||||
|
||||
* Documentation hosted at [RubyDoc](http://rubydoc.info/github/guard/guard/master/frames).
|
||||
* Source hosted at [GitHub](https://github.com/guard/guard).
|
||||
* Report issues and feature requests to [GitHub Issues](https://github.com/guard/guard/issues).
|
||||
|
||||
Pull requests are very welcome! Make sure your patches are well tested. Please create a topic branch for every separate change you make. Please **do not change** the version in your pull-request.
|
||||
Pull requests are very welcome! Please try to follow these simple "rules", though:
|
||||
|
||||
- Please create a topic branch for every separate change you make;
|
||||
- Make sure your patches are well tested;
|
||||
- Update the README (if applicable);
|
||||
- Update the CHANGELOG (maybe not for a typo but don't hesitate!);
|
||||
- Please **do not change** the version number.
|
||||
|
||||
For questions please join us on our [Google group](http://groups.google.com/group/guard-dev) or on `#guard` (irc.freenode.net).
|
||||
|
||||
@ -476,6 +454,6 @@ Author
|
||||
[Thibaud Guillaume-Gentil](https://github.com/thibaudgg)
|
||||
|
||||
Contributors
|
||||
------
|
||||
------------
|
||||
|
||||
https://github.com/guard/guard/contributors
|
||||
[https://github.com/guard/guard/contributors](https://github.com/guard/guard/contributors)
|
||||
|
4
Rakefile
4
Rakefile
@ -39,8 +39,8 @@ namespace(:spec) do
|
||||
echo "`ruby -v`";
|
||||
for ((c=1; c<$ruby_version_string_size; c++)); do echo -n "="; done
|
||||
echo;
|
||||
bundle install;
|
||||
bundle exec rspec spec -f doc 2>&1;'
|
||||
RBXOPT="-Xrbc.db" bundle install;
|
||||
RBXOPT="-Xrbc.db" bundle exec rspec spec -f doc 2>&1;'
|
||||
BASH
|
||||
end
|
||||
end
|
||||
|
@ -3,4 +3,4 @@
|
||||
require 'guard'
|
||||
require 'guard/cli'
|
||||
|
||||
Guard::CLI.start
|
||||
Guard::CLI.start
|
||||
|
@ -14,11 +14,13 @@ Gem::Specification.new do |s|
|
||||
s.required_rubygems_version = '>= 1.3.6'
|
||||
s.rubyforge_project = 'guard'
|
||||
|
||||
s.add_dependency 'thor', '~> 0.14.6'
|
||||
|
||||
s.add_development_dependency 'bundler'
|
||||
s.add_development_dependency 'rspec', '~> 2.6.0'
|
||||
s.add_development_dependency 'guard-rspec', '~> 0.3.1'
|
||||
|
||||
s.add_dependency 'thor', '~> 0.14.6'
|
||||
s.add_development_dependency 'yard', '~> 0.7.2'
|
||||
s.add_development_dependency 'kramdown', '~> 0.13.3'
|
||||
|
||||
s.files = Dir.glob('{bin,images,lib}/**/*') + %w[CHANGELOG.md LICENSE man/guard.1 man/guard.1.html README.md]
|
||||
s.executable = 'guard'
|
||||
|
403
lib/guard.rb
403
lib/guard.rb
@ -1,132 +1,412 @@
|
||||
require 'thread'
|
||||
|
||||
# Guard is the main module for all Guard related modules and classes.
|
||||
# Also other Guard implementation should use this namespace.
|
||||
#
|
||||
module Guard
|
||||
|
||||
autoload :UI, 'guard/ui'
|
||||
autoload :Dsl, 'guard/dsl'
|
||||
autoload :DslDescriber, 'guard/dsl_describer'
|
||||
autoload :Group, 'guard/group'
|
||||
autoload :Interactor, 'guard/interactor'
|
||||
autoload :Listener, 'guard/listener'
|
||||
autoload :Watcher, 'guard/watcher'
|
||||
autoload :Notifier, 'guard/notifier'
|
||||
autoload :Hook, 'guard/hook'
|
||||
|
||||
class << self
|
||||
attr_accessor :options, :guards, :groups, :listener
|
||||
attr_accessor :options, :interactor, :listener, :lock
|
||||
|
||||
# initialize this singleton
|
||||
# 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.
|
||||
#
|
||||
# @option options [Boolean] clear if auto clear the UI should be done
|
||||
# @option options [Boolean] notify if system notifications should be shown
|
||||
# @option options [Boolean] debug if debug output should be shown
|
||||
# @option options [Array<String>] group the list of groups to start
|
||||
# @option options [String] watchdir the director to watch
|
||||
# @option options [String] guardfile the path to the Guardfile
|
||||
# @option options [Boolean] watch_all_modifications watches all file modifications if true
|
||||
#
|
||||
def setup(options = {})
|
||||
@options = options
|
||||
@listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd)
|
||||
@groups = [:default]
|
||||
@guards = []
|
||||
@lock = Mutex.new
|
||||
|
||||
@options[:notify] && ENV["GUARD_NOTIFY"] != 'false' ? Notifier.turn_on : Notifier.turn_off
|
||||
@options = options
|
||||
@guards = []
|
||||
@groups = [Group.new(:default)]
|
||||
@interactor = Interactor.new unless @options[:no_interactions]
|
||||
@listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd, options)
|
||||
|
||||
@options[:notify] && ENV['GUARD_NOTIFY'] != 'false' ? Notifier.turn_on : Notifier.turn_off
|
||||
|
||||
UI.clear if @options[:clear]
|
||||
|
||||
debug_command_execution if @options[:debug]
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
def start(options = {})
|
||||
Interactor.init_signal_traps
|
||||
# Smart accessor for retrieving a specific guard or several guards at once.
|
||||
#
|
||||
# @param [String, Symbol] filter return the guard with the given name, or nil if not found
|
||||
# @param [Regexp] filter returns all guards matching the Regexp, or [] if no guard found
|
||||
# @param [Hash] filter returns all guards matching the given Hash.
|
||||
# Example: `{ :name => 'rspec', :group => 'backend' }`, or [] if no guard found
|
||||
# @param [NilClass] filter returns all guards
|
||||
#
|
||||
# @see Guard.groups
|
||||
#
|
||||
def guards(filter = nil)
|
||||
case filter
|
||||
when String, Symbol
|
||||
@guards.find { |guard| guard.class.to_s.downcase.sub('guard::', '') == filter.to_s.downcase.gsub('-', '') }
|
||||
when Regexp
|
||||
@guards.find_all { |guard| guard.class.to_s.downcase.sub('guard::', '') =~ filter }
|
||||
when Hash
|
||||
filter.inject(@guards) do |matches, (k, v)|
|
||||
if k.to_sym == :name
|
||||
matches.find_all { |guard| guard.class.to_s.downcase.sub('guard::', '') == v.to_s.downcase.gsub('-', '') }
|
||||
else
|
||||
matches.find_all { |guard| guard.send(k).to_sym == v.to_sym }
|
||||
end
|
||||
end
|
||||
else
|
||||
@guards
|
||||
end
|
||||
end
|
||||
|
||||
# Smart accessor for retrieving a specific group or several groups at once.
|
||||
#
|
||||
# @param [NilClass] filter returns all groups
|
||||
# @param [String, Symbol] filter return the group with the given name, or nil if not found
|
||||
# @param [Regexp] filter returns all groups matching the Regexp, or [] if no group found
|
||||
#
|
||||
# @see Guard.guards
|
||||
#
|
||||
def groups(filter = nil)
|
||||
case filter
|
||||
when String, Symbol
|
||||
@groups.find { |group| group.name == filter.to_sym }
|
||||
when Regexp
|
||||
@groups.find_all { |group| group.name.to_s =~ filter }
|
||||
else
|
||||
@groups
|
||||
end
|
||||
end
|
||||
|
||||
# Start Guard by evaluate the `Guardfile`, initialize the declared Guards
|
||||
# and start the available file change listener.
|
||||
#
|
||||
# @option options [Boolean] clear if auto clear the UI should be done
|
||||
# @option options [Boolean] notify if system notifications should be shown
|
||||
# @option options [Boolean] debug if debug output should be shown
|
||||
# @option options [Array<String>] group the list of groups to start
|
||||
# @option options [String] watchdir the director to watch
|
||||
# @option options [String] guardfile the path to the Guardfile
|
||||
#
|
||||
def start(options = {})
|
||||
setup(options)
|
||||
|
||||
Dsl.evaluate_guardfile(options)
|
||||
|
||||
listener.on_change do |files|
|
||||
Dsl.reevaluate_guardfile if Watcher.match_guardfile?(files)
|
||||
|
||||
run { run_on_change_for_all_guards(files) } if Watcher.match_files?(guards, files)
|
||||
Dsl.reevaluate_guardfile if Watcher.match_guardfile?(files)
|
||||
listener.changed_files += files if Watcher.match_files?(guards, files)
|
||||
end
|
||||
|
||||
UI.info "Guard is now watching at '#{listener.directory}'"
|
||||
guards.each { |guard| supervised_task(guard, :start) }
|
||||
UI.info "Guard is now watching at '#{ listener.directory }'"
|
||||
|
||||
run_guard_task(:start)
|
||||
|
||||
interactor.start if interactor
|
||||
listener.start
|
||||
end
|
||||
|
||||
def run_on_change_for_all_guards(files)
|
||||
guards.each do |guard|
|
||||
paths = Watcher.match_files(guard, files)
|
||||
unless paths.empty?
|
||||
UI.debug "#{guard.class.name}#run_on_change with #{paths.inspect}"
|
||||
supervised_task(guard, :run_on_change, paths)
|
||||
# Stop Guard listening to file changes
|
||||
#
|
||||
def stop
|
||||
UI.info 'Bye bye...', :reset => true
|
||||
|
||||
run_guard_task(:stop)
|
||||
|
||||
listener.stop
|
||||
abort
|
||||
end
|
||||
|
||||
# Reload all Guards currently enabled.
|
||||
#
|
||||
def reload
|
||||
run do
|
||||
run_guard_task(:reload)
|
||||
end
|
||||
end
|
||||
|
||||
# Trigger `run_all` on all Guards currently enabled.
|
||||
#
|
||||
def run_all
|
||||
run do
|
||||
run_guard_task(:run_all)
|
||||
end
|
||||
end
|
||||
|
||||
# Pause Guard listening to file changes.
|
||||
#
|
||||
def pause
|
||||
if listener.paused?
|
||||
UI.info 'Un-paused files modification listening', :reset => true
|
||||
listener.clear_changed_files
|
||||
listener.run
|
||||
else
|
||||
UI.info 'Paused files modification listening', :reset => true
|
||||
listener.pause
|
||||
end
|
||||
end
|
||||
|
||||
# Trigger `run_on_change` on all Guards currently enabled.
|
||||
#
|
||||
def run_on_change(paths)
|
||||
run do
|
||||
run_guard_task(:run_on_change, paths)
|
||||
end
|
||||
end
|
||||
|
||||
# Run a block where the listener and the interactor is
|
||||
# blocked.
|
||||
#
|
||||
# @yield the block to run
|
||||
#
|
||||
def run
|
||||
UI.clear if options[:clear]
|
||||
|
||||
lock.synchronize do
|
||||
begin
|
||||
interactor.stop_if_not_current if interactor
|
||||
yield
|
||||
rescue Interrupt
|
||||
end
|
||||
|
||||
interactor.start if interactor
|
||||
end
|
||||
end
|
||||
|
||||
# Loop through all groups and run the given task for each Guard.
|
||||
#
|
||||
# Stop the task run for the all Guards within a group if one Guard
|
||||
# throws `:task_has_failed`.
|
||||
#
|
||||
# @param [Symbol] task the task to run
|
||||
# @param [Array<String>] files the list of files to pass to the task
|
||||
#
|
||||
def run_guard_task(task, files = nil)
|
||||
groups.each do |group|
|
||||
catch :task_has_failed do
|
||||
guards(:group => group.name).each do |guard|
|
||||
if task == :run_on_change
|
||||
run_on_change_task(files, guard, task)
|
||||
else
|
||||
run_supervised_task(guard, task)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Reparse the whole directory to catch new files modified during the guards run
|
||||
new_modified_files = listener.modified_files([listener.directory], :all => true)
|
||||
if !new_modified_files.empty? && Watcher.match_files?(guards, new_modified_files)
|
||||
run { run_on_change_for_all_guards(new_modified_files) }
|
||||
# Run the `:run_on_change` task. When the option `:watch_all_modifications` is set,
|
||||
# the task is split to run changed paths on {Guard::Guard#run_on_change}, whereas
|
||||
# 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
|
||||
|
||||
# Let a guard execute its task but
|
||||
# fire it if his work leads to a system failure
|
||||
def supervised_task(guard, task_to_supervise, *args)
|
||||
guard.send(task_to_supervise, *args)
|
||||
# Detects the paths that have changed.
|
||||
#
|
||||
# Deleted paths are prefixed by an exclamation point.
|
||||
# @see Guard::Listener#modified_files
|
||||
#
|
||||
# @param [Array<String>] paths the watched paths
|
||||
# @return [Array<String>] the changed paths
|
||||
#
|
||||
def changed_paths(paths)
|
||||
paths.select { |f| !f.start_with?('!') }
|
||||
end
|
||||
|
||||
# Detects the paths that have been deleted.
|
||||
#
|
||||
# Deleted paths are prefixed by an exclamation point.
|
||||
# @see Guard::Listener#modified_files
|
||||
#
|
||||
# @param [Array<String>] paths the watched paths
|
||||
# @return [Array<String>] the deleted paths
|
||||
#
|
||||
def deleted_paths(paths)
|
||||
paths.select { |f| f.start_with?('!') }.map { |f| f.slice(1..-1) }
|
||||
end
|
||||
|
||||
# Run a Guard task, but remove the Guard when his work leads to a system failure.
|
||||
#
|
||||
# When the Group has `:halt_on_fail` disabled, we've to catch `:task_has_failed`
|
||||
# here in order to avoid an uncaught throw error.
|
||||
#
|
||||
# @param [Guard::Guard] guard the Guard to execute
|
||||
# @param [Symbol] task the task to run
|
||||
# @param [Array] args the arguments for the task
|
||||
# @raise [:task_has_failed] when task has failed
|
||||
#
|
||||
def run_supervised_task(guard, task, *args)
|
||||
catch guard_symbol(guard) do
|
||||
guard.hook("#{ task }_begin", *args)
|
||||
result = guard.send(task, *args)
|
||||
guard.hook("#{ task }_end", result)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
rescue Exception => ex
|
||||
UI.error("#{guard.class.name} failed to achieve its <#{task_to_supervise.to_s}>, exception was:" +
|
||||
"\n#{ex.class}: #{ex.message}\n#{ex.backtrace.join("\n")}")
|
||||
UI.error("#{ guard.class.name } failed to achieve its <#{ task.to_s }>, exception was:" +
|
||||
"\n#{ ex.class }: #{ ex.message }\n#{ ex.backtrace.join("\n") }")
|
||||
|
||||
guards.delete guard
|
||||
UI.info("\n#{guard.class.name} has just been fired")
|
||||
return ex
|
||||
UI.info("\n#{ guard.class.name } has just been fired")
|
||||
|
||||
ex
|
||||
end
|
||||
|
||||
def run
|
||||
listener.stop
|
||||
UI.clear if options[:clear]
|
||||
begin
|
||||
yield
|
||||
rescue Interrupt
|
||||
end
|
||||
listener.start
|
||||
end
|
||||
|
||||
def add_guard(name, watchers = [], options = {})
|
||||
if name.to_sym == :ego
|
||||
UI.deprecation("Guard::Ego is now part of Guard. You can remove it from your Guardfile.")
|
||||
# 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
|
||||
guard = get_guard_class(name).new(watchers, options)
|
||||
@guards << guard
|
||||
:task_has_failed
|
||||
end
|
||||
end
|
||||
|
||||
def add_group(name)
|
||||
@groups << name.to_sym unless name.nil?
|
||||
# Add a Guard to use.
|
||||
#
|
||||
# @param [String] name the Guard name
|
||||
# @param [Array<Watcher>] watchers the list of declared watchers
|
||||
# @param [Array<Hash>] callbacks the list of callbacks
|
||||
# @param [Hash] options the Guard options (see the given Guard documentation)
|
||||
#
|
||||
def add_guard(name, watchers = [], callbacks = [], options = {})
|
||||
if name.to_sym == :ego
|
||||
UI.deprecation('Guard::Ego is now part of Guard. You can remove it from your Guardfile.')
|
||||
else
|
||||
guard_class = get_guard_class(name)
|
||||
callbacks.each { |callback| Hook.add_callback(callback[:listener], guard_class, callback[:events]) }
|
||||
@guards << guard_class.new(watchers, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Add a Guard group.
|
||||
#
|
||||
# @param [String] name the group name
|
||||
# @option options [Boolean] halt_on_fail if a task execution
|
||||
# should be halted for all Guards in this group if one Guard throws `:task_has_failed`
|
||||
# @return [Guard::Group] the group added (or retrieved from the `@groups` variable if already present)
|
||||
#
|
||||
def add_group(name, options = {})
|
||||
group = groups(name)
|
||||
if group.nil?
|
||||
group = Group.new(name, options)
|
||||
@groups << group
|
||||
end
|
||||
group
|
||||
end
|
||||
|
||||
# Tries to load the Guard main class.
|
||||
#
|
||||
# @param [String] name the name of the Guard
|
||||
# @return [Class, nil] the loaded class
|
||||
#
|
||||
def get_guard_class(name)
|
||||
name = name.to_s
|
||||
try_require = false
|
||||
const_name = name.downcase.gsub('-', '')
|
||||
begin
|
||||
require "guard/#{name.downcase}" if try_require
|
||||
require "guard/#{ name.downcase }" if try_require
|
||||
self.const_get(self.constants.find { |c| c.to_s.downcase == const_name })
|
||||
rescue TypeError
|
||||
unless try_require
|
||||
try_require = true
|
||||
retry
|
||||
else
|
||||
UI.error "Could not find class Guard::#{const_name.capitalize}"
|
||||
UI.error "Could not find class Guard::#{ const_name.capitalize }"
|
||||
end
|
||||
rescue LoadError => loadError
|
||||
UI.error "Could not load 'guard/#{name.downcase}' or find class Guard::#{const_name.capitalize}"
|
||||
UI.error "Could not load 'guard/#{ name.downcase }' or find class Guard::#{ const_name.capitalize }"
|
||||
UI.error loadError.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Locate a path to a Guard gem.
|
||||
#
|
||||
# @param [String] name the name of the Guard without the prefix `guard-`
|
||||
# @return [String] the full path to the Guard gem
|
||||
#
|
||||
def locate_guard(name)
|
||||
if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
|
||||
Gem::Specification.find_by_name("guard-#{name}").full_gem_path
|
||||
Gem::Specification.find_by_name("guard-#{ name }").full_gem_path
|
||||
else
|
||||
Gem.source_index.find_name("guard-#{name}").last.full_gem_path
|
||||
Gem.source_index.find_name("guard-#{ name }").last.full_gem_path
|
||||
end
|
||||
rescue
|
||||
UI.error "Could not find 'guard-#{name}' gem path."
|
||||
UI.error "Could not find 'guard-#{ name }' gem path."
|
||||
end
|
||||
|
||||
##
|
||||
# Returns a list of guard Gem names installed locally.
|
||||
#
|
||||
# @return [Array<String>] a list of guard gem names
|
||||
#
|
||||
def guard_gem_names
|
||||
if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
|
||||
Gem::Specification.find_all.select { |x| x.name =~ /^guard-/ }
|
||||
@ -135,16 +415,19 @@ module Guard
|
||||
end.map { |x| x.name.sub /^guard-/, '' }
|
||||
end
|
||||
|
||||
# Adds a command logger in debug mode. This wraps common command
|
||||
# execution functions and logs the executed command before execution.
|
||||
#
|
||||
def debug_command_execution
|
||||
Kernel.send(:alias_method, :original_system, :system)
|
||||
Kernel.send(:define_method, :system) do |command, *args|
|
||||
::Guard::UI.debug "Command execution: #{command} #{args.join(' ')}"
|
||||
::Guard::UI.debug "Command execution: #{ command } #{ args.join(' ') }"
|
||||
original_system command, *args
|
||||
end
|
||||
|
||||
Kernel.send(:alias_method, :original_backtick, :"`")
|
||||
Kernel.send(:define_method, :"`") do |command|
|
||||
::Guard::UI.debug "Command execution: #{command}"
|
||||
Kernel.send(:alias_method, :original_backtick, :'`')
|
||||
Kernel.send(:define_method, :'`') do |command|
|
||||
::Guard::UI.debug "Command execution: #{ command }"
|
||||
original_backtick command
|
||||
end
|
||||
end
|
||||
|
163
lib/guard/cli.rb
163
lib/guard/cli.rb
@ -2,89 +2,118 @@ require 'thor'
|
||||
require 'guard/version'
|
||||
|
||||
module Guard
|
||||
|
||||
# Facade for the Guard command line interface managed by [Thor](https://github.com/wycats/thor).
|
||||
# This is the main interface to Guard that is called by the Guard binary `bin/guard`.
|
||||
# Do not put any logic in here, create a class and delegate instead.
|
||||
#
|
||||
class CLI < Thor
|
||||
|
||||
default_task :start
|
||||
|
||||
method_option :clear, :type => :boolean, :default => false, :aliases => '-c', :banner => "Auto clear shell before each change/run_all/reload"
|
||||
method_option :notify, :type => :boolean, :default => true, :aliases => '-n', :banner => "Notifications feature (growl/libnotify)"
|
||||
method_option :debug, :type => :boolean, :default => false, :aliases => '-d', :banner => "Print debug messages"
|
||||
method_option :group, :type => :array, :default => [], :aliases => '-g', :banner => "Run only the passed groups"
|
||||
method_option :watchdir, :type => :string, :aliases => '-w', :banner => "Specify the directory to watch"
|
||||
method_option :guardfile, :type => :string, :aliases => '-G', :banner => "Specify a Guardfile"
|
||||
desc 'start', 'Starts Guard'
|
||||
|
||||
desc "start", "Starts Guard"
|
||||
method_option :clear,
|
||||
:type => :boolean,
|
||||
:default => false,
|
||||
:aliases => '-c',
|
||||
:banner => 'Auto clear shell before each change/run_all/reload'
|
||||
|
||||
method_option :notify,
|
||||
:type => :boolean,
|
||||
:default => true,
|
||||
:aliases => '-n',
|
||||
:banner => 'Notifications feature (growl/libnotify)'
|
||||
|
||||
method_option :debug,
|
||||
:type => :boolean,
|
||||
:default => false,
|
||||
:aliases => '-d',
|
||||
:banner => 'Print debug messages'
|
||||
|
||||
method_option :group,
|
||||
:type => :array,
|
||||
:default => [],
|
||||
:aliases => '-g',
|
||||
:banner => 'Run only the passed groups'
|
||||
|
||||
method_option :watchdir,
|
||||
:type => :string,
|
||||
:aliases => '-w',
|
||||
:banner => 'Specify the directory to watch'
|
||||
|
||||
method_option :guardfile,
|
||||
:type => :string,
|
||||
:aliases => '-G',
|
||||
:banner => 'Specify a Guardfile'
|
||||
|
||||
method_option :watch_all_modifications,
|
||||
:type => :boolean,
|
||||
:default => false,
|
||||
:aliases => '-A',
|
||||
:banner => 'Watch for all file modifications including moves and deletions'
|
||||
|
||||
method_option :no_interactions,
|
||||
:type => :boolean,
|
||||
:default => false,
|
||||
:aliases => '-i',
|
||||
:banner => 'Turn off completely any guard terminal interactions'
|
||||
|
||||
# Start Guard by initialize the defined Guards and watch the file system.
|
||||
# This is the default task, so calling `guard` is the same as calling `guard start`.
|
||||
#
|
||||
# @see Guard.start
|
||||
#
|
||||
def start
|
||||
::Guard.start(options)
|
||||
end
|
||||
|
||||
desc "list", "Lists guards that can be used with init"
|
||||
desc 'list', 'Lists guards that can be used with init'
|
||||
|
||||
# List the Guards that are available for use in your system and marks
|
||||
# those that are currently used in your `Guardfile`.
|
||||
#
|
||||
# @see Guard::DslDescriber.list
|
||||
#
|
||||
def list
|
||||
::Guard::DslDescriber.evaluate_guardfile(options)
|
||||
installed = []
|
||||
::Guard::DslDescriber.guardfile_structure.each do |group|
|
||||
group[:guards].each {|x| installed << x[:name]} if group[:guards]
|
||||
end
|
||||
|
||||
::Guard::UI.info "Available guards:"
|
||||
::Guard::guard_gem_names.sort.each do |name|
|
||||
if installed.include? name
|
||||
::Guard::UI.info " #{name} *"
|
||||
else
|
||||
::Guard::UI.info " #{name}"
|
||||
end
|
||||
end
|
||||
::Guard::UI.info ' '
|
||||
::Guard::UI.info "See also https://github.com/guard/guard/wiki/List-of-available-Guards"
|
||||
::Guard::UI.info "* denotes ones already in your Guardfile"
|
||||
::Guard::DslDescriber.list(options)
|
||||
end
|
||||
|
||||
desc "version", "Prints Guard's version"
|
||||
def version
|
||||
::Guard::UI.info "Guard version #{Guard::VERSION}"
|
||||
end
|
||||
desc 'version', 'Show the Guard version'
|
||||
map %w(-v --version) => :version
|
||||
|
||||
desc "init [GUARD]", "Generates a Guardfile into the current working directory, or insert the given GUARD in an existing Guardfile"
|
||||
# Shows the current version of Guard.
|
||||
#
|
||||
# @see Guard::VERSION
|
||||
#
|
||||
def version
|
||||
::Guard::UI.info "Guard version #{ Guard::VERSION }"
|
||||
end
|
||||
|
||||
desc 'init [GUARD]', 'Generates a Guardfile at the current working directory, or insert the given GUARD to an existing Guardfile'
|
||||
|
||||
# Appends the Guard template to the `Guardfile`, or creates an initial
|
||||
# `Guardfile` when no Guard name is passed.
|
||||
#
|
||||
# @see Guard.initialize_template
|
||||
#
|
||||
# @param [String] guard_name the name of the Guard to initialize
|
||||
#
|
||||
def init(guard_name = nil)
|
||||
if !File.exist?("Guardfile")
|
||||
puts "Writing new Guardfile to #{Dir.pwd}/Guardfile"
|
||||
FileUtils.cp(File.expand_path('../templates/Guardfile', __FILE__), 'Guardfile')
|
||||
elsif guard_name.nil?
|
||||
::Guard::UI.error "Guardfile already exists at #{Dir.pwd}/Guardfile"
|
||||
exit 1
|
||||
end
|
||||
|
||||
if guard_name
|
||||
guard_class = ::Guard.get_guard_class(guard_name)
|
||||
guard_class.init(guard_name)
|
||||
end
|
||||
::Guard.initialize_template(guard_name)
|
||||
end
|
||||
|
||||
desc "show", "Show all defined Guards and their options"
|
||||
def show
|
||||
::Guard::DslDescriber.evaluate_guardfile(options)
|
||||
|
||||
::Guard::DslDescriber.guardfile_structure.each do |group|
|
||||
if !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]}"
|
||||
|
||||
if !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
|
||||
desc 'show', 'Show all defined Guards and their options'
|
||||
map %w(-T) => :show
|
||||
|
||||
# Shows all Guards and their options that are defined in
|
||||
# the `Guardfile`.
|
||||
#
|
||||
# @see Guard::DslDescriber.show
|
||||
#
|
||||
def show
|
||||
::Guard::DslDescriber.show(options)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
306
lib/guard/dsl.rb
306
lib/guard/dsl.rb
@ -1,62 +1,160 @@
|
||||
module Guard
|
||||
|
||||
# The DSL class provides the methods that are used in each `Guardfile` to describe
|
||||
# the behaviour of Guard.
|
||||
#
|
||||
# The main keywords of the DSL are `guard` and `watch`. These are necessary to define
|
||||
# the used Guards and the file changes they are watching.
|
||||
#
|
||||
# You can optionally group the Guards with the `group` keyword and ignore certain paths
|
||||
# with the `ignore_paths` keyword.
|
||||
#
|
||||
# A more advanced DSL use is the `callback` keyword that allows you to execute arbitrary
|
||||
# code before or after any of the `start`, `stop`, `reload`, `run_all` and `run_on_change`
|
||||
# Guards' method. You can even insert more hooks inside these methods.
|
||||
# Please [checkout the Wiki page](https://github.com/guard/guard/wiki/Hooks-and-callbacks) for more details.
|
||||
#
|
||||
# The DSL will also evaluate normal Ruby code.
|
||||
#
|
||||
# There are two possible locations for the `Guardfile`:
|
||||
# - The `Guardfile` in the current directory where Guard has been started
|
||||
# - The `.Guardfile` in your home directory.
|
||||
#
|
||||
# In addition, if a user configuration `.guard.rb` in your home directory is found, it will
|
||||
# be appended to the current project `Guardfile`.
|
||||
#
|
||||
# @example A sample of a complex Guardfile
|
||||
#
|
||||
# group 'frontend' do
|
||||
# guard 'passenger', :ping => true do
|
||||
# watch('config/application.rb')
|
||||
# watch('config/environment.rb')
|
||||
# watch(%r{^config/environments/.+\.rb})
|
||||
# watch(%r{^config/initializers/.+\.rb})
|
||||
# end
|
||||
#
|
||||
# guard 'livereload', :apply_js_live => false do
|
||||
# watch(%r{^app/.+\.(erb|haml)})
|
||||
# watch(%r{^app/helpers/.+\.rb})
|
||||
# watch(%r{^public/javascripts/.+\.js})
|
||||
# watch(%r{^public/stylesheets/.+\.css})
|
||||
# watch(%r{^public/.+\.html})
|
||||
# watch(%r{^config/locales/.+\.yml})
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# group 'backend' do
|
||||
# # Reload the bundle when the Gemfile is modified
|
||||
# guard 'bundler' do
|
||||
# watch('Gemfile')
|
||||
# end
|
||||
#
|
||||
# # for big project you can fine tune the "timeout" before Spork's launch is considered failed
|
||||
# guard 'spork', :wait => 40 do
|
||||
# watch('Gemfile')
|
||||
# watch('config/application.rb')
|
||||
# watch('config/environment.rb')
|
||||
# watch(%r{^config/environments/.+\.rb})
|
||||
# watch(%r{^config/initializers/.+\.rb})
|
||||
# watch('spec/spec_helper.rb')
|
||||
# end
|
||||
#
|
||||
# # use RSpec 2, from the system's gem and with some direct RSpec CLI options
|
||||
# guard 'rspec', :version => 2, :cli => "--color --drb -f doc", :bundler => false do
|
||||
# watch('spec/spec_helper.rb') { "spec" }
|
||||
# watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
||||
# watch('config/routes.rb') { "spec/routing" }
|
||||
# watch(%r{^spec/support/(controllers|acceptance)_helpers\.rb}) { |m| "spec/#{m[1]}" }
|
||||
# watch(%r{^spec/.+_spec\.rb})
|
||||
#
|
||||
# watch(%r{^app/controllers/(.+)_(controller)\.rb}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
||||
#
|
||||
# watch(%r{^app/(.+)\.rb}) { |m| "spec/#{m[1]}_spec.rb" }
|
||||
# watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
||||
# end
|
||||
# end
|
||||
#
|
||||
class Dsl
|
||||
class << self
|
||||
|
||||
@@options = nil
|
||||
|
||||
# Evaluate the DSL methods in the `Guardfile`.
|
||||
#
|
||||
# @option options [Array<Symbol,String>] groups the groups to evaluate
|
||||
# @option options [String] guardfile the path to a valid Guardfile
|
||||
# @option options [String] guardfile_contents a string representing the content of a valid Guardfile
|
||||
# @raise [ArgumentError] when options are not a Hash
|
||||
#
|
||||
def evaluate_guardfile(options = {})
|
||||
options.is_a?(Hash) or raise ArgumentError.new("evaluate_guardfile not passed a Hash!")
|
||||
raise ArgumentError.new('No option hash passed to evaluate_guardfile!') unless options.is_a?(Hash)
|
||||
|
||||
@@options = options.dup
|
||||
|
||||
fetch_guardfile_contents
|
||||
instance_eval_guardfile(guardfile_contents_with_user_config)
|
||||
|
||||
UI.error "No guards found in Guardfile, please add at least one." if !::Guard.guards.nil? && ::Guard.guards.empty?
|
||||
UI.error 'No guards found in Guardfile, please add at least one.' if !::Guard.guards.nil? && ::Guard.guards.empty?
|
||||
end
|
||||
|
||||
# Re-evaluate the `Guardfile` to update the current Guard configuration.
|
||||
#
|
||||
def reevaluate_guardfile
|
||||
::Guard.guards.clear
|
||||
::Guard.groups.clear
|
||||
@@options.delete(:guardfile_contents)
|
||||
Dsl.evaluate_guardfile(@@options)
|
||||
msg = "Guardfile has been re-evaluated."
|
||||
msg = 'Guardfile has been re-evaluated.'
|
||||
UI.info(msg)
|
||||
Notifier.notify(msg)
|
||||
end
|
||||
|
||||
# Evaluate the content of the `Guardfile`.
|
||||
#
|
||||
# @param [String] contents the content to evaluate.
|
||||
#
|
||||
def instance_eval_guardfile(contents)
|
||||
begin
|
||||
new.instance_eval(contents, @@options[:guardfile_path], 1)
|
||||
rescue
|
||||
UI.error "Invalid Guardfile, original error is:\n#{$!}"
|
||||
exit 1
|
||||
end
|
||||
new.instance_eval(contents, @@options[:guardfile_path], 1)
|
||||
rescue
|
||||
UI.error "Invalid Guardfile, original error is:\n#{ $! }"
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Test if the current `Guardfile` contains a specific Guard.
|
||||
#
|
||||
# @param [String] guard_name the name of the Guard
|
||||
# @return [Boolean] whether the Guard has been declared
|
||||
#
|
||||
def guardfile_include?(guard_name)
|
||||
guardfile_contents.match(/^guard\s*\(?\s*['":]#{guard_name}['"]?/)
|
||||
guardfile_contents.match(/^guard\s*\(?\s*['":]#{ guard_name }['"]?/)
|
||||
end
|
||||
|
||||
# Read the current `Guardfile` content.
|
||||
#
|
||||
# @param [String] the path to the Guardfile
|
||||
#
|
||||
def read_guardfile(guardfile_path)
|
||||
begin
|
||||
@@options[:guardfile_path] = guardfile_path
|
||||
@@options[:guardfile_contents] = File.read(guardfile_path)
|
||||
rescue
|
||||
UI.error("Error reading file #{guardfile_path}")
|
||||
exit 1
|
||||
end
|
||||
@@options[:guardfile_path] = guardfile_path
|
||||
@@options[:guardfile_contents] = File.read(guardfile_path)
|
||||
rescue
|
||||
UI.error("Error reading file #{ guardfile_path }")
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Get the content to evaluate and stores it into
|
||||
# the options as `:guardfile_contents`.
|
||||
#
|
||||
def fetch_guardfile_contents
|
||||
# TODO: do we need .rc file interaction?
|
||||
if @@options[:guardfile_contents]
|
||||
UI.info "Using inline Guardfile."
|
||||
UI.info 'Using inline Guardfile.'
|
||||
@@options[:guardfile_path] = 'Inline Guardfile'
|
||||
|
||||
elsif @@options[:guardfile]
|
||||
if File.exist?(@@options[:guardfile])
|
||||
read_guardfile(@@options[:guardfile])
|
||||
UI.info "Using Guardfile at #{@@options[:guardfile]}."
|
||||
UI.info "Using Guardfile at #{ @@options[:guardfile] }."
|
||||
else
|
||||
UI.error "No Guardfile exists at #{@@options[:guardfile]}."
|
||||
UI.error "No Guardfile exists at #{ @@options[:guardfile] }."
|
||||
exit 1
|
||||
end
|
||||
|
||||
@ -64,79 +162,209 @@ module Guard
|
||||
if File.exist?(guardfile_default_path)
|
||||
read_guardfile(guardfile_default_path)
|
||||
else
|
||||
UI.error "No Guardfile found, please create one with `guard init`."
|
||||
UI.error 'No Guardfile found, please create one with `guard init`.'
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
unless guardfile_contents_usable?
|
||||
UI.error "The command file(#{@@options[:guardfile]}) seems to be empty."
|
||||
UI.error "The command file(#{ @@options[:guardfile] }) seems to be empty."
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
# Get the content of the `Guardfile`.
|
||||
#
|
||||
# @return [String] the Guardfile content
|
||||
#
|
||||
def guardfile_contents
|
||||
@@options ? @@options[:guardfile_contents] : ""
|
||||
@@options ? @@options[:guardfile_contents] : ''
|
||||
end
|
||||
|
||||
# Get the content of the `Guardfile` and the global
|
||||
# user configuration file.
|
||||
#
|
||||
# @see #user_config_path
|
||||
#
|
||||
# @return [String] the Guardfile content
|
||||
#
|
||||
def guardfile_contents_with_user_config
|
||||
config = File.read(user_config_path) if File.exist?(user_config_path)
|
||||
[guardfile_contents, config].join("\n")
|
||||
end
|
||||
|
||||
# Get the file path to the project `Guardfile`.
|
||||
#
|
||||
# @return [String] the path to the Guardfile
|
||||
#
|
||||
def guardfile_path
|
||||
@@options ? @@options[:guardfile_path] : ""
|
||||
@@options ? @@options[:guardfile_path] : ''
|
||||
end
|
||||
|
||||
# Tests if the current `Guardfile` content is usable.
|
||||
#
|
||||
# @return [Boolean] if the Guardfile is usable
|
||||
#
|
||||
def guardfile_contents_usable?
|
||||
guardfile_contents && guardfile_contents.size >= 'guard :a'.size # smallest guard-definition
|
||||
guardfile_contents && guardfile_contents.size >= 'guard :a'.size # Smallest Guard definition
|
||||
end
|
||||
|
||||
# Gets the default path of the `Guardfile`. This returns the `Guardfile`
|
||||
# from the current directory when existing, or the global `.Guardfile`
|
||||
# at the home directory.
|
||||
#
|
||||
# @return [String] the path to the Guardfile
|
||||
#
|
||||
def guardfile_default_path
|
||||
File.exist?(local_guardfile_path) ? local_guardfile_path : home_guardfile_path
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
# The path to the `Guardfile` that is located at
|
||||
# the directory, where Guard has been started from.
|
||||
#
|
||||
# @param [String] the path to the local Guardfile
|
||||
#
|
||||
def local_guardfile_path
|
||||
File.join(Dir.pwd, "Guardfile")
|
||||
File.join(Dir.pwd, 'Guardfile')
|
||||
end
|
||||
|
||||
# The path to the `.Guardfile` that is located at
|
||||
# the users home directory.
|
||||
#
|
||||
# @param [String] the path to ~/.Guardfile
|
||||
#
|
||||
def home_guardfile_path
|
||||
File.expand_path(File.join("~", ".Guardfile"))
|
||||
File.expand_path(File.join('~', '.Guardfile'))
|
||||
end
|
||||
|
||||
# The path to the user configuration `.guard.rb`
|
||||
# that is located at the users home directory.
|
||||
#
|
||||
# @param [String] the path to ~/.guard.rb
|
||||
#
|
||||
def user_config_path
|
||||
File.expand_path(File.join("~", ".guard.rb"))
|
||||
File.expand_path(File.join('~', '.guard.rb'))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def group(name, &guard_definition)
|
||||
# Declares a group of guards to be run with `guard start --group group_name`.
|
||||
#
|
||||
# @example Declare two groups of Guards
|
||||
#
|
||||
# group 'backend' do
|
||||
# guard 'spork'
|
||||
# guard 'rspec'
|
||||
# end
|
||||
#
|
||||
# group 'frontend' do
|
||||
# guard 'passenger'
|
||||
# guard 'livereload'
|
||||
# end
|
||||
#
|
||||
# @param [Symbol, String] name the group's name called from the CLI
|
||||
# @param [Hash] options the options accepted by the group
|
||||
# @yield a block where you can declare several guards
|
||||
#
|
||||
# @see Guard.add_group
|
||||
# @see Dsl#guard
|
||||
# @see Guard::DslDescriber
|
||||
#
|
||||
def group(name, options = {})
|
||||
@groups = @@options[:group] || []
|
||||
name = name.to_sym
|
||||
name = name.to_sym
|
||||
|
||||
if guard_definition && (@groups.empty? || @groups.map(&:to_sym).include?(name))
|
||||
if block_given? && (@groups.empty? || @groups.map(&:to_sym).include?(name))
|
||||
::Guard.add_group(name.to_s.downcase, options)
|
||||
@current_group = name
|
||||
guard_definition.call
|
||||
|
||||
yield if block_given?
|
||||
|
||||
@current_group = nil
|
||||
end
|
||||
end
|
||||
|
||||
def guard(name, options = {}, &watch_definition)
|
||||
@watchers = []
|
||||
watch_definition.call if watch_definition
|
||||
# Declare a guard to be used when running `guard start`.
|
||||
#
|
||||
# The name parameter is usually the name of the gem without
|
||||
# the 'guard-' prefix.
|
||||
#
|
||||
# The available options are different for each Guard implementation.
|
||||
#
|
||||
# @example Declare a Guard
|
||||
#
|
||||
# guard 'rspec' do
|
||||
# end
|
||||
#
|
||||
# @param [String] name the Guard name
|
||||
# @param [Hash] options the options accepted by the Guard
|
||||
# @yield a block where you can declare several watch patterns and actions
|
||||
#
|
||||
# @see Guard.add_guard
|
||||
# @see Dsl#group
|
||||
# @see Dsl#watch
|
||||
# @see Guard::DslDescriber
|
||||
#
|
||||
def guard(name, options = {})
|
||||
@watchers = []
|
||||
@callbacks = []
|
||||
|
||||
yield if block_given?
|
||||
|
||||
options.update(:group => (@current_group || :default))
|
||||
::Guard.add_guard(name.to_s.downcase.to_sym, @watchers, options)
|
||||
::Guard.add_guard(name.to_s.downcase, @watchers, @callbacks, options)
|
||||
end
|
||||
|
||||
# Define a pattern to be watched in order to run actions on file modification.
|
||||
#
|
||||
# @example Declare watchers for a Guard
|
||||
#
|
||||
# guard 'rspec' do
|
||||
# watch('spec/spec_helper.rb')
|
||||
# watch(%r{^.+_spec.rb})
|
||||
# watch(%r{^app/controllers/(.+).rb}) { |m| 'spec/acceptance/#{m[1]}s_spec.rb' }
|
||||
# end
|
||||
#
|
||||
# @param [String, Regexp] pattern the pattern to be watched by the guard
|
||||
# @yield a block to be run when the pattern is matched
|
||||
# @yieldparam [MatchData] m matches of the pattern
|
||||
# @yieldreturn a directory, a filename, an array of directories / filenames, or nothing (can be an arbitrary command)
|
||||
#
|
||||
# @see Guard::Watcher
|
||||
# @see Dsl#guard
|
||||
#
|
||||
def watch(pattern, &action)
|
||||
@watchers << ::Guard::Watcher.new(pattern, action)
|
||||
end
|
||||
|
||||
# Define a callback to execute arbitrary code before or after any of
|
||||
# the `start`, `stop`, `reload`, `run_all` and `run_on_change` guards' method.
|
||||
#
|
||||
# @param [Array] args the callback arguments
|
||||
# @yield a block with listeners
|
||||
#
|
||||
# @see Guard::Hook
|
||||
#
|
||||
def callback(*args, &listener)
|
||||
listener, events = args.size > 1 ? args : [listener, args[0]]
|
||||
@callbacks << { :events => events, :listener => listener }
|
||||
end
|
||||
|
||||
# Ignore certain paths globally.
|
||||
#
|
||||
# @example Ignore some paths
|
||||
# ignore_paths ".git", ".svn"
|
||||
#
|
||||
# @param [Array] paths the list of paths to ignore
|
||||
#
|
||||
# @see Guard::Listener
|
||||
#
|
||||
def ignore_paths(*paths)
|
||||
UI.info "Ignoring paths: #{paths.join(', ')}"
|
||||
UI.info "Ignoring paths: #{ paths.join(', ') }"
|
||||
::Guard.listener.ignore_paths.push(*paths)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -1,29 +1,150 @@
|
||||
require 'guard/dsl'
|
||||
|
||||
module Guard
|
||||
|
||||
autoload :UI, 'guard/ui'
|
||||
|
||||
# The DslDescriber overrides methods to create an internal structure
|
||||
# of the Guardfile that is used in some inspection utility methods
|
||||
# like the CLI commands `show` and `list`.
|
||||
#
|
||||
# @see Guard::Dsl
|
||||
# @see Guard::CLI
|
||||
#
|
||||
class DslDescriber < Dsl
|
||||
@@guardfile_structure = [ { :guards => [] } ]
|
||||
|
||||
class << self
|
||||
|
||||
# Evaluate the DSL methods in the `Guardfile`.
|
||||
#
|
||||
# @option options [Array<Symbol,String>] groups the groups to evaluate
|
||||
# @option options [String] guardfile the path to a valid Guardfile
|
||||
# @option options [String] guardfile_contents a string representing the content of a valid Guardfile
|
||||
# @raise [ArgumentError] when options are not a Hash
|
||||
#
|
||||
def evaluate_guardfile(options = {})
|
||||
@@guardfile_structure = [{ :guards => [] }]
|
||||
super options
|
||||
end
|
||||
|
||||
# List the Guards that are available for use in your system and marks
|
||||
# those that are currently used in your `Guardfile`.
|
||||
#
|
||||
# @example Guard list output
|
||||
#
|
||||
# Available guards:
|
||||
# bundler *
|
||||
# livereload
|
||||
# ronn
|
||||
# rspec *
|
||||
# spork
|
||||
#
|
||||
# See also https://github.com/guard/guard/wiki/List-of-available-Guards
|
||||
# * denotes ones already in your Guardfile
|
||||
#
|
||||
# @param [Hash] options the Guard options
|
||||
#
|
||||
def list(options)
|
||||
evaluate_guardfile(options)
|
||||
|
||||
installed = guardfile_structure.inject([]) do |installed, group|
|
||||
group[:guards].each { |guard| installed << guard[:name] } if group[:guards]
|
||||
installed
|
||||
end
|
||||
|
||||
UI.info 'Available guards:'
|
||||
|
||||
::Guard.guard_gem_names.sort.uniq.each do |name|
|
||||
UI.info " #{ name }#{ installed.include?(name) ? '*' : '' }"
|
||||
end
|
||||
|
||||
UI.info ''
|
||||
UI.info 'See also https://github.com/guard/guard/wiki/List-of-available-Guards'
|
||||
UI.info '* denotes ones already in your Guardfile'
|
||||
end
|
||||
|
||||
# Shows all Guards and their options that are defined in
|
||||
# the `Guardfile`.
|
||||
#
|
||||
# @example guard show output
|
||||
#
|
||||
# (global):
|
||||
# bundler
|
||||
# coffeescript: input => "app/assets/javascripts", noop => true
|
||||
# jasmine
|
||||
# rspec: cli => "--fail-fast --format Fuubar
|
||||
#
|
||||
# @param [Hash] options the Guard options
|
||||
#
|
||||
def show(options)
|
||||
evaluate_guardfile(options)
|
||||
|
||||
guardfile_structure.each do |group|
|
||||
unless group[:guards].empty?
|
||||
if group[:group]
|
||||
UI.info "Group #{ group[:group] }:"
|
||||
else
|
||||
UI.info '(global):'
|
||||
end
|
||||
|
||||
group[:guards].each do |guard|
|
||||
line = " #{ guard[:name] }"
|
||||
|
||||
unless guard[:options].empty?
|
||||
line += ": #{ guard[:options].sort.collect { |k, v| "#{ k } => #{ v.inspect }" }.join(', ') }"
|
||||
end
|
||||
|
||||
UI.info line
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
UI.info ''
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Get the Guardfile structure.
|
||||
#
|
||||
# @return [Array<Hash>] the structure
|
||||
#
|
||||
def guardfile_structure
|
||||
@@guardfile_structure
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
def group(name, &guard_definition)
|
||||
@@guardfile_structure << { :group => name.to_sym, :guards => [] }
|
||||
|
||||
# Declares a group of guards.
|
||||
#
|
||||
# @param [String] name the group's name called from the CLI
|
||||
# @yield a block where you can declare several guards
|
||||
#
|
||||
# @see Guard::Dsl#group
|
||||
#
|
||||
def group(name)
|
||||
@@guardfile_structure << { :group => name.to_sym, :guards => [] }
|
||||
@group = true
|
||||
guard_definition.call
|
||||
|
||||
yield if block_given?
|
||||
|
||||
@group = false
|
||||
end
|
||||
|
||||
def guard(name, options = {}, &watch_definition)
|
||||
# Declares a Guard.
|
||||
#
|
||||
# @param [String] name the Guard name
|
||||
# @param [Hash] options the options accepted by the Guard
|
||||
# @yield a block where you can declare several watch patterns and actions
|
||||
#
|
||||
# @see Guard::Dsl#guard
|
||||
#
|
||||
def guard(name, options = { })
|
||||
node = (@group ? @@guardfile_structure.last : @@guardfile_structure.first)
|
||||
|
||||
node[:guards] << { :name => name, :options => options }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
37
lib/guard/group.rb
Normal file
37
lib/guard/group.rb
Normal file
@ -0,0 +1,37 @@
|
||||
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
|
@ -1,57 +1,129 @@
|
||||
module Guard
|
||||
|
||||
# Main class that every Guard implementation must subclass.
|
||||
#
|
||||
# Guard will trigger the `start`, `stop`, `reload`, `run_all`, `run_on_change` and
|
||||
# `run_on_deletion` task methods depending on user interaction and file modification.
|
||||
#
|
||||
# In each of these Guard task methods you have to implement some work when you want to
|
||||
# support this kind of task. The return value of each Guard task method is not evaluated
|
||||
# by Guard, but I'll be passed to the "_end" hook for further evaluation. You can
|
||||
# throw `:task_has_failed` to indicate that your Guard method was not successful,
|
||||
# and successive guard tasks will be aborted when the group has set the `:halt_on_fail`
|
||||
# option.
|
||||
#
|
||||
# @see Guard::Hook
|
||||
# @see Guard::Group
|
||||
#
|
||||
# @example Throw :task_has_failed
|
||||
#
|
||||
# def run_all
|
||||
# if !runner.run(['all'])
|
||||
# throw :task_has_failed
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Each Guard should provide a template Guardfile located within the Gem
|
||||
# at `lib/guard/guard-name/templates/Guardfile`.
|
||||
#
|
||||
# By default all watchers for a Guard are returning strings of paths to the
|
||||
# Guard, but if your Guard want to allow any return value from a watcher,
|
||||
# you can set the `any_return` option to true.
|
||||
#
|
||||
# If one of those methods raise an exception other than `:task_has_failed`,
|
||||
# the Guard::GuardName instance will be removed from the active guards.
|
||||
#
|
||||
class Guard
|
||||
include Hook
|
||||
|
||||
attr_accessor :watchers, :options, :group
|
||||
|
||||
# Initialize a Guard.
|
||||
#
|
||||
# @param [Array<Guard::Watcher>] watchers the Guard file watchers
|
||||
# @param [Hash] options the custom Guard options
|
||||
# @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 = {})
|
||||
@group = options.delete(:group) || :default
|
||||
@group = options[:group] ? options.delete(:group).to_sym : :default
|
||||
@watchers, @options = watchers, options
|
||||
end
|
||||
|
||||
# Guardfile template needed inside guard gem
|
||||
# Initialize the Guard. This will copy the Guardfile template inside the Guard gem.
|
||||
# The template Guardfile must be located within the Gem at `lib/guard/guard-name/templates/Guardfile`.
|
||||
#
|
||||
# @param [String] name the name of the Guard
|
||||
#
|
||||
def self.init(name)
|
||||
if ::Guard::Dsl.guardfile_include?(name)
|
||||
::Guard::UI.info "Guardfile already includes #{name} guard"
|
||||
::Guard::UI.info "Guardfile already includes #{ name } guard"
|
||||
else
|
||||
content = File.read('Guardfile')
|
||||
guard = File.read("#{::Guard.locate_guard(name)}/lib/guard/#{name}/templates/Guardfile")
|
||||
guard = File.read("#{ ::Guard.locate_guard(name) }/lib/guard/#{ name }/templates/Guardfile")
|
||||
|
||||
File.open('Guardfile', 'wb') do |f|
|
||||
f.puts(content)
|
||||
f.puts("")
|
||||
f.puts(guard)
|
||||
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
|
||||
|
||||
# ================
|
||||
# = Guard method =
|
||||
# ================
|
||||
|
||||
# Call once when guard starts
|
||||
# Please override initialize method to init stuff
|
||||
# Call once when Guard starts. Please override initialize method to init stuff.
|
||||
#
|
||||
# @raise [:task_has_failed] when start has failed
|
||||
# @return [Object] the task result
|
||||
#
|
||||
def start
|
||||
true
|
||||
end
|
||||
|
||||
# Call once when guard quit
|
||||
# Called when `stop|quit|exit|s|q|e + enter` is pressed (when Guard quits).
|
||||
#
|
||||
# @raise [:task_has_failed] when stop has failed
|
||||
# @return [Object] the task result
|
||||
#
|
||||
def stop
|
||||
true
|
||||
end
|
||||
|
||||
# Should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/...
|
||||
# Called when `reload|r|z + enter` is pressed.
|
||||
# This method should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/...
|
||||
#
|
||||
# @raise [:task_has_failed] when reload has failed
|
||||
# @return [Object] the task result
|
||||
#
|
||||
def reload
|
||||
true
|
||||
end
|
||||
|
||||
# Should be principally used for long action like running all specs/tests/...
|
||||
# Called when just `enter` is pressed
|
||||
# This method should be principally used for long action like running all specs/tests/...
|
||||
#
|
||||
# @raise [:task_has_failed] when run_all has failed
|
||||
# @return [Object] the task result
|
||||
#
|
||||
def run_all
|
||||
true
|
||||
end
|
||||
|
||||
# Called on file(s) modifications that the Guard watches.
|
||||
#
|
||||
# @param [Array<String>] paths the changes files or paths
|
||||
# @raise [:task_has_failed] when run_on_change has failed
|
||||
# @return [Object] the task result
|
||||
#
|
||||
def run_on_change(paths)
|
||||
true
|
||||
end
|
||||
|
||||
# Called on file(s) deletions that the Guard watches.
|
||||
#
|
||||
# @param [Array<String>] paths the deleted files or paths
|
||||
# @raise [:task_has_failed] when run_on_change has failed
|
||||
# @return [Object] the task result
|
||||
#
|
||||
def run_on_deletion(paths)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
118
lib/guard/hook.rb
Normal file
118
lib/guard/hook.rb
Normal file
@ -0,0 +1,118 @@
|
||||
module Guard
|
||||
|
||||
# Guard has a hook mechanism that allows you to insert callbacks for individual Guards.
|
||||
# By default, each of the Guard instance methods has a "_begin" and an "_end" hook.
|
||||
# For example, the Guard::Guard#start method has a :start_begin hook that is runs immediately
|
||||
# before Guard::Guard#start, and a :start_end hook that runs immediately after Guard::Guard#start.
|
||||
#
|
||||
# Read more about [hooks and callbacks on the wiki](https://github.com/guard/guard/wiki/Hooks-and-callbacks).
|
||||
#
|
||||
module Hook
|
||||
|
||||
# The Hook module gets included.
|
||||
#
|
||||
# @param [Class] base the class that includes the module
|
||||
#
|
||||
def self.included(base)
|
||||
base.send :include, InstanceMethods
|
||||
end
|
||||
|
||||
# Instance methods that gets included in the base class.
|
||||
#
|
||||
module InstanceMethods
|
||||
|
||||
# When event is a Symbol, {#hook} will generate a hook name
|
||||
# by concatenating the method name from where {#hook} is called
|
||||
# with the given Symbol.
|
||||
#
|
||||
# @example Add a hook with a Symbol
|
||||
#
|
||||
# def run_all
|
||||
# hook :foo
|
||||
# end
|
||||
#
|
||||
# Here, when {Guard::Guard#run_all} is called, {#hook} will notify callbacks
|
||||
# registered for the "run_all_foo" event.
|
||||
#
|
||||
# When event is a String, {#hook} will directly turn the String
|
||||
# into a Symbol.
|
||||
#
|
||||
# @example Add a hook with a String
|
||||
#
|
||||
# def run_all
|
||||
# hook "foo_bar"
|
||||
# end
|
||||
#
|
||||
# When {Guard::Guard#run_all} is called, {#hook} will notify callbacks
|
||||
# registered for the "foo_bar" event.
|
||||
#
|
||||
# @param [Symbol, String] event the name of the Guard event
|
||||
# @param [Array] args the parameters are passed as is to the callbacks registered for the given event.
|
||||
#
|
||||
def hook(event, *args)
|
||||
hook_name = if event.is_a? Symbol
|
||||
calling_method = caller[0][/`([^']*)'/, 1]
|
||||
"#{ calling_method }_#{ event }"
|
||||
else
|
||||
event
|
||||
end.to_sym
|
||||
|
||||
UI.debug "Hook :#{ hook_name } executed for #{ self.class }"
|
||||
|
||||
Hook.notify(self.class, hook_name, *args)
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
|
||||
# Get all callbacks.
|
||||
#
|
||||
def callbacks
|
||||
@callbacks ||= Hash.new { |hash, key| hash[key] = [] }
|
||||
end
|
||||
|
||||
# Add a callback.
|
||||
#
|
||||
# @param [Block] listener the listener to notify
|
||||
# @param [Guard::Guard] guard_class the Guard class to add the callback
|
||||
# @param [Array<Symbol>] events the events to register
|
||||
#
|
||||
def add_callback(listener, guard_class, events)
|
||||
_events = events.is_a?(Array) ? events : [events]
|
||||
_events.each do |event|
|
||||
callbacks[[guard_class, event]] << listener
|
||||
end
|
||||
end
|
||||
|
||||
# Checks if a callback has been registered.
|
||||
#
|
||||
# @param [Block] listener the listener to notify
|
||||
# @param [Guard::Guard] guard_class the Guard class to add the callback
|
||||
# @param [Symbol] event the event to look for
|
||||
#
|
||||
def has_callback?(listener, guard_class, event)
|
||||
callbacks[[guard_class, event]].include?(listener)
|
||||
end
|
||||
|
||||
# Notify a callback.
|
||||
#
|
||||
# @param [Guard::Guard] guard_class the Guard class to add the callback
|
||||
# @param [Symbol] event the event to trigger
|
||||
# @param [Array] args the arguments for the listener
|
||||
#
|
||||
def notify(guard_class, event, *args)
|
||||
callbacks[[guard_class, event]].each do |listener|
|
||||
listener.call(guard_class, event, *args)
|
||||
end
|
||||
end
|
||||
|
||||
# Reset all callbacks.
|
||||
#
|
||||
def reset_callbacks!
|
||||
@callbacks = nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
@ -1,54 +1,44 @@
|
||||
module Guard
|
||||
module Interactor
|
||||
extend self
|
||||
|
||||
def run_all
|
||||
::Guard.run do
|
||||
::Guard.guards.each { |guard| ::Guard.supervised_task(guard, :run_all) }
|
||||
# The interactor reads user input and triggers
|
||||
# specific action upon them unless its locked.
|
||||
#
|
||||
# Currently the following actions are implemented:
|
||||
#
|
||||
# - stop, quit, exit, s, q, e => Exit Guard
|
||||
# - reload, r, z => Reload Guard
|
||||
# - pause, p => Pause Guard
|
||||
# - Everything else => Run all
|
||||
#
|
||||
class Interactor
|
||||
# Start the interactor in its own thread.
|
||||
#
|
||||
def start
|
||||
return if ENV["GUARD_ENV"] == 'test'
|
||||
|
||||
if !@thread || @thread.stop?
|
||||
@thread = Thread.new do
|
||||
while entry = $stdin.gets.chomp
|
||||
case entry
|
||||
when 'stop', 'quit', 'exit', 's', 'q', 'e'
|
||||
::Guard.stop
|
||||
when 'reload', 'r', 'z'
|
||||
::Guard::Dsl.reevaluate_guardfile
|
||||
::Guard.reload
|
||||
when 'pause', 'p'
|
||||
::Guard.pause
|
||||
else
|
||||
::Guard.run_all
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def stop
|
||||
UI.info "Bye bye...", :reset => true
|
||||
::Guard.listener.stop
|
||||
::Guard.guards.each { |guard| ::Guard.supervised_task(guard, :stop) }
|
||||
abort
|
||||
end
|
||||
|
||||
def reload
|
||||
::Guard.run do
|
||||
::Guard.guards.each { |guard| ::Guard.supervised_task(guard, :reload) }
|
||||
def stop_if_not_current
|
||||
unless Thread.current == @thread
|
||||
@thread.kill
|
||||
end
|
||||
end
|
||||
|
||||
def self.init_signal_traps
|
||||
# Run all (Ctrl-\)
|
||||
if Signal.list.has_key?('QUIT')
|
||||
Signal.trap('QUIT') do
|
||||
run_all
|
||||
end
|
||||
else
|
||||
UI.info "Your system doesn't support QUIT signal, so Ctrl-\\ (Run all) won't work"
|
||||
end
|
||||
|
||||
# Stop (Ctrl-C)
|
||||
if Signal.list.has_key?('INT')
|
||||
Signal.trap('INT') do
|
||||
stop
|
||||
end
|
||||
else
|
||||
UI.info "Your system doesn't support INT signal, so Ctrl-C (Stop) won't work"
|
||||
end
|
||||
|
||||
# Reload (Ctrl-Z)
|
||||
if Signal.list.has_key?('TSTP')
|
||||
Signal.trap('TSTP') do
|
||||
reload
|
||||
end
|
||||
else
|
||||
UI.info "Your system doesn't support TSTP signal, so Ctrl-Z (Reload) won't work"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -8,97 +8,229 @@ module Guard
|
||||
autoload :Windows, 'guard/listeners/windows'
|
||||
autoload :Polling, 'guard/listeners/polling'
|
||||
|
||||
# The Listener is the base class for all listener
|
||||
# implementations.
|
||||
#
|
||||
# @abstract
|
||||
#
|
||||
class Listener
|
||||
DefaultIgnorePaths = %w[. .. .bundle .git log tmp vendor]
|
||||
|
||||
|
||||
# Default paths that gets ignored by the listener
|
||||
DEFAULT_IGNORE_PATHS = %w[. .. .bundle .git log tmp vendor]
|
||||
|
||||
attr_accessor :changed_files
|
||||
attr_reader :directory, :ignore_paths
|
||||
|
||||
def self.select_and_init(*a)
|
||||
def paused?
|
||||
@paused
|
||||
end
|
||||
|
||||
# Select the appropriate listener implementation for the
|
||||
# current OS and initializes it.
|
||||
#
|
||||
# @param [Array] args the arguments for the listener
|
||||
# @return [Guard::Listener] the chosen listener
|
||||
#
|
||||
def self.select_and_init(*args)
|
||||
if mac? && Darwin.usable?
|
||||
Darwin.new(*a)
|
||||
Darwin.new(*args)
|
||||
elsif linux? && Linux.usable?
|
||||
Linux.new(*a)
|
||||
Linux.new(*args)
|
||||
elsif windows? && Windows.usable?
|
||||
Windows.new(*a)
|
||||
Windows.new(*args)
|
||||
else
|
||||
UI.info "Using polling (Please help us to support your system better than that.)"
|
||||
Polling.new(*a)
|
||||
UI.info 'Using polling (Please help us to support your system better than that).'
|
||||
Polling.new(*args)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(directory=Dir.pwd, options={})
|
||||
@directory = directory.to_s
|
||||
@sha1_checksums_hash = {}
|
||||
@relativize_paths = options.fetch(:relativize_paths, true)
|
||||
@ignore_paths = DefaultIgnorePaths
|
||||
@ignore_paths |= options[:ignore_paths] if options[:ignore_paths]
|
||||
# Initialize the listener.
|
||||
#
|
||||
# @param [String] directory the root directory to listen to
|
||||
# @option options [Boolean] relativize_paths use only relative paths
|
||||
# @option options [Array<String>] ignore_paths the paths to ignore by the listener
|
||||
#
|
||||
def initialize(directory = Dir.pwd, options = {})
|
||||
@directory = directory.to_s
|
||||
@sha1_checksums_hash = {}
|
||||
@file_timestamp_hash = {}
|
||||
@relativize_paths = options.fetch(:relativize_paths, true)
|
||||
@changed_files = []
|
||||
@paused = false
|
||||
@ignore_paths = DEFAULT_IGNORE_PATHS
|
||||
@ignore_paths |= options[:ignore_paths] if options[:ignore_paths]
|
||||
@watch_all_modifications = options.fetch(:watch_all_modifications, false)
|
||||
|
||||
update_last_event
|
||||
start_reactor
|
||||
end
|
||||
|
||||
# Start the listener thread.
|
||||
#
|
||||
def start_reactor
|
||||
return if ENV["GUARD_ENV"] == 'test'
|
||||
|
||||
Thread.new do
|
||||
loop do
|
||||
if @changed_files != [] && !@paused
|
||||
changed_files = @changed_files.dup
|
||||
clear_changed_files
|
||||
::Guard.run_on_change(changed_files)
|
||||
else
|
||||
sleep 0.1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Start watching the root directory.
|
||||
#
|
||||
def start
|
||||
watch(@directory)
|
||||
timestamp_files
|
||||
end
|
||||
|
||||
# Stop listening for events.
|
||||
#
|
||||
def stop
|
||||
end
|
||||
|
||||
# Pause the listener to ignore change events.
|
||||
#
|
||||
def pause
|
||||
@paused = true
|
||||
end
|
||||
|
||||
# Unpause the listener to listen again to change events.
|
||||
#
|
||||
def run
|
||||
@paused = false
|
||||
end
|
||||
|
||||
# Clear the list of changed files.
|
||||
#
|
||||
def clear_changed_files
|
||||
@changed_files.clear
|
||||
end
|
||||
|
||||
# Store a listener callback.
|
||||
#
|
||||
# @param [Block] callback the callback to store
|
||||
#
|
||||
def on_change(&callback)
|
||||
@callback = callback
|
||||
end
|
||||
|
||||
# Updates the timestamp of the last event.
|
||||
#
|
||||
def update_last_event
|
||||
@last_event = Time.now
|
||||
end
|
||||
|
||||
def modified_files(dirs, options={})
|
||||
files = potentially_modified_files(dirs, options).select { |path| file_modified?(path) }
|
||||
# Get the modified files.
|
||||
#
|
||||
# If the `:watch_all_modifications` option is true, then moved and
|
||||
# deleted files are also reported, but prefixed by an exclamation point.
|
||||
#
|
||||
# @example Deleted or moved file
|
||||
# !/home/user/dir/file.rb
|
||||
#
|
||||
# @param [Array<String>] dirs the watched directories
|
||||
# @param [Hash] options the listener options
|
||||
# @option options [Symbol] all whether to files in sub directories
|
||||
# @return [Array<String>] paths of files that have been modified
|
||||
#
|
||||
def modified_files(dirs, options = {})
|
||||
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
|
||||
files.concat(potentially_modified_files(dirs, options).select { |path| file_modified?(path, last_event) })
|
||||
|
||||
relativize_paths(files)
|
||||
end
|
||||
|
||||
def worker
|
||||
raise NotImplementedError, "should respond to #watch"
|
||||
end
|
||||
|
||||
# register a directory to watch. must be implemented by the subclasses
|
||||
# Register a directory to watch.
|
||||
# Must be implemented by the subclasses.
|
||||
#
|
||||
# @param [String] directory the directory to watch
|
||||
#
|
||||
def watch(directory)
|
||||
raise NotImplementedError, "do whatever you want here, given the directory as only argument"
|
||||
end
|
||||
|
||||
# Get all files that are in the watched directory.
|
||||
#
|
||||
# @return [Array<String>] the list of files
|
||||
#
|
||||
def all_files
|
||||
potentially_modified_files([@directory], :all => true)
|
||||
end
|
||||
|
||||
# scopes all given paths to the current #directory
|
||||
# Scopes all given paths to the current directory.
|
||||
#
|
||||
# @param [Array<String>] paths the paths to change
|
||||
# @return [Array<String>] all paths now relative to the current dir
|
||||
#
|
||||
def relativize_paths(paths)
|
||||
return paths unless relativize_paths?
|
||||
paths.map do |path|
|
||||
path.gsub(%r{^#{@directory}/}, '')
|
||||
path.gsub(%r{^(!)?#{ @directory }/},'\1')
|
||||
end
|
||||
end
|
||||
|
||||
# Use paths relative to the current directory.
|
||||
#
|
||||
# @return [Boolean] whether to use relative or absolute paths
|
||||
#
|
||||
def relativize_paths?
|
||||
!!@relativize_paths
|
||||
end
|
||||
|
||||
# return children of the passed dirs that are not in the ignore_paths list
|
||||
|
||||
# Populate initial timestamp file hash to watch for deleted or moved files.
|
||||
#
|
||||
def timestamp_files
|
||||
all_files.each {|path| set_file_timestamp_hash(path, file_timestamp(path)) } if @watch_all_modifications
|
||||
end
|
||||
|
||||
# Removes the ignored paths from the directory list.
|
||||
#
|
||||
# @param [Array<String>] dirs the directory to listen to
|
||||
# @param [Array<String>] ignore_paths the paths to ignore
|
||||
# @return children of the passed dirs that are not in the ignore_paths list
|
||||
#
|
||||
def exclude_ignored_paths(dirs, ignore_paths = self.ignore_paths)
|
||||
Dir.glob(dirs.map { |d| "#{d.sub(%r{/+$}, '')}/*" }, File::FNM_DOTMATCH).reject do |path|
|
||||
ignore_paths.include?(File.basename(path))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def potentially_modified_files(dirs, options={})
|
||||
# Gets a list of files that are in the modified directories.
|
||||
#
|
||||
# @param [Array<String>] dirs the list of directories
|
||||
# @param [Hash] options the find file option
|
||||
# @option options [Symbol] all whether to files in sub directories
|
||||
#
|
||||
def potentially_modified_files(dirs, options = {})
|
||||
paths = exclude_ignored_paths(dirs)
|
||||
|
||||
|
||||
if options[:all]
|
||||
paths.inject([]) do |array, path|
|
||||
if File.file?(path)
|
||||
array << path
|
||||
else
|
||||
array += Dir.glob("#{path}/**/*", File::FNM_DOTMATCH).select { |p| File.file?(p) }
|
||||
array += Dir.glob("#{ path }/**/*", File::FNM_DOTMATCH).select { |p| File.file?(p) }
|
||||
end
|
||||
array
|
||||
end
|
||||
@ -107,19 +239,44 @@ module Guard
|
||||
end
|
||||
end
|
||||
|
||||
# Depending on the filesystem, mtime is probably only precise to the second, so round
|
||||
# Test if the file content has changed.
|
||||
#
|
||||
# Depending on the filesystem, mtime/ctime is probably only precise to the second, so round
|
||||
# both values down to the second for the comparison.
|
||||
def file_modified?(path)
|
||||
if File.mtime(path).to_i == @last_event.to_i
|
||||
#
|
||||
# ctime is used only on == comparison to always catches Rails 3.1 Assets pipelined on Mac OSX
|
||||
#
|
||||
# @param [String] path the file path
|
||||
# @param [Time] last_event the time of the last event
|
||||
# @return [Boolean] Whether the file content has changed or not.
|
||||
#
|
||||
def file_modified?(path, last_event)
|
||||
ctime = File.ctime(path).to_i
|
||||
mtime = File.mtime(path).to_i
|
||||
if [mtime, ctime].max == last_event.to_i
|
||||
file_content_modified?(path, sha1_checksum(path))
|
||||
elsif File.mtime(path).to_i > @last_event.to_i
|
||||
elsif mtime > last_event.to_i
|
||||
set_sha1_checksums_hash(path, sha1_checksum(path))
|
||||
true
|
||||
elsif @watch_all_modifications
|
||||
ts = file_timestamp(path)
|
||||
if ts != @file_timestamp_hash[path]
|
||||
set_file_timestamp_hash(path, ts)
|
||||
true
|
||||
end
|
||||
else
|
||||
false
|
||||
end
|
||||
rescue
|
||||
false
|
||||
end
|
||||
|
||||
# Tests if the file content has been modified by
|
||||
# comparing the SHA1 checksum.
|
||||
#
|
||||
# @param [String] path the file path
|
||||
# @param [String] sha1_checksum the checksum of the file
|
||||
#
|
||||
def file_content_modified?(path, sha1_checksum)
|
||||
if @sha1_checksums_hash[path] != sha1_checksum
|
||||
set_sha1_checksums_hash(path, sha1_checksum)
|
||||
@ -129,22 +286,62 @@ module Guard
|
||||
end
|
||||
end
|
||||
|
||||
# Set save a files current timestamp
|
||||
#
|
||||
# @param [String] path the file path
|
||||
# @param [Int] file_timestamp the files modified timestamp
|
||||
#
|
||||
def set_file_timestamp_hash(path, file_timestamp)
|
||||
@file_timestamp_hash[path] = file_timestamp
|
||||
end
|
||||
|
||||
# Set the current checksum of a file.
|
||||
#
|
||||
# @param [String] path the file path
|
||||
# @param [String] sha1_checksum the checksum of the file
|
||||
#
|
||||
def set_sha1_checksums_hash(path, sha1_checksum)
|
||||
@sha1_checksums_hash[path] = sha1_checksum
|
||||
end
|
||||
|
||||
# Gets a files modified timestamp
|
||||
#
|
||||
# @path [String] path the file path
|
||||
# @return [Int] file modified timestamp
|
||||
#
|
||||
def file_timestamp(path)
|
||||
File.mtime(path).to_i
|
||||
end
|
||||
|
||||
# Calculates the SHA1 checksum of a file.
|
||||
#
|
||||
# @param [String] path the path to the file
|
||||
# @return [String] the SHA1 checksum
|
||||
#
|
||||
def sha1_checksum(path)
|
||||
Digest::SHA1.file(path).to_s
|
||||
end
|
||||
|
||||
# Test if the OS is Mac OS X.
|
||||
#
|
||||
# @return [Boolean] Whether the OS is Mac OS X
|
||||
#
|
||||
def self.mac?
|
||||
RbConfig::CONFIG['target_os'] =~ /darwin/i
|
||||
end
|
||||
|
||||
# Test if the OS is Linux.
|
||||
#
|
||||
# @return [Boolean] Whether the OS is Linux
|
||||
#
|
||||
def self.linux?
|
||||
RbConfig::CONFIG['target_os'] =~ /linux/i
|
||||
end
|
||||
|
||||
# Test if the OS is Windows.
|
||||
#
|
||||
# @return [Boolean] Whether the OS is Windows
|
||||
#
|
||||
def self.windows?
|
||||
RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
|
||||
end
|
||||
|
@ -1,41 +1,60 @@
|
||||
module Guard
|
||||
|
||||
# Listener implementation for Mac OS X `FSEvents`.
|
||||
#
|
||||
class Darwin < Listener
|
||||
|
||||
# Initialize the Listener.
|
||||
#
|
||||
def initialize(*)
|
||||
super
|
||||
@fsevent = FSEvent.new
|
||||
end
|
||||
|
||||
def worker
|
||||
@fsevent
|
||||
end
|
||||
|
||||
# Start the listener.
|
||||
#
|
||||
def start
|
||||
super
|
||||
worker.run
|
||||
end
|
||||
|
||||
# Stop the listener.
|
||||
#
|
||||
def stop
|
||||
super
|
||||
worker.stop
|
||||
end
|
||||
|
||||
# Check if the listener is usable on the current OS.
|
||||
#
|
||||
# @return [Boolean] whether usable or not
|
||||
#
|
||||
def self.usable?
|
||||
require 'rb-fsevent'
|
||||
if !defined?(FSEvent::VERSION) || (defined?(Gem::Version) &&
|
||||
Gem::Version.new(FSEvent::VERSION) < Gem::Version.new('0.4.0'))
|
||||
UI.info "Please update rb-fsevent (>= 0.4.0)"
|
||||
UI.info 'Please update rb-fsevent (>= 0.4.0)'
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
rescue LoadError
|
||||
UI.info "Please install rb-fsevent gem for Mac OSX FSEvents support"
|
||||
UI.info 'Please install rb-fsevent gem for Mac OSX FSEvents support'
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
# Get the listener worker.
|
||||
#
|
||||
def worker
|
||||
@fsevent
|
||||
end
|
||||
|
||||
# Watch the given directory for file changes.
|
||||
#
|
||||
# @param [String] directory the directory to watch
|
||||
#
|
||||
def watch(directory)
|
||||
worker.watch(directory) do |modified_dirs|
|
||||
files = modified_files(modified_dirs)
|
||||
|
@ -1,53 +1,65 @@
|
||||
module Guard
|
||||
|
||||
# Listener implementation for Linux `inotify`.
|
||||
#
|
||||
class Linux < Listener
|
||||
|
||||
# Initialize the Listener.
|
||||
#
|
||||
def initialize(*)
|
||||
super
|
||||
|
||||
@inotify = INotify::Notifier.new
|
||||
@files = []
|
||||
@latency = 0.5
|
||||
end
|
||||
|
||||
# Start the listener.
|
||||
#
|
||||
def start
|
||||
@stop = false
|
||||
super
|
||||
watch_change unless watch_change?
|
||||
end
|
||||
|
||||
# Stop the listener.
|
||||
#
|
||||
def stop
|
||||
super
|
||||
@stop = true
|
||||
sleep(@latency)
|
||||
end
|
||||
|
||||
# Check if the listener is usable on the current OS.
|
||||
#
|
||||
# @return [Boolean] whether usable or not
|
||||
#
|
||||
def self.usable?
|
||||
require 'rb-inotify'
|
||||
if !defined?(INotify::VERSION) || (defined?(Gem::Version) &&
|
||||
Gem::Version.new(INotify::VERSION.join('.')) < Gem::Version.new('0.8.5'))
|
||||
UI.info "Please update rb-inotify (>= 0.8.5)"
|
||||
UI.info 'Please update rb-inotify (>= 0.8.5)'
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
rescue LoadError
|
||||
UI.info "Please install rb-inotify gem for Linux inotify support"
|
||||
UI.info 'Please install rb-inotify gem for Linux inotify support'
|
||||
false
|
||||
end
|
||||
|
||||
def watch_change?
|
||||
!!@watch_change
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
# Get the listener worker.
|
||||
#
|
||||
def worker
|
||||
@inotify
|
||||
end
|
||||
|
||||
# Watch the given directory for file changes.
|
||||
#
|
||||
# @param [String] directory the directory to watch
|
||||
#
|
||||
def watch(directory)
|
||||
# The event selection is based on https://github.com/guard/guard/wiki/Analysis-of-inotify-events-for-different-editors
|
||||
worker.watch(directory, :recursive, :create, :move_self, :close_write) do |event|
|
||||
worker.watch(directory, :recursive, :attrib, :create, :move_self, :close_write) do |event|
|
||||
unless event.name == "" # Event on root directory
|
||||
@files << event.absolute_name
|
||||
end
|
||||
@ -55,6 +67,16 @@ module Guard
|
||||
rescue Interrupt
|
||||
end
|
||||
|
||||
# Test if inotify is watching for changes.
|
||||
#
|
||||
# @return [Boolean] whether inotify is active or not
|
||||
#
|
||||
def watch_change?
|
||||
!!@watch_change
|
||||
end
|
||||
|
||||
# Watch for file system changes.
|
||||
#
|
||||
def watch_change
|
||||
@watch_change = true
|
||||
until @stop
|
||||
|
@ -1,24 +1,46 @@
|
||||
module Guard
|
||||
|
||||
# Polling listener that works cross-platform and
|
||||
# has no dependencies. This is the listener that
|
||||
# uses the most CPU processing power and has higher
|
||||
# file IO that the other implementations.
|
||||
#
|
||||
class Polling < Listener
|
||||
|
||||
# Initialize the Listener.
|
||||
#
|
||||
def initialize(*)
|
||||
super
|
||||
@latency = 1.5
|
||||
end
|
||||
|
||||
# Start the listener.
|
||||
#
|
||||
def start
|
||||
@stop = false
|
||||
super
|
||||
watch_change
|
||||
end
|
||||
|
||||
# Stop the listener.
|
||||
#
|
||||
def stop
|
||||
super
|
||||
@stop = true
|
||||
end
|
||||
|
||||
private
|
||||
# Watch the given directory for file changes.
|
||||
#
|
||||
# @param [String] directory the directory to watch
|
||||
#
|
||||
def watch(directory)
|
||||
@existing = all_files
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Watch for file system changes.
|
||||
#
|
||||
def watch_change
|
||||
until @stop
|
||||
start = Time.now.to_f
|
||||
@ -29,9 +51,5 @@ module Guard
|
||||
end
|
||||
end
|
||||
|
||||
def watch(directory)
|
||||
@existing = all_files
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -1,36 +1,48 @@
|
||||
module Guard
|
||||
|
||||
# Listener implementation for Windows `fchange`.
|
||||
#
|
||||
class Windows < Listener
|
||||
|
||||
# Initialize the Listener.
|
||||
#
|
||||
def initialize(*)
|
||||
super
|
||||
|
||||
@fchange = FChange::Notifier.new
|
||||
end
|
||||
|
||||
# Start the listener.
|
||||
#
|
||||
def start
|
||||
super
|
||||
worker.run
|
||||
end
|
||||
|
||||
# Stop the listener.
|
||||
#
|
||||
def stop
|
||||
super
|
||||
worker.stop
|
||||
end
|
||||
|
||||
# Check if the listener is usable on the current OS.
|
||||
#
|
||||
# @return [Boolean] whether usable or not
|
||||
#
|
||||
def self.usable?
|
||||
require 'rb-fchange'
|
||||
true
|
||||
rescue LoadError
|
||||
UI.info "Please install rb-fchange gem for Windows file events support"
|
||||
UI.info 'Please install rb-fchange gem for Windows file events support'
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def worker
|
||||
@fchange
|
||||
end
|
||||
private
|
||||
|
||||
# Watch the given directory for file changes.
|
||||
#
|
||||
# @param [String] directory the directory to watch
|
||||
#
|
||||
def watch(directory)
|
||||
worker.watch(directory, :all_events, :recursive) do |event|
|
||||
paths = [File.expand_path(event.watcher.path)]
|
||||
@ -39,5 +51,11 @@ module Guard
|
||||
end
|
||||
end
|
||||
|
||||
# Get the listener worker.
|
||||
#
|
||||
def worker
|
||||
@fchange
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -3,134 +3,288 @@ require 'pathname'
|
||||
require 'guard/ui'
|
||||
|
||||
module Guard
|
||||
|
||||
# The notifier class handles cross-platform system notifications that supports:
|
||||
#
|
||||
# - Growl on Mac OS X
|
||||
# - Libnotify on Linux
|
||||
# - Notifu on Windows
|
||||
#
|
||||
module Notifier
|
||||
|
||||
# Application name as shown in the specific notification settings
|
||||
APPLICATION_NAME = "Guard"
|
||||
|
||||
def self.turn_off
|
||||
ENV["GUARD_NOTIFY"] = 'false'
|
||||
end
|
||||
class << self
|
||||
|
||||
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
|
||||
attr_accessor :growl_library, :gntp
|
||||
|
||||
# Turn notifications off.
|
||||
#
|
||||
def turn_off
|
||||
ENV["GUARD_NOTIFY"] = 'false'
|
||||
end
|
||||
end
|
||||
|
||||
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']
|
||||
when /darwin/i
|
||||
notify_mac(title, message, image, options)
|
||||
when /linux/i
|
||||
notify_linux(title, message, image, options)
|
||||
when /mswin|mingw/i
|
||||
notify_windows(title, message, image, options)
|
||||
when /darwin/i
|
||||
require_growl
|
||||
when /linux/i
|
||||
require_libnotify
|
||||
when /mswin|mingw/i
|
||||
require_rbnotifu
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.enabled?
|
||||
ENV["GUARD_NOTIFY"] == 'true'
|
||||
end
|
||||
# Show a message with the system notification.
|
||||
#
|
||||
# @see .image_path
|
||||
#
|
||||
# @param [String] the message to show
|
||||
# @option options [Symbol, String] image the image symbol or path to an image
|
||||
# @option options [String] title the notification title
|
||||
#
|
||||
def notify(message, options = { })
|
||||
if enabled?
|
||||
image = options.delete(:image) || :success
|
||||
title = options.delete(:title) || "Guard"
|
||||
|
||||
private
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
def self.image_path(image)
|
||||
images_path = Pathname.new(File.dirname(__FILE__)).join('../../images')
|
||||
case image
|
||||
when :failed
|
||||
images_path.join("failed.png").to_s
|
||||
when :pending
|
||||
images_path.join("pending.png").to_s
|
||||
when :success
|
||||
images_path.join("success.png").to_s
|
||||
else
|
||||
# path given
|
||||
image
|
||||
end
|
||||
end
|
||||
|
||||
def self.image_level(image)
|
||||
case image
|
||||
when :failed
|
||||
:error
|
||||
when :pending
|
||||
:warn
|
||||
when :success
|
||||
:info
|
||||
else
|
||||
:info
|
||||
end
|
||||
end
|
||||
|
||||
def self.require_growl
|
||||
begin
|
||||
require 'growl_notify'
|
||||
|
||||
if GrowlNotify.application_name != APPLICATION_NAME
|
||||
GrowlNotify.config do |c|
|
||||
c.notifications = c.default_notifications = [ APPLICATION_NAME ]
|
||||
c.application_name = c.notifications.first
|
||||
case RbConfig::CONFIG['target_os']
|
||||
when /darwin/i
|
||||
notify_mac(title, message, image, options)
|
||||
when /linux/i
|
||||
notify_linux(title, message, image, options)
|
||||
when /mswin|mingw/i
|
||||
notify_windows(title, message, image, options)
|
||||
end
|
||||
end
|
||||
rescue LoadError
|
||||
require 'growl'
|
||||
end
|
||||
rescue LoadError
|
||||
turn_off
|
||||
UI.info "Please install growl_notify or growl gem for Mac OS X notification support and add it to your Gemfile"
|
||||
end
|
||||
|
||||
def self.require_libnotify
|
||||
require 'libnotify'
|
||||
rescue LoadError
|
||||
turn_off
|
||||
UI.info "Please install libnotify gem for Linux notification support and add it to your Gemfile"
|
||||
end
|
||||
# Test if the notifications are enabled and available.
|
||||
#
|
||||
# @return [Boolean] whether the notifications are available
|
||||
#
|
||||
def enabled?
|
||||
ENV["GUARD_NOTIFY"] == 'true'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Send a message to Growl either with the `growl` gem or the `growl_notify` gem.
|
||||
#
|
||||
# @param [String] title the notification title
|
||||
# @param [String] message the message to show
|
||||
# @param [Symbol, String] the image to user
|
||||
# @param [Hash] options the growl options
|
||||
#
|
||||
def notify_mac(title, message, image, options = { })
|
||||
require_growl # need for guard-rspec formatter that is called out of guard scope
|
||||
|
||||
notification = { :title => title, :icon => image_path(image) }.merge(options)
|
||||
|
||||
case self.growl_library
|
||||
when :growl_notify
|
||||
notification.delete(:name)
|
||||
|
||||
GrowlNotify.send_notification({
|
||||
:description => message,
|
||||
:application_name => APPLICATION_NAME
|
||||
}.merge(notification))
|
||||
|
||||
when :ruby_gntp
|
||||
icon = "file://#{ notification.delete(:icon) }"
|
||||
|
||||
self.gntp.notify({
|
||||
:name => [:pending, :success, :failed].include?(image) ? image.to_s : 'notify',
|
||||
:text => message,
|
||||
:icon => icon
|
||||
}.merge(notification))
|
||||
|
||||
when :growl
|
||||
Growl.notify(message, {
|
||||
:name => APPLICATION_NAME
|
||||
}.merge(notification))
|
||||
end
|
||||
end
|
||||
|
||||
# Send a message to libnotify.
|
||||
#
|
||||
# @param [String] title the notification title
|
||||
# @param [String] message the message to show
|
||||
# @param [Symbol, String] the image to user
|
||||
# @param [Hash] options the libnotify options
|
||||
#
|
||||
def notify_linux(title, message, image, options = { })
|
||||
require_libnotify # need for guard-rspec formatter that is called out of guard scope
|
||||
|
||||
notification = { :body => message, :summary => title, :icon_path => image_path(image), :transient => true }
|
||||
Libnotify.show notification.merge(options)
|
||||
end
|
||||
|
||||
# Send a message to notifu.
|
||||
#
|
||||
# @param [String] title the notification title
|
||||
# @param [String] message the message to show
|
||||
# @param [Symbol, String] the image to user
|
||||
# @param [Hash] options the notifu options
|
||||
#
|
||||
def notify_windows(title, message, image, options = { })
|
||||
require_rbnotifu # need for guard-rspec formatter that is called out of guard scope
|
||||
|
||||
notification = { :message => message, :title => title, :type => image_level(image), :time => 3 }
|
||||
Notifu.show notification.merge(options)
|
||||
end
|
||||
|
||||
# Get the image path for an image symbol.
|
||||
#
|
||||
# Known symbols are:
|
||||
#
|
||||
# - failed
|
||||
# - pending
|
||||
# - success
|
||||
#
|
||||
# @param [Symbol] image the image name
|
||||
# @return [String] the image path
|
||||
#
|
||||
def image_path(image)
|
||||
images_path = Pathname.new(File.dirname(__FILE__)).join('../../images')
|
||||
case image
|
||||
when :failed
|
||||
images_path.join("failed.png").to_s
|
||||
when :pending
|
||||
images_path.join("pending.png").to_s
|
||||
when :success
|
||||
images_path.join("success.png").to_s
|
||||
else
|
||||
# path given
|
||||
image
|
||||
end
|
||||
end
|
||||
|
||||
# The notification level type for the given image.
|
||||
#
|
||||
# @param [Symbol] image the image
|
||||
# @return [Symbol] the level
|
||||
#
|
||||
def image_level(image)
|
||||
case image
|
||||
when :failed
|
||||
:error
|
||||
when :pending
|
||||
:warn
|
||||
when :success
|
||||
:info
|
||||
else
|
||||
:info
|
||||
end
|
||||
end
|
||||
|
||||
# Try to safely load growl and turns notifications off on load failure.
|
||||
# The Guard notifier knows three different library to handle sending
|
||||
# Growl messages and tries to loading them in the given order:
|
||||
#
|
||||
# - [Growl Notify](https://github.com/scottdavis/growl_notify)
|
||||
# - [Ruby GNTP](https://github.com/snaka/ruby_gntp)
|
||||
# - [Growl](https://github.com/visionmedia/growl)
|
||||
#
|
||||
# On successful loading of any of the libraries, the active library name is
|
||||
# accessible through `.growl_library`.
|
||||
#
|
||||
def require_growl
|
||||
self.growl_library = try_growl_notify || try_ruby_gntp || try_growl
|
||||
|
||||
unless self.growl_library
|
||||
turn_off
|
||||
UI.info "Please install growl_notify or growl gem for Mac OS X notification support and add it to your Gemfile"
|
||||
end
|
||||
end
|
||||
|
||||
# Try to load the `growl_notify` gem.
|
||||
#
|
||||
# @return [Symbol, nil] A symbol with the name of the loaded library
|
||||
#
|
||||
def try_growl_notify
|
||||
require 'growl_notify'
|
||||
|
||||
begin
|
||||
if GrowlNotify.application_name != APPLICATION_NAME
|
||||
GrowlNotify.config do |c|
|
||||
c.notifications = c.default_notifications = [APPLICATION_NAME]
|
||||
c.application_name = c.notifications.first
|
||||
end
|
||||
end
|
||||
|
||||
rescue ::GrowlNotify::GrowlNotFound
|
||||
turn_off
|
||||
UI.info "Please install Growl from http://growl.info"
|
||||
end
|
||||
|
||||
:growl_notify
|
||||
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
# Try to load the `ruby_gntp` gem and register the available
|
||||
# notification channels.
|
||||
#
|
||||
# @return [Symbol, nil] A symbol with the name of the loaded library
|
||||
#
|
||||
def try_ruby_gntp
|
||||
require 'ruby_gntp'
|
||||
|
||||
self.gntp = GNTP.new(APPLICATION_NAME)
|
||||
self.gntp.register(:notifications => [
|
||||
{ :name => 'notify', :enabled => true },
|
||||
{ :name => 'failed', :enabled => true },
|
||||
{ :name => 'pending', :enabled => true },
|
||||
{ :name => 'success', :enabled => true }
|
||||
])
|
||||
|
||||
:ruby_gntp
|
||||
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
# Try to load the `growl_notify` gem.
|
||||
#
|
||||
# @return [Symbol, nil] A symbol with the name of the loaded library
|
||||
#
|
||||
def try_growl
|
||||
require 'growl'
|
||||
|
||||
:growl
|
||||
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
# Try to safely load libnotify and turns notifications
|
||||
# off on load failure.
|
||||
#
|
||||
def require_libnotify
|
||||
require 'libnotify'
|
||||
|
||||
rescue LoadError
|
||||
turn_off
|
||||
UI.info "Please install libnotify gem for Linux notification support and add it to your Gemfile"
|
||||
end
|
||||
|
||||
# Try to safely load rb-notifu and turns notifications
|
||||
# off on load failure.
|
||||
#
|
||||
def require_rbnotifu
|
||||
require 'rb-notifu'
|
||||
|
||||
rescue LoadError
|
||||
turn_off
|
||||
UI.info "Please install rb-notifu gem for Windows notification support and add it to your Gemfile"
|
||||
end
|
||||
|
||||
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
|
||||
|
187
lib/guard/ui.rb
187
lib/guard/ui.rb
@ -1,88 +1,93 @@
|
||||
module Guard
|
||||
|
||||
# The UI class helps to format messages for the user. Everything that is logged
|
||||
# through this class is considered either as an error message or a diagnostic
|
||||
# message and is written to standard error (STDERR).
|
||||
#
|
||||
# If your Guard does some output that is piped into another process for further
|
||||
# processing, please just write it to STDOUT with `puts`.
|
||||
#
|
||||
module UI
|
||||
|
||||
ANSI_ESCAPE_BRIGHT = "1"
|
||||
|
||||
ANSI_ESCAPE_BLACK = "30"
|
||||
ANSI_ESCAPE_RED = "31"
|
||||
ANSI_ESCAPE_GREEN = "32"
|
||||
ANSI_ESCAPE_YELLOW = "33"
|
||||
ANSI_ESCAPE_BLUE = "34"
|
||||
ANSI_ESCAPE_MAGENTA = "35"
|
||||
ANSI_ESCAPE_CYAN = "36"
|
||||
ANSI_ESCAPE_WHITE = "37"
|
||||
|
||||
ANSI_ESCAPE_BGBLACK = "40"
|
||||
ANSI_ESCAPE_BGRED = "41"
|
||||
ANSI_ESCAPE_BGGREEN = "42"
|
||||
ANSI_ESCAPE_BGYELLOW = "43"
|
||||
ANSI_ESCAPE_BGBLUE = "44"
|
||||
ANSI_ESCAPE_BGMAGENTA = "45"
|
||||
ANSI_ESCAPE_BGCYAN = "46"
|
||||
ANSI_ESCAPE_BGWHITE = "47"
|
||||
|
||||
class << self
|
||||
|
||||
color_enabled = nil
|
||||
|
||||
def info(message, options = {})
|
||||
unless ENV["GUARD_ENV"] == "test"
|
||||
# Show an info message.
|
||||
#
|
||||
# @param [String] message the message to show
|
||||
# @option options [Boolean] reset whether to clean the output before
|
||||
#
|
||||
def info(message, options = { })
|
||||
unless ENV['GUARD_ENV'] == 'test'
|
||||
reset_line if options[:reset]
|
||||
puts color(message) if message != ''
|
||||
STDERR.puts color(message) if message != ''
|
||||
end
|
||||
end
|
||||
|
||||
def error(message, options = {})
|
||||
unless ENV["GUARD_ENV"] == "test"
|
||||
# Show a red error message that is prefixed with ERROR.
|
||||
#
|
||||
# @param [String] message the message to show
|
||||
# @option options [Boolean] reset whether to clean the output before
|
||||
#
|
||||
def error(message, options = { })
|
||||
unless ENV['GUARD_ENV'] == 'test'
|
||||
reset_line if options[:reset]
|
||||
puts color('ERROR: ', :red) + message
|
||||
STDERR.puts color('ERROR: ', :red) + message
|
||||
end
|
||||
end
|
||||
|
||||
def deprecation(message, options = {})
|
||||
unless ENV["GUARD_ENV"] == "test"
|
||||
# Show a red deprecation message that is prefixed with DEPRECATION.
|
||||
#
|
||||
# @param [String] message the message to show
|
||||
# @option options [Boolean] reset whether to clean the output before
|
||||
#
|
||||
def deprecation(message, options = { })
|
||||
unless ENV['GUARD_ENV'] == 'test'
|
||||
reset_line if options[:reset]
|
||||
puts color('DEPRECATION: ', :red) + message
|
||||
STDERR.puts color('DEPRECATION: ', :red) + message
|
||||
end
|
||||
end
|
||||
|
||||
def debug(message, options = {})
|
||||
unless ENV["GUARD_ENV"] == "test"
|
||||
# Show a debug message that is prefixed with DEBUG and a timestamp.
|
||||
#
|
||||
# @param [String] message the message to show
|
||||
# @option options [Boolean] reset whether to clean the output before
|
||||
#
|
||||
def debug(message, options = { })
|
||||
unless ENV['GUARD_ENV'] == 'test'
|
||||
reset_line if options[:reset]
|
||||
puts color("DEBUG (#{Time.now.strftime('%T')}): ", :yellow) + message if ::Guard.options && ::Guard.options[:debug]
|
||||
STDERR.puts color("DEBUG (#{Time.now.strftime('%T')}): ", :yellow) + message if ::Guard.options && ::Guard.options[:debug]
|
||||
end
|
||||
end
|
||||
|
||||
# Reset a line.
|
||||
#
|
||||
def reset_line
|
||||
print(color_enabled? ? "\r\e[0m" : "\r\n")
|
||||
STDERR.print(color_enabled? ? "\r\e[0m" : "\r\n")
|
||||
end
|
||||
|
||||
# Clear the output.
|
||||
#
|
||||
def clear
|
||||
system("clear;")
|
||||
system('clear;')
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
# Reset a color sequence.
|
||||
#
|
||||
# @deprecated
|
||||
# @param [String] text the text
|
||||
#
|
||||
def reset_color(text)
|
||||
deprecation('UI.reset_color(text) is deprecated, please use color(text, "") instead.')
|
||||
color(text, "")
|
||||
end
|
||||
|
||||
def color(text, *color_options)
|
||||
color_code = ""
|
||||
color_options.each do |color_option|
|
||||
color_option = color_option.to_s
|
||||
if color_option != ""
|
||||
if !(color_option =~ /\d+/)
|
||||
color_option = const_get("ANSI_ESCAPE_#{color_option.upcase}")
|
||||
end
|
||||
color_code += ";" + color_option
|
||||
end
|
||||
end
|
||||
color_enabled? ? "\e[0#{color_code}m#{text}\e[0m" : text
|
||||
deprecation('UI.reset_color(text) is deprecated, please use color(text, ' ') instead.')
|
||||
color(text, '')
|
||||
end
|
||||
|
||||
# Checks if color output can be enabled.
|
||||
#
|
||||
# @return [Boolean] whether color is enabled or not
|
||||
#
|
||||
def color_enabled?
|
||||
if @color_enabled.nil?
|
||||
if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
|
||||
@ -102,9 +107,87 @@ module Guard
|
||||
@color_enabled = true
|
||||
end
|
||||
end
|
||||
|
||||
@color_enabled
|
||||
end
|
||||
|
||||
# Colorizes a text message. See the constant in the UI class for possible
|
||||
# color_options parameters. You can pass optionally :bright, a foreground
|
||||
# color and a background color.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# color('Hello World', :red, :bright)
|
||||
#
|
||||
# @param [String] the text to colorize
|
||||
# @param [Array] color_options the color options
|
||||
#
|
||||
def color(text, *color_options)
|
||||
color_code = ''
|
||||
color_options.each do |color_option|
|
||||
color_option = color_option.to_s
|
||||
if color_option != ''
|
||||
if !(color_option =~ /\d+/)
|
||||
color_option = const_get("ANSI_ESCAPE_#{ color_option.upcase }")
|
||||
end
|
||||
color_code += ';' + color_option
|
||||
end
|
||||
end
|
||||
color_enabled? ? "\e[0#{ color_code }m#{ text }\e[0m" : text
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Brighten the color
|
||||
ANSI_ESCAPE_BRIGHT = '1'
|
||||
|
||||
# Black foreground color
|
||||
ANSI_ESCAPE_BLACK = '30'
|
||||
|
||||
# Red foreground color
|
||||
ANSI_ESCAPE_RED = '31'
|
||||
|
||||
# Green foreground color
|
||||
ANSI_ESCAPE_GREEN = '32'
|
||||
|
||||
# Yellow foreground color
|
||||
ANSI_ESCAPE_YELLOW = '33'
|
||||
|
||||
# Blue foreground color
|
||||
ANSI_ESCAPE_BLUE = '34'
|
||||
|
||||
# Magenta foreground color
|
||||
ANSI_ESCAPE_MAGENTA = '35'
|
||||
|
||||
# Cyan foreground color
|
||||
ANSI_ESCAPE_CYAN = '36'
|
||||
|
||||
# White foreground color
|
||||
ANSI_ESCAPE_WHITE = '37'
|
||||
|
||||
# Black background color
|
||||
ANSI_ESCAPE_BGBLACK = '40'
|
||||
|
||||
# Red background color
|
||||
ANSI_ESCAPE_BGRED = '41'
|
||||
|
||||
# Green background color
|
||||
ANSI_ESCAPE_BGGREEN = '42'
|
||||
|
||||
# Yellow background color
|
||||
ANSI_ESCAPE_BGYELLOW = '43'
|
||||
|
||||
# Blue background color
|
||||
ANSI_ESCAPE_BGBLUE = '44'
|
||||
|
||||
# Magenta background color
|
||||
ANSI_ESCAPE_BGMAGENTA = '45'
|
||||
|
||||
# Cyan background color
|
||||
ANSI_ESCAPE_BGCYAN = '46'
|
||||
|
||||
# White background color
|
||||
ANSI_ESCAPE_BGWHITE = '47'
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -1,3 +1,6 @@
|
||||
module Guard
|
||||
VERSION = "0.6.3" unless defined? Guard::VERSION
|
||||
unless defined? Guard::VERSION
|
||||
# The current gem version of Guard
|
||||
VERSION = '0.8.4'
|
||||
end
|
||||
end
|
||||
|
@ -1,7 +1,18 @@
|
||||
module Guard
|
||||
|
||||
# The watcher defines a RegExp that will be matched against file system modifications.
|
||||
# When a watcher matches a change, an optional action block is executed to enable
|
||||
# processing the file system change result.
|
||||
#
|
||||
class Watcher
|
||||
|
||||
attr_accessor :pattern, :action
|
||||
|
||||
# Initialize a file watcher.
|
||||
#
|
||||
# @param [String, Regexp] pattern the pattern to be watched by the guard
|
||||
# @param [Block] action the action to execute before passing the result to the Guard
|
||||
#
|
||||
def initialize(pattern, action = nil)
|
||||
@pattern, @action = pattern, action
|
||||
@@warning_printed ||= false
|
||||
@ -10,30 +21,52 @@ module Guard
|
||||
if @pattern.is_a?(String) && @pattern =~ /(^(\^))|(>?(\\\.)|(\.\*))|(\(.*\))|(\[.*\])|(\$$)/
|
||||
unless @@warning_printed
|
||||
UI.info "*"*20 + "\nDEPRECATION WARNING!\n" + "*"*20
|
||||
UI.info "You have strings in your Guardfile's watch patterns that seem to represent regexps.\nGuard matchs String with == and Regexp with Regexp#match.\nYou should either use plain String (without Regexp special characters) or real Regexp.\n"
|
||||
UI.info <<-MSG
|
||||
You have a string in your Guardfile watch patterns that seem to represent a Regexp.
|
||||
Guard matches String with == and Regexp with Regexp#match.
|
||||
You should either use plain String (without Regexp special characters) or real Regexp.
|
||||
MSG
|
||||
@@warning_printed = true
|
||||
end
|
||||
UI.info "\"#{@pattern}\" has been converted to #{Regexp.new(@pattern).inspect}\n"
|
||||
|
||||
UI.info "\"#{@pattern}\" has been converted to #{ Regexp.new(@pattern).inspect }\n"
|
||||
@pattern = Regexp.new(@pattern)
|
||||
end
|
||||
end
|
||||
|
||||
# Finds the files that matches a Guard.
|
||||
#
|
||||
# @param [Guard::Guard] guard the guard which watchers are used
|
||||
# @param [Array<String>] files the changed files
|
||||
# @return [Array<Object>] the matched watcher response
|
||||
#
|
||||
def self.match_files(guard, files)
|
||||
guard.watchers.inject([]) do |paths, watcher|
|
||||
files.each do |file|
|
||||
if matches = watcher.match_file?(file)
|
||||
if watcher.action
|
||||
result = watcher.call_action(matches)
|
||||
paths << Array(result) if result.respond_to?(:empty?) && !result.empty?
|
||||
if guard.options[:any_return]
|
||||
paths << result
|
||||
elsif result.respond_to?(:empty?) && !result.empty?
|
||||
paths << Array(result)
|
||||
end
|
||||
else
|
||||
paths << matches[0]
|
||||
end
|
||||
end
|
||||
end
|
||||
paths.flatten.map { |p| p.to_s }
|
||||
|
||||
guard.options[:any_return] ? paths : paths.flatten.map { |p| p.to_s }
|
||||
end
|
||||
end
|
||||
|
||||
# Test if a file would be matched by any of the Guards watchers.
|
||||
#
|
||||
# @param [Array<Guard::Guard>] guards the guards to use the watchers from
|
||||
# @param [Array<String>] files the files to test
|
||||
# @return [Boolean] Whether a file matches
|
||||
#
|
||||
def self.match_files?(guards, files)
|
||||
guards.any? do |guard|
|
||||
guard.watchers.any? do |watcher|
|
||||
@ -42,6 +75,11 @@ module Guard
|
||||
end
|
||||
end
|
||||
|
||||
# Test the watchers pattern against a file.
|
||||
#
|
||||
# @param [String] file the file to test
|
||||
# @return [Boolean] whether the given file is matched
|
||||
#
|
||||
def match_file?(file)
|
||||
if @pattern.is_a?(Regexp)
|
||||
file.match(@pattern)
|
||||
@ -50,15 +88,25 @@ module Guard
|
||||
end
|
||||
end
|
||||
|
||||
# Test if any of the files is the Guardfile.
|
||||
#
|
||||
# @param [Array<String>] the files to test
|
||||
# @return [Boolean] whether one of these files is the Guardfile
|
||||
#
|
||||
def self.match_guardfile?(files)
|
||||
files.any? { |file| "#{Dir.pwd}/#{file}" == Dsl.guardfile_path }
|
||||
files.any? { |file| "#{ Dir.pwd }/#{ file }" == Dsl.guardfile_path }
|
||||
end
|
||||
|
||||
# Executes a watcher action.
|
||||
#
|
||||
# @param [String, MatchData] the matched path or the match from the Regex
|
||||
# @return [String] the final paths
|
||||
#
|
||||
def call_action(matches)
|
||||
begin
|
||||
@action.arity > 0 ? @action.call(matches) : @action.call
|
||||
rescue Exception => e
|
||||
UI.error "Problem with watch action!\n#{e.message}\n\n#{e.backtrace.join("\n")}"
|
||||
UI.error "Problem with watch action!\n#{ e.message }\n\n#{ e.backtrace.join("\n") }"
|
||||
end
|
||||
end
|
||||
|
||||
|
0
spec/fixtures/folder1/file1.txt
vendored
Normal file → Executable file
0
spec/fixtures/folder1/file1.txt
vendored
Normal file → Executable file
@ -1,40 +1,70 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Guard::DslDescriber do
|
||||
before(:each) do
|
||||
::Guard.stub!(:guards).and_return([mock('Guard')])
|
||||
user_config_path = File.expand_path(File.join('~', '.guard.rb'))
|
||||
File.stub(:exist?).with(user_config_path) { false }
|
||||
|
||||
let(:describer) { ::Guard::DslDescriber }
|
||||
|
||||
let(:guardfile) do
|
||||
<<-GUARD
|
||||
guard 'test', :a => :b do
|
||||
watch('c')
|
||||
end
|
||||
|
||||
group :a do
|
||||
guard 'test', :x => 1 do
|
||||
watch('c')
|
||||
end
|
||||
end
|
||||
|
||||
group "b" do
|
||||
guard 'another' do
|
||||
watch('c')
|
||||
end
|
||||
end
|
||||
GUARD
|
||||
end
|
||||
subject { described_class }
|
||||
|
||||
|
||||
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')
|
||||
before do
|
||||
@output = ''
|
||||
Guard::UI.stub(:info) { |msg| @output << msg + "\n" }
|
||||
end
|
||||
end
|
||||
|
||||
group "b" do
|
||||
guard 'another' do
|
||||
watch('c')
|
||||
after do
|
||||
Guard::UI.unstub(:info)
|
||||
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 => {} } ] }
|
||||
]
|
||||
describe '.list' do
|
||||
it 'lists the available Guards' do
|
||||
Guard.stub(:guard_gem_names).and_return ['test', 'another', 'even', 'more']
|
||||
describer.list(:guardfile_contents => guardfile)
|
||||
@output.should eql <<OUTPUT
|
||||
Using inline Guardfile.
|
||||
Available guards:
|
||||
another*
|
||||
even
|
||||
more
|
||||
test*
|
||||
|
||||
See also https://github.com/guard/guard/wiki/List-of-available-Guards
|
||||
* denotes ones already in your Guardfile
|
||||
OUTPUT
|
||||
end
|
||||
end
|
||||
|
||||
describe '.show' do
|
||||
it 'shows the Guards and their options' do
|
||||
describer.show(:guardfile_contents => guardfile)
|
||||
@output.should eql <<OUTPUT
|
||||
Using inline Guardfile.
|
||||
(global):
|
||||
test: a => :b
|
||||
Group a:
|
||||
test: x => 1
|
||||
Group b:
|
||||
another
|
||||
|
||||
OUTPUT
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,11 +1,15 @@
|
||||
require 'spec_helper'
|
||||
require 'guard/guard'
|
||||
|
||||
describe Guard::Dsl do
|
||||
subject { described_class }
|
||||
|
||||
class Guard::Dummy < Guard::Guard; end
|
||||
|
||||
before(:each) do
|
||||
@local_guardfile_path = File.join(Dir.pwd, 'Guardfile')
|
||||
@home_guardfile_path = File.expand_path(File.join("~", ".Guardfile"))
|
||||
@user_config_path = File.expand_path(File.join("~", ".guard.rb"))
|
||||
::Guard.setup
|
||||
::Guard.stub!(:options).and_return(:debug => true)
|
||||
::Guard.stub!(:guards).and_return([mock('Guard')])
|
||||
end
|
||||
@ -20,24 +24,24 @@ describe Guard::Dsl do
|
||||
|
||||
it "should use a string for initializing" do
|
||||
Guard::UI.should_not_receive(:error)
|
||||
lambda { subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
|
||||
subject.guardfile_contents.should == valid_guardfile_string
|
||||
lambda { described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
|
||||
described_class.guardfile_contents.should == valid_guardfile_string
|
||||
end
|
||||
|
||||
it "should use a given file over the default loc" do
|
||||
fake_guardfile('/abc/Guardfile', "guard :foo")
|
||||
|
||||
Guard::UI.should_not_receive(:error)
|
||||
lambda { subject.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
|
||||
subject.guardfile_contents.should == "guard :foo"
|
||||
lambda { described_class.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
|
||||
described_class.guardfile_contents.should == "guard :foo"
|
||||
end
|
||||
|
||||
it "should use a default file if no other options are given" do
|
||||
fake_guardfile(@local_guardfile_path, "guard :bar")
|
||||
|
||||
Guard::UI.should_not_receive(:error)
|
||||
lambda { subject.evaluate_guardfile }.should_not raise_error
|
||||
subject.guardfile_contents.should == "guard :bar"
|
||||
lambda { described_class.evaluate_guardfile }.should_not raise_error
|
||||
described_class.guardfile_contents.should == "guard :bar"
|
||||
end
|
||||
|
||||
it "should use a string over any other method" do
|
||||
@ -45,8 +49,8 @@ describe Guard::Dsl do
|
||||
fake_guardfile(@local_guardfile_path, "guard :bar")
|
||||
|
||||
Guard::UI.should_not_receive(:error)
|
||||
lambda { subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
|
||||
subject.guardfile_contents.should == valid_guardfile_string
|
||||
lambda { described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
|
||||
described_class.guardfile_contents.should == valid_guardfile_string
|
||||
end
|
||||
|
||||
it "should use the given Guardfile over default Guardfile" do
|
||||
@ -54,31 +58,31 @@ describe Guard::Dsl do
|
||||
fake_guardfile(@local_guardfile_path, "guard :bar")
|
||||
|
||||
Guard::UI.should_not_receive(:error)
|
||||
lambda { subject.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
|
||||
subject.guardfile_contents.should == "guard :foo"
|
||||
lambda { described_class.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
|
||||
described_class.guardfile_contents.should == "guard :foo"
|
||||
end
|
||||
|
||||
it 'should append the user config file if present' do
|
||||
fake_guardfile('/abc/Guardfile', "guard :foo")
|
||||
fake_guardfile(@user_config_path, "guard :bar")
|
||||
Guard::UI.should_not_receive(:error)
|
||||
lambda { subject.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
|
||||
subject.guardfile_contents_with_user_config.should == "guard :foo\nguard :bar"
|
||||
lambda { described_class.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
|
||||
described_class.guardfile_contents_with_user_config.should == "guard :foo\nguard :bar"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
it "displays an error message when no Guardfile is found" do
|
||||
subject.stub(:guardfile_default_path).and_return("no_guardfile_here")
|
||||
described_class.stub(:guardfile_default_path).and_return("no_guardfile_here")
|
||||
Guard::UI.should_receive(:error).with("No Guardfile found, please create one with `guard init`.")
|
||||
lambda { subject.evaluate_guardfile }.should raise_error
|
||||
lambda { described_class.evaluate_guardfile }.should raise_error
|
||||
end
|
||||
|
||||
it "displays an error message when no guard are defined in Guardfile" do
|
||||
::Guard::Dsl.stub!(:instance_eval_guardfile)
|
||||
::Guard.stub!(:guards).and_return([])
|
||||
Guard::UI.should_receive(:error)
|
||||
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string)
|
||||
described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string)
|
||||
end
|
||||
|
||||
describe "correctly reads data from its valid data source" do
|
||||
@ -86,22 +90,22 @@ describe Guard::Dsl do
|
||||
disable_user_config
|
||||
|
||||
it "reads correctly from a string" do
|
||||
lambda { subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
|
||||
subject.guardfile_contents.should == valid_guardfile_string
|
||||
lambda { described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
|
||||
described_class.guardfile_contents.should == valid_guardfile_string
|
||||
end
|
||||
|
||||
it "reads correctly from a Guardfile" do
|
||||
fake_guardfile('/abc/Guardfile', "guard :foo" )
|
||||
|
||||
lambda { subject.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
|
||||
subject.guardfile_contents.should == "guard :foo"
|
||||
lambda { described_class.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
|
||||
described_class.guardfile_contents.should == "guard :foo"
|
||||
end
|
||||
|
||||
it "reads correctly from a Guardfile" do
|
||||
fake_guardfile(File.join(Dir.pwd, 'Guardfile'), valid_guardfile_string)
|
||||
|
||||
lambda { subject.evaluate_guardfile }.should_not raise_error
|
||||
subject.guardfile_contents.should == valid_guardfile_string
|
||||
lambda { described_class.evaluate_guardfile }.should_not raise_error
|
||||
described_class.guardfile_contents.should == valid_guardfile_string
|
||||
end
|
||||
end
|
||||
|
||||
@ -113,14 +117,14 @@ describe Guard::Dsl do
|
||||
File.stub!(:read).with('/def/Guardfile') { raise Errno::EACCES.new("permission error") }
|
||||
|
||||
Guard::UI.should_receive(:error).with(/^Error reading file/)
|
||||
lambda { subject.evaluate_guardfile(:guardfile => '/def/Guardfile') }.should raise_error
|
||||
lambda { described_class.evaluate_guardfile(:guardfile => '/def/Guardfile') }.should raise_error
|
||||
end
|
||||
|
||||
it "raises error when given Guardfile doesn't exist" do
|
||||
File.stub!(:exist?).with('/def/Guardfile') { false }
|
||||
|
||||
Guard::UI.should_receive(:error).with(/No Guardfile exists at/)
|
||||
lambda { subject.evaluate_guardfile(:guardfile => '/def/Guardfile') }.should raise_error
|
||||
lambda { described_class.evaluate_guardfile(:guardfile => '/def/Guardfile') }.should raise_error
|
||||
end
|
||||
|
||||
it "raises error when resorting to use default, finds no default" do
|
||||
@ -128,24 +132,24 @@ describe Guard::Dsl do
|
||||
File.stub!(:exist?).with(@home_guardfile_path) { false }
|
||||
|
||||
Guard::UI.should_receive(:error).with("No Guardfile found, please create one with `guard init`.")
|
||||
lambda { subject.evaluate_guardfile }.should raise_error
|
||||
lambda { described_class.evaluate_guardfile }.should raise_error
|
||||
end
|
||||
|
||||
it "raises error when guardfile_content ends up empty or nil" do
|
||||
Guard::UI.should_receive(:error).with(/The command file/)
|
||||
lambda { subject.evaluate_guardfile(:guardfile_contents => "") }.should raise_error
|
||||
lambda { described_class.evaluate_guardfile(:guardfile_contents => "") }.should raise_error
|
||||
end
|
||||
|
||||
it "doesn't raise error when guardfile_content is nil (skipped)" do
|
||||
Guard::UI.should_not_receive(:error)
|
||||
lambda { subject.evaluate_guardfile(:guardfile_contents => nil) }.should_not raise_error
|
||||
lambda { described_class.evaluate_guardfile(:guardfile_contents => nil) }.should_not raise_error
|
||||
end
|
||||
end
|
||||
|
||||
it "displays an error message when Guardfile is not valid" do
|
||||
Guard::UI.should_receive(:error).with(/Invalid Guardfile, original error is:/)
|
||||
|
||||
lambda { subject.evaluate_guardfile(:guardfile_contents => invalid_guardfile_string ) }.should raise_error
|
||||
lambda { described_class.evaluate_guardfile(:guardfile_contents => invalid_guardfile_string ) }.should raise_error
|
||||
end
|
||||
|
||||
describe ".reevaluate_guardfile" do
|
||||
@ -153,10 +157,10 @@ describe Guard::Dsl do
|
||||
|
||||
it "resets already definded guards before calling evaluate_guardfile" do
|
||||
Guard::Notifier.turn_off
|
||||
subject.evaluate_guardfile(:guardfile_contents => invalid_guardfile_string)
|
||||
described_class.evaluate_guardfile(:guardfile_contents => invalid_guardfile_string)
|
||||
::Guard.guards.should_not be_empty
|
||||
::Guard::Dsl.should_receive(:evaluate_guardfile)
|
||||
subject.reevaluate_guardfile
|
||||
described_class.reevaluate_guardfile
|
||||
::Guard.guards.should be_empty
|
||||
end
|
||||
end
|
||||
@ -169,14 +173,14 @@ describe Guard::Dsl do
|
||||
context "when there is a local Guardfile" do
|
||||
it "returns the path to the local Guardfile" do
|
||||
File.stub(:exist?).with(local_path).and_return(true)
|
||||
subject.guardfile_default_path.should == local_path
|
||||
described_class.guardfile_default_path.should == local_path
|
||||
end
|
||||
end
|
||||
|
||||
context "when there is a Guardfile in the user's home directory" do
|
||||
it "returns the path to the user Guardfile" do
|
||||
File.stub(:exist?).with(user_path).and_return(true)
|
||||
subject.guardfile_default_path.should == user_path
|
||||
described_class.guardfile_default_path.should == user_path
|
||||
end
|
||||
end
|
||||
|
||||
@ -184,90 +188,90 @@ describe Guard::Dsl do
|
||||
it "returns the path to the local Guardfile" do
|
||||
File.stub(:exist?).with(local_path).and_return(true)
|
||||
File.stub(:exist?).with(user_path).and_return(true)
|
||||
subject.guardfile_default_path.should == local_path
|
||||
described_class.guardfile_default_path.should == local_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".guardfile_include?" do
|
||||
it "detects a guard specified by a string with double quotes" do
|
||||
subject.stub(:guardfile_contents => 'guard "test" {watch("c")}')
|
||||
described_class.stub(:guardfile_contents => 'guard "test" {watch("c")}')
|
||||
|
||||
subject.guardfile_include?('test').should be_true
|
||||
described_class.guardfile_include?('test').should be_true
|
||||
end
|
||||
|
||||
it "detects a guard specified by a string with single quote" do
|
||||
subject.stub(:guardfile_contents => 'guard \'test\' {watch("c")}')
|
||||
described_class.stub(:guardfile_contents => 'guard \'test\' {watch("c")}')
|
||||
|
||||
subject.guardfile_include?('test').should be_true
|
||||
described_class.guardfile_include?('test').should be_true
|
||||
end
|
||||
|
||||
it "detects a guard specified by a symbol" do
|
||||
subject.stub(:guardfile_contents => 'guard :test {watch("c")}')
|
||||
described_class.stub(:guardfile_contents => 'guard :test {watch("c")}')
|
||||
|
||||
subject.guardfile_include?('test').should be_true
|
||||
described_class.guardfile_include?('test').should be_true
|
||||
end
|
||||
|
||||
it "detects a guard wrapped in parentheses" do
|
||||
subject.stub(:guardfile_contents => 'guard(:test) {watch("c")}')
|
||||
described_class.stub(:guardfile_contents => 'guard(:test) {watch("c")}')
|
||||
|
||||
subject.guardfile_include?('test').should be_true
|
||||
described_class.guardfile_include?('test').should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "#ignore_paths" do
|
||||
disable_user_config
|
||||
|
||||
|
||||
it "adds the paths to the listener's ignore_paths" do
|
||||
::Guard.stub!(:listener).and_return(mock('Listener'))
|
||||
::Guard.listener.should_receive(:ignore_paths).and_return(ignore_paths = ['faz'])
|
||||
|
||||
subject.evaluate_guardfile(:guardfile_contents => "ignore_paths 'foo', 'bar'")
|
||||
|
||||
described_class.evaluate_guardfile(:guardfile_contents => "ignore_paths 'foo', 'bar'")
|
||||
ignore_paths.should == ['faz', 'foo', 'bar']
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "#group" do
|
||||
disable_user_config
|
||||
|
||||
it "evaluates only the specified string group" do
|
||||
::Guard.should_receive(:add_guard).with(:pow, [], { :group => :default })
|
||||
::Guard.should_receive(:add_guard).with(:test, [], { :group => :w })
|
||||
::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
|
||||
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w })
|
||||
|
||||
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => ['w'])
|
||||
described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
|
||||
end
|
||||
|
||||
it "evaluates only the specified symbol group" do
|
||||
::Guard.should_receive(:add_guard).with(:pow, [], { :group => :default })
|
||||
::Guard.should_receive(:add_guard).with(:test, [], { :group => :w })
|
||||
::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
|
||||
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w })
|
||||
|
||||
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
|
||||
described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
|
||||
end
|
||||
|
||||
it "evaluates only the specified groups" do
|
||||
::Guard.should_receive(:add_guard).with(:pow, [], { :group => :default })
|
||||
::Guard.should_receive(:add_guard).with(:rspec, [], { :group => :x })
|
||||
::Guard.should_receive(:add_guard).with(:ronn, [], { :group => :x })
|
||||
::Guard.should_receive(:add_guard).with(:less, [], { :group => :y })
|
||||
it "evaluates only the specified groups (with their options)" do
|
||||
::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
|
||||
::Guard.should_receive(:add_guard).with('rspec', [], [], { :group => :x })
|
||||
::Guard.should_receive(:add_guard).with('ronn', [], [], { :group => :x })
|
||||
::Guard.should_receive(:add_guard).with('less', [], [], { :group => :y })
|
||||
|
||||
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:x, :y])
|
||||
described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:x, :y])
|
||||
end
|
||||
|
||||
it "evaluates always guard outside any group (even when a group is given)" do
|
||||
::Guard.should_receive(:add_guard).with(:pow, [], { :group => :default })
|
||||
::Guard.should_receive(:add_guard).with(:test, [], { :group => :w })
|
||||
::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
|
||||
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w })
|
||||
|
||||
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
|
||||
described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
|
||||
end
|
||||
|
||||
it "evaluates all groups when no group option is specified" do
|
||||
::Guard.should_receive(:add_guard).with(:pow, [], { :group => :default })
|
||||
::Guard.should_receive(:add_guard).with(:test, [], { :group => :w })
|
||||
::Guard.should_receive(:add_guard).with(:rspec, [], { :group => :x })
|
||||
::Guard.should_receive(:add_guard).with(:ronn, [], { :group => :x })
|
||||
::Guard.should_receive(:add_guard).with(:less, [], { :group => :y })
|
||||
it "evaluates all groups when no group option is specified (with their options)" do
|
||||
::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
|
||||
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w })
|
||||
::Guard.should_receive(:add_guard).with('rspec', [], [], { :group => :x })
|
||||
::Guard.should_receive(:add_guard).with('ronn', [], [], { :group => :x })
|
||||
::Guard.should_receive(:add_guard).with('less', [], [], { :group => :y })
|
||||
|
||||
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string)
|
||||
described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string)
|
||||
end
|
||||
end
|
||||
|
||||
@ -275,33 +279,33 @@ describe Guard::Dsl do
|
||||
disable_user_config
|
||||
|
||||
it "loads a guard specified as a quoted string from the DSL" do
|
||||
::Guard.should_receive(:add_guard).with(:test, [], { :group => :default })
|
||||
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default })
|
||||
|
||||
subject.evaluate_guardfile(:guardfile_contents => "guard 'test'")
|
||||
described_class.evaluate_guardfile(:guardfile_contents => "guard 'test'")
|
||||
end
|
||||
|
||||
it "loads a guard specified as a double quoted string from the DSL" do
|
||||
::Guard.should_receive(:add_guard).with(:test, [], { :group => :default })
|
||||
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default })
|
||||
|
||||
subject.evaluate_guardfile(:guardfile_contents => 'guard "test"')
|
||||
described_class.evaluate_guardfile(:guardfile_contents => 'guard "test"')
|
||||
end
|
||||
|
||||
it "loads a guard specified as a symbol from the DSL" do
|
||||
::Guard.should_receive(:add_guard).with(:test, [], { :group => :default })
|
||||
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default })
|
||||
|
||||
subject.evaluate_guardfile(:guardfile_contents => "guard :test")
|
||||
described_class.evaluate_guardfile(:guardfile_contents => "guard :test")
|
||||
end
|
||||
|
||||
it "loads a guard specified as a symbol and called with parens from the DSL" do
|
||||
::Guard.should_receive(:add_guard).with(:test, [], { :group => :default })
|
||||
::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default })
|
||||
|
||||
subject.evaluate_guardfile(:guardfile_contents => "guard(:test)")
|
||||
described_class.evaluate_guardfile(:guardfile_contents => "guard(:test)")
|
||||
end
|
||||
|
||||
it "receives options when specified, from normal arg" do
|
||||
::Guard.should_receive(:add_guard).with(:test, [], { :opt_a => 1, :opt_b => 'fancy', :group => :default })
|
||||
::Guard.should_receive(:add_guard).with('test', [], [], { :opt_a => 1, :opt_b => 'fancy', :group => :default })
|
||||
|
||||
subject.evaluate_guardfile(:guardfile_contents => "guard 'test', :opt_a => 1, :opt_b => 'fancy'")
|
||||
described_class.evaluate_guardfile(:guardfile_contents => "guard 'test', :opt_a => 1, :opt_b => 'fancy'")
|
||||
end
|
||||
end
|
||||
|
||||
@ -309,19 +313,41 @@ describe Guard::Dsl do
|
||||
disable_user_config
|
||||
|
||||
it "should receive watchers when specified" do
|
||||
guardfile_with_watchers = "guard 'test' do
|
||||
watch('a') { 'b' }
|
||||
watch('c')
|
||||
end"
|
||||
|
||||
::Guard.should_receive(:add_guard).with(:test, anything, { :group => :default }) do |name, watchers, options|
|
||||
::Guard.should_receive(:add_guard).with('dummy', anything, anything, { :group => :default }) do |name, watchers, callbacks, options|
|
||||
watchers.size.should == 2
|
||||
watchers[0].pattern.should == 'a'
|
||||
watchers[0].action.call.should == proc { 'b' }.call
|
||||
watchers[1].pattern.should == 'c'
|
||||
watchers[1].action.should == nil
|
||||
end
|
||||
subject.evaluate_guardfile(:guardfile_contents => guardfile_with_watchers)
|
||||
described_class.evaluate_guardfile(:guardfile_contents => "
|
||||
guard :dummy do
|
||||
watch('a') { 'b' }
|
||||
watch('c')
|
||||
end")
|
||||
end
|
||||
end
|
||||
|
||||
describe "#callback" do
|
||||
it "creates callbacks for the guard" do
|
||||
class MyCustomCallback
|
||||
def self.call(guard_class, event, args)
|
||||
# do nothing
|
||||
end
|
||||
end
|
||||
|
||||
::Guard.should_receive(:add_guard).with('dummy', anything, anything, { :group => :default }) do |name, watchers, callbacks, options|
|
||||
callbacks.should have(2).items
|
||||
callbacks[0][:events].should == :start_end
|
||||
callbacks[0][:listener].call(Guard::Dummy, :start_end, 'foo').should == "Guard::Dummy executed 'start_end' hook with foo!"
|
||||
callbacks[1][:events].should == [:start_begin, :run_all_begin]
|
||||
callbacks[1][:listener].should == MyCustomCallback
|
||||
end
|
||||
described_class.evaluate_guardfile(:guardfile_contents => '
|
||||
guard :dummy do
|
||||
callback(:start_end) { |guard_class, event, args| "#{guard_class} executed \'#{event}\' hook with #{args}!" }
|
||||
callback(MyCustomCallback, [:start_begin, :run_all_begin])
|
||||
end')
|
||||
end
|
||||
end
|
||||
|
||||
@ -336,25 +362,21 @@ private
|
||||
"
|
||||
guard :pow
|
||||
|
||||
group 'w' do
|
||||
guard 'test'
|
||||
group :w do
|
||||
guard :test
|
||||
end
|
||||
|
||||
group :x do
|
||||
guard 'rspec'
|
||||
group :x, :halt_on_fail => true do
|
||||
guard :rspec
|
||||
guard :ronn
|
||||
end
|
||||
|
||||
group 'y' do
|
||||
guard 'less'
|
||||
group :y do
|
||||
guard :less
|
||||
end
|
||||
"
|
||||
end
|
||||
|
||||
def mock_guardfile_content(content)
|
||||
File.stub!(:read).with(subject.guardfile_default_path) { content }
|
||||
end
|
||||
|
||||
def invalid_guardfile_string
|
||||
"Bad Guardfile"
|
||||
end
|
||||
|
19
spec/guard/group_spec.rb
Normal file
19
spec/guard/group_spec.rb
Normal file
@ -0,0 +1,19 @@
|
||||
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
|
60
spec/guard/guard_spec.rb
Normal file
60
spec/guard/guard_spec.rb
Normal file
@ -0,0 +1,60 @@
|
||||
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
|
89
spec/guard/hook_spec.rb
Normal file
89
spec/guard/hook_spec.rb
Normal file
@ -0,0 +1,89 @@
|
||||
require 'spec_helper'
|
||||
require 'guard/guard'
|
||||
|
||||
describe Guard::Hook do
|
||||
|
||||
class Guard::Dummy < Guard::Guard; end
|
||||
|
||||
let(:guard_class) { ::Guard::Dummy }
|
||||
let(:listener) { double('listener').as_null_object }
|
||||
|
||||
after { described_class.reset_callbacks! }
|
||||
|
||||
describe "--module methods--" do
|
||||
before { described_class.add_callback(listener, guard_class, :start_begin) }
|
||||
|
||||
describe ".add_callback" do
|
||||
it "can add a single callback" do
|
||||
described_class.has_callback?(listener, guard_class, :start_begin).should be_true
|
||||
end
|
||||
|
||||
it "can add multiple callbacks" do
|
||||
described_class.add_callback(listener, guard_class, [:event1, :event2])
|
||||
described_class.has_callback?(listener, guard_class, :event1).should be_true
|
||||
described_class.has_callback?(listener, guard_class, :event2).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe ".notify" do
|
||||
it "sends :call to the given Guard class's callbacks" do
|
||||
listener.should_receive(:call).with(guard_class, :start_begin, "args")
|
||||
described_class.notify(guard_class, :start_begin, "args")
|
||||
end
|
||||
|
||||
it "runs only the given callbacks" do
|
||||
listener2 = double('listener2')
|
||||
described_class.add_callback(listener2, guard_class, :start_end)
|
||||
listener2.should_not_receive(:call).with(guard_class, :start_end)
|
||||
described_class.notify(guard_class, :start_begin)
|
||||
end
|
||||
|
||||
it "runs callbacks only for the guard given" do
|
||||
guard2_class = double('Guard::Dummy2').class
|
||||
described_class.add_callback(listener, guard2_class, :start_begin)
|
||||
listener.should_not_receive(:call).with(guard2_class, :start_begin)
|
||||
described_class.notify(guard_class, :start_begin)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#hook" do
|
||||
before(:all) do
|
||||
guard_class.class_eval do
|
||||
def start
|
||||
hook "my_hook"
|
||||
end
|
||||
|
||||
def run_all
|
||||
hook :begin
|
||||
hook :end
|
||||
end
|
||||
|
||||
def stop
|
||||
hook :begin, 'args'
|
||||
hook 'special_sauce', 'first_arg', 'second_arg'
|
||||
end
|
||||
end
|
||||
|
||||
@guard = guard_class.new
|
||||
end
|
||||
|
||||
it "calls Guard::Hook.notify" do
|
||||
Guard::Hook.should_receive(:notify).with(guard_class, :run_all_begin)
|
||||
Guard::Hook.should_receive(:notify).with(guard_class, :run_all_end)
|
||||
@guard.run_all
|
||||
end
|
||||
|
||||
it "if passed a string parameter, will use that for the hook name" do
|
||||
Guard::Hook.should_receive(:notify).with(guard_class, :my_hook)
|
||||
@guard.start
|
||||
end
|
||||
|
||||
it "accepts extra args" do
|
||||
Guard::Hook.should_receive(:notify).with(guard_class, :stop_begin, 'args')
|
||||
Guard::Hook.should_receive(:notify).with(guard_class, :special_sauce, 'first_arg', 'second_arg')
|
||||
@guard.stop
|
||||
end
|
||||
end
|
||||
|
||||
end
|
@ -1,34 +1,6 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Guard::Interactor do
|
||||
subject { Guard::Interactor }
|
||||
subject { Guard::Interactor.new }
|
||||
|
||||
let(:guard) { mock "guard" }
|
||||
|
||||
before :each do
|
||||
Guard.stub!(:guards).and_return([guard])
|
||||
Guard.stub!(:options).and_return({})
|
||||
Guard.stub!(:listener).and_return(mock(:start => nil, :stop => nil))
|
||||
end
|
||||
|
||||
describe ".run_all" do
|
||||
it "sends :run_all to all guards" do
|
||||
guard.should_receive(:run_all)
|
||||
subject.run_all
|
||||
end
|
||||
end
|
||||
|
||||
describe ".stop" do
|
||||
it "sends :stop to all guards" do
|
||||
guard.should_receive(:stop)
|
||||
lambda { subject.stop }.should raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".reload" do
|
||||
it "sends :reload to all guards" do
|
||||
guard.should_receive(:reload)
|
||||
subject.reload
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,191 +1,291 @@
|
||||
require 'spec_helper'
|
||||
|
||||
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'] }
|
||||
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'
|
||||
Guard::Darwin.stub(:usable?).and_return(true)
|
||||
Guard::Darwin.should_receive(:new)
|
||||
subject.select_and_init
|
||||
described_class.select_and_init
|
||||
end
|
||||
|
||||
it "uses the Windows listener on Windows" do
|
||||
it 'uses the Windows listener on Windows' do
|
||||
RbConfig::CONFIG['target_os'] = 'mingw'
|
||||
Guard::Windows.stub(:usable?).and_return(true)
|
||||
Guard::Windows.should_receive(:new)
|
||||
subject.select_and_init
|
||||
described_class.select_and_init
|
||||
end
|
||||
|
||||
it "uses the Linux listener on Linux" do
|
||||
it 'uses the Linux listener on Linux' do
|
||||
RbConfig::CONFIG['target_os'] = 'linux'
|
||||
Guard::Linux.stub(:usable?).and_return(true)
|
||||
Guard::Linux.should_receive(:new)
|
||||
subject.select_and_init
|
||||
described_class.select_and_init
|
||||
end
|
||||
|
||||
it "forwards its arguments to the constructor" do
|
||||
subject.stub!(:mac?).and_return(true)
|
||||
it 'forwards its arguments to the constructor' do
|
||||
described_class.stub!(:mac?).and_return(true)
|
||||
Guard::Darwin.stub!(:usable?).and_return(true)
|
||||
|
||||
path, opts = 'path', { :foo => 23 }
|
||||
Guard::Darwin.should_receive(:new).with(path, opts).and_return(true)
|
||||
subject.select_and_init(path, opts)
|
||||
described_class.select_and_init(path, opts)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#all_files" do
|
||||
describe '#all_files' do
|
||||
subject { described_class.new(@fixture_path) }
|
||||
|
||||
it "should return all files" do
|
||||
subject.all_files.should =~ Dir.glob("#{@fixture_path}/**/*", File::FNM_DOTMATCH).select { |file| File.file?(file) }
|
||||
it 'should return all files' do
|
||||
subject.all_files.should =~
|
||||
Dir.glob("#{ @fixture_path }/**/*", File::FNM_DOTMATCH).select { |file| File.file?(file) }
|
||||
end
|
||||
end
|
||||
|
||||
describe "#relativize_paths" do
|
||||
describe '#relativize_paths' do
|
||||
subject { described_class.new('/tmp') }
|
||||
before :each do
|
||||
@paths = %w( /tmp/a /tmp/a/b /tmp/a.b/c.d )
|
||||
|
||||
let(:paths) { %w( /tmp/a /tmp/a/b /tmp/a.b/c.d ) }
|
||||
|
||||
it 'should relativize paths to the configured directory' do
|
||||
subject.relativize_paths(paths).should =~ %w( a a/b a.b/c.d )
|
||||
end
|
||||
|
||||
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
|
||||
context 'when set to false' do
|
||||
subject { described_class.new('/tmp', :relativize_paths => false) }
|
||||
|
||||
it "can be disabled" do
|
||||
subject.relativize_paths(@paths).should eql @paths
|
||||
|
||||
it 'can be disabled' do
|
||||
subject.relativize_paths(paths).should eql paths
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#update_last_event" do
|
||||
describe '#update_last_event' do
|
||||
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
|
||||
subject.update_last_event
|
||||
subject.instance_variable_get(:@last_event).to_i.should >= time.to_i
|
||||
end
|
||||
end
|
||||
|
||||
describe "#modified_files" do
|
||||
describe '#modified_files' do
|
||||
subject { described_class.new }
|
||||
|
||||
let(:file1) { @fixture_path.join("folder1", "file1.txt") }
|
||||
let(:file2) { @fixture_path.join("folder1", "folder2", "file2.txt") }
|
||||
let(:file3) { @fixture_path.join("folder1", "deletedfile1.txt") }
|
||||
let(:file1) { fixture('folder1', 'file1.txt') }
|
||||
let(:file2) { fixture('folder1', 'folder2', 'file2.txt') }
|
||||
let(:file3) { fixture('folder1', 'deletedfile1.txt') }
|
||||
let(:file4) { fixture('folder1', 'movedfile1.txt') }
|
||||
let(:file5) { fixture('folder1', 'folder2', 'movedfile1.txt') }
|
||||
|
||||
before do
|
||||
subject.update_last_event
|
||||
sleep 0.6
|
||||
end
|
||||
before { listen_to subject }
|
||||
|
||||
context "without the :all option" do
|
||||
it "finds modified files only in the directory supplied" do
|
||||
FileUtils.touch([file1, file2, file3])
|
||||
subject.modified_files([@fixture_path.join("folder1")], {}).should =~ ["spec/fixtures/folder1/deletedfile1.txt", "spec/fixtures/folder1/file1.txt"]
|
||||
context 'without the :all option' do
|
||||
it 'finds modified files only in the directory supplied' do
|
||||
watch do
|
||||
FileUtils.touch([file1, file2, file3])
|
||||
subject.modified_files([fixture('folder1')], {}).should =~
|
||||
['spec/fixtures/folder1/deletedfile1.txt', 'spec/fixtures/folder1/file1.txt']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with the :all options" do
|
||||
it "finds modified files within subdirectories" do
|
||||
FileUtils.touch([file1, file2, file3])
|
||||
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"]
|
||||
context 'with the :all options' do
|
||||
it 'finds modified files within subdirectories' do
|
||||
watch do
|
||||
FileUtils.touch([file1, file2, file3])
|
||||
subject.modified_files([fixture('folder1')], { :all => true }).should =~
|
||||
['spec/fixtures/folder1/deletedfile1.txt',
|
||||
'spec/fixtures/folder1/file1.txt',
|
||||
'spec/fixtures/folder1/folder2/file2.txt']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "without updating the content" do
|
||||
it "ignores the files for the second time" do
|
||||
FileUtils.touch([file1, file2, file3])
|
||||
subject.modified_files([@fixture_path.join("folder1")], {}).should =~ ["spec/fixtures/folder1/deletedfile1.txt", "spec/fixtures/folder1/file1.txt"]
|
||||
context 'without updating the content' do
|
||||
it 'ignores the files for the second time' do
|
||||
watch do
|
||||
FileUtils.touch([file1, file2, file3])
|
||||
subject.modified_files([fixture('folder1')], {}).should =~
|
||||
['spec/fixtures/folder1/deletedfile1.txt', 'spec/fixtures/folder1/file1.txt']
|
||||
|
||||
subject.update_last_event
|
||||
|
||||
FileUtils.touch([file1, file2, file3])
|
||||
subject.modified_files([fixture('folder1')], {}).should be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with content that has changed' do
|
||||
after { File.open(file1, 'w') { |f| f.write('') } }
|
||||
|
||||
it 'identifies the files for the second time' do
|
||||
watch do
|
||||
FileUtils.touch([file1, file2, file3])
|
||||
subject.modified_files([fixture('folder1')], {}).should =~
|
||||
['spec/fixtures/folder1/deletedfile1.txt', 'spec/fixtures/folder1/file1.txt']
|
||||
|
||||
subject.update_last_event
|
||||
|
||||
FileUtils.touch([file2, file3])
|
||||
File.open(file1, 'w') { |f| f.write('changed content') }
|
||||
subject.modified_files([fixture('folder1')], {}).should =~
|
||||
['spec/fixtures/folder1/file1.txt']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without the :watch_all_modifications option' do
|
||||
after { FileUtils.touch(file3) }
|
||||
|
||||
it 'defaults to false' do
|
||||
subject.instance_variable_get(:@watch_all_modifications).should eql false
|
||||
end
|
||||
|
||||
context 'for a deleted file' do
|
||||
after { FileUtils.touch(file3) }
|
||||
|
||||
it 'does not catch the deletion' do
|
||||
File.exists?(file3).should be_true
|
||||
|
||||
watch do
|
||||
FileUtils.remove_file(file3)
|
||||
end
|
||||
|
||||
subject.modified_files([fixture('folder1')], {}).should =~ []
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a moved file' do
|
||||
after { FileUtils.move(file4, file1) }
|
||||
|
||||
it 'does not catch the move' do
|
||||
File.exists?(file1).should be_true
|
||||
File.exists?(file4).should be_false
|
||||
|
||||
watch do
|
||||
FileUtils.move(file1, file4)
|
||||
end
|
||||
|
||||
subject.modified_files([@fixture_path.join('folder1')], {}).should =~ []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the :watch_all_modifications option' do
|
||||
subject { described_class.new(Dir.pwd, :watch_all_modifications => true) }
|
||||
|
||||
before do
|
||||
subject.timestamp_files
|
||||
subject.update_last_event
|
||||
FileUtils.touch([file1, file2, file3])
|
||||
subject.modified_files([@fixture_path.join("folder1")], {}).should be_empty
|
||||
sleep 1
|
||||
end
|
||||
end
|
||||
|
||||
context "with content that has changed" do
|
||||
after { File.open(file1, "w") { |f| f.write("") } }
|
||||
it 'should be true when set' do
|
||||
subject.instance_variable_get(:@watch_all_modifications).should eql true
|
||||
end
|
||||
|
||||
it "identifies the files for the second time" do
|
||||
FileUtils.touch([file1, file2, file3])
|
||||
subject.modified_files([@fixture_path.join("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_path.join("folder1")], {}).should =~ ["spec/fixtures/folder1/file1.txt"]
|
||||
sleep 1
|
||||
context 'for a deleted file' do
|
||||
after { FileUtils.touch(file3) }
|
||||
|
||||
it 'catches the deletion' do
|
||||
File.exists?(file3).should be_true
|
||||
|
||||
watch do
|
||||
FileUtils.remove_file(file3)
|
||||
end
|
||||
|
||||
subject.modified_files([fixture('folder1')], {}).should =~
|
||||
['!spec/fixtures/folder1/deletedfile1.txt']
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a moved file' do
|
||||
after { FileUtils.move(file4, file1) }
|
||||
|
||||
it 'catches the move' do
|
||||
File.exists?(file1).should be_true
|
||||
File.exists?(file4).should be_false
|
||||
|
||||
watch do
|
||||
FileUtils.move(file1, file4)
|
||||
end
|
||||
|
||||
subject.modified_files([@fixture_path.join('folder1')], {}).should =~
|
||||
['!spec/fixtures/folder1/file1.txt', 'spec/fixtures/folder1/movedfile1.txt']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "working directory" do
|
||||
|
||||
context "unspecified" do
|
||||
describe 'working directory' do
|
||||
context 'unspecified' do
|
||||
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
|
||||
end
|
||||
it "can be not changed" do
|
||||
|
||||
it 'can be not changed' do
|
||||
subject.should_not respond_to(:directory=)
|
||||
end
|
||||
end
|
||||
|
||||
context "specified as first argument to ::new" do
|
||||
before :each do
|
||||
@wd = @fixture_path.join("folder1")
|
||||
context 'specified as first argument to ::new' do
|
||||
let(:working_directory) { fixture('folder1') }
|
||||
|
||||
subject { described_class.new working_directory }
|
||||
|
||||
before { listen_to subject }
|
||||
|
||||
it 'can be inspected' do
|
||||
subject.instance_variable_get(:@directory).should eql working_directory.to_s
|
||||
end
|
||||
subject { described_class.new @wd }
|
||||
it "can be inspected" do
|
||||
subject.instance_variable_get(:@directory).should eql @wd.to_s
|
||||
end
|
||||
it "can be not changed" do
|
||||
|
||||
it 'can be not changed' do
|
||||
subject.should_not respond_to(:directory=)
|
||||
end
|
||||
|
||||
it "will be used to watch" do
|
||||
subject.should_receive(:watch).with(@wd.to_s)
|
||||
@listener = subject # indeed.
|
||||
it 'will be used to watch' do
|
||||
subject.should_receive(:watch).with(working_directory.to_s)
|
||||
start
|
||||
stop
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "#ignore_paths" do
|
||||
it "defaults to the default ignore paths" do
|
||||
subject.new.ignore_paths.should == Guard::Listener::DefaultIgnorePaths
|
||||
|
||||
describe '#ignore_paths' do
|
||||
it 'defaults to the default ignore paths' do
|
||||
described_class.new.ignore_paths.should == Guard::Listener::DEFAULT_IGNORE_PATHS
|
||||
end
|
||||
|
||||
it "can be added to via :ignore_paths option" do
|
||||
listener = subject.new 'path', :ignore_paths => ['foo', 'bar']
|
||||
|
||||
it 'can be added to via :ignore_paths option' do
|
||||
listener = described_class.new 'path', :ignore_paths => ['foo', 'bar']
|
||||
listener.ignore_paths.should include('foo', 'bar')
|
||||
end
|
||||
end
|
||||
|
||||
describe "#exclude_ignored_paths [<dirs>]" do
|
||||
|
||||
describe '#exclude_ignored_paths [<dirs>]' do
|
||||
let(:ignore_paths) { nil }
|
||||
subject { described_class.new(@fixture_path, {:ignore_paths => ignore_paths}) }
|
||||
|
||||
it "returns children of <dirs>" do
|
||||
subject.exclude_ignored_paths(["spec/fixtures"]).should =~ ["spec/fixtures/.dotfile", "spec/fixtures/folder1", "spec/fixtures/Guardfile"]
|
||||
subject { described_class.new(@fixture_path, { :ignore_paths => ignore_paths }) }
|
||||
|
||||
it 'returns children of <dirs>' do
|
||||
subject.exclude_ignored_paths(['spec/fixtures']).should =~
|
||||
['spec/fixtures/.dotfile', 'spec/fixtures/folder1', 'spec/fixtures/Guardfile']
|
||||
end
|
||||
|
||||
describe "when ignore_paths set to some of <dirs> children" do
|
||||
|
||||
describe 'when ignore_paths set to some of <dirs> children' do
|
||||
let(:ignore_paths) { ['Guardfile', '.dotfile'] }
|
||||
|
||||
it "excludes the ignored paths" do
|
||||
subject.exclude_ignored_paths(["spec/fixtures"]).should =~ ["spec/fixtures/folder1"]
|
||||
|
||||
it 'excludes the ignored paths' do
|
||||
subject.exclude_ignored_paths(['spec/fixtures']).should =~ ['spec/fixtures/folder1']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -2,26 +2,26 @@ require 'spec_helper'
|
||||
require 'guard/listeners/darwin'
|
||||
|
||||
describe Guard::Darwin do
|
||||
subject { Guard::Darwin }
|
||||
|
||||
if windows?
|
||||
it "isn't usable on windows" do
|
||||
subject.should_not be_usable
|
||||
described_class.should_not be_usable
|
||||
end
|
||||
end
|
||||
|
||||
if linux?
|
||||
it "isn't usable on linux" do
|
||||
subject.should_not be_usable
|
||||
described_class.should_not be_usable
|
||||
end
|
||||
end
|
||||
|
||||
if mac? && Guard::Darwin.usable?
|
||||
it "is usable on 10.6" do
|
||||
subject.should be_usable
|
||||
described_class.should be_usable
|
||||
end
|
||||
|
||||
it_should_behave_like "a listener that reacts to #on_change", 0.4
|
||||
it_should_behave_like "a listener scoped to a specific directory", 0.4
|
||||
it_should_behave_like "a listener that reacts to #on_change"
|
||||
it_should_behave_like "a listener scoped to a specific directory"
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -3,23 +3,22 @@ require 'fileutils'
|
||||
require 'guard/listeners/linux'
|
||||
|
||||
describe Guard::Linux do
|
||||
subject { Guard::Linux }
|
||||
|
||||
if mac?
|
||||
it "isn't usable on 10.6" do
|
||||
subject.should_not be_usable
|
||||
described_class.should_not be_usable
|
||||
end
|
||||
end
|
||||
|
||||
if windows?
|
||||
it "isn't usable on windows" do
|
||||
subject.should_not be_usable
|
||||
described_class.should_not be_usable
|
||||
end
|
||||
end
|
||||
|
||||
if linux? && Guard::Linux.usable?
|
||||
it "is usable on linux" do
|
||||
subject.should be_usable
|
||||
described_class.should be_usable
|
||||
end
|
||||
|
||||
describe "#start", :long_running => true do
|
||||
@ -72,6 +71,6 @@ describe Guard::Linux do
|
||||
stop
|
||||
File.open(file, 'w') {|f| f.write('') }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -2,8 +2,8 @@ require 'spec_helper'
|
||||
require 'guard/listeners/polling'
|
||||
|
||||
describe Guard::Polling do
|
||||
subject { Guard::Polling }
|
||||
|
||||
it_should_behave_like "a listener that reacts to #on_change"
|
||||
it_should_behave_like "a listener scoped to a specific directory"
|
||||
|
||||
end
|
||||
|
@ -2,27 +2,26 @@ require 'spec_helper'
|
||||
require 'guard/listeners/windows'
|
||||
|
||||
describe Guard::Windows do
|
||||
subject { Guard::Windows }
|
||||
|
||||
if linux?
|
||||
it "isn't usable on linux" do
|
||||
subject.should_not be_usable
|
||||
described_class.should_not be_usable
|
||||
end
|
||||
end
|
||||
|
||||
if mac?
|
||||
it "isn't usable on Mac" do
|
||||
subject.should_not be_usable
|
||||
described_class.should_not be_usable
|
||||
end
|
||||
end
|
||||
|
||||
if windows?
|
||||
it "is usable on Windows 2000 and later" do
|
||||
subject.should be_usable
|
||||
described_class.should be_usable
|
||||
end
|
||||
|
||||
it_should_behave_like "a listener that reacts to #on_change"
|
||||
it_should_behave_like "a listener scoped to a specific directory"
|
||||
|
||||
it_should_behave_like "a listener that reacts to #on_change"
|
||||
it_should_behave_like "a listener scoped to a specific directory"
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,12 +1,11 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Guard::Notifier do
|
||||
subject { Guard::Notifier }
|
||||
|
||||
describe ".turn_off" do
|
||||
before do
|
||||
ENV["GUARD_NOTIFY"] = 'true'
|
||||
subject.turn_off
|
||||
described_class.turn_off
|
||||
end
|
||||
|
||||
it "disables the notifications" do
|
||||
@ -22,16 +21,27 @@ describe Guard::Notifier do
|
||||
|
||||
context "with the GrowlNotify library available" do
|
||||
before do
|
||||
module ::GrowlNotify
|
||||
class ::GrowlNotify
|
||||
class GrowlNotFound < Exception; end
|
||||
def self.config ; end
|
||||
end
|
||||
end
|
||||
|
||||
it "loads the library and enables the notifications" do
|
||||
subject.should_receive(:require).with('growl_notify').and_return true
|
||||
described_class.should_receive(:require).with('growl_notify').and_return true
|
||||
GrowlNotify.should_receive(:application_name).and_return ''
|
||||
subject.turn_on
|
||||
subject.should be_enabled
|
||||
described_class.turn_on
|
||||
described_class.should be_enabled
|
||||
end
|
||||
|
||||
it "should respond properly to a GrowlNotify exception" do
|
||||
::GrowlNotify.should_receive(:config).and_raise ::GrowlNotify::GrowlNotFound
|
||||
::GrowlNotify.should_receive(:application_name).and_return ''
|
||||
::Guard::UI.should_receive(:info)
|
||||
described_class.should_receive(:require).with('growl_notify').and_return true
|
||||
described_class.turn_on
|
||||
described_class.should_not be_enabled
|
||||
described_class.growl_library.should eql :growl_notify
|
||||
end
|
||||
|
||||
after do
|
||||
@ -39,21 +49,46 @@ describe Guard::Notifier do
|
||||
end
|
||||
end
|
||||
|
||||
context "with the Growl library available" do
|
||||
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
|
||||
subject.should_receive(:require).with('growl_notify').and_raise LoadError
|
||||
subject.should_receive(:require).with('growl').and_return true
|
||||
subject.turn_on
|
||||
subject.should be_enabled
|
||||
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 "without the Growl library available" do
|
||||
context "with the Growl library available" do
|
||||
it "loads the library and enables the notifications" do
|
||||
described_class.should_receive(:require).with('growl_notify').and_raise LoadError
|
||||
described_class.should_receive(:require).with('ruby_gntp').and_raise LoadError
|
||||
described_class.should_receive(:require).with('growl').and_return true
|
||||
described_class.turn_on
|
||||
described_class.should be_enabled
|
||||
described_class.growl_library.should eql :growl
|
||||
end
|
||||
end
|
||||
|
||||
context "without a Growl library available" do
|
||||
it "disables the notifications" do
|
||||
subject.should_receive(:require).with('growl_notify').and_raise LoadError
|
||||
subject.should_receive(:require).with('growl').and_raise LoadError
|
||||
subject.turn_on
|
||||
subject.should_not be_enabled
|
||||
described_class.should_receive(:require).with('growl_notify').and_raise LoadError
|
||||
described_class.should_receive(:require).with('ruby_gntp').and_raise LoadError
|
||||
described_class.should_receive(:require).with('growl').and_raise LoadError
|
||||
described_class.turn_on
|
||||
described_class.should_not be_enabled
|
||||
described_class.growl_library.should be nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -65,17 +100,17 @@ describe Guard::Notifier do
|
||||
|
||||
context "with the Libnotify library available" do
|
||||
it "loads the library and enables the notifications" do
|
||||
subject.should_receive(:require).with('libnotify').and_return true
|
||||
subject.turn_on
|
||||
subject.should be_enabled
|
||||
described_class.should_receive(:require).with('libnotify').and_return true
|
||||
described_class.turn_on
|
||||
described_class.should be_enabled
|
||||
end
|
||||
end
|
||||
|
||||
context "without the Libnotify library available" do
|
||||
it "disables the notifications" do
|
||||
subject.should_receive(:require).with('libnotify').and_raise LoadError
|
||||
subject.turn_on
|
||||
subject.should_not be_enabled
|
||||
described_class.should_receive(:require).with('libnotify').and_raise LoadError
|
||||
described_class.turn_on
|
||||
described_class.should_not be_enabled
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -87,35 +122,36 @@ describe Guard::Notifier do
|
||||
|
||||
context "with the rb-notifu library available" do
|
||||
it "loads the library and enables the notifications" do
|
||||
subject.should_receive(:require).with('rb-notifu').and_return true
|
||||
subject.turn_on
|
||||
subject.should be_enabled
|
||||
described_class.should_receive(:require).with('rb-notifu').and_return true
|
||||
described_class.turn_on
|
||||
described_class.should be_enabled
|
||||
end
|
||||
end
|
||||
|
||||
context "without the rb-notify library available" do
|
||||
it "disables the notifications" do
|
||||
subject.should_receive(:require).with('rb-notifu').and_raise LoadError
|
||||
subject.turn_on
|
||||
subject.should_not be_enabled
|
||||
described_class.should_receive(:require).with('rb-notifu').and_raise LoadError
|
||||
described_class.turn_on
|
||||
described_class.should_not be_enabled
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".notify" do
|
||||
before { subject.stub(:enabled?).and_return(true) }
|
||||
before { described_class.stub(:enabled?).and_return(true) }
|
||||
|
||||
context "on Mac OS" do
|
||||
before do
|
||||
RbConfig::CONFIG.should_receive(:[]).with('target_os').and_return 'darwin'
|
||||
subject.stub(:require_growl)
|
||||
RbConfig::CONFIG.stub(:[]).and_return 'darwin'
|
||||
described_class.stub(:require_growl)
|
||||
end
|
||||
|
||||
context 'with growl gem' do
|
||||
before do
|
||||
Object.send(:remove_const, :Growl) if defined?(Growl)
|
||||
Growl = Object.new
|
||||
described_class.growl_library = :growl
|
||||
end
|
||||
|
||||
after do
|
||||
@ -128,13 +164,14 @@ describe Guard::Notifier do
|
||||
:icon => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
|
||||
:name => "Guard"
|
||||
)
|
||||
subject.notify 'great', :title => 'Guard'
|
||||
described_class.notify 'great', :title => 'Guard'
|
||||
end
|
||||
|
||||
it "don't passes the notification to Growl if library is not available" do
|
||||
Growl.should_not_receive(:notify)
|
||||
subject.should_receive(:enabled?).and_return(true, false)
|
||||
subject.notify 'great', :title => 'Guard'
|
||||
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
|
||||
@ -144,7 +181,7 @@ describe Guard::Notifier do
|
||||
:name => "Guard",
|
||||
:priority => 1
|
||||
)
|
||||
subject.notify 'great', :title => 'Guard', :priority => 1
|
||||
described_class.notify 'great', :title => 'Guard', :priority => 1
|
||||
end
|
||||
|
||||
it "allows to overwrite a default notification option" do
|
||||
@ -153,7 +190,7 @@ describe Guard::Notifier do
|
||||
:icon => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
|
||||
:name => "Guard-Cucumber"
|
||||
)
|
||||
subject.notify 'great', :title => 'Guard', :name => "Guard-Cucumber"
|
||||
described_class.notify 'great', :title => 'Guard', :name => "Guard-Cucumber"
|
||||
end
|
||||
end
|
||||
|
||||
@ -161,6 +198,7 @@ describe Guard::Notifier do
|
||||
before do
|
||||
Object.send(:remove_const, :GrowlNotify) if defined?(GrowlNotify)
|
||||
GrowlNotify = Object.new
|
||||
described_class.growl_library = :growl_notify
|
||||
end
|
||||
|
||||
after do
|
||||
@ -174,13 +212,14 @@ describe Guard::Notifier do
|
||||
:application_name => "Guard",
|
||||
:description => 'great'
|
||||
)
|
||||
subject.notify 'great', :title => 'Guard'
|
||||
described_class.notify 'great', :title => 'Guard'
|
||||
end
|
||||
|
||||
it "don't passes the notification to Growl if library is not available" do
|
||||
GrowlNotify.should_not_receive(:send_notification)
|
||||
subject.should_receive(:enabled?).and_return(true, false)
|
||||
subject.notify 'great', :title => 'Guard'
|
||||
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
|
||||
@ -191,7 +230,7 @@ describe Guard::Notifier do
|
||||
:description => 'great',
|
||||
:priority => 1
|
||||
)
|
||||
subject.notify 'great', :title => 'Guard', :priority => 1
|
||||
described_class.notify 'great', :title => 'Guard', :priority => 1
|
||||
end
|
||||
|
||||
it "throws out the application name since Guard should only use one Growl App Name while running" do
|
||||
@ -201,15 +240,80 @@ describe Guard::Notifier do
|
||||
:application_name => "Guard",
|
||||
:description => 'great'
|
||||
)
|
||||
subject.notify 'great', :title => 'Guard', :name => "Guard-Cucumber"
|
||||
described_class.notify 'great', :title => 'Guard', :name => "Guard-Cucumber"
|
||||
end
|
||||
end
|
||||
|
||||
context 'with ruby_gntp gem' do
|
||||
before do
|
||||
described_class.growl_library = :ruby_gntp
|
||||
described_class.gntp = Object.new
|
||||
end
|
||||
|
||||
it "passes a success notification to Ruby GNTP" do
|
||||
described_class.gntp.should_receive(:notify).with(
|
||||
:name => "success",
|
||||
:text => 'great',
|
||||
:title => "Guard",
|
||||
:icon => 'file://' + Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s
|
||||
)
|
||||
described_class.notify 'great', :title => 'Guard'
|
||||
end
|
||||
|
||||
it "passes a pending notification to Ruby GNTP" do
|
||||
described_class.gntp.should_receive(:notify).with(
|
||||
:name => "pending",
|
||||
:text => 'great',
|
||||
:title => "Guard",
|
||||
:icon => 'file://' + Pathname.new(File.dirname(__FILE__)).join('../../images/pending.png').to_s
|
||||
)
|
||||
described_class.notify 'great', :title => 'Guard', :image => :pending
|
||||
end
|
||||
|
||||
it "passes a failure notification to Ruby GNTP" do
|
||||
described_class.gntp.should_receive(:notify).with(
|
||||
:name => "failed",
|
||||
:text => 'great',
|
||||
:title => "Guard",
|
||||
:icon => 'file://' + Pathname.new(File.dirname(__FILE__)).join('../../images/failed.png').to_s
|
||||
)
|
||||
described_class.notify 'great', :title => 'Guard', :image => :failed
|
||||
end
|
||||
|
||||
it "passes a general notification to Ruby GNTP" do
|
||||
described_class.gntp.should_receive(:notify).with(
|
||||
:name => "notify",
|
||||
:text => 'great',
|
||||
:title => "Guard",
|
||||
:icon => 'file:///path/to/custom.png'
|
||||
)
|
||||
described_class.notify 'great', :title => 'Guard', :image => '/path/to/custom.png'
|
||||
end
|
||||
|
||||
it "don't passes the notification to Ruby GNTP if library is not available" do
|
||||
described_class.gntp.should_not_receive(:notify)
|
||||
described_class.growl_library = nil
|
||||
described_class.should_receive(:enabled?).and_return(false)
|
||||
described_class.notify 'great', :title => 'Guard'
|
||||
end
|
||||
|
||||
it "allows additional notification options" do
|
||||
described_class.gntp.should_receive(:notify).with(
|
||||
:name => "success",
|
||||
:text => 'great',
|
||||
:title => "Guard",
|
||||
:icon => 'file://' + Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
|
||||
:sticky => true
|
||||
)
|
||||
described_class.notify 'great', :title => 'Guard', :sticky => true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "on Linux" do
|
||||
before do
|
||||
RbConfig::CONFIG.should_receive(:[]).with('target_os').and_return 'linux'
|
||||
subject.stub(:require_libnotify)
|
||||
RbConfig::CONFIG.stub(:[]).and_return 'linux'
|
||||
described_class.stub(:require_libnotify)
|
||||
Object.send(:remove_const, :Libnotify) if defined?(Libnotify)
|
||||
Libnotify = Object.new
|
||||
end
|
||||
@ -225,13 +329,13 @@ describe Guard::Notifier do
|
||||
:icon_path => Pathname.new(File.dirname(__FILE__)).join('../../images/success.png').to_s,
|
||||
:transient => true
|
||||
)
|
||||
subject.notify 'great', :title => 'Guard'
|
||||
described_class.notify 'great', :title => 'Guard'
|
||||
end
|
||||
|
||||
it "don't passes the notification to Libnotify if library is not available" do
|
||||
Libnotify.should_not_receive(:show)
|
||||
subject.should_receive(:enabled?).and_return(true, false)
|
||||
subject.notify 'great', :title => 'Guard'
|
||||
described_class.should_receive(:enabled?).and_return(false)
|
||||
described_class.notify 'great', :title => 'Guard'
|
||||
end
|
||||
|
||||
it "allows additional notification options" do
|
||||
@ -242,7 +346,7 @@ describe Guard::Notifier do
|
||||
:transient => true,
|
||||
:urgency => :critical
|
||||
)
|
||||
subject.notify 'great', :title => 'Guard', :urgency => :critical
|
||||
described_class.notify 'great', :title => 'Guard', :urgency => :critical
|
||||
end
|
||||
|
||||
it "allows to overwrite a default notification option" do
|
||||
@ -252,14 +356,14 @@ describe Guard::Notifier do
|
||||
:icon_path => '~/.guard/success.png',
|
||||
:transient => true
|
||||
)
|
||||
subject.notify 'great', :title => 'Guard', :icon_path => '~/.guard/success.png'
|
||||
described_class.notify 'great', :title => 'Guard', :icon_path => '~/.guard/success.png'
|
||||
end
|
||||
end
|
||||
|
||||
context "on Windows" do
|
||||
before do
|
||||
RbConfig::CONFIG.should_receive(:[]).with('target_os').and_return 'mswin'
|
||||
subject.stub(:require_rbnotifu)
|
||||
RbConfig::CONFIG.stub(:[]).and_return 'mswin'
|
||||
described_class.stub(:require_rbnotifu)
|
||||
Object.send(:remove_const, :Notifu) if defined?(Notifu)
|
||||
Notifu = Object.new
|
||||
end
|
||||
@ -275,13 +379,13 @@ describe Guard::Notifier do
|
||||
:type => :info,
|
||||
:time => 3
|
||||
)
|
||||
subject.notify 'great', :title => 'Guard'
|
||||
described_class.notify 'great', :title => 'Guard'
|
||||
end
|
||||
|
||||
it "don't passes the notification to rb-notifu if library is not available" do
|
||||
Notifu.should_not_receive(:show)
|
||||
subject.should_receive(:enabled?).and_return(true, false)
|
||||
subject.notify 'great', :title => 'Guard'
|
||||
described_class.should_receive(:enabled?).and_return(false)
|
||||
described_class.notify 'great', :title => 'Guard'
|
||||
end
|
||||
|
||||
it "allows additional notification options" do
|
||||
@ -292,7 +396,7 @@ describe Guard::Notifier do
|
||||
:time => 3,
|
||||
:nosound => true
|
||||
)
|
||||
subject.notify 'great', :title => 'Guard', :nosound => true
|
||||
described_class.notify 'great', :title => 'Guard', :nosound => true
|
||||
end
|
||||
|
||||
it "allows to overwrite a default notification option" do
|
||||
@ -302,7 +406,7 @@ describe Guard::Notifier do
|
||||
:type => :info,
|
||||
:time => 10
|
||||
)
|
||||
subject.notify 'great', :title => 'Guard', :time => 10
|
||||
described_class.notify 'great', :title => 'Guard', :time => 10
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -320,4 +424,5 @@ describe Guard::Notifier do
|
||||
it { should_not be_enabled }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -5,19 +5,19 @@ describe Guard::Watcher do
|
||||
|
||||
describe "#initialize" do
|
||||
it "requires a pattern parameter" do
|
||||
expect { Guard::Watcher.new }.to raise_error(ArgumentError)
|
||||
expect { described_class.new }.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
context "with a pattern parameter" do
|
||||
context "that is a string" do
|
||||
it "keeps the string pattern unmodified" do
|
||||
Guard::Watcher.new('spec_helper.rb').pattern.should == 'spec_helper.rb'
|
||||
described_class.new('spec_helper.rb').pattern.should == 'spec_helper.rb'
|
||||
end
|
||||
end
|
||||
|
||||
context "that is a regexp" do
|
||||
it "keeps the regex pattern unmodified" do
|
||||
Guard::Watcher.new(/spec_helper\.rb/).pattern.should == /spec_helper\.rb/
|
||||
described_class.new(/spec_helper\.rb/).pattern.should == /spec_helper\.rb/
|
||||
end
|
||||
end
|
||||
|
||||
@ -25,10 +25,10 @@ describe Guard::Watcher do
|
||||
before(:each) { Guard::UI.should_receive(:info).any_number_of_times }
|
||||
|
||||
it "converts the string automatically to a regex" do
|
||||
Guard::Watcher.new('^spec_helper.rb').pattern.should == /^spec_helper.rb/
|
||||
Guard::Watcher.new('spec_helper.rb$').pattern.should == /spec_helper.rb$/
|
||||
Guard::Watcher.new('spec_helper\.rb').pattern.should == /spec_helper\.rb/
|
||||
Guard::Watcher.new('.*_spec.rb').pattern.should == /.*_spec.rb/
|
||||
described_class.new('^spec_helper.rb').pattern.should == /^spec_helper.rb/
|
||||
described_class.new('spec_helper.rb$').pattern.should == /spec_helper.rb$/
|
||||
described_class.new('spec_helper\.rb').pattern.should == /spec_helper\.rb/
|
||||
described_class.new('.*_spec.rb').pattern.should == /.*_spec.rb/
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -36,144 +36,228 @@ describe Guard::Watcher do
|
||||
|
||||
describe "#action" do
|
||||
it "sets the action to nothing by default" do
|
||||
Guard::Watcher.new(/spec_helper\.rb/).action.should be_nil
|
||||
described_class.new(/spec_helper\.rb/).action.should be_nil
|
||||
end
|
||||
|
||||
it "sets the action to the supplied block" do
|
||||
action = lambda { |m| "spec/#{m[1]}_spec.rb" }
|
||||
Guard::Watcher.new(%r{^lib/(.*).rb}, action).action.should == action
|
||||
described_class.new(%r{^lib/(.*).rb}, action).action.should == action
|
||||
end
|
||||
end
|
||||
|
||||
describe ".match_files" do
|
||||
before(:all) { @guard = Guard::Guard.new }
|
||||
before(:all) do
|
||||
@guard = Guard::Guard.new
|
||||
@guard_any_return = Guard::Guard.new
|
||||
@guard_any_return.options[:any_return] = true
|
||||
end
|
||||
|
||||
context "with a watcher without action" do
|
||||
context "that is a regex pattern" do
|
||||
before(:all) { @guard.watchers = [Guard::Watcher.new(/.*_spec\.rb/)] }
|
||||
before(:all) { @guard.watchers = [described_class.new(/.*_spec\.rb/)] }
|
||||
|
||||
it "returns the paths that matches the regex" do
|
||||
Guard::Watcher.match_files(@guard, ['guard_rocks_spec.rb', 'guard_rocks.rb']).should == ['guard_rocks_spec.rb']
|
||||
described_class.match_files(@guard, ['guard_rocks_spec.rb', 'guard_rocks.rb']).should == ['guard_rocks_spec.rb']
|
||||
end
|
||||
end
|
||||
|
||||
context "that is a string pattern" do
|
||||
before(:all) { @guard.watchers = [Guard::Watcher.new('guard_rocks_spec.rb')] }
|
||||
before(:all) { @guard.watchers = [described_class.new('guard_rocks_spec.rb')] }
|
||||
|
||||
it "returns the path that matches the string" do
|
||||
Guard::Watcher.match_files(@guard, ['guard_rocks_spec.rb', 'guard_rocks.rb']).should == ['guard_rocks_spec.rb']
|
||||
described_class.match_files(@guard, ['guard_rocks_spec.rb', 'guard_rocks.rb']).should == ['guard_rocks_spec.rb']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with a watcher action without parameter" do
|
||||
before(:all) do
|
||||
@guard.watchers = [
|
||||
Guard::Watcher.new('spec_helper.rb', lambda { 'spec' }),
|
||||
Guard::Watcher.new('addition.rb', lambda { 1 + 1 }),
|
||||
Guard::Watcher.new('hash.rb', lambda { Hash[:foo, 'bar'] }),
|
||||
Guard::Watcher.new('array.rb', lambda { ['foo', 'bar'] }),
|
||||
Guard::Watcher.new('blank.rb', lambda { '' }),
|
||||
Guard::Watcher.new(/^uptime\.rb/, lambda { `uptime > /dev/null` })
|
||||
]
|
||||
context "for a watcher that matches file strings" do
|
||||
before(:all) do
|
||||
@guard.watchers = [
|
||||
described_class.new('spec_helper.rb', lambda { 'spec' }),
|
||||
described_class.new('addition.rb', lambda { 1 + 1 }),
|
||||
described_class.new('hash.rb', lambda { Hash[:foo, 'bar'] }),
|
||||
described_class.new('array.rb', lambda { ['foo', 'bar'] }),
|
||||
described_class.new('blank.rb', lambda { '' }),
|
||||
described_class.new(/^uptime\.rb/, lambda { `uptime > /dev/null` })
|
||||
]
|
||||
end
|
||||
|
||||
it "returns a single file specified within the action" do
|
||||
described_class.match_files(@guard, ['spec_helper.rb']).should == ['spec']
|
||||
end
|
||||
|
||||
it "returns multiple files specified within the action" do
|
||||
described_class.match_files(@guard, ['hash.rb']).should == ['foo', 'bar']
|
||||
end
|
||||
|
||||
it "returns multiple files by combining the results of different actions" do
|
||||
described_class.match_files(@guard, ['spec_helper.rb', 'array.rb']).should == ['spec', 'foo', 'bar']
|
||||
end
|
||||
|
||||
it "returns nothing if the action returns something other than a string or an array of strings" do
|
||||
described_class.match_files(@guard, ['addition.rb']).should == []
|
||||
end
|
||||
|
||||
it "returns nothing if the action response is empty" do
|
||||
described_class.match_files(@guard, ['blank.rb']).should == []
|
||||
end
|
||||
|
||||
it "returns nothing if the action returns nothing" do
|
||||
described_class.match_files(@guard, ['uptime.rb']).should == []
|
||||
end
|
||||
end
|
||||
|
||||
it "returns a single file specified within the action" do
|
||||
Guard::Watcher.match_files(@guard, ['spec_helper.rb']).should == ['spec']
|
||||
end
|
||||
context 'for a watcher that matches information objects' do
|
||||
before(:all) do
|
||||
@guard_any_return.watchers = [
|
||||
described_class.new('spec_helper.rb', lambda { 'spec' }),
|
||||
described_class.new('addition.rb', lambda { 1 + 1 }),
|
||||
described_class.new('hash.rb', lambda { Hash[:foo, 'bar'] }),
|
||||
described_class.new('array.rb', lambda { ['foo', 'bar'] }),
|
||||
described_class.new('blank.rb', lambda { '' }),
|
||||
described_class.new(/^uptime\.rb/, lambda { `uptime > /dev/null` })
|
||||
]
|
||||
end
|
||||
|
||||
it "returns multiple files specified within the action" do
|
||||
Guard::Watcher.match_files(@guard, ['hash.rb']).should == ['foo', 'bar']
|
||||
end
|
||||
it "returns a single file specified within the action" do
|
||||
described_class.match_files(@guard_any_return, ['spec_helper.rb']).class.should == Array
|
||||
described_class.match_files(@guard_any_return, ['spec_helper.rb']).empty?.should == false
|
||||
end
|
||||
|
||||
it "returns multiple files by combining the results of different actions" do
|
||||
Guard::Watcher.match_files(@guard, ['spec_helper.rb', 'array.rb']).should == ['spec', 'foo', 'bar']
|
||||
end
|
||||
it "returns multiple files specified within the action" do
|
||||
described_class.match_files(@guard_any_return, ['hash.rb']).should == [{:foo => 'bar'}]
|
||||
end
|
||||
|
||||
it "returns nothing if the action returns something other than a string or an array of strings" do
|
||||
Guard::Watcher.match_files(@guard, ['addition.rb']).should == []
|
||||
end
|
||||
it "returns multiple files by combining the results of different actions" do
|
||||
described_class.match_files(@guard_any_return, ['spec_helper.rb', 'array.rb']).should == ['spec', ['foo', 'bar']]
|
||||
end
|
||||
|
||||
it "returns nothing if the action response is empty" do
|
||||
Guard::Watcher.match_files(@guard, ['blank.rb']).should == []
|
||||
end
|
||||
it "returns the evaluated addition argument in an array" do
|
||||
described_class.match_files(@guard_any_return, ['addition.rb']).class.should == Array
|
||||
described_class.match_files(@guard_any_return, ['addition.rb'])[0].should == 2
|
||||
end
|
||||
|
||||
it "returns nothing if the action returns nothing" do
|
||||
Guard::Watcher.match_files(@guard, ['uptime.rb']).should == []
|
||||
it "returns nothing if the action response is empty string" do
|
||||
described_class.match_files(@guard_any_return, ['blank.rb']).should == ['']
|
||||
end
|
||||
|
||||
it "returns nothing if the action returns empty string" do
|
||||
described_class.match_files(@guard_any_return, ['uptime.rb']).should == ['']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with a watcher action that takes a parameter" do
|
||||
before(:all) do
|
||||
@guard.watchers = [
|
||||
Guard::Watcher.new(%r{lib/(.*)\.rb}, lambda { |m| "spec/#{m[1]}_spec.rb" }),
|
||||
Guard::Watcher.new(/addition(.*)\.rb/, lambda { |m| 1 + 1 }),
|
||||
Guard::Watcher.new('hash.rb', lambda { Hash[:foo, 'bar'] }),
|
||||
Guard::Watcher.new(/array(.*)\.rb/, lambda { |m| ['foo', 'bar'] }),
|
||||
Guard::Watcher.new(/blank(.*)\.rb/, lambda { |m| '' }),
|
||||
Guard::Watcher.new(/uptime(.*)\.rb/, lambda { |m| `uptime > /dev/null` })
|
||||
]
|
||||
context "for a watcher that matches file strings" do
|
||||
before(:all) do
|
||||
@guard.watchers = [
|
||||
described_class.new(%r{lib/(.*)\.rb}, lambda { |m| "spec/#{m[1]}_spec.rb" }),
|
||||
described_class.new(/addition(.*)\.rb/, lambda { |m| 1 + 1 }),
|
||||
described_class.new('hash.rb', lambda { |m| Hash[:foo, 'bar'] }),
|
||||
described_class.new(/array(.*)\.rb/, lambda { |m| ['foo', 'bar'] }),
|
||||
described_class.new(/blank(.*)\.rb/, lambda { |m| '' }),
|
||||
described_class.new(/uptime(.*)\.rb/, lambda { |m| `uptime > /dev/null` })
|
||||
]
|
||||
end
|
||||
|
||||
it "returns a substituted single file specified within the action" do
|
||||
described_class.match_files(@guard, ['lib/my_wonderful_lib.rb']).should == ['spec/my_wonderful_lib_spec.rb']
|
||||
end
|
||||
|
||||
it "returns multiple files specified within the action" do
|
||||
described_class.match_files(@guard, ['hash.rb']).should == ['foo', 'bar']
|
||||
end
|
||||
|
||||
it "returns multiple files by combining the results of different actions" do
|
||||
described_class.match_files(@guard, ['lib/my_wonderful_lib.rb', 'array.rb']).should == ['spec/my_wonderful_lib_spec.rb', 'foo', 'bar']
|
||||
end
|
||||
|
||||
it "returns nothing if the action returns something other than a string or an array of strings" do
|
||||
described_class.match_files(@guard, ['addition.rb']).should == []
|
||||
end
|
||||
|
||||
it "returns nothing if the action response is empty" do
|
||||
described_class.match_files(@guard, ['blank.rb']).should == []
|
||||
end
|
||||
|
||||
it "returns nothing if the action returns nothing" do
|
||||
described_class.match_files(@guard, ['uptime.rb']).should == []
|
||||
end
|
||||
end
|
||||
|
||||
it "returns a substituted single file specified within the action" do
|
||||
Guard::Watcher.match_files(@guard, ['lib/my_wonderful_lib.rb']).should == ['spec/my_wonderful_lib_spec.rb']
|
||||
end
|
||||
context "for a watcher that matches information objects" do
|
||||
before(:all) do
|
||||
@guard_any_return.watchers = [
|
||||
described_class.new(%r{lib/(.*)\.rb}, lambda { |m| "spec/#{m[1]}_spec.rb" }),
|
||||
described_class.new(/addition(.*)\.rb/, lambda { |m| (1 + 1).to_s + m[0] }),
|
||||
described_class.new('hash.rb', lambda { |m| Hash[:foo, 'bar', :file_name, m[0]] }),
|
||||
described_class.new(/array(.*)\.rb/, lambda { |m| ['foo', 'bar', m[0]] }),
|
||||
described_class.new(/blank(.*)\.rb/, lambda { |m| '' }),
|
||||
described_class.new(/uptime(.*)\.rb/, lambda { |m| `uptime > /dev/null` })
|
||||
]
|
||||
end
|
||||
|
||||
it "returns multiple files specified within the action" do
|
||||
Guard::Watcher.match_files(@guard, ['hash.rb']).should == ['foo', 'bar']
|
||||
end
|
||||
it "returns a substituted single file specified within the action" do
|
||||
described_class.match_files(@guard_any_return, ['lib/my_wonderful_lib.rb']).should == ['spec/my_wonderful_lib_spec.rb']
|
||||
end
|
||||
|
||||
it "returns multiple files by combining the results of different actions" do
|
||||
Guard::Watcher.match_files(@guard, ['lib/my_wonderful_lib.rb', 'array.rb']).should == ['spec/my_wonderful_lib_spec.rb', 'foo', 'bar']
|
||||
end
|
||||
it "returns a hash specified within the action" do
|
||||
described_class.match_files(@guard_any_return, ['hash.rb']).should == [{:foo => 'bar', :file_name => 'hash.rb'}]
|
||||
end
|
||||
|
||||
it "returns nothing if the action returns something other than a string or an array of strings" do
|
||||
Guard::Watcher.match_files(@guard, ['addition.rb']).should == []
|
||||
end
|
||||
it "returns multiple files by combining the results of different actions" do
|
||||
described_class.match_files(@guard_any_return, ['lib/my_wonderful_lib.rb', 'array.rb']).should == ['spec/my_wonderful_lib_spec.rb', ['foo', 'bar', "array.rb"]]
|
||||
end
|
||||
|
||||
it "returns nothing if the action response is empty" do
|
||||
Guard::Watcher.match_files(@guard, ['blank.rb']).should == []
|
||||
end
|
||||
it "returns the evaluated addition argument + the path" do
|
||||
described_class.match_files(@guard_any_return, ['addition.rb']).should == ["2addition.rb"]
|
||||
end
|
||||
|
||||
it "returns nothing if the action returns nothing" do
|
||||
Guard::Watcher.match_files(@guard, ['uptime.rb']).should == []
|
||||
it "returns nothing if the action response is empty string" do
|
||||
described_class.match_files(@guard_any_return, ['blank.rb']).should == ['']
|
||||
end
|
||||
|
||||
it "returns nothing if the action returns empty string" do
|
||||
described_class.match_files(@guard_any_return, ['uptime.rb']).should == ['']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with an exception that is raised" do
|
||||
before(:all) { @guard.watchers = [Guard::Watcher.new('evil.rb', lambda { raise "EVIL" })] }
|
||||
before(:all) { @guard.watchers = [described_class.new('evil.rb', lambda { raise "EVIL" })] }
|
||||
|
||||
it "displays the error and backtrace" do
|
||||
Guard::UI.should_receive(:error) { |msg|
|
||||
msg.should include("Problem with watch action!")
|
||||
msg.should include("EVIL")
|
||||
}
|
||||
it "displays the error and backtrace" do
|
||||
Guard::UI.should_receive(:error) do |msg|
|
||||
msg.should include("Problem with watch action!")
|
||||
msg.should include("EVIL")
|
||||
end
|
||||
|
||||
Guard::Watcher.match_files(@guard, ['evil.rb'])
|
||||
end
|
||||
end
|
||||
end
|
||||
described_class.match_files(@guard, ['evil.rb'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".match_files?" do
|
||||
before(:all) do
|
||||
@guard1 = Guard::Guard.new([Guard::Watcher.new(/.*_spec\.rb/)])
|
||||
@guard2 = Guard::Guard.new([Guard::Watcher.new('spec_helper.rb', 'spec')])
|
||||
@guard1 = Guard::Guard.new([described_class.new(/.*_spec\.rb/)])
|
||||
@guard2 = Guard::Guard.new([described_class.new('spec_helper.rb', 'spec')])
|
||||
@guards = [@guard1, @guard2]
|
||||
end
|
||||
|
||||
context "with a watcher that matches a file" do
|
||||
specify { Guard::Watcher.match_files?(@guards, ['lib/my_wonderful_lib.rb', 'guard_rocks_spec.rb']).should be_true }
|
||||
specify { described_class.match_files?(@guards, ['lib/my_wonderful_lib.rb', 'guard_rocks_spec.rb']).should be_true }
|
||||
end
|
||||
|
||||
context "with no watcher that matches a file" do
|
||||
specify { Guard::Watcher.match_files?(@guards, ['lib/my_wonderful_lib.rb']).should be_false }
|
||||
specify { described_class.match_files?(@guards, ['lib/my_wonderful_lib.rb']).should be_false }
|
||||
end
|
||||
end
|
||||
|
||||
describe "#match_file?" do
|
||||
context "with a string pattern" do
|
||||
context "that is a normal string" do
|
||||
subject { Guard::Watcher.new('guard_rocks_spec.rb') }
|
||||
subject { described_class.new('guard_rocks_spec.rb') }
|
||||
|
||||
context "with a watcher that matches a file" do
|
||||
specify { subject.match_file?('guard_rocks_spec.rb').should be_true }
|
||||
@ -185,7 +269,7 @@ describe Guard::Watcher do
|
||||
end
|
||||
|
||||
context "that is a string representing a regexp (deprecated)" do
|
||||
subject { Guard::Watcher.new('^guard_rocks_spec\.rb$') }
|
||||
subject { described_class.new('^guard_rocks_spec\.rb$') }
|
||||
|
||||
context "with a watcher that matches a file" do
|
||||
specify { subject.match_file?('guard_rocks_spec.rb').should be_true }
|
||||
@ -198,7 +282,7 @@ describe Guard::Watcher do
|
||||
end
|
||||
|
||||
context "that is a regexp pattern" do
|
||||
subject { Guard::Watcher.new(/.*_spec\.rb/) }
|
||||
subject { described_class.new(/.*_spec\.rb/) }
|
||||
|
||||
context "with a watcher that matches a file" do
|
||||
specify { subject.match_file?('guard_rocks_spec.rb').should be_true }
|
||||
@ -214,11 +298,11 @@ describe Guard::Watcher do
|
||||
before(:all) { Guard::Dsl.stub(:guardfile_path) { Dir.pwd + '/Guardfile' } }
|
||||
|
||||
context "with files that match the Guardfile" do
|
||||
specify { Guard::Watcher.match_guardfile?(['Guardfile', 'guard_rocks_spec.rb']).should be_true }
|
||||
specify { described_class.match_guardfile?(['Guardfile', 'guard_rocks_spec.rb']).should be_true }
|
||||
end
|
||||
|
||||
context "with no files that match the Guardfile" do
|
||||
specify { Guard::Watcher.match_guardfile?(['guard_rocks.rb', 'guard_rocks_spec.rb']).should be_false }
|
||||
specify { described_class.match_guardfile?(['guard_rocks.rb', 'guard_rocks_spec.rb']).should be_false }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -3,6 +3,45 @@ require 'guard/guard'
|
||||
|
||||
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
|
||||
subject { ::Guard.setup }
|
||||
|
||||
@ -15,7 +54,8 @@ describe Guard do
|
||||
end
|
||||
|
||||
it "initializes @groups" do
|
||||
Guard.groups.should eql [:default]
|
||||
described_class.groups[0].name.should eql :default
|
||||
described_class.groups[0].options.should == {}
|
||||
end
|
||||
|
||||
it "initializes the options" do
|
||||
@ -53,6 +93,129 @@ describe Guard do
|
||||
::Guard.should_receive(:debug_command_execution)
|
||||
::Guard.setup(:debug => true)
|
||||
end
|
||||
|
||||
it "initializes the interactor" do
|
||||
::Guard.setup
|
||||
::Guard.interactor.should be_kind_of(Guard::Interactor)
|
||||
end
|
||||
|
||||
it "skips the interactor initalization if no-interactions is true" do
|
||||
::Guard.interactor = nil
|
||||
::Guard.setup(:no_interactions => true)
|
||||
::Guard.interactor.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
describe ".start" do
|
||||
@ -116,7 +279,7 @@ describe Guard do
|
||||
it "gives an empty hash of options" do
|
||||
@guard_rspec_class.should_receive(:new).with([], {}).and_return(@guard_rspec)
|
||||
|
||||
Guard.add_guard(:rspec, [], {})
|
||||
Guard.add_guard(:rspec, [], [], {})
|
||||
end
|
||||
end
|
||||
|
||||
@ -124,27 +287,33 @@ describe Guard do
|
||||
it "give the options hash" do
|
||||
@guard_rspec_class.should_receive(:new).with([], { :foo => true, :group => :backend }).and_return(@guard_rspec)
|
||||
|
||||
Guard.add_guard(:rspec, [], { :foo => true, :group => :backend })
|
||||
Guard.add_guard(:rspec, [], [], { :foo => true, :group => :backend })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe ".add_group" do
|
||||
before(:each) do
|
||||
Guard.setup
|
||||
end
|
||||
subject { ::Guard.setup }
|
||||
|
||||
it "accepts group name as string" do
|
||||
Guard.add_group('backend')
|
||||
subject.add_group('backend')
|
||||
|
||||
Guard.groups.should eql [:default, :backend]
|
||||
subject.groups[0].name.should eql :default
|
||||
subject.groups[1].name.should eql :backend
|
||||
end
|
||||
|
||||
it "accepts group name as symbol" do
|
||||
Guard.add_group(:backend)
|
||||
subject.add_group(:backend)
|
||||
|
||||
Guard.groups.should eql [:default, :backend]
|
||||
subject.groups[0].name.should eql :default
|
||||
subject.groups[1].name.should eql :backend
|
||||
end
|
||||
|
||||
it "accepts options" do
|
||||
subject.add_group(:backend, { :halt_on_fail => true })
|
||||
|
||||
subject.groups[0].options.should == {}
|
||||
subject.groups[1].options.should == { :halt_on_fail => true }
|
||||
end
|
||||
end
|
||||
|
||||
@ -223,26 +392,127 @@ describe Guard do
|
||||
end
|
||||
end
|
||||
|
||||
describe ".supervised_task" do
|
||||
describe ".run_guard_task" do
|
||||
subject { ::Guard.setup }
|
||||
|
||||
before(:each) do
|
||||
before do
|
||||
class Guard::Dummy < Guard::Guard; end
|
||||
|
||||
subject.add_group(:foo, { :halt_on_fail => true })
|
||||
subject.add_group(:bar)
|
||||
subject.add_guard(:dummy, [], [], { :group => :foo })
|
||||
subject.add_guard(:dummy, [], [], { :group => :foo })
|
||||
subject.add_guard(:dummy, [], [], { :group => :bar })
|
||||
subject.add_guard(:dummy, [], [], { :group => :bar })
|
||||
@sum = { :foo => 0, :bar => 0}
|
||||
end
|
||||
|
||||
context "all tasks succeed" do
|
||||
before do
|
||||
subject.guards.each { |guard| guard.stub!(:task) { @sum[guard.group] += 1; true } }
|
||||
end
|
||||
|
||||
it "executes the task for each guard in each group" do
|
||||
subject.run_guard_task(:task)
|
||||
|
||||
@sum.all? { |k, v| v == 2 }.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
context "one guard fails" do
|
||||
before do
|
||||
subject.guards.each_with_index do |g, i|
|
||||
g.stub!(:task) do
|
||||
@sum[g.group] += i+1
|
||||
if i % 2 == 0
|
||||
throw :task_has_failed
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "executes the task only for guards that didn't fail for group with :halt_on_fail == true" do
|
||||
subject.run_guard_task(:task)
|
||||
|
||||
@sum[:foo].should eql 1
|
||||
@sum[:bar].should eql 7
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".run_on_change_task" do
|
||||
let(:guard) do
|
||||
class Guard::Dummy < Guard::Guard
|
||||
def watchers
|
||||
[Guard::Watcher.new(/.+\.rb/)]
|
||||
end
|
||||
end
|
||||
|
||||
Guard::Dummy.new
|
||||
end
|
||||
|
||||
it 'runs the :run_on_change task with the watched file changes' do
|
||||
Guard.should_receive(:run_supervised_task).with(guard, :run_on_change, ['a.rb', 'b.rb'])
|
||||
Guard.run_on_change_task(['a.rb', 'b.rb', 'templates/d.haml'], guard, :run_on_change)
|
||||
end
|
||||
|
||||
it 'runs the :run_on_deletion task with the watched file deletions' do
|
||||
Guard.should_receive(:run_supervised_task).with(guard, :run_on_deletion, ['c.rb'])
|
||||
Guard.run_on_change_task(['!c.rb', '!templates/e.haml'], guard, :run_on_change)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".changed_paths" do
|
||||
let(:paths) { ['a.rb', 'b.rb', '!c.rb', 'templates/d.haml', '!templates/e.haml'] }
|
||||
|
||||
it 'returns the changed paths' do
|
||||
Guard.changed_paths(paths).should =~ ['a.rb', 'b.rb', 'templates/d.haml']
|
||||
end
|
||||
end
|
||||
|
||||
describe ".deleted_paths" do
|
||||
let(:paths) { ['a.rb', 'b.rb', '!c.rb', 'templates/d.haml', '!templates/e.haml'] }
|
||||
|
||||
it 'returns the deleted paths' do
|
||||
Guard.deleted_paths(paths).should =~ ['c.rb', 'templates/e.haml']
|
||||
end
|
||||
end
|
||||
|
||||
describe ".run_supervised_task" do
|
||||
subject { ::Guard.setup }
|
||||
|
||||
before do
|
||||
@g = mock(Guard::Guard).as_null_object
|
||||
subject.guards.push(@g)
|
||||
subject.add_group(:foo, { :halt_on_fail => true })
|
||||
subject.add_group(:bar, { :halt_on_fail => false })
|
||||
end
|
||||
|
||||
context "with a task that succeed" do
|
||||
context 'without any arguments' do
|
||||
before(:each) do
|
||||
@g.stub!(:regular) { true }
|
||||
@g.stub!(:regular_without_arg) { true }
|
||||
end
|
||||
|
||||
it "doesn't fire the Guard" do
|
||||
lambda { subject.supervised_task(@g, :regular) }.should_not change(subject.guards, :size)
|
||||
lambda { subject.run_supervised_task(@g, :regular_without_arg) }.should_not change(subject.guards, :size)
|
||||
end
|
||||
|
||||
it "returns the result of the task" do
|
||||
::Guard.supervised_task(@g, :regular).should be_true
|
||||
::Guard.run_supervised_task(@g, :regular_without_arg).should be_true
|
||||
end
|
||||
|
||||
it "passes the args to the :begin hook" do
|
||||
@g.should_receive(:hook).with("regular_without_arg_begin", "given_path")
|
||||
::Guard.run_supervised_task(@g, :regular_without_arg, "given_path")
|
||||
end
|
||||
|
||||
it "passes the result of the supervised method to the :end hook" do
|
||||
@g.should_receive(:hook).with("regular_without_arg_begin", "given_path")
|
||||
@g.should_receive(:hook).with("regular_without_arg_end", true)
|
||||
::Guard.run_supervised_task(@g, :regular_without_arg, "given_path")
|
||||
end
|
||||
end
|
||||
|
||||
@ -252,31 +522,91 @@ describe Guard do
|
||||
end
|
||||
|
||||
it "doesn't fire the Guard" do
|
||||
lambda { subject.supervised_task(@g, :regular_with_arg, "given_path") }.should_not change(subject.guards, :size)
|
||||
lambda { subject.run_supervised_task(@g, :regular_with_arg, "given_path") }.should_not change(subject.guards, :size)
|
||||
end
|
||||
|
||||
it "returns the result of the task" do
|
||||
::Guard.supervised_task(@g, :regular_with_arg, "given_path").should == "I'm a success"
|
||||
::Guard.run_supervised_task(@g, :regular_with_arg, "given_path").should eql "I'm a success"
|
||||
end
|
||||
|
||||
it "calls the default begin hook but not the default end hook" do
|
||||
@g.should_receive(:hook).with("failing_begin")
|
||||
@g.should_not_receive(:hook).with("failing_end")
|
||||
::Guard.run_supervised_task(@g, :failing)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with a task that throw :task_has_failed" do
|
||||
context "for a guard's group has the :halt_on_fail option == true" do
|
||||
before(:each) { @g.stub!(:group) { :foo }; @g.stub!(:failing) { throw :task_has_failed } }
|
||||
|
||||
it "throws :task_has_failed" do
|
||||
expect { subject.run_supervised_task(@g, :failing) }.to throw_symbol(:task_has_failed)
|
||||
end
|
||||
end
|
||||
|
||||
context "for a guard's group has the :halt_on_fail option == false" do
|
||||
before(:each) { @g.stub!(:group) { :bar }; @g.stub!(:failing) { throw :task_has_failed } }
|
||||
|
||||
it "catches :task_has_failed" do
|
||||
expect { subject.run_supervised_task(@g, :failing) }.to_not throw_symbol(:task_has_failed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with a task that raises an exception" do
|
||||
before(:each) { @g.stub!(:failing) { raise "I break your system" } }
|
||||
before(:each) { @g.stub!(:group) { :foo }; @g.stub!(:failing) { raise "I break your system" } }
|
||||
|
||||
it "fires the Guard" do
|
||||
lambda { subject.supervised_task(@g, :failing) }.should change(subject.guards, :size).by(-1)
|
||||
lambda { subject.run_supervised_task(@g, :failing) }.should change(subject.guards, :size).by(-1)
|
||||
subject.guards.should_not include(@g)
|
||||
end
|
||||
|
||||
it "returns the exception" do
|
||||
failing_result = ::Guard.supervised_task(@g, :failing)
|
||||
failing_result = ::Guard.run_supervised_task(@g, :failing)
|
||||
failing_result.should be_kind_of(Exception)
|
||||
failing_result.message.should == 'I break your system'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.guard_symbol' do
|
||||
let(:guard) { mock(Guard::Guard).as_null_object }
|
||||
|
||||
it 'returns :task_has_failed when the group is missing' do
|
||||
subject.guard_symbol(guard).should eql :task_has_failed
|
||||
end
|
||||
|
||||
context 'for a group with :halt_on_fail' do
|
||||
let(:group) { mock(Guard::Group) }
|
||||
|
||||
before do
|
||||
guard.stub(:group).and_return :foo
|
||||
group.stub(:options).and_return({ :halt_on_fail => true })
|
||||
end
|
||||
|
||||
it 'returns :no_catch' do
|
||||
subject.should_receive(:groups).with(:foo).and_return group
|
||||
subject.guard_symbol(guard).should eql :no_catch
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a group without :halt_on_fail' do
|
||||
let(:group) { mock(Guard::Group) }
|
||||
|
||||
before do
|
||||
guard.stub(:group).and_return :foo
|
||||
group.stub(:options).and_return({ :halt_on_fail => false })
|
||||
end
|
||||
|
||||
it 'returns :task_has_failed' do
|
||||
subject.should_receive(:groups).with(:foo).and_return group
|
||||
subject.guard_symbol(guard).should eql :task_has_failed
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".debug_command_execution" do
|
||||
subject { ::Guard.setup }
|
||||
|
||||
|
@ -12,6 +12,7 @@ RSpec.configure do |config|
|
||||
config.color_enabled = true
|
||||
|
||||
config.filter_run :focus => true
|
||||
config.treat_symbols_as_metadata_keys_with_true_values = true
|
||||
config.run_all_when_everything_filtered = true
|
||||
|
||||
config.before(:each) do
|
||||
|
@ -1,130 +1,225 @@
|
||||
private
|
||||
|
||||
def start
|
||||
sleep(@rest_delay || 1)
|
||||
@listener.update_last_event
|
||||
Thread.new { @listener.start }
|
||||
sleep(@rest_delay || 1)
|
||||
# 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
|
||||
|
||||
def record_results
|
||||
noise = %r|\.sw.$| # don't fail specs due to editor swap files, etc.
|
||||
# Make the spec listen to a specific listener.
|
||||
# This automatically starts to record results for the supplied listener.
|
||||
#
|
||||
# @param [Guard::Listener] listener the Guard listener
|
||||
#
|
||||
def listen_to(listener)
|
||||
@listener = listener
|
||||
record_results
|
||||
end
|
||||
|
||||
# Start the listener. Normally you use {#watch} to wrap
|
||||
# the code block that should be listen to instead of starting
|
||||
# it manually.
|
||||
#
|
||||
def start
|
||||
sleep(sleep_time)
|
||||
@listener.update_last_event
|
||||
Thread.new { @listener.start }
|
||||
sleep(sleep_time)
|
||||
end
|
||||
|
||||
# Stop the listener. Normally you use {#watch} to wrap
|
||||
# the code block that should be listen to instead of stopping
|
||||
# it manually.
|
||||
#
|
||||
def stop
|
||||
sleep(sleep_time)
|
||||
@listener.stop
|
||||
sleep(sleep_time)
|
||||
end
|
||||
|
||||
# Watch file changes in a code block.
|
||||
#
|
||||
# @example Watch file changes
|
||||
# watch do
|
||||
# File.mv file1, file2
|
||||
# end
|
||||
#
|
||||
# @yield The block to listen for file changes
|
||||
#
|
||||
def watch
|
||||
start
|
||||
yield if block_given?
|
||||
stop
|
||||
end
|
||||
|
||||
# Start recording results from the current listener.
|
||||
# You may want to use {#listen_to} to set a listener
|
||||
# instead of set it up manually.
|
||||
#
|
||||
def record_results
|
||||
# Don't fail specs due to editor swap files, etc.
|
||||
noise = %r|\.sw.$|
|
||||
@results = []
|
||||
|
||||
@listener.on_change do |files|
|
||||
@results += files.reject { |f| f =~ noise }
|
||||
end
|
||||
end
|
||||
|
||||
def stop
|
||||
sleep(@rest_delay || 1)
|
||||
@listener.stop
|
||||
sleep(@rest_delay || 1)
|
||||
end
|
||||
|
||||
# Get the recorded result from the listener.
|
||||
#
|
||||
# @return [Array<String>] the result files
|
||||
#
|
||||
def results
|
||||
@results.flatten
|
||||
end
|
||||
|
||||
shared_examples_for 'a listener that reacts to #on_change' do |rest_delay|
|
||||
before(:each) do
|
||||
@rest_delay = rest_delay
|
||||
@listener = described_class.new
|
||||
record_results
|
||||
# Define a file absolute to the fixture path.
|
||||
#
|
||||
# @param [String, Array<String>] file the relative file name, separated by segment
|
||||
# @return [String] the absolute file
|
||||
#
|
||||
def fixture(*file)
|
||||
@fixture_path.join(*file)
|
||||
end
|
||||
|
||||
it "catches a new file" do
|
||||
file = @fixture_path.join("newfile.rb")
|
||||
if File.exists?(file)
|
||||
begin
|
||||
File.delete file
|
||||
rescue
|
||||
shared_examples_for 'a listener that reacts to #on_change' do
|
||||
before do
|
||||
listen_to described_class.new
|
||||
end
|
||||
|
||||
context 'for a new file' do
|
||||
let(:file) { fixture('newfile.rb') }
|
||||
|
||||
before { File.delete(file) if File.exists?(file) }
|
||||
after { File.delete file }
|
||||
|
||||
it 'catches the new file' do
|
||||
File.exists?(file).should be_false
|
||||
|
||||
watch do
|
||||
FileUtils.touch file
|
||||
end
|
||||
|
||||
results.should =~ ['spec/fixtures/newfile.rb']
|
||||
end
|
||||
File.exists?(file).should be_false
|
||||
start
|
||||
FileUtils.touch file
|
||||
stop
|
||||
begin
|
||||
File.delete file
|
||||
rescue
|
||||
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
|
||||
results.should =~ ['spec/fixtures/newfile.rb']
|
||||
end
|
||||
|
||||
it "catches a single file update" do
|
||||
file = @fixture_path.join("folder1/file1.txt")
|
||||
File.exists?(file).should be_true
|
||||
start
|
||||
File.open(file, 'w') { |f| f.write('') }
|
||||
stop
|
||||
results.should =~ ['spec/fixtures/folder1/file1.txt']
|
||||
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
|
||||
|
||||
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']
|
||||
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
|
||||
|
||||
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']
|
||||
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
|
||||
|
||||
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 =~ []
|
||||
context 'for a deleted file' do
|
||||
let(:file) { fixture('folder1', 'file1.txt') }
|
||||
|
||||
after { FileUtils.touch file }
|
||||
|
||||
it 'does not catch the deletion' do
|
||||
File.exists?(file).should be_true
|
||||
|
||||
watch do
|
||||
File.delete file
|
||||
end
|
||||
|
||||
results.should =~ []
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
context 'for a moved file' do
|
||||
let(:file1) { fixture('folder1', 'file1.txt') }
|
||||
let(:file2) { fixture('folder1', 'movedfile1.txt') }
|
||||
|
||||
after { FileUtils.mv file2, file1 }
|
||||
|
||||
it 'does not catch the move' do
|
||||
File.exists?(file1).should be_true
|
||||
File.exists?(file2).should be_false
|
||||
|
||||
watch do
|
||||
FileUtils.mv file1, file2
|
||||
end
|
||||
|
||||
results.should =~ []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for "a listener scoped to a specific directory" do |rest_delay|
|
||||
before :each do
|
||||
@rest_delay = rest_delay
|
||||
@wd = @fixture_path.join("folder1")
|
||||
@listener = described_class.new @wd
|
||||
end
|
||||
shared_examples_for "a listener scoped to a specific directory" do
|
||||
|
||||
it "should base paths within this directory" do
|
||||
record_results
|
||||
new_file = @wd.join("folder2/newfile.rb")
|
||||
modified = @wd.join("file1.txt")
|
||||
let(:work_directory) { fixture('folder1') }
|
||||
|
||||
let(:new_file) { work_directory.join('folder2', 'newfile.rb') }
|
||||
let(:modified) { work_directory.join('file1.txt') }
|
||||
|
||||
before { listen_to described_class.new(work_directory) }
|
||||
after { File.delete new_file }
|
||||
|
||||
it 'should base paths within this directory' do
|
||||
File.exists?(modified).should be_true
|
||||
File.exists?(new_file).should be_false
|
||||
start
|
||||
FileUtils.touch new_file
|
||||
File.open(modified, 'w') { |f| f.write('') }
|
||||
stop
|
||||
File.delete new_file
|
||||
results.should =~ ["folder2/newfile.rb", 'file1.txt']
|
||||
|
||||
watch do
|
||||
FileUtils.touch new_file
|
||||
File.open(modified, 'w') { |f| f.write('') }
|
||||
end
|
||||
|
||||
results.should =~ ['folder2/newfile.rb', 'file1.txt']
|
||||
end
|
||||
end
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user