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 CHANGED
@@ -1,32 +1,39 @@
1
- ==== Summary
1
+ === Summary
2
2
 
3
- Agile development tool that monitors a directory recursively, and triggers a
4
- user defined action whenever an observed file is modified. Its most typical use
5
- is continious testing, and as such it is a more flexible alternative to
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
- ==== Features
8
+ === Features
10
9
 
11
- * Ridiculously simple to use
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
- ==== Usage
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 all files from within the current directory and below it
26
- recursively, and react to events on those files in accordance with the script.
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
- ==== Scripts
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 files and run them whenever they are modified.
47
+ will match any test file and run it whenever it is saved.
41
48
 
42
- A continious testing script for a basic project could be
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
- ==== Install
75
+ === Install
58
76
 
59
- gem install mynyml-watchr --source http://gems.github.com/
77
+ gem install watchr --source http://gemcutter.org
60
78
 
61
79
 
62
- ==== See Also
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
- ==== Links
86
+ === Links
69
87
 
70
88
  source:: http://github.com/mynyml/watchr
71
- rdocs:: http://docs.github.com/mynyml/watchr
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
- require 'pathname'
7
- require 'yaml'
8
- require 'lib/watchr/version'
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
- Rake::GemPackageTask.new(spec) do |p|
51
- p.gem_spec = spec
52
- end
53
-
54
- desc "Remove package products"
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
- o write a few prepackaged scripts
3
- o post on gists
4
- o post links on wiki
5
- o post main links in readme
6
-
7
- o split watchr.rb classes into own files?
8
- o run script within own context?
9
-
10
- o use filesystem events for file monitoring (MA)
11
- o linux: inotify
12
- o osx: fsevent
13
- o win: Directory Management? NTFS Change Journal? ReadDirectoryChangesW?
14
- o *BSD: kqueue? pnotify?
15
- o solaris: FEM?
16
- o fallback to current method when no other method is available
17
- o api will also need refactoring for clearer separation of functionality
18
-
19
- o document source
20
- o rdoc or yard?
21
-
22
- o respond to different file events
23
- o modified
24
- o created
25
- o deleted
26
- o etc.
27
- o watch(pattern, EVENT, &action)
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
- root = Pathname(__FILE__).dirname.parent
6
- require root.join('lib/watchr')
7
- require root.join('lib/watchr/version')
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::Runner.new(file).run
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
- def map
104
- @map || self.map!
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
- protected
108
-
109
- def call_action!
110
- puts "[debug] monitoring paths: #{self.paths.inspect}" if Watchr.options.debug
111
- raise "no reference file" if self.reference_file.nil?
112
-
113
- ref = self.reference_file.to_s
114
- pattern, action = self.map[ref]
115
- md = ref.match(pattern)
116
- action.call(md)
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
- @map
131
- end
132
-
133
- def local_files
134
- Dir['**/*']
135
- end
74
+ end
136
75
  end
137
76
  end