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

readdir: add join option to join dir with names #33113

Merged
merged 2 commits into from
Aug 30, 2019
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
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ New library functions
* The `splitpath` function now accepts any `AbstractString` whereas previously it only accepted paths of type `String` ([#33012]).
* The `tempname` function now takes an optional `parent::AbstractString` argument to give it a directory in which to attempt to produce a temporary path name ([#33090]).
* The `tempname` function now takes a `cleanup::Bool` keyword argument defaulting to `true`, which causes the process to try to ensure that any file or directory at the path returned by `tempname` is deleted upon process exit ([#33090]).
* The `readdir` function now takes a `join::Bool` keyword argument defaulting to `false`, which when set causes `readdir` to join its directory argument with each listed name ([#33113]).


Standard library changes
Expand Down
74 changes: 59 additions & 15 deletions base/file.jl
Original file line number Diff line number Diff line change
Expand Up @@ -671,41 +671,87 @@ struct uv_dirent_t
end

"""
readdir(dir::AbstractString=".") -> Vector{String}
readdir(dir::AbstractString=pwd(); join::Bool=true) -> Vector{String}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed join=true to join=false (skipping making a PR hoping it would save CI some work, but CI got triggered anyway...)
Also, why did you choose pwd() for dir's default? I guess it makes readdir(join=true) more useful, as otherwise it would be quite equivalent to readdir() (modulo the leading dot), but I think I would still expect . rather than pwd() as the default, as is the case for quite many command line tools.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, why did you choose pwd() for dir's default? I guess it makes readdir(join=true) more useful, as otherwise it would be quite equivalent to readdir() (modulo the leading dot), but I think I would still expect . rather than pwd() as the default, as is the case for quite many command line tools.

Yes, I did that so that readdir(join=true) would return the absolute paths of everything in the current directory. What difference does using "." versus pwd() for the default make? The only case I can think of where it would matter is if the current working directory has been deleted. Then readdir(".") might work but pwd() would fail. Yeah, now that I write that (and tested the difference), I guess that's what we should do: use "." as the default when join is false and pwd() as the default when join is true.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What difference does using "." versus pwd() for the default make?

I hadn't even thought about deleted directories, but I meant that I would expect readdir(join=true) to return relative entries of the form ./this.txt. But I'm not convinced either way actually, because it's probably way more common to want the absolute path when you ask join=true.

Copy link
Member Author

@StefanKarpinski StefanKarpinski Sep 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning a bunch of names with ./ prefixed is pretty useless, whereas returning the absolute paths of all the files in the current directory is useful. If you really want the former, you can always call it as readdir(".", join=true). If you haven't asked for join=true then the only difference between pwd() and "." as a default is the deleted current directory thing, which I'm working on a PR to fix.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must admit to not really understand why I argued this point, but I'm glad it allowed you to find the "deleted directory" corner case :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had already thought of it but found it too obscure to bother with, but why not be as thorough as possible: #33175.


Return the files and directories in the directory `dir` (or the current working directory if not given).
Return the names in the directory `dir` or the current working directory if not
given. When `join` is false, `readdir` returns just the names in the directory
as is; when `join` is true, it returns `joinpath(dir, name)` for each `name` so
that the returned strings are full paths. If you want to get absolute paths
back, call `readdir` with an absolute directory path and `join` set to true.

!!! compat "Julia 1.4"
The `join` keyword argument requires at least Julia 1.4.

# Examples
```julia-repl
julia> readdir("/home/JuliaUser/Projects/julia")
34-element Array{String,1}:
".circleci"
".freebsdci.sh"
julia> cd("/home/JuliaUser/dev/julia")

julia> readdir()
30-element Array{String,1}:
".appveyor.yml"
".git"
".gitattributes"
".github"
"test"
"ui"
"usr"
"usr-staging"

julia> readdir(join=true)
30-element Array{String,1}:
"/home/JuliaUser/dev/julia/.appveyor.yml"
"/home/JuliaUser/dev/julia/.git"
"/home/JuliaUser/dev/julia/.gitattributes"
"/home/JuliaUser/dev/julia/ui"
"/home/JuliaUser/dev/julia/usr"
"/home/JuliaUser/dev/julia/usr-staging"

julia> readdir("base")
145-element Array{String,1}:
".gitignore"
"Base.jl"
"Enums.jl"
"version_git.sh"
"views.jl"
"weakkeydict.jl"

julia> readdir("base", join=true)
145-element Array{String,1}:
"base/.gitignore"
"base/Base.jl"
"base/Enums.jl"
"base/version_git.sh"
"base/views.jl"
"base/weakkeydict.jl"```

julia> readdir(abspath("base"), join=true)
145-element Array{String,1}:
"/home/JuliaUser/dev/julia/base/.gitignore"
"/home/JuliaUser/dev/julia/base/Base.jl"
"/home/JuliaUser/dev/julia/base/Enums.jl"
"/home/JuliaUser/dev/julia/base/version_git.sh"
"/home/JuliaUser/dev/julia/base/views.jl"
"/home/JuliaUser/dev/julia/base/weakkeydict.jl"
```
"""
function readdir(path::AbstractString)
function readdir(dir::AbstractString=pwd(); join::Bool=false)
# Allocate space for uv_fs_t struct
uv_readdir_req = zeros(UInt8, ccall(:jl_sizeof_uv_fs_t, Int32, ()))

# defined in sys.c, to call uv_fs_readdir, which sets errno on error.
err = ccall(:uv_fs_scandir, Int32, (Ptr{Cvoid}, Ptr{UInt8}, Cstring, Cint, Ptr{Cvoid}),
C_NULL, uv_readdir_req, path, 0, C_NULL)
err < 0 && throw(SystemError("unable to read directory $path", -err))
#uv_error("unable to read directory $path", err)
C_NULL, uv_readdir_req, dir, 0, C_NULL)
err < 0 && throw(SystemError("unable to read directory $dir", -err))

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

# Clean up the request string
Expand All @@ -714,8 +760,6 @@ function readdir(path::AbstractString)
return entries
end

readdir() = readdir(".")

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

Expand Down
28 changes: 28 additions & 0 deletions test/file.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1404,3 +1404,31 @@ end
@test sizeof(basename(tmpdir)) == 6
end
end

@testset "readir tests" begin
≛(a, b) = sort(a) == sort(b)
mktempdir() do dir
d = cd(pwd, dir) # might resolve symlinks
@test isempty(readdir(d))
@test isempty(readdir(d, join=true))
cd(d) do
@test isempty(readdir())
@test isempty(readdir(join=true))
end
touch(joinpath(d, "file"))
mkdir(joinpath(d, "dir"))
names = ["dir", "file"]
paths = [joinpath(d, x) for x in names]
@test readdir(d) ≛ names
@test readdir(d, join=true) ≛ paths
cd(d) do
@test readdir() ≛ names
@test readdir(join=true) ≛ paths
end
t, b = splitdir(d)
cd(t) do
@test readdir(b) ≛ names
@test readdir(b, join=true) ≛ [joinpath(b, x) for x in names]
end
end
end