watchr 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright © 2009 Martin Aumont (mynyml)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,94 @@
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 can also take a look at watchr's own specs.watchr script in the root dir,
72
+ as well as docs.watchr
73
+
74
+
75
+ === Install
76
+
77
+ gem install mynyml-watchr --source http://gems.github.com/
78
+
79
+
80
+ === See Also
81
+
82
+ redgreen[http://github.com/mynyml/redgreen]:: Standalone redgreen eye candy for test results, ala autotest.
83
+ phocus[http://github.com/mynyml/phocus]:: Run focused tests when running the whole file/suite is unnecessary.
84
+
85
+
86
+ === Links
87
+
88
+ source:: http://github.com/mynyml/watchr
89
+ rdocs:: http://docs.github.com/mynyml/watchr
90
+ wiki:: http://wiki.github.com/mynyml/watchr
91
+
92
+ === Acknowledgement
93
+
94
+ * Thanks to macournoyer[http://github.com/macournoyer] for the evented backend idea
data/Rakefile ADDED
@@ -0,0 +1,85 @@
1
+ # --------------------------------------------------
2
+ # based on thin's Rakefile (http://github.com/macournoyer/thin)
3
+ # --------------------------------------------------
4
+ require 'rake/gempackagetask'
5
+ require 'rake/rdoctask'
6
+ require 'pathname'
7
+ require 'yaml'
8
+ require 'lib/watchr/version'
9
+ begin
10
+ require 'yard'
11
+ rescue LoadError, RuntimeError
12
+ end
13
+
14
+ RUBY_1_9 = RUBY_VERSION =~ /^1\.9/
15
+ WIN = (RUBY_PLATFORM =~ /mswin|cygwin/)
16
+ SUDO = (WIN ? "" : "sudo")
17
+
18
+ def gem
19
+ RUBY_1_9 ? 'gem19' : 'gem'
20
+ end
21
+
22
+ def all_except(res)
23
+ Dir['**/*'].reject do |path|
24
+ Array(res).any? {|re| path.match(re) }
25
+ end
26
+ end
27
+
28
+ spec = Gem::Specification.new do |s|
29
+ s.name = 'watchr'
30
+ s.version = Watchr.version
31
+ s.summary = "Continious anything"
32
+ s.description = "Continious anything; project files observer/trigger."
33
+ s.author = "Martin Aumont"
34
+ s.email = 'mynyml@gmail.com'
35
+ s.homepage = ''
36
+ s.has_rdoc = true
37
+ s.require_path = "lib"
38
+ s.bindir = "bin"
39
+ s.executables = "watchr"
40
+ s.files = all_except %w( ^doc/ ^doc$ ^pkg ^bk ^\.wiki ^\.yardoc )
41
+ #s.add_dependency 'every', '>= 1.0'
42
+ s.add_dependency 'rev', '>= 0.3.0'
43
+ end
44
+
45
+ desc "Generate rdoc documentation."
46
+ Rake::RDocTask.new(:rdoc => 'rdoc', :clobber_rdoc => 'rdoc:clean', :rerdoc => 'rdoc:force') { |rdoc|
47
+ rdoc.rdoc_dir = 'doc/rdoc'
48
+ rdoc.title = "Watchr"
49
+ rdoc.options << '--line-numbers' << '--inline-source'
50
+ rdoc.options << '--charset' << 'utf-8'
51
+ rdoc.main = 'README.rdoc'
52
+ rdoc.rdoc_files.include('README.rdoc')
53
+ rdoc.rdoc_files.include('TODO.txt')
54
+ rdoc.rdoc_files.include('LICENSE')
55
+ rdoc.rdoc_files.include('lib/**/*.rb')
56
+ }
57
+
58
+ if defined? YARD
59
+ YARD::Rake::YardocTask.new do |t|
60
+ t.files = %w( lib/**/*.rb )
61
+ t.options = %w( -o doc/yard --readme README.rdoc --files LICENSE,TODO.txt )
62
+ end
63
+ end
64
+
65
+ Rake::GemPackageTask.new(spec) do |p|
66
+ p.gem_spec = spec
67
+ end
68
+
69
+ desc "Remove package products"
70
+ task :clean => :clobber_package
71
+
72
+ desc "Update the gemspec for GitHub's gem server"
73
+ task :gemspec do
74
+ Pathname("#{spec.name}.gemspec").open('w') {|f| f << YAML.dump(spec) }
75
+ end
76
+
77
+ desc "Install gem"
78
+ task :install => [:clobber, :package] do
79
+ sh "#{SUDO} #{gem} install pkg/#{spec.full_name}.gem"
80
+ end
81
+
82
+ desc "Uninstall gem"
83
+ task :uninstall => :clean do
84
+ sh "#{SUDO} #{gem} uninstall -v #{spec.version} -x #{spec.name}"
85
+ end
data/TODO.txt ADDED
@@ -0,0 +1,31 @@
1
+
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 ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pathname'
4
+ require 'optparse'
5
+
6
+ require 'watchr'
7
+ require 'watchr/version'
8
+
9
+ def usage
10
+ "Usage: watchr [opts] path/to/script"
11
+ end
12
+ def version
13
+ "watchr version: %s" % Watchr.version
14
+ end
15
+
16
+ opts = OptionParser.new do |opts|
17
+ opts.banner = usage
18
+
19
+ opts.on('-d', '--debug', "Print extra debug info while program runs") {
20
+ Watchr.options.debug = true
21
+ begin
22
+ require 'ruby-debug'
23
+ rescue LoadError, RuntimeError
24
+ end
25
+ }
26
+
27
+ opts.on_tail('-h', '--help', "Print inline help") { puts opts; exit }
28
+ opts.on_tail('-v', '--version', "Print version" ) { puts version; exit }
29
+
30
+ opts.parse! ARGV
31
+ end
32
+
33
+ abort(usage) unless ARGV.first
34
+
35
+ file = Pathname(ARGV.first).expand_path
36
+ abort(%|no script found; file "#{file.to_s}" doesn't exist.|) unless file.exist?
37
+
38
+ Watchr::Controller.new(Watchr::Script.new(file), Watchr.handler.new).run
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 ADDED
@@ -0,0 +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
+ #
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
+
25
+ class << self
26
+ attr_accessor :options
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.
44
+ def options
45
+ @options ||= Struct.new(:debug).new
46
+ @options.debug ||= false
47
+ @options
48
+ end
49
+
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
61
+ end
62
+
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
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,79 @@
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
+ @handler.add_observer(self)
29
+
30
+ Watchr.debug "using %s handler" % handler.class.name
31
+ end
32
+
33
+ # Enters listening loop.
34
+ #
35
+ # Will block control flow until application is explicitly stopped/killed.
36
+ #
37
+ def run
38
+ @handler.listen(monitored_paths)
39
+ end
40
+
41
+ # Callback for file events.
42
+ #
43
+ # Called while control flow in in listening loop. It will execute the
44
+ # file's corresponding action as defined in the script. If the file is the
45
+ # script itself, it will refresh its state to account for potential changes.
46
+ #
47
+ # ===== Parameters
48
+ # path<Pathname, String>:: path that triggered event
49
+ # event<Symbol>:: event type (ignored for now)
50
+ #
51
+ def update(path, event = nil)
52
+ path = Pathname(path).expand_path
53
+
54
+ if path == @script.path
55
+ @script.parse!
56
+ @handler.refresh(monitored_paths)
57
+ else
58
+ @script.action_for(path).call
59
+ end
60
+ end
61
+
62
+ # List of paths the script is monitoring.
63
+ #
64
+ # Basically this means all paths below current directoly recursivelly that
65
+ # match any of the rules' patterns, plus the script file.
66
+ #
67
+ # ===== Returns
68
+ # paths<Array[Pathname]>:: List of monitored paths
69
+ #
70
+ def monitored_paths
71
+ paths = Dir['**/*'].select do |path|
72
+ @script.patterns.any? {|p| path.match(p) }
73
+ end
74
+ paths.push(@script.path).compact!
75
+ paths.map {|path| Pathname(path).expand_path }
76
+ end
77
+ end
78
+ end
79
+