Skip to content

Commit 3812bc1

Browse files
authored
Merge pull request #82 from rofinn/rf/parsing
Replace `ispathtype` and string constructor with `tryparse`
2 parents f40a9b9 + e70e784 commit 3812bc1

File tree

8 files changed

+69
-64
lines changed

8 files changed

+69
-64
lines changed

src/FilePathsBase.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ export isexecutable
7171
const PATH_TYPES = DataType[]
7272

7373
function __init__()
74-
register(PosixPath)
75-
register(WindowsPath)
74+
# Register the default fallback path type based on the os.
75+
register(Sys.iswindows() ? WindowsPath : PosixPath)
7676
end
7777

7878
"""
@@ -88,8 +88,7 @@ Defines an abstract filesystem path.
8888
- `separator::String` - path separator (defaults to "/")
8989
9090
# Required Methods
91-
- `T(str::String)` - A string constructor
92-
- `FilePathsBase.ispathtype(::Type{T}, x::AbstractString) = true`
91+
- `tryparse(::Type{T}, str::String)` - For parsing string representations of your path
9392
- `read(path::T)`
9493
- `write(path::T, data)`
9594
- `exists(path::T` - whether the path exists

src/path.jl

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,7 @@ NOTE: `Path(::AbstractString)` can also work for custom paths if
1717
function Path end
1818

1919
Path(fp::AbstractPath) = fp
20-
21-
# May want to support using the registry for other constructors as well
22-
function Path(str::AbstractString; debug=false)
23-
types = filter(t -> ispathtype(t, str), PATH_TYPES)
24-
25-
if length(types) > 1
26-
@debug(
27-
string(
28-
"Found multiple path types that match the string specified ($types). ",
29-
"Please use a specific constructor if $(first(types)) is not the correct type."
30-
)
31-
)
32-
end
33-
34-
return first(types)(str)
35-
end
36-
20+
Path(str::AbstractString) = parse(AbstractPath, str)
3721
function Path(fp::T, segments::Tuple{Vararg{String}}) where T <: AbstractPath
3822
T((s === :segments ? segments : getfield(fp, s) for s in fieldnames(T))...)
3923
end
@@ -78,8 +62,49 @@ function Base.show(io::IO, fp::AbstractPath)
7862
get(io, :compact, false) ? print(io, fp) : print(io, "p\"$fp\"")
7963
end
8064

81-
Base.parse(::Type{<:AbstractPath}, x::AbstractString) = Path(x)
82-
Base.convert(::Type{<:AbstractPath}, x::AbstractString) = Path(x)
65+
# Default string constructors for AbstractPath types should fall back to calling `parse`.
66+
(::Type{T})(str::AbstractString) where {T<:AbstractPath} = parse(T, str)
67+
68+
function Base.parse(::Type{P}, str::AbstractString; kwargs...) where P<:AbstractPath
69+
result = tryparse(P, str; kwargs...)
70+
result === nothing && throw(ArgumentError("$str cannot be parsed as $P"))
71+
return result
72+
end
73+
74+
function Base.tryparse(::Type{AbstractPath}, str::AbstractString; debug=false)
75+
result = nothing
76+
types = Vector{eltype(PATH_TYPES)}()
77+
78+
for P in PATH_TYPES
79+
r = tryparse(P, str)
80+
81+
# If we successfully parsed the path then save that result
82+
# and break if we aren't in debug mode, otherwise record how many
83+
if r !== nothing
84+
# Only assign the result if it's currently `nothing`
85+
result = result === nothing ? r : result
86+
87+
if debug
88+
push!(types, P)
89+
else
90+
break
91+
end
92+
end
93+
end
94+
95+
if length(types) > 1
96+
@debug(
97+
string(
98+
"Found multiple path types that could parse the string specified ($types). ",
99+
"Please use a specific `parse` method if $(first(types)) is not the correct type."
100+
)
101+
)
102+
end
103+
104+
return result
105+
end
106+
107+
Base.convert(T::Type{<:AbstractPath}, x::AbstractString) = parse(T, x)
83108
Base.convert(::Type{String}, x::AbstractPath) = string(x)
84109
Base.promote_rule(::Type{String}, ::Type{<:AbstractPath}) = String
85110
Base.isless(a::P, b::P) where P<:AbstractPath = isless(a.segments, b.segments)
@@ -221,7 +246,7 @@ p"foobar"
221246
```
222247
"""
223248
function Base.:(*)(a::T, b::Union{T, AbstractString, AbstractChar}...) where T <: AbstractPath
224-
T(*(string(a), string.(b)...))
249+
return parse(T, *(string(a), string.(b)...))
225250
end
226251

