Skip to content

Commit

Permalink
add _readdirx for returning more object info gathered during dir scan (
Browse files Browse the repository at this point in the history
…#53377)

(cherry picked from commit 989c4db)
  • Loading branch information
IanButterworth authored and KristofferC committed Mar 1, 2024
1 parent b701b5d commit 1366e34
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 7 deletions.
85 changes: 80 additions & 5 deletions base/file.jl
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,79 @@ julia> readdir(abspath("base"), join=true)
"/home/JuliaUser/dev/julia/base/weakkeydict.jl"
```
"""
function readdir(dir::AbstractString; join::Bool=false, sort::Bool=true)
readdir(; join::Bool=false, kwargs...) = readdir(join ? pwd() : "."; join, kwargs...)::Vector{String}
readdir(dir::AbstractString; kwargs...) = _readdir(dir; return_objects=false, kwargs...)::Vector{String}

# this might be better as an Enum but they're not available here
# UV_DIRENT_T
const UV_DIRENT_UNKNOWN = Cint(0)
const UV_DIRENT_FILE = Cint(1)
const UV_DIRENT_DIR = Cint(2)
const UV_DIRENT_LINK = Cint(3)
const UV_DIRENT_FIFO = Cint(4)
const UV_DIRENT_SOCKET = Cint(5)
const UV_DIRENT_CHAR = Cint(6)
const UV_DIRENT_BLOCK = Cint(7)

"""
DirEntry
A type representing a filesystem entry that contains the name of the entry, the directory, and
the raw type of the entry. The full path of the entry can be obtained lazily by accessing the
`path` field. The type of the entry can be checked for by calling [`isfile`](@ref), [`isdir`](@ref),
[`islink`](@ref), [`isfifo`](@ref), [`issocket`](@ref), [`ischardev`](@ref), and [`isblockdev`](@ref)
"""
struct DirEntry
dir::String
name::String
rawtype::Cint
end
function Base.getproperty(obj::DirEntry, p::Symbol)
if p === :path
return joinpath(obj.dir, obj.name)
else
return getfield(obj, p)
end
end
Base.propertynames(::DirEntry) = (:dir, :name, :path, :rawtype)
Base.isless(a::DirEntry, b::DirEntry) = a.dir == b.dir ? isless(a.name, b.name) : isless(a.dir, b.dir)
Base.hash(o::DirEntry, h::UInt) = hash(o.dir, hash(o.name, hash(o.rawtype, h)))
Base.:(==)(a::DirEntry, b::DirEntry) = a.name == b.name && a.dir == b.dir && a.rawtype == b.rawtype
joinpath(obj::DirEntry, args...) = joinpath(obj.path, args...)
isunknown(obj::DirEntry) = obj.rawtype == UV_DIRENT_UNKNOWN
islink(obj::DirEntry) = isunknown(obj) ? islink(obj.path) : obj.rawtype == UV_DIRENT_LINK
isfile(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? isfile(obj.path) : obj.rawtype == UV_DIRENT_FILE
isdir(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? isdir(obj.path) : obj.rawtype == UV_DIRENT_DIR
isfifo(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? isfifo(obj.path) : obj.rawtype == UV_DIRENT_FIFO
issocket(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? issocket(obj.path) : obj.rawtype == UV_DIRENT_SOCKET
ischardev(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? ischardev(obj.path) : obj.rawtype == UV_DIRENT_CHAR
isblockdev(obj::DirEntry) = (isunknown(obj) || islink(obj)) ? isblockdev(obj.path) : obj.rawtype == UV_DIRENT_BLOCK
realpath(obj::DirEntry) = realpath(obj.path)

"""
_readdirx(dir::AbstractString=pwd(); sort::Bool = true) -> Vector{DirEntry}
Return a vector of [`DirEntry`](@ref) objects representing the contents of the directory `dir`,
or the current working directory if not given. If `sort` is true, the returned vector is
sorted by name.
Unlike [`readdir`](@ref), `_readdirx` returns [`DirEntry`](@ref) objects, which contain the name of the
file, the directory it is in, and the type of the file which is determined during the
directory scan. This means that calls to [`isfile`](@ref), [`isdir`](@ref), [`islink`](@ref), [`isfifo`](@ref),
[`issocket`](@ref), [`ischardev`](@ref), and [`isblockdev`](@ref) can be made on the
returned objects without further stat calls. However, for some filesystems, the type of the file
cannot be determined without a stat call. In these cases the `rawtype` field of the [`DirEntry`](@ref))
object will be 0 (`UV_DIRENT_UNKNOWN`) and [`isfile`](@ref) etc. will fall back to a `stat` call.
```julia
for obj in _readdirx()
isfile(obj) && println("\$(obj.name) is a file with path \$(obj.path)")
end
```
"""
_readdirx(dir::AbstractString=pwd(); sort::Bool=true) = _readdir(dir; return_objects=true, sort)::Vector{DirEntry}

function _readdir(dir::AbstractString; return_objects::Bool=false, join::Bool=false, sort::Bool=true)
# Allocate space for uv_fs_t struct
req = Libc.malloc(_sizeof_uv_fs)
try
Expand All @@ -923,11 +995,16 @@ function readdir(dir::AbstractString; join::Bool=false, sort::Bool=true)
err < 0 && uv_error("readdir($(repr(dir)))", err)

# iterate the listing into entries
entries = String[]
entries = return_objects ? DirEntry[] : String[]
ent = Ref{uv_dirent_t}()
while Base.UV_EOF != ccall(:uv_fs_scandir_next, Cint, (Ptr{Cvoid}, Ptr{uv_dirent_t}), req, ent)
name = unsafe_string(ent[].name)
push!(entries, join ? joinpath(dir, name) : name)
if return_objects
rawtype = ent[].typ
push!(entries, DirEntry(dir, name, rawtype))
else
push!(entries, join ? joinpath(dir, name) : name)
end
end

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

"""
walkdir(dir; topdown=true, follow_symlinks=false, onerror=throw)
Expand Down
8 changes: 8 additions & 0 deletions test/file.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,16 @@ if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER
symlink(subdir, dirlink)
@test stat(dirlink) == stat(subdir)
@test readdir(dirlink) == readdir(subdir)
@test map(o->o.names, Base.Filesystem._readdirx(dirlink)) == map(o->o.names, Base.Filesystem._readdirx(subdir))
@test realpath.(Base.Filesystem._readdirx(dirlink)) == realpath.(Base.Filesystem._readdirx(subdir))

# relative link
relsubdirlink = joinpath(subdir, "rel_subdirlink")
reldir = joinpath("..", "adir2")
symlink(reldir, relsubdirlink)
@test stat(relsubdirlink) == stat(subdir2)
@test readdir(relsubdirlink) == readdir(subdir2)
@test Base.Filesystem._readdirx(relsubdirlink) == Base.Filesystem._readdirx(subdir2)

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

rm(foo_file)
rm(new_dir)
Expand Down Expand Up @@ -1438,6 +1442,10 @@ rm(dirwalk, recursive=true)
touch(randstring())
end
@test issorted(readdir())
@test issorted(Base.Filesystem._readdirx())
@test map(o->o.name, Base.Filesystem._readdirx()) == readdir()
@test map(o->o.path, Base.Filesystem._readdirx()) == readdir(join=true)
@test count(isfile, readdir(join=true)) == count(isfile, Base.Filesystem._readdirx())
end
end
end
Expand Down
5 changes: 3 additions & 2 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1363,9 +1363,10 @@ end
@test isdefined(KwdefWithEsc_TestModule, :Struct)

@testset "exports of modules" begin
for (_, mod) in Base.loaded_modules
@testset "$mod" for (_, mod) in Base.loaded_modules
mod === Main && continue # Main exports everything
for v in names(mod)
@testset "$v" for v in names(mod)
isdefined(mod, v) || @error "missing $v in $mod"
@test isdefined(mod, v)
end
end
Expand Down

0 comments on commit 1366e34

Please sign in to comment.