Skip to content

Commit d6abea0

Browse files
IanButterworthtecosaur
authored andcommitted
add _readdirx for returning more object info gathered during dir scan (JuliaLang#53377)
1 parent ca586f8 commit d6abea0

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

9411013
# iterate the listing into entries
942-
entries = String[]
1014+
entries = return_objects ? DirEntry[] : String[]
9431015
ent = Ref{uv_dirent_t}()
9441016
while Base.UV_EOF != ccall(:uv_fs_scandir_next, Cint, (Ptr{Cvoid}, Ptr{uv_dirent_t}), req, ent)
9451017
name = unsafe_string(ent[].name)
946-
push!(entries, join ? joinpath(dir, name) : name)
1018+
if return_objects
1019+
rawtype = ent[].typ
1020+
push!(entries, DirEntry(dir, name, rawtype))
1021+
else
1022+
push!(entries, join ? joinpath(dir, name) : name)
1023+
end
9471024
end
9481025

9491026
# Clean up the request string
@@ -957,8 +1034,6 @@ function readdir(dir::AbstractString; join::Bool=false, sort::Bool=true)
9571034
Libc.free(req)
9581035
end
9591036
end
960-
readdir(; join::Bool=false, sort::Bool=true) =
961-
readdir(join ? pwd() : ".", join=join, sort=sort)
9621037

9631038
"""
9641039
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)
@@ -1441,6 +1445,10 @@ rm(dirwalk, recursive=true)
14411445
touch(randstring())
14421446
end
14431447
@test issorted(readdir())
1448+
@test issorted(Base.Filesystem._readdirx())
1449+
@test map(o->o.name, Base.Filesystem._readdirx()) == readdir()
1450+
@test map(o->o.path, Base.Filesystem._readdirx()) == readdir(join=true)
1451+
@test count(isfile, readdir(join=true)) == count(isfile, Base.Filesystem._readdirx())
14441452
end
14451453
end
14461454
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)