Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add _readdirx for returning more object info gathered during dir scan #53377

Merged
merged 2 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 80 additions & 5 deletions base/file.jl
Original file line number Diff line number Diff line change
Expand Up @@ -929,7 +929,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
IanButterworth marked this conversation as resolved.
Show resolved Hide resolved
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)
IanButterworth marked this conversation as resolved.
Show resolved Hide resolved
# Allocate space for uv_fs_t struct
req = Libc.malloc(_sizeof_uv_fs)
try
Expand All @@ -939,11 +1011,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 @@ -957,8 +1034,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 @@ -1441,6 +1445,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
topolarity marked this conversation as resolved.
Show resolved Hide resolved
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