nestor 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +34 -0
- data/Rakefile +76 -0
- data/VERSION +1 -0
- data/bin/nestor +3 -0
- data/doc/.gitignore +3 -0
- data/doc/state-diagram.graffle +3870 -0
- data/doc/state-diagram.png +0 -0
- data/lib/nestor/cli.rb +52 -0
- data/lib/nestor/machine.rb +161 -0
- data/lib/nestor/strategies/test/unit.rb +116 -0
- data/lib/nestor/strategies.rb +18 -0
- data/lib/nestor/watchers/rails.rb +56 -0
- data/lib/nestor/watchers/rails_script.rb +83 -0
- data/lib/nestor/watchers.rb +1 -0
- data/lib/nestor.rb +11 -0
- data/spec/machine_spec.rb +56 -0
- data/spec/spec_helper.rb +9 -0
- data/vendor/watchr-0.5.7/.gitignore +5 -0
- data/vendor/watchr-0.5.7/History.txt +32 -0
- data/vendor/watchr-0.5.7/LICENSE +19 -0
- data/vendor/watchr-0.5.7/Manifest +27 -0
- data/vendor/watchr-0.5.7/README.rdoc +108 -0
- data/vendor/watchr-0.5.7/Rakefile +49 -0
- data/vendor/watchr-0.5.7/TODO.txt +40 -0
- data/vendor/watchr-0.5.7/bin/watchr +77 -0
- data/vendor/watchr-0.5.7/docs.watchr +26 -0
- data/vendor/watchr-0.5.7/gem.watchr +32 -0
- data/vendor/watchr-0.5.7/lib/watchr/controller.rb +81 -0
- data/vendor/watchr-0.5.7/lib/watchr/event_handlers/base.rb +48 -0
- data/vendor/watchr-0.5.7/lib/watchr/event_handlers/portable.rb +55 -0
- data/vendor/watchr-0.5.7/lib/watchr/event_handlers/unix.rb +97 -0
- data/vendor/watchr-0.5.7/lib/watchr/script.rb +203 -0
- data/vendor/watchr-0.5.7/lib/watchr.rb +113 -0
- data/vendor/watchr-0.5.7/manifest.watchr +70 -0
- data/vendor/watchr-0.5.7/specs.watchr +38 -0
- data/vendor/watchr-0.5.7/test/README +11 -0
- data/vendor/watchr-0.5.7/test/event_handlers/test_base.rb +24 -0
- data/vendor/watchr-0.5.7/test/event_handlers/test_portable.rb +58 -0
- data/vendor/watchr-0.5.7/test/event_handlers/test_unix.rb +162 -0
- data/vendor/watchr-0.5.7/test/test_controller.rb +103 -0
- data/vendor/watchr-0.5.7/test/test_helper.rb +52 -0
- data/vendor/watchr-0.5.7/test/test_script.rb +123 -0
- data/vendor/watchr-0.5.7/test/test_watchr.rb +60 -0
- data/vendor/watchr-0.5.7/watchr.gemspec +60 -0
- metadata +152 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
=== Summary
|
2
|
+
|
3
|
+
Agile development tool that monitors a directory tree, and triggers a user
|
4
|
+
defined action whenever an observed file is modified. Its most typical use is
|
5
|
+
continuous testing, and as such it is a more flexible alternative to autotest.
|
6
|
+
|
7
|
+
|
8
|
+
=== Features
|
9
|
+
|
10
|
+
watchr is:
|
11
|
+
|
12
|
+
* Simple to use
|
13
|
+
* Highly flexible
|
14
|
+
* Evented ( Listens for filesystem events with native c libs )
|
15
|
+
* Portable ( Linux, *BSD, OSX, Solaris, Windows )
|
16
|
+
* Fast ( Immediately reacts to file changes )
|
17
|
+
|
18
|
+
Most importantly it allows running tests in an environment that is *agnostic* to:
|
19
|
+
|
20
|
+
* Web frameworks ( rails, merb, sinatra, camping, invisible, ... )
|
21
|
+
* Test frameworks ( test/unit, minitest, rspec, test/spec, expectations, ... )
|
22
|
+
* Ruby interpreters ( ruby1.8, ruby1.9, MRI, JRuby, Rubinius, ... )
|
23
|
+
* Package frameworks ( rubygems, rip, ... )
|
24
|
+
|
25
|
+
|
26
|
+
=== Usage
|
27
|
+
|
28
|
+
On the command line,
|
29
|
+
|
30
|
+
$ watchr path/to/script.file
|
31
|
+
|
32
|
+
will monitor files in the current directory tree, and react to events on those
|
33
|
+
files in accordance with the script.
|
34
|
+
|
35
|
+
|
36
|
+
=== Scripts
|
37
|
+
|
38
|
+
The script contains a set of simple rules that map observed files to an action.
|
39
|
+
Its DSL is a single method: watch(pattern, &action)
|
40
|
+
|
41
|
+
watch( 'a regexp pattern matching paths to observe' ) {|match_data_object| command_to_run }
|
42
|
+
|
43
|
+
So for example,
|
44
|
+
|
45
|
+
watch( 'test/test_.*\.rb' ) {|md| system("ruby #{md[0]}") }
|
46
|
+
|
47
|
+
will match any test file and run it whenever it is saved.
|
48
|
+
|
49
|
+
A continuous testing script for a basic project could be
|
50
|
+
|
51
|
+
watch( 'test/test_.*\.rb' ) {|md| system("ruby #{md[0]}") }
|
52
|
+
watch( 'lib/(.*)\.rb' ) {|md| system("ruby test/test_#{md[1]}.rb") }
|
53
|
+
|
54
|
+
which, in addition to running any saved test file as above, will also run a
|
55
|
+
lib file's associated test. This mimics the equivalent autotest behaviour.
|
56
|
+
|
57
|
+
It's easy to see why watchr is so flexible, since the whole command is custom.
|
58
|
+
The above actions could just as easily call "jruby", "ruby --rubygems", "ruby
|
59
|
+
-Ilib", "specrb", "rbx", ... or any combination of these. For the sake of
|
60
|
+
comparison, autotest runs with:
|
61
|
+
|
62
|
+
/usr/bin/ruby1.8 -I.:lib:test -rubygems -e "%w[test/unit test/test_helper.rb test/test_watchr.rb].each { |f| require f }"
|
63
|
+
|
64
|
+
locking the environment into ruby1.8, rubygems and test/unit for all tests.
|
65
|
+
|
66
|
+
And remember the scripts are pure ruby, so feel free to add methods,
|
67
|
+
Signal#trap calls, etc. Updates to script files are picked up on the fly (no
|
68
|
+
need to restart watchr) so experimenting is painless.
|
69
|
+
|
70
|
+
The wiki[http://wiki.github.com/mynyml/watchr] has more details and examples.
|
71
|
+
You might also want to take a look at watchr's own scripts,
|
72
|
+
specs.watchr[http://github.com/mynyml/watchr/blob/master/specs.watchr],
|
73
|
+
docs.watchr[http://github.com/mynyml/watchr/blob/master/docs.watchr] and
|
74
|
+
gem.watchr[http://github.com/mynyml/watchr/blob/master/gem.watchr], to get you
|
75
|
+
started.
|
76
|
+
|
77
|
+
|
78
|
+
=== Install
|
79
|
+
|
80
|
+
gem install watchr --source http://gemcutter.org
|
81
|
+
|
82
|
+
If you're on *nix and have the rev[http://github.com/tarcieri/rev/] gem
|
83
|
+
installed, Watchr will detect it and use it automatically. This will make
|
84
|
+
Watchr evented.
|
85
|
+
|
86
|
+
gem install rev
|
87
|
+
|
88
|
+
|
89
|
+
=== See Also
|
90
|
+
|
91
|
+
redgreen[http://github.com/mynyml/redgreen]:: Standalone redgreen eye candy for test results, ala autotest.
|
92
|
+
phocus[http://github.com/mynyml/phocus]:: Run focused tests when running the whole file/suite is unnecessary.
|
93
|
+
|
94
|
+
|
95
|
+
=== Links
|
96
|
+
|
97
|
+
source:: http://github.com/mynyml/watchr
|
98
|
+
docs:: http://docs.github.com/mynyml/watchr
|
99
|
+
wiki:: http://wiki.github.com/mynyml/watchr
|
100
|
+
bugs:: http://github.com/mynyml/watchr/issues
|
101
|
+
|
102
|
+
=== Contributions
|
103
|
+
|
104
|
+
macournoyer[http://github.com/macournoyer]:: suggested evented backend
|
105
|
+
foca[http://github.com/foca]:: suggested automatically picking up watchr scripts bundled in gems
|
106
|
+
TwP[http://github.com/TwP]:: patch, Rev gem optional in development
|
107
|
+
gzuki[http://github.com/gzuki]:: patch, recognize some event types
|
108
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'rake/rdoctask'
|
2
|
+
begin
|
3
|
+
require 'yard'
|
4
|
+
rescue LoadError, RuntimeError
|
5
|
+
end
|
6
|
+
|
7
|
+
desc "Generate rdoc documentation."
|
8
|
+
Rake::RDocTask.new(:rdoc => 'rdoc', :clobber_rdoc => 'rdoc:clean', :rerdoc => 'rdoc:force') { |rdoc|
|
9
|
+
rdoc.rdoc_dir = 'doc/rdoc'
|
10
|
+
rdoc.title = "Watchr"
|
11
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
12
|
+
rdoc.options << '--charset' << 'utf-8'
|
13
|
+
rdoc.main = 'README.rdoc'
|
14
|
+
rdoc.rdoc_files.include('README.rdoc')
|
15
|
+
rdoc.rdoc_files.include('TODO.txt')
|
16
|
+
rdoc.rdoc_files.include('LICENSE')
|
17
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
18
|
+
}
|
19
|
+
|
20
|
+
if defined? YARD
|
21
|
+
YARD::Rake::YardocTask.new do |t|
|
22
|
+
t.files = %w( lib/**/*.rb )
|
23
|
+
t.options = %w( -o doc/yard --readme README.rdoc --files LICENSE,TODO.txt )
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
namespace(:test) do
|
28
|
+
|
29
|
+
desc "Run all tests"
|
30
|
+
task(:all) do
|
31
|
+
tests = Dir['test/**/test_*.rb'] - ['test/test_helper.rb']
|
32
|
+
cmd = "ruby -rubygems -Ilib -e'%w( #{tests.join(' ')} ).each {|file| require file }'"
|
33
|
+
puts cmd if ENV['VERBOSE']
|
34
|
+
system cmd
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "Run all tests on multiple ruby versions (requires rvm with 1.8.6 and 1.8.7)"
|
38
|
+
task(:portability) do
|
39
|
+
versions = %w( 1.8.6 1.8.7 )
|
40
|
+
versions.each do |version|
|
41
|
+
system <<-BASH
|
42
|
+
bash -c 'source ~/.rvm/scripts/rvm;
|
43
|
+
rvm use #{version};
|
44
|
+
echo "--------- `ruby -v` ----------\n";
|
45
|
+
rake -s test:all'
|
46
|
+
BASH
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
* 1.9 compatibility
|
3
|
+
|
4
|
+
* sometimes an action is fired without a file being saved
|
5
|
+
* buffer flushing issue?
|
6
|
+
* libev issue?
|
7
|
+
* probably fixed with event type handling update, which ignores atime
|
8
|
+
updates by defaults
|
9
|
+
|
10
|
+
* when a file is saved twice quickly, subsequent events are ignored.
|
11
|
+
* seems like rev/libev drops the file watch
|
12
|
+
|
13
|
+
* test on other platforms
|
14
|
+
x mswin
|
15
|
+
x cygwin
|
16
|
+
* bsd
|
17
|
+
* osx
|
18
|
+
* solaris
|
19
|
+
|
20
|
+
* write a few prepackaged scripts
|
21
|
+
* post on gists
|
22
|
+
* post links on wiki
|
23
|
+
* post main links in readme
|
24
|
+
|
25
|
+
* eval script within own context?
|
26
|
+
* use case: using <tt>path</tt> within script accesses Script#path
|
27
|
+
|
28
|
+
* respond to different file events?
|
29
|
+
* modified
|
30
|
+
* created
|
31
|
+
* deleted
|
32
|
+
* etc.
|
33
|
+
* watch(pattern, EVENT, &action)
|
34
|
+
* use case: a script updates a manifest file when a file is deleted
|
35
|
+
|
36
|
+
* memory profiling / benchmarks
|
37
|
+
|
38
|
+
* version.watchr
|
39
|
+
* sync versions (gemspec & Watchr::VERSION)
|
40
|
+
|
@@ -0,0 +1,77 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
require 'watchr'
|
7
|
+
|
8
|
+
module Watchr
|
9
|
+
# Namespaced to avoid defining global methods
|
10
|
+
module Bin #:nodoc:
|
11
|
+
extend self
|
12
|
+
|
13
|
+
def usage
|
14
|
+
"Usage: watchr [opts] path/to/script"
|
15
|
+
end
|
16
|
+
|
17
|
+
def version
|
18
|
+
"watchr version: %s" % Watchr::VERSION
|
19
|
+
end
|
20
|
+
|
21
|
+
# Find a partial path name in load path
|
22
|
+
#
|
23
|
+
# ===== Params
|
24
|
+
# path<Pathname>:: partial pathname
|
25
|
+
#
|
26
|
+
# ===== Returns
|
27
|
+
# <Pathname>::
|
28
|
+
# absolute path of first occurence of partial path in load path, or nil if not found
|
29
|
+
#
|
30
|
+
def find_in_load_path(path)
|
31
|
+
dir = potentially_with_gem( path.basename('.watchr') ) do
|
32
|
+
$LOAD_PATH.detect {|p| Pathname(p).join(path).exist? }
|
33
|
+
end
|
34
|
+
dir ? path.expand_path(dir) : nil
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# If the block returns nil, requires gem <tt>name</tt> and tries running the
|
40
|
+
# block again. If all fails, returns nil
|
41
|
+
#
|
42
|
+
# ===== Params
|
43
|
+
# name<Pathname,String>:: name of gem to require
|
44
|
+
#
|
45
|
+
# ===== Returns
|
46
|
+
# block's value or nil if gem <tt>name</tt> doesn't exist
|
47
|
+
#
|
48
|
+
def potentially_with_gem(name)
|
49
|
+
yield || (require(name) && yield)
|
50
|
+
rescue LoadError
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
opts = OptionParser.new do |opts|
|
57
|
+
opts.banner = Watchr::Bin.usage
|
58
|
+
|
59
|
+
opts.on('-d', '--debug', "Print extra debug info while program runs") {
|
60
|
+
Watchr.options.debug = true
|
61
|
+
begin
|
62
|
+
require 'ruby-debug'
|
63
|
+
rescue LoadError, RuntimeError
|
64
|
+
end
|
65
|
+
}
|
66
|
+
|
67
|
+
opts.on_tail('-h', '--help', "Print inline help") { puts opts; exit }
|
68
|
+
opts.on_tail('-v', '--version', "Print version" ) { puts Watchr::Bin.version; exit }
|
69
|
+
|
70
|
+
opts.parse! ARGV
|
71
|
+
end
|
72
|
+
|
73
|
+
relative_path = Pathname( ARGV.first ) rescue abort(Watchr::Bin.usage)
|
74
|
+
absolute_path = Watchr::Bin.find_in_load_path(relative_path) or abort("no script found; file #{relative_path.to_s.inspect} is not in path.")
|
75
|
+
|
76
|
+
Watchr::Controller.new(Watchr::Script.new(absolute_path), Watchr.handler.new).run
|
77
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Run me with:
|
2
|
+
#
|
3
|
+
# $ watchr docs.watchr
|
4
|
+
|
5
|
+
def run_rdoc
|
6
|
+
system('rake --silent rdoc')
|
7
|
+
end
|
8
|
+
|
9
|
+
def run_yard
|
10
|
+
print "\nUpdating yardocs... "
|
11
|
+
system('rake --silent yardoc')
|
12
|
+
print "done.\n"
|
13
|
+
end
|
14
|
+
|
15
|
+
def document
|
16
|
+
run_rdoc
|
17
|
+
run_yard
|
18
|
+
end
|
19
|
+
|
20
|
+
watch( 'lib/.*\.rb' ) { document }
|
21
|
+
watch( 'README.rdoc' ) { document }
|
22
|
+
watch( 'TODO.txt' ) { document }
|
23
|
+
watch( 'LICENSE' ) { document }
|
24
|
+
|
25
|
+
|
26
|
+
# vim:ft=ruby
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Run me with:
|
2
|
+
#
|
3
|
+
# $ watchr gem.watchr
|
4
|
+
|
5
|
+
# --------------------------------------------------
|
6
|
+
# Convenience Methods
|
7
|
+
# --------------------------------------------------
|
8
|
+
def build(gemspec)
|
9
|
+
system "gem build %s" % gemspec
|
10
|
+
FileUtils.mv Dir['watchr-*.gem'], 'pkg/'
|
11
|
+
puts
|
12
|
+
end
|
13
|
+
|
14
|
+
# --------------------------------------------------
|
15
|
+
# Watchr Rules
|
16
|
+
# --------------------------------------------------
|
17
|
+
watch( '^watchr.gemspec$' ) { |m| build m[0] }
|
18
|
+
|
19
|
+
# --------------------------------------------------
|
20
|
+
# Signal Handling
|
21
|
+
# --------------------------------------------------
|
22
|
+
# Ctrl-\
|
23
|
+
Signal.trap('QUIT') do
|
24
|
+
puts " --- Building Gem ---\n\n"
|
25
|
+
build 'watchr.gemspec'
|
26
|
+
end
|
27
|
+
|
28
|
+
# Ctrl-C
|
29
|
+
Signal.trap('INT') { abort("\n") }
|
30
|
+
|
31
|
+
|
32
|
+
# vim:ft=ruby
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Watchr
|
2
|
+
|
3
|
+
# The controller contains the app's core logic.
|
4
|
+
#
|
5
|
+
# ===== Examples
|
6
|
+
#
|
7
|
+
# script = Watchr::Script.new(file)
|
8
|
+
# contrl = Watchr::Controller.new(script)
|
9
|
+
# contrl.run
|
10
|
+
#
|
11
|
+
# Calling <tt>#run</tt> will enter the listening loop, and from then on every
|
12
|
+
# file event will trigger its corresponding action defined in <tt>script</tt>
|
13
|
+
#
|
14
|
+
# The controller also automatically adds the script's file itself to its list
|
15
|
+
# of monitored files and will detect any changes to it, providing on the fly
|
16
|
+
# updates of defined rules.
|
17
|
+
#
|
18
|
+
class Controller
|
19
|
+
|
20
|
+
# Creates a controller object around given <tt>script</tt>
|
21
|
+
#
|
22
|
+
# ===== Parameters
|
23
|
+
# script<Script>:: The script object
|
24
|
+
#
|
25
|
+
def initialize(script, handler)
|
26
|
+
@script = script
|
27
|
+
@handler = handler
|
28
|
+
|
29
|
+
@handler.add_observer(self)
|
30
|
+
|
31
|
+
Watchr.debug "using %s handler" % handler.class.name
|
32
|
+
end
|
33
|
+
|
34
|
+
# Enters listening loop.
|
35
|
+
#
|
36
|
+
# Will block control flow until application is explicitly stopped/killed.
|
37
|
+
#
|
38
|
+
def run
|
39
|
+
@script.parse!
|
40
|
+
@handler.listen(monitored_paths)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Callback for file events.
|
44
|
+
#
|
45
|
+
# Called while control flow in in listening loop. It will execute the
|
46
|
+
# file's corresponding action as defined in the script. If the file is the
|
47
|
+
# script itself, it will refresh its state to account for potential changes.
|
48
|
+
#
|
49
|
+
# ===== Parameters
|
50
|
+
# path<Pathname, String>:: path that triggered event
|
51
|
+
# event<Symbol>:: event type (ignored for now)
|
52
|
+
#
|
53
|
+
def update(path, event_type = nil)
|
54
|
+
path = Pathname(path).expand_path
|
55
|
+
|
56
|
+
if path == @script.path
|
57
|
+
@script.parse!
|
58
|
+
@handler.refresh(monitored_paths)
|
59
|
+
else
|
60
|
+
@script.action_for(path, event_type).call
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# List of paths the script is monitoring.
|
65
|
+
#
|
66
|
+
# Basically this means all paths below current directoly recursivelly that
|
67
|
+
# match any of the rules' patterns, plus the script file.
|
68
|
+
#
|
69
|
+
# ===== Returns
|
70
|
+
# paths<Array[Pathname]>:: List of monitored paths
|
71
|
+
#
|
72
|
+
def monitored_paths
|
73
|
+
paths = Dir['**/*'].select do |path|
|
74
|
+
@script.patterns.any? {|p| path.match(p) }
|
75
|
+
end
|
76
|
+
paths.push(@script.path).compact!
|
77
|
+
paths.map {|path| Pathname(path).expand_path }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'observer'
|
2
|
+
|
3
|
+
module Watchr
|
4
|
+
module EventHandler
|
5
|
+
class AbstractMethod < Exception #:nodoc:
|
6
|
+
end
|
7
|
+
|
8
|
+
# Base functionality mixin meant to be included in specific event handlers.
|
9
|
+
module Base
|
10
|
+
include Observable
|
11
|
+
|
12
|
+
# Notify that a file was modified.
|
13
|
+
#
|
14
|
+
# ===== Parameters
|
15
|
+
# path<Pathname, String>:: full path or path relative to current working directory
|
16
|
+
# event_type<Symbol>:: event type.
|
17
|
+
#--
|
18
|
+
# #changed and #notify_observers are Observable methods
|
19
|
+
def notify(path, event_type = nil)
|
20
|
+
changed(true)
|
21
|
+
notify_observers(path, event_type)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Begin watching given paths and enter listening loop. Called by the controller.
|
25
|
+
#
|
26
|
+
# Abstract method
|
27
|
+
#
|
28
|
+
# ===== Parameters
|
29
|
+
# monitored_paths<Array(Pathname)>:: list of paths the application is currently monitoring.
|
30
|
+
#
|
31
|
+
def listen(monitored_paths)
|
32
|
+
raise AbstractMethod
|
33
|
+
end
|
34
|
+
|
35
|
+
# Called by the controller when the list of paths monitored by wantchr
|
36
|
+
# has changed. It should refresh the list of paths being watched.
|
37
|
+
#
|
38
|
+
# Abstract method
|
39
|
+
#
|
40
|
+
# ===== Parameters
|
41
|
+
# monitored_paths<Array(Pathname)>:: list of paths the application is currently monitoring.
|
42
|
+
#
|
43
|
+
def refresh(monitored_paths)
|
44
|
+
raise AbstractMethod
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Watchr
|
2
|
+
module EventHandler
|
3
|
+
class Portable
|
4
|
+
include Base
|
5
|
+
|
6
|
+
attr_accessor :monitored_paths
|
7
|
+
attr_accessor :reference_mtime
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@reference_mtime = Time.now
|
11
|
+
end
|
12
|
+
|
13
|
+
# Enters listening loop.
|
14
|
+
#
|
15
|
+
# Will block control flow until application is explicitly stopped/killed.
|
16
|
+
#
|
17
|
+
def listen(monitored_paths)
|
18
|
+
@monitored_paths = monitored_paths
|
19
|
+
loop { trigger; sleep(1) }
|
20
|
+
end
|
21
|
+
|
22
|
+
# See if an event occured, and if so notify observers.
|
23
|
+
def trigger #:nodoc:
|
24
|
+
path, type = detect_event
|
25
|
+
notify(path, type) unless path.nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
# Update list of monitored paths.
|
29
|
+
def refresh(monitored_paths)
|
30
|
+
@monitored_paths = monitored_paths
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Verify mtimes of monitored files.
|
36
|
+
#
|
37
|
+
# If the latest mtime is more recent than the reference mtime, return
|
38
|
+
# that file's path.
|
39
|
+
#
|
40
|
+
# ===== Returns
|
41
|
+
# path and type of event if event occured, nil otherwise
|
42
|
+
#
|
43
|
+
def detect_event
|
44
|
+
path = @monitored_paths.max {|a,b| a.mtime <=> b.mtime }
|
45
|
+
|
46
|
+
if path.mtime > @reference_mtime
|
47
|
+
@reference_mtime = path.mtime
|
48
|
+
[path, :modified]
|
49
|
+
else
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Watchr
|
2
|
+
module EventHandler
|
3
|
+
class Unix
|
4
|
+
include Base
|
5
|
+
|
6
|
+
# Used by Rev. Wraps a monitored path, and Rev::Loop will call its
|
7
|
+
# callback on file events.
|
8
|
+
class SingleFileWatcher < Rev::StatWatcher #:nodoc:
|
9
|
+
class << self
|
10
|
+
# Stores a reference back to handler so we can call its #nofity
|
11
|
+
# method with file event info
|
12
|
+
attr_accessor :handler
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(path)
|
16
|
+
super
|
17
|
+
update_reference_times
|
18
|
+
end
|
19
|
+
|
20
|
+
# File's path as a Pathname
|
21
|
+
def pathname
|
22
|
+
@pathname ||= Pathname(@path)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Callback. Called on file change event
|
26
|
+
# Delegates to Controller#update, passing in path and event type
|
27
|
+
def on_change
|
28
|
+
self.class.handler.notify(path, type)
|
29
|
+
update_reference_times unless type == :deleted
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def update_reference_times
|
35
|
+
@reference_atime = pathname.atime
|
36
|
+
@reference_mtime = pathname.mtime
|
37
|
+
@reference_ctime = pathname.ctime
|
38
|
+
end
|
39
|
+
|
40
|
+
# Type of latest event.
|
41
|
+
#
|
42
|
+
# A single type is determined, even though more than one stat times may
|
43
|
+
# have changed on the file. The type is the first to match in the
|
44
|
+
# following hierarchy:
|
45
|
+
#
|
46
|
+
# :deleted, :modified (mtime), :accessed (atime), :changed (ctime)
|
47
|
+
#
|
48
|
+
# ===== Returns
|
49
|
+
# type<Symbol>:: latest event's type
|
50
|
+
#
|
51
|
+
def type
|
52
|
+
return :deleted if !pathname.exist?
|
53
|
+
return :modified if pathname.mtime > @reference_mtime
|
54
|
+
return :accessed if pathname.atime > @reference_atime
|
55
|
+
return :changed if pathname.ctime > @reference_ctime
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize
|
60
|
+
SingleFileWatcher.handler = self
|
61
|
+
@loop = Rev::Loop.default
|
62
|
+
end
|
63
|
+
|
64
|
+
# Enters listening loop.
|
65
|
+
#
|
66
|
+
# Will block control flow until application is explicitly stopped/killed.
|
67
|
+
#
|
68
|
+
def listen(monitored_paths)
|
69
|
+
@monitored_paths = monitored_paths
|
70
|
+
attach
|
71
|
+
@loop.run
|
72
|
+
end
|
73
|
+
|
74
|
+
# Rebuilds file bindings.
|
75
|
+
#
|
76
|
+
# will detach all current bindings, and reattach the <tt>monitored_paths</tt>
|
77
|
+
#
|
78
|
+
def refresh(monitored_paths)
|
79
|
+
@monitored_paths = monitored_paths
|
80
|
+
detach
|
81
|
+
attach
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
# Binds all <tt>monitored_paths</tt> to the listening loop.
|
87
|
+
def attach
|
88
|
+
@monitored_paths.each {|path| SingleFileWatcher.new(path.to_s).attach(@loop) }
|
89
|
+
end
|
90
|
+
|
91
|
+
# Unbinds all paths currently attached to listening loop.
|
92
|
+
def detach
|
93
|
+
@loop.watchers.each {|watcher| watcher.detach }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|