diff options
Diffstat (limited to 'lib/find.rb')
| -rw-r--r-- | lib/find.rb | 145 |
1 files changed, 90 insertions, 55 deletions
diff --git a/lib/find.rb b/lib/find.rb index 9ca39cabcb..d9b81eb92d 100644 --- a/lib/find.rb +++ b/lib/find.rb @@ -1,74 +1,109 @@ +# frozen_string_literal: true # # find.rb: the Find module for processing all files under a given directory. # +# :markup: markdown # -# The +Find+ module supports the top-down traversal of a set of file paths. -# -# For example, to total the size of all files under your home directory, -# ignoring anything in a "dot" directory (e.g. $HOME/.ssh): -# -# require 'find' -# -# total_size = 0 -# -# Find.find(ENV["HOME"]) do |path| -# if FileTest.directory?(path) -# if File.basename(path)[0] == ?. -# Find.prune # Don't look any further into this directory. -# else -# next -# end -# else -# total_size += FileTest.size(path) -# end -# end -# +# \Module \Find supports the top-down traversal of entries in the file system. module Find + # The version string + VERSION = "0.2.0" + + # :markup: markdown + # + # With a block given, performs a depth-first traversal of each given path in `paths`; + # calls the block with each found file or directory path: # - # Calls the associated block with the name of every file and directory listed - # as arguments, then recursively on their subdirectories, and so on. + # ```ruby + # paths = [] + # Find.find('bin', 'jit') {|path| paths << path } + # paths + # # => + # # ["bin", + # # "bin/gem", + # # "jit", + # # "jit/Cargo.toml", + # # "jit/src", + # # "jit/src/lib.rs"] + # ``` # - # See the +Find+ module documentation for an example. + # Raises an exception if a given path cannot be read. # - def find(*paths) # :yield: path - paths.collect!{|d| d.dup} - while file = paths.shift - catch(:prune) do - yield file - begin - if File.lstat(file).directory? then - d = Dir.open(file) - begin - for f in d - next if f == "." or f == ".." - if File::ALT_SEPARATOR and file =~ /^(?:[\/\\]|[A-Za-z]:[\/\\]?)$/ then - f = file + f - elsif file == "/" then - f = "/" + f - else - f = File.join(file, f) - end - paths.unshift f - end - ensure - d.close - end - end - rescue Errno::ENOENT, Errno::EACCES - end + # When keyword argument `ignore_error` is given as `true` (the default), + # certain exceptions during traversal are ignored (i.e., silently rescued): + # Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG, Errno::EINVAL; + # when given as `false`, no exceptions are rescued. + # + # Note that these exceptions may be ignored only in `Find` traversal code; + # an exception raised before traversal begins, + # or raised while in the block is not ignored. + # Each of the calls below raises an Errno::ENOENT exception that is not ignored: + # + # ```ruby + # Find.find('nosuch') { } + # Find.find('lib') {|entry| raise Errno::ENOENT } + # ``` + # + # With no block given, returns a new Enumerator. + def find(*paths, ignore_error: true) # :yield: path + block_given? or return enum_for(__method__, *paths, ignore_error: ignore_error) + + fs_encoding = Encoding.find("filesystem") + + paths.collect!{|d| raise Errno::ENOENT, d unless File.exist?(d); d.dup}.each do |path| + path = path.to_path if path.respond_to? :to_path + enc = path.encoding == Encoding::US_ASCII ? fs_encoding : path.encoding + ps = [path] + while file = ps.shift + catch(:prune) do + yield file.dup + begin + s = File.lstat(file) + rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG, Errno::EINVAL + raise unless ignore_error + next + end + if s.directory? then + begin + fs = Dir.children(file, encoding: enc) + rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG, Errno::EINVAL + raise unless ignore_error + next + end + fs.sort! + fs.reverse_each {|f| + f = File.join(file, f) + ps.unshift f + } + end + end end end + nil end + # :markup: markdown + # + # call-seq: + # Find.prune + # + # This method is meaningful only within a block given with Find.find. # - # Skips the current file or directory, restarting the loop with the next - # entry. If the current file is a directory, that directory will not be - # recursively entered. Meaningful only within the block associated with - # Find::find. + # Inside such a block, + # "prunes" the traversed file tree by not descending into the current directory: # - # See the +Find+ module documentation for an example. + # ```ruby + # files = [] + # Find.find('.') do |path| + # Find.prune if File.basename(path) == 'test' + # next unless File.file?(path) && File.extname(path) == '.rb' + # files << path + # end + # files.size # => 6690 + # files.take(3) # => ["./KNOWNBUGS.rb", "./array.rb", "./ast.rb"] + # ``` # def prune throw :prune |
