mynyml-watchr 0.3.0 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +46 -24
- data/Rakefile +10 -55
- data/TODO.txt +30 -26
- data/bin/watchr +9 -6
- data/docs.watchr +26 -0
- data/lib/watchr.rb +62 -123
- data/lib/watchr/controller.rb +79 -0
- data/lib/watchr/event_handlers/base.rb +48 -0
- data/lib/watchr/event_handlers/portable.rb +55 -0
- data/lib/watchr/event_handlers/unix.rb +62 -0
- data/lib/watchr/script.rb +192 -0
- data/lib/watchr/version.rb +4 -4
- data/specs.watchr +21 -12
- data/test/event_handlers/test_base.rb +24 -0
- data/test/event_handlers/test_portable.rb +58 -0
- data/test/event_handlers/test_unix.rb +56 -0
- data/test/test_controller.rb +104 -0
- data/test/test_helper.rb +20 -37
- data/test/test_script.rb +88 -0
- data/test/test_watchr.rb +32 -155
- data/watchr.gemspec +61 -70
- metadata +99 -29
- data/rdoc.watchr +0 -15
- data/yard.watchr +0 -18
data/README.rdoc
CHANGED
@@ -1,32 +1,39 @@
|
|
1
|
-
|
1
|
+
=== Summary
|
2
2
|
|
3
|
-
Agile development tool that monitors a directory
|
4
|
-
|
5
|
-
|
6
|
-
autotest.
|
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.
|
7
6
|
|
8
7
|
|
9
|
-
|
8
|
+
=== Features
|
10
9
|
|
11
|
-
|
12
|
-
* web framework agnostic <i>(rails, merb, sinatra, camping, invisible, ...)</i>
|
13
|
-
* test framework agnostic <i>(test/unit, minitest, rspec, test/spec, expectations, ...)</i>
|
14
|
-
* ruby interpreter agnostic <i>(ruby1.8, ruby1.9, MRI, JRuby, ...)</i>
|
15
|
-
* package framework agnostic <i>(rubygems, rip, ...)</i>
|
16
|
-
* Low level / highly flexible
|
10
|
+
watchr is:
|
17
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 )
|
18
17
|
|
19
|
-
|
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
|
20
27
|
|
21
28
|
On the command line,
|
22
29
|
|
23
30
|
$ watchr path/to/script.file
|
24
31
|
|
25
|
-
will monitor
|
26
|
-
|
32
|
+
will monitor files in the current directory tree, and react to events on those
|
33
|
+
files in accordance with the script.
|
27
34
|
|
28
35
|
|
29
|
-
|
36
|
+
=== Scripts
|
30
37
|
|
31
38
|
The script contains a set of simple rules that map observed files to an action.
|
32
39
|
Its DSL is a single method: watch(pattern, &action)
|
@@ -37,9 +44,9 @@ So for example,
|
|
37
44
|
|
38
45
|
watch( 'test/test_.*\.rb' ) {|md| system("ruby #{md[0]}") }
|
39
46
|
|
40
|
-
will match test
|
47
|
+
will match any test file and run it whenever it is saved.
|
41
48
|
|
42
|
-
A
|
49
|
+
A continuous testing script for a basic project could be
|
43
50
|
|
44
51
|
watch( 'test/test_.*\.rb' ) {|md| system("ruby #{md[0]}") }
|
45
52
|
watch( 'lib/(.*)\.rb' ) {|md| system("ruby test/test_#{md[1]}.rb") }
|
@@ -48,26 +55,41 @@ which, in addition to running any saved test file as above, will also run a
|
|
48
55
|
lib file's associated test. This mimics the equivalent autotest behaviour.
|
49
56
|
|
50
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
|
+
|
51
66
|
And remember the scripts are pure ruby, so feel free to add methods,
|
52
|
-
Signal#trap calls, etc.
|
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.
|
53
69
|
|
54
70
|
The wiki[http://wiki.github.com/mynyml/watchr] has more details and examples.
|
71
|
+
You can also take a look at watchr's own specs.watchr script in the root dir,
|
72
|
+
as well as docs.watchr
|
55
73
|
|
56
74
|
|
57
|
-
|
75
|
+
=== Install
|
58
76
|
|
59
|
-
gem install
|
77
|
+
gem install watchr --source http://gemcutter.org
|
60
78
|
|
61
79
|
|
62
|
-
|
80
|
+
=== See Also
|
63
81
|
|
64
82
|
redgreen[http://github.com/mynyml/redgreen]:: Standalone redgreen eye candy for test results, ala autotest.
|
65
83
|
phocus[http://github.com/mynyml/phocus]:: Run focused tests when running the whole file/suite is unnecessary.
|
66
84
|
|
67
85
|
|
68
|
-
|
86
|
+
=== Links
|
69
87
|
|
70
88
|
source:: http://github.com/mynyml/watchr
|
71
|
-
|
89
|
+
docs:: http://docs.github.com/mynyml/watchr
|
72
90
|
wiki:: http://wiki.github.com/mynyml/watchr
|
91
|
+
bugs:: http://github.com/mynyml/watchr/issues
|
92
|
+
|
93
|
+
=== Acknowledgement
|
73
94
|
|
95
|
+
* Thanks to macournoyer[http://github.com/macournoyer] for the evented backend idea
|
data/Rakefile
CHANGED
@@ -1,39 +1,7 @@
|
|
1
|
-
# --------------------------------------------------
|
2
|
-
# based on thin's Rakefile (http://github.com/macournoyer/thin)
|
3
|
-
# --------------------------------------------------
|
4
|
-
require 'rake/gempackagetask'
|
5
1
|
require 'rake/rdoctask'
|
6
|
-
|
7
|
-
require '
|
8
|
-
|
9
|
-
|
10
|
-
RUBY_1_9 = RUBY_VERSION =~ /^1\.9/
|
11
|
-
WIN = (RUBY_PLATFORM =~ /mswin|cygwin/)
|
12
|
-
SUDO = (WIN ? "" : "sudo")
|
13
|
-
|
14
|
-
def gem
|
15
|
-
RUBY_1_9 ? 'gem19' : 'gem'
|
16
|
-
end
|
17
|
-
|
18
|
-
def all_except(res)
|
19
|
-
Dir['**/*'].reject do |path|
|
20
|
-
Array(res).any? {|re| path.match(re) }
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
spec = Gem::Specification.new do |s|
|
25
|
-
s.name = 'watchr'
|
26
|
-
s.version = Watchr.version
|
27
|
-
s.summary = "Continious anything"
|
28
|
-
s.description = "Continious anything; project files observer/trigger."
|
29
|
-
s.author = "Martin Aumont"
|
30
|
-
s.email = 'mynyml@gmail.com'
|
31
|
-
s.homepage = ''
|
32
|
-
s.has_rdoc = true
|
33
|
-
s.require_path = "lib"
|
34
|
-
s.bindir = "bin"
|
35
|
-
s.executables = "watchr"
|
36
|
-
s.files = all_except %w( ^doc ^pkg ^test/fixtures )
|
2
|
+
begin
|
3
|
+
require 'yard'
|
4
|
+
rescue LoadError, RuntimeError
|
37
5
|
end
|
38
6
|
|
39
7
|
desc "Generate rdoc documentation."
|
@@ -44,27 +12,14 @@ Rake::RDocTask.new(:rdoc => 'rdoc', :clobber_rdoc => 'rdoc:clean', :rerdoc => 'r
|
|
44
12
|
rdoc.options << '--charset' << 'utf-8'
|
45
13
|
rdoc.main = 'README.rdoc'
|
46
14
|
rdoc.rdoc_files.include('README.rdoc')
|
15
|
+
rdoc.rdoc_files.include('TODO.txt')
|
16
|
+
rdoc.rdoc_files.include('LICENSE')
|
47
17
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
48
18
|
}
|
49
19
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
task :clean => :clobber_package
|
56
|
-
|
57
|
-
desc "Update the gemspec for GitHub's gem server"
|
58
|
-
task :gemspec do
|
59
|
-
Pathname("#{spec.name}.gemspec").open('w') {|f| f << YAML.dump(spec) }
|
60
|
-
end
|
61
|
-
|
62
|
-
desc "Install gem"
|
63
|
-
task :install => [:clobber, :package] do
|
64
|
-
sh "#{SUDO} #{gem} install pkg/#{spec.full_name}.gem"
|
65
|
-
end
|
66
|
-
|
67
|
-
desc "Uninstall gem"
|
68
|
-
task :uninstall => :clean do
|
69
|
-
sh "#{SUDO} #{gem} uninstall -v #{spec.version} -x #{spec.name}"
|
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
|
70
25
|
end
|
data/TODO.txt
CHANGED
@@ -1,27 +1,31 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
2
|
+
* rev dependency should be conditional on OS
|
3
|
+
|
4
|
+
* fix issue with Script#parse!
|
5
|
+
* only accept paths in initialize?
|
6
|
+
|
7
|
+
* sometimes an action is fired without a file being saved
|
8
|
+
* buffer flushing issue?
|
9
|
+
* libev issue?
|
10
|
+
|
11
|
+
* test on other platforms
|
12
|
+
* mswin
|
13
|
+
* bsd
|
14
|
+
* osx
|
15
|
+
* solaris
|
16
|
+
|
17
|
+
* write a few prepackaged scripts
|
18
|
+
* post on gists
|
19
|
+
* post links on wiki
|
20
|
+
* post main links in readme
|
21
|
+
|
22
|
+
* eval script within own context?
|
23
|
+
|
24
|
+
* respond to different file events?
|
25
|
+
* modified
|
26
|
+
* created
|
27
|
+
* deleted
|
28
|
+
* etc.
|
29
|
+
* watch(pattern, EVENT, &action)
|
30
|
+
|
31
|
+
* memory profiling / benchmarks
|
data/bin/watchr
CHANGED
@@ -2,14 +2,13 @@
|
|
2
2
|
|
3
3
|
require 'pathname'
|
4
4
|
require 'optparse'
|
5
|
-
|
6
|
-
require
|
7
|
-
require
|
5
|
+
|
6
|
+
require 'watchr'
|
7
|
+
require 'watchr/version'
|
8
8
|
|
9
9
|
def usage
|
10
10
|
"Usage: watchr [opts] path/to/script"
|
11
11
|
end
|
12
|
-
|
13
12
|
def version
|
14
13
|
"watchr version: %s" % Watchr.version
|
15
14
|
end
|
@@ -19,6 +18,10 @@ opts = OptionParser.new do |opts|
|
|
19
18
|
|
20
19
|
opts.on('-d', '--debug', "Print extra debug info while program runs") {
|
21
20
|
Watchr.options.debug = true
|
21
|
+
begin
|
22
|
+
require 'ruby-debug'
|
23
|
+
rescue LoadError, RuntimeError
|
24
|
+
end
|
22
25
|
}
|
23
26
|
|
24
27
|
opts.on_tail('-h', '--help', "Print inline help") { puts opts; exit }
|
@@ -29,8 +32,8 @@ end
|
|
29
32
|
|
30
33
|
abort(usage) unless ARGV.first
|
31
34
|
|
32
|
-
file = Pathname(ARGV.first)
|
35
|
+
file = Pathname(ARGV.first).expand_path
|
33
36
|
abort(%|no script found; file "#{file.to_s}" doesn't exist.|) unless file.exist?
|
34
37
|
|
35
|
-
Watchr::
|
38
|
+
Watchr::Controller.new(Watchr::Script.new(file), Watchr.handler.new).run
|
36
39
|
|
data/docs.watchr
ADDED
@@ -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
|
data/lib/watchr.rb
CHANGED
@@ -1,137 +1,76 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'rbconfig'
|
3
|
+
|
4
|
+
# Agile development tool that monitors a directory recursively, and triggers a
|
5
|
+
# user defined action whenever an observed file is modified. Its most typical
|
6
|
+
# use is continuous testing.
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
#
|
10
|
+
# # on command line, from project's root dir
|
11
|
+
# $ watchr path/to/script
|
12
|
+
#
|
13
|
+
# See README for more details
|
14
|
+
#
|
1
15
|
module Watchr
|
16
|
+
autoload :Script, 'watchr/script'
|
17
|
+
autoload :Controller, 'watchr/controller'
|
18
|
+
|
19
|
+
module EventHandler
|
20
|
+
autoload :Base, 'watchr/event_handlers/base'
|
21
|
+
autoload :Unix, 'watchr/event_handlers/unix'
|
22
|
+
autoload :Portable, 'watchr/event_handlers/portable'
|
23
|
+
end
|
24
|
+
|
2
25
|
class << self
|
3
26
|
attr_accessor :options
|
4
|
-
|
27
|
+
attr_accessor :handler
|
28
|
+
|
29
|
+
# Options proxy.
|
30
|
+
#
|
31
|
+
# Currently supported options:
|
32
|
+
# * debug<Boolean> Debugging state. More verbose.
|
33
|
+
#
|
34
|
+
# ===== Examples
|
35
|
+
#
|
36
|
+
# Watchr.options.debug #=> false
|
37
|
+
# Watchr.options.debug = true
|
38
|
+
#
|
39
|
+
# ===== Returns
|
40
|
+
# options<Struct>:: options proxy.
|
41
|
+
#
|
42
|
+
#--
|
43
|
+
# On first use, initialize the options struct and default option values.
|
5
44
|
def options
|
6
45
|
@options ||= Struct.new(:debug).new
|
7
|
-
# set default options
|
8
46
|
@options.debug ||= false
|
9
47
|
@options
|
10
48
|
end
|
11
|
-
end
|
12
|
-
|
13
|
-
class Script
|
14
|
-
attr_accessor :map
|
15
|
-
attr_accessor :file
|
16
|
-
attr_accessor :reference_time
|
17
|
-
|
18
|
-
def initialize(file = nil)
|
19
|
-
self.map = []
|
20
|
-
self.file = file.is_a?(Pathname) ? file : Pathname.new(file) unless file.nil?
|
21
|
-
self.parse!
|
22
|
-
end
|
23
|
-
|
24
|
-
def watch(pattern, &action)
|
25
|
-
a = block_given? ? action : @default_action
|
26
|
-
self.map << [pattern, a]
|
27
|
-
end
|
28
|
-
|
29
|
-
def default_action(&action)
|
30
|
-
@default_action = action
|
31
|
-
end
|
32
|
-
|
33
|
-
def changed?
|
34
|
-
return false unless self.bound?
|
35
|
-
self.file.mtime > self.reference_time
|
36
|
-
end
|
37
|
-
|
38
|
-
def parse!
|
39
|
-
puts "[debug] loading script file #{self.file.to_s.inspect}" if Watchr.options.debug
|
40
|
-
|
41
|
-
return false unless self.bound?
|
42
|
-
self.map.clear
|
43
|
-
self.instance_eval(self.file.read)
|
44
|
-
self.reference_time = self.file.mtime
|
45
|
-
end
|
46
|
-
|
47
|
-
def bound?
|
48
|
-
self.file && self.file.respond_to?(:exist?) && self.file.exist?
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
class Runner
|
53
|
-
attr_accessor :script
|
54
|
-
attr_accessor :map
|
55
|
-
attr_accessor :init_time
|
56
|
-
attr_accessor :reference_file
|
57
|
-
|
58
|
-
# Caches reference_file.mtime to allow picking up an update to the
|
59
|
-
# reference file itself
|
60
|
-
attr_accessor :reference_time
|
61
|
-
|
62
|
-
def initialize(script)
|
63
|
-
self.init_time = Time.now.to_f
|
64
|
-
self.script = script.is_a?(Script) ? script : Script.new(script)
|
65
|
-
end
|
66
|
-
|
67
|
-
def paths
|
68
|
-
self.map.keys
|
69
|
-
end
|
70
|
-
|
71
|
-
def last_updated_file
|
72
|
-
path = self.paths.max {|a,b| File.mtime(a) <=> File.mtime(b) }
|
73
|
-
Pathname(path)
|
74
|
-
end
|
75
|
-
|
76
|
-
# TODO extract updating the reference out of this method
|
77
|
-
def changed?
|
78
|
-
return true if self.paths.empty?
|
79
|
-
return false if self.last_updated_file.mtime.to_f < self.init_time.to_f
|
80
|
-
|
81
|
-
if self.reference_file.nil? || (self.reference_time.to_f < self.last_updated_file.mtime.to_f)
|
82
|
-
self.reference_file = self.last_updated_file
|
83
|
-
self.reference_time = self.last_updated_file.mtime
|
84
|
-
true
|
85
|
-
else
|
86
|
-
false
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
def run
|
91
|
-
# enter monitoring state
|
92
|
-
loop do
|
93
|
-
self.trigger
|
94
|
-
Kernel.sleep(1)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def trigger
|
99
|
-
self.script.parse! && self.map! if self.script.changed?
|
100
|
-
self.call_action! if self.changed?
|
101
|
-
end
|
102
49
|
|
103
|
-
|
104
|
-
|
50
|
+
# Outputs formatted debug statement to stdout, only if ::options.debug is true
|
51
|
+
#
|
52
|
+
# ===== Examples
|
53
|
+
#
|
54
|
+
# Watchr.options.debug = true
|
55
|
+
# Watchr.debug('im in ur codes, notifayinin u')
|
56
|
+
#
|
57
|
+
# outputs: "[watchr debug] im in ur codes, notifayinin u"
|
58
|
+
#
|
59
|
+
def debug(str)
|
60
|
+
puts "[watchr debug] #{str}" if options.debug
|
105
61
|
end
|
106
62
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
end
|
118
|
-
|
119
|
-
def map!
|
120
|
-
@map = {}
|
121
|
-
patterns = self.script.map.map {|mapping| mapping[0] }
|
122
|
-
patterns.each do |pattern|
|
123
|
-
local_files.each do |path|
|
124
|
-
if path.match(pattern)
|
125
|
-
action = self.script.map.assoc(pattern)[1]
|
126
|
-
@map[path] = [pattern, action]
|
127
|
-
end
|
128
|
-
end
|
63
|
+
def handler
|
64
|
+
@handler ||=
|
65
|
+
#case ENV['HANDLER'] || RUBY_PLATFORM
|
66
|
+
case ENV['HANDLER'] || Config::CONFIG['host_os']
|
67
|
+
when /mswin|windows|cygwin/i
|
68
|
+
Watchr::EventHandler::Portable
|
69
|
+
when /bsd|sunos|solaris|darwin|osx|mach|linux/i, 'unix'
|
70
|
+
Watchr::EventHandler::Unix
|
71
|
+
else
|
72
|
+
Watchr::EventHandler::Portable
|
129
73
|
end
|
130
|
-
|
131
|
-
end
|
132
|
-
|
133
|
-
def local_files
|
134
|
-
Dir['**/*']
|
135
|
-
end
|
74
|
+
end
|
136
75
|
end
|
137
76
|
end
|