Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi directories support #16

Merged
merged 27 commits into from Mar 28, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
09b8159
Replace "or" with "||"
Maher4Ever Mar 17, 2012
2a7a9de
Add DirectoryRecord class to abstract filesystem operations from the …
Maher4Ever Mar 21, 2012
247dfa4
Allow the linux adapter to watch multiple directories
Maher4Ever Mar 21, 2012
8596a88
Allow the polling adapter to watch multiple directories
Maher4Ever Mar 21, 2012
14e542c
Change the implementation of watching multiple directories on the lin…
Maher4Ever Mar 21, 2012
096121c
Allow the windows adapter to watch multiple directories
Maher4Ever Mar 22, 2012
f72a193
Allow the darwin adapter to watch multiple directories
Maher4Ever Mar 22, 2012
59e1ea5
Fix a spec
Maher4Ever Mar 22, 2012
891e41a
Add MultiListener class
Maher4Ever Mar 22, 2012
d268f9c
Add Listen.to_each method
Maher4Ever Mar 22, 2012
ac9e381
Fix specs on 1.8 implementations of ruby
Maher4Ever Mar 22, 2012
82391e5
Update CHANGELOG [ci skip]
Maher4Ever Mar 22, 2012
cd17789
Make listeners block execution by default
Maher4Ever Mar 23, 2012
d7e6578
Update CHANGELOG [ci skip]
Maher4Ever Mar 23, 2012
d0d9b5c
Fix typo [ci skip]
Maher4Ever Mar 23, 2012
d08f87b
Update README with the new changes [ci skip]
Maher4Ever Mar 23, 2012
8ce6f74
Use absolute paths by default on all listeners.
Maher4Ever Mar 25, 2012
1aa5ebe
Rename the absolute_paths option to relative_paths
Maher4Ever Mar 26, 2012
48d5787
Remove Listen.to_each() in favor of using Listen.to() to handle singl…
Maher4Ever Mar 26, 2012
3d71d60
Fix a few specs bugs on ruby 1.8
Maher4Ever Mar 26, 2012
912ba05
Fix a few typos
Maher4Ever Mar 26, 2012
d29fd1b
Fix spacing
Maher4Ever Mar 26, 2012
ca5e296
Update CHANGELOG
Maher4Ever Mar 26, 2012
e987611
Update README
Maher4Ever Mar 26, 2012
23d8e41
Fix formatting in the README file [ci skip]
Maher4Ever Mar 26, 2012
5d24614
Fix typos and spacing
Maher4Ever Mar 28, 2012
3dd5b5f
Merge remote-tracking branch 'origin/master' into multi-dirs
Maher4Ever Mar 28, 2012
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 8 additions & 2 deletions CHANGELOG.md
@@ -1,7 +1,13 @@
## 0.4 - March 17, 2012
## 0.4 - March 26, 2012

### New features
- Add `wait_for_callback` method to adapters. ([@Maher4Ever][])

- Add `wait_for_callback` method to all adapters. ([@Maher4Ever][])
- Add `Listen::MultiListener` class to listen to multiple directories at once. ([@Maher4Ever][])
- Allow passing multiple directories to the `Listen.to` method. ([@Maher4Ever][])
- Add `blocking` option to `Listen#start` which can be used to disable blocking the current thread upon starting. ([@Maher4Ever][])
- Use absolute-paths in callbacks by default instead of relative-paths. ([@Maher4Ever][])
- Add `relative_paths` option to `Listen::Listener` to retain the old functionality. ([@Maher4Ever][])

### Improvements

Expand Down
161 changes: 137 additions & 24 deletions README.md
Expand Up @@ -5,12 +5,13 @@ The Listen gem listens to file modifications and notifies you about the changes.
## Features

