listen 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ## Master
2
+
3
+ ## 0.1.0 - January 28, 2012
4
+
5
+ - First version with only a polling adapter and basic features set. ([@thibaudgg][])
6
+
7
+ [@thibaudgg]: https://github.com/thibaudgg
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.
@@ -0,0 +1,63 @@
1
+ # Listen [![Build Status](https://secure.travis-ci.org/guard/listen.png?branch=master)](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
+ ```
@@ -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
@@ -0,0 +1,3 @@
1
+ module Listen
2
+ VERSION = '0.1.0'
3
+ 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: