This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
# Copyright (c) 2011 Konstantin Haase
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
|
||||
require 'stringio'
|
||||
|
||||
module OTNetstring
|
||||
class Error < StandardError; end
|
||||
|
||||
class << self
|
||||
def parse(io, encoding = 'internal', fallback_encoding = nil)
|
||||
fallback_encoding = io.encoding if io.respond_to? :encoding
|
||||
io = StringIO.new(io) if io.respond_to? :to_str
|
||||
length, byte = String.new, nil
|
||||
|
||||
while byte.nil? || byte =~ /\d/
|
||||
length << byte if byte
|
||||
byte = io.read(1)
|
||||
end
|
||||
|
||||
if length.size > 9
|
||||
raise Error, "#{length} is longer than 9 digits"
|
||||
elsif length !~ /\d+/
|
||||
raise Error, "Expected '#{byte}' to be a digit"
|
||||
end
|
||||
length = Integer(length)
|
||||
|
||||
case byte
|
||||
when '#' then Integer io.read(length)
|
||||
when ',' then with_encoding io.read(length), encoding, fallback_encoding
|
||||
when '~' then
|
||||
raise Error, "nil has length of 0, #{length} given" unless length == 0
|
||||
when '!' then io.read(length) == 'true'
|
||||
when '[', '{'
|
||||
array = []
|
||||
start = io.pos
|
||||
array << parse(io, encoding, fallback_encoding) while io.pos - start < length
|
||||
raise Error, 'Nested element longer than container' if io.pos - start != length
|
||||
byte == "{" ? Hash[*array] : array
|
||||
else
|
||||
raise Error, "Unknown type '#{byte}'"
|
||||
end
|
||||
end
|
||||
|
||||
def encode(obj, string_sep = ',')
|
||||
case obj
|
||||
when String then with_encoding "#{obj.bytesize}#{string_sep}#{obj}", "binary"
|
||||
when Integer then encode(obj.inspect, '#')
|
||||
when NilClass then "0~"
|
||||
when Array then encode(obj.map { |e| encode(e) }.join, '[')
|
||||
when Hash then encode(obj.map { |a,b| encode(a)+encode(b) }.join, '{')
|
||||
when FalseClass, TrueClass then encode(obj.inspect, '!')
|
||||
else raise Error, 'cannot encode %p' % obj
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def with_encoding(str, encoding, fallback = nil)
|
||||
return str unless str.respond_to? :encode
|
||||
encoding = Encoding.find encoding if encoding.respond_to? :to_str
|
||||
encoding ||= fallback
|
||||
encoding ? str.encode(encoding) : str
|
||||
rescue EncodingError
|
||||
str.force_encoding(encoding)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
require 'rb-fsevent/fsevent'
|
||||
require 'rb-fsevent/version'
|
||||
@@ -0,0 +1,157 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
require 'otnetstring'
|
||||
|
||||
class FSEvent
|
||||
class << self
|
||||
class_eval <<-END
|
||||
def root_path
|
||||
"#{File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))}"
|
||||
end
|
||||
END
|
||||
class_eval <<-END
|
||||
def watcher_path
|
||||
"#{File.join(FSEvent.root_path, 'bin', 'fsevent_watch')}"
|
||||
end
|
||||
END
|
||||
end
|
||||
|
||||
attr_reader :paths, :callback
|
||||
|
||||
def initialize args = nil, &block
|
||||
watch(args, &block) unless args.nil?
|
||||
end
|
||||
|
||||
def watch(watch_paths, options=nil, &block)
|
||||
@paths = watch_paths.kind_of?(Array) ? watch_paths : [watch_paths]
|
||||
@callback = block
|
||||
|
||||
if options.kind_of?(Hash)
|
||||
@options = parse_options(options)
|
||||
elsif options.kind_of?(Array)
|
||||
@options = options
|
||||
else
|
||||
@options = []
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
@pipe = open_pipe
|
||||
@running = true
|
||||
|
||||
# please note the use of IO::select() here, as it is used specifically to
|
||||
# preserve correct signal handling behavior in ruby 1.8.
|
||||
while @running && IO::select([@pipe], nil, nil, nil)
|
||||
# managing the IO ourselves allows us to be careful and never pass an
|
||||
# incomplete message to OTNetstring.parse()
|
||||
message = String.new
|
||||
length = String.new
|
||||
byte = nil
|
||||
|
||||
reading_length = true
|
||||
found_length = false
|
||||
|
||||
while reading_length
|
||||
byte = @pipe.read_nonblock(1)
|
||||
if "#{byte}" =~ /\d/
|
||||
length << byte
|
||||
found_length = true
|
||||
elsif found_length == false
|
||||
next
|
||||
else
|
||||
reading_length = false
|
||||
end
|
||||
end
|
||||
length = Integer(length, 10)
|
||||
type = byte
|
||||
|
||||
message << "#{length}#{type}"
|
||||
message << @pipe.read(length)
|
||||
|
||||
decoded = OTNetstring.parse(message)
|
||||
modified_paths = decoded["events"].map {|event| event["path"]}
|
||||
# passing the full info as a second block param feels icky, but such is
|
||||
# the trap of backward compatibility.
|
||||
case callback.arity
|
||||
when 1
|
||||
callback.call(modified_paths)
|
||||
when 2
|
||||
callback.call(modified_paths, decoded)
|
||||
end
|
||||
end
|
||||
rescue Interrupt, IOError, Errno::EBADF
|
||||
ensure
|
||||
stop
|
||||
end
|
||||
|
||||
def stop
|
||||
unless @pipe.nil?
|
||||
Process.kill('KILL', @pipe.pid) if process_running?(@pipe.pid)
|
||||
@pipe.close
|
||||
end
|
||||
rescue IOError, Errno::EBADF
|
||||
ensure
|
||||
@running = false
|
||||
end
|
||||
|
||||
def process_running?(pid)
|
||||
begin
|
||||
Process.kill(0, pid)
|
||||
true
|
||||
rescue Errno::ESRCH
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
if RUBY_VERSION < '1.9'
|
||||
def open_pipe
|
||||
IO.popen("'#{self.class.watcher_path}' #{options_string} #{shellescaped_paths}")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def options_string
|
||||
@options.join(' ')
|
||||
end
|
||||
|
||||
def shellescaped_paths
|
||||
@paths.map {|path| shellescape(path)}.join(' ')
|
||||
end
|
||||
|
||||
# for Ruby 1.8.6 support
|
||||
def shellescape(str)
|
||||
# An empty argument will be skipped, so return empty quotes.
|
||||
return "''" if str.empty?
|
||||
|
||||
str = str.dup
|
||||
|
||||
# Process as a single byte sequence because not all shell
|
||||
# implementations are multibyte aware.
|
||||
str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
|
||||
|
||||
# A LF cannot be escaped with a backslash because a backslash + LF
|
||||
# combo is regarded as line continuation and simply ignored.
|
||||
str.gsub!(/\n/, "'\n'")
|
||||
|
||||
return str
|
||||
end
|
||||
else
|
||||
def open_pipe
|
||||
IO.popen([self.class.watcher_path] + @options + @paths)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_options(options={})
|
||||
opts = ['--format=otnetstring']
|
||||
opts.concat(['--since-when', options[:since_when]]) if options[:since_when]
|
||||
opts.concat(['--latency', options[:latency]]) if options[:latency]
|
||||
opts.push('--no-defer') if options[:no_defer]
|
||||
opts.push('--watch-root') if options[:watch_root]
|
||||
opts.push('--file-events') if options[:file_events]
|
||||
# ruby 1.9's IO.popen(array-of-stuff) syntax requires all items to be strings
|
||||
opts.map {|opt| "#{opt}"}
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
class FSEvent
|
||||
VERSION = '0.11.2'
|
||||
end
|
||||
Reference in New Issue
Block a user