listen 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +7 -0
- data/LICENSE +20 -0
- data/README.md +63 -0
- data/lib/listen.rb +24 -0
- data/lib/listen/adapter.rb +18 -0
- data/lib/listen/adapters/polling.rb +48 -0
- data/lib/listen/listener.rb +203 -0
- data/lib/listen/version.rb +3 -0
- metadata +143 -0
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 The listen team
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Listen [](http://travis-ci.org/guard/listen)
|
2
|
+
|
3
|
+
Work in progress...
|
4
|
+
|
5
|
+
The Listen gem listens to file modifications and notifies you about the changes.
|
6
|
+
|
7
|
+
Here the API that should be implemented, feel free to give your feeback via [Listen issues](https://github.com/guard/listener/issues)
|
8
|
+
|
9
|
+
## Block API
|
10
|
+
|
11
|
+
### One dir
|
12
|
+
|
13
|
+
``` ruby
|
14
|
+
Listen.to(dir, filter: '**/*', ignore: paths) do |modified, added, removed|
|
15
|
+
...
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
### Multiple dir
|
20
|
+
|
21
|
+
``` ruby
|
22
|
+
Listen.to do
|
23
|
+
path(dir1, filter: '**/*', ignore: paths) do |modified, added, removed|
|
24
|
+
...
|
25
|
+
end
|
26
|
+
path(dir2, filter: '**/*', ignore: paths) do |modified, added, removed|
|
27
|
+
...
|
28
|
+
end
|
29
|
+
....
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
Question: if dir2 is a child of dir1 both path block will be call if a file inside dir2 is modified right?
|
34
|
+
|
35
|
+
## "Object" API
|
36
|
+
|
37
|
+
``` ruby
|
38
|
+
listen = Listen.to(dir)
|
39
|
+
listen.ignore('.git')
|
40
|
+
listen.filter('*.rb')
|
41
|
+
listen.modification(&on_modification)
|
42
|
+
listen.addition(&on_addition)
|
43
|
+
listen.removal(&on_removal)
|
44
|
+
listen.start # enter the run loop
|
45
|
+
listen.stop
|
46
|
+
```
|
47
|
+
|
48
|
+
### Chainable
|
49
|
+
|
50
|
+
``` ruby
|
51
|
+
Listen.to(dir).ignore('.git').filter('*.rb').modification(&on_modification).addition(&on_addition).removal(&on_removal).start # enter the run loop
|
52
|
+
```
|
53
|
+
|
54
|
+
### Multiple dir support available via Thread.
|
55
|
+
|
56
|
+
``` ruby
|
57
|
+
listen = Listen.ignore('.git')
|
58
|
+
styles = listen.to(dir1).filter('*.css').addition(&on_style_addition)
|
59
|
+
scripts = listen.to(dir2).filter('*.js').addition(&on_script_addition)
|
60
|
+
|
61
|
+
Thread.new { styles.start } # enter the run loop
|
62
|
+
Thread.new { scripts.start } # enter the run loop
|
63
|
+
```
|
data/lib/listen.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'listen/listener'
|
2
|
+
|
3
|
+
module Listen
|
4
|
+
|
5
|
+
# Listen to file system modifications.
|
6
|
+
#
|
7
|
+
# @param [String, Pathname] dir the directory to watch
|
8
|
+
# @param [Hash] options the listen options
|
9
|
+
# @option options [String] ignore a list of paths to ignore
|
10
|
+
# @option options [Regexp] filter a list of regexps file filters
|
11
|
+
#
|
12
|
+
# @yield [modified, added, removed] the changed files
|
13
|
+
# @yieldparam [Array<String>] modified the list of modified files
|
14
|
+
# @yieldparam [Array<String>] added the list of added files
|
15
|
+
# @yieldparam [Array<String>] removed the list of removed files
|
16
|
+
#
|
17
|
+
# @return [Listen::Listener] the file listener if no block given
|
18
|
+
#
|
19
|
+
def self.to(*args, &block)
|
20
|
+
listener = Listener.new(*args, &block)
|
21
|
+
block ? listener.start : listener
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Listen
|
2
|
+
class Adapter
|
3
|
+
|
4
|
+
# Select the appropriate adapter implementation for the
|
5
|
+
# current OS and initializes it.
|
6
|
+
#
|
7
|
+
# @return [Listen::Adapter] the chosen adapter
|
8
|
+
#
|
9
|
+
def self.select_and_initialize(listener)
|
10
|
+
Adapters::Polling.new(listener)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(listener)
|
14
|
+
@listener = listener
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Listen
|
2
|
+
module Adapters
|
3
|
+
|
4
|
+
# Polling Adapter that works cross-platform and
|
5
|
+
# has no dependencies. This is the adapter that
|
6
|
+
# uses the most CPU processing power and has higher
|
7
|
+
# file IO that the other implementations.
|
8
|
+
#
|
9
|
+
class Polling < Adapter
|
10
|
+
attr_accessor :latency
|
11
|
+
|
12
|
+
# Initialize the Adapter.
|
13
|
+
#
|
14
|
+
def initialize(*)
|
15
|
+
super
|
16
|
+
@latency = 1.0
|
17
|
+
end
|
18
|
+
|
19
|
+
# Start the adapter.
|
20
|
+
#
|
21
|
+
def start
|
22
|
+
@stop = false
|
23
|
+
poll
|
24
|
+
end
|
25
|
+
|
26
|
+
# Stop the adapter.
|
27
|
+
#
|
28
|
+
def stop
|
29
|
+
@stop = true
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# Poll listener directory for file system changes.
|
35
|
+
#
|
36
|
+
def poll
|
37
|
+
until @stop
|
38
|
+
start = Time.now.to_f
|
39
|
+
@listener.on_change(@listener.directory)
|
40
|
+
nap_time = @latency - (Time.now.to_f - start)
|
41
|
+
sleep(nap_time) if nap_time > 0
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require 'find'
|
2
|
+
require 'listen/adapter'
|
3
|
+
require 'listen/adapters/polling'
|
4
|
+
|
5
|
+
module Listen
|
6
|
+
class Listener
|
7
|
+
attr_accessor :directory, :ignored_paths, :file_filters, :paths
|
8
|
+
|
9
|
+
# Default paths that gets ignored by the listener
|
10
|
+
DEFAULT_IGNORED_PATHS = %w[.bundle .git log tmp vendor]
|
11
|
+
|
12
|
+
# Initialize the file listener.
|
13
|
+
#
|
14
|
+
# @param [String, Pathname] dir the directory to watch
|
15
|
+
# @param [Hash] options the listen options
|
16
|
+
# @option options [String] ignore a list of paths to ignore
|
17
|
+
# @option options [Regexp] filter a list of regexps file filters
|
18
|
+
#
|
19
|
+
# @yield [modified, added, removed] the changed files
|
20
|
+
# @yieldparam [Array<String>] modified the list of modified files
|
21
|
+
# @yieldparam [Array<String>] added the list of added files
|
22
|
+
# @yieldparam [Array<String>] removed the list of removed files
|
23
|
+
#
|
24
|
+
# @return [Listen::Listener] the file listener
|
25
|
+
#
|
26
|
+
def initialize(*args, &block)
|
27
|
+
@directory = args.first
|
28
|
+
@ignored_paths = DEFAULT_IGNORED_PATHS
|
29
|
+
@file_filters = []
|
30
|
+
@block = block
|
31
|
+
if args[1]
|
32
|
+
@ignored_paths += Array(args[1][:ignore]) if args[1][:ignore]
|
33
|
+
@file_filters += Array(args[1][:filter]) if args[1][:filter]
|
34
|
+
end
|
35
|
+
@adapter = Adapter.select_and_initialize(self)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Initialize the @paths and start the adapter.
|
39
|
+
#
|
40
|
+
def start
|
41
|
+
init_paths
|
42
|
+
@adapter.start
|
43
|
+
end
|
44
|
+
|
45
|
+
# Stop the adapter.
|
46
|
+
#
|
47
|
+
def stop
|
48
|
+
@adapter.stop
|
49
|
+
end
|
50
|
+
|
51
|
+
# Add ignored path to the listener.
|
52
|
+
#
|
53
|
+
# @example Ignore some paths
|
54
|
+
# ignore ".git", ".svn"
|
55
|
+
#
|
56
|
+
# @param [Array<String>] paths a list of paths to ignore
|
57
|
+
#
|
58
|
+
# @return [Listen::Listener] the listener itself
|
59
|
+
#
|
60
|
+
def ignore(*paths)
|
61
|
+
@ignored_paths.push(*paths)
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
# Add file filters to the listener.
|
66
|
+
#
|
67
|
+
# @example Filter some files
|
68
|
+
# ignore /\.txt$/, /.*\.zip/
|
69
|
+
#
|
70
|
+
# @param [Array<Regexp>] regexps a list of regexps file filters
|
71
|
+
#
|
72
|
+
# @return [Listen::Listener] the listener itself
|
73
|
+
#
|
74
|
+
def filter(*regexps)
|
75
|
+
@file_filters.push(*regexps)
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
# Set change callback block to the listener.
|
80
|
+
#
|
81
|
+
# @example Filter some files
|
82
|
+
# callback = lambda { |modified, added, removed| ... }
|
83
|
+
# change &callback
|
84
|
+
#
|
85
|
+
# @param [Block] block a block callback called on changes
|
86
|
+
#
|
87
|
+
# @return [Listen::Listener] the listener itself
|
88
|
+
#
|
89
|
+
def change(&block) # modified, added, removed
|
90
|
+
@block = block
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
# Call @block callback when there is a diff in the passed directory.
|
95
|
+
#
|
96
|
+
# @param [String] directory the directory to diff
|
97
|
+
#
|
98
|
+
def on_change(directory)
|
99
|
+
changes = diff(directory)
|
100
|
+
unless changes == { :modified => [], :added => [], :removed => [] }
|
101
|
+
@block.call(changes[:modified],changes[:added],changes[:removed])
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Initialize the @paths double levels Hash with all existing paths.
|
106
|
+
#
|
107
|
+
def init_paths
|
108
|
+
@paths = Hash.new { |h,k| h[k] = {} }
|
109
|
+
all_existing_paths { |path| insert_path(path) }
|
110
|
+
end
|
111
|
+
|
112
|
+
# Detect changes diff in a directory.
|
113
|
+
#
|
114
|
+
# @param [String] directory the path to diff
|
115
|
+
# @return [Hash<Array>] the file changes
|
116
|
+
#
|
117
|
+
def diff(directory = @directory)
|
118
|
+
@changes = { :modified => [], :added => [], :removed => [] }
|
119
|
+
detect_modifications_and_removals(directory)
|
120
|
+
detect_additions(directory)
|
121
|
+
@changes
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
# Research all existing paths (directories & files) filtered and without ignored directories paths.
|
127
|
+
#
|
128
|
+
# @yield [path] the existing path
|
129
|
+
#
|
130
|
+
def all_existing_paths
|
131
|
+
Find.find(@directory) do |path|
|
132
|
+
if File.directory?(path)
|
133
|
+
if @ignored_paths.any? { |ignored_path| path =~ /#{ignored_path}$/ }
|
134
|
+
Find.prune # Don't look any further into this directory.
|
135
|
+
else
|
136
|
+
yield(path)
|
137
|
+
end
|
138
|
+
elsif @file_filters.empty? || @file_filters.any? { |file_filter| path =~ file_filter }
|
139
|
+
yield(path)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Insert a path with its File.stat in @paths.
|
145
|
+
#
|
146
|
+
# @param [String] path the path to insert in @paths.
|
147
|
+
#
|
148
|
+
def insert_path(path)
|
149
|
+
@paths[File.dirname(path)][File.basename(path)] = File.stat(path)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Detect modifications and removals recursivly in a directory.
|
153
|
+
#
|
154
|
+
# @param [String] directory the path to analyze
|
155
|
+
#
|
156
|
+
def detect_modifications_and_removals(directory)
|
157
|
+
@paths[directory].each do |basename, stat|
|
158
|
+
path = File.join(directory, basename)
|
159
|
+
|
160
|
+
if stat.directory?
|
161
|
+
detect_modifications_and_removals(path)
|
162
|
+
@paths[directory].delete(basename) unless File.directory?(path)
|
163
|
+
else
|
164
|
+
if File.exist?(path)
|
165
|
+
new_stat = File.stat(path)
|
166
|
+
if stat.mtime != new_stat.mtime
|
167
|
+
@changes[:modified] << relative_path(path)
|
168
|
+
@paths[directory][basename] = new_stat
|
169
|
+
end
|
170
|
+
else
|
171
|
+
@paths[directory].delete(basename)
|
172
|
+
@changes[:removed] << relative_path(path)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Detect additions in a directory.
|
179
|
+
#
|
180
|
+
# @param [String] directory the path to analyze
|
181
|
+
#
|
182
|
+
def detect_additions(directory)
|
183
|
+
all_existing_paths do |path|
|
184
|
+
next if @paths[File.dirname(path)][File.basename(path)]
|
185
|
+
|
186
|
+
@changes[:added] << relative_path(path) if File.file?(path)
|
187
|
+
insert_path(path)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Convert absolute path to a path relative to the listener directory (by default).
|
192
|
+
#
|
193
|
+
# @param [String] path the path to convert
|
194
|
+
# @param [String] directory the directoy path to relative from
|
195
|
+
# @return [String] the relative converted path
|
196
|
+
#
|
197
|
+
def relative_path(path, directory = @directory)
|
198
|
+
base_dir = directory.sub(/\/$/, '')
|
199
|
+
path.sub(%r(^#{base_dir}/), '')
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
end
|
metadata
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: listen
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Travis Tilley
|
9
|
+
- Yehuda Katz
|
10
|
+
- Thibaud Guillaume-Gentil
|
11
|
+
- Rémy Coutable
|
12
|
+
- Michael Kessler
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
date: 2012-01-28 00:00:00.000000000 Z
|
17
|
+
dependencies:
|
18
|
+
- !ruby/object:Gem::Dependency
|
19
|
+
name: bundler
|
20
|
+
requirement: &70178027214260 !ruby/object:Gem::Requirement
|
21
|
+
none: false
|
22
|
+
requirements:
|
23
|
+
- - ! '>='
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
type: :development
|
27
|
+
prerelease: false
|
28
|
+
version_requirements: *70178027214260
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: guard
|
31
|
+
requirement: &70178027229800 !ruby/object:Gem::Requirement
|
32
|
+
none: false
|
33
|
+
requirements:
|
34
|
+
- - ~>
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 1.0.0
|
37
|
+
type: :development
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: *70178027229800
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: rspec
|
42
|
+
requirement: &70178027228980 !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.8.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: *70178027228980
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: guard-rspec
|
53
|
+
requirement: &70178027228220 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ~>
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: 0.6.0
|
59
|
+
type: :development
|
60
|
+
prerelease: false
|
61
|
+
version_requirements: *70178027228220
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: yard
|
64
|
+
requirement: &70178027227420 !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: *70178027227420
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: redcarpet
|
75
|
+
requirement: &70178027226740 !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
type: :development
|
82
|
+
prerelease: false
|
83
|
+
version_requirements: *70178027226740
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: pry
|
86
|
+
requirement: &70178027226280 !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ! '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
type: :development
|
93
|
+
prerelease: false
|
94
|
+
version_requirements: *70178027226280
|
95
|
+
description: The Listen gem listens to file modifications and notifies you about the
|
96
|
+
changes.
|
97
|
+
email:
|
98
|
+
- ttilley@gmail.com
|
99
|
+
- wycats@gmail.com
|
100
|
+
- thibaud@thibaud.me
|
101
|
+
- rymai@rymai.me
|
102
|
+
- michi@netzpiraten.ch
|
103
|
+
executables: []
|
104
|
+
extensions: []
|
105
|
+
extra_rdoc_files: []
|
106
|
+
files:
|
107
|
+
- lib/listen/adapter.rb
|
108
|
+
- lib/listen/adapters/polling.rb
|
109
|
+
- lib/listen/listener.rb
|
110
|
+
- lib/listen/version.rb
|
111
|
+
- lib/listen.rb
|
112
|
+
- CHANGELOG.md
|
113
|
+
- LICENSE
|
114
|
+
- README.md
|
115
|
+
homepage: https://github.com/guard/listen
|
116
|
+
licenses: []
|
117
|
+
post_install_message:
|
118
|
+
rdoc_options: []
|
119
|
+
require_paths:
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ! '>='
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
hash: -4340389000594607932
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
none: false
|
132
|
+
requirements:
|
133
|
+
- - ! '>='
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: 1.3.6
|
136
|
+
requirements: []
|
137
|
+
rubyforge_project: listen
|
138
|
+
rubygems_version: 1.8.12
|
139
|
+
signing_key:
|
140
|
+
specification_version: 3
|
141
|
+
summary: Listen to file modifications
|
142
|
+
test_files: []
|
143
|
+
has_rdoc:
|