module Bind class Listener attr_accessor :files, :action, :timeout, :interval, :event attr_reader :run_time, :start_time, :finish_time #-- # Exceptions #++ class Error < StandardError; end ## # Event listener. Must specify the :files, and :action options. # # === Options: # # :files array of files and / or directories # :action an object responding to #call, which is used as the callback for the event handler # :timeout time in seconds, after which the listener should stop. Defaults to 0, meaning infinity # :event event to bind to, may be one of (:change). Defaults to :change # :debug log verbose debugging information to this stream # :interval sleep interval in seconds. Defaults to 2 # def initialize options = {} @run_time, @mtimes = 0, {} @files = options.fetch :files do raise ArgumentError, 'specify one or more :files (or directories) to bind the listener to' end @action = options.fetch :action do raise ArgumentError, 'pass a valid :action responding to #call' end @log = options.fetch :debug, false @timeout = options.fetch :timeout, 0 @interval = options.fetch :interval, 2 @event = options.fetch :event, :change end ## # Start the listener. def run! start_time = Time.now log "binding to #{files.join(', ')}, watching #{event} every #{interval} second(s)." + (timeout > 0 ? " Terminating in #{timeout} seconds" : '') catch :halt do loop do @run_time = Time.now - start_time throw :halt if timeout > 0 and @run_time >= timeout log '.', true files.each { |file| send event, File.new(file) } sleep interval end end finish_time = Time.now log "binding terminated" end private ## # Handle change event. def change file if changed? file log "changed #{file.path}" action.call file @mtimes[file.path] = file.mtime end end ## # Check if +file+ has been modified since the last check. def changed? file file.mtime > (@mtimes[file.path] ||= file.mtime) end ## # Optionally log a +message+ when a stream has been specified. def log message, print = false if @log print ? @log.print(message) : @log.puts(message) @log.flush end end end end