diff --git a/.travis.yml b/.travis.yml index e70c175d..87f93008 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ os: - linux - osx julia: - - 0.5 - 0.6 - nightly notifications: diff --git a/REQUIRE b/REQUIRE index 2652857b..f725f9d9 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,2 +1,2 @@ -julia 0.5 +julia 0.6 Compat 0.17.0 diff --git a/appveyor.yml b/appveyor.yml index 47bca1fd..43a17c2e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,5 @@ environment: matrix: - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.5/julia-0.5-latest-win32.exe" - - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.5/julia-0.5-latest-win64.exe" - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe" - JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe" @@ -31,4 +29,7 @@ build_script: - C:\projects\julia\bin\julia -e "Pkg.clone(pwd(), \"FilePaths\"); versioninfo(); Pkg.build(\"FilePaths\")" test_script: - - C:\projects\julia\bin\julia --check-bounds=yes -e "Pkg.test(\"FilePaths\")" + - C:\projects\julia\bin\julia --check-bounds=yes -e "Pkg.test(\"FilePaths\", coverage=true)" + +after_test: + - C:\projects\julia\bin\julia -e "cd(Pkg.dir(\"FilePaths\")); Pkg.add(\"Coverage\"); using Coverage; Codecov.submit(Codecov.process_folder())" diff --git a/src/FilePaths.jl b/src/FilePaths.jl index d1df208a..54e0debd 100644 --- a/src/FilePaths.jl +++ b/src/FilePaths.jl @@ -15,7 +15,9 @@ export Status, # Methods + anchor, cwd, + drive, home, parts, root, @@ -66,6 +68,7 @@ Base.next(p::AbstractPath, i::Int) = next(String(p), i) Base.String(path::AbstractPath) = error("`String not implemented") parts(path::AbstractPath) = error("`parts` not implemented.") root(path::AbstractPath) = error("`root` not implemented.") +drive(path::AbstractPath) = error("`drive` not implemented.") Base.convert(::Type{AbstractPath}, x::AbstractString) = Path(x) diff --git a/src/libc.jl b/src/libc.jl index 76ff5e0d..7450274a 100644 --- a/src/libc.jl +++ b/src/libc.jl @@ -76,7 +76,7 @@ function User(name::String) User(ps) end -function User(uid::UInt64) +function User(uid::UInt) ps = @static if is_unix() ccall((:getpwuid, "libc"), Ptr{Cpasswd}, (UInt64,), uid) else @@ -114,7 +114,7 @@ function Group(name::String) Group(ps) end -function Group(gid::UInt64) +function Group(gid::UInt) gr = @static if is_unix() ccall((:getgrgid, "libc"), Ptr{Cgroup}, (UInt64,), gid) else diff --git a/src/path.jl b/src/path.jl index 62e4c96e..0069a7b4 100644 --- a/src/path.jl +++ b/src/path.jl @@ -9,11 +9,9 @@ Responsible for creating the appropriate platform specific path """ Path() = @static is_unix() ? PosixPath() : WindowsPath() Path(path::AbstractPath) = path -Path(pieces::Tuple) = @static is_unix() ? PosixPath(pieces) : WindowsPath(pieces, "") +Path(pieces::Tuple) = @static is_unix() ? PosixPath(pieces) : WindowsPath(pieces) Path(str::AbstractString) = @static is_unix() ? PosixPath(str) : WindowsPath(str) -Base.show(io::IO, path::AbstractPath) = print(io, "p\"$(join(parts(path), '/'))\"") - """ @p_str -> Path @@ -27,6 +25,8 @@ end cwd() = Path(pwd()) home() = Path(homedir()) +anchor(path::AbstractPath) = drive(path) * root(path) + #= Path Modifiers =============================================== @@ -144,7 +144,7 @@ function extension(path::AbstractPath) end """ - extension(path::AbstractPath) -> AbstractString + extensions(path::AbstractPath) -> AbstractString Extracts all extensions from a filename if there any, otherwise it returns an empty string. @@ -250,14 +250,21 @@ function relative{T<:AbstractPath}(path::T, start::T=T(".")) p = parts(abs(path)) s = parts(abs(start)) - p == s && return curdir + p == s && return T(curdir) i = 0 while i < min(length(p), length(s)) i += 1 - if p[i] != s[i] - i -= 1 - break + @static if is_windows() + if lowercase(p[i]) != lowercase(s[i]) + i -= 1 + break + end + else + if p[i] != s[i] + i -= 1 + break + end end end diff --git a/src/posix.jl b/src/posix.jl index 126b2093..ce427e75 100644 --- a/src/posix.jl +++ b/src/posix.jl @@ -23,6 +23,8 @@ end Base.String(path::PosixPath) = joinpath(parts(path)...) parts(path::PosixPath) = path.parts +Base.show(io::IO, path::PosixPath) = print(io, "p\"$(join(parts(path), '/'))\"") + function isabs(path::PosixPath) if parts(path)[1] == POSIX_PATH_SEPARATOR return true diff --git a/src/windows.jl b/src/windows.jl index fad48ad5..68534291 100644 --- a/src/windows.jl +++ b/src/windows.jl @@ -1,46 +1,76 @@ immutable WindowsPath <: AbstractPath - parts::Tuple + parts::Tuple{Vararg{String}} drive::String + root::String end -WindowsPath() = WindowsPath(tuple(), "") +WindowsPath() = WindowsPath(tuple(), "", "") + +WindowsPath(parts::Tuple) = WindowsPath(parts, "", "") function WindowsPath(str::AbstractString) if isempty(str) - return WindowsPath(tuple("."), "") + return WindowsPath(tuple("."), "", "") end - drive, path = splitdir(str) - tokenized = split(path, WIN_PATH_SEPARATOR) - - if isempty(tokenized[1]) - tokenized[1] = WIN_PATH_SEPARATOR + if startswith(str, "\\\\?\\") + error("The \\\\?\\ prefix is currently not supported.") end - return WindowsPath(tuple(map(String, tokenized)...), drive) -end + str = replace(str, POSIX_PATH_SEPARATOR, WIN_PATH_SEPARATOR) + + if startswith(str, "\\\\") + error("UNC paths are currently not supported.") + elseif startswith(str, "\\") + tokenized = split(str, WIN_PATH_SEPARATOR) + + return WindowsPath(tuple(WIN_PATH_SEPARATOR, String.(tokenized[2:end])...), "", WIN_PATH_SEPARATOR) + elseif contains(str, ":") + l_drive, l_path = splitdrive(str) + + tokenized = split(l_path, WIN_PATH_SEPARATOR) + + l_root = isempty(tokenized[1]) ? WIN_PATH_SEPARATOR : "" + + if isempty(tokenized[1]) + tokenized = tokenized[2:end] + end + + if !isempty(l_drive) || !isempty(l_root) + tokenized = tuple(string(l_drive, l_root), tokenized...) + end + return WindowsPath(tuple(String.(tokenized)...), l_drive, l_root) + else + tokenized = split(str, WIN_PATH_SEPARATOR) -# The following should be implemented in the concrete types -==(a::WindowsPath, b::WindowsPath) = parts(a) == parts(b) && drive(a) == drive(b) + return WindowsPath(tuple(String.(tokenized)...), "", "") + end +end + +function ==(a::WindowsPath, b::WindowsPath) + return lowercase.(parts(a)) == lowercase.(parts(b)) && + lowercase(drive(a)) == lowercase(drive(b)) && + lowercase(root(a)) == lowercase(root(b)) +end Base.String(path::WindowsPath) = joinpath(parts(path)...) parts(path::WindowsPath) = path.parts drive(path::WindowsPath) = path.drive +root(path::WindowsPath) = path.root -function isabs(path::WindowsPath) - if parts(path[1]) == WIN_PATH_SEPARATOR && !isempty(drive(path)) - return true +function Base.show(io::IO, path::WindowsPath) + print(io, "p\"") + if isabs(path) + print(io, replace(anchor(path), "\\", "/")) + print(io, join(parts(path)[2:end], "/")) else - return false + print(io, join(parts(path), "/")) end + print(io, "\"") end -function root(path::WindowsPath) - if parts(path)[1] == WIN_PATH_SEPARATOR - return WIN_PATH_SEPARATOR - else - return "" - end +function isabs(path::WindowsPath) + return !isempty(drive(path)) || !isempty(root(path)) end expanduser(path::WindowsPath) = path diff --git a/test/path.jl b/test/path.jl index ca0469c6..051166e3 100644 --- a/test/path.jl +++ b/test/path.jl @@ -1,7 +1,7 @@ cd(abs(parent(Path(@__FILE__)))) do @testset "Simple Path Usage" begin - reg = "../src/FilePaths.jl" + reg = is_windows() ? "..\\src\\FilePaths.jl" : "../src/FilePaths.jl" @test ispath(reg) p = Path(reg) @@ -30,7 +30,17 @@ cd(abs(parent(Path(@__FILE__)))) do @test !isabs(p) @test String(norm(p"../src/../src/FilePaths.jl")) == normpath("../src/../src/FilePaths.jl") @test String(abs(p)) == abspath(String(p)) - @test String(relative(p, home())) == relpath(String(p), homedir()) + # This works around an issue with Base.relpath: that function does not take + # into account the paths on Windows should be compared case insensitive. + homedir_patched = homedir() + if is_windows() + conv_f = isupper(abspath(String(p))[1]) ? uppercase : lowercase + homedir_patched = conv_f(homedir_patched[1]) * homedir_patched[2:end] + end + @test String(relative(p, home())) == relpath(String(p), homedir_patched) + + @test isa(relative(Path(".")), AbstractPath) + @test relative(Path(".")) == Path(".") s = stat(p) lstat(p) @@ -106,22 +116,24 @@ mktmpdir() do d @test_throws ErrorException chown(p"newfile", "nobody", "nogroup"; recursive=true) end - chmod(p"newfile", user=(READ+WRITE+EXEC), group=(READ+EXEC), other=READ) - @test String(mode(p"newfile")) == "-rwxr-xr--" - @test isexecutable(p"newfile") - @test iswritable(p"newfile") - @test isreadable(p"newfile") - - chmod(p"newfile", "-x") - @test !isexecutable(p"newfile") - - @test String(mode(p"newfile")) == "-rw-r--r--" - chmod(p"newfile", "+x") - write(p"newfile", "foobar") - @test read(p"newfile") == "foobar" - chmod(p"newfile", "u=rwx") - - chmod(new_path, mode(p"newfile"); recursive=true) + @static if is_unix() + chmod(p"newfile", user=(READ+WRITE+EXEC), group=(READ+EXEC), other=READ) + @test String(mode(p"newfile")) == "-rwxr-xr--" + @test isexecutable(p"newfile") + @test iswritable(p"newfile") + @test isreadable(p"newfile") + + chmod(p"newfile", "-x") + @test !isexecutable(p"newfile") + + @test String(mode(p"newfile")) == "-rw-r--r--" + chmod(p"newfile", "+x") + write(p"newfile", "foobar") + @test read(p"newfile") == "foobar" + chmod(p"newfile", "u=rwx") + + chmod(new_path, mode(p"newfile"); recursive=true) + end end end end