gazr 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +65 -0
- data/Rakefile +1 -0
- data/bin/gazr +101 -0
- data/gazr.gemspec +25 -0
- data/lib/gazr.rb +68 -0
- data/lib/gazr/controller.rb +37 -0
- data/lib/gazr/event_handlers/base.rb +24 -0
- data/lib/gazr/event_handlers/darwin.rb +85 -0
- data/lib/gazr/event_handlers/portable.rb +45 -0
- data/lib/gazr/event_handlers/unix.rb +125 -0
- data/lib/gazr/script.rb +94 -0
- data/lib/gazr/version.rb +3 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6050b846af04b87fe491c3c00947a67de2a91dad
|
4
|
+
data.tar.gz: b004f3d6544d14d2b44b90961aa16c7434572380
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f685ba0475b63b4c1ce50f01630af484c6ead5eb8af130c1b5d5de9536b66eed9be3e4fcbf827373e31e677218296e8c888b49cd37d277102290aecd390e9e15
|
7
|
+
data.tar.gz: 73b06692d25b9d453a7b5e1f7141e8c9876f17f2006f5888debe372b7079cd09a63a9374c97a9a6c548cbf5c13e98bd90aa020deb8fffefe0863ab173af9cdcf
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 James Akers
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# Gazr
|
2
|
+
|
3
|
+
## Summary
|
4
|
+
Gazr is a development tool that's purpose is to continue on where Martin Aumont's watchr left off at Ruby 1.9.2. It currently is only slightly modified to work with `>=` Ruby 1.9.3.
|
5
|
+
|
6
|
+
* For use with `<=` Ruby 1.9.2 just use [watchr][1]: `gem install watchr`
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'gazr'
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install gazr
|
21
|
+
|
22
|
+
If you're on Linux/BSD and have the [rev][4] gem installed, Gazr will detect
|
23
|
+
it and use it automatically. This will make Gazr evented.
|
24
|
+
|
25
|
+
gem install rev
|
26
|
+
|
27
|
+
You can get the same evented behaviour on OS X by installing
|
28
|
+
[ruby-fsevent][10].
|
29
|
+
|
30
|
+
gem install ruby-fsevent
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
|
34
|
+
On the command line,
|
35
|
+
|
36
|
+
$ gazr path/to/script.file
|
37
|
+
|
38
|
+
will monitor files in the current directory tree, and react to events on those
|
39
|
+
files in accordance with the script.
|
40
|
+
|
41
|
+
## Scripts
|
42
|
+
|
43
|
+
The script contains a set of simple rules that map observed files to an action.
|
44
|
+
Its DSL is a single method: `watch(pattern, &action)`
|
45
|
+
|
46
|
+
watch( 'a regexp pattern matching paths to observe' ) {|match_data_object| command_to_run }
|
47
|
+
|
48
|
+
So for example,
|
49
|
+
|
50
|
+
watch( 'test/test_.*\.rb' ) {|md| system("ruby #{md[0]}") }
|
51
|
+
|
52
|
+
will match any test file and run it whenever it is saved.
|
53
|
+
|
54
|
+
## Contributing
|
55
|
+
|
56
|
+
1. Fork it
|
57
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
58
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
59
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
60
|
+
5. Create new Pull Request
|
61
|
+
|
62
|
+
[1]:https://github.com/mynyml/watchr
|
63
|
+
[4]: http://github.com/tarcieri/rev/
|
64
|
+
[10]: http://github.com/sandro/ruby-fsevent
|
65
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/gazr
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'optparse'
|
5
|
+
require 'tempfile'
|
6
|
+
|
7
|
+
require File.dirname(__FILE__) + '/../lib/gazr'
|
8
|
+
|
9
|
+
module Gazr
|
10
|
+
module Bin
|
11
|
+
extend self
|
12
|
+
|
13
|
+
DEFAULT_SCRIPT_PATH = Pathname.new('specs.gazr')
|
14
|
+
|
15
|
+
attr_accessor :path
|
16
|
+
|
17
|
+
def usage
|
18
|
+
"Usage: gazr [opts] path/to/script"
|
19
|
+
end
|
20
|
+
|
21
|
+
def version
|
22
|
+
"gazr version: %s" % Gazr::VERSION
|
23
|
+
end
|
24
|
+
|
25
|
+
# Absolute path to script file
|
26
|
+
#
|
27
|
+
# Unless set manually, the script's path is either first arg or
|
28
|
+
# `DEFAULT_SCRIPT_PATH`. If neither exists, the script immediatly aborts
|
29
|
+
# with an appropriate error message.
|
30
|
+
#
|
31
|
+
# @return [Pathname]
|
32
|
+
# absolute path to script
|
33
|
+
#
|
34
|
+
def path!
|
35
|
+
return @path unless @path.nil?
|
36
|
+
rel = relative_path or abort( usage )
|
37
|
+
find_in_load_path(rel) or abort("no script found: file #{rel.to_s.inspect} is not in path.")
|
38
|
+
end
|
39
|
+
|
40
|
+
# Find a partial path name in load path
|
41
|
+
#
|
42
|
+
# @param [Pathname] path
|
43
|
+
# partial pathname
|
44
|
+
#
|
45
|
+
# @return [Pathname]
|
46
|
+
# absolute path of first occurence of partial path in load path, or nil if not found
|
47
|
+
#
|
48
|
+
def find_in_load_path(path)
|
49
|
+
# Adds '.' for ruby1.9.2
|
50
|
+
dir = (['.'] + $LOAD_PATH).uniq.detect {|p| Pathname(p).join(path).exist? }
|
51
|
+
dir ? path.expand_path(dir) : nil
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def relative_path
|
57
|
+
return Pathname.new(ARGV.first) if ARGV.first
|
58
|
+
return DEFAULT_SCRIPT_PATH if DEFAULT_SCRIPT_PATH.exist?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
opts = OptionParser.new do |opts|
|
64
|
+
opts.banner = Gazr::Bin.usage
|
65
|
+
|
66
|
+
opts.on('-d', '--debug', "Print extra debug info while program runs") {
|
67
|
+
Gazr.options.debug = true
|
68
|
+
begin
|
69
|
+
require 'ruby-debug'
|
70
|
+
rescue LoadError, RuntimeError
|
71
|
+
end
|
72
|
+
}
|
73
|
+
opts.on('-l', '--list', "Display list of files monitored by script and exit") {
|
74
|
+
script = Gazr::Script.new(Gazr::Bin.path!)
|
75
|
+
controller = Gazr::Controller.new(script, Gazr.handler.new)
|
76
|
+
script.parse!
|
77
|
+
puts controller.monitored_paths
|
78
|
+
exit
|
79
|
+
}
|
80
|
+
|
81
|
+
def assert_syntax(code)
|
82
|
+
catch(:ok) { Object.new.instance_eval("BEGIN { throw :ok }; #{code}", %|-e "#{code}"|, 0) }
|
83
|
+
rescue SyntaxError => e
|
84
|
+
puts e.message.split("\n")[1]
|
85
|
+
exit
|
86
|
+
end
|
87
|
+
|
88
|
+
opts.on('-e', '--eval INLINE_SCRIPT', %|Evaluate script inline ($ gazr -e "watch('foo') { puts 'bar' }")|) {|code|
|
89
|
+
assert_syntax(code)
|
90
|
+
|
91
|
+
Tempfile.open('foo') {|f| f << code; @__path = f.path }
|
92
|
+
Gazr::Bin.path = Pathname(@__path)
|
93
|
+
}
|
94
|
+
|
95
|
+
opts.on_tail('-h', '--help', "Print inline help") { puts opts; exit }
|
96
|
+
opts.on_tail('-v', '--version', "Print version" ) { puts Gazr::Bin.version; exit }
|
97
|
+
|
98
|
+
opts.parse! ARGV
|
99
|
+
end
|
100
|
+
|
101
|
+
Gazr::Controller.new(Gazr::Script.new(Gazr::Bin.path!), Gazr.handler.new).run
|
data/gazr.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'gazr/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "gazr"
|
8
|
+
spec.version = Gazr::VERSION
|
9
|
+
spec.authors = ["James Akers"]
|
10
|
+
spec.email = ["j.f.akers@gmail.com"]
|
11
|
+
spec.description = %q{Flexible, Simple alternative to Guard. Watchr for Ruby 1.9.3 and beyond.}
|
12
|
+
spec.summary = %q{Flexible, Simple alternative to Guard. Watchr for Ruby 1.9.3 and beyond.}
|
13
|
+
spec.homepage = "http://github.com/jamesakers/gazr"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.require_paths = ["lib"]
|
17
|
+
spec.files = `git ls-files`.split($/)
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/*}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "mocha"
|
23
|
+
spec.add_development_dependency "minitest"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
end
|
data/lib/gazr.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require "gazr/version"
|
2
|
+
|
3
|
+
module Gazr
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'fsevent'
|
7
|
+
HAVE_FSE = true
|
8
|
+
rescue LoadError, RuntimeError
|
9
|
+
HAVE_FSE = false
|
10
|
+
end
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'rev'
|
14
|
+
HAVE_REV = true
|
15
|
+
rescue LoadError, RuntimeError
|
16
|
+
HAVE_REV = false
|
17
|
+
end
|
18
|
+
|
19
|
+
autoload :Script, 'gazr/script'
|
20
|
+
autoload :Controller, 'gazr/controller'
|
21
|
+
|
22
|
+
module EventHandler
|
23
|
+
autoload :Base, 'gazr/event_handlers/base'
|
24
|
+
autoload :Portable, 'gazr/event_handlers/portable'
|
25
|
+
autoload :Unix, 'gazr/event_handlers/unix' if ::Gazr::HAVE_REV
|
26
|
+
autoload :Darwin, 'gazr/event_handlers/darwin' if ::Gazr::HAVE_FSE
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
attr_accessor :options
|
31
|
+
attr_accessor :handler
|
32
|
+
|
33
|
+
def options
|
34
|
+
@options ||= Struct.new(:debug).new
|
35
|
+
@options.debug ||= false
|
36
|
+
@options
|
37
|
+
end
|
38
|
+
|
39
|
+
def debug(msg)
|
40
|
+
puts "[gazr debug] #{msg}" if options.debug
|
41
|
+
end
|
42
|
+
|
43
|
+
def handler
|
44
|
+
@handler ||=
|
45
|
+
case ENV['HANDLER'] || RbConfig::CONFIG['host_os']
|
46
|
+
when /darwin|mach|osx|fsevents?/i
|
47
|
+
if Gazr::HAVE_FSE
|
48
|
+
Gazr::EventHandler::Darwin
|
49
|
+
else
|
50
|
+
Gazr.debug "fsevent not found. `gem install ruby-fsevent` to get evented handler"
|
51
|
+
Gazr::EventHandler::Portable
|
52
|
+
end
|
53
|
+
when /sunos|solaris|bsd|linux|unix/i
|
54
|
+
if Gazr::HAVE_REV
|
55
|
+
Gazr::EventHandler::Unix
|
56
|
+
else
|
57
|
+
Gazr.debug "rev not found. `gem install rev` to get evented handler"
|
58
|
+
Gazr::EventHandler::Portable
|
59
|
+
end
|
60
|
+
when /mswin|windows|cygwin/i
|
61
|
+
Gazr::EventHandler::Portable
|
62
|
+
else
|
63
|
+
Gazr::EventHandler::Portable
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Gazr
|
2
|
+
class Controller
|
3
|
+
def initialize(script, handler)
|
4
|
+
@script, @handler = script, handler
|
5
|
+
@handler.add_observer(self)
|
6
|
+
Gazr.debug "using %s handler" % handler.class.name
|
7
|
+
end
|
8
|
+
|
9
|
+
def run
|
10
|
+
@script.parse!
|
11
|
+
@handler.listen(monitored_paths)
|
12
|
+
rescue Interrupt
|
13
|
+
end
|
14
|
+
|
15
|
+
def update(path, event_type = nil)
|
16
|
+
path = Pathname(path).expand_path
|
17
|
+
|
18
|
+
Gazr.debug("received #{event_type.inspect} event for #{path.relative_path_from(Pathname(Dir.pwd))}")
|
19
|
+
if path == @script.path && event_type != :accessed
|
20
|
+
@script.parse!
|
21
|
+
@handler.refresh(monitored_paths)
|
22
|
+
else
|
23
|
+
@script.action_for(path, event_type).call
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def monitored_paths
|
28
|
+
paths = Dir['**/*'].select do |path|
|
29
|
+
@script.patterns.any? {|p| path.match(p) }
|
30
|
+
end
|
31
|
+
paths.push(@script.path).compact!
|
32
|
+
paths.map {|path| Pathname(path).expand_path }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'observer'
|
2
|
+
|
3
|
+
module Gazr
|
4
|
+
module EventHandler
|
5
|
+
class AbstractMethod < Exception; end
|
6
|
+
module Base
|
7
|
+
include Observable
|
8
|
+
|
9
|
+
def notify(path, event_type = nil)
|
10
|
+
changed(true)
|
11
|
+
notify_observers(path, event_type)
|
12
|
+
end
|
13
|
+
|
14
|
+
def listen(monitored_paths)
|
15
|
+
raise AbstractMethod
|
16
|
+
end
|
17
|
+
|
18
|
+
def refresh(monitored_paths)
|
19
|
+
raise AbstractMethod
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Gazr
|
2
|
+
module EventHandler
|
3
|
+
|
4
|
+
class ::FSEvents
|
5
|
+
def self.debug(msg)
|
6
|
+
puts "[fsevents] #{msg}" if Watchr.options.debug
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Darwin < FSEvent
|
11
|
+
include Base
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
super
|
15
|
+
self.latency = 0.2
|
16
|
+
end
|
17
|
+
|
18
|
+
def listen(monitored_paths)
|
19
|
+
register_paths(monitored_paths)
|
20
|
+
start
|
21
|
+
end
|
22
|
+
|
23
|
+
def refresh(monitored_paths)
|
24
|
+
register_paths(monitored_paths)
|
25
|
+
restart
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def on_change(dirs)
|
31
|
+
dirs.each do |dir|
|
32
|
+
path, type = detect_change(dir)
|
33
|
+
notify(path, type) unless path.nil?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def detect_change(dir)
|
38
|
+
paths = monitored_paths_for(dir)
|
39
|
+
type = nil
|
40
|
+
path = paths.find {|path| type = event_type(path) }
|
41
|
+
|
42
|
+
FSEvents.debug("event detection error") if type.nil?
|
43
|
+
|
44
|
+
update_reference_times
|
45
|
+
[path, type]
|
46
|
+
end
|
47
|
+
|
48
|
+
def event_type(path)
|
49
|
+
return :deleted if !path.exist?
|
50
|
+
return :modified if path.mtime > @reference_times[path][:mtime]
|
51
|
+
return :accessed if path.atime > @reference_times[path][:atime]
|
52
|
+
return :changed if path.ctime > @reference_times[path][:ctime]
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def monitored_paths_for(dir)
|
57
|
+
dir = Pathname(dir).expand_path
|
58
|
+
@paths.select {|path| path.dirname.expand_path == dir }
|
59
|
+
end
|
60
|
+
|
61
|
+
def register_paths(paths)
|
62
|
+
@paths = paths
|
63
|
+
watch_directories(dirs_for(@paths))
|
64
|
+
update_reference_times
|
65
|
+
end
|
66
|
+
|
67
|
+
def dirs_for(paths)
|
68
|
+
paths.map {|path| path.dirname.to_s }.uniq
|
69
|
+
end
|
70
|
+
|
71
|
+
def update_reference_times
|
72
|
+
@reference_times = {}
|
73
|
+
now = Time.now
|
74
|
+
@paths.each do |path|
|
75
|
+
@reference_times[path] = {}
|
76
|
+
@reference_times[path][:atime] = now
|
77
|
+
@reference_times[path][:mtime] = now
|
78
|
+
@reference_times[path][:ctime] = now
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Gazr
|
2
|
+
module EventHandler
|
3
|
+
class Portable
|
4
|
+
include Base
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@reference_mtime = @reference_atime = @reference_ctime = Time.now
|
8
|
+
end
|
9
|
+
|
10
|
+
def listen(monitored_paths)
|
11
|
+
@monitored_paths = monitored_paths
|
12
|
+
loop { trigger; sleep(1) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def trigger
|
16
|
+
path, type = detect_event
|
17
|
+
notify(path, type) unless path.nil?
|
18
|
+
end
|
19
|
+
|
20
|
+
def refresh(monitored_paths)
|
21
|
+
@monitored_paths = monitored_paths
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def detect_event # OPTIMIZE, REFACTOR
|
27
|
+
@monitored_paths.each do |path|
|
28
|
+
return [path, :deleted] unless path.exist?
|
29
|
+
end
|
30
|
+
|
31
|
+
mtime_path = @monitored_paths.max {|a,b| a.mtime <=> b.mtime }
|
32
|
+
atime_path = @monitored_paths.max {|a,b| a.atime <=> b.atime }
|
33
|
+
ctime_path = @monitored_paths.max {|a,b| a.ctime <=> b.ctime }
|
34
|
+
|
35
|
+
if mtime_path.mtime > @reference_mtime then @reference_mtime = mtime_path.mtime; [mtime_path, :modified]
|
36
|
+
elsif atime_path.atime > @reference_atime then @reference_atime = atime_path.atime; [atime_path, :accessed]
|
37
|
+
elsif ctime_path.ctime > @reference_ctime then @reference_ctime = ctime_path.ctime; [ctime_path, :changed ]
|
38
|
+
else; nil; end
|
39
|
+
rescue Errno::ENOENT
|
40
|
+
retry
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Gazr
|
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
|
+
#
|
9
|
+
# @private
|
10
|
+
class SingleFileWatcher < Rev::StatWatcher
|
11
|
+
class << self
|
12
|
+
# Stores a reference back to handler so we can call its {Base#notify notify}
|
13
|
+
# method with file event info
|
14
|
+
#
|
15
|
+
# @return [EventHandler::Base]
|
16
|
+
#
|
17
|
+
attr_accessor :handler
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param [String] path
|
21
|
+
# single file to monitor
|
22
|
+
#
|
23
|
+
def initialize(path)
|
24
|
+
super
|
25
|
+
update_reference_times
|
26
|
+
end
|
27
|
+
|
28
|
+
# File's path as a Pathname
|
29
|
+
#
|
30
|
+
# @return [Pathname]
|
31
|
+
#
|
32
|
+
def pathname
|
33
|
+
@pathname ||= Pathname(@path)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Callback. Called on file change event. Delegates to
|
37
|
+
# {Controller#update}, passing in path and event type
|
38
|
+
#
|
39
|
+
# @return [undefined]
|
40
|
+
#
|
41
|
+
def on_change
|
42
|
+
self.class.handler.notify(path, type)
|
43
|
+
update_reference_times unless type == :deleted
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# @todo improve ENOENT error handling
|
49
|
+
def update_reference_times
|
50
|
+
@reference_atime = pathname.atime
|
51
|
+
@reference_mtime = pathname.mtime
|
52
|
+
@reference_ctime = pathname.ctime
|
53
|
+
rescue Errno::ENOENT
|
54
|
+
retry
|
55
|
+
end
|
56
|
+
|
57
|
+
# Type of latest event.
|
58
|
+
#
|
59
|
+
# A single type is determined, even though more than one stat times may
|
60
|
+
# have changed on the file. The type is the first to match in the
|
61
|
+
# following hierarchy:
|
62
|
+
#
|
63
|
+
# :deleted, :modified (mtime), :accessed (atime), :changed (ctime)
|
64
|
+
#
|
65
|
+
# @return [Symbol] type
|
66
|
+
# latest event's type
|
67
|
+
#
|
68
|
+
def type
|
69
|
+
return :deleted if !pathname.exist?
|
70
|
+
return :modified if pathname.mtime > @reference_mtime
|
71
|
+
return :accessed if pathname.atime > @reference_atime
|
72
|
+
return :changed if pathname.ctime > @reference_ctime
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def initialize
|
77
|
+
SingleFileWatcher.handler = self
|
78
|
+
@loop = Rev::Loop.default
|
79
|
+
end
|
80
|
+
|
81
|
+
# Enters listening loop. Will block control flow until application is
|
82
|
+
# explicitly stopped/killed.
|
83
|
+
#
|
84
|
+
# @return [undefined]
|
85
|
+
#
|
86
|
+
def listen(monitored_paths)
|
87
|
+
@monitored_paths = monitored_paths
|
88
|
+
attach
|
89
|
+
@loop.run
|
90
|
+
end
|
91
|
+
|
92
|
+
# Rebuilds file bindings. Will detach all current bindings, and reattach
|
93
|
+
# the `monitored_paths`
|
94
|
+
#
|
95
|
+
# @param [Array<Pathname>] monitored_paths
|
96
|
+
# list of paths the application is currently monitoring.
|
97
|
+
#
|
98
|
+
# @return [undefined]
|
99
|
+
#
|
100
|
+
def refresh(monitored_paths)
|
101
|
+
@monitored_paths = monitored_paths
|
102
|
+
detach
|
103
|
+
attach
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
# Binds all `monitored_paths` to the listening loop.
|
109
|
+
#
|
110
|
+
# @return [undefined]
|
111
|
+
#
|
112
|
+
def attach
|
113
|
+
@monitored_paths.each {|path| SingleFileWatcher.new(path.to_s).attach(@loop) }
|
114
|
+
end
|
115
|
+
|
116
|
+
# Unbinds all paths currently attached to listening loop.
|
117
|
+
#
|
118
|
+
# @return [undefined]
|
119
|
+
#
|
120
|
+
def detach
|
121
|
+
@loop.watchers.each {|watcher| watcher.detach }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
data/lib/gazr/script.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
module Gazr
|
2
|
+
class Script
|
3
|
+
|
4
|
+
DEFAULT_EVENT_TYPE = :modified
|
5
|
+
|
6
|
+
attr_reader :ec
|
7
|
+
attr_reader :rules
|
8
|
+
|
9
|
+
Rule = Struct.new(:pattern, :event_type, :action)
|
10
|
+
|
11
|
+
class EvalContext
|
12
|
+
|
13
|
+
def initialize(script)
|
14
|
+
@__script = script
|
15
|
+
end
|
16
|
+
|
17
|
+
def default_action(&action)
|
18
|
+
@__script.default_action(&action)
|
19
|
+
end
|
20
|
+
|
21
|
+
def watch(*args, &block)
|
22
|
+
@__script.watch(*args, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def reload
|
26
|
+
@__script.parse!
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def initialize(path = nil)
|
32
|
+
@path = path
|
33
|
+
@rules = []
|
34
|
+
@default_action = Proc.new {}
|
35
|
+
@ec = EvalContext.new(self)
|
36
|
+
end
|
37
|
+
|
38
|
+
def watch(pattern, event_type = DEFAULT_EVENT_TYPE, &action)
|
39
|
+
@rules << Rule.new(pattern, event_type, action || @default_action)
|
40
|
+
@rules.last
|
41
|
+
end
|
42
|
+
|
43
|
+
def default_action(&action)
|
44
|
+
@default_action = action if action
|
45
|
+
@default_action
|
46
|
+
end
|
47
|
+
|
48
|
+
def reset
|
49
|
+
@rules = []
|
50
|
+
@default_action = Proc.new {}
|
51
|
+
end
|
52
|
+
|
53
|
+
def parse!
|
54
|
+
return unless @path
|
55
|
+
reset
|
56
|
+
@ec.instance_eval(@path.read, @path.to_s)
|
57
|
+
rescue Errno::ENOENT
|
58
|
+
sleep(0.5)
|
59
|
+
retry
|
60
|
+
ensure
|
61
|
+
Gazr.debug('loaded script file %s' % @path.to_s.inspect)
|
62
|
+
end
|
63
|
+
|
64
|
+
def action_for(path, event_type = DEFAULT_EVENT_TYPE)
|
65
|
+
path = rel_path(path).to_s
|
66
|
+
rule = rules_for(path).detect {|rule| rule.event_type.nil? || rule.event_type == event_type }
|
67
|
+
if rule
|
68
|
+
data = path.match(rule.pattern)
|
69
|
+
lambda { rule.action.call(data) }
|
70
|
+
else
|
71
|
+
lambda {}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def patterns
|
76
|
+
@rules.map {|r| r.pattern }
|
77
|
+
end
|
78
|
+
|
79
|
+
def path
|
80
|
+
@path && Pathname(@path.respond_to?(:to_path) ? @path.to_path : @path.to_s).expand_path
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def rules_for(path)
|
86
|
+
@rules.reverse.select {|rule| path.match(rule.pattern) }
|
87
|
+
end
|
88
|
+
|
89
|
+
def rel_path(path)
|
90
|
+
Pathname(path).expand_path.relative_path_from(Pathname(Dir.pwd))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
data/lib/gazr/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gazr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James Akers
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-06-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mocha
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Flexible, Simple alternative to Guard. Watchr for Ruby 1.9.3 and beyond.
|
70
|
+
email:
|
71
|
+
- j.f.akers@gmail.com
|
72
|
+
executables:
|
73
|
+
- gazr
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- .gitignore
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- bin/gazr
|
83
|
+
- gazr.gemspec
|
84
|
+
- lib/gazr.rb
|
85
|
+
- lib/gazr/controller.rb
|
86
|
+
- lib/gazr/event_handlers/base.rb
|
87
|
+
- lib/gazr/event_handlers/darwin.rb
|
88
|
+
- lib/gazr/event_handlers/portable.rb
|
89
|
+
- lib/gazr/event_handlers/unix.rb
|
90
|
+
- lib/gazr/script.rb
|
91
|
+
- lib/gazr/version.rb
|
92
|
+
homepage: http://github.com/jamesakers/gazr
|
93
|
+
licenses:
|
94
|
+
- MIT
|
95
|
+
metadata: {}
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
requirements: []
|
111
|
+
rubyforge_project:
|
112
|
+
rubygems_version: 2.0.3
|
113
|
+
signing_key:
|
114
|
+
specification_version: 4
|
115
|
+
summary: Flexible, Simple alternative to Guard. Watchr for Ruby 1.9.3 and beyond.
|
116
|
+
test_files: []
|