* Works everywhere!
* Supports watching multiple directories from a single listener.
* OS-specific adapters for Mac OS X 10.6+, Linux and Windows.
* Automatic fallback to polling if OS-specific adapter doesn't work.
* Detects files modification, addidation and removal.
* Checksum comparaison for modifications made under the same second.
* Allows ignoring paths and supplying filters for better results.
* Tested on all Ruby environments via [travis-ci](http://travis-ci.org/guard/listen).
* Threadable.

## Install

Expand All @@ -22,17 +23,23 @@ gem install listen

There are **two ways** to use Listen:

1. Call `Listen.to` with a path and a few (optional) params, then define the `change` callback in a block.
1. Call `Listen.to` with either a single directory or multiple directories, then define the `change` callback in a block.
2. Create a `listener` object and use it in an (ARel style) chainable way.

Feel free to give your feeback via [Listen issues](https://github.com/guard/listener/issues)

### Block API

``` ruby
# Listen to a single directory.
Listen.to('dir/path/to/listen', filter: /.*\.rb/, ignore: '/ignored/path') do |modified, added, removed|
# ...
end

# Listen to multiple directories.
Listen.to('dir/to/awesome_app', 'dir/to/other_app', filter: /.*\.rb/, latency: 0.1) do |modified, added, removed|
# ...
end
```

### "Object" API
Expand All @@ -45,11 +52,10 @@ listener = listener.latency(0.5)
listener = listener.force_polling(true)
listener = listener.polling_fallback_message(false)
listener = listener.change(&callback)
listener.start # enter the run loop
listener.stop
listener.start # blocks execution!
```

#### Chainable
### Chainable

``` ruby
Listen.to('dir/path/to/listen')
Expand All @@ -59,21 +65,121 @@ Listen.to('dir/path/to/listen')
.force_polling(true)
.polling_fallback_message('custom message')
.change(&callback)
.start # enter the run loop
.start # blocks execution!
```

#### Multiple listeners support available via Thread
### Pause/Unpause

Listener can also easily be paused/unpaused:

``` ruby
listener = Listen.to(dir1).ignore('/ignored/path/')
styles = listener.filter(/.*\.css/).change(&style_callback)
scripts = listener.filter(/.*\.js/).change(&scripts_callback)
listener = Listen.to('dir/path/to/listen')
listener.start(false) # non-blocking mode
listener.pause # stop listening to changes
listener.paused? # => true
listener.unpause
listener.stop
```

## Listening to changes on multiple directories

The Listen gem provides the `MultiListener` class to watch multiple directories and
handle their changes from a single listener:

```ruby
listener = Listen::MultiListener.new('app/css', 'app/js')
listener.latency(0.5)

# Configure the listener to your needs...

listener.start # blocks execution!
````

For an easier access, the `Listen.to` method can also be used to create a multi-listener:

``` ruby
listener = Listen.to('app/css', 'app/js')
.ignore('vendor') # both js/vendor and css/vendor will be ignored
.change(&assets_callback)

listener.start # blocks execution!
```

## Changes callback

Changes to the listened-to directories gets reported back to the user in a callback.
The registered callback gets invoked, when there are changes, with **three** parameters:
`modified_paths`, `added_paths` and `removed_paths` in that particular order.

You can register a callback in two ways. The first way is by passing a block when calling
the `Listen.to` method or when initializing a listener object:

```ruby
Listen.to('path/to/app') do |modified, added, removed|
# This block will be called when there are changes.
end

# or ...

listener = Listen::Listener.new('path/to/app') do |modified, added, removed|
# This block will be called when there are changes.
end

```

Thread.new { styles.start } # enter the run loop
Thread.new { scripts.start } # enter the run loop
The second way to register a callback is be calling the `change` method on any
listener passing it a block:

```ruby
# Create a callback
callback = Proc.new do |modified, added, removed|
# This proc will be called when there are changes.
end

listener = Listen.to('dir')
listener.change(&callback) # convert the callback to a block and register it

listener.start # blocks execution
```

### Paths in callbacks

Listeners invoke callbacks passing them absolute paths by default:

```ruby
# Assume someone changes the 'style.css' file in '/home/user/app/css' after creating
# the listener.
Listen.to('/home/user/app/css') do |modified, added, removed|
modified.inspect # => ['/home/user/app/css/style.css']
end
```

### Options
#### Relative paths in callbacks

When creating a listener for a **single** path (more specifically a `Listen::Listener` instance),
you can pass `:relative_paths => true` as an option to get relative paths in
your callback:

```ruby
# Assume someone changes the 'style.css' file in '/home/user/app/css' after creating
# the listener.
Listen.to('/home/user/app/css', :relative_paths => true) do |modified, added, removed|
modified.inspect # => ['style.css']
end
```

Passing the `:relative_paths => true` option won't work when listeneing to multiple
directories:

```ruby
# Assume someone changes the 'style.css' file in '/home/user/app/css' after creating
# the listener.
Listen.to('/home/user/app/css', '/home/user/app/js', :relative_paths => true) do |modified, added, removed|
modified.inspect # => ['/home/user/app/css/style.css']
end
```

## Options

These options can be set through `Listen.to` params or via methods (see the "Object" API)

Expand All @@ -94,21 +200,28 @@ These options can be set through `Listen.to` params or via methods (see the "Obj
# default: "WARNING: Listen fallen back to polling, learn more at https://github.com/guard/listen#fallback."
```

### Pause/Unpause
### Non-blocking listening to changes

Listener can also easily be paused/unpaused:
Starting a listener blocks the current thread by default. That means any code after the
`start` call won't be run until the listener is stopped (which needs to be done from another thread).

``` ruby
For advanced usage there is an option to disable this behavior and have the listener start working
in the background without blocking. To enable non-blocking listening the `start` method of
the listener (be it `Listener` or `MultiListener`) needs to be called with `false` as a parameter.

Here is an example of using a listener in the non-blocking mode:

```ruby
listener = Listen.to('dir/path/to/listen')
Thread.new { listener.start } # enter the run loop
listener.wait_until_listening # blocks exection until the listener starts
listener.pause # stop listening to changes
listener.paused? # => true
listener.unpause
listener.wait_until_listening
listener.stop
listener.start(false) # doesn't block execution

# Code here will run immediately after starting the listener

```

**note**: Using the `Listen.to` helper-method with a callback-block will always
block execution. See the "Block API" section for an example.

## Listen adapters

The Listen gem has a set of adapters to notify it when there are changes.
Expand All @@ -124,7 +237,7 @@ while initializing the listener or call the `force_polling` method on your liste
before starting it.

<a name="fallback"/>
### Polling fallback
## Polling fallback

When a OS-specific adapter doesn't work the Listen gem automatically falls back to the polling adapter.
Here are some things you could try to avoid the polling fallback:
Expand Down
32 changes: 22 additions & 10 deletions lib/listen.rb
@@ -1,15 +1,22 @@
require 'listen/listener'

module Listen

# Listen to file system modifications.
autoload :Turnstile, 'listen/turnstile'
autoload :Listener, 'listen/listener'
autoload :MultiListener, 'listen/multi_listener'
autoload :DirectoryRecord, 'listen/directory_record'
autoload :Adapter, 'listen/adapter'

module Adapters
autoload :Darwin, 'listen/adapters/darwin'
autoload :Linux, 'listen/adapters/linux'
autoload :Windows, 'listen/adapters/windows'
autoload :Polling, 'listen/adapters/polling'
end

# Listens to filesystem modifications on a either single directory or multiple directories.
#
# @param [String, Pathname] dir the directory to watch
# @param [Hash] options the listen options
# @option options [String] ignore a list of paths to ignore
# @option options [Regexp] filter a list of regexps file filters
# @option options [Float] latency the delay between checking for changes in seconds
# @option options [Boolean] polling whether to force or disable the polling adapter
# @param (see Listen::Listener#new)
# @param (see Listen::MultiListener#new)
#
# @yield [modified, added, removed] the changed files
# @yieldparam [Array<String>] modified the list of modified files
Expand All @@ -19,7 +26,12 @@ module Listen
# @return [Listen::Listener] the file listener if no block given
#
def self.to(*args, &block)
listener = Listener.new(*args, &block)
listener = if args.length == 1 || ! args[1].is_a?(String)
Listener.new(*args, &block)
else
MultiListener.new(*args, &block)
end

block ? listener.start : listener
end

Expand Down