227252
"""
@@ -393,18 +418,18 @@ function absolute(fp::AbstractPath)
393418
end
394419

395420
"""
396-
relative{T<:AbstractPath}(fp::T, start::T=T("."))
421+
relative{T<:AbstractPath}(fp::T, start::T=cwd())
397422
398423
Creates a relative path from either the current directory or an arbitrary start directory.
399424
"""
400-
function relative(fp::T, start::T=T(".")) where {T <: AbstractPath}
425+
function relative(fp::T, start::T=cwd()) where {T<:AbstractPath}
401426
curdir = "."
402427
pardir = ".."
403428

404429
p = absolute(fp).segments
405430
s = absolute(start).segments
406431

407-
p == s && return T(curdir)
432+
p == s && return parse(T, curdir)
408433

409434
i = 0
410435
while i < min(length(p), length(s))
@@ -431,7 +456,7 @@ function relative(fp::T, start::T=T(".")) where {T <: AbstractPath}
431456
else
432457
relpath_ = tuple(pathpart...)
433458
end
434-
return isempty(relpath_) ? T(curdir) : Path(fp, relpath_)
459+
return isempty(relpath_) ? parse(T, curdir) : Path(fp, relpath_)
435460
end
436461

437462
"""

src/posix.jl

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,15 @@ end
1212
PosixPath() = PosixPath(tuple(), "")
1313
PosixPath(segments::Tuple; root="") = PosixPath(segments, root)
1414

15-
function PosixPath(str::AbstractString)
15+
function Base.tryparse(::Type{PosixPath}, str::AbstractString)
1616
str = string(str)
17-
root = ""
18-
1917
isempty(str) && return PosixPath(tuple("."))
2018

2119
tokenized = split(str, POSIX_PATH_SEPARATOR)
22-
if isempty(tokenized[1])
23-
root = POSIX_PATH_SEPARATOR
24-
end
20+
root = isempty(tokenized[1]) ? POSIX_PATH_SEPARATOR : ""
2521
return PosixPath(tuple(map(String, filter!(!isempty, tokenized))...), root)
2622
end
2723

28-
ispathtype(::Type{PosixPath}, str::AbstractString) = Sys.isunix()
29-
3024
function Base.expanduser(fp::PosixPath)::PosixPath
3125
p = fp.segments
3226

