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 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