filemonitor 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,4 +1,16 @@
1
+ === 0.0.2 2009-09-15
2
+
3
+ * Major Enhancements:
4
+ * Various API Changes!
5
+ * RSpec Testing! Oh yah!
6
+ * RDocs! Oh yah!
7
+ * Reduced I/O load: Updated method of saving old file state. Changes are not
8
+ detectable if changed within 1 second of prior digest. If this is a problem
9
+ for anyone, I can make a md5 digest of the file optional.
10
+ * Handle duplicates file entries by overwriting the old with the new
11
+ * Add start / stop & halt methods for handling spawns
12
+
1
13
  === 0.0.1 2009-09-15
2
14
 
3
15
  * 1 major enhancement:
4
- * Initial release
16
+ * Initial release
data/Manifest.txt CHANGED
@@ -3,10 +3,12 @@ Manifest.txt
3
3
  README.markdown
4
4
  Rakefile
5
5
  lib/FileMonitor.rb
6
+ lib/FileMonitor/store.rb
6
7
  script/console
7
8
  script/destroy
8
9
  script/generate
9
10
  spec/FileMonitor_spec.rb
11
+ spec/MonitoredItem_spec.rb
10
12
  spec/spec.opts
11
13
  spec/spec_helper.rb
12
14
  tasks/rspec.rake
data/README.markdown CHANGED
@@ -5,7 +5,11 @@
5
5
 
6
6
  ## DESCRIPTION
7
7
 
8
- Watches the file system for changes.
8
+ Watches the file system for changes.
9
+
10
+ ## Notice
11
+ This project has only been tested on posix compatible systems, sorry for
12
+ you windows users. If you get this to run under windows, let me know and I'll release a windows compatible version.
9
13
 
10
14
  ## Usage
11
15
  require 'filemonitor'
@@ -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
data/lib/FileMonitor.rb CHANGED
@@ -1,111 +1,288 @@
1
+ require File.dirname(__FILE__) + '/FileMonitor/store'
2
+ require 'find' # needed for the :files_recursive method
3
+
1
4
  # Purpose:
2
5
  # Watches the file system for changes
3
6
  #
4
7
  # Usage:
5
- # > require 'file_monitor'
6
- # > file_spy = FileMonitor.new
7
- # > file_spy.add(Dir.pwd) do |i|
8
- # > puts "you probably should handle the change in #{i.file}"
9
- # > end
10
- # > file_spy.spawn
11
-
12
- require 'digest/md5'
13
-
14
- $:.unshift(File.dirname(__FILE__)) unless
15
- $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
16
-
17
- module HashAccess
18
- def method_missing(mth, *args)
19
- return args.empty? ? self[mth] : self[mth.to_s.chomp('=').to_sym]=args.first
20
- end
21
- end
22
-
8
+ #
9
+ # require 'filemonitor'
10
+ #
11
+ # # Create a FileMonitor instance and assign the callback to the entire object.
12
+ # # In the following example, "watched_item" is a MonitoredItems::Store, see the new
13
+ # # method for a better example of working in your block.
14
+ # file_spy = FileMonitor.new do |watched_item| ... end
15
+ #
16
+ # # Any files in the working directory (Dir.pwd) and its sub-directories will
17
+ # # be watched for changes. If a change is found the callback assigned above
18
+ # # will be enacted.
19
+ # file_spy << Dir.pwd
20
+ # file_spy << "/path/to/other/file.rb"
21
+ #
22
+ # # launch an independent process to do the monitoring:
23
+ # file_spy.spawn
24
+ #
25
+ # # Alternatively you can do all of the above in one line:
26
+ # FileMonitor.when_modified(Dir.pwd, "/path/to/other/file.rb") do |watched_item| ... end
23
27
  class FileMonitor
24
- VERSION = '0.0.1'
25
- attr_accessor :spawns
26
- def initialize()
28
+ VERSION = '0.0.2'
29
+ attr_accessor :callback, :pid, :watched
30
+ # The new method may be called with an optional callback which must be a block
31
+ # either do...end or {...}.
32
+ # The block may consiste of upto arguments ie {|watched_item, monitored|}, in which case, the
33
+ # watched_item is an instance of FileMonitor::Store and monitored is the FileMonitor instances self.
34
+ #
35
+ # The first argument of the block, 'watched_item' in this case, will respond to: (:path, :modified & :callback).
36
+ # The second argument of the block, 'monitored' in this case, will respond any FileMonitor method.
37
+ #
38
+ # Example:
39
+ # FileMonitor.new do |watched_item|
40
+ # puts "I am watched this file: #{watched_item.path}"
41
+ # puts "When I find a change I will call watched_item.callback"
42
+ # end
43
+ #
44
+ # FileMonitor.new do |watched_item, monitored|
45
+ #
46
+ # # add files from a file that is a list of files to watch... Note: There
47
+ # IO.readlines(watched_item.path).each {|file| monitored << file } if watched_item.path == '/path/to/file/watchme.list'
48
+ #
49
+ # # clear watchme.list so we won't add all watch files every time the file changes
50
+ # open(watched_item) { |f| puts '' }
51
+ # end
52
+ def initialize(&callback)
27
53
  @watched = []
54
+ @callback = callback unless callback.nil?
28
55
  end
