Skip to content

Commit

Permalink
Merge pull request #10 from JuliaIO/sk/arg_mkdir
Browse files Browse the repository at this point in the history
document and improve arg_{is,mk}dir
  • Loading branch information
StefanKarpinski authored Jun 17, 2020
2 parents 61e6d1c + 610f2de commit b42dfef
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 13 deletions.
36 changes: 30 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

<!-- BEGIN: copied from inline doc strings -->

Expand All @@ -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.

Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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.

<!-- END: copied from inline doc strings -->

### Function Testing
Expand Down
44 changes: 37 additions & 7 deletions src/ArgTools.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

0 comments on commit b42dfef

Please sign in to comment.