lib/tins/find.rb
require 'enumerator' require 'tins/module_group' module Tins module Find EXPECTED_STANDARD_ERRORS = ModuleGroup[ Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG ] class Finder def initialize(opts = {}) @show_hidden = opts.fetch(:show_hidden) { true } @raise_errors = opts.fetch(:raise_errors) { false } @follow_symlinks = opts.fetch(:follow_symlinks) { true } end attr_reader :show_hidden attr_reader :raise_errors attr_reader :follow_symlinks def find(*paths) block_given? or return enum_for(__method__, *paths) paths.collect! { |d| d.dup } while path = paths.shift path = prepare_path(path) catch(:prune) do path.stat or next yield path if path.stat.directory? begin ps = Dir.entries(path) rescue EXPECTED_STANDARD_ERRORS @raise_errors ? raise : next end ps.sort! ps.reverse_each do |p| next if p == "." or p == ".." next if !@show_hidden && p.start_with?('.') p = File.join(path, p) paths.unshift p.untaint end end end end end private def prepare_path(path) path = path.dup.taint path.extend PathExtension path.finder = self path end end module PathExtension attr_accessor :finder def stat begin @stat ||= if finder.follow_symlinks File.stat(self) else File.lstat(self) end rescue EXPECTED_STANDARD_ERRORS if finder.raise_errors raise end end end def file if stat.file? File.new(self) end end end # # Calls the associated block with the name of every path and directory # listed as arguments, then recursively on their subdirectories, and so on. # # See the +Find+ module documentation for an example. # def find(*paths, &block) # :yield: path opts = Hash === paths.last ? paths.pop : {} Finder.new(opts).find(*paths, &block) end # # Skips the current path or directory, restarting the loop with the next # entry. If the current path is a directory, that directory will not be # recursively entered. Meaningful only within the block associated with # Find::find. # # See the +Find+ module documentation for an example. # def prune throw :prune end module_function :find, :prune end end