summaryrefslogtreecommitdiff
path: root/lib/find.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/find.rb')
-rw-r--r--lib/find.rb145
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