nestor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/.document +5 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +34 -0
  5. data/Rakefile +76 -0
  6. data/VERSION +1 -0
  7. data/bin/nestor +3 -0
  8. data/doc/.gitignore +3 -0
  9. data/doc/state-diagram.graffle +3870 -0
  10. data/doc/state-diagram.png +0 -0
  11. data/lib/nestor/cli.rb +52 -0
  12. data/lib/nestor/machine.rb +161 -0
  13. data/lib/nestor/strategies/test/unit.rb +116 -0
  14. data/lib/nestor/strategies.rb +18 -0
  15. data/lib/nestor/watchers/rails.rb +56 -0
  16. data/lib/nestor/watchers/rails_script.rb +83 -0
  17. data/lib/nestor/watchers.rb +1 -0
  18. data/lib/nestor.rb +11 -0
  19. data/spec/machine_spec.rb +56 -0
  20. data/spec/spec_helper.rb +9 -0
  21. data/vendor/watchr-0.5.7/.gitignore +5 -0
  22. data/vendor/watchr-0.5.7/History.txt +32 -0
  23. data/vendor/watchr-0.5.7/LICENSE +19 -0
  24. data/vendor/watchr-0.5.7/Manifest +27 -0
  25. data/vendor/watchr-0.5.7/README.rdoc +108 -0
  26. data/vendor/watchr-0.5.7/Rakefile +49 -0
  27. data/vendor/watchr-0.5.7/TODO.txt +40 -0
  28. data/vendor/watchr-0.5.7/bin/watchr +77 -0
  29. data/vendor/watchr-0.5.7/docs.watchr +26 -0
  30. data/vendor/watchr-0.5.7/gem.watchr +32 -0
  31. data/vendor/watchr-0.5.7/lib/watchr/controller.rb +81 -0
  32. data/vendor/watchr-0.5.7/lib/watchr/event_handlers/base.rb +48 -0
  33. data/vendor/watchr-0.5.7/lib/watchr/event_handlers/portable.rb +55 -0
  34. data/vendor/watchr-0.5.7/lib/watchr/event_handlers/unix.rb +97 -0
  35. data/vendor/watchr-0.5.7/lib/watchr/script.rb +203 -0
  36. data/vendor/watchr-0.5.7/lib/watchr.rb +113 -0
  37. data/vendor/watchr-0.5.7/manifest.watchr +70 -0
  38. data/vendor/watchr-0.5.7/specs.watchr +38 -0
  39. data/vendor/watchr-0.5.7/test/README +11 -0
  40. data/vendor/watchr-0.5.7/test/event_handlers/test_base.rb +24 -0
  41. data/vendor/watchr-0.5.7/test/event_handlers/test_portable.rb +58 -0
  42. data/vendor/watchr-0.5.7/test/event_handlers/test_unix.rb +162 -0
  43. data/vendor/watchr-0.5.7/test/test_controller.rb +103 -0
  44. data/vendor/watchr-0.5.7/test/test_helper.rb +52 -0
  45. data/vendor/watchr-0.5.7/test/test_script.rb +123 -0
  46. data/vendor/watchr-0.5.7/test/test_watchr.rb +60 -0
  47. data/vendor/watchr-0.5.7/watchr.gemspec +60 -0
  48. 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