mynyml-watchr 0.3.0 → 0.5.2
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.
- 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
|