Skip to content

Commit 92e95d8

Browse files
hsbtclaude
andcommitted
Implement Pathname#find in builtin Pathname
Copy the traversal logic from Find.find (lib/find.rb) into pathname_builtin.rb to remove the runtime dependency on the find library. The implementation preserves the same error handling, encoding, traversal order, and catch(:prune) support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a2a69b4 commit 92e95d8

2 files changed

Lines changed: 52 additions & 29 deletions

File tree

lib/pathname.rb

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,6 @@
99
#
1010
# For documentation, see class Pathname.
1111
#
12-
class Pathname # * Find *
13-
#
14-
# Iterates over the directory tree in a depth first manner, yielding a
15-
# Pathname for each file under "this" directory.
16-
#
17-
# Note that you need to require 'pathname' to use this method.
18-
#
19-
# Returns an Enumerator if no block is given.
20-
#
21-
# Since it is implemented by the standard library module Find, Find.prune can
22-
# be used to control the traversal.
23-
#
24-
# If +self+ is +.+, yielded pathnames begin with a filename in the
25-
# current directory, not +./+.
26-
#
27-
# See Find.find
28-
#
29-
def find(ignore_error: true) # :yield: pathname
30-
return to_enum(__method__, ignore_error: ignore_error) unless block_given?
31-
require 'find'
32-
if @path == '.'
33-
Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f.delete_prefix('./')) }
34-
else
35-
Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f) }
36-
end
37-
end
38-
end
39-
40-
4112
class Pathname # * FileUtils *
4213
# Recursively deletes a directory, including all directories beneath it.
4314
#

pathname_builtin.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,6 +1386,58 @@ def unlink()
13861386
end
13871387

13881388
class Pathname
1389+
# Iterates over the directory tree in a depth first manner, yielding a
1390+
# Pathname for each file under "this" directory.
1391+
#
1392+
# Returns an Enumerator if no block is given.
1393+
#
1394+
# If +self+ is +.+, yielded pathnames begin with a filename in the
1395+
# current directory, not +./+.
1396+
#
1397+
# If +ignore_error+ is true (the default), errors during traversal
1398+
# are silently ignored. If false, errors are raised.
1399+
#
1400+
# Pathname("/usr/local").find {|f| p f }
1401+
# #=> #<Pathname:/usr/local>
1402+
# #=> #<Pathname:/usr/local/bin>
1403+
# #=> ...
1404+
#
1405+
def find(ignore_error: true) # :yield: pathname
1406+
return to_enum(__method__, ignore_error: ignore_error) unless block_given?
1407+
fs_encoding = Encoding.find("filesystem")
1408+
enc = @path.encoding == Encoding::US_ASCII ? fs_encoding : @path.encoding
1409+
ps = [@path]
1410+
while file = ps.shift
1411+
catch(:prune) do
1412+
if @path == '.'
1413+
yield self.class.new(file == '.' ? file : file.delete_prefix('./'))
1414+
else
1415+
yield self.class.new(file)
1416+
end
1417+
begin
1418+
s = File.lstat(file)
1419+
rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG, Errno::EINVAL
1420+
raise unless ignore_error
1421+
next
1422+
end
1423+
if s.directory? then
1424+
begin
1425+
fs = Dir.children(file, encoding: enc)
1426+
rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG, Errno::EINVAL
1427+
raise unless ignore_error
1428+
next
1429+
end
1430+
fs.sort!
1431+
fs.reverse_each {|f|
1432+
f = File.join(file, f)
1433+
ps.unshift f
1434+
}
1435+
end
1436+
end
1437+
end
1438+
nil
1439+
end
1440+
13891441
undef =~ if Kernel.method_defined?(:=~)
13901442
end
13911443

0 commit comments

Comments
 (0)