tekkub-watchr 0.5.0

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/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 it 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
+