-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathScanDir.jl
121 lines (101 loc) · 4.25 KB
/
ScanDir.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
module ScanDir
export scandir, scandirtree, DirEntry
import Base.Filesystem: uv_dirent_t
import Base: uv_error, _sizeof_uv_fs
module PosixFileTypes
@enum PosixFileType Unknown File Directory Link FIFO Socket CharDev BlockDev
end
using .PosixFileTypes: PosixFileType
struct DirEntry
name::String
path::String
type::PosixFileType
end
function Base.show(io::IO, ::MIME"text/plain", e::DirEntry)
print(io, "<", lowercase(string(e.type)), " ", repr(e.path), ">")
end
_islink(e::DirEntry) = e.type == PosixFileTypes.Link
for (i,s) in enumerate((:isfile, :isdir, :islink, :isfifo, :issocket, :ischardev, :isblockdev))
@eval Base.Filesystem.$s(e::DirEntry) = e.type == PosixFileType($i) || _islink(e) && $s(e.path)
end
filename(e::DirEntry) = e.name
# Implementation copied from Base.readdir and modified to return DirEntry's
function scandir(dir::AbstractString="."; sort=true)
# Allocate space for uv_fs_t struct
req = Libc.malloc(_sizeof_uv_fs)
try
# defined in sys.c, to call uv_fs_readdir, which sets errno on error.
err = ccall(:uv_fs_scandir, Int32, (Ptr{Cvoid}, Ptr{Cvoid}, Cstring, Cint, Ptr{Cvoid}),
C_NULL, req, dir, 0, C_NULL)
err < 0 && uv_error("readdir($(repr(dir)))", err)
# iterate the listing into entries
entries = DirEntry[]
ent = Ref{uv_dirent_t}()
while Base.UV_EOF != ccall(:uv_fs_scandir_next, Cint, (Ptr{Cvoid}, Ptr{uv_dirent_t}), req, ent)
ent_name = unsafe_string(ent[].name)
ent_path = joinpath(dir, ent_name)
push!(entries, DirEntry(ent_name, ent_path, PosixFileType(ent[].typ)))
end
# Clean up the request string
# on newer julia versions this can be: Base.Filesystem.uv_fs_req_cleanup(req)
ccall(:uv_fs_req_cleanup, Cvoid, (Ptr{Cvoid},), req)
# sort entries unless opted out
sort && sort!(entries; by=filename)
return entries
finally
Libc.free(req)
end
end
_channel_try_io(f, channel, onerror) = try
f()
catch err
isa(err, Base.IOError) || rethrow()
try
onerror(err)
catch err2
close(channel, err2)
end
return
end
# Implementation copied from Base.walkdir and modified to use scandir,
# to avoid unnecessary stat()ing
function scandirtree(root="."; topdown=true, follow_symlinks=false, onerror=throw, prune=_->false)
function _scandirtree(chnl, root)
isfilelike(e) = (!follow_symlinks && islink(e)) || !isdir(e)
tryf(f, p) = _channel_try_io(()->f(p), chnl, onerror)
content = tryf(scandir, root)
content === nothing && return
dirs = DirEntry[]
files = DirEntry[]
for entry in content
prune(entry) && continue
filelike = tryf(isfilelike, entry)
filelike === nothing && return
push!(filelike ? files : dirs, entry)
end
if topdown
push!(chnl, (; root, dirs, files))
end
for dir in dirs
_scandirtree(chnl, joinpath(root, dir.name))
end
if !topdown
push!(chnl, (; root, dirs, files))
end
nothing
end
TreeEntry = NamedTuple{(:root, :dirs, :files), Tuple{String, Vector{DirEntry}, Vector{DirEntry}}}
return Channel{TreeEntry}(chnl -> _scandirtree(chnl, root))
end
function walkdir(root="."; topdown=true, follow_symlinks=false, onerror=throw, prune=_->false)
scan_channel = scandirtree(root; topdown, follow_symlinks, onerror, prune)
WalkdirEntry = NamedTuple{(:root, :dirs, :files), Tuple{String, Vector{String}, Vector{String}}}
return Channel{WalkdirEntry}() do channel
_channel_try_io(channel, onerror) do
for (root, dirs, files) in scan_channel
push!(channel, (; root, dirs = [e.name for e in dirs], files = [e.name for e in files]))
end
end
end
end
end # module