-
Notifications
You must be signed in to change notification settings - Fork 488
Understanding Guard
Guard's syntax and behavior tend to be very confusing to newcomers.
This will hopefully be changed in the near future.
Meanwhile, we hope this guide will help you quickly "get it", without having to go through hours of mind-bending "mistakes".
If you don't take a few minutes to even just skim through this, you might get stuck for hours trying to even get Guard doing something simple.
But, if you do ... you'll be able to force Guard to do truly awesome things - that we as Guard authors and maintainers would have never imagined!
(Do share with us if you create something cool! Feel free to even add a Wiki page about it!)
Even this trivial example is misleading:
guard :bundler do
watch('Gemfile')
endwatch does not mean Guard will watch the file.
Nope.
It means: "when Gemfile is DETECTED as changed, added or removed, let this plugin handle it.
For the above code to "work as expected",
- Guard has to be ALREADY watching the directory the
Gemfileis in (although this is platform/adapter specific). - The plugin has to be designed to respond to the kind of change (modified or added or deleted)
- The plugin has to be designed to actually handle that file (and not ignore the change)
Well, if it "doesn't work", how do you know what's wrong then?
Let's dive into debugging, because this will save you tons of hours:
First, run guard with the -d parameter (bundle exec guard -d)
Now, if you change the file while running Guard, you should see:
20:43:26 - DEBUG - Interactor was stopped or killed
20:43:26 - DEBUG - Hook :run_on_modifications_begin executed for Guard::Bundler
20:43:26 - DEBUG - Hook :run_on_modifications_end executed for Guard::Bundler
20:43:26 - DEBUG - Start interactor
This says a few things:
- That Guard detected relevant changes (meaning - one of the plugins matched the changes)
- That some change matching the
watchstatement was passed to the plugin - That the change was a file
modification - That the plugin actually handles modified files
So if nothing is happening at this point, check that:
- The plugin is configured properly
- The plugin doesn't actually ignore the file you are watching
First, let's start with plugin problems (and then we'll get back to Guard).
guard :minitest, options do
watch('project/module/foo.rb')
endAnd here's the debug output when editing e.g. project/module/foo.rb:
20:43:26 - DEBUG - Hook :run_on_additions_begin executed for Guard::Minitest
20:43:26 - DEBUG - Hook :run_on_additions_end executed for Guard::Minitest
This means :minitest got the change, but didn't do anything.
Why? Because minitest responds to test files.
So, if minitest gets "foo.rb", it will do nothing by design. But if you pass "foo_test.rb" (given that file exists), it will run the tests.
If we did something silly like passing Gemfile to Guard::Minitest, it wouldn't anything either.
Well, in this case if we want Guard::Minitest to run the tests in foo_test.rb, we have to map the files ourselves, e.g:
guard :minitest, options do
watch('project/module/foo.rb') { 'project/tests/module/foo_test.rb' }
endHere's how to read this: "if Guard detects changes in project/module/foo.rb, it will pass project/module/foo_test.rb to Guard::Minitest.
Without the block, it would pass project/module/foo.rb, which Guard::Minitest just ignores.
Ideally, rename your Guardfile, update your gems, and run bundle exec guard init again - and tweak the defaults just enough to get things working.
If you think the defaults could be better, submit an issues to the plugin home page (which you can usually in the gem's README or here: https://rubygems.org/gems or on GitHub). Chances are, the plugin might have a fork here in this GitHub organization: https://github.com/guard. If no one is responding, just mention me in the comments (as @e2).
If the plugin is not getting the event - there are a few tricks to work out why.
With bundle exec guard init minitest for the example above, the Guardfile will include something like:
guard :minitest do
# This runs the modified test
watch(%r{^test/(.*)\/?test_(.*)\.rb$})
# This calls the plugin with a new file name - which may not even exist
watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
# This call the plugin with the 'test' parameter - see Guard::Minitest docs
# for information in how it finds/choose files in the given 'test' directory
watch(%r{^test/test_helper\.rb$}) { 'test' }
endThe "easy" way to see if a watch is triggered is to just print it, e.g:
guard :minitest do
watch(%r{^lib/(.*/)?([^/]+)\.rb$}) do |m|
"test/#{m[1]}test_#{m[2]}.rb".tap do |result|
Guard::UI.info "Sending changes to Minitest: #{result.inspect}"
Guard::UI.info "The original match is: #{m.inspect}"
end
end
end(Note the tap there).
If you are not getting the output, try restarting Guard - especially since newer version of Guard may not reload themselves.
Guard shell had a bad design decision, but quickly became too popular to change things.
guard :shell do
watch /(.*)/ do |m|
`echo #{m[0]}`
end
endThis is a bit confusing, because while it works as expected, it's not how it "should" work.
The Guard::Shell plugin just prints the stuff returned by the block.
The "Guard way" of doing things would be to either have something like:
# this non-existing plugin would print whatever is returned
guard :print, output: $stdout do
watch /(.*)/ do |m|
`echo #{m[0]}`
end
end(NOTE: this plugin doesn't exist - it's just an example)
or:
# this non-existing plugin would e.g. pass the string to a system() call
guard :execute, any_return: true do
watch /(.*)/ do |m|
'echo #{m[0]}'
end
end(NOTE: this plugin also doesn't exist - it's just an example)
or even:
# this non-existing plugin would e.g. capture exceptions and throw :task_failed on errors
guard :call_safely, any_return: true do
watch /(.*)/ do |m|
Proc.new do
system("my_tool #{m[1]}") || fail "Command failed: #{m[1]}"
end
end
endThis is because Guard expects you to return paths (unless you provide the any_return option).
And Guard expects your watch block to just work out which paths to pass to the plugin.
Also, Guard expects failing tasks to throw the :task_failed symbol - no
gracefully notify Guard that the failure is a normal one to occur (e.g. failing test).
If you have complex rules, and the file you're changing is triggering the wrong actions ...
... this should help show you how Guard matches changes and what it calls the plugin.
Start Guard and in the Pry session type in:
# NOTE: this API is somewhat internal and in progress of being defined
plugin = Guard.state.session.plugins.all(:minitest).first
Guard::Watcher.match_files(plugin, %w(project/module/foo.rb))Or, on older versions of Guard:
plugin = Guard.plugin(:minitest)
Guard::Watcher.match_files(plugin, %w(project/module/foo.rb))This will show you what your rules are returning.
One handy option is :first_match, e.g.:
guard :minitest, first_match: true do
# Rule for all tests
watch(/test\/(.*)_test\.rb$/) # no block means return the matched file
# Run multiple tests when an abstract base class changes:
watch(/lib\/net/base.rb$/) { |m| "tests/network" }
# Special rule for files in `ui` directory
watch(/lib\/ui\/(.*)\.rb$/) { |m| "acceptance/#{m[1]}_test.rb" }
# General translating implementation files to test files
watch(/lib\/(.*)\.rb$/) { |m| "tests/#{m[1]}_test.rb" }
# Note the `nil` at the end to avoid passing file to plugin
watch(/(.*)/) { |m| Guard::UI.puts "Unknown file: #{m[1]}"; nil }
endThe first_match prevents Guard for running the plugin multiple times for every block the changed (or added, or removed) file matches.
How do you know if Guard is even responding to changes?
Well, when running guard in debug mode with -w, you'll know by:
20:43:26 - DEBUG - Interactor was stopped or killed
20:43:26 - DEBUG - Start interactor
The Interactor is killed whenever a file change is relevant - meaning it matches any watch rule in any plugin.
'Start interactor' means Guard is finished processing all the changes.
So if you don't see those lines, it means Guard didn't get any changes.
You can put a 'catch all' block at the top of your Guard, and it will go into the default group:
watch(/(.*)/) { |m| Guard::UI.puts "Unknown file: #{m[1]}"; nil }If you see the message (or the debug statement above), you'll know Guard is watching the file.
If you're using an editor, try changing the file using a tool that doesn't make backups or use atomic save (like most editors do), e.g.
$ touch test/foo_test.rbIf that triggers a change, but your editor doesn't, go here to Listen Wiki here, to work out the problem: https://github.com/guard/listen/wiki (in short, try disabling atomic save in your editor, or try tweaking file save backup options in your editor).
This may be a problem in either Guard or Listen.
If you suspect Guard is the problem (because modifying other files in other directories triggers changes), check the following:
- If you're on Linux, check if the file is a symlink (also, try running guard with the
-poption to see if there's a difference) - Work out the real directory containing the real file
If the file is a symlink, check where it's symlinked to - then not which directory it really is in.
Then, make sure that directory is watched:
In Guard's Pry session, type in:
Guard.state.session.watchdirsOr, in older versions of Guard:
# Should work, but I'm not sure
Guard.instance_variable_get(:@watchdirs)
Make sure the directory is physically in one of those directories.
(You can change this setting using the -w parameter in the commandline, or by using the directories statement in your Guardfile)
Guard watches directories recursively, so watching the parent directory should be enough.
Also, make sure you're not using chdir anywhere - because this may confuse Guard.
Then, your file may be ignored with the ignore rules. But it's best to check this from Listen:
$ LISTEN_GEM_DEBUGGING=1 bundle exec guard -d
If you don't understand the output (clue: the final changes should show what Guard is getting), then go through the Listen Wiki for more info: https://github.com/guard/listen/wiki
If you are getting "nothing" or "too many changes", here's how to get an answer quickly:
- include your
Guardfile(mostly the rules) - include your output from running
Guardwith the-dflag (if it's long, put it into a Gist) - include the output using
LISTEN_GEM_DEBUGGING=1above or evenLISTEN_GEM_DEBUGGING=2by pasting the output into a Gist
Those 3 should be enough for us to help you work out the problem.
At this point you should be a master at Guard - enough to know when something is problem with your config or with Guard.
Please report bugs as soon as possible (with the above info if you think the cause isn't "obvious").
Feel free to create Pull Requests - though to save time, open an issue first and ask to get guidance (since Guard is undergoing heavy rework).
Also, feel free to ask for new features or improvements - since Guard 3.x may be soon released with any wishes you have.
And, feel free to update/correct this Wiki to help others.
Thanks and have a great time using Guard!
This wiki and the Guard README document contain a lot of information, please take your time and read these instructions carefully.
If you run into any trouble, you may start by understanding how Guard works.
We provide detailed changes for each Guard release.
Be sure to read the CONTRIBUTING guidelines before reporting a new Guard issue or open a pull request.
If you have any questions about the Guard usage or want to share some information with the Guard community, please go to one of the following places:
- Google+ community
- Google group
- StackOverflow
- IRC channel
#guard(irc.freenode.net) for chatting