1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
require 'enumerator'
require 'pathname'
require 'tins/module_group'
module Tins
module Find
EXPECTED_STANDARD_ERRORS = ModuleGroup[
Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP,
Errno::ENAMETOOLONG
]
class Finder
module PathExtension
attr_accessor :finder
def finder_stat
finder.protect_from_errors do
finder.follow_symlinks ? File.stat(self) : File.lstat(self)
end
end
def file
finder.protect_from_errors do
File.new(self) if file?
end
end
def file?
finder.protect_from_errors { s = finder_stat and s.file? }
end
def directory?
finder.protect_from_errors { s = finder_stat and s.directory? }
end
def exist?
finder.protect_from_errors { File.exist?(self) }
end
def stat
finder.protect_from_errors { File.stat(self) }
end
def lstat
finder.protect_from_errors { File.lstat(self) }
end
def pathname
Pathname.new(self)
end
def suffix
pathname.extname[1..-1] || ''
end
end
def initialize(opts = {})
@show_hidden = opts.fetch(:show_hidden) { true }
@raise_errors = opts.fetch(:raise_errors) { false }
@follow_symlinks = opts.fetch(:follow_symlinks) { true }
if opts.key?(:visit) && opts.key?(:suffix)
raise ArgumentError, 'either use visit or suffix argument'
elsif opts.key?(:visit)
@visit = opts.fetch(:visit) { -> path { true } }
elsif opts.key?(:suffix)
@suffix = Array(opts[:suffix])
@visit = -> path { @suffix.nil? || @suffix.empty? || @suffix.include?(path.suffix) }
end
end
attr_accessor :show_hidden
attr_accessor :raise_errors
attr_accessor :follow_symlinks
attr_accessor :suffix
def visit_path?(path)
if !defined?(@visit) || @visit.nil?
true
else
@visit.(path)
end
end
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
stat = path.finder_stat or next
visit_path?(path) and yield path
if stat.directory?
ps = protect_from_errors { Dir.entries(path) } or next
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
end
end
end
end
end
def prepare_path(path)
path = path.dup
path.extend PathExtension
path.finder = self
path
end
def protect_from_errors(errors = Find::EXPECTED_STANDARD_ERRORS)
yield
rescue errors
raise_errors and raise
return
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
|