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.
@@ -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: