Compare commits

...

244 Commits
v0.6 ... master

Author SHA1 Message Date
Michael Kessler
95f895e1ed GNTP needs Mac Growl >= 1.3. [ci skip] 2011-10-13 11:16:29 +02:00
Thibaud Guillaume-Gentil
3584dbbfbb Keep CHANGELOG up-to-date [ci skip] 2011-10-13 10:41:20 +02:00
Michael Kessler
10f1542ac3 Avoid Guard is not missing constant ... exceptions. (fixes #160) 2011-10-12 23:07:06 +02:00
Rémy Coutable
a5fb1aa601 Forcing some links for Rubydoc.info [ci skip] 2011-10-12 23:33:36 +03:00
Thibaud Guillaume-Gentil
50b35cc99c Fix README for rubydoc [ci skip] 2011-10-12 22:22:44 +02:00
Thibaud Guillaume-Gentil
5111e8594e Add new cli option
- The new cli option (-i / --no-interactions) allow to completely turn off any Guard terminal interactions
2011-10-12 22:14:25 +02:00
Thibaud Guillaume-Gentil
1a45a77969 Typo [ci skip] 2011-10-12 22:30:06 +03:00
Michael Kessler
67d73cf6a6 Avoid warnings on 1.8.7 2011-10-12 21:03:59 +02:00
Michael Kessler
6094451cfb Merge branch 'master' of github.com:guard/guard 2011-10-12 20:55:28 +02:00
Michael Kessler
3b0e2ad305 Add support for Growl Notification Transport Protocol. 2011-10-12 20:54:57 +02:00
Michael Kessler
83def5004f Disable bdd cycle.
Yes, it takes a way to long before you can get started.
We anyway have to run spec:portability to also locally
test on all supported Rubies.
2011-10-12 18:15:11 +02:00
Rémy Coutable
18e0f99a6d Added a sentence to tell people to check out the installation instructions specific to their operating systems. 2011-10-12 15:53:54 +03:00
Michael Kessler
4ed28a3cbe Add watcher changes to the changelog. 2011-10-11 11:18:24 +02:00
Michael Kessler
7383efaf31 Document any_return on the Guard class. 2011-10-11 11:16:11 +02:00
Michael Kessler
f6bea4da59 Document known guard options. 2011-10-11 11:09:41 +02:00
Michael Kessler
8527cf40e3 Make only one assumption per context. 2011-10-11 11:06:30 +02:00
Michael Kessler
8891776e34 Merge remote-tracking branch 'earlonrails/return_on_guard' 2011-10-11 10:55:12 +02:00
Kevin Krauss
5429d10eb5 Removed extra attr_accessor and used proper formatting. 2011-10-10 15:10:44 -07:00
Kevin Krauss
037b1e6feb Optional user defined return with guard options[:any_return] 2011-10-10 13:38:14 -07:00
Kevin Krauss
bdfdf45325 Switched the parameter to be added to the guard instead of the watcher. 2011-10-10 09:52:53 -07:00
Michael Kessler
0b837a20ec Keep changelog in sync. [ci skip] 2011-10-10 08:36:32 +02:00
Michael Kessler
5c0105b2c1 Describe why UI class uses STDERR. 2011-10-10 08:29:43 +02:00
Thibaud Guillaume-Gentil
6f1a0acb2e Merge pull request #156 from sunaku/patch-1
ui: print to STDERR (play nice with UNIX pipes)
2011-10-09 23:19:08 -07:00
Michael Kessler
1845efb11e Add missing specs for Guard implementation base class 2011-10-10 08:05:10 +02:00
Kevin Krauss
a274fb1ddf fixed spec for guard 2011-10-09 16:06:38 -07:00
Kevin Krauss
b2f282f1e8 watcher cleaned up. 2011-10-09 15:54:24 -07:00
Suraj N. Kurapati
31519887cd ui: print to STDERR (play nice with UNIX pipes) 2011-10-09 00:10:39 -07:00
Thibaud Guillaume-Gentil
1c6ce380ea Update README about Growl 1.3 2011-10-08 13:18:10 +03:00
Kevin Krauss
a7b6ebeef1 updated the watch spec to describe the tests better. 2011-10-07 10:45:26 -07:00
Kevin Krauss
33542acec9 watcher init now has an addition param that allows the user to return any obj type. The spec has been restored to its original and then more specs added for object support. 2011-10-07 10:36:47 -07:00
Kevin Krauss
d6b47f6448 Changed {:foo, 'bar'} to {:foo => 'bar'} 2011-10-05 18:09:28 -07:00
Kevin Krauss
a5dd0c7847 any return is ok. Pass the path and some other parameters if you would like and they don't become strings! 2011-10-05 18:01:55 -07:00
Thibaud Guillaume-Gentil
bebaef1592 Merge pull request #152 from scottdavis/master
Growl Notify Api Update
2011-10-04 23:34:29 -07:00
Kevin Krauss
47f3d878f6 When using a block allow none string return 2011-10-04 22:17:07 -07:00
Scott Davis
1181b8b234 updated the growl notify code to gracefuly fail if growl is not installed - this is new in growl_notify-0.0.3 2011-10-05 01:10:48 -04:00
Michael Kessler
8b6a035dfe Make spec example simpler to avoid Ruby hash ordering issue. 2011-10-04 11:37:12 +02:00
Michael Kessler
a1e5723efe Sort the Guard options when describe the DSL. 2011-10-04 10:26:06 +02:00
Michael Kessler
4451d73583 Move the template initialization to the Guard module and add specs. 2011-10-04 09:46:14 +02:00
Michael Kessler
cdf0906614 Move the DSL describing logic from the CLI to the DSLDescriber. 2011-10-04 09:04:50 +02:00
Michael Kessler
cba4e4c163 Add --fail-fast to RSpec configuration. 2011-10-04 08:51:55 +02:00
Michael Kessler
eb7081e3d2 Describe the relation of a task return value and the hook functions. 2011-10-03 11:32:40 +02:00
Thibaud Guillaume-Gentil
da866d3380 Version 0.8.4 2011-10-03 08:25:07 +02:00
Rémy Coutable
ac42712335 Fixed link.
[ci skip]
2011-10-03 01:10:37 +03:00
Rémy Coutable
6ae9a83dbd Complete & simplify the CHANGELOG 2011-10-03 01:09:04 +03:00
Rémy Coutable
910856362e Merge pull request #150 from f1sherman/use_mutex_instead_of_lock
fix issue where interator thread was continuing to capture input from std
2011-10-02 14:56:43 -07:00
Brian John
cd545d5207 fix issue where interator thread was continuing to capture input from stdin while a guard is being executed 2011-10-02 16:46:20 -05:00
Michael Kessler
1090e0f797 Merge pull request #148 from simi/patch-1
Correct string interpolation at guard init
2011-10-02 06:20:17 -07:00
Josef Šimánek
b15638f13c Correct string interpolation at guard init 2011-10-02 16:15:48 +03:00
Thibaud Guillaume-Gentil
64ac60a1fe Version 0.8.3 2011-10-01 20:53:21 +02:00
Michael Kessler
e26c1d2179 Update CHANGELOG. 2011-10-01 13:38:04 +02:00
Michael Kessler
7dcd7ca168 Update wrong spec description. 2011-10-01 13:37:49 +02:00
Brian John
b3535b4a4e use a mutex instead of a lock for more efficient/simple locking 2011-09-30 20:45:41 -05:00
Michael Kessler
3c75205dd0 Merge branch 'master' of github.com:guard/guard 2011-09-30 18:35:31 +02:00
Michael Kessler
e33f5df518 Make Guard implementation of :task_has_failed simple.
This change makes sure that Guard implementation can just
`throw :task_has_failed` without knowing whether the enclosing
group has enabled :halt_on_fail.

The problem with throw/catch is, that when you throw a :symbol,
you'll have to catch it, otherwise you get a 'uncatched throw' error.

When the Guard group has not enabled :halt_on_fail, we catch the throw
when execute the supervised task.
2011-09-30 18:35:04 +02:00
Thibaud Guillaume-Gentil
b36bea28f3 Merge pull request #145 from johnbintz/master
Fix over-utilization of CPU in Interactor
2011-09-30 09:17:44 -07:00
John Bintz
776361d038 add a sleep to the interactor thread, because it's healthy to take a break every once in a while 2011-09-30 11:22:35 -04:00
Thibaud Guillaume-Gentil
6ecc541bda Version 0.8.2 2011-09-30 16:47:43 +02:00
Michael Kessler
e9ddb1c110 Update wrong doc example. [ci skip] 2011-09-30 12:13:50 +02:00
Michael Kessler
5bdb56caa0 Update docs regarding :task_has_failed.
- Add :task_has_failed documentation to Guard and Group classes
- Consolidated Guard documentation from the README and its YARDOC.
- Remove all return `true` values from Guard task methods.
2011-09-30 11:30:05 +02:00
Thibaud Guillaume-Gentil
56ebe9f9f4 Improve guard stop
- prevent run_guard_task(:stop) to be skipped
2011-09-29 22:39:24 +02:00
Thibaud Guillaume-Gentil
aaa08d3a89 Version 0.8.1 2011-09-29 09:22:02 +02:00
Rémy Coutable
1607901e43 Fixes #144, due to a too-hardore refactoring! ;)
We will need specs for Guard::CLI!
2011-09-29 00:27:37 +02:00
Thibaud Guillaume-Gentil
243ea157a9 Version 0.8.0 2011-09-28 22:18:29 +02:00
Rémy Coutable
b2488e7b9e Edited CHANGELOG.md via GitHub 2011-09-28 17:20:02 +03:00
Rémy Coutable
309ecc7b4b Edited README.md via GitHub 2011-09-28 17:15:47 +03:00
Michael Kessler
db949bf9e4 Refactor the watch_all_modifications specs.
- Just test a single expectation per it block
- Better separation of fixture setup/teardown
2011-09-28 15:57:30 +02:00
Michael Kessler
40e033ce0f Document passed options. 2011-09-28 15:47:53 +02:00
Michael Kessler
5d122466fa Use new fixture helper. 2011-09-28 15:16:55 +02:00
Michael Kessler
ca7b059c66 Fix wrong wording. 2011-09-28 15:16:19 +02:00
Michael Kessler
5325cbdea1 Refactor listener specs.
- Introduce `listen_to`, `fixture` and `watch` spec helpers.
- Added docs to spec helper.
- Better separation of fixture setup/teardown and the actual expectation.
- Make line match within 120 characters.
2011-09-28 15:06:33 +02:00
Michael Kessler
14150889c5 Align variable assignment, make return variable clear. 2011-09-28 13:46:05 +02:00
Michael Kessler
ec71eea227 Minor doc changes. 2011-09-28 13:41:29 +02:00
Michael Kessler
81f713a9ae Ignore .rvmrc. 2011-09-28 13:36:13 +02:00
Michael Kessler
83b1c9f787 Trigger all listener specs when listener_helper.rb is changed. 2011-09-28 13:34:53 +02:00
Michael Kessler
b00c850ef8 Put guards into groups.
Now I can just run the :specs group and run all specs
without generating the man pages.

In addition I enabled the full BDD cycle for development,
not sure why it wasn't enabled. Feel free to revert if there
is a specific reason for that.
2011-09-28 13:24:01 +02:00
Michael Kessler
a91874a4df Ignore .rbx folder 2011-09-28 13:20:30 +02:00
Michael Kessler
b64b7882f7 Refactor massive execute_supervised_task_for_all_guards method.
- Renamed some Guard methods to be shorter and more consistent.
- Extract methods from execute_supervised_task_for_all_guards for less complexity.
- Added more specs for extracted methods.
- Added more docs on how marking of deleted/moved files works.
- Refactor Guard to be unaware of the :watch_all_modifications options for simplicity.
2011-09-28 12:42:09 +02:00
Michael Kessler
805fd174d9 Merge branch 'master' of github.com:guard/guard 2011-09-28 10:22:02 +02:00
Thibaud Guillaume-Gentil
479c6b1918 Typo 2011-09-28 08:47:01 +02:00
Michael Kessler
d857134446 Fix code indention. 2011-09-27 20:44:17 +02:00
Michael Kessler
7e6e52a2c9 Docs for #run_on_deletion Guard method. 2011-09-27 20:29:52 +02:00
Darren Pearce
87656c2065 Merge branch 'master' of git://github.com/guard/guard 2011-09-26 13:47:20 -06:00
Rémy Coutable
4572cbed0c Remove strange man pages 2011-09-26 21:43:52 +02:00
Darren Pearce
67882bcceb Merge branch 'master' of git://github.com/guard/guard
Conflicts:
	lib/guard.rb
2011-09-26 12:22:02 -06:00
Darren Pearce
d9fc071492 fixed typo and updated relativize_paths regex 2011-09-26 11:37:54 -06:00
Michael Kessler
a1f37f60d6 Re-evaluate the Guardfile before reload all Guards (Fixes #141). 2011-09-24 12:58:27 +02:00
Michael Kessler
0936771a9e Decrease Travis sleep time. 2011-09-24 12:20:35 +02:00
Michael Kessler
e9eaa39e4d Remove Travis debug output. 2011-09-24 12:14:59 +02:00
Michael Kessler
1571292ba7 Make spec sleep time a float. 2011-09-24 11:35:53 +02:00
Michael Kessler
8365d7c429 Set a generous spec sleep time on Travis. 2011-09-24 11:10:01 +02:00
Michael Kessler
5de287ba95 Make the sleep time for listener specs configurable. 2011-09-24 11:09:32 +02:00
Rémy Coutable
5c9ee2afdf Yardoc improvements [ci skip] 2011-09-23 11:01:52 +02:00
Rémy Coutable
a7a6c5c69e Improves yardoc (hopefully) [ci skip] 2011-09-23 10:52:58 +02:00
Rémy Coutable
a326c35875 Fix specs for Ruby != 1.9 2011-09-23 00:53:13 +02:00
Rémy Coutable
2f59a10558 Update CHANGELOG 2011-09-23 00:41:39 +02:00
Rémy Coutable
41ada16595 Allow more complex conditions when searching for guards 2011-09-23 00:39:27 +02:00
Rémy Coutable
0a60575dde Update CHANGELOG 2011-09-23 00:32:30 +02:00
Rémy Coutable
dc526f2898 Man 2011-09-23 00:23:46 +02:00
Rémy Coutable
1cd669bf60 Refactor & fix specs 2011-09-23 00:22:44 +02:00
Rémy Coutable
916613c027 Ensure group name is a Symbol in Guard::Guard#initialize 2011-09-23 00:22:25 +02:00
Rémy Coutable
12fcf15a95 New smart accessors for guards and groups 2011-09-23 00:21:30 +02:00
Rémy Coutable
ed97336c7d New Group class 2011-09-23 00:20:35 +02:00
Rémy Coutable
4c1cf825e5 Merge branch 'master' into guard_dependencies
Conflicts:
	lib/guard.rb
	lib/guard/dsl.rb
	man/guard
	man/guard.html
2011-09-22 22:44:15 +02:00
Darren Pearce
ec463a271b Merge branch 'master' of git://github.com/guard/guard
Conflicts:
	lib/guard/listener.rb
2011-09-21 17:14:09 -06:00
Darren Pearce
0c9634a1c2 updated README 2011-09-21 17:04:10 -06:00
Darren Pearce
432d4a0991 changed watch deletions option to watch_all_modifiactions, Merge branch 'master' of git://github.com/guard/guard
Conflicts:
	lib/guard.rb
	lib/guard/cli.rb
	lib/guard/listener.rb
2011-09-21 17:00:53 -06:00
Rémy Coutable
bda57d1d8f Little doc improvements 2011-09-21 18:54:33 +02:00
Michael Kessler
507abce5b6 Fix some typos, better wording and formatting. 2011-09-21 01:30:35 +02:00
Michael Kessler
78448631ab Remove GitHub flavoured Markdown until rubydoc.info shows the README correct. 2011-09-21 00:55:07 +02:00
Michael Kessler
072d5404ee Merge pull request #137 from hron/guard
---

It seems like the new interactor eats input from $stdin even while it is locked. This disallow using tools like ruby-debug or pry in specs or cucumber.

The fix just kills the interactor when it is locked and runs it again when ulocked.

Conflicts:
	lib/guard/interactor.rb
2011-09-21 00:34:11 +02:00
Rémy Coutable
9ebc59f533 Merge pull request #138 from rmm5t/patch-1
Fixed comments in example scaffold to reference interactions
2011-09-20 13:18:16 -07:00
Rémy Coutable
d493e3c5d9 Tiny doc improvements 2011-09-20 21:52:59 +02:00
Ryan McGeary
dd1a5cbb49 Fixed comments in example scaffold to reference interactions instead of signal handlers 2011-09-20 16:24:00 -03:00
Aleksei Gusev
8c6a30795a Change Guard::Interactor#lock and #unlock methods so they will lock interactor
in the right thread and free $stdin [closes #137].
2011-09-20 21:54:21 +03:00
Michael Kessler
c0dcef9dbc 100% yardoc
Files:          16
Modules:         5 (    0 undocumented)
Classes:        11 (    0 undocumented)
Constants:      20 (    0 undocumented)
Methods:       136 (    0 undocumented)
 100.00% documented
2011-09-20 15:07:29 +02:00
Michael Kessler
b944932f53 Document the hook module. 2011-09-20 14:42:32 +02:00
Michael Kessler
33b52d2955 Fix guard groups. 2011-09-20 14:26:27 +02:00
Michael Kessler
53802ed355 Add yardoc to listeners. 2011-09-20 13:58:25 +02:00
Michael Kessler
ad6fe6f69b Yardoc for the interactor. 2011-09-20 13:10:16 +02:00
Michael Kessler
98ee450037 Initial notifier yardoc. 2011-09-20 13:06:35 +02:00
Michael Kessler
ab91117ed7 Add yardoc to the UI class. 2011-09-20 12:54:36 +02:00
Michael Kessler
dddc2ad369 Add yardoc to the watcher class. 2011-09-20 12:54:28 +02:00
Michael Kessler
044100b7c3 Fix command debug. 2011-09-20 12:10:53 +02:00
Michael Kessler
2fa0f7255a Fix wrong params tag. 2011-09-20 12:08:51 +02:00
Michael Kessler
0f5b2b764a Initial yardoc for the main Guard class for specific implementations. 2011-09-20 12:07:34 +02:00
Michael Kessler
dc009d445a Add yardoc for the DslDescriber. 2011-09-20 11:49:05 +02:00
Michael Kessler
d66a872f4a Initial yardoc for Guard. 2011-09-20 11:40:47 +02:00
Michael Kessler
9df4b3c291 Add options to evaluate_guardfile. 2011-09-20 11:23:37 +02:00
Michael Kessler
7099774e7c Fix wrong yard param tags. 2011-09-20 11:13:12 +02:00
Michael Kessler
aa55d48b96 Initial yardoc for the DSL class. 2011-09-20 11:11:40 +02:00
Michael Kessler
e126c7f609 Add yardoc for CLI class. 2011-09-20 10:05:11 +02:00
Michael Kessler
48863fee0d Ignore generated documentation. 2011-09-20 10:03:09 +02:00
Michael Kessler
42d4d94611 Add yard options. 2011-09-20 10:02:54 +02:00
Michael Kessler
fc3e179a0c Add yard and kramdown to development dependencies. 2011-09-20 10:02:43 +02:00
Aleksei Gusev
443f57efce Fix interacting with tools like ruby-debug.
It seems like the new interactor eats input from $stdin even while it locked.
  This disallow using tools like 'ruby-debug' or 'pry' in specs or cucumber.

  The fix just kills the interactor when it is locked and runs it again when
  ulocked.
2011-09-19 23:27:05 +03:00
Rémy Coutable
f9521fe0fe nil is not accepted for catch's arg in other implementation than 1.9.2 2011-09-16 01:26:27 +02:00
Rémy Coutable
1608b17501 Build only master and guard_dependencies branches on Travis 2011-09-16 01:20:42 +02:00
Rémy Coutable
078d55f13c Actually halt guards' execution in a group only when the guard's task throw :task_has_failed (not when it returns false). 2011-09-16 01:20:22 +02:00
Rémy Coutable
b1b69924a7 First implementation of #97 "Guard dependencies". 2011-09-16 01:01:58 +02:00
Rémy Coutable
22001c5ecd Optimize multiple if statements in Gemfile. 2011-09-15 22:02:23 +03:00
Darren Pearce
6993712c38 Merge branch 'master' of git://github.com/guard/guard 2011-09-14 11:44:36 -06:00
Darren Pearce
5a70faf974 updated deletion option name to be more explict 2011-09-14 11:44:01 -06:00
Thibaud Guillaume-Gentil
fb2c320a2a Version 0.7.0 2011-09-14 09:52:36 +02:00
Darren Pearce
879732fb43 updated README with deletions option 2011-09-13 22:56:23 -06:00
Darren Pearce
34aaed9741 fixed specs to cleanup afterwards 2011-09-13 22:46:56 -06:00
Darren Pearce
b74d09b9d6 Merge remote branch 'upstream/master'
Conflicts:
	lib/guard.rb
	lib/guard/listener.rb
	spec/guard/listener_spec.rb
2011-09-13 16:50:24 -06:00
Darren Pearce
423610f22d moved timestamp hash creation into it's own method add initial specs for watching deleted files and fixed some minor formatting 2011-09-13 16:22:39 -06:00
Rémy Coutable
9ad866079f https://secure.travis-ci.org is working again! Houra! 2011-09-06 13:01:06 +03:00
Thibaud Guillaume-Gentil
c704fe5d0b Version 0.7.0.rc1 2011-09-05 11:25:49 +02:00
Thibaud Guillaume-Gentil
da46791204 Added @netzpirat to the JRuby & Rubinius support in the CHANGELOG [ci skip] 2011-09-05 12:24:26 +03:00
Michael Kessler
84d6641925 Merge branch 'master' of github.com:guard/guard 2011-09-05 10:57:41 +02:00
Michael Kessler
2edf8bead6 Add JRuby and Rubinius to the list of tested Rubies. 2011-09-05 10:57:21 +02:00
Michael Kessler
b37928a540 Remove jruby_spec branch from Travis. 2011-09-05 10:54:45 +02:00
Michael Kessler
b1bef902b1 Correct typo. 2011-09-05 10:44:50 +02:00
Thibaud Guillaume-Gentil
16fe7441d4 Use [mtime, ctime].max for == comparaison
Conflicts:

	lib/guard/listener.rb
2011-09-05 10:43:18 +02:00
Rémy Coutable
58af2b2c1d Merge branch 'master' of github.com:guard/guard 2011-09-05 10:40:13 +02:00
Rémy Coutable
330493a572 Don't put a newline in debug output 2011-09-05 10:39:56 +02:00
Michael Kessler
59103ffe09 Use the listener_helpers start/stop. 2011-09-05 10:36:45 +02:00
Michael Kessler
d88ffec9a0 Merge branch 'master' of github.com:guard/guard 2011-09-05 10:17:18 +02:00
Michael Kessler
54689318cc Configure Travis to build new branches. 2011-09-05 10:16:40 +02:00
Rémy Coutable
dc9bddf02e Merge branch 'master' of github.com:guard/guard 2011-09-05 10:16:17 +02:00
Rémy Coutable
771c9d08c0 Tell Travis-CI to notify only the core team.
Currently it notifies some people but I don't get how they are chosen, it even notifies me on 2 email addresses (my current and my former registered on GitHub)…
2011-09-05 10:16:11 +02:00
Michael Kessler
d1c452bc1e Revert "More Travis tests."
This reverts commit bd67a69a8b.
2011-09-05 10:07:19 +02:00
Michael Kessler
bd67a69a8b More Travis tests. 2011-09-05 09:42:05 +02:00
Rémy Coutable
3de4f505d6 Merge branch 'master' into hook 2011-09-04 18:50:59 +02:00
Rémy Coutable
596f92f7eb Update README 2011-09-04 18:47:51 +02:00
Rémy Coutable
fe103ca9a4 Update CHANGELOG 2011-09-04 18:45:26 +02:00
Rémy Coutable
b8beb3fb9e Update man 2011-09-04 18:45:14 +02:00
Rémy Coutable
96604060ed Clean Hook code 2011-09-04 18:44:42 +02:00
Rémy Coutable
3d9d1ad649 Make it (even) more clear that stdin.gets is only available in the HEAD/0.7! [ci skip] 2011-09-04 19:29:18 +03:00
Rémy Coutable
11495687f4 Merge branch 'master' into hook
Conflicts:
	Guardfile
	lib/guard.rb
	lib/guard/dsl.rb
	spec/guard/interactor_spec.rb
	spec/guard/listeners/darwin_spec.rb
2011-09-04 18:00:29 +02:00
Michael Kessler
8255ae0be9 Working on a fork with Travis won't work. Re-add feature branch. 2011-09-04 16:18:31 +02:00
Michael Kessler
20305a59ba Remove testing branch. 2011-09-04 15:26:58 +02:00
Michael Kessler
b8e43e7a7c Add JRuby/Travis test branch to CI. 2011-09-04 14:41:28 +02:00
Michael Kessler
69d308b505 Fix Travis CI build status image.
The image is not displayed properly because Travis SSL uses the
Heroku wildcard SSL certificate and is thus not trusted because
of the URL mismatch.
2011-09-04 01:13:23 +02:00
Michael Kessler
8bb295f105 Link to the wiki for more information on growl_notify vs. growl. 2011-09-03 23:38:07 +02:00
Rémy Coutable
0a2a34e55f Edited CHANGELOG.md via GitHub 2011-09-04 00:33:53 +03:00
Thibaud Guillaume-Gentil
50d80d962b Update CHANGELOG 2011-09-03 23:17:00 +02:00
Thibaud Guillaume-Gentil
85f67f26ab Update man 2011-09-03 23:15:03 +02:00
Thibaud Guillaume-Gentil
60874a6991 Clean 2011-09-03 22:46:09 +02:00
Thibaud Guillaume-Gentil
1c5d1fd268 Merge branch 'master' into stdin 2011-09-03 22:45:04 +02:00
Thibaud Guillaume-Gentil
a288f51ec8 Update man files 2011-09-03 22:40:18 +02:00
Thibaud Guillaume-Gentil
215af072a2 Replace Signal handlers README section by Interactions 2011-09-03 22:40:06 +02:00
Thibaud Guillaume-Gentil
9fcd8621ad Ignore *.rbc 2011-09-03 22:38:03 +02:00
Thibaud Guillaume-Gentil
f91622adc5 Clean 2011-09-03 22:37:50 +02:00
Thibaud Guillaume-Gentil
e1472cb0c4 Make specs pass on jruby & rubinius (Mac OS X) 2011-09-03 22:37:36 +02:00
Thibaud Guillaume-Gentil
ee5468eeeb Don't kill listener thread on spec 2011-09-03 21:44:09 +02:00
Thibaud Guillaume-Gentil
b0295c1437 Merge branch 'master' into stdin 2011-09-03 21:33:32 +02:00
Thibaud Guillaume-Gentil
8b66b71716 Travis test 2011-09-03 21:33:06 +02:00
Thibaud Guillaume-Gentil
41127e2cbf Only use crime on == timestamp comparison 2011-09-03 14:52:33 +02:00
Thibaud Guillaume-Gentil
5978e875df Put mtime (maybe ctime is updated on linux when moving a file) 2011-09-03 14:43:25 +02:00
Thibaud Guillaume-Gentil
68efb0d52f Focus on failed spec 2011-09-03 14:30:15 +02:00
Thibaud Guillaume-Gentil
667b248ad0 Add attrib on linux listener 2011-09-03 14:25:12 +02:00
Thibaud Guillaume-Gentil
18cb3471af Try to debug linux spec on travis 2011-09-03 14:16:32 +02:00
Thibaud Guillaume-Gentil
905c32dcc9 Add listener spec for chmod modification 2011-09-03 14:15:54 +02:00
Thibaud Guillaume-Gentil
45ba095093 Merge branch 'master' into stdin 2011-09-02 16:22:49 +02:00
Thibaud Guillaume-Gentil
0c7b496296 Add stdin branch to travis 2011-09-02 16:22:24 +02:00
Thibaud Guillaume-Gentil
f8b4f45737 Fix spec 2011-09-02 16:22:09 +02:00
Thibaud Guillaume-Gentil
47be15125b Skip reactor on test env
Update last_event sooner
Use ctime instead of mtime (Rails 3.1 assets pipeline issue)
2011-09-02 16:22:01 +02:00
Thibaud Guillaume-Gentil
c4ddb29fc6 Work on specs 2011-09-01 23:24:45 +02:00
Thibaud Guillaume-Gentil
3b73ea77b7 Merge branch 'master' into stdin 2011-09-01 21:59:00 +02:00
Thibaud Guillaume-Gentil
5c1fb285e8 Merge branch 'master' into stdin
Conflicts:
	lib/guard/listener.rb
2011-09-01 21:28:03 +02:00
Thibaud Guillaume-Gentil
74b3445549 Fix travis png link 2011-09-01 21:24:53 +02:00
Thibaud Guillaume-Gentil
4ac556bfe3 Back to '--format doc' for guard-rspec 2011-08-30 21:21:09 +02:00
Thibaud Guillaume-Gentil
5de94ccbcb Merge branch 'master' into stdin
Conflicts:
	Guardfile
	lib/guard.rb
2011-08-30 21:16:30 +02:00
Thibaud Guillaume-Gentil
63c3015532 Merge branch 'stdin' of github.com:guard/guard into stdin
Conflicts:
	lib/guard.rb
2011-08-30 21:14:22 +02:00
Thibaud Guillaume-Gentil
3717179591 Refactor new interactor/listener mechanic now it's
look pretty.
Specs still need some love :)
2011-08-30 21:13:51 +02:00
Thibaud Guillaume-Gentil
742b9fe3ff Add sleep to not eat 100% cpu :) 2011-08-30 10:48:29 +02:00
Thibaud Guillaume-Gentil
e752dbe1c1 Maybe first working version (but still a work in progress) 2011-08-29 21:25:58 +02:00
Darren Pearce
02c4465940 added fetch to fix failing tests 2011-08-26 23:42:31 -06:00
Darren Pearce
f8960ec783 converted sha1 check in favour of checking cached file modified timestamps 2011-08-26 11:05:16 -06:00
Darren Pearce
8da8f6a33d added optional support for watching deletions and with that comes file moves 2011-08-23 10:07:23 -06:00
Darren Pearce
e795ab29f5 inital idea for deleted files 2011-08-19 14:53:48 -06:00
Rémy Coutable
4fd1db3fb7 Use doc formatter again (instead of Fuubar) 2011-08-17 10:45:54 +02:00
Rémy Coutable
652c3d8661 Merge branch 'master' into hook
Conflicts:
	lib/guard.rb
	lib/guard/dsl.rb
	spec/guard/dsl_spec.rb
	spec/guard_spec.rb
2011-08-17 10:45:20 +02:00
Thibaud Guillaume-Gentil
af408ceb65 Add thread in guard interactor 2011-08-17 10:07:30 +02:00
Rémy Coutable
32904356cb Merge branch 'master' into hook 2011-08-17 01:47:25 +02:00
Rémy Coutable
eb347ee266 Merge branch 'master' into hook
Conflicts:
	Gemfile
	Guardfile
	README.markdown
	Rakefile
	lib/guard.rb
	lib/guard/dsl.rb
	lib/guard/interactor.rb
	lib/guard/listener.rb
	lib/guard/ui.rb
	lib/guard/watcher.rb
	spec/guard/dsl_spec.rb
	spec/guard/notifier_spec.rb
2011-08-17 01:34:27 +02:00
Thibaud Guillaume-Gentil
d6e27fe334 Merge branch 'master' into stdin
Conflicts:
	Guardfile
2011-08-16 09:08:05 +02:00
Thibaud Guillaume-Gentil
8144a43726 Merge branch 'master' of github.com:guard/guard 2011-08-16 09:06:23 +02:00
Thibaud Guillaume-Gentil
e7c23ff78d Add gem 'win32console' note in the README 2011-08-13 16:50:27 +02:00
Thibaud Guillaume-Gentil
573ddf9d9d First trial to remove Posix Signals interaction 2011-08-13 16:43:32 +02:00
Rémy Coutable
32cb5d7bf8 Added a "require" in spec that was making specs failing on Travis CI 2011-05-02 15:34:33 +02:00
Rémy Coutable
aeb2c67c17 Typo 2011-05-02 00:07:39 +02:00
monocle
efdacce491 Added hooks to README 2011-04-30 20:46:41 -07:00
monocle
d798bf05e2 Merge branch hook of github.com:guard/guard into hook 2011-04-30 18:41:27 -07:00
Rémy Coutable
20b8a9af69 Merge branch 'hook' of github.com:guard/guard into hook
Conflicts:
	lib/guard.rb
	lib/guard/dsl.rb
	lib/guard/hook.rb
	spec/guard/dsl_spec.rb
	spec/guard/hook_spec.rb
	spec/guard_spec.rb
2011-04-30 00:49:46 +02:00
Rémy Coutable
e853009528 Refactored Guard::Dsl#callback and updated specs, improved inline docs for Guard::Hook#hook, added ENV["GUARD_ENV"] = 'development' in Guardfile so we see hooks firing! Run specs on REE too.
Signed-off-by: Rémy Coutable <rymai@rymai.me>
2011-04-30 00:45:38 +02:00
monocle
134cbdb007 Hook - 1) Send args to hooks from Guard.supervised_task
2) Pass args from hooks to callbacks
3) Suppress UI message from hooks unless in 'development'
2011-04-30 00:45:38 +02:00
Rémy Coutable
b83653db2e Added #callback DSL, modified Guard and Guard::Hook a bit in consequence.
Signed-off-by: Rémy Coutable <remy@jilion.com>
2011-04-30 00:45:38 +02:00
monocle
b646ae53f6 Fix preexisting tests after adding default hooks 2011-04-30 00:45:13 +02:00
monocle
386b0be53d Hooks - Add :begin and :end hook to all guard actions via Guard.supervised_task 2011-04-30 00:41:25 +02:00
monocle
7916139726 Added hook/callback feature. 2011-04-30 00:41:25 +02:00
monocle
c82e1582f8 Hook - 1) Send args to hooks from Guard.supervised_task
2) Pass args from hooks to callbacks
3) Suppress UI message from hooks unless in 'development'
2011-04-17 17:06:45 -07:00
Rémy Coutable
5b8ae609da Merge branch 'master' into hook
Conflicts:
	lib/guard/guard.rb
	spec/guard_spec.rb
