Skip to content

Commit

Permalink
add readdirx which returns more object info from dir scan
Browse files Browse the repository at this point in the history
Co-Authored-By: Cody Tapscott <84105208+topolarity@users.noreply.github.com>
  • Loading branch information
IanButterworth and topolarity committed Feb 29, 2024
1 parent 77c0672 commit 007241e
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 5 deletions.
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ New library functions
---------------------

* `logrange(start, stop; length)` makes a range of constant ratio, instead of constant step ([#39071])
* `readdirx` for returning directory contents along with the type of the entries in a vector of new `DirEntry`
objects ([#53377])

New library features
--------------------
Expand Down
2 changes: 2 additions & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,7 @@ export
eof,
fd,
fdio,
DirEntry,
flush,
gethostname,
htol,
Expand All @@ -865,6 +866,7 @@ export
readbytes!,
readchomp,
readdir,
readdirx,
readline,
readlines,
readuntil,
Expand Down
86 changes: 81 additions & 5 deletions base/file.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export
rename,
readlink,
readdir,
readdirx,
rm,
samefile,
sendfile,
Expand All @@ -29,6 +30,7 @@ export
unlink,
walkdir


# get and set current directory

"""
Expand Down Expand Up @@ -929,7 +931,78 @@ 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

"""
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 @@ -939,11 +1012,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 +1035,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
2 changes: 2 additions & 0 deletions doc/src/base/file.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Base.Filesystem.pwd
Base.Filesystem.cd(::AbstractString)
Base.Filesystem.cd(::Function)
Base.Filesystem.readdir
Base.Filesystem.readdirx
Base.Filesystem.DirEntry
Base.Filesystem.walkdir
Base.Filesystem.mkdir
Base.Filesystem.mkpath
Expand Down
7 changes: 7 additions & 0 deletions test/file.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER
symlink(subdir, dirlink)
@test stat(dirlink) == stat(subdir)
@test readdir(dirlink) == readdir(subdir)
@test readdirx(dirlink) == 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 readdirx(relsubdirlink) == readdirx(subdir2)

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

rm(foo_file)
rm(new_dir)
Expand Down Expand Up @@ -1441,6 +1444,10 @@ rm(dirwalk, recursive=true)
touch(randstring())
end
@test issorted(readdir())
@test issorted(readdirx())
@test map(o->o.name, readdirx()) == readdir()
@test map(o->o.path, readdirx()) == readdir(join=true)
@test count(isfile, readdir(join=true)) == count(isfile, readdirx())
end
end
end
Expand Down

0 comments on commit 007241e

Please sign in to comment.