Skip to content

Commit 1366e34

Browse files
IanButterworthKristofferC
authored andcommitted
add _readdirx for returning more object info gathered during dir scan (#53377)
(cherry picked from commit 989c4db)
1 parent b701b5d commit 1366e34

File tree

3 files changed

+91
-7
lines changed

3 files changed

+91
-7
lines changed

base/file.jl

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -913,7 +913,79 @@ julia> readdir(abspath("base"), join=true)
913913
"/home/JuliaUser/dev/julia/base/weakkeydict.jl"
914914
```
915915
"""
916-
function readdir(dir::AbstractString; join::Bool=false, sort::Bool=true)
916+
readdir(; join::Bool=false, kwargs...) = readdir(join ? pwd() : "."; join, kwargs...)::Vector{String}
917+
readdir(dir::AbstractString; kwargs...) = _readdir(dir; return_objects=false, kwargs...)::Vector{String}
918+
919+
# this might be better as an Enum but they're not available here
920+
# UV_DIRENT_T
921+
const UV_DIRENT_UNKNOWN = Cint(0)
922+
const UV_DIRENT_FILE = Cint(1)
923+
const UV_DIRENT_DIR = Cint(2)
924+
const UV_DIRENT_LINK = Cint(3)
925+
const UV_DIRENT_FIFO = Cint(4)
926+
const UV_DIRENT_SOCKET = Cint(5)
927+
const UV_DIRENT_CHAR = Cint(6)
928+
const UV_DIRENT_BLOCK = Cint(7)
929+
930+
"""
931+
DirEntry
932+
933+
A type representing a filesystem entry that contains the name of the entry, the directory, and
934+
the raw type of the entry. The full path of the entry can be obtained lazily by accessing the
935+
`path` field. The type of the entry can be checked for by calling [`isfile`](@ref), [`isdir`](@ref),
936+
[`islink`](@ref), [`isfifo`](@ref), [`issocket`](@ref), [`ischardev`](@ref), and [`isblockdev`](@ref)
937+
"""
938+
struct DirEntry
939+
dir::String
940+
name::String
941+
rawtype::Cint
942+
end
943+
function Base.getproperty(obj::DirEntry, p::Symbol)
944+
if p === :path
945+
return joinpath(obj.dir, obj.name)
946+
else
947+
return getfield(obj, p)
948+
end
949+
end
950+
Base.propertynames(::DirEntry) = (:dir, :name, :path, :rawtype)
951+
Base.isless(a::DirEntry, b::DirEntry) = a.dir == b.dir ? isless(a.name, b.name) : isless(a.dir, b.dir)
952+
Base.hash(o::DirEntry, h::UInt) = hash(o.dir, hash(o.name, hash(o.rawtype, h)))
953+
Base.:(==)(a::DirEntry, b::DirEntry) = a.name == b.name && a.dir == b.dir && a.rawtype == b.rawtype
954+
joinpath(obj::DirEntry, args...) = joinpath(obj.path, args...)
955+
isunknown(obj::DirEntry) = obj.rawtype == UV_DIRENT_UNKNOWN
956+
islink(obj::DirEntry) = isunknown(obj) ? islink(obj.path) : obj.rawtype == UV_DIRENT_LINK
957+
isfile(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? isfile(obj.path) : obj.rawtype == UV_DIRENT_FILE
958+
isdir(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? isdir(obj.path) : obj.rawtype == UV_DIRENT_DIR
959+
isfifo(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? isfifo(obj.path) : obj.rawtype == UV_DIRENT_FIFO
960+
issocket(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? issocket(obj.path) : obj.rawtype == UV_DIRENT_SOCKET
961+
ischardev(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? ischardev(obj.path) : obj.rawtype == UV_DIRENT_CHAR
962+
isblockdev(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? isblockdev(obj.path) : obj.rawtype == UV_DIRENT_BLOCK
963+
realpath(obj::DirEntry) = realpath(obj.path)
964+
965+
"""
966+
_readdirx(dir::AbstractString=pwd(); sort::Bool = true) -> Vector{DirEntry}
967+
968+
Return a vector of [`DirEntry`](@ref) objects representing the contents of the directory `dir`,
969+
or the current working directory if not given. If `sort` is true, the returned vector is
970+
sorted by name.
971+
972+
Unlike [`readdir`](@ref), `_readdirx` returns [`DirEntry`](@ref) objects, which contain the name of the
973+
file, the directory it is in, and the type of the file which is determined during the
974+
directory scan. This means that calls to [`isfile`](@ref), [`isdir`](@ref), [`islink`](@ref), [`isfifo`](@ref),
975+
[`issocket`](@ref), [`ischardev`](@ref), and [`isblockdev`](@ref) can be made on the
976+
returned objects without further stat calls. However, for some filesystems, the type of the file
977+
cannot be determined without a stat call. In these cases the `rawtype` field of the [`DirEntry`](@ref))
978+
object will be 0 (`UV_DIRENT_UNKNOWN`) and [`isfile`](@ref) etc. will fall back to a `stat` call.
979+
980+
```julia
981+
for obj in _readdirx()
982+
isfile(obj) && println("\$(obj.name) is a file with path \$(obj.path)")
983+
end
984+
```
985+
"""
986+
_readdirx(dir::AbstractString=pwd(); sort::Bool=true) = _readdir(dir; return_objects=true, sort)::Vector{DirEntry}
987+
988+
function _readdir(dir::AbstractString; return_objects::Bool=false, join::Bool=false, sort::Bool=true)
917989
# Allocate space for uv_fs_t struct
918990
req = Libc.malloc(_sizeof_uv_fs)
919991
try
@@ -923,11 +995,16 @@ function readdir(dir::AbstractString; join::Bool=false, sort::Bool=true)
923995
err < 0 && uv_error("readdir($(repr(dir)))", err)
924996

925997
# iterate the listing into entries
926-
entries = String[]
998+
entries = return_objects ? DirEntry[] : String[]
927999
ent = Ref{uv_dirent_t}()
9281000
while Base.UV_EOF != ccall(:uv_fs_scandir_next, Cint, (Ptr{Cvoid}, Ptr{uv_dirent_t}), req, ent)
9291001
name = unsafe_string(ent[].name)
930-
push!(entries, join ? joinpath(dir, name) : name)
1002+
if return_objects
1003+
rawtype = ent[].typ
1004+
push!(entries, DirEntry(dir, name, rawtype))
1005+
else
1006+
push!(entries, join ? joinpath(dir, name) : name)
1007+
end
9311008
end
9321009

9331010
# Clean up the request string
@@ -941,8 +1018,6 @@ function readdir(dir::AbstractString; join::Bool=false, sort::Bool=true)
9411018
Libc.free(req)
9421019
end
9431020
end
944-
readdir(; join::Bool=false, sort::Bool=true) =
945-
readdir(join ? pwd() : ".", join=join, sort=sort)
9461021

9471022
"""
9481023
walkdir(dir; topdown=true, follow_symlinks=false, onerror=throw)

test/file.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,16 @@ if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER
3131
symlink(subdir, dirlink)
3232
@test stat(dirlink) == stat(subdir)
3333
@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))
3436

3537
# relative link
3638
relsubdirlink = joinpath(subdir, "rel_subdirlink")
3739
reldir = joinpath("..", "adir2")
3840
symlink(reldir, relsubdirlink)
3941
@test stat(relsubdirlink) == stat(subdir2)
4042
@test readdir(relsubdirlink) == readdir(subdir2)
43+
@test Base.Filesystem._readdirx(relsubdirlink) == Base.Filesystem._readdirx(subdir2)
4144

4245
# creation of symlink to directory that does not yet exist
4346
new_dir = joinpath(subdir, "new_dir")
@@ -56,6 +59,7 @@ if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER
5659
mkdir(new_dir)
5760
touch(foo_file)
5861
@test readdir(new_dir) == readdir(nedlink)
62+
@test realpath.(Base.Filesystem._readdirx(new_dir)) == realpath.(Base.Filesystem._readdirx(nedlink))
5963

6064
rm(foo_file)
6165
rm(new_dir)
@@ -1438,6 +1442,10 @@ rm(dirwalk, recursive=true)
14381442
touch(randstring())
14391443
end
14401444
@test issorted(readdir())
1445+
@test issorted(Base.Filesystem._readdirx())
1446+
@test map(o->o.name, Base.Filesystem._readdirx()) == readdir()
1447+
@test map(o->o.path, Base.Filesystem._readdirx()) == readdir(join=true)
1448+
@test count(isfile, readdir(join=true)) == count(isfile, Base.Filesystem._readdirx())
14411449
end
14421450
end
14431451
end

test/misc.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1363,9 +1363,10 @@ end
13631363
@test isdefined(KwdefWithEsc_TestModule, :Struct)
13641364

13651365
@testset "exports of modules" begin
1366-
for (_, mod) in Base.loaded_modules
1366+
@testset "$mod" for (_, mod) in Base.loaded_modules
13671367
mod === Main && continue # Main exports everything
1368-
for v in names(mod)
1368+
@testset "$v" for v in names(mod)
1369+
isdefined(mod, v) || @error "missing $v in $mod"
13691370
@test isdefined(mod, v)
13701371
end
13711372
end

0 commit comments

Comments
 (0)