2011-04-16 23:23:08 +02:00
Rémy Coutable
44aed3264c Added #callback DSL, modified Guard and Guard::Hook a bit in consequence.
Signed-off-by: Rémy Coutable <remy@jilion.com>
2011-04-16 23:02:13 +02:00
Rémy Coutable
d47aebd424 Merge branch 'hook' of git://github.com/monocle/guard 2011-04-16 19:23:08 +02:00
monocle
1d38c59200 Fix preexisting tests after adding default hooks 2011-04-14 13:56:12 -07:00
Thibaud Guillaume-Gentil
bbc63abd5e Updated CHANGELOG (0.3.1 tag) 2011-04-14 13:54:18 -07:00
Thibaud Guillaume-Gentil
1027e4b6b3 Version 0.3.1 2011-04-14 13:54:18 -07:00
Thibaud Guillaume-Gentil
da0d059a43 Updated CHANGELOG 2011-04-14 13:54:18 -07:00
Nico Rieck
154ef207ed Use the correct ANSI escape code to reset SGR parameters. 2011-04-14 13:54:18 -07:00
monocle
7b559ce255 Hooks - Add :begin and :end hook to all guard actions via Guard.supervised_task 2011-04-14 13:31:34 -07:00
monocle
c4ce612bde Added hook/callback feature. 2011-04-10 16:08:43 -07:00
44 changed files with 3786 additions and 1345 deletions

