Add yardoc to listeners.

This commit is contained in:
Michael Kessler 2011-09-20 13:58:25 +02:00
parent ad6fe6f69b
commit 53802ed355
6 changed files with 225 additions and 50 deletions

View File

@ -8,40 +8,60 @@ module Guard
autoload :Windows, 'guard/listeners/windows' autoload :Windows, 'guard/listeners/windows'
autoload :Polling, 'guard/listeners/polling' autoload :Polling, 'guard/listeners/polling'
# The Listener is the base class for all listener
# implementations.
#
class Listener class Listener
DefaultIgnorePaths = %w[. .. .bundle .git log tmp vendor] DEFAULT_IGNORE_PATHS = %w[. .. .bundle .git log tmp vendor]
attr_accessor :changed_files attr_accessor :changed_files
attr_reader :directory, :ignore_paths, :locked attr_reader :directory, :ignore_paths, :locked
def self.select_and_init(*a) # Select the appropriate listener implementation for the
# current OS and initializes it.
#
# @param [Array] args the arguments for the listener
# @return [Guard::Listener] the chosen listener
#
def self.select_and_init(*args)
if mac? && Darwin.usable? if mac? && Darwin.usable?
Darwin.new(*a) Darwin.new(*args)
elsif linux? && Linux.usable? elsif linux? && Linux.usable?
Linux.new(*a) Linux.new(*args)
elsif windows? && Windows.usable? elsif windows? && Windows.usable?
Windows.new(*a) Windows.new(*args)
else else
UI.info "Using polling (Please help us to support your system better than that.)" UI.info 'Using polling (Please help us to support your system better than that).'
Polling.new(*a) Polling.new(*args)
end end
end end
def initialize(directory = Dir.pwd, options = {}) # Initialize the listener.
#
# @param [String] directory the root directory to listen to
# @param [Hash] options the listener options
# @option options [Boolean] relativize_paths use only relative paths
# @option options [Array<String>] ignore_paths the paths to ignore by the listener
#
def initialize(directory = Dir.pwd, options = { })
@directory = directory.to_s @directory = directory.to_s
@sha1_checksums_hash = {} @sha1_checksums_hash = { }
@relativize_paths = options.fetch(:relativize_paths, true) @relativize_paths = options.fetch(:relativize_paths, true)
@changed_files = [] @changed_files = []
@locked = false @locked = false
@ignore_paths = DefaultIgnorePaths @ignore_paths = DEFAULT_IGNORE_PATHS
@ignore_paths |= options[:ignore_paths] if options[:ignore_paths] @ignore_paths |= options[:ignore_paths] if options[:ignore_paths]
update_last_event update_last_event
start_reactor start_reactor
end end
# Start the listener thread.
#
def start_reactor def start_reactor
return if ENV["GUARD_ENV"] == 'test' return if ENV["GUARD_ENV"] == 'test'
Thread.new do Thread.new do
loop do loop do
if @changed_files != [] && !@locked if @changed_files != [] && !@locked
@ -55,75 +75,114 @@ module Guard
end end
end end
# Start watching the root directory.
#
def start def start
watch(@directory) watch(@directory)
end end
# Stop listening for events.
#
def stop def stop
end end
# Lock the listener to ignore change events.
#
def lock def lock
@locked = true @locked = true
end end
# Unlock the listener to listen again to change events.
#
def unlock def unlock
@locked = false @locked = false
end end
# Clear the list of changed files.
#
def clear_changed_files def clear_changed_files
@changed_files.clear @changed_files.clear
end end
# Store a listener callback.
#
# @param [Block] callback the callback to store
#
def on_change(&callback) def on_change(&callback)
@callback = callback @callback = callback
end end
# Updates the timestamp of the last event.
#
def update_last_event def update_last_event
@last_event = Time.now @last_event = Time.now
end end
def modified_files(dirs, options = {}) def modified_files(dirs, options = { })
last_event = @last_event last_event = @last_event
update_last_event update_last_event
files = potentially_modified_files(dirs, options).select { |path| file_modified?(path, last_event) } files = potentially_modified_files(dirs, options).select { |path| file_modified?(path, last_event) }
relativize_paths(files) relativize_paths(files)
end end
def worker # Register a directory to watch.
raise NotImplementedError, "should respond to #watch" # Must be implemented by the subclasses.
end #
# @param [String] directory the directory to watch
# register a directory to watch. must be implemented by the subclasses #
def watch(directory) def watch(directory)
raise NotImplementedError, "do whatever you want here, given the directory as only argument" raise NotImplementedError, "do whatever you want here, given the directory as only argument"
end end
# Get all files that are in the watched directory.
#
# @return [Array<String>] the list of files
#
def all_files def all_files
potentially_modified_files([@directory], :all => true) potentially_modified_files([@directory], :all => true)
end end
# scopes all given paths to the current #directory # Scopes all given paths to the current directory.
#
# @param [Array<String>] paths the paths to change
# @return [Array<String>] all paths now relative to the current dir
#
def relativize_paths(paths) def relativize_paths(paths)
return paths unless relativize_paths? return paths unless relativize_paths?
paths.map do |path| paths.map do |path|
path.gsub(%r{^#{@directory}/}, '') path.gsub(%r{^#{ @directory }/}, '')
end end
end end
# Use relative paths?
#
# @return [Boolean] whether to use relative or absolute paths
#
def relativize_paths? def relativize_paths?
!!@relativize_paths !!@relativize_paths
end end
# return children of the passed dirs that are not in the ignore_paths list # Removes ignored paths from the directory list.
#
# @param [Array<String>] dirs the directory to listen to
# @param [Array<String>] ignore_paths the paths to ignore
# @return children of the passed dirs that are not in the ignore_paths list
#
def exclude_ignored_paths(dirs, ignore_paths = self.ignore_paths) def exclude_ignored_paths(dirs, ignore_paths = self.ignore_paths)
Dir.glob(dirs.map { |d| "#{d.sub(%r{/+$}, '')}/*" }, File::FNM_DOTMATCH).reject do |path| Dir.glob(dirs.map { |d| "#{d.sub(%r{/+$}, '')}/*" }, File::FNM_DOTMATCH).reject do |path|
ignore_paths.include?(File.basename(path)) ignore_paths.include?(File.basename(path))
end end
end end
private private
def potentially_modified_files(dirs, options={}) # Gets a list of files that are in the modified firectories.
#
# @param [Array<String>] dirs the list of directories
# @param [Hash] options the options
# @option options [Symbol] all whether to include all files
#
def potentially_modified_files(dirs, options = {})
paths = exclude_ignored_paths(dirs) paths = exclude_ignored_paths(dirs)
if options[:all] if options[:all]
@ -131,7 +190,7 @@ module Guard
if File.file?(path) if File.file?(path)
array << path array << path
else else
array += Dir.glob("#{path}/**/*", File::FNM_DOTMATCH).select { |p| File.file?(p) } array += Dir.glob("#{ path }/**/*", File::FNM_DOTMATCH).select { |p| File.file?(p) }
end end
array array
end end
@ -140,9 +199,17 @@ module Guard
end end
end end
# Test if the file content has changed.
#
# Depending on the filesystem, mtime/ctime is probably only precise to the second, so round # Depending on the filesystem, mtime/ctime is probably only precise to the second, so round
# both values down to the second for the comparison. # both values down to the second for the comparison.
#
# ctime is used only on == comparison to always catches Rails 3.1 Assets pipelined on Mac OSX # ctime is used only on == comparison to always catches Rails 3.1 Assets pipelined on Mac OSX
#
# @param [String] path the file path
# @param [Time] last_event the time of the last event
# @return [Boolean] Whether the file content has changed or not.
#
def file_modified?(path, last_event) def file_modified?(path, last_event)
ctime = File.ctime(path).to_i ctime = File.ctime(path).to_i
mtime = File.mtime(path).to_i mtime = File.mtime(path).to_i
@ -158,6 +225,12 @@ module Guard
false false
end end
# Tests if the file content has been modified by
# comparing the SHA1 checksum.
#
# @param [String] path the file path
# @param [String] sha1_checksum the checksum of the file
#
def file_content_modified?(path, sha1_checksum) def file_content_modified?(path, sha1_checksum)
if @sha1_checksums_hash[path] != sha1_checksum if @sha1_checksums_hash[path] != sha1_checksum
set_sha1_checksums_hash(path, sha1_checksum) set_sha1_checksums_hash(path, sha1_checksum)
@ -167,22 +240,44 @@ module Guard
end end
end end
# Set the current checksum of a file.
#
# @param [String] path the file path
# @param [String] sha1_checksum the checksum of the file
#
def set_sha1_checksums_hash(path, sha1_checksum) def set_sha1_checksums_hash(path, sha1_checksum)
@sha1_checksums_hash[path] = sha1_checksum @sha1_checksums_hash[path] = sha1_checksum
end end
# Calculates the SHA1 checksum of a file.
#
# @param [String] path the path to the file
# @return [String] the SHA1 checksum
#
def sha1_checksum(path) def sha1_checksum(path)
Digest::SHA1.file(path).to_s Digest::SHA1.file(path).to_s
end end
# Test if the OS is Mac OS X.
#
# @return [Boolean] Whether the OS is Mac OS X
#
def self.mac? def self.mac?
RbConfig::CONFIG['target_os'] =~ /darwin/i RbConfig::CONFIG['target_os'] =~ /darwin/i
end end
# Test if the OS is Linux.
#
# @return [Boolean] Whether the OS is Linux
#
def self.linux? def self.linux?
RbConfig::CONFIG['target_os'] =~ /linux/i RbConfig::CONFIG['target_os'] =~ /linux/i
end end
# Test if the OS is Windows.
#
# @return [Boolean] Whether the OS is Windows
#
def self.windows? def self.windows?
RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i RbConfig::CONFIG['target_os'] =~ /mswin|mingw/i
end end

View File

@ -1,41 +1,60 @@
module Guard module Guard
# Listener implementation for Mac OS X FSEvents.
#
class Darwin < Listener class Darwin < Listener
# Initialize the Listener.
#
def initialize(*) def initialize(*)
super super
@fsevent = FSEvent.new @fsevent = FSEvent.new
end end
def worker # Start the listener.
@fsevent #
end
def start def start
super super
worker.run worker.run
end end
# Stop the listener.
#
def stop def stop
super super
worker.stop worker.stop
end end
# Check if the listener is usable on the current OS.
#
# @return [Boolean] whether usable or not
#
def self.usable? def self.usable?
require 'rb-fsevent' require 'rb-fsevent'
if !defined?(FSEvent::VERSION) || (defined?(Gem::Version) && if !defined?(FSEvent::VERSION) || (defined?(Gem::Version) &&
Gem::Version.new(FSEvent::VERSION) < Gem::Version.new('0.4.0')) Gem::Version.new(FSEvent::VERSION) < Gem::Version.new('0.4.0'))
UI.info "Please update rb-fsevent (>= 0.4.0)" UI.info 'Please update rb-fsevent (>= 0.4.0)'
false false
else else
true true
end end
rescue LoadError rescue LoadError
UI.info "Please install rb-fsevent gem for Mac OSX FSEvents support" UI.info 'Please install rb-fsevent gem for Mac OSX FSEvents support'
false false
end end
private private
# Get the listener worker.
#
def worker
@fsevent
end
# Watch the given directory for file changes.
#
# @param [String] directory the directory to watch
#
def watch(directory) def watch(directory)
worker.watch(directory) do |modified_dirs| worker.watch(directory) do |modified_dirs|
files = modified_files(modified_dirs) files = modified_files(modified_dirs)

View File

@ -1,6 +1,11 @@
module Guard module Guard
# Listener implementation for Linux inotify.
#
class Linux < Listener class Linux < Listener
# Initialize the Listener.
#
def initialize(*) def initialize(*)
super super
@inotify = INotify::Notifier.new @inotify = INotify::Notifier.new
@ -8,44 +13,53 @@ module Guard
@latency = 0.5 @latency = 0.5
end end
# Start the listener.
#
def start def start
@stop = false @stop = false
super super
watch_change unless watch_change? watch_change unless watch_change?
end end
# Stop the listener.
#
def stop def stop
super super
@stop = true @stop = true
sleep(@latency) sleep(@latency)
end end
# Check if the listener is usable on the current OS.
#
# @return [Boolean] whether usable or not
#
def self.usable? def self.usable?
require 'rb-inotify' require 'rb-inotify'
if !defined?(INotify::VERSION) || (defined?(Gem::Version) && if !defined?(INotify::VERSION) || (defined?(Gem::Version) &&
Gem::Version.new(INotify::VERSION.join('.')) < Gem::Version.new('0.8.5')) Gem::Version.new(INotify::VERSION.join('.')) < Gem::Version.new('0.8.5'))
UI.info "Please update rb-inotify (>= 0.8.5)" UI.info 'Please update rb-inotify (>= 0.8.5)'
false false
else else
true true
end end
rescue LoadError rescue LoadError
UI.info "Please install rb-inotify gem for Linux inotify support" UI.info 'Please install rb-inotify gem for Linux inotify support'
false false
end end
def watch_change? private
!!@watch_change
end
private
# Get the listener worker.
#
def worker def worker
@inotify @inotify
end end
# Watch the given directory for file changes.
#
# @param [String] directory the directory to watch
#
def watch(directory) def watch(directory)
# The event selection is based on https://github.com/guard/guard/wiki/Analysis-of-inotify-events-for-different-editors
worker.watch(directory, :recursive, :attrib, :create, :move_self, :close_write) do |event| worker.watch(directory, :recursive, :attrib, :create, :move_self, :close_write) do |event|
unless event.name == "" # Event on root directory unless event.name == "" # Event on root directory
@files << event.absolute_name @files << event.absolute_name
@ -54,6 +68,16 @@ module Guard
rescue Interrupt rescue Interrupt
end end
# Test if inotify is watching for changes.
#
# @return [Boolean] whether inotify is active or not
#
def watch_change?
!!@watch_change
end
# Watch for file system changes.
#
def watch_change def watch_change
@watch_change = true @watch_change = true
until @stop until @stop

View File

@ -1,24 +1,46 @@
module Guard module Guard
# Polling listener that works cross-plattform and
# has no dependencies. This is the listener that
# uses the most CPU processing power and has higher
# File IO that the other implementations.
#
class Polling < Listener class Polling < Listener
# Initialize the Listener.
#
def initialize(*) def initialize(*)
super super
@latency = 1.5 @latency = 1.5
end end
# Start the listener.
#
def start def start
@stop = false @stop = false
super super
watch_change watch_change
end end
# Stop the listener.
#
def stop def stop
super super
@stop = true @stop = true
end end
private # Watch the given directory for file changes.
#
# @param [String] directory the directory to watch
#
def watch(directory)
@existing = all_files
end
private
# Watch for file system changes.
#
def watch_change def watch_change
until @stop until @stop
start = Time.now.to_f start = Time.now.to_f
@ -29,9 +51,5 @@ module Guard
end end
end end
def watch(directory)
@existing = all_files
end
end end
end end

View File

@ -1,35 +1,48 @@
module Guard module Guard
# Listener implementation for Windows fchange.
#
class Windows < Listener class Windows < Listener
# Initialize the Listener.
#
def initialize(*) def initialize(*)
super super
@fchange = FChange::Notifier.new @fchange = FChange::Notifier.new
end end
# Start the listener.
#
def start def start
super super
worker.run worker.run
end end
# Stop the listener.
#
def stop def stop
super super
worker.stop worker.stop
end end
# Check if the listener is usable on the current OS.
#
# @return [Boolean] whether usable or not
#
def self.usable? def self.usable?
require 'rb-fchange' require 'rb-fchange'
true true
rescue LoadError rescue LoadError
UI.info "Please install rb-fchange gem for Windows file events support" UI.info 'Please install rb-fchange gem for Windows file events support'
false false
end end
private private
def worker
@fchange
end
# Watch the given directory for file changes.
#
# @param [String] directory the directory to watch
#
def watch(directory) def watch(directory)
worker.watch(directory, :all_events, :recursive) do |event| worker.watch(directory, :all_events, :recursive) do |event|
paths = [File.expand_path(event.watcher.path)] paths = [File.expand_path(event.watcher.path)]
@ -38,5 +51,11 @@ module Guard
end end
end end
# Get the listener worker.
#
def worker
@fchange
end
end end
end end

View File

@ -172,7 +172,7 @@ describe Guard::Listener do
describe "#ignore_paths" do describe "#ignore_paths" do
it "defaults to the default ignore paths" do it "defaults to the default ignore paths" do
subject.new.ignore_paths.should == Guard::Listener::DefaultIgnorePaths subject.new.ignore_paths.should == Guard::Listener::DEFAULT_IGNORE_PATHS
end end
it "can be added to via :ignore_paths option" do it "can be added to via :ignore_paths option" do