Skip to content

Commit d3fa718

Browse files
committed
Implement PathStruct and pass to _ishidden functions
Instead of keeping track of both the file given by the user _and_ the real path of the file (which some internal functions require, and others necessarily don't), we can construct a PathStruct object and pass that around internally. This also allows us to perform error checking within the PathStruct object, rather than in the ishidden function. Related: #19. Still to do: - Write documentation for PathStruct and InvaliidRealPathError - Implement further tests - Refine error checking for PathStruct constructor with method PathStruct(path::AbstractString, rp::AbstractString)
1 parent 2d4a64e commit d3fa718

3 files changed

Lines changed: 83 additions & 30 deletions

File tree

src/HiddenFiles.jl

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,20 @@ On Unix-like systems, a file or directory is hidden if it starts with a full sto
2525
ishidden
2626

2727
include("docs.jl")
28+
include("path.jl")
2829

2930
@static if Sys.isunix()
3031
include("utils/zfs.jl")
3132
if iszfs() # @static breaks here # ZFS
3233
error("not yet implemented")
33-
_ishidden_zfs(f::AbstractString, rp::AbstractString) = error("not yet implemented")
34+
_ishidden_zfs(ps::PathStruct) = error("not yet implemented")
3435
_ishidden = _ishidden_zfs
3536
end
3637

3738
# Trivial Unix check
3839
_isdotfile(f::AbstractString) = startswith(basename(f), '.')
3940
# Check dotfiles, but also account for ZFS
40-
_ishidden_unix(f::AbstractString, rp::AbstractString) = _isdotfile(rp) || (iszfs() && _ishidden_zfs("", ""))
41+
_ishidden_unix(ps::PathStruct) = _isdotfile(ps.realpath) || (iszfs() && _ishidden_zfs("", ""))
4142

4243
@static if Sys.isbsd() # BDS-related; this is true for macOS as well
4344
# https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/chflags.2.html
@@ -64,7 +65,7 @@ include("docs.jl")
6465
# https://github.com/davidkaya/corefx/blob/4fd3d39f831f3e14f311b0cdc0a33d662e684a9c/src/System.IO.FileSystem/src/System/IO/FileStatus.Unix.cs#L88
6566
_isinvisible(f::AbstractString) = (_st_flags(f) & UF_HIDDEN) == UF_HIDDEN
6667

67-
_ishidden_bsd_related(f::AbstractString, rp::AbstractString) = _ishidden_unix(f, rp) || _isinvisible(rp)
68+
_ishidden_bsd_related(ps::PathStruct) = _ishidden_unix(ps) || _isinvisible(ps.realpath)
6869
end
6970

7071
@static if Sys.isapple() # macOS/Darwin
@@ -157,10 +158,10 @@ include("docs.jl")
157158

158159

159160
#=== All macOS cases ===#
160-
_ishidden_macos(f::AbstractString, rp::AbstractString) = _ishidden_bsd_related(f, rp) || _issystemfile(f) || _exists_inside_package_or_bundle(rp)
161+
_ishidden_macos(ps::PathStruct) = _ishidden_bsd_related(ps) || _issystemfile(ps.path) || _exists_inside_package_or_bundle(ps.realpath)
161162
_ishidden = _ishidden_macos
162163
elseif Sys.isbsd() # BSD; this excludes macOS through control flow (as macOS is checked for first)
163-
_ishidden_bsd(f::AbstractString, rp::AbstractString) = _ishidden_bsd_related(f, rp)
164+
_ishidden_bsd(ps::PathStruct) = _ishidden_bsd_related(ps)
164165
_ishidden = _ishidden_bsd
165166
else # General UNIX
166167
_ishidden = _ishidden_unix
@@ -172,10 +173,10 @@ elseif Sys.iswindows()
172173
const FILE_ATTRIBUTE_HIDDEN = 0x2
173174
const FILE_ATTRIBUTE_SYSTEM = 0x4
174175

175-
function _ishidden_windows(f::AbstractString, rp::AbstractString)
176+
function _ishidden_windows(ps::PathStruct)
176177
# https://docs.microsoft.com/en-gb/windows/win32/api/fileapi/nf-fileapi-getfileattributesa
177178
# DWORD GetFileAttributesA([in] LPCSTR lpFileName);
178-
f_attrs = ccall(:GetFileAttributesA, UInt32, (Cstring,), rp)
179+
f_attrs = ccall(:GetFileAttributesA, UInt32, (Cstring,), ps.realpath)
179180

180181
# https://stackoverflow.com/a/1343643/12069968
181182
# https://stackoverflow.com/a/14063074/12069968
@@ -187,29 +188,11 @@ else
187188
end
188189

189190

190-
# Each OS branch defines its own _ishidden function. In the main ishidden function, we check that the path exists, expand
191-
# the real path out, and apply the branch's _ishidden function to that path to get a final result
191+
# Each OS branch defines its own _ishidden function. In the main ishidden function, we check construct
192+
# our PathStruct object to pass around to the branch's _ishidden function to use as the function necessitates
192193
function ishidden(f::AbstractString)
193-
# If path does not exist, `realpath` will error™
194-
local rp::String
195-
try
196-
rp = realpath(f)
197-
catch e
198-
err_prexif = "ishidden($(repr(f)))"
199-
# Julia < 1.3 throws a SystemError when `realpath` fails
200-
isa(e, SystemError) && throw(SystemError(err_prexif, e.errnum))
201-
# Julia ≥ 1.3 throws an IOError, constructed from UV Error codes
202-
isa(e, Base.IOError) && throw(Base.uv_error(err_prexif, e.code))
203-
# If this fails for some other reason, rethrow
204-
rethrow()
205-
end
206-
207-
# Julia < 1.2 on Windows does not error on `realpath` if path does not exist, so we
208-
# must do so manually here
209-
ispath(rp) || throw(Base.uv_error("ishidden($(repr(f)))", Base.UV_ENOENT))
210-
211-
# If we got here, the path exists, and we can continue safely with our _ishidden checks
212-
return _ishidden(f, rp)
194+
ps = PathStruct(f; err_prefix = :ishidden)
195+
return _ishidden(ps)
213196
end
214197

215198

src/path.jl

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
struct InvalidRealPathError <: Exception
2+
msg::String
3+
expected::AbstractString
4+
actual::AbstractString
5+
end
6+
7+
function Base.showerror(io::IO, e::InvalidRealPathError)
8+
print(io, typeof(e), ": ", e.msg, ": ")
9+
print(io, "Invalid real path: expected ", '"', e.expected, '"', ", ")
10+
print(io, "found ", '"', e.actual, '"')
11+
end
12+
13+
struct PathStruct
14+
path::AbstractString
15+
realpath::AbstractString
16+
17+
function PathStruct(path::AbstractString, rp::AbstractString)
18+
ispath(rp) || throw(Base.uv_error("PathStruct($(repr(path)))", Base.UV_ENOENT))
19+
# TODO: this will fail if path is not valid
20+
realpath(path) == rp || throw(InvalidRealPathError("PathStruct($(repr(path)))", realpath(path), rp))
21+
return new(path, rp)
22+
end
23+
24+
# Each OS branch defines its own _ishidden functions, some of which require the user-provided path, and some of
25+
# which require a real path. To easily maintain both of these, we pass around a PathStruct containing both
26+
# information. If PathStruct is constructed with one positional argument, it attempts to construct the real path
27+
# of the file (and will error with an IOError or SystemError if it fails).
28+
function PathStruct(path::AbstractString; err_prefix::Symbol = :ishidden)
29+
# If path does not exist, `realpath` will error™
30+
local rp::String
31+
try
32+
rp = realpath(path)
33+
catch e
34+
err_prexif = "$(err_prefix)(PathStruct($(repr(path))))"
35+
# Julia < 1.3 throws a SystemError when `realpath` fails
36+
isa(e, SystemError) && throw(SystemError(err_prexif, e.errnum))
37+
# Julia ≥ 1.3 throws an IOError, constructed from UV Error codes
38+
isa(e, Base.IOError) && throw(Base.uv_error(err_prexif, e.code))
39+
# If this fails for some other reason, rethrow
40+
rethrow()
41+
end
42+
43+
# Julia < 1.2 on Windows does not error on `realpath` if path does not exist, so we
44+
# must do so manually here
45+
ispath(rp) || throw(Base.uv_error("$(err_prefix)(PathStruct($(repr(path))))", Base.UV_ENOENT))
46+
47+
# If we got here, the path exists, and we can continue safely construct our PathStruct
48+
# for our _ishidden tests
49+
return new(path, rp)
50+
end
51+
end
52+

test/runtests.jl

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,27 @@ using Test
105105
end
106106

107107

108-
@testset "HiddenFiles.jl—Path Handling" begin
108+
@testset "HiddenFiles.jl—Path Handling (PathStruct)" begin
109+
@static if Sys.isunix()
110+
@test HiddenFiles.PathStruct("/bin", "/bin") isa HiddenFiles.PathStruct
111+
@test HiddenFiles.PathStruct("/../bin", "/bin") isa HiddenFiles.PathStruct
112+
@test_throws HiddenFiles.InvalidRealPathError HiddenFiles.PathStruct("/bin", "/../bin")
113+
114+
115+
elseif Sys.iswindows()
116+
@test HiddenFiles.PathStruct("C:\\", "C:\\") isa HiddenFiles.PathStruct
117+
@test HiddenFiles.PathStruct("C:\\..\\", "C:\\") isa HiddenFiles.PathStruct
118+
@test_throws HiddenFiles.InvalidRealPathError HiddenFiles.PathStruct("C:\\", "C:\\..\\")
119+
else
120+
# TODO
121+
@test false
122+
end
123+
109124
f = randpath()
110125
# Julia < 1.3 throws a SystemError when `realpath` fails
126+
@test_throws Union{Base.IOError, SystemError} HiddenFiles.PathStruct(f)
127+
@test_throws Union{Base.IOError, SystemError} HiddenFiles.PathStruct(f, "")
128+
# ishidden calls to PathStruct
111129
@test_throws Union{Base.IOError, SystemError} ishidden(f)
112130
end
113131
end

0 commit comments

Comments
 (0)