joshaven-filemonitor 0.0.3

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,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
+