29
-
30
- def add(item, &callback)
31
- if File.file? item
32
- h = {:file=>File.expand_path(item), :callback=>callback}
33
- h.extend(HashAccess)
34
- @watched << h
35
- elsif File.directory? item
36
- files_recursive(item).each {|f| add(f, &callback) }
56
+
57
+ # Returns a spawned FileMonitor instance. The independent process automatically calls the given
58
+ # callback when changes are found.
59
+ #
60
+ # Example:
61
+ # fm = FileMonitor.when_modified(Dir.pwd, "/path/to/other/file.rb") {|watched_item, file_monitor| ... }
62
+ # fm.pid # => 23994
63
+ # fm.callback.nil? # => false
64
+ # fm.watched.size # => 28
65
+ def self.when_modified(*paths, &callback)
66
+ fm = FileMonitor.new &callback
67
+ paths.each {|path| fm << path}
68
+ fm.spawn
69
+ return fm
70
+ end
71
+
72
+ # The add method accepts a directory path or file path and optional callback. If a directory path is given all files in that
73
+ # path are recursively added. If a callback is given then that proc will be called when a change is detected on that file or
74
+ # group of files. If no proc is given via the add method then the object callback is called.
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 its own callback on changed files in the /home folder:
85
+ # fm.add '/home' do |path|
86
+ # puts "A users file has changed: #{path}"
87
+ # end
88
+ def add(path, &callback)
89
+ if File.file? path
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, &callback) }
95
+ return true
37
96
  end
97
+ false
38
98
  end
39
-
40
- def changes?
41
- return first_changed
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
42
111
  end
43
112
 
44
- def process
45
- # itterates watched files and runs callbacks when changes are detected.
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(options={})
46
121
  @watched.each do |i|
47
- key = digest(i.file)
48
- i.digest = key if i.digest.nil? # skip first change detection, its always unknown on first run
49
- update(i, key) unless i.digest == key
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, options)
127
+ end
50
128
  end
51
129
  end
52
130
 
53
- def watching
54
- # Returns an Array of files being watched
55
- @watched
56
- end
57
-
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."
58
140
  def monitor(interval = 1)
59
141
  trap("INT") do
60
- STDERR.puts " Interrupted by Control-C"
61
- exit 2
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
62
151
  end
63
152
 
153
+ trap("USR2") do
154
+ puts " FileMonitor was halted."
155
+ pid = nil
156
+ exit
157
+ end
158
+
159
+
64
160
  while true
161
+ exit if @shutdown
65
162
  process
66
- sleep interval
163
+ sleep interval unless @shutdown
67
164
  end
68
165
  end
69
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...
70
188
  def spawn(interval = 1)
71
- if @spawns.nil?
72
- @spawns = fork {monitor interval}
73
- Process.detach(@spawns)
189
+ if pid.nil?
190
+ @pid = fork {monitor interval}
191
+ Process.detach(pid)
192
+
74
193
  Kernel.at_exit do
75
- Process.kill('HUP', @spawns)
76
- @spawns = nil
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
77
197
  end
78
- true
79
- else
80
- @spawns
81
198
  end
82
- @spawns ||= []
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
83
229
  end
230
+
84
231
  private
85
- def update(item, key)
86
- item.callback.call(item) unless item.callback.nil?
87
- item.digest= key
232
+ # Returns true or false
233
+ def process_running?
234
+ pid ? !`ps -p #{pid}|grep ^[0-9]`.split().empty? : false
88
235
  end
89
236
 
90
- def first_changed
91
- # returns first changed item or false
92
- @watched.each {|i| return i if changed? i}
93
- false
237
+ # Call callback and update digest with given key.
238
+ def respond_to_change(item, key, options)
239
+ if Proc === item.callback # Use watched instance callback if possible.
240
+ call item.callback, item, self, options
241
+ elsif Proc === self.callback # Use object level callback if possible.
242
+ call self.callback, item, self, options
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
94
255
  end
95
256
 
257
+ # Returns new digest if changed or false if unchanged.
96
258
  def changed?(item)
97
- # returns md5 if changed, returns false when not changed
98
- md5 = digest(item.file)
99
- md5 == item.digest ? false : md5
259
+ d = digest(item.path) # get current digets
260
+ d == item.digest ? false : d # return new digest or false if unchanged
100
261
  end
101
262
 
263
+ # Returns a string representation of the file state.
102
264
  def digest(file)
103
- # returns the md5 of a file
104
- Digest::MD5.hexdigest( File.read(file) )
265
+ begin
266
+ File.mtime(file).to_f
267
+ rescue Errno::ENOENT
268
+ nil
269
+ end
105
270
  end
106
271
 
107
- def files_recursive(dirname)
108
- # return an array of files from this (dirname) point forth.
109
- Dir["#{dirname}/**/**"].collect {|f| f if File.file? f }.compact
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
110
287
  end
111
288
  end
@@ -2,10 +2,171 @@ require File.dirname(__FILE__) + '/spec_helper.rb'
2
2
 
3
3
  # Time to add your specs!
4
4
  # http://rspec.info/
5
- describe "Place your specs here" do
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
6
121
 
7
- it "find this spec in spec directory" do
8
- # violated "Be sure to write your specs"
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)}
9
143
  end
10
144
 
11
- end
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
+ 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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: filemonitor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshaven Potter
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-15 00:00:00 -04:00
12
+ date: 2009-09-24 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -38,10 +38,12 @@ files:
38
38
  - README.markdown
39
39
  - Rakefile
40
40
  - lib/FileMonitor.rb
41
+ - lib/FileMonitor/store.rb
41
42
  - script/console
42
43
  - script/destroy
43
44
  - script/generate
44
45
  - spec/FileMonitor_spec.rb
46
+ - spec/MonitoredItem_spec.rb
45
47
  - spec/spec.opts
46
48
  - spec/spec_helper.rb
47
49
  - tasks/rspec.rake