module RGen

module Util

# WARNING: the mechanism of taking timestamps of directories in order to find out if the
# content has changed doesn't work reliably across all kinds of filesystems
#
class CachedGlob

  def initialize(dir_glob, file_glob)
    @dir_glob = dir_glob
    @file_glob = file_glob
    @root_dirs = []
    @dirs = {}
    @files = {}
    @timestamps = {}
  end

  # returns all files contained in directories matched by +dir_glob+ which match +file_glob+.
  # +file_glob+ must be relative to +dir_glob+.
  # dir_glob "*/a" with file_glob "**/*.txt" is basically equivalent with Dir.glob("*/a/**/*.txt")
  # the idea is that the file glob will only be re-eavluated when the content of one of the 
  # directories matched by dir_glob has changed.
  # this will only be faster than a normal Dir.glob if the number of dirs matched by dir_glob is
  # relatively large and changes in files affect only a few of them at a time.
  def glob
    root_dirs = Dir.glob(@dir_glob)
    (@root_dirs - root_dirs).each do |d|
      remove_root_dir(d)
    end
    (@root_dirs & root_dirs).each do |d|
      update_root_dir(d) if dir_changed?(d)
    end
    (root_dirs - @root_dirs).each do |d|
      update_root_dir(d)
    end
    @root_dirs = root_dirs
    @root_dirs.sort.collect{|d| @files[d]}.flatten
  end

  private

  def dir_changed?(dir)
    @dirs[dir].any?{|d| File.mtime(d) != @timestamps[dir][d]}
  end

  def update_root_dir(dir)
    @dirs[dir] = Dir.glob(dir+"/**/")
    @files[dir] = Dir.glob(dir+"/"+@file_glob)
    @timestamps[dir] = {}
    @dirs[dir].each do |d|
      @timestamps[dir][d] = File.mtime(d)
    end
  end

  def remove_root_dir(dir)
    @dirs.delete(dir)
    @files.delete(dir)
    @timestamps.delete(dir)
  end

end

end

end

