gazr 0.0.1
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.
- 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: []
|