diff --git a/README.md b/README.md index 98e3607..a0415d7 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,9 @@ sure everything is working as intented. ### Argument Handling -The API for helping defing flexible function signatures consists of two helper -functions and two types: `arg_read`, `arg_write`, `ArgRead` and `ArgWrite`. +The API for helping defing flexible function signatures consists of two types +and four helper functions: `ArgRead` and `ArgWrite`; `arg_read`, `arg_write`, +`arg_isdir` and `arg_mkdir`. @@ -35,7 +36,6 @@ functions and two types: `arg_read`, `arg_write`, `ArgRead` and `ArgWrite`. ```jl ArgRead = Union{AbstractString, AbstractCmd, IO} ``` - The `ArgRead` types is a union of the types that the `arg_read` function knows how to convert into readable IO handles. See [`arg_read`](@ref) for details. @@ -44,7 +44,6 @@ how to convert into readable IO handles. See [`arg_read`](@ref) for details. ```jl ArgWrite = Union{AbstractString, AbstractCmd, IO} ``` - The `ArgWrite` types is a union of the types that the `arg_write` function knows how to convert into writeable IO handles, except for `Nothing` which `arg_write` handles by generating a temporary file. See [`arg_write`](@ref) for details. @@ -54,7 +53,6 @@ handles by generating a temporary file. See [`arg_write`](@ref) for details. ```jl arg_read(f::Function, arg::ArgRead) -> f(arg_io) ``` - The `arg_read` function accepts an argument `arg` that can be any of these: - `AbstractString`: a file path to be opened for reading @@ -71,7 +69,6 @@ flushed but not closed before returning from `arg_read`. arg_write(f::Function, arg::ArgWrite) -> arg arg_write(f::Function, arg::Nothing) -> tempname() ``` - The `arg_write` function accepts an argument `arg` that can be any of these: - `AbstractString`: a file path to be opened for writing @@ -90,6 +87,33 @@ If there is an error during the evaluation of the body, a path that is opened by `arg_write` for writing will be deleted, whether it's passed in as a string or a temporary path generated when `arg` is `nothing`. +#### arg_isdir + +```jl +arg_isdir(f::Function, arg::AbstractString) -> f(arg) +``` +The `arg_isdir` function takes `arg` which must be the path to an existing +directory (an error is raised otherwise) and passes that path to `f` finally +returning the result of `f(arg)`. This is definitely the least useful tool +offered by `ArgTools` and mostly exists for symmetry with `arg_mkdir` and to +give consistent error messages. + +#### arg_mkdir + +```jl +arg_mkdir(f::Function, arg::AbstractString) -> arg +arg_mkdir(f::Function, arg::Nothing) -> mktempdir() +``` +The `arg_mkdir` function takes `arg` which must either be one of: + +- a path to an already existing empty directory, +- a non-existent path which can be created as a directory, or +- `nothing` in which case a temporary directory is created. + +In all cases the path to the directory is returned. If an error occurs during +`f(arg)`, the directory is returned to its original state: if it already existed +but was empty, it will be emptied; if it did not exist it will be deleted. + ### Function Testing diff --git a/src/ArgTools.jl b/src/ArgTools.jl index 776df32..0765f7d 100644 --- a/src/ArgTools.jl +++ b/src/ArgTools.jl @@ -98,32 +98,62 @@ function arg_write(f::Function, arg::IO) return arg end +""" + arg_isdir(f::Function, arg::AbstractString) -> f(arg) + +The `arg_isdir` function takes `arg` which must be the path to an existing +directory (an error is raised otherwise) and passes that path to `f` finally +returning the result of `f(arg)`. This is definitely the least useful tool +offered by `ArgTools` and mostly exists for symmetry with `arg_mkdir` and to +give consistent error messages. +""" function arg_isdir(f::Function, arg::AbstractString) isdir(arg) || error("arg_isdir: $(repr(arg)) not a directory") return f(arg) end +""" + arg_mkdir(f::Function, arg::AbstractString) -> arg + arg_mkdir(f::Function, arg::Nothing) -> mktempdir() + +The `arg_mkdir` function takes `arg` which must either be one of: + +- a path to an already existing empty directory, +- a non-existent path which can be created as a directory, or +- `nothing` in which case a temporary directory is created. + +In all cases the path to the directory is returned. If an error occurs during +`f(arg)`, the directory is returned to its original state: if it already existed +but was empty, it will be emptied; if it did not exist it will be deleted. +""" function arg_mkdir(f::Function, arg::Union{AbstractString, Nothing}) - restore = false + existed = false if arg === nothing arg = mktempdir() else st = stat(arg) - if !ispath(arg) + if !ispath(st) mkdir(arg) - elseif !isdir(arg) + elseif !isdir(st) error("arg_mkdir: $(repr(arg)) not a directory") else isempty(readdir(arg)) || error("arg_mkdir: $(repr(arg)) directory not empty") - restore = true + existed = true end end try f(arg) catch - chmod(arg, 0o700, recursive=true) - rm(arg, force=true, recursive=true) - restore && mkdir(arg) + if existed + for name in readdir(arg) + path = joinpath(arg, name) + chmod(path, 0o700, recursive=true) + rm(path, force=true, recursive=true) + end + else + chmod(arg, 0o700, recursive=true) + rm(arg, force=true, recursive=true) + end rethrow() end return arg diff --git a/test/runtests.jl b/test/runtests.jl index 4b0b9b5..dd3105d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -107,19 +107,26 @@ end @testset "arg_mkdir" begin dir = tempname() + # creates a non-existent directory @test dir == arg_mkdir(d -> "%$d%", dir) @test isdir(dir) + # accepts a pre-existing empty directory @test dir == arg_mkdir(d -> "%$d%", dir) + @test isdir(dir) + # refuses a non-empty directory touch(joinpath(dir, "file")) @test_throws ErrorException arg_mkdir(identity, dir) rm(dir, recursive=true) + # refuses a non-directory file = tempname() touch(file) @test_throws ErrorException arg_mkdir(identity, file) rm(file) + # creates a temporary directory dir = arg_mkdir(d -> "%$d%", nothing) @test isdir(dir) rm(dir) + # on error, restores (deletes) a non-existent directory tmp = tempname() @test_throws ErrorException arg_mkdir(tmp) do dir @test dir == tmp @@ -129,5 +136,21 @@ end error("boof") end @test !ispath(tmp) + # on error, restores (empties) an empty directory + mkdir(tmp) + chmod(tmp, 0o741) + st = stat(tmp) + file = joinpath(tmp, "file") + @test_throws ErrorException arg_mkdir(tmp) do dir + @test dir == tmp + touch(file) + chmod(file, 0o000) + error("blammo") + end + @test !ispath(file) + @test isdir(tmp) + @test isempty(readdir(tmp)) + @test filemode(tmp) == filemode(st) + @test Base.Filesystem.samefile(st, stat(tmp)) end end