joshaven-filemonitor 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ === 0.0.3 2009-09-24
2
+ * Major Enhancements:
3
+ * Accept regexp filter when adding files: fm.add(path, regexp=/.*/, &block)
4
+ * Minor changes
5
+ * Change this file from History.txt to CHANGELOG
6
+ * Remove unused options hash from the process method
7
+ * Remove script/generate script/console, script/destroy & Rakefile from gem
8
+ * Fixup filemonitor.gemspec to work for github gem creation
9
+
10
+ === 0.0.2 2009-09-24
11
+
12
+ * Major Enhancements:
13
+ * Various API Changes!
14
+ * RSpec Testing! Oh yah!
15
+ * RDocs! Oh yah!
16
+ * Reduced I/O load: Updated method of saving old file state. Changes are not
17
+ detectable if changed within 1 second of prior digest. If this is a problem
18
+ for anyone, I can make a md5 digest of the file optional.
19
+ * Handle duplicates file entries by overwriting the old with the new
20
+ * Add start / stop & halt methods for handling spawns
21
+
22
+ === 0.0.1 2009-09-15
23
+
24
+ * 1 major enhancement:
25
+ * Initial release
@@ -0,0 +1,11 @@
1
+ CHANGELOG
2
+ Manifest.txt
3
+ README.markdown
4
+ lib/FileMonitor.rb
5
+ lib/FileMonitor/store.rb
6
+ spec/FileMonitor_spec.rb
7
+ spec/MonitoredItem_spec.rb
8
+ spec/spec.opts
9
+ spec/spec_helper.rb
10
+ tasks/rspec.rake
11
+ tasks/rdoc.rake
@@ -0,0 +1,90 @@
1
+ # FileMonitor
2
+ Calls a Proc when a watched file is changed.
3
+
4
+ * Documentation: http://filemonitor.rubyforge.org/rdoc
5
+ * Code Repository: http://github.com/joshaven/FileMonitor
6
+ * Joshaven Potter: yourtech@gmail.com
7
+
8
+ ## Notice
9
+ This project has only been tested on posix compatible systems, sorry for you windows users. If you get this
10
+ to run under windows, let me know and I'll release a windows compatible version.
11
+
12
+ ## Installation
13
+ gem install filemonitor # you may need to run: sudo gem install filemonitor
14
+
15
+ ## Examples
16
+ In the following examples, the block passed will be executed when the file changes. These examples only
17
+ echo strings but I am sure you'll find something more useful to do in the callback proc.
18
+
19
+ require 'filemonitor'
20
+
21
+ # Example 1: Generate a FileMonitor subprocess with one command:
22
+ fm = FileMonitor.when_modified(Dir.pwd, "/path/to/other/file.rb") do |watched_item|
23
+ puts "Change detected in #{watched_item.path} "
24
+ end
25
+
26
+ # thats it... if you want to stop the subprocess, issue: fm.stop
27
+
28
+ # Example 2: Generate a manually run FileMonitor:
29
+ file_spy = FileMonitor.new {|f| puts "Default proc called because #{f.path} changed."}
30
+ file_spy.add(Dir.pwd) do |f|
31
+ puts "Monitored Item's proc called because #{f.path} changed."
32
+ end
33
+
34
+ file_spy.process # This will look for changes one time... call this again when you want to find changes.
35
+ # if you want to launch a background process then do file_spy.spawn or its alias: file_spy.start
36
+
37
+ ### Context Examples
38
+ Both the FileMonitor.new method and a FileMonitor instance fm.add method can accept a block with 0 to 2 arities.
39
+ See Also the api docs: <http://filemonitor.rubyforge.org/rdoc>
40
+
41
+ This block is working in the context in which it was created:
42
+
43
+ changes = false
44
+ fm.add(Dir.pwd) {changes = true} # The 'changes' variable will be set to true when a change is detected
45
+ fm.process
46
+ respond_to_file_changes if changes
47
+
48
+ This block is working in the context created and is aware of the watched file instance 'f'. The watched file will
49
+ respond to :path, :callback, & :digest and is an instance of: MonitoredItems::Store
50
+
51
+ changed_ruby_files = []
52
+ fm.add(Dir.pwd, /\.rb$/) do |f| # Adds all .rb files recursively starting with the working directory
53
+ changed_ruby_files << f.path # The changed_ruby_files will contain the path of the changed files.
54
+ end
55
+ fm.process
56
+ handel_changes(changed_ruby_files) # Do somehting with the changed files
57
+ changed_ruby_files = [] # Cleanup changes so the array doesn't keep growing
58
+
59
+
60
+ This block is working in the context created and is aware of the watched_file instance 'f' as well as the
61
+ file_monitor instance 'fm'. The fm object can be acted on within the block the same way it is outside the block,
62
+ an example use of this may be to add all files in a folder when a file is changed...
63
+
64
+ fm.add(Dir.pwd) do |f, fm|
65
+ fm.add(APP_ROOT) if f.path == (APP_ROOT + Manifest.txt) # Start watching any new files when added to to the manifest.txt file.
66
+ end
67
+
68
+ ## License
69
+ (The MIT License)
70
+
71
+ Copyright (c) 2009 Joshaven Potter
72
+
73
+ Permission is hereby granted, free of charge, to any person obtaining
74
+ a copy of this software and associated documentation files (the
75
+ 'Software'), to deal in the Software without restriction, including
76
+ without limitation the rights to use, copy, modify, merge, publish,
77
+ distribute, sublicense, and/or sell copies of the Software, and to
78
+ permit persons to whom the Software is furnished to do so, subject to
79
+ the following conditions:
80
+
81
+ The above copyright notice and this permission notice shall be
82
+ included in all copies or substantial portions of the Software.
83
+
84
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
85
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
86
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
87
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
88
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
89
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
90
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,288 @@
1
+ require File.dirname(__FILE__) + '/FileMonitor/store'
2
+ require 'find' # needed for the :files_recursive method
3
+ # Purpose:
4
+ # Watches the file system for changes
5
+ #
6
+ # Usage:
7
+ #
8
+ # require 'filemonitor'
9
+ #
10
+ # # Create a FileMonitor instance and assign the callback to the entire object.
11
+ # # In the following example, "watched_item" is a MonitoredItems::Store, see the new
12
+ # # method for a better example of working in your block.
13
+ # file_spy = FileMonitor.new do |watched_item| ... end
14
+ #
15
+ # # Any files in the working directory (Dir.pwd) and its sub-directories will
16
+ # # be watched for changes. If a change is found the callback assigned above
17
+ # # will be enacted.
18
+ # file_spy << Dir.pwd
19
+ # file_spy << "/path/to/other/file.rb"
20
+ #
21
+ # # launch an independent process to do the monitoring:
22
+ # file_spy.spawn
23
+ #
24
+ # # Alternatively you can do all of the above in one line:
25
+ # FileMonitor.when_modified(Dir.pwd, "/path/to/other/file.rb") do |watched_item| ... end
26
+ class FileMonitor
27
+ VERSION = '0.0.3'
28
+ attr_accessor :callback, :pid, :watched
29
+ # The new method may be called with an optional callback which must be a block
30
+ # either do...end or {...}.
31
+ # The block may consiste of upto arguments ie {|watched_item, monitored|}, in which case, the
32
+ # watched_item is an instance of FileMonitor::Store and monitored is the FileMonitor instances self.
33
+ #
34
+ # The first argument of the block, 'watched_item' in this case, will respond to: (:path, :modified & :callback).
35
+ # The second argument of the block, 'monitored' in this case, will respond any FileMonitor method.
36
+ #
37
+ # Example:
38
+ # FileMonitor.new do |watched_item|
39
+ # puts "I am watched this file: #{watched_item.path}"
40
+ # puts "When I find a change I will call watched_item.callback"
41
+ # end
42
+ #
43
+ # FileMonitor.new do |watched_item, monitored|
44
+ #
45
+ # # add files from a file that is a list of files to watch... Note: There
46
+ # IO.readlines(watched_item.path).each {|file| monitored << file } if watched_item.path == '/path/to/file/watchme.list'
47
+ #
48
+ # # clear watchme.list so we won't add all watch files every time the file changes
49
+ # open(watched_item) { |f| puts '' }
50
+ # end
51
+ def initialize(&callback)
52
+ @watched = []
53
+ @callback = callback unless callback.nil?
54
+ end
55
+
56
+ # Returns a spawned FileMonitor instance. The independent process automatically calls the given
57
+ # callback when changes are found.
58
+ #
59
+ # Example:
60
+ # fm = FileMonitor.when_modified(Dir.pwd, "/path/to/other/file.rb") {|watched_item, file_monitor| ... }
61
+ # fm.pid # => 23994
62
+ # fm.callback.nil? # => false
63
+ # fm.watched.size # => 28
64
+ def self.when_modified(*paths, &callback)
65
+ fm = FileMonitor.new &callback
66
+ paths.each {|path| fm << path}
67
+ fm.spawn
68
+ return fm
69
+ end
70
+
71
+ # The add method accepts a directory path or file path and optional callback. If a directory path is given all files in that
72
+ # path are recursively added. If a callback is given then that proc will be called when a change is detected on that file or
73
+ # group of files. If no proc is given via the add method then the object callback is called. If a regexp is given as the
74
+ # second argument only files matching the regexp will be monitored.
75
+ #
76
+ # Example:
77
+ # fm = FileMonitor.new do |path|
78
+ # puts "Detected a change on #{path}"
79
+ # end
80
+ #
81
+ # # The following will run the default callback when changes are found in the /tmp folder:
82
+ # fm.add '/tmp'
83
+ #
84
+ # # The following will run the given callback on any files ending in 'txt' in the /home folder when changed:
85
+ # fm.add('/home', /txt$/) do |path|
86
+ # puts "A users file has changed: #{path}"
87
+ # end
88
+ def add(path, regexp_file_filter=/.*/, &callback)
89
+ if File.file?(path) && regexp_file_filter === File.split(path).last
90
+ index = index_of(path) || @watched.size
91
+ @watched[index] = MonitoredItems::Store.new({:path=>File.expand_path(path), :callback=>callback, :digest=>digest(path)})
92
+ return true
93
+ elsif File.directory? path
94
+ files_recursive(path).each {|f| add(f, regexp_file_filter, &callback) }
95
+ return true
96
+ end
97
+ false
98
+ end
99
+
100
+ # The '<<' method works the same way as the 'add' method but does not support a callback.
101
+ #
102
+ # Example:
103
+ # fm = FileMonitor.new do |path|
104
+ # puts "Detected a change on #{path}"
105
+ # end
106
+ #
107
+ # # The following will run the default callback when changes are found in the /tmp folder:
108
+ # fm << '/tmp'
109
+ def <<(path)
110
+ add path, regexp_file_filter
111
+ end
112
+
113
+ # Itterates watched files and runs callbacks when changes are detected. This is the semi-automatic way to run the FileMonitor.
114
+ #
115
+ # Example:
116
+ # changed_files = []
117
+ # fm = FileMonitor.new() {|watched_item| changed_files = watched_item.path}
118
+ # fm << '/tmp'
119
+ # fm.process # this will look for changes in any watched items only once... call this when you want to look for changes.
120
+ def process
121
+ @watched.each do |i|
122
+ key = digest(i.path)
123
+ # i.digest = key if i.digest.nil? # skip first change detection, its always unknown on first run
124
+
125
+ unless i.digest == key
126
+ respond_to_change(i, key)
127
+ end
128
+ end
129
+ end
130
+
131
+ # Runs an endless loop watching for changes. It will sleep for the given interval between looking for changes. This method
132
+ # is intended to be run in a subprocess or threaded environment. The spawn method calls this method and takes care of
133
+ # the forking and pid management for you.
134
+ #
135
+ # Example:
136
+ # fm = FileMonitor.new
137
+ # fm << '/tmp'
138
+ # fm.monitor
139
+ # puts "will not get here unless a signal is sent to the process which interrupts the loop."
140
+ def monitor(interval = 1)
141
+ trap("INT") do
142
+ puts " FileMonitor was interrupted by Control-C... exiting gracefully"
143
+ # exit
144
+ @shutdown = true
145
+ end
146
+
147
+ trap("USR1") do
148
+ puts " FileMonitor was asked nicely to stop."
149
+ @shutdown = true
150
+ pid = nil
151
+ end
152
+
153
+ trap("USR2") do
154
+ puts " FileMonitor was halted."
155
+ pid = nil
156
+ exit
157
+ end
158
+
159
+
160
+ while true
161
+ exit if @shutdown
162
+ process
163
+ sleep interval unless @shutdown
164
+ end
165
+ end
166
+
167
+ # Returns index of watched item or false if non existant.
168
+ #
169
+ # Example:
170
+ # fm = FileMonitor.new
171
+ # fm << '/tmp/first.txt'
172
+ # fm << '/tmp/second.txt'
173
+ # fm.index_of '/tmp/first.txt' # => 0
174
+ # fm.index_of '/tmp/first.txt' # => 1
175
+ # fm.index_of '/tmp/woops.txt' # => false
176
+ def index_of(path)
177
+ watched.each_with_index {|watched,i| return i if watched.path == path}
178
+ false
179
+ end
180
+
181
+ # Spauns a child process that is looking for changes at every given interval.
182
+ # The interval is in seconds and defaults to 1 second.
183
+ #
184
+ # Example:
185
+ # fm = FileMonitor.new {|watched_item| puts 'do something when file is changed'}
186
+ # fm << @app_root + '/lib'
187
+ # fm.spawn # and now its doing its job...
188
+ def spawn(interval = 1)
189
+ if pid.nil?
190
+ @pid = fork {monitor interval}
191
+ Process.detach(pid)
192
+
193
+ Kernel.at_exit do
194
+ # sends the kill command unless the pid is not found on the system
195
+ Process.kill('HUP', pid) if process_running?
196
+ pid = nil
197
+ end
198
+ end
199
+ pid
200
+ end
201
+ alias_method :start, :spawn
202
+ # Stops a spawned FileMonitor instance. The FileMonitor will finish the the currnet iteration and exit gracefully. See Also: Halt
203
+ #
204
+ # Example:
205
+ # fm = FileMonitor.new {|watched_item| puts 'do something when file is changed'}
206
+ # fm.spawn # and now its doing its job...
207
+ # fm.stop
208
+ def stop()
209
+ # Send user defined signal USR1 to process. This is trapped in spauned processes and tells the process to Ctrl+C
210
+ # The user defined signial is sent as a safty percausion because the process id is not tracked through a pid file
211
+ # nor compared with the running command. The FileMonitor spaun will respond to the USR1 signal by exiting properly.*
212
+ if Fixnum === pid
213
+ Process.kill('USR1', pid)
214
+ Process.wait pid
215
+ end
216
+ end
217
+
218
+ # Halts a spawned FileMonitor Instance. The current iteration will be halted in its tracks. See Also: stop
219
+ #
220
+ # Example:
221
+ # fm = FileMonitor.new {|watched_item| puts 'do something when file is changed'}
222
+ # fm.spawn # and now its doing its job...
223
+ # fm.stop
224
+ def halt()
225
+ if Fixnum === pid
226
+ Process.kill('USR2', pid)
227
+ Process.wait pid
228
+ end
229
+ end
230
+
231
+ private
232
+ # Returns true or false
233
+ def process_running?
234
+ pid ? !`ps -p #{pid}|grep ^[0-9]`.split().empty? : false
235
+ end
236
+
237
+ # Call callback and update digest with given key.
238
+ def respond_to_change(item, key)
239
+ if Proc === item.callback # Use watched instance callback if possible.
240
+ call item.callback, item, self
241
+ elsif Proc === self.callback # Use object level callback if possible.
242
+ call self.callback, item, self
243
+ end
244
+ item.digest(key)
245
+ end
246
+
247
+ # Calls a proc with no more then the needed arguments. This avoids an error when
248
+ # calling a proc with two arguments when the proc only handels one argument.
249
+ def call(proc, *args)
250
+ if proc.arity > 0
251
+ proc.call(*args[0..(proc.arity-1)])
252
+ else
253
+ proc.call
254
+ end
255
+ end
256
+
257
+ # Returns new digest if changed or false if unchanged.
258
+ def changed?(item)
259
+ d = digest(item.path) # get current digets
260
+ d == item.digest ? false : d # return new digest or false if unchanged
261
+ end
262
+
263
+ # Returns a string representation of the file state.
264
+ def digest(file)
265
+ begin
266
+ File.mtime(file).to_f
267
+ rescue Errno::ENOENT
268
+ nil
269
+ end
270
+ end
271
+
272
+ # Returns an array of all files in dirname recursively. Accepts an optional file name regexp filter.
273
+ # The following will find files ending in ".rb" recursively beginning in the working dir:
274
+ # files_recursive Dir.pwd, /\.rb$/
275
+ def files_recursive(dirname, file_name_regexp=/.*/)
276
+ paths = []
277
+
278
+ Find.find(dirname) do |path|
279
+ if FileTest.directory?(path)
280
+ Find.prune if File.basename(path)[0] == ?. # Don't look any further into directies beginning with a dot.
281
+ else
282
+ paths << path if file_name_regexp === path # Amend the return array if the file found matches the regexp
283
+ end
284
+ end
285
+
286
+ return paths
287
+ end
288
+ end
@@ -0,0 +1,45 @@
1
+ module MonitoredItems #:nodoc:
2
+ class Nothing #:nodoc:
3
+ end
4
+
5
+
6
+ # A Store object is much like a hash but instead of getting an setting keys you get and set instance variables.
7
+ # The following examples are using methods used by the FileMonitor object, however the store object is not limited to these methods.
8
+ #
9
+ # Example:
10
+ #
11
+ # s = MonitoredItems::Store.new
12
+ # s.path = '/tmp' # => "/tmp"
13
+ # s.callback Proc.new {'Hello World'} # => #<Proc:0x0000000100317508@(irb):18>
14
+ # s.callback.call # => "Hello World"
15
+ # s.path # => "/tmp"
16
+ #
17
+ # # OR send a hash when initializing the store
18
+ # i = MonitoredItems::Store.new({:path => '/tmp'})
19
+ # i.path #=> "/tmp"
20
+ class Store
21
+ # Supports initialization with a hash of methods & values. It makes no difference if
22
+ # the keys of the hash are strings or symbols, but they are case sensitive.
23
+ def initialize(hsh = {})
24
+ hsh.map {|k,v| self.send(k, v)} if Hash === hsh
25
+ end
26
+
27
+ # Gets or sets instance variables based upon the methods called.
28
+ def method_missing(mth, arg=Nothing)
29
+ # append the @ symbol and remove the equal symbol (if exists):
30
+ mth = "@#{mth}".chomp('=').to_sym
31
+ # get or set instnace variable
32
+ arg == Nothing ? self.instance_variable_get(mth) : self.instance_variable_set(mth, arg)
33
+ end
34
+
35
+ # Return Hash representation of Store object
36
+ def to_h
37
+ Hash[*self.instance_variables.collect {|m| [m.slice(1..-1).to_sym, self.instance_variable_get(m)] }.flatten]
38
+ end
39
+
40
+ # Return inspection of hash representation of Store object
41
+ def to_s
42
+ self.to_h.inspect
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,180 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ # Time to add your specs!
4
+ # http://rspec.info/
5
+ describe "FileMonitor" do
6
+ before :all do
7
+ @app_root = File.expand_path(File.dirname(__FILE__)+"/..")
8
+ end
9
+
10
+ it 'should be able to instantize' do
11
+ fm = FileMonitor.new
12
+ FileMonitor.should === fm
13
+ end
14
+
15
+ it 'should support adding files' do
16
+ fm = FileMonitor.new
17
+ fm.watched.size.should == 0
18
+ (fm << @app_root + '/lib/FileMonitor.rb').should be_true # Should add single file using the '<<' method
19
+ fm.watched.size.should == 1
20
+ (fm.add @app_root + '/lib/FileMonitor/store.rb').should be_true # Should add single file using the 'add' method
21
+ fm.watched.size.should == 2
22
+ end
23
+
24
+ it 'should add files recursively when given a directory as a path' do
25
+ fm = FileMonitor.new
26
+ fm.watched.size.should == 0
27
+ fm.add(@app_root + '/lib').should be_true # Should add single file
28
+ fm.watched.size.should > 1
29
+ end
30
+
31
+ it 'should respond to index_of' do
32
+ fm = FileMonitor.new
33
+ fm.add(@app_root + '/lib').should be_true # Should add single file
34
+ fm.watched.size.should > 1
35
+ fm.index_of(@app_root + '/lib/FileMonitor.rb').should < fm.index_of(@app_root + '/lib/FileMonitor/store.rb')
36
+ fm.index_of(@app_root + '/lib/not_here.txt').should be_false
37
+ end
38
+
39
+ it 'should support a FileMonitor object level callback' do
40
+ fm = FileMonitor.new {true}
41
+ fm.callback.arity.should == -1
42
+
43
+ fm = FileMonitor.new {|watched_item| true}
44
+ fm.callback.arity.should == 1
45
+
46
+ fm = FileMonitor.new {|watched_item, file_monitor| true}
47
+ fm.callback.arity.should == 2
48
+ end
49
+
50
+ it 'should support adding files with individual callbacks' do
51
+ proc1 = Proc.new {return "hello from proc 1"}
52
+ proc2 = Proc.new {return "hello from proc 2"}
53
+ fm = FileMonitor.new &proc1
54
+ (fm << @app_root + '/lib/FileMonitor.rb').should be_true # Should add single file
55
+ (fm.add @app_root + '/lib/FileMonitor/store.rb', &proc2).should be_true # Should add single file
56
+ fm.watched.first.callback.should be_nil
57
+ fm.watched.last.callback.should == proc2
58
+ fm.watched.last.callback.should_not == proc1
59
+ end
60
+
61
+ it 'should overwrite existing file watches with successive additions' do
62
+ fm = FileMonitor.new
63
+ fm.watched.size.should == 0
64
+ (fm << @app_root + '/lib/FileMonitor.rb').should be_true # Should add single file
65
+ (fm << @app_root + '/lib/FileMonitor.rb').should be_true # Should add single file
66
+ fm.watched.size.should == 1
67
+ end
68
+
69
+ it 'should spawn processes' do
70
+ fm = FileMonitor.new
71
+ fm << @app_root + '/lib'
72
+ pid = fm.spawn
73
+ pid.should_not == Process.pid
74
+ `ps -p #{fm.spawn} -o 'pid ppid'|grep ^[0-9]`.split().should == [pid.to_s, Process.pid.to_s]
75
+ end
76
+
77
+ it 'should stop spawn' do
78
+ fm = FileMonitor.new
79
+ fm << @app_root + '/lib'
80
+ pid = fm.spawn
81
+ `ps -p #{fm.spawn} -o 'pid ppid'|grep ^[0-9]`.split().should == [pid.to_s, Process.pid.to_s]
82
+ t=Time.now
83
+ fm.stop
84
+ puts "Time to stop: #{Time.now-t}"
85
+ `ps -p #{fm.spawn} -o 'pid ppid'|grep ^[0-9]`.split().should be_empty
86
+ end
87
+
88
+ it 'should halt spawn' do
89
+ fm = FileMonitor.new
90
+ fm << @app_root + '/lib'
91
+ pid = fm.spawn
92
+ `ps -p #{fm.spawn} -o 'pid ppid'|grep ^[0-9]`.split().should == [pid.to_s, Process.pid.to_s]
93
+ t=Time.now
94
+ fm.halt
95
+ puts "Time to halt: #{Time.now-t}"
96
+ `ps -p #{fm.spawn} -o 'pid ppid'|grep ^[0-9]`.split().should be_empty
97
+ end
98
+
99
+ it 'should setup & spawn using the when_modified class method' do
100
+ fm = FileMonitor.when_modified(Dir.pwd, "/path/to/other/file.rb") {|watched_item| true}
101
+ fm.callback.arity.should == 1
102
+ fm.pid.should > 1
103
+ fm.pid.should == fm.spawn
104
+ end
105
+
106
+ it 'should run callback on change' do
107
+ changed_files = nil
108
+ filename = @app_root + '/spec/temp.txt'
109
+ File.open(filename, 'w') {|f| f.write('hello') }
110
+
111
+ fm = FileMonitor.new() {|watched_item| changed_files = watched_item.path}
112
+ fm << filename
113
+
114
+ original = fm.watched.first.digest
115
+ sleep 1
116
+ File.open(filename, 'w') {|f| f.write('hello world') }
117
+ fm.process
118
+ fm.watched.first.digest.should_not == original
119
+ File.delete filename
120
+ end
121
+
122
+ it 'should use item callback if possible, otherwise object callback' do
123
+ @global_callback = []
124
+ @files_callback = []
125
+ file1 = @app_root + '/spec/temp1.txt'
126
+ file2 = @app_root + '/spec/temp2.txt'
127
+ file3 = @app_root + '/spec/temp3.txt'
128
+
129
+ [file1,file2,file3].each {|file| File.open(file, 'w') {|f| f.write('hello') }}
130
+
131
+ fm = FileMonitor.new() {|watched_item| @global_callback << watched_item.path }
132
+ fm << file1
133
+ fm.add(file2) {|watched_item| @files_callback << watched_item.path }
134
+ fm << file3
135
+
136
+ sleep 1
137
+ [file1,file2,file3].each {|file| File.open(file, 'w') {|f| f.write('Hello World') }}
138
+ fm.process
139
+
140
+ @global_callback.should == [file1,file3]
141
+ @files_callback.should == [file2]
142
+ [file1,file2,file3].each {|file| File.delete(file)}
143
+ end
144
+
145
+ it 'should have access to local, FileMonitor & Store contexts' do
146
+ filename = @app_root + '/spec/temp.txt'
147
+ File.open(filename, 'w') {|f| f.write('hello') }
148
+
149
+ fm = FileMonitor.new() do |watched_item, file_monitor|
150
+ MonitoredItems::Store.should === watched_item
151
+ Spec::Example::ExampleGroup::Subclass_1.should === self
152
+ FileMonitor.should === file_monitor
153
+ end
154
+ fm << filename
155
+ sleep 1
156
+ File.open(filename, 'w') {|f| f.write('hello world') }
157
+ fm.process
158
+
159
+ File.delete filename
160
+ end
161
+
162
+ it 'should handel missing files without throwing errors' do
163
+ filename = @app_root + '/spec/temp.txt'
164
+ File.open(filename, 'w') {|f| f.write('hello') }
165
+ fm = FileMonitor.new() {true}
166
+ fm << filename
167
+ fm.process
168
+ File.delete(filename)
169
+ fm.process
170
+ fm.watched.first.digest.should be_nil
171
+ end
172
+
173
+ it 'should be able to use a filter when specifying files' do
174
+ fm = FileMonitor.new() {|watched_item| changed_files = watched_item.path}
175
+ fm.add(@app_root, /\.rb$/) {|f| puts "Change found in: #{f.path}"}
176
+ fm.watched.each do |i|
177
+ File.split(i.path).last.should =~ /\.rb$/
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,42 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ # Time to add your specs!
4
+ # http://rspec.info/
5
+ describe "FileMonitor::Store" do
6
+ before :all do
7
+ @item = MonitoredItems::Store.new
8
+ end
9
+
10
+ it "should store data and be accessable via mehtod() calls" do
11
+ pth = Dir.pwd
12
+ @item.file(pth).should == pth
13
+ @item.file.should == pth
14
+ end
15
+
16
+ it "should store data and be accessable via mehtod()= calls" do
17
+ pth = Dir.pwd
18
+ (@item.file = pth).should == pth
19
+ @item.file.should == pth
20
+ end
21
+
22
+ it 'should store keys as methods & values as data when initialized a hash' do
23
+ i = MonitoredItems::Store.new({'string' => true, :symbol => true, :path => Dir.pwd})
24
+ i.string.should be_true
25
+ i.symbol.should be_true
26
+ i.path.should == Dir.pwd
27
+ end
28
+
29
+ it 'should know how to to_h' do
30
+ @item = MonitoredItems::Store.new
31
+ @item.test true
32
+ @item.hello = 'world'
33
+ @item.to_h.should == {:test => true, :hello => 'world'}
34
+ end
35
+
36
+ it 'should know how to to_s' do
37
+ @item = MonitoredItems::Store.new
38
+ @item.test true
39
+ @item.hello = 'world'
40
+ @item.to_s.should == '{:test=>true, :hello=>"world"}'
41
+ end
42
+ end
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,10 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems' unless ENV['NO_RUBYGEMS']
5
+ gem 'rspec'
6
+ require 'spec'
7
+ end
8
+
9
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
10
+ require 'FileMonitor'
@@ -0,0 +1,9 @@
1
+ require 'rake/rdoctask'
2
+ require 'rdiscount'
3
+
4
+ Rake::RDocTask.new('rdoc') do |t|
5
+ t.rdoc_files.include('README.markdown', 'lib/**/*.rb')
6
+ t.main = 'README.markdown'
7
+ t.title = "FileMonitor API Documentation"
8
+ t.rdoc_dir = 'doc'
9
+ end
@@ -0,0 +1,21 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems' unless ENV['NO_RUBYGEMS']
5
+ require 'spec'
6
+ end
7
+ begin
8
+ require 'spec/rake/spectask'
9
+ rescue LoadError
10
+ puts <<-EOS
11
+ To use rspec for testing you must install rspec gem:
12
+ gem install rspec
13
+ EOS
14
+ exit(0)
15
+ end
16
+
17
+ desc "Run the specs under spec/models"
18
+ Spec::Rake::SpecTask.new do |t|
19
+ t.spec_opts = ['--options', "spec/spec.opts"]
20
+ t.spec_files = FileList['spec/**/*_spec.rb']
21
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: joshaven-filemonitor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Joshaven Potter
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-24 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.3.3
24
+ version:
25
+ description: ""
26
+ email:
27
+ - yourtech@gmail.com
28
+ executables: []
29
+
30
+ extensions: []
31
+
32
+ extra_rdoc_files:
33
+ - Manifest.txt
34
+ files:
35
+ - CHANGELOG
36
+ - Manifest.txt
37
+ - README.markdown
38
+ - lib/FileMonitor.rb
39
+ - lib/FileMonitor/store.rb
40
+ - spec/FileMonitor_spec.rb
41
+ - spec/MonitoredItem_spec.rb
42
+ - spec/spec.opts
43
+ - spec/spec_helper.rb
44
+ - tasks/rspec.rake
45
+ - tasks/rdoc.rake
46
+ has_rdoc: false
47
+ homepage: http://filemonitor.rubyforge.org/
48
+ licenses:
49
+ post_install_message:
50
+ rdoc_options:
51
+ - --main
52
+ - README.markdown
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project: filemonitor
70
+ rubygems_version: 1.3.5
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Calls a Proc when a watched file is changed.
74
+ test_files: []
75
+