src/test.jl

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ ps = PathSet(; symlink=true)
1313
# Select the subset of tests to run
1414
# Inspect TestPaths.TESTALL to see full list
1515
testsets = [
16-
test_constructor,
1716
test_registration,
1817
test_show,
1918
test_parse,
@@ -67,7 +66,6 @@ module TestPaths
6766
export PathSet,
6867
TESTALL,
6968
test,
70-
test_constructor,
7169
test_registration,
7270
test_show,
7371
test_parse,
@@ -180,17 +178,12 @@ module TestPaths
180178
end
181179
end
182180

183-
function test_constructor(ps::PathSet{P}) where P <: AbstractPath
184-
@testset "Constructor" begin
185-
str = string(ps.root)
186-
@test P(str) == ps.root
187-
end
188-
end
181+
# NOTE: Most paths should test their own constructors as necessary.
189182

190183
function test_registration(ps::PathSet{P}) where P <: AbstractPath
191184
@testset "Path constructor" begin
192185
str = string(ps.root)
193-
@test FilePathsBase.ispathtype(P, str)
186+
@test tryparse(P, str) !== nothing
194187
@test Path(str) == ps.root
195188
@test p"foo/bar" == Path("foo/bar")
196189
end
@@ -211,6 +204,7 @@ module TestPaths
211204
@testset "parsing" begin
212205
str = string(ps.root)
213206
@test parse(P, str) == ps.root
207+
@test tryparse(P, str) == ps.root
214208
end
215209
end
216210

@@ -898,7 +892,6 @@ module TestPaths
898892
end
899893

900894
TESTALL = [
901-
test_constructor,
902895
test_registration,
903896
test_show,
904897
test_parse,

src/windows.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,19 @@ function WindowsPath(segments::Tuple; root="", drive="", separator="\\")
2828
return WindowsPath(segments, root, drive, separator)
2929
end
3030

31-
function WindowsPath(str::AbstractString)
31+
function Base.tryparse(::Type{WindowsPath}, str::AbstractString)
3232
isempty(str) && WindowsPath(tuple("."), "", "")
3333

3434
if startswith(str, "\\\\?\\")
35-
error("The \\\\?\\ prefix is currently not supported.")
35+
@debug("The \\\\?\\ prefix is currently not supported.")
36+
return nothing
3637
end
3738

3839
str = replace(str, POSIX_PATH_SEPARATOR => WIN_PATH_SEPARATOR)
3940

4041
if startswith(str, "\\\\")
41-
error("UNC paths are currently not supported.")
42+
@debug("UNC paths are currently not supported.")
43+
return nothing
4244
elseif startswith(str, "\\")
4345
tokenized = split(str, WIN_PATH_SEPARATOR)
4446

@@ -72,8 +74,6 @@ function Base.:(==)(a::WindowsPath, b::WindowsPath)
7274
lowercase(a.drive) == lowercase(b.drive)
7375
end
7476

75-
ispathtype(::Type{WindowsPath}, str::AbstractString) = Sys.iswindows()
76-
7777
function Base.show(io::IO, fp::WindowsPath)
7878
print(io, "p\"")
7979
if isabsolute(fp)

test/runtests.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ include("testpkg.jl")
1919

2020
@testset "$(typeof(ps.root))" begin
2121
testsets = [
22-
test_constructor,
2322
test_registration,
2423
test_show,
2524
test_parse,

test/system.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ ps = PathSet(; symlink=true)
22

33
@testset "$(typeof(ps.root))" begin
44
testsets = [
5-
test_constructor,
65
test_registration,
76
test_show,
87
test_parse,
@@ -75,6 +74,9 @@ ps = PathSet(; symlink=true)
7574

7675
p = Path(reg)
7776

77+
# Test calling the Path tryparse method with debug mode.
78+
p = tryparse(AbstractPath, reg; debug=true)
79+
7880
@test p == p"../src/FilePathsBase.jl"
7981
@test string(p) == reg
8082
@test string(cwd()) == pwd()

test/testpkg.jl

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,20 @@ end
1717

1818
TestPath() = TestPath(tuple(), "", "test:", ";")
1919

20-
function TestPath(str::AbstractString)
20+
function Base.tryparse(::Type{TestPath}, str::AbstractString)
21+
startswith(str, "test:") || return nothing
2122
str = String(str)
22-
23-
@assert startswith(str, "test:")
2423
drive = "test:"
25-
root = ""
2624
str = str[6:end]
2725

28-
if isempty(str)
29-
return TestPath(tuple("."), "", drive)
30-
end
26+
isempty(str) && return TestPath(tuple("."), "", drive)
3127

3228
tokenized = split(str, ";")
33-
if isempty(tokenized[1])
34-
root = ";"
35-
end
29+
root = isempty(tokenized[1]) ? ";" : ""
3630

3731
return TestPath(tuple(map(String, filter!(!isempty, tokenized))...), root, drive, ";")
3832
end
3933

40-
FilePathsBase.ispathtype(::Type{TestPath}, str::AbstractString) = startswith(str, "test:")
4134
function test2posix(fp::TestPath)
4235
return PosixPath(fp.segments, isempty(fp.root) ? "" : "/")
4336
end

0 commit comments

Comments
 (0)