9
.gitignore vendored
View File

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

View File

@ -2,9 +2,18 @@ rvm:
- 1.8.7 - 1.8.7
- 1.9.2 - 1.9.2
- ree - ree
- jruby
- rbx
branches: branches:
only: only:
- master - master
- hook - guard_dependencies
env:
- GUARD_SLEEP=1
notifications: 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
View File

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

View File

@ -1,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 ## 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][]) - [#130](https://github.com/guard/guard/pull/130): Adds `ignore_paths` method 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][]) - [#128](https://github.com/guard/guard/pull/128): Users can add additional settings to `~/.guard.rb` that augment the existing Guardfile. ([@tpope][])
## 0.6.2 - August 17, 2011 ## 0.6.2 - August 17, 2011
### 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][]) - Re-add the possibility to use the `growl` gem since the `growl_notify` gem this is currently known to not work in conjunction with Spork. ([@netzpirat][])
- Ensure that scoped groups and group name are symbolized before checking for inclusion. ([@rymai][]) - Ensure that scoped groups and group name are symbolized before checking for inclusion. ([@rymai][])
### New features: ### New features
- Groups are now stored in a @groups variable (will be used for future features). ([@rymai][]) - Groups are now stored in a `@groups` variable (will be used for future features). ([@rymai][])
- Guards will now receive their group in the options hash at initialization (will be used for future features). ([@rymai][]) - Guards will now receive their group in the options hash at initialization (will be used for future features). ([@rymai][])
### Improvement: ### Improvement
- Explain the growl/growl_notify differences in the README. ([@netzpirat][]) - Explain the growl/growl_notify differences in the README. ([@netzpirat][])
## 0.6.1 - August 15, 2011 ## 0.6.1 - August 15, 2011
### 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][]) - [#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][]) - [#119](https://github.com/guard/guard/pull/119): `Dsl.evaluate_guardfile` uses all groups if none specified. ([@ches][])
## 0.6.0 - August 13, 2011 ## 0.6.0 - August 13, 2011
### Bugs fixes: ### Bug fixes
- Pull request [#107](https://github.com/guard/guard/pull/107): Small spelling fix. ([@dnagir][]) - Pull request [#107](https://github.com/guard/guard/pull/107): Small spelling fix. ([@dnagir][])
- Dir.glob now ignores files that don't need to be watched. ([@rymai][]) - `Dir.glob` now ignores files that don't need to be watched. ([@rymai][])
### New features: ### New feature
- Pull request [#112](https://github.com/guard/guard/pull/112): Add `list` command to CLI. ([@docwhat][]) - Pull request [#112](https://github.com/guard/guard/pull/112): Add `list` command to CLI. ([@docwhat][])
### Improvements: ### Improvements
- Pull request [#99](https://github.com/guard/guard/pull/99): [OS X] Switch from growl gem to growl_notify gem. ([@johnbintz][]) - [#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][]) - [#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][]) - [#95](https://github.com/guard/guard/pull/95): Output system commands and options to be executed when in debug mode. ([@uk-ar][] and [@netzpirat][])
- `Guard::Dsl.revaluate_guardfile` has been renamed to `Guard::Dsl.reevaluate_guardfile`. ([@rymai][]) - `Guard::Dsl.revaluate_guardfile` has been renamed to `Guard::Dsl.reevaluate_guardfile`. ([@rymai][])
- New CLI options: ([@nestegg][]) - New CLI options: ([@nestegg][])
- `watchdir`/`-w` to specify the directory in which Guard should watch for changes, - `watchdir`/`-w` to specify the directory in which Guard should watch for changes,
- `guardfile`/`-G` to specify an alternate location for the Guardfile to use. - `guardfile`/`-G` to specify an alternate location for the Guardfile to use.
- 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 ## 0.5.1 - July 2, 2011
### Bugs fixes: ### Bug fix
- Fixed `guard show` command. ([@bronson][] & [@thibaudgg][]) - Fixed `guard show` command. ([@bronson][] & [@thibaudgg][])
## 0.5.0 - July 2, 2011 ## 0.5.0 - July 2, 2011
### New features: ### New features
- Guard::Ego is now part of Guard, so Guardfile is automagically re-evaluated when modified. ([@thibaudgg][]) - Guard::Ego is now part of Guard, so Guardfile is automagically re-evaluated when modified. ([@thibaudgg][])
- 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][]) - [#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][]) - [#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][]) - [#88](https://github.com/guard/guard/pull/88): Write exception trace in the terminal when a supervised task fail. ([@mcmire][])
- Color in red the "ERROR:" flag when using `UI.error`. ([@rymai][]) - Color in red the "ERROR:" flag when using `UI.error`. ([@rymai][])
- 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][]) - [#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][]) - [#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][]) - [#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][]) - [#80](https://github.com/guard/guard/pull/80): Watching dotfile (hidden files under unix). (reported by [@chrisberkhout][], fixed by [@yannlugrin][])
- Clear the terminal on start when the `:clear` option is given. ([@rymai][]) - Clear the terminal on start when the `:clear` option is given. ([@rymai][])
- Rename home directory Guardfile to `.Guardfile`. ([@tpope][]) - Rename home directory Guardfile to `.Guardfile`. ([@tpope][])
## 0.4.2 - June 7, 2011 ## 0.4.2 - June 7, 2011
### Bugs fixes: ### Bug fixes
- Fixed Guard::Version in ruby 1.8.7 ([@thibaudgg][]) - Fixed Guard::Version in ruby 1.8.7 ([@thibaudgg][])
- Fix ([@mislav][]) link in CHANGELOG (Note: this is a recursive CHANGELOG item). ([@fnichol][]) - Fix ([@mislav][]) link in CHANGELOG (Note: this is a recursive CHANGELOG item). ([@fnichol][])
## 0.4.1 - June 7, 2011 ## 0.4.1 - June 7, 2011
### Improvements: ### Improvements
- 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][]) - Notifier improvement, don't use system notification library if could not be required. ([@yannlugrin][])
## 0.4.0 - June 5, 2011 ## 0.4.0 - June 5, 2011
### Bugs fixes: ### Bug fix
- In Ruby < 1.9, `Symbol#downcase` doesn't exist! ([@rymai][]) - 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][]) - [#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][]) - [#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 ## 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][]) - [#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][]) - [#66](https://github.com/guard/guard/pull/66): Support for dashes in guard names. ([@johnbintz][])
- Require `guard/ui` because `Guard::Notifier` can be required without full Guard. ([@yannlugrin][]) - Require `guard/ui` because `Guard::Notifier` can be required without full Guard. ([@yannlugrin][])
- Handled quick file (<1s) modification. Avoid to catch modified files without content modification (sha1 checksum). ([@thibaudgg][] & [@netzpirat][]) - Handled quick file (<1s) modification. Avoid to catch modified files without content modification (sha1 checksum). ([@thibaudgg][] & [@netzpirat][])
- Fixed `Guard::Notifier` (when growl/libnotify not present). ([@thibaudgg][]) - Fixed `Guard::Notifier` (when growl/libnotify not present). ([@thibaudgg][])
- Fixed Rubygems deprecation messages. ([@thibaudgg][]) - Fixed Rubygems deprecation messages. ([@thibaudgg][])
### New features: ### New features
- Pull request [#67](https://github.com/guard/guard/pull/67): Allow Guardfile in `$HOME` folder. ([@hashrocketeer][]) - [#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][]) - [#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][]) - [#63](https://github.com/guard/guard/pull/63): Refactor listeners to work as a library. ([@niklas][])
- Use `ENV["GUARD_NOTIFY"]` to disable notifications. ([@thibaudgg][]) - Use `ENV["GUARD_NOTIFY"]` to disable notifications. ([@thibaudgg][])
- Cleaning up all specs. ([@netzpirat][]) - Cleaning up all specs. ([@netzpirat][])
- Pull request [#60](https://github.com/guard/guard/pull/60): Added Windows support. ([@stereobooster][]) - [#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][]) - [#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][]) - [#55](https://github.com/guard/guard/pull/55): It is now possible to pass `:guardfile` (a Guardfile path) or `:guardfile_contents` (the content of a Guardfile) to `Guard::Dsl.evaluate_guardfile`. Hence this allows the use of `Guard::Dsl.evaluate_guardfile` in a programmatic manner. ([@anithri][], improved by [@rymai][])
## 0.3.4 - April 24, 2011 ## 0.3.4 - April 24, 2011
### 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 CHANGELOG from RDOC to Markdown and cleaned it! Let's celebrate! ([@rymai][])
- Changed README from RDOC to Markdown! Let's celebrate! ([@thibaudgg][]) - Changed README from RDOC to Markdown! Let's celebrate! ([@thibaudgg][])
- 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 ## 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][]) - Fixed `new_modified_files` rerun conditions on `Guard.run_on_change_for_all_guards`. ([@thibaudgg][])
## 0.3.2 - April 17, 2011 ## 0.3.2 - April 17, 2011
### Bugs fixes: ### Bug fixe
- Pull request [#43](https://github.com/guard/guard/pull/43): Fixed `guard init` command. ([@brainopia][])
- [#43](https://github.com/guard/guard/pull/43): Fixed `guard init` command. ([@brainopia][])
## 0.3.1 - April 14, 2011 ## 0.3.1 - April 14, 2011
### Bugs fixes: ### Bug fixes
- Return unique filenames from Linux listener. (Marian Schubert) - Return unique filenames from Linux listener. (Marian Schubert)
- `Guard.get_guard_class` return wrong class when loaded nested class. ([@koshigoe][]) - `Guard.get_guard_class` return wrong class when loaded nested class. ([@koshigoe][])
- 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][]) - [#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][]) - [#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: ### New feature
- Issue [#28](https://github.com/guard/guard/issues/28): New `-n` command line option to disable notifications (Growl / Libnotify). ([@thibaudgg][])
- [#28](https://github.com/guard/guard/issues/28): New `-n` command line option to disable notifications (Growl / Libnotify). ([@thibaudgg][])
## 0.3.0 - January 19, 2011 ## 0.3.0 - January 19, 2011
### Bugs fixes: ### Bug fix
- Avoid launching `run_on_change` guards method when no files matched. `--clear` guard argument is now usable. ([@thibaudgg][]) - Avoid launching `run_on_change` guards method when no files matched. `--clear` guard argument is now usable. ([@thibaudgg][])
### New features: ### New features
- The whole directory is now watched during `run_on_change` to detect new files modifications. ([@thibaudgg][]) - The whole directory is now watched during `run_on_change` to detect new files modifications. ([@thibaudgg][])
- 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][]) - `watch` patterns are now more strict: strings are matched with `String#==`, `Regexp` are matched with `Regexp#match`. ([@rymai][])
- A deprecation warning is displayed if your `Guardfile` contains `String` that look like `Regexp` (bad!). ([@rymai][]) - A deprecation warning is displayed if your `Guardfile` contains `String` that look like `Regexp` (bad!). ([@rymai][])
- It's now possible to return an `Enumerable` in the `watch` optional blocks in the `Guardfile`. ([@rymai][]) - It's now possible to return an `Enumerable` in the `watch` optional blocks in the `Guardfile`. ([@rymai][])
### New specs: ### New specs
- `Guard::Watcher`. ([@rymai][]) - `Guard::Watcher`. ([@rymai][])
- 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 ## 0.2.2 - October 25, 2010
### Bugs fixes: ### Bug fix
- Issue [#5](https://github.com/guard/guard/issues/5): avoid creating new copy of `fsevent_watch` every time a file is changed. (reported by [@stouset][], fixed by [@thibaudgg][])
- [#5](https://github.com/guard/guard/issues/5): avoid creating new copy of `fsevent_watch` every time a file is changed. (reported by [@stouset][], fixed by [@thibaudgg][])
## 0.2.1 - October 24, 2010 ## 0.2.1 - October 24, 2010
### Bugs fixes: ### Bug fixes
- Pull request [#7](https://github.com/guard/guard/pull/7): Fixes for Linux support. ([@yannlugrin][])
- Pull request [#6](https://github.com/guard/guard/pull/6): Locate guard now chomp newline in result path. ([@yannlugrin][])
- [#7](https://github.com/guard/guard/pull/7): Fixes for Linux support. ([@yannlugrin][])
- [#6](https://github.com/guard/guard/pull/6): Locate guard now chomp newline in result path. ([@yannlugrin][])
## 0.2.0 - October 21, 2010 ## 0.2.0 - October 21, 2010
### 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][]) - [#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][]) - [#2](https://github.com/guard/guard/issues/2): 1.8.6 compatibility. (reported by [@veged][], fixed by [@thibaudgg][])
- Removes Growl & Libnotify dependencies. ([@thibaudgg][]) - Removes Growl & Libnotify dependencies. ([@thibaudgg][])
## 0.2.0.beta.1 - October 17, 2010 ## 0.2.0.beta.1 - October 17, 2010
### New features: ### New features
- Improved listeners support (`rb-fsevent` & `rb-inotify`). ([@thibaudgg][]) - Improved listeners support (`rb-fsevent` & `rb-inotify`). ([@thibaudgg][])
- Added polling listening fallback. ([@thibaudgg][]) - Added polling listening fallback. ([@thibaudgg][])
@ -228,18 +303,26 @@
[@chrisberkhout]: https://github.com/chrisberkhout [@chrisberkhout]: https://github.com/chrisberkhout
[@dnagir]: https://github.com/dnagir [@dnagir]: https://github.com/dnagir
[@docwhat]: https://github.com/docwhat [@docwhat]: https://github.com/docwhat
[@earlonrails]: https://github.com/earlonrails
[@etehtsea]: https://github.com/etehtsea [@etehtsea]: https://github.com/etehtsea
[@f1sherman]: https://github.com/f1sherman
[@fabioyamate]: https://github.com/fabioyamate
[@fnichol]: https://github.com/fnichol [@fnichol]: https://github.com/fnichol
[@Gazer]: https://github.com/Gazer [@Gazer]: https://github.com/Gazer
[@gix]: https://github.com/gix [@gix]: https://github.com/gix
[@hron]: https://github.com/hron
[@hardipe]: https://github.com/hardipe
[@hashrocketeer]: https://github.com/hashrocketeer [@hashrocketeer]: https://github.com/hashrocketeer
[@ianwhite]: https://github.com/ianwhite
[@indirect]: https://github.com/indirect [@indirect]: https://github.com/indirect
[@jeffutter]: https://github.com/jeffutter [@jeffutter]: https://github.com/jeffutter
[@johnbintz]: https://github.com/johnbintz [@johnbintz]: https://github.com/johnbintz
[@jrsacks]: https://github.com/jrsacks [@jrsacks]: https://github.com/jrsacks
[@koshigoe]: https://github.com/koshigoe [@koshigoe]: https://github.com/koshigoe
[@limeyd]: https://github.com/limeyd
[@mcmire]: https://github.com/mcmire [@mcmire]: https://github.com/mcmire
[@mislav]: https://github.com/mislav [@mislav]: https://github.com/mislav
[@monocle]: https://github.com/monocle
[@mordaroso]: https://github.com/mordaroso [@mordaroso]: https://github.com/mordaroso
[@nestegg]: https://github.com/nestegg [@nestegg]: https://github.com/nestegg
[@netzpirat]: https://github.com/netzpirat [@netzpirat]: https://github.com/netzpirat
@ -247,9 +330,12 @@
[@niklas]: https://github.com/niklas [@niklas]: https://github.com/niklas
[@oliamb]: https://github.com/oliamb [@oliamb]: https://github.com/oliamb
[@pcreux]: https://github.com/pcreux [@pcreux]: https://github.com/pcreux
[@rmm5t]: https://github.com/rmm5t
[@rymai]: https://github.com/rymai [@rymai]: https://github.com/rymai
[@scottdavis]: https://github.com/scottdavis
[@stereobooster]: https://github.com/stereobooster [@stereobooster]: https://github.com/stereobooster
[@stouset]: https://github.com/stouset [@stouset]: https://github.com/stouset
[@sunaku]: https://github.com/sunaku
[@thibaudgg]: https://github.com/thibaudgg [@thibaudgg]: https://github.com/thibaudgg
[@thierryhenrio]: https://github.com/thierryhenrio [@thierryhenrio]: https://github.com/thierryhenrio
[@tinogomes]: https://github.com/tinogomes [@tinogomes]: https://github.com/tinogomes

View File

@ -13,12 +13,10 @@ require 'rbconfig'
if RbConfig::CONFIG['target_os'] =~ /darwin/i if RbConfig::CONFIG['target_os'] =~ /darwin/i
gem 'rb-fsevent', '>= 0.4.0', :require => false gem 'rb-fsevent', '>= 0.4.0', :require => false
gem 'growl', '~> 1.0.3', :require => false gem 'growl', '~> 1.0.3', :require => false
end elsif RbConfig::CONFIG['target_os'] =~ /linux/i
if RbConfig::CONFIG['target_os'] =~ /linux/i
gem 'rb-inotify', '>= 0.8.5', :require => false gem 'rb-inotify', '>= 0.8.5', :require => false
gem 'libnotify', '~> 0.1.3', :require => false gem 'libnotify', '~> 0.1.3', :require => false
end elsif RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
gem 'win32console', :require => false gem 'win32console', :require => false
gem 'rb-fchange', '>= 0.0.2', :require => false gem 'rb-fchange', '>= 0.0.2', :require => false
gem 'rb-notifu', '>= 0.0.4', :require => false gem 'rb-notifu', '>= 0.0.4', :require => false

View File

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

486
README.md
View File

@ -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. 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). * 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). * 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)). * 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 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
------- -------
Install the gem: Install the gem:
``` bash $ gem install guard
$ gem install guard
```
Or add it to your Gemfile (inside the `development` group): Or add it to your Gemfile (inside the `development` group):
``` ruby gem 'guard'
gem 'guard'
```
and install it via Bundler: and install it via Bundler:
``` bash $ bundle install
$ bundle install
```
Generate an empty Guardfile with: Generate an empty Guardfile with:
``` bash $ guard init
$ guard init
```
You may optionally place a .Guardfile in your home directory to use it across multiple projects. You may optionally place a .Guardfile in your home directory to use it across multiple projects.
Also note that if a `.guard.rb` is found in your home directory, it will be appended to the Guardfile. Also note that if a `.guard.rb` is found in your home directory, it will be appended to the Guardfile.
Add the guards you need to your Guardfile (see the existing guards below). Add the guards you need to your Guardfile (see the existing guards below).
Now, be sure to read the particular instructions for your operating system: [Mac OS X](#mac) | [Linux](#linux) | [Windows](#win)
<a name="mac" />
### On Mac OS X ### On Mac OS X
Install the rb-fsevent gem for [FSEvent](http://en.wikipedia.org/wiki/FSEvents) support: Install the rb-fsevent gem for [FSEvent](http://en.wikipedia.org/wiki/FSEvents) support:
``` 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 Use the [ruby_gntp gem](https://github.com/snaka/ruby_gntp):
$ brew install growlnotify
$ gem install growl
```
And add them to your Gemfile: $ gem install ruby_gntp
``` ruby The `ruby_gntp` gem is compatible with Growl >= 1.3 and uses the Growl Notification Transport Protocol to send Growl
gem 'rb-fsevent' notifications. Guard supports multiple notification channels for customizing each notification type, but it's limited
gem 'growl_notify' # or gem 'growl' to the local host currently.
```
The difference between growl and growl_notify is that growl_notify uses AppleScript to Use the [growl gem](https://rubygems.org/gems/growl):
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. $ 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 ### On Linux
Install the [rb-inotify gem](https://rubygems.org/gems/rb-inotify) for [inotify](http://en.wikipedia.org/wiki/Inotify) support: Install the [rb-inotify gem](https://rubygems.org/gems/rb-inotify) for [inotify](http://en.wikipedia.org/wiki/Inotify) support:
``` bash $ gem install rb-inotify
$ gem install rb-inotify
```
Install the [libnotify gem](https://rubygems.org/gems/libnotify) if you want visual notification support: Install the [libnotify gem](https://rubygems.org/gems/libnotify) if you want visual notification support:
``` bash $ gem install libnotify
$ gem install libnotify
```
And add them to your Gemfile: And add them to your Gemfile:
``` ruby gem 'rb-inotify'
gem 'rb-inotify' gem 'libnotify'
gem 'libnotify'
``` <a name="win" />
### On Windows ### On Windows
Install the [rb-fchange gem](https://rubygems.org/gems/rb-fchange) for [Directory Change Notification](http://msdn.microsoft.com/en-us/library/aa365261\(VS.85\).aspx) support: Install the [rb-fchange gem](https://rubygems.org/gems/rb-fchange) for [Directory Change Notification](http://msdn.microsoft.com/en-us/library/aa365261\(VS.85\).aspx) support:
``` bash $ gem install rb-fchange
$ gem install rb-fchange
```
Install the [win32console gem](https://rubygems.org/gems/win32console) if you want colors in your terminal: Install the [win32console gem](https://rubygems.org/gems/win32console) if you want colors in your terminal:
``` bash $ gem install win32console
$ gem install win32console
```
Install the [rb-notifu gem](https://rubygems.org/gems/rb-notifu) if you want visual notification support: Install the [rb-notifu gem](https://rubygems.org/gems/rb-notifu) if you want visual notification support:
``` bash $ gem install rb-notifu
$ gem install rb-notifu
```
And add them to your Gemfile: And add them to your Gemfile:
``` ruby gem 'rb-fchange'
gem 'rb-fchange' gem 'rb-notifu'
gem 'rb-notifu' gem 'win32console'
```
Usage Usage
----- -----
Just launch Guard inside your Ruby / Rails project with: Just launch Guard inside your Ruby / Rails project with:
``` bash $ guard [start]
$ guard [start]
```
or if you use Bundler, to run the Guard executable specific to your bundle: or if you use Bundler, to run the Guard executable specific to your bundle:
``` bash $ bundle exec guard [start]
$ bundle exec guard [start]
```
Guard will look for a Guardfile in your current directory. If it does not find one, it will look in your `$HOME` directory for a .Guardfile. Guard will look for a Guardfile in your current directory. If it does not find one, it will look in your `$HOME` directory for a .Guardfile.
@ -159,19 +148,15 @@ Command line options
Shell can be cleared after each change: Shell can be cleared after each change:
``` bash $ guard --clear
$ guard --clear $ guard -c # shortcut
$ guard -c # shortcut
```
### `-n`/`--notify` option ### `-n`/`--notify` option
Notifications (growl/libnotify) can be disabled: Notifications (growl/libnotify) can be disabled:
``` bash $ guard --notify false
$ guard --notify false $ guard -n f # shortcut
$ guard -n f # shortcut
```
Notifications can also be disabled globally by setting a `GUARD_NOTIFY` environment variable to `false` Notifications can also be disabled globally by setting a `GUARD_NOTIFY` environment variable to `false`
@ -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): Only certain guards groups can be run (see the Guardfile DSL below for creating groups):
``` bash $ guard --group group_name another_group_name
$ guard --group group_name another_group_name $ guard -g group_name another_group_name # shortcut
$ guard -g group_name another_group_name # shortcut
```
### `-d`/`--debug` option ### `-d`/`--debug` option
Guard can be run in debug mode: Guard can be run in debug mode:
``` bash $ guard --debug
$ guard --debug $ guard -d # shortcut
$ guard -d # shortcut
```
### `-w`/`--watchdir` option ### `-w`/`--watchdir` option
Guard can watch in any directory (instead of the current directory): Guard can watch in any directory (instead of the current directory):
``` bash $ guard --watchdir ~/your/fancy/project
$ guard --watchdir ~/your/fancy/project $ guard -w ~/your/fancy/project # shortcut
$ guard -w ~/your/fancy/project # shortcut
```
### `-G`/`--guardfile` option ### `-G`/`--guardfile` option
Guard can use a Guardfile not located in the current directory: Guard can use a Guardfile not located in the current directory:
``` bash $ guard --guardfile ~/.your_global_guardfile
$ guard --guardfile ~/.your_global_guardfile $ guard -G ~/.your_global_guardfile # shortcut
$ guard -G ~/.your_global_guardfile # shortcut
``` ### `-A`/`--watch-all-modifications` option
Guard can optionally watch all file modifications like moves or deletions with:
$ guard start -A
$ guard start --watch-all-modifications
### `-i`/`--no-interactions` option
Turn off completely any Guard terminal [interactions](#interactions) with:
$ guard start -A
$ guard start --watch-all-modifications
An exhaustive list of options is available with: An exhaustive list of options is available with:
``` 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. **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).**
* `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.
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 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): Add it to your Gemfile (inside the `development` group):
``` ruby gem '<guard-name>'
gem '<guard-name>'
```
You can list all guards installed on your system with: You can list all guards installed on your system with:
``` bash $ guard list
$ guard list
```
Insert default guard's definition to your Guardfile by running this command: Insert default guard's definition to your Guardfile by running this command:
``` bash $ guard init <guard-name>
$ guard init <guard-name>
```
You are good to go, or you can modify your guards' definition to suit your needs. You are good to go, or you can modify your guards' definition to suit your needs.
Guardfile DSL Guardfile DSL
------------- -------------
The Guardfile DSL consists of just three simple methods: `#guard`, `#watch` & `#group`. The Guardfile DSL consists of the following methods:
Required: * `#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.
* The `#guard` method allows you to add a guard with an optional hash of options. * `#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.
Optional: * `#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.
* 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.
Example: Example:
``` ruby ignore_paths 'foo', 'bar'
ignore_paths 'foo', 'bar'
group 'backend' do group 'backend' do
guard 'bundler' do guard 'bundler' do
watch('Gemfile') watch('Gemfile')
end end
guard 'rspec', :cli => '--color --format doc' do guard 'rspec', :cli => '--color --format doc' do
# Regexp watch patterns are matched with Regexp#match # Regexp watch patterns are matched with Regexp#match
watch(%r{^spec/.+_spec\.rb$}) watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
watch(%r{^spec/models/.+\.rb$}) { ["spec/models", "spec/acceptance"] } watch(%r{^spec/models/.+\.rb$}) { ["spec/models", "spec/acceptance"] }
watch(%r{^spec/.+\.rb$}) { `say hello` } watch(%r{^spec/.+\.rb$}) { `say hello` }
# String watch patterns are matched with simple '==' # String watch patterns are matched with simple '=='
watch('spec/spec_helper.rb') { "spec" } watch('spec/spec_helper.rb') { "spec" }
end end
end end
group 'frontend' do group 'frontend' do
guard 'coffeescript', :output => 'public/javascripts/compiled' do guard 'coffeescript', :output => 'public/javascripts/compiled' do
watch(%r{^app/coffeescripts/.+\.coffee$}) watch(%r{^app/coffeescripts/.+\.coffee$})
end end
guard 'livereload' do guard 'livereload' do
watch(%r{^app/.+\.(erb|haml)$}) watch(%r{^app/.+\.(erb|haml)$})
end end
end end
```
### Using a Guardfile without the `guard` binary Using a Guardfile without the `guard` binary
--------------------------------------------
The Guardfile DSL can also be used in a programmatic fashion by calling directly `Guard::Dsl.evaluate_guardfile`. The Guardfile DSL can also be used in a programmatic fashion by calling directly `Guard::Dsl.evaluate_guardfile`.
Available options are as follow: 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: For instance, you could use it as follow:
``` ruby gem 'guard'
gem 'guard' require 'guard'
require 'guard'
Guard.setup Guard.setup
Guard::Dsl.evaluate_guardfile(:guardfile => '/your/custom/path/to/a/valid/Guardfile') Guard::Dsl.evaluate_guardfile(:guardfile => '/your/custom/path/to/a/valid/Guardfile')
# or # or
Guard::Dsl.evaluate_guardfile(:guardfile_contents => " Guard::Dsl.evaluate_guardfile(:guardfile_contents => "
guard 'rspec' do guard 'rspec' do
watch(%r{^spec/.+_spec\.rb$}) watch(%r{^spec/.+_spec\.rb$})
end end
") ")
```
### Listing defined guards/groups for the current project ### Listing defined guards/groups for the current project
You can list the defined groups and guards for the current Guardfile from the command line using `guard show` or `guard -T`: You can list the defined groups and guards for the current Guardfile from the command line using `guard show` or `guard -T`:
``` bash $ guard -T
# guard -T
(global): (global):
shell shell
Group backend: Group backend:
bundler bundler
rspec: cli => "--color --format doc" rspec: cli => "--color --format doc"
Group frontend: Group frontend:
coffeescript: output => "public/javascripts/compiled" coffeescript: output => "public/javascripts/compiled"
livereload livereload
```
User config file User config file
---------------- ----------------
@ -355,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 other users probably don't. For example, indexing your source tree with
[Ctags](http://ctags.sourceforge.net): [Ctags](http://ctags.sourceforge.net):
``` ruby guard 'shell' do
guard 'shell' do watch(%r{^(?:app|lib)/.+\.rb$}) { `ctags -R` }
watch(%r{^(?:app|lib)/.+\.rb$}) { `ctags -R` } end
end
```
Create a new guard Create a new guard
------------------ ------------------
Creating a new guard is very easy, just create a new gem (`bundle gem` if you use Bundler) with this basic structure: Creating a new guard is very easy, just create a new gem (`bundle gem` if you use Bundler) with this basic structure:
``` .travis.yml # bonus point!
.travis.yml # bonus point! CHANGELOG.md # bonus point!
CHANGELOG.md # bonus point! Gemfile
Gemfile guard-name.gemspec
guard-name.gemspec Guardfile
Guardfile lib/
lib/ guard/
guard/ guard-name/
guard-name/ templates/
templates/ Guardfile # needed for `guard init <guard-name>`
Guardfile # needed for `guard init <guard-name>` version.rb
version.rb guard-name.rb
guard-name.rb test/ # or spec/
test/ # or spec/ README.md
README.md
```
`Guard::GuardName` (in `lib/guard/guard-name.rb`) must inherit from `Guard::Guard` and should overwrite at least one of the five basic `Guard::Guard` instance methods. `Guard::GuardName` (in `lib/guard/guard-name.rb`) must inherit from
[Guard::Guard](http://rubydoc.info/github/guard/guard/master/Guard/Guard) and should overwrite at least one of
the basic `Guard::Guard` task methods.
Here is an example scaffold for `lib/guard/guard-name.rb`: Here is an example scaffold for `lib/guard/guard-name.rb`:
``` ruby require 'guard'
require 'guard' require 'guard/guard'
require 'guard/guard'
module Guard module Guard
class GuardName < Guard class GuardName < Guard
def initialize(watchers=[], options={}) # Initialize a Guard.
super # @param [Array<Guard::Watcher>] watchers the Guard file watchers
# init stuff here, thx! # @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 end
# ================= Please take a look at the [existing guards' source code](https://github.com/guard/guard/wiki/List-of-available-Guards)
# = Guard methods = for more concrete example and inspiration.
# =================
# If one of those methods raise an exception, the Guard::GuardName instance
# will be removed from the active guards.
# Called once when Guard starts
# Please override initialize method to init stuff
def start
true
end
# Called on Ctrl-C signal (when Guard quits)
def stop
true
end
# Called on Ctrl-Z signal
# This method should be mainly used for "reload" (really!) actions like reloading passenger/spork/bundler/...
def reload
true
end
# Called on Ctrl-\ signal
# This method should be principally used for long action like running all specs/tests/...
def run_all
true
end
# Called on file(s) modifications
def run_on_change(paths)
true
end
end
end
```
Please take a look at the [existing guards' source code](https://github.com/guard/guard/wiki/List-of-available-Guards) for more concrete example and inspiration.
Alternatively, a new guard can be added inline to a Guardfile with this basic structure: Alternatively, a new guard can be added inline to a Guardfile with this basic structure:
``` ruby require 'guard/guard'
require 'guard/guard'
module ::Guard module ::Guard
class InlineGuard < ::Guard::Guard class InlineGuard < ::Guard::Guard
def run_all def run_all
true end
def run_on_change(paths)
end
end
end end
def run_on_change(paths) 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)
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
Development Development
----------- -----------
* Documentation hosted at [RubyDoc](http://rubydoc.info/github/guard/guard/master/frames).
* Source hosted at [GitHub](https://github.com/guard/guard). * Source hosted at [GitHub](https://github.com/guard/guard).
* Report issues and feature requests to [GitHub Issues](https://github.com/guard/guard/issues). * Report issues and feature requests to [GitHub Issues](https://github.com/guard/guard/issues).
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). 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) [Thibaud Guillaume-Gentil](https://github.com/thibaudgg)
Contributors Contributors
------ ------------
https://github.com/guard/guard/contributors [https://github.com/guard/guard/contributors](https://github.com/guard/guard/contributors)

View File

@ -39,8 +39,8 @@ namespace(:spec) do
echo "`ruby -v`"; echo "`ruby -v`";
for ((c=1; c<$ruby_version_string_size; c++)); do echo -n "="; done for ((c=1; c<$ruby_version_string_size; c++)); do echo -n "="; done
echo; echo;
bundle install; RBXOPT="-Xrbc.db" bundle install;
bundle exec rspec spec -f doc 2>&1;' RBXOPT="-Xrbc.db" bundle exec rspec spec -f doc 2>&1;'
BASH BASH
end end
end end

View File

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

View File

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

View File

@ -1,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 module Guard
autoload :UI, 'guard/ui' autoload :UI, 'guard/ui'
autoload :Dsl, 'guard/dsl' autoload :Dsl, 'guard/dsl'
autoload :DslDescriber, 'guard/dsl_describer' autoload :DslDescriber, 'guard/dsl_describer'
autoload :Group, 'guard/group'
autoload :Interactor, 'guard/interactor' autoload :Interactor, 'guard/interactor'
autoload :Listener, 'guard/listener' autoload :Listener, 'guard/listener'
autoload :Watcher, 'guard/watcher' autoload :Watcher, 'guard/watcher'
autoload :Notifier, 'guard/notifier' autoload :Notifier, 'guard/notifier'
autoload :Hook, 'guard/hook'
class << self 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 = {}) def setup(options = {})
@options = options @lock = Mutex.new
@listener = Listener.select_and_init(@options[:watchdir] ? File.expand_path(@options[:watchdir]) : Dir.pwd)
@groups = [:default]
@guards = []
@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] UI.clear if @options[:clear]
debug_command_execution if @options[:debug] debug_command_execution if @options[:debug]
self self
end end
def start(options = {}) # Smart accessor for retrieving a specific guard or several guards at once.
Interactor.init_signal_traps #
# @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) setup(options)
Dsl.evaluate_guardfile(options) Dsl.evaluate_guardfile(options)
listener.on_change do |files| listener.on_change do |files|
Dsl.reevaluate_guardfile if Watcher.match_guardfile?(files) Dsl.reevaluate_guardfile if Watcher.match_guardfile?(files)
listener.changed_files += files if Watcher.match_files?(guards, files)
run { run_on_change_for_all_guards(files) } if Watcher.match_files?(guards, files)
end end
UI.info "Guard is now watching at '#{listener.directory}'" UI.info "Guard is now watching at '#{ listener.directory }'"
guards.each { |guard| supervised_task(guard, :start) }
run_guard_task(:start)
interactor.start if interactor
listener.start listener.start
end end
def run_on_change_for_all_guards(files) # Stop Guard listening to file changes
guards.each do |guard| #
paths = Watcher.match_files(guard, files) def stop
unless paths.empty? UI.info 'Bye bye...', :reset => true
UI.debug "#{guard.class.name}#run_on_change with #{paths.inspect}"
supervised_task(guard, :run_on_change, paths) 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 end
end
# Reparse the whole directory to catch new files modified during the guards run # Run the `:run_on_change` task. When the option `:watch_all_modifications` is set,
new_modified_files = listener.modified_files([listener.directory], :all => true) # the task is split to run changed paths on {Guard::Guard#run_on_change}, whereas
if !new_modified_files.empty? && Watcher.match_files?(guards, new_modified_files) # deleted paths run on {Guard::Guard#run_on_deletion}.
run { run_on_change_for_all_guards(new_modified_files) } #
# @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
end end
# Let a guard execute its task but # Detects the paths that have changed.
# fire it if his work leads to a system failure #
def supervised_task(guard, task_to_supervise, *args) # Deleted paths are prefixed by an exclamation point.
guard.send(task_to_supervise, *args) # @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 rescue Exception => ex
UI.error("#{guard.class.name} failed to achieve its <#{task_to_supervise.to_s}>, exception was:" + UI.error("#{ guard.class.name } failed to achieve its <#{ task.to_s }>, exception was:" +
"\n#{ex.class}: #{ex.message}\n#{ex.backtrace.join("\n")}") "\n#{ ex.class }: #{ ex.message }\n#{ ex.backtrace.join("\n") }")
guards.delete guard guards.delete guard
UI.info("\n#{guard.class.name} has just been fired") UI.info("\n#{ guard.class.name } has just been fired")
return ex
ex
end end
def run # Get the symbol we have to catch when running a supervised task.
listener.stop # If we are within a Guard group that has the `:halt_on_fail`
UI.clear if options[:clear] # option set, we do NOT catch it here, it will be catched at the
begin # group level.
yield #
rescue Interrupt # @see .run_guard_task
end #
listener.start # @param [Guard::Guard] guard the Guard to execute
end # @return [Symbol] the symbol to catch
#
def add_guard(name, watchers = [], options = {}) def guard_symbol(guard)
if name.to_sym == :ego if guard.group.class == Symbol
UI.deprecation("Guard::Ego is now part of Guard. You can remove it from your Guardfile.") group = groups(guard.group)
group.options[:halt_on_fail] ? :no_catch : :task_has_failed
else else
guard = get_guard_class(name).new(watchers, options) :task_has_failed
@guards << guard
end end
end end
def add_group(name) # Add a Guard to use.
@groups << name.to_sym unless name.nil? #
# @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 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) def get_guard_class(name)
name = name.to_s name = name.to_s
try_require = false try_require = false
const_name = name.downcase.gsub('-', '') const_name = name.downcase.gsub('-', '')
begin begin
require "guard/#{name.downcase}" if try_require require "guard/#{ name.downcase }" if try_require
self.const_get(self.constants.find { |c| c.to_s.downcase == const_name }) self.const_get(self.constants.find { |c| c.to_s.downcase == const_name })
rescue TypeError rescue TypeError
unless try_require unless try_require
try_require = true try_require = true
retry retry
else else
UI.error "Could not find class Guard::#{const_name.capitalize}" UI.error "Could not find class Guard::#{ const_name.capitalize }"
end end
rescue LoadError => loadError rescue LoadError => loadError
UI.error "Could not load 'guard/#{name.downcase}' or find class Guard::#{const_name.capitalize}" UI.error "Could not load 'guard/#{ name.downcase }' or find class Guard::#{ const_name.capitalize }"
UI.error loadError.to_s UI.error loadError.to_s
end end
end end
# Locate a path to a Guard gem.
#
# @param [String] name the name of the Guard without the prefix `guard-`
# @return [String] the full path to the Guard gem
#
def locate_guard(name) def locate_guard(name)
if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0') if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
Gem::Specification.find_by_name("guard-#{name}").full_gem_path Gem::Specification.find_by_name("guard-#{ name }").full_gem_path
else else
Gem.source_index.find_name("guard-#{name}").last.full_gem_path Gem.source_index.find_name("guard-#{ name }").last.full_gem_path
end end
rescue rescue
UI.error "Could not find 'guard-#{name}' gem path." UI.error "Could not find 'guard-#{ name }' gem path."
end end
##
# Returns a list of guard Gem names installed locally. # Returns a list of guard Gem names installed locally.
#
# @return [Array<String>] a list of guard gem names
#
def guard_gem_names def guard_gem_names
if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0') if Gem::Version.create(Gem::VERSION) >= Gem::Version.create('1.8.0')
Gem::Specification.find_all.select { |x| x.name =~ /^guard-/ } Gem::Specification.find_all.select { |x| x.name =~ /^guard-/ }
@ -135,16 +415,19 @@ module Guard
end.map { |x| x.name.sub /^guard-/, '' } end.map { |x| x.name.sub /^guard-/, '' }
end end
# Adds a command logger in debug mode. This wraps common command
# execution functions and logs the executed command before execution.
#
def debug_command_execution def debug_command_execution
Kernel.send(:alias_method, :original_system, :system) Kernel.send(:alias_method, :original_system, :system)
Kernel.send(:define_method, :system) do |command, *args| Kernel.send(:define_method, :system) do |command, *args|
::Guard::UI.debug "Command execution: #{command} #{args.join(' ')}" ::Guard::UI.debug "Command execution: #{ command } #{ args.join(' ') }"
original_system command, *args original_system command, *args
end end
Kernel.send(:alias_method, :original_backtick, :"`") Kernel.send(:alias_method, :original_backtick, :'`')
Kernel.send(:define_method, :"`") do |command| Kernel.send(:define_method, :'`') do |command|
::Guard::UI.debug "Command execution: #{command}" ::Guard::UI.debug "Command execution: #{ command }"
original_backtick command original_backtick command
end end
end end

View File

@ -2,89 +2,118 @@ require 'thor'
require 'guard/version' require 'guard/version'
module Guard module Guard
# Facade for the Guard command line interface managed by [Thor](https://github.com/wycats/thor).
# This is the main interface to Guard that is called by the Guard binary `bin/guard`.
# Do not put any logic in here, create a class and delegate instead.
#
class CLI < Thor class CLI < Thor
default_task :start default_task :start
method_option :clear, :type => :boolean, :default => false, :aliases => '-c', :banner => "Auto clear shell before each change/run_all/reload" desc 'start', 'Starts Guard'
method_option :notify, :type => :boolean, :default => true, :aliases => '-n', :banner => "Notifications feature (growl/libnotify)"
method_option :debug, :type => :boolean, :default => false, :aliases => '-d', :banner => "Print debug messages"
method_option :group, :type => :array, :default => [], :aliases => '-g', :banner => "Run only the passed groups"
method_option :watchdir, :type => :string, :aliases => '-w', :banner => "Specify the directory to watch"
method_option :guardfile, :type => :string, :aliases => '-G', :banner => "Specify a Guardfile"
desc "start", "Starts Guard" method_option :clear,
:type => :boolean,
:default => false,
:aliases => '-c',
:banner => 'Auto clear shell before each change/run_all/reload'
method_option :notify,
:type => :boolean,
:default => true,
:aliases => '-n',
:banner => 'Notifications feature (growl/libnotify)'
method_option :debug,
:type => :boolean,
:default => false,
:aliases => '-d',
:banner => 'Print debug messages'
method_option :group,
:type => :array,
:default => [],
:aliases => '-g',
:banner => 'Run only the passed groups'
method_option :watchdir,
:type => :string,
:aliases => '-w',
:banner => 'Specify the directory to watch'
method_option :guardfile,
:type => :string,
:aliases => '-G',
:banner => 'Specify a Guardfile'
method_option :watch_all_modifications,
:type => :boolean,
:default => false,
:aliases => '-A',
:banner => 'Watch for all file modifications including moves and deletions'
method_option :no_interactions,
:type => :boolean,
:default => false,
:aliases => '-i',
:banner => 'Turn off completely any guard terminal interactions'
# Start Guard by initialize the defined Guards and watch the file system.
# This is the default task, so calling `guard` is the same as calling `guard start`.
#
# @see Guard.start
#
def start def start
::Guard.start(options) ::Guard.start(options)
end end
desc "list", "Lists guards that can be used with init" desc '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 def list
::Guard::DslDescriber.evaluate_guardfile(options) ::Guard::DslDescriber.list(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"
end end
desc "version", "Prints Guard's version" desc 'version', 'Show the Guard version'
def version
::Guard::UI.info "Guard version #{Guard::VERSION}"
end
map %w(-v --version) => :version map %w(-v --version) => :version
desc "init [GUARD]", "Generates a Guardfile into the current working directory, or insert the given GUARD in an existing Guardfile" # Shows the current version of Guard.
#
# @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) def init(guard_name = nil)
if !File.exist?("Guardfile") ::Guard.initialize_template(guard_name)
puts "Writing new Guardfile to #{Dir.pwd}/Guardfile"
FileUtils.cp(File.expand_path('../templates/Guardfile', __FILE__), 'Guardfile')
elsif guard_name.nil?
::Guard::UI.error "Guardfile already exists at #{Dir.pwd}/Guardfile"
exit 1
end
if guard_name
guard_class = ::Guard.get_guard_class(guard_name)
guard_class.init(guard_name)
end
end end
desc "show", "Show all defined Guards and their options" 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
map %w(-T) => :show map %w(-T) => :show
# Shows all Guards and their options that are defined in
# the `Guardfile`.
#
# @see Guard::DslDescriber.show
#
def show
::Guard::DslDescriber.show(options)
end
end end
end end

View File

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

View File

@ -1,29 +1,150 @@
require 'guard/dsl' require 'guard/dsl'
module Guard module Guard
autoload :UI, 'guard/ui'
# The DslDescriber overrides methods to create an internal structure
# of the Guardfile that is used in some inspection utility methods
# like the CLI commands `show` and `list`.
#
# @see Guard::Dsl
# @see Guard::CLI
#
class DslDescriber < Dsl class DslDescriber < Dsl
@@guardfile_structure = [ { :guards => [] } ]
class << self class << self
# Evaluate the DSL methods in the `Guardfile`.
#
# @option options [Array<Symbol,String>] groups the groups to evaluate
# @option options [String] guardfile the path to a valid Guardfile
# @option options [String] guardfile_contents a string representing the content of a valid Guardfile
# @raise [ArgumentError] when options are not a Hash
#
def evaluate_guardfile(options = {})
@@guardfile_structure = [{ :guards => [] }]
super options
end
# List the Guards that are available for use in your system and marks
# those that are currently used in your `Guardfile`.
#
# @example Guard list output
#
# Available guards:
# bundler *
# livereload
# ronn
# rspec *
# spork
#
# See also https://github.com/guard/guard/wiki/List-of-available-Guards
# * denotes ones already in your Guardfile
#
# @param [Hash] options the Guard options
#
def list(options)
evaluate_guardfile(options)
installed = guardfile_structure.inject([]) do |installed, group|
group[:guards].each { |guard| installed << guard[:name] } if group[:guards]
installed
end
UI.info 'Available guards:'
::Guard.guard_gem_names.sort.uniq.each do |name|
UI.info " #{ name }#{ installed.include?(name) ? '*' : '' }"
end
UI.info ''
UI.info 'See also https://github.com/guard/guard/wiki/List-of-available-Guards'
UI.info '* denotes ones already in your Guardfile'
end
# Shows all Guards and their options that are defined in
# the `Guardfile`.
#
# @example guard show output
#
# (global):
# bundler
# coffeescript: input => "app/assets/javascripts", noop => true
# jasmine
# rspec: cli => "--fail-fast --format Fuubar
#
# @param [Hash] options the Guard options
#
def show(options)
evaluate_guardfile(options)
guardfile_structure.each do |group|
unless group[:guards].empty?
if group[:group]
UI.info "Group #{ group[:group] }:"
else
UI.info '(global):'
end
group[:guards].each do |guard|
line = " #{ guard[:name] }"
unless guard[:options].empty?
line += ": #{ guard[:options].sort.collect { |k, v| "#{ k } => #{ v.inspect }" }.join(', ') }"
end
UI.info line
end
end
end
UI.info ''
end
private
# Get the Guardfile structure.
#
# @return [Array<Hash>] the structure
#
def guardfile_structure def guardfile_structure
@@guardfile_structure @@guardfile_structure
end end
end end
private private
def group(name, &guard_definition)
@@guardfile_structure << { :group => name.to_sym, :guards => [] }
# Declares a group of guards.
#
# @param [String] name the group's name called from the CLI
# @yield a block where you can declare several guards
#
# @see Guard::Dsl#group
#
def group(name)
@@guardfile_structure << { :group => name.to_sym, :guards => [] }
@group = true @group = true
guard_definition.call
yield if block_given?
@group = false @group = false
end end
def guard(name, options = {}, &watch_definition) # Declares a Guard.
#
# @param [String] name the Guard name
# @param [Hash] options the options accepted by the Guard
# @yield a block where you can declare several watch patterns and actions
#
# @see Guard::Dsl#guard
#
def guard(name, options = { })
node = (@group ? @@guardfile_structure.last : @@guardfile_structure.first) node = (@group ? @@guardfile_structure.last : @@guardfile_structure.first)
node[:guards] << { :name => name, :options => options } node[:guards] << { :name => name, :options => options }
end end
end end
end end

37
lib/guard/group.rb Normal file
View 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

View File

@ -1,57 +1,129 @@
module Guard module Guard
# Main class that every Guard implementation must subclass.
#
# Guard will trigger the `start`, `stop`, `reload`, `run_all`, `run_on_change` and
# `run_on_deletion` task methods depending on user interaction and file modification.
#
# In each of these Guard task methods you have to implement some work when you want to
# support this kind of task. The return value of each Guard task method is not evaluated
# by Guard, but I'll be passed to the "_end" hook for further evaluation. You can
# throw `:task_has_failed` to indicate that your Guard method was not successful,
# and successive guard tasks will be aborted when the group has set the `:halt_on_fail`
# option.
#
# @see Guard::Hook
# @see Guard::Group
#
# @example Throw :task_has_failed
#
# def run_all
# if !runner.run(['all'])
# throw :task_has_failed
# end
# end
#
# Each Guard should provide a template Guardfile located within the Gem
# at `lib/guard/guard-name/templates/Guardfile`.
#
# By default all watchers for a Guard are returning strings of paths to the
# Guard, but if your Guard want to allow any return value from a watcher,
# you can set the `any_return` option to true.
#
# If one of those methods raise an exception other than `:task_has_failed`,
# the Guard::GuardName instance will be removed from the active guards.
#
class Guard class Guard
include Hook
attr_accessor :watchers, :options, :group attr_accessor :watchers, :options, :group
# Initialize a Guard.
#
# @param [Array<Guard::Watcher>] watchers the Guard file watchers
# @param [Hash] options the custom Guard options
# @options [Symbol] group the group this Guard belongs to
# @options [Boolean] any_return allow any object to be returned from a watcher
#
def initialize(watchers = [], options = {}) def initialize(watchers = [], options = {})
@group = options.delete(:group) || :default @group = options[:group] ? options.delete(:group).to_sym : :default
@watchers, @options = watchers, options @watchers, @options = watchers, options
end end
# Guardfile template needed inside guard gem # Initialize the Guard. This will copy the Guardfile template inside the Guard gem.
# The template Guardfile must be located within the Gem at `lib/guard/guard-name/templates/Guardfile`.
#
# @param [String] name the name of the Guard
#
def self.init(name) def self.init(name)
if ::Guard::Dsl.guardfile_include?(name) if ::Guard::Dsl.guardfile_include?(name)
::Guard::UI.info "Guardfile already includes #{name} guard" ::Guard::UI.info "Guardfile already includes #{ name } guard"
else else
content = File.read('Guardfile') content = File.read('Guardfile')
guard = File.read("#{::Guard.locate_guard(name)}/lib/guard/#{name}/templates/Guardfile") guard = File.read("#{ ::Guard.locate_guard(name) }/lib/guard/#{ name }/templates/Guardfile")
File.open('Guardfile', 'wb') do |f| File.open('Guardfile', 'wb') do |f|
f.puts(content) f.puts(content)
f.puts("") f.puts("")
f.puts(guard) f.puts(guard)
end end
::Guard::UI.info "#{name} guard added to Guardfile, feel free to edit it"
::Guard::UI.info "#{ name } guard added to Guardfile, feel free to edit it"
end end
end end
# ================ # Call once when Guard starts. Please override initialize method to init stuff.
# = Guard method = #
# ================ # @raise [:task_has_failed] when start has failed
# @return [Object] the task result
# Call once when guard starts #
# Please override initialize method to init stuff
def start def start
true
end end
# 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 def stop
true
end 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 def reload
true
end 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 def run_all
true
end 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) 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
end
end

118
lib/guard/hook.rb Normal file
View 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

View File

@ -1,54 +1,44 @@
module Guard module Guard
module Interactor
extend self
def run_all # The interactor reads user input and triggers
::Guard.run do # specific action upon them unless its locked.
::Guard.guards.each { |guard| ::Guard.supervised_task(guard, :run_all) } #
# 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
end end
def stop def stop_if_not_current
UI.info "Bye bye...", :reset => true unless Thread.current == @thread
::Guard.listener.stop @thread.kill
::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) }
end end
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
end end

View File

@ -8,97 +8,229 @@ module Guard
autoload :Windows, 'guard/listeners/windows' autoload :Windows, 'guard/listeners/windows'
autoload :Polling, 'guard/listeners/polling' autoload :Polling, 'guard/listeners/polling'
# The Listener is the base class for all listener
# implementations.
#
# @abstract
#
class Listener class Listener
DefaultIgnorePaths = %w[. .. .bundle .git log tmp vendor]
# Default paths that gets ignored by the listener
DEFAULT_IGNORE_PATHS = %w[. .. .bundle .git log tmp vendor]
attr_accessor :changed_files
attr_reader :directory, :ignore_paths 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? if mac? && Darwin.usable?
Darwin.new(*a) Darwin.new(*args)
elsif linux? && Linux.usable? elsif linux? && Linux.usable?
Linux.new(*a) Linux.new(*args)
elsif windows? && Windows.usable? elsif windows? && Windows.usable?
Windows.new(*a) Windows.new(*args)
else else
UI.info "Using polling (Please help us to support your system better than that.)" UI.info 'Using polling (Please help us to support your system better than that).'
Polling.new(*a) Polling.new(*args)
end end
end end
def initialize(directory=Dir.pwd, options={}) # Initialize the listener.
@directory = directory.to_s #
@sha1_checksums_hash = {} # @param [String] directory the root directory to listen to
@relativize_paths = options.fetch(:relativize_paths, true) # @option options [Boolean] relativize_paths use only relative paths
@ignore_paths = DefaultIgnorePaths # @option options [Array<String>] ignore_paths the paths to ignore by the listener
@ignore_paths |= options[:ignore_paths] if options[:ignore_paths] #
def initialize(directory = Dir.pwd, options = {})
@directory = directory.to_s
@sha1_checksums_hash = {}
@file_timestamp_hash = {}
@relativize_paths = options.fetch(:relativize_paths, true)
@changed_files = []
@paused = false
@ignore_paths = DEFAULT_IGNORE_PATHS
@ignore_paths |= options[:ignore_paths] if options[:ignore_paths]
@watch_all_modifications = options.fetch(:watch_all_modifications, false)
update_last_event update_last_event
start_reactor
end end
# Start the listener thread.
#
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 def start
watch(@directory) watch(@directory)
timestamp_files
end end
# Stop listening for events.
#
def stop def stop
end 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) def on_change(&callback)
@callback = callback @callback = callback
end end
# Updates the timestamp of the last event.
#
def update_last_event def update_last_event
@last_event = Time.now @last_event = Time.now
end end
def modified_files(dirs, options={}) # Get the modified files.
files = potentially_modified_files(dirs, options).select { |path| file_modified?(path) } #
# 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 update_last_event
files.concat(potentially_modified_files(dirs, options).select { |path| file_modified?(path, last_event) })
relativize_paths(files) relativize_paths(files)
end end
def worker # Register a directory to watch.
raise NotImplementedError, "should respond to #watch" # Must be implemented by the subclasses.
end #
# @param [String] directory the directory to watch
# register a directory to watch. must be implemented by the subclasses #
def watch(directory) def watch(directory)
raise NotImplementedError, "do whatever you want here, given the directory as only argument" raise NotImplementedError, "do whatever you want here, given the directory as only argument"
end end
# Get all files that are in the watched directory.
#
# @return [Array<String>] the list of files
#
def all_files def all_files
potentially_modified_files([@directory], :all => true) potentially_modified_files([@directory], :all => true)
end end
# scopes all given paths to the current #directory # Scopes all given paths to the current directory.
#
# @param [Array<String>] paths the paths to change
# @return [Array<String>] all paths now relative to the current dir
#
def relativize_paths(paths) def relativize_paths(paths)
return paths unless relativize_paths? return paths unless relativize_paths?
paths.map do |path| paths.map do |path|
path.gsub(%r{^#{@directory}/}, '') path.gsub(%r{^(!)?#{ @directory }/},'\1')
end end
end end
# Use paths relative to the current directory.
#
# @return [Boolean] whether to use relative or absolute paths
#
def relativize_paths? def relativize_paths?
!!@relativize_paths !!@relativize_paths
end end
# return children of the passed dirs that are not in the ignore_paths list # 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) def exclude_ignored_paths(dirs, ignore_paths = self.ignore_paths)
Dir.glob(dirs.map { |d| "#{d.sub(%r{/+$}, '')}/*" }, File::FNM_DOTMATCH).reject do |path| Dir.glob(dirs.map { |d| "#{d.sub(%r{/+$}, '')}/*" }, File::FNM_DOTMATCH).reject do |path|
ignore_paths.include?(File.basename(path)) ignore_paths.include?(File.basename(path))
end end
end end
private private
def potentially_modified_files(dirs, options={}) # Gets a list of files that are in the modified directories.
#
# @param [Array<String>] dirs the list of directories
# @param [Hash] options the find file option
# @option options [Symbol] all whether to files in sub directories
#
def potentially_modified_files(dirs, options = {})
paths = exclude_ignored_paths(dirs) paths = exclude_ignored_paths(dirs)
if options[:all] if options[:all]
paths.inject([]) do |array, path| paths.inject([]) do |array, path|
if File.file?(path) if File.file?(path)
array << path array << path
else else
array += Dir.glob("#{path}/**/*", File::FNM_DOTMATCH).select { |p| File.file?(p) } array += Dir.glob("#{ path }/**/*", File::FNM_DOTMATCH).select { |p| File.file?(p) }
end end
array array
end end
@ -107,19 +239,44 @@ module Guard
end end
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. # 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)) 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)) set_sha1_checksums_hash(path, sha1_checksum(path))
true true
elsif @watch_all_modifications
ts = file_timestamp(path)
if ts != @file_timestamp_hash[path]
set_file_timestamp_hash(path, ts)
true
end
else
false
end end
rescue rescue
false false
end end
# Tests if the file content has been modified by
# comparing the SHA1 checksum.
#
# @param [String] path the file path
# @param [String] sha1_checksum the checksum of the file
#
def file_content_modified?(path, sha1_checksum) def file_content_modified?(path, sha1_checksum)
if @sha1_checksums_hash[path] != sha1_checksum if @sha1_checksums_hash[path] != sha1_checksum
set_sha1_checksums_hash(path, sha1_checksum) set_sha1_checksums_hash(path, sha1_checksum)
@ -129,22 +286,62 @@ module Guard
end end
end end
# Set save a files current timestamp
#
# @param [String] path the file path
# @param [Int] file_timestamp the files modified timestamp
#
def set_file_timestamp_hash(path, file_timestamp)
@file_timestamp_hash[path] = file_timestamp
end
# Set the current checksum of a file.
#
# @param [String] path the file path
# @param [String] sha1_checksum the checksum of the file
#
def set_sha1_checksums_hash(path, sha1_checksum) def set_sha1_checksums_hash(path, sha1_checksum)
@sha1_checksums_hash[path] = sha1_checksum @sha1_checksums_hash[path] = sha1_checksum
end end
# 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) def sha1_checksum(path)
Digest::SHA1.file(path).to_s Digest::SHA1.file(path).to_s
end end
# Test if the OS is Mac OS X.
#
# @return [Boolean] Whether the OS is Mac OS X
#
def self.mac? def self.mac?
RbConfig::CONFIG['target_os'] =~ /darwin/i RbConfig::CONFIG['target_os'] =~ /darwin/i
end end
# Test if the OS is Linux.
#
# @return [Boolean] Whether the OS is Linux
#
def self.linux? def self.linux?
RbConfig::CONFIG['target_os'] =~ /linux/i RbConfig::CONFIG['target_os'] =~ /linux/i
end end
# Test if the OS is Windows.
#
# @return [Boolean] Whether the OS is Windows
#
def self.windows? def self.windows?
RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
end end

View File

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

View File

@ -1,53 +1,65 @@
module Guard module Guard
# Listener implementation for Linux `inotify`.
#
class Linux < Listener class Linux < Listener
# Initialize the Listener.
#
def initialize(*) def initialize(*)
super super
@inotify = INotify::Notifier.new @inotify = INotify::Notifier.new
@files = [] @files = []
@latency = 0.5 @latency = 0.5
end end
# Start the listener.
#
def start def start
@stop = false @stop = false
super super
watch_change unless watch_change? watch_change unless watch_change?
end end
# Stop the listener.
#
def stop def stop
super super
@stop = true @stop = true
sleep(@latency)
end end
# Check if the listener is usable on the current OS.
#
# @return [Boolean] whether usable or not
#
def self.usable? def self.usable?
require 'rb-inotify' require 'rb-inotify'
if !defined?(INotify::VERSION) || (defined?(Gem::Version) && if !defined?(INotify::VERSION) || (defined?(Gem::Version) &&
Gem::Version.new(INotify::VERSION.join('.')) < Gem::Version.new('0.8.5')) Gem::Version.new(INotify::VERSION.join('.')) < Gem::Version.new('0.8.5'))
UI.info "Please update rb-inotify (>= 0.8.5)" UI.info 'Please update rb-inotify (>= 0.8.5)'
false false
else else
true true
end end
rescue LoadError rescue LoadError
UI.info "Please install rb-inotify gem for Linux inotify support" UI.info 'Please install rb-inotify gem for Linux inotify support'
false false
end end
def watch_change? private
!!@watch_change
end
private
# Get the listener worker.
#
def worker def worker
@inotify @inotify
end end
# Watch the given directory for file changes.
#
# @param [String] directory the directory to watch
#
def watch(directory) def watch(directory)
# The event selection is based on https://github.com/guard/guard/wiki/Analysis-of-inotify-events-for-different-editors worker.watch(directory, :recursive, :attrib, :create, :move_self, :close_write) do |event|
worker.watch(directory, :recursive, :create, :move_self, :close_write) do |event|
unless event.name == "" # Event on root directory unless event.name == "" # Event on root directory
@files << event.absolute_name @files << event.absolute_name
end end
@ -55,6 +67,16 @@ module Guard
rescue Interrupt rescue Interrupt
end end
# Test if inotify is watching for changes.
#
# @return [Boolean] whether inotify is active or not
#
def watch_change?
!!@watch_change
end
# Watch for file system changes.
#
def watch_change def watch_change
@watch_change = true @watch_change = true
until @stop until @stop

View File

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

View File

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

View File

@ -3,134 +3,288 @@ require 'pathname'
require 'guard/ui' require 'guard/ui'
module Guard module Guard
# The notifier class handles cross-platform system notifications that supports:
#
# - Growl on Mac OS X
# - Libnotify on Linux
# - Notifu on Windows
#
module Notifier module Notifier
# Application name as shown in the specific notification settings
APPLICATION_NAME = "Guard" APPLICATION_NAME = "Guard"
def self.turn_off class << self
ENV["GUARD_NOTIFY"] = 'false'
end
def self.turn_on attr_accessor :growl_library, :gntp
ENV["GUARD_NOTIFY"] = 'true'
case RbConfig::CONFIG['target_os'] # Turn notifications off.
when /darwin/i #
require_growl def turn_off
when /linux/i ENV["GUARD_NOTIFY"] = 'false'
require_libnotify
when /mswin|mingw/i
require_rbnotifu
end 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'] case RbConfig::CONFIG['target_os']
when /darwin/i when /darwin/i
notify_mac(title, message, image, options) require_growl
when /linux/i when /linux/i
notify_linux(title, message, image, options) require_libnotify
when /mswin|mingw/i when /mswin|mingw/i
notify_windows(title, message, image, options) require_rbnotifu
end end
end end
end
def self.enabled? # Show a message with the system notification.
ENV["GUARD_NOTIFY"] == 'true' #
end # @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 case RbConfig::CONFIG['target_os']
when /darwin/i
def self.notify_mac(title, message, image, options) notify_mac(title, message, image, options)
require_growl # need for guard-rspec formatter that is called out of guard scope when /linux/i
notify_linux(title, message, image, options)
default_options = { :title => title, :icon => image_path(image), :name => APPLICATION_NAME } when /mswin|mingw/i
default_options.merge!(options) notify_windows(title, message, image, 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
end end
end end
rescue LoadError
require 'growl'
end 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 # Test if the notifications are enabled and available.
require 'libnotify' #
rescue LoadError # @return [Boolean] whether the notifications are available
turn_off #
UI.info "Please install libnotify gem for Linux notification support and add it to your Gemfile" def enabled?
end 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 end
end end

View File

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

View File

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

View File

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

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

View File

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

View File

@ -1,11 +1,15 @@
require 'spec_helper' require 'spec_helper'
require 'guard/guard'
describe Guard::Dsl do describe Guard::Dsl do
subject { described_class }
class Guard::Dummy < Guard::Guard; end
before(:each) do before(:each) do
@local_guardfile_path = File.join(Dir.pwd, 'Guardfile') @local_guardfile_path = File.join(Dir.pwd, 'Guardfile')
@home_guardfile_path = File.expand_path(File.join("~", ".Guardfile")) @home_guardfile_path = File.expand_path(File.join("~", ".Guardfile"))
@user_config_path = File.expand_path(File.join("~", ".guard.rb")) @user_config_path = File.expand_path(File.join("~", ".guard.rb"))
::Guard.setup
::Guard.stub!(:options).and_return(:debug => true) ::Guard.stub!(:options).and_return(:debug => true)
::Guard.stub!(:guards).and_return([mock('Guard')]) ::Guard.stub!(:guards).and_return([mock('Guard')])
end end
@ -20,24 +24,24 @@ describe Guard::Dsl do
it "should use a string for initializing" do it "should use a string for initializing" do
Guard::UI.should_not_receive(:error) Guard::UI.should_not_receive(:error)
lambda { subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error lambda { described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
subject.guardfile_contents.should == valid_guardfile_string described_class.guardfile_contents.should == valid_guardfile_string
end end
it "should use a given file over the default loc" do it "should use a given file over the default loc" do
fake_guardfile('/abc/Guardfile', "guard :foo") fake_guardfile('/abc/Guardfile', "guard :foo")
Guard::UI.should_not_receive(:error) Guard::UI.should_not_receive(:error)
lambda { subject.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error lambda { described_class.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
subject.guardfile_contents.should == "guard :foo" described_class.guardfile_contents.should == "guard :foo"
end end
it "should use a default file if no other options are given" do it "should use a default file if no other options are given" do
fake_guardfile(@local_guardfile_path, "guard :bar") fake_guardfile(@local_guardfile_path, "guard :bar")
Guard::UI.should_not_receive(:error) Guard::UI.should_not_receive(:error)
lambda { subject.evaluate_guardfile }.should_not raise_error lambda { described_class.evaluate_guardfile }.should_not raise_error
subject.guardfile_contents.should == "guard :bar" described_class.guardfile_contents.should == "guard :bar"
end end
it "should use a string over any other method" do it "should use a string over any other method" do
@ -45,8 +49,8 @@ describe Guard::Dsl do
fake_guardfile(@local_guardfile_path, "guard :bar") fake_guardfile(@local_guardfile_path, "guard :bar")
Guard::UI.should_not_receive(:error) Guard::UI.should_not_receive(:error)
lambda { subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error lambda { described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
subject.guardfile_contents.should == valid_guardfile_string described_class.guardfile_contents.should == valid_guardfile_string
end end
it "should use the given Guardfile over default Guardfile" do it "should use the given Guardfile over default Guardfile" do
@ -54,31 +58,31 @@ describe Guard::Dsl do
fake_guardfile(@local_guardfile_path, "guard :bar") fake_guardfile(@local_guardfile_path, "guard :bar")
Guard::UI.should_not_receive(:error) Guard::UI.should_not_receive(:error)
lambda { subject.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error lambda { described_class.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
subject.guardfile_contents.should == "guard :foo" described_class.guardfile_contents.should == "guard :foo"
end end
it 'should append the user config file if present' do it 'should append the user config file if present' do
fake_guardfile('/abc/Guardfile', "guard :foo") fake_guardfile('/abc/Guardfile', "guard :foo")
fake_guardfile(@user_config_path, "guard :bar") fake_guardfile(@user_config_path, "guard :bar")
Guard::UI.should_not_receive(:error) Guard::UI.should_not_receive(:error)
lambda { subject.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error lambda { described_class.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
subject.guardfile_contents_with_user_config.should == "guard :foo\nguard :bar" described_class.guardfile_contents_with_user_config.should == "guard :foo\nguard :bar"
end end
end end
it "displays an error message when no Guardfile is found" do it "displays an error message when no Guardfile is found" do
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`.") 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 end
it "displays an error message when no guard are defined in Guardfile" do it "displays an error message when no guard are defined in Guardfile" do
::Guard::Dsl.stub!(:instance_eval_guardfile) ::Guard::Dsl.stub!(:instance_eval_guardfile)
::Guard.stub!(:guards).and_return([]) ::Guard.stub!(:guards).and_return([])
Guard::UI.should_receive(:error) Guard::UI.should_receive(:error)
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string)
end end
describe "correctly reads data from its valid data source" do describe "correctly reads data from its valid data source" do
@ -86,22 +90,22 @@ describe Guard::Dsl do
disable_user_config disable_user_config
it "reads correctly from a string" do it "reads correctly from a string" do
lambda { subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error lambda { described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) }.should_not raise_error
subject.guardfile_contents.should == valid_guardfile_string described_class.guardfile_contents.should == valid_guardfile_string
end end
it "reads correctly from a Guardfile" do it "reads correctly from a Guardfile" do
fake_guardfile('/abc/Guardfile', "guard :foo" ) fake_guardfile('/abc/Guardfile', "guard :foo" )
lambda { subject.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error lambda { described_class.evaluate_guardfile(:guardfile => '/abc/Guardfile') }.should_not raise_error
subject.guardfile_contents.should == "guard :foo" described_class.guardfile_contents.should == "guard :foo"
end end
it "reads correctly from a Guardfile" do it "reads correctly from a Guardfile" do
fake_guardfile(File.join(Dir.pwd, 'Guardfile'), valid_guardfile_string) fake_guardfile(File.join(Dir.pwd, 'Guardfile'), valid_guardfile_string)
lambda { subject.evaluate_guardfile }.should_not raise_error lambda { described_class.evaluate_guardfile }.should_not raise_error
subject.guardfile_contents.should == valid_guardfile_string described_class.guardfile_contents.should == valid_guardfile_string
end end
end end
@ -113,14 +117,14 @@ describe Guard::Dsl do
File.stub!(:read).with('/def/Guardfile') { raise Errno::EACCES.new("permission error") } File.stub!(:read).with('/def/Guardfile') { raise Errno::EACCES.new("permission error") }
Guard::UI.should_receive(:error).with(/^Error reading file/) Guard::UI.should_receive(:error).with(/^Error reading file/)
lambda { subject.evaluate_guardfile(:guardfile => '/def/Guardfile') }.should raise_error lambda { described_class.evaluate_guardfile(:guardfile => '/def/Guardfile') }.should raise_error
end end
it "raises error when given Guardfile doesn't exist" do it "raises error when given Guardfile doesn't exist" do
File.stub!(:exist?).with('/def/Guardfile') { false } File.stub!(:exist?).with('/def/Guardfile') { false }
Guard::UI.should_receive(:error).with(/No Guardfile exists at/) Guard::UI.should_receive(:error).with(/No Guardfile exists at/)
lambda { subject.evaluate_guardfile(:guardfile => '/def/Guardfile') }.should raise_error lambda { described_class.evaluate_guardfile(:guardfile => '/def/Guardfile') }.should raise_error
end end
it "raises error when resorting to use default, finds no default" do it "raises error when resorting to use default, finds no default" do
@ -128,24 +132,24 @@ describe Guard::Dsl do
File.stub!(:exist?).with(@home_guardfile_path) { false } File.stub!(:exist?).with(@home_guardfile_path) { false }
Guard::UI.should_receive(:error).with("No Guardfile found, please create one with `guard init`.") Guard::UI.should_receive(:error).with("No Guardfile found, please create one with `guard init`.")
lambda { subject.evaluate_guardfile }.should raise_error lambda { described_class.evaluate_guardfile }.should raise_error
end end
it "raises error when guardfile_content ends up empty or nil" do it "raises error when guardfile_content ends up empty or nil" do
Guard::UI.should_receive(:error).with(/The command file/) Guard::UI.should_receive(:error).with(/The command file/)
lambda { subject.evaluate_guardfile(:guardfile_contents => "") }.should raise_error lambda { described_class.evaluate_guardfile(:guardfile_contents => "") }.should raise_error
end end
it "doesn't raise error when guardfile_content is nil (skipped)" do it "doesn't raise error when guardfile_content is nil (skipped)" do
Guard::UI.should_not_receive(:error) Guard::UI.should_not_receive(:error)
lambda { subject.evaluate_guardfile(:guardfile_contents => nil) }.should_not raise_error lambda { described_class.evaluate_guardfile(:guardfile_contents => nil) }.should_not raise_error
end end
end end
it "displays an error message when Guardfile is not valid" do it "displays an error message when Guardfile is not valid" do
Guard::UI.should_receive(:error).with(/Invalid Guardfile, original error is:/) Guard::UI.should_receive(:error).with(/Invalid Guardfile, original error is:/)
lambda { 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 end
describe ".reevaluate_guardfile" do describe ".reevaluate_guardfile" do
@ -153,10 +157,10 @@ describe Guard::Dsl do
it "resets already definded guards before calling evaluate_guardfile" do it "resets already definded guards before calling evaluate_guardfile" do
Guard::Notifier.turn_off Guard::Notifier.turn_off
subject.evaluate_guardfile(:guardfile_contents => invalid_guardfile_string) described_class.evaluate_guardfile(:guardfile_contents => invalid_guardfile_string)
::Guard.guards.should_not be_empty ::Guard.guards.should_not be_empty
::Guard::Dsl.should_receive(:evaluate_guardfile) ::Guard::Dsl.should_receive(:evaluate_guardfile)
subject.reevaluate_guardfile described_class.reevaluate_guardfile
::Guard.guards.should be_empty ::Guard.guards.should be_empty
end end
end end
@ -169,14 +173,14 @@ describe Guard::Dsl do
context "when there is a local Guardfile" do context "when there is a local Guardfile" do
it "returns the path to the local Guardfile" do it "returns the path to the local Guardfile" do
File.stub(:exist?).with(local_path).and_return(true) File.stub(:exist?).with(local_path).and_return(true)
subject.guardfile_default_path.should == local_path described_class.guardfile_default_path.should == local_path
end end
end end
context "when there is a Guardfile in the user's home directory" do context "when there is a Guardfile in the user's home directory" do
it "returns the path to the user Guardfile" do it "returns the path to the user Guardfile" do
File.stub(:exist?).with(user_path).and_return(true) File.stub(:exist?).with(user_path).and_return(true)
subject.guardfile_default_path.should == user_path described_class.guardfile_default_path.should == user_path
end end
end end
@ -184,90 +188,90 @@ describe Guard::Dsl do
it "returns the path to the local Guardfile" do it "returns the path to the local Guardfile" do
File.stub(:exist?).with(local_path).and_return(true) File.stub(:exist?).with(local_path).and_return(true)
File.stub(:exist?).with(user_path).and_return(true) File.stub(:exist?).with(user_path).and_return(true)
subject.guardfile_default_path.should == local_path described_class.guardfile_default_path.should == local_path
end end
end end
end end
describe ".guardfile_include?" do describe ".guardfile_include?" do
it "detects a guard specified by a string with double quotes" do it "detects a guard specified by a string with double quotes" do
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
it "detects a guard specified by a string with single quote" do 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 end
it "detects a guard specified by a symbol" do 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 end
it "detects a guard wrapped in parentheses" do 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
end end
describe "#ignore_paths" do describe "#ignore_paths" do
disable_user_config disable_user_config
it "adds the paths to the listener's ignore_paths" do it "adds the paths to the listener's ignore_paths" do
::Guard.stub!(:listener).and_return(mock('Listener')) ::Guard.stub!(:listener).and_return(mock('Listener'))
::Guard.listener.should_receive(:ignore_paths).and_return(ignore_paths = ['faz']) ::Guard.listener.should_receive(:ignore_paths).and_return(ignore_paths = ['faz'])
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'] ignore_paths.should == ['faz', 'foo', 'bar']
end end
end end
describe "#group" do describe "#group" do
disable_user_config disable_user_config
it "evaluates only the specified string group" do it "evaluates only the specified string group" do
::Guard.should_receive(:add_guard).with(:pow, [], { :group => :default }) ::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
::Guard.should_receive(:add_guard).with(:test, [], { :group => :w }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => ['w']) described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
end end
it "evaluates only the specified symbol group" do it "evaluates only the specified symbol group" do
::Guard.should_receive(:add_guard).with(:pow, [], { :group => :default }) ::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
::Guard.should_receive(:add_guard).with(:test, [], { :group => :w }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w]) described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
end end
it "evaluates only the specified groups" do it "evaluates only the specified groups (with their options)" do
::Guard.should_receive(:add_guard).with(:pow, [], { :group => :default }) ::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
::Guard.should_receive(:add_guard).with(:rspec, [], { :group => :x }) ::Guard.should_receive(:add_guard).with('rspec', [], [], { :group => :x })
::Guard.should_receive(:add_guard).with(:ronn, [], { :group => :x }) ::Guard.should_receive(:add_guard).with('ronn', [], [], { :group => :x })
::Guard.should_receive(:add_guard).with(:less, [], { :group => :y }) ::Guard.should_receive(:add_guard).with('less', [], [], { :group => :y })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:x, :y]) described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:x, :y])
end end
it "evaluates always guard outside any group (even when a group is given)" do it "evaluates always guard outside any group (even when a group is given)" do
::Guard.should_receive(:add_guard).with(:pow, [], { :group => :default }) ::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
::Guard.should_receive(:add_guard).with(:test, [], { :group => :w }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w]) described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string, :group => [:w])
end end
it "evaluates all groups when no group option is specified" do it "evaluates all groups when no group option is specified (with their options)" do
::Guard.should_receive(:add_guard).with(:pow, [], { :group => :default }) ::Guard.should_receive(:add_guard).with('pow', [], [], { :group => :default })
::Guard.should_receive(:add_guard).with(:test, [], { :group => :w }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :w })
::Guard.should_receive(:add_guard).with(:rspec, [], { :group => :x }) ::Guard.should_receive(:add_guard).with('rspec', [], [], { :group => :x })
::Guard.should_receive(:add_guard).with(:ronn, [], { :group => :x }) ::Guard.should_receive(:add_guard).with('ronn', [], [], { :group => :x })
::Guard.should_receive(:add_guard).with(:less, [], { :group => :y }) ::Guard.should_receive(:add_guard).with('less', [], [], { :group => :y })
subject.evaluate_guardfile(:guardfile_contents => valid_guardfile_string) described_class.evaluate_guardfile(:guardfile_contents => valid_guardfile_string)
end end
end end
@ -275,33 +279,33 @@ describe Guard::Dsl do
disable_user_config disable_user_config
it "loads a guard specified as a quoted string from the DSL" do it "loads a guard specified as a quoted string from the DSL" do
::Guard.should_receive(:add_guard).with(:test, [], { :group => :default }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default })
subject.evaluate_guardfile(:guardfile_contents => "guard 'test'") described_class.evaluate_guardfile(:guardfile_contents => "guard 'test'")
end end
it "loads a guard specified as a double quoted string from the DSL" do it "loads a guard specified as a double quoted string from the DSL" do
::Guard.should_receive(:add_guard).with(:test, [], { :group => :default }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default })
subject.evaluate_guardfile(:guardfile_contents => 'guard "test"') described_class.evaluate_guardfile(:guardfile_contents => 'guard "test"')
end end
it "loads a guard specified as a symbol from the DSL" do it "loads a guard specified as a symbol from the DSL" do
::Guard.should_receive(:add_guard).with(:test, [], { :group => :default }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default })
subject.evaluate_guardfile(:guardfile_contents => "guard :test") described_class.evaluate_guardfile(:guardfile_contents => "guard :test")
end end
it "loads a guard specified as a symbol and called with parens from the DSL" do it "loads a guard specified as a symbol and called with parens from the DSL" do
::Guard.should_receive(:add_guard).with(:test, [], { :group => :default }) ::Guard.should_receive(:add_guard).with('test', [], [], { :group => :default })
subject.evaluate_guardfile(:guardfile_contents => "guard(:test)") described_class.evaluate_guardfile(:guardfile_contents => "guard(:test)")
end end
it "receives options when specified, from normal arg" do it "receives options when specified, from normal arg" do
::Guard.should_receive(:add_guard).with(:test, [], { :opt_a => 1, :opt_b => 'fancy', :group => :default }) ::Guard.should_receive(:add_guard).with('test', [], [], { :opt_a => 1, :opt_b => 'fancy', :group => :default })
subject.evaluate_guardfile(:guardfile_contents => "guard 'test', :opt_a => 1, :opt_b => 'fancy'") described_class.evaluate_guardfile(:guardfile_contents => "guard 'test', :opt_a => 1, :opt_b => 'fancy'")
end end
end end
@ -309,19 +313,41 @@ describe Guard::Dsl do
disable_user_config disable_user_config
it "should receive watchers when specified" do it "should receive watchers when specified" do
guardfile_with_watchers = "guard 'test' do ::Guard.should_receive(:add_guard).with('dummy', anything, anything, { :group => :default }) do |name, watchers, callbacks, options|
watch('a') { 'b' }
watch('c')
end"
::Guard.should_receive(:add_guard).with(:test, anything, { :group => :default }) do |name, watchers, options|
watchers.size.should == 2 watchers.size.should == 2
watchers[0].pattern.should == 'a' watchers[0].pattern.should == 'a'
watchers[0].action.call.should == proc { 'b' }.call watchers[0].action.call.should == proc { 'b' }.call
watchers[1].pattern.should == 'c' watchers[1].pattern.should == 'c'
watchers[1].action.should == nil watchers[1].action.should == nil
end 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
end end
@ -336,25 +362,21 @@ private
" "
guard :pow guard :pow
group 'w' do group :w do
guard 'test' guard :test
end end
group :x do group :x, :halt_on_fail => true do
guard 'rspec' guard :rspec
guard :ronn guard :ronn
end end
group 'y' do group :y do
guard 'less' guard :less
end end
" "
end end
def mock_guardfile_content(content)
File.stub!(:read).with(subject.guardfile_default_path) { content }
end
def invalid_guardfile_string def invalid_guardfile_string
"Bad Guardfile" "Bad Guardfile"
end end

19
spec/guard/group_spec.rb Normal file
View 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
View 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
View 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

View File

@ -1,34 +1,6 @@
require 'spec_helper' require 'spec_helper'
describe Guard::Interactor do 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 end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,45 @@ require 'guard/guard'
describe Guard do describe Guard do
describe ".initialize_template" do
context "with a Guard name" do
it "initializes a the Guard" do
class Guard::TestGuard < Guard::Guard
end
Guard::TestGuard.should_receive(:init)
Guard.initialize_template('test-guard')
end
end
context "without a Guard name" do
context "with an existing Guardfile" do
before do
File.stub(:exist?).and_return true
Dir.stub(:pwd).and_return "/home/user"
end
it "shows an error" do
Guard.should_receive(:exit).with 1
::Guard::UI.should_receive(:error).with("Guardfile already exists at /home/user/Guardfile")
Guard.initialize_template()
end
end
context "without an existing Guardfile" do
before do
File.stub(:exist?).and_return false
Dir.stub(:pwd).and_return "/home/user"
end
it "copies the Guardfile template" do
::Guard::UI.should_receive(:info).with("Writing new Guardfile to /home/user/Guardfile")
FileUtils.should_receive(:cp).with(an_instance_of(String), 'Guardfile')
Guard.initialize_template()
end
end
end
end
describe ".setup" do describe ".setup" do
subject { ::Guard.setup } subject { ::Guard.setup }
@ -15,7 +54,8 @@ describe Guard do
end end
it "initializes @groups" do it "initializes @groups" do
Guard.groups.should eql [:default] described_class.groups[0].name.should eql :default
described_class.groups[0].options.should == {}
end end
it "initializes the options" do it "initializes the options" do
@ -53,6 +93,129 @@ describe Guard do
::Guard.should_receive(:debug_command_execution) ::Guard.should_receive(:debug_command_execution)
::Guard.setup(:debug => true) ::Guard.setup(:debug => true)
end end
it "initializes the interactor" do
::Guard.setup
::Guard.interactor.should be_kind_of(Guard::Interactor)
end
it "skips the interactor initalization if no-interactions is true" do
::Guard.interactor = nil
::Guard.setup(:no_interactions => true)
::Guard.interactor.should be_nil
end
end
describe ".guards" do
class Guard::FooBar < Guard::Guard; end
class Guard::FooBaz < Guard::Guard; end
subject do
guard = ::Guard.setup
@guard_foo_bar_backend = Guard::FooBar.new([], { :group => 'backend' })
@guard_foo_bar_frontend = Guard::FooBar.new([], { :group => 'frontend' })
@guard_foo_baz_backend = Guard::FooBaz.new([], { :group => 'backend' })
@guard_foo_baz_frontend = Guard::FooBaz.new([], { :group => 'frontend' })
guard.instance_variable_get("@guards").push(@guard_foo_bar_backend)
guard.instance_variable_get("@guards").push(@guard_foo_bar_frontend)
guard.instance_variable_get("@guards").push(@guard_foo_baz_backend)
guard.instance_variable_get("@guards").push(@guard_foo_baz_frontend)
guard
end
it "return @guards without any argument" do
subject.guards.should eql subject.instance_variable_get("@guards")
end
describe "find a guard by as string/symbol" do
it "find a guard by a string" do
subject.guards('foo-bar').should eql @guard_foo_bar_backend
end
it "find a guard by a symbol" do
subject.guards(:'foo-bar').should eql @guard_foo_bar_backend
end
it "returns nil if guard is not found" do
subject.guards('foo-foo').should be_nil
end
end
describe "find guards matching a regexp" do
it "with matches" do
subject.guards(/^foobar/).should eql [@guard_foo_bar_backend, @guard_foo_bar_frontend]
end
it "without matches" do
subject.guards(/foo$/).should eql []
end
end
describe "find guards by their group" do
it "group name is a string" do
subject.guards(:group => 'backend').should eql [@guard_foo_bar_backend, @guard_foo_baz_backend]
end
it "group name is a symbol" do
subject.guards(:group => :frontend).should eql [@guard_foo_bar_frontend, @guard_foo_baz_frontend]
end
it "returns [] if guard is not found" do
subject.guards(:group => :unknown).should eql []
end
end
describe "find guards by their group & name" do
it "group name is a string" do
subject.guards(:group => 'backend', :name => 'foo-bar').should eql [@guard_foo_bar_backend]
end
it "group name is a symbol" do
subject.guards(:group => :frontend, :name => :'foo-baz').should eql [@guard_foo_baz_frontend]
end
it "returns [] if guard is not found" do
subject.guards(:group => :unknown, :name => :'foo-baz').should eql []
end
end
end
describe ".groups" do
subject do
guard = ::Guard.setup
@group_backend = guard.add_group(:backend)
@group_backflip = guard.add_group(:backflip)
guard
end
it "return @groups without any argument" do
subject.groups.should eql subject.instance_variable_get("@groups")
end
describe "find a group by as string/symbol" do
it "find a group by a string" do
subject.groups('backend').should eql @group_backend
end
it "find a group by a symbol" do
subject.groups(:backend).should eql @group_backend
end
it "returns nil if group is not found" do
subject.groups(:foo).should be_nil
end
end
describe "find groups matching a regexp" do
it "with matches" do
subject.groups(/^back/).should eql [@group_backend, @group_backflip]
end
it "without matches" do
subject.groups(/back$/).should eql []
end
end
end end
describe ".start" do describe ".start" do
@ -116,7 +279,7 @@ describe Guard do
it "gives an empty hash of options" do it "gives an empty hash of options" do
@guard_rspec_class.should_receive(:new).with([], {}).and_return(@guard_rspec) @guard_rspec_class.should_receive(:new).with([], {}).and_return(@guard_rspec)
Guard.add_guard(:rspec, [], {}) Guard.add_guard(:rspec, [], [], {})
end end
end end
@ -124,27 +287,33 @@ describe Guard do
it "give the options hash" do it "give the options hash" do
@guard_rspec_class.should_receive(:new).with([], { :foo => true, :group => :backend }).and_return(@guard_rspec) @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 end
end end
describe ".add_group" do describe ".add_group" do
before(:each) do subject { ::Guard.setup }
Guard.setup
end
it "accepts group name as string" do 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 end
it "accepts group name as symbol" do 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
end end
@ -223,26 +392,127 @@ describe Guard do
end end
end end
describe ".supervised_task" do describe ".run_guard_task" do
subject { ::Guard.setup } 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 @g = mock(Guard::Guard).as_null_object
subject.guards.push(@g) subject.guards.push(@g)
subject.add_group(:foo, { :halt_on_fail => true })
subject.add_group(:bar, { :halt_on_fail => false })
end end
context "with a task that succeed" do context "with a task that succeed" do
context 'without any arguments' do context 'without any arguments' do
before(:each) do before(:each) do
@g.stub!(:regular) { true } @g.stub!(:regular_without_arg) { true }
end end
it "doesn't fire the Guard" do 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 end
it "returns the result of the task" do 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
end end
@ -252,31 +522,91 @@ describe Guard do
end end
it "doesn't fire the Guard" do 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 end
it "returns the result of the task" do 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 end
end end
context "with a task that raises an exception" do context "with a task that raises an exception" do
before(:each) { @g.stub!(: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 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) subject.guards.should_not include(@g)
end end
it "returns the exception" do 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.should be_kind_of(Exception)
failing_result.message.should == 'I break your system' failing_result.message.should == 'I break your system'
end end
end end
end end
describe '.guard_symbol' do
let(:guard) { mock(Guard::Guard).as_null_object }
it 'returns :task_has_failed when the group is missing' do
subject.guard_symbol(guard).should eql :task_has_failed
end
context 'for a group with :halt_on_fail' do
let(:group) { mock(Guard::Group) }
before do
guard.stub(:group).and_return :foo
group.stub(:options).and_return({ :halt_on_fail => true })
end
it 'returns :no_catch' do
subject.should_receive(:groups).with(:foo).and_return group
subject.guard_symbol(guard).should eql :no_catch
end
end
context 'for a group without :halt_on_fail' do
let(:group) { mock(Guard::Group) }
before do
guard.stub(:group).and_return :foo
group.stub(:options).and_return({ :halt_on_fail => false })
end
it 'returns :task_has_failed' do
subject.should_receive(:groups).with(:foo).and_return group
subject.guard_symbol(guard).should eql :task_has_failed
end
end
end
describe ".debug_command_execution" do describe ".debug_command_execution" do
subject { ::Guard.setup } subject { ::Guard.setup }

View File

@ -12,6 +12,7 @@ RSpec.configure do |config|
config.color_enabled = true config.color_enabled = true
config.filter_run :focus => true config.filter_run :focus => true
config.treat_symbols_as_metadata_keys_with_true_values = true
config.run_all_when_everything_filtered = true config.run_all_when_everything_filtered = true
config.before(:each) do config.before(:each) do

View File

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