Skip to content

Commit 88e2a62

Browse files
Add DirEntry-based readdir methods
Co-Authored-By: Ian Butterworth <i.r.butterworth@gmail.com> Co-Authored-By: Simeon David Schaub <simeon@schaub.rocks>
1 parent 40ecf69 commit 88e2a62

File tree

7 files changed

+79
-23
lines changed

7 files changed

+79
-23
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ New library features
102102
the uniquing checking ([#53474])
103103
* `RegexMatch` objects can now be used to construct `NamedTuple`s and `Dict`s ([#50988])
104104
* `Lockable` is now exported ([#54595])
105+
* New methods `readdir(DirEntry, path::String)` and `readdir(e::DirEntry)` will now return directory contents
106+
along with the type of the entries in a vector of new `DirEntry` objects to provide more efficient `isfile`
107+
etc. checks ([#55358])
105108

106109
Standard library changes
107110
------------------------

base/exports.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,7 @@ export
836836
close,
837837
closewrite,
838838
countlines,
839+
DirEntry,
839840
eachline,
840841
readeach,
841842
eof,

base/file.jl

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export
88
chown,
99
cp,
1010
cptree,
11+
DirEntry,
1112
diskstat,
1213
hardlink,
1314
mkdir,
@@ -949,11 +950,20 @@ const UV_DIRENT_BLOCK = Cint(7)
949950

950951
"""
951952
DirEntry
953+
DirEntry(path::String)
952954
953955
A type representing a filesystem entry that contains the name of the entry, the directory, and
954956
the raw type of the entry. The full path of the entry can be obtained lazily by accessing the
955-
`path` field. The type of the entry can be checked for by calling [`isfile`](@ref), [`isdir`](@ref),
957+
`path` field.
958+
959+
Public fields:
960+
- `dir::String`: The directory containing the entry.
961+
- `name::String`: The name of the entry.
962+
- `path::String`: The full path of the entry, lazily constructed from `dir` and `name`. Also accessible via `joinpath(entry)`.
963+
964+
The type of the entry can be checked for by calling [`isfile`](@ref), [`isdir`](@ref),
956965
[`islink`](@ref), [`isfifo`](@ref), [`issocket`](@ref), [`ischardev`](@ref), and [`isblockdev`](@ref)
966+
on the entry object.
957967
"""
958968
struct DirEntry
959969
dir::String
@@ -983,27 +993,38 @@ isblockdev(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? isblockdev(obj.pat
983993
realpath(obj::DirEntry) = realpath(obj.path)
984994

985995
"""
986-
_readdirx(dir::AbstractString=pwd(); sort::Bool = true) -> Vector{DirEntry}
996+
readdir(::Type{DirEntry}, dir::Union{AbstractString,DirEntry}=pwd(); sort::Bool = true) -> Vector{DirEntry}
997+
readdir(entry::DirEntry; sort::Bool=true) -> Vector{DirEntry}
987998
988999
Return a vector of [`DirEntry`](@ref) objects representing the contents of the directory `dir`,
9891000
or the current working directory if not given. If `sort` is true, the returned vector is
9901001
sorted by name.
9911002
992-
Unlike [`readdir`](@ref), `_readdirx` returns [`DirEntry`](@ref) objects, which contain the name of the
1003+
The [`DirEntry`](@ref) objects that are returned contain the name of the
9931004
file, the directory it is in, and the type of the file which is determined during the
9941005
directory scan. This means that calls to [`isfile`](@ref), [`isdir`](@ref), [`islink`](@ref), [`isfifo`](@ref),
9951006
[`issocket`](@ref), [`ischardev`](@ref), and [`isblockdev`](@ref) can be made on the
9961007
returned objects without further stat calls. However, for some filesystems, the type of the file
9971008
cannot be determined without a stat call. In these cases the `rawtype` field of the [`DirEntry`](@ref))
9981009
object will be 0 (`UV_DIRENT_UNKNOWN`) and [`isfile`](@ref) etc. will fall back to a `stat` call.
9991010
1011+
# Examples
10001012
```julia
1001-
for obj in _readdirx()
1002-
isfile(obj) && println("\$(obj.name) is a file with path \$(obj.path)")
1013+
for entry in readdir(DirEntry, ".")
1014+
if isfile(entry)
1015+
println("\$(entry.name) is a file with path \$(entry.path)")
1016+
continue
1017+
end
1018+
isdir(entry) || continue
1019+
for entry2 in readdir(entry)
1020+
...
1021+
end
10031022
end
10041023
```
10051024
"""
1006-
_readdirx(dir::AbstractString=pwd(); sort::Bool=true) = _readdir(dir; return_objects=true, sort)::Vector{DirEntry}
1025+
readdir(::Type{DirEntry}, dir::AbstractString=pwd(); sort::Bool=true) = _readdir(dir; return_objects=true, sort)::Vector{DirEntry}
1026+
readdir(::Type{DirEntry}, entry::DirEntry; sort::Bool=true) = readdir(entry; sort)::Vector{DirEntry}
1027+
readdir(entry::DirEntry; sort::Bool=true) = readdir(DirEntry, entry.path; sort)::Vector{DirEntry}
10071028

10081029
function _readdir(dir::AbstractString; return_objects::Bool=false, join::Bool=false, sort::Bool=true)
10091030
# Allocate space for uv_fs_t struct
@@ -1093,7 +1114,7 @@ function walkdir(root; topdown=true, follow_symlinks=false, onerror=throw)
10931114
end
10941115
return
10951116
end
1096-
entries = tryf(_readdirx, root)
1117+
entries = tryf(p -> readdir(DirEntry, p), root)
10971118
entries === nothing && return
10981119
dirs = Vector{String}()
10991120
files = Vector{String}()

doc/src/base/file.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Base.Filesystem.pwd
77
Base.Filesystem.cd(::AbstractString)
88
Base.Filesystem.cd(::Function)
99
Base.Filesystem.readdir
10+
Base.Filesystem.DirEntry
1011
Base.Filesystem.walkdir
1112
Base.Filesystem.mkdir
1213
Base.Filesystem.mkpath

stdlib/REPL/src/REPLCompletions.jl

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ using Core: Const
88
const CC = Core.Compiler
99
using Base.Meta
1010
using Base: propertynames, something, IdSet
11-
using Base.Filesystem: _readdirx
1211

1312
abstract type Completion end
1413

@@ -335,7 +334,7 @@ function cache_PATH()
335334
end
336335

337336
path_entries = try
338-
_readdirx(pathdir)
337+
readdir(DirEntry, pathdir)
339338
catch e
340339
# Bash allows dirs in PATH that can't be read, so we should as well.
341340
if isa(e, Base.IOError) || isa(e, Base.ArgumentError)
@@ -398,9 +397,9 @@ function complete_path(path::AbstractString;
398397
end
399398
entries = try
400399
if isempty(dir)
401-
_readdirx()
400+
readdir(DirEntry)
402401
elseif isdir(dir)
403-
_readdirx(dir)
402+
readdir(DirEntry, dir)
404403
else
405404
return Completion[], dir, false
406405
end
@@ -1435,7 +1434,7 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif
14351434
complete_loading_candidates!(suggestions, s, dir)
14361435
end
14371436
isdir(dir) || continue
1438-
for entry in _readdirx(dir)
1437+
for entry in readdir(DirEntry, dir)
14391438
pname = entry.name
14401439
if pname[1] != '.' && pname != "METADATA" &&
14411440
pname != "REQUIRE" && startswith(pname, s)

stdlib/REPL/src/docview.jl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import Base.Docs: doc, formatdoc, parsedoc, apropos
1111

1212
using Base: with_output_color, mapany, isdeprecated, isexported
1313

14-
using Base.Filesystem: _readdirx
15-
1614
using InteractiveUtils: subtypes
1715

1816
using Unicode: normalize
@@ -365,7 +363,7 @@ function find_readme(m::Module)::Union{String, Nothing}
365363
path = dirname(mpath)
366364
top_path = pkgdir(m)
367365
while true
368-
for entry in _readdirx(path; sort=true)
366+
for entry in readdir(DirEntry, path; sort=true)
369367
isfile(entry) && (lowercase(entry.name) in ["readme.md", "readme"]) || continue
370368
return entry.path
371369
end

test/file.jl

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,54 @@ let err = nothing
2626
end
2727
end
2828

29+
@testset "readdir" begin
30+
@test ispath("does/not/exist") == false
31+
@test isdir("does/not/exist") == false
32+
@test_throws Base.IOError readdir("does/not/exist")
33+
@test_throws Base.IOError readdir(DirEntry, "does/not/exist")
34+
35+
mktempdir() do dir
36+
touch(joinpath(dir, "afile.txt"))
37+
mkdir(joinpath(dir, "adir"))
38+
touch(joinpath(dir, "adir", "bfile.txt"))
39+
@test length(readdir(dir)) == 2
40+
@test readdir(dir) == map(e->e.name, readdir(DirEntry, dir))
41+
for p in readdir(dir, join=true)
42+
if isdir(p)
43+
@test only(readdir(p)) == "bfile.txt"
44+
else
45+
@test isfile(p)
46+
@test p == joinpath(dir, "afile.txt")
47+
end
48+
end
49+
for e in readdir(DirEntry, dir)
50+
if isdir(e) || continue
51+
@test only(readdir(e)).name == "bfile.txt"
52+
@test only(readdir(DirEntry, e)).name == "bfile.txt"
53+
else
54+
@test isfile(e)
55+
@test e.name == "afile.txt"
56+
end
57+
end
58+
end
59+
end
60+
2961
if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER
3062
dirlink = joinpath(dir, "dirlink")
3163
symlink(subdir, dirlink)
3264
@test stat(dirlink) == stat(subdir)
3365
@test readdir(dirlink) == readdir(subdir)
34-
@test map(o->o.names, Base.Filesystem._readdirx(dirlink)) == map(o->o.names, Base.Filesystem._readdirx(subdir))
35-
@test realpath.(Base.Filesystem._readdirx(dirlink)) == realpath.(Base.Filesystem._readdirx(subdir))
66+
@test isempty(readdir(DirEntry, dirlink))
67+
@test isempty(readdir(DirEntry, subdir))
3668

3769
# relative link
3870
relsubdirlink = joinpath(subdir, "rel_subdirlink")
3971
reldir = joinpath("..", "adir2")
4072
symlink(reldir, relsubdirlink)
4173
@test stat(relsubdirlink) == stat(subdir2)
4274
@test readdir(relsubdirlink) == readdir(subdir2)
43-
@test Base.Filesystem._readdirx(relsubdirlink) == Base.Filesystem._readdirx(subdir2)
75+
@test isempty(readdir(DirEntry, relsubdirlink))
76+
@test isempty(readdir(DirEntry, subdir2))
4477

4578
# creation of symlink to directory that does not yet exist
4679
new_dir = joinpath(subdir, "new_dir")
@@ -59,7 +92,7 @@ if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER
5992
mkdir(new_dir)
6093
touch(foo_file)
6194
@test readdir(new_dir) == readdir(nedlink)
62-
@test realpath.(Base.Filesystem._readdirx(new_dir)) == realpath.(Base.Filesystem._readdirx(nedlink))
95+
@test realpath.(readdir(DirEntry, new_dir)) == realpath.(readdir(DirEntry, nedlink))
6396

6497
rm(foo_file)
6598
rm(new_dir)
@@ -1444,10 +1477,10 @@ rm(dirwalk, recursive=true)
14441477
touch(randstring())
14451478
end
14461479
@test issorted(readdir())
1447-
@test issorted(Base.Filesystem._readdirx())
1448-
@test map(o->o.name, Base.Filesystem._readdirx()) == readdir()
1449-
@test map(o->o.path, Base.Filesystem._readdirx()) == readdir(join=true)
1450-
@test count(isfile, readdir(join=true)) == count(isfile, Base.Filesystem._readdirx())
1480+
@test issorted(readdir(DirEntry))
1481+
@test map(o->o.name, readdir(DirEntry)) == readdir()
1482+
@test map(o->o.path, readdir(DirEntry)) == readdir(join=true)
1483+
@test count(isfile, readdir(join=true)) == count(isfile, readdir(DirEntry))
14511484
end
14521485
end
14531486
end

0 commit comments

Comments
 (0)