Browse Source

Improved command line argument parsing (#123)

This patch improves the command line argument parsing to handle more
edge cases related to input files and directories (such as e.g. empty
directories).
pull/124/head
Fredrik Ekre 12 months ago committed by GitHub
parent
commit
3ace1613c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      CHANGELOG.md
  2. 63
      src/main.jl
  3. 30
      test/maintests.jl

3
CHANGELOG.md

@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Runic is now silent by default. Use `--verbose` to enable the verbose file progress - Runic is now silent by default. Use `--verbose` to enable the verbose file progress
printing from previous releases ([#121]). printing from previous releases ([#121]).
### Fixed
- Improved the command line argument parsing to handle more edge cases related to input
files and directories such as e.g. empty directories ([#123]).
## [v1.2.0] - 2024-12-09 ## [v1.2.0] - 2024-12-09
### Added ### Added

63
src/main.jl

@ -165,16 +165,6 @@ function print_version()
return return
end end
function maybe_expand_directory!(outfiles, dir)
if !isdir(dir)
# Assumed to be a file, checked when using it
push!(outfiles, dir)
else
scandir!(outfiles, dir)
end
return
end
# juliac: type-stable output struct (required for juliac but useful in general too) # juliac: type-stable output struct (required for juliac but useful in general too)
struct Output{IO} struct Output{IO}
which::Symbol which::Symbol
@ -235,6 +225,8 @@ function main(argv)
check = false check = false
fail_fast = false fail_fast = false
line_ranges = typeof(1:2)[] line_ranges = typeof(1:2)[]
input_is_stdin = true
multiple_inputs = false
# Parse the arguments # Parse the arguments
while length(argv) > 0 while length(argv) > 0
@ -271,19 +263,41 @@ function main(argv)
elseif (m = match(r"^--output=(.+)$", x); m !== nothing) elseif (m = match(r"^--output=(.+)$", x); m !== nothing)
outputfile = String(m.captures[1]::SubString) outputfile = String(m.captures[1]::SubString)
else else
# Remaining arguments must be inputfile(s) # Remaining arguments must be `-`, files, or directories
maybe_expand_directory!(inputfiles, x) first = true
for x in argv while true
if x == "-" if x == "-"
return panic("input `-` can not be used with multiple files") # `-` is only allowed once and only in first position
if length(argv) > 0 || !first
return panic("input `-` can not be combined with other input")
end
push!(inputfiles, x)
input_is_stdin = true
else
input_is_stdin = false
if isdir(x)
scandir!(inputfiles, x)
# Directories are considered to be multiple (potential) inputs even
# if they end up being empty
multiple_inputs = true
else # isfile(x)
push!(inputfiles, x) # Assume it is a file for now
end end
maybe_expand_directory!(inputfiles, x) end
length(argv) == 0 && break
x = popfirst!(argv)
first = false
multiple_inputs = true
end end
break break
end end
end end
input_is_stdin = length(inputfiles) == 0 || inputfiles[1] == "-" # Insert `-` as the input if there were no input files/directories on the command line
if input_is_stdin && length(inputfiles) == 0
@assert !multiple_inputs
push!(inputfiles, "-")
end
# Check the arguments # Check the arguments
if inplace && check if inplace && check
@ -298,20 +312,16 @@ function main(argv)
if inplace && input_is_stdin if inplace && input_is_stdin
return panic("option `--inplace` can not be used together with stdin input") return panic("option `--inplace` can not be used together with stdin input")
end end
if outputfile != "" && length(inputfiles) > 1 if outputfile != "" && multiple_inputs
return panic("option `--output` can not be used together with multiple input files") return panic("option `--output` can not be used together with multiple input files")
end end
if !isempty(line_ranges) && length(inputfiles) > 1 if !isempty(line_ranges) && multiple_inputs
return panic("option `--lines` can not be used together with multiple input files") return panic("option `--lines` can not be used together with multiple input files")
end end
if length(inputfiles) > 1 && !(inplace || check) if multiple_inputs && !(inplace || check)
return panic("option `--inplace` or `--check` required with multiple input files") return panic("option `--inplace` or `--check` required with multiple input files")
end end
if length(inputfiles) == 0
push!(inputfiles, "-")
end
if diff if diff
if Sys.which("git") === nothing if Sys.which("git") === nothing
return panic("option `--diff` requires `git` to be installed") return panic("option `--diff` requires `git` to be installed")
@ -322,8 +332,10 @@ function main(argv)
nfiles_str = string(length(inputfiles)) nfiles_str = string(length(inputfiles))
for (file_counter, inputfile) in enumerate(inputfiles) for (file_counter, inputfile) in enumerate(inputfiles)
# Read the input # Read the input
if input_is_stdin if inputfile == "-"
@assert input_is_stdin
@assert length(inputfiles) == 1 @assert length(inputfiles) == 1
@assert !multiple_inputs
sourcetext = try sourcetext = try
read(stdin, String) read(stdin, String)
catch err catch err
@ -338,7 +350,7 @@ function main(argv)
continue continue
end end
else else
panic("input file does not exist: `$(inputfile)`") panic("input path is not a file or directory: `$(inputfile)`")
continue continue
end end
@ -353,6 +365,7 @@ function main(argv)
output = Output(:devnull, "", stdout, false, false) output = Output(:devnull, "", stdout, false, false)
else else
@assert length(inputfiles) == 1 @assert length(inputfiles) == 1
@assert !multiple_inputs
if outputfile == "" || outputfile == "-" if outputfile == "" || outputfile == "-"
output = Output(:stdout, "", stdout, false, false) output = Output(:stdout, "", stdout, false, false)
elseif isfile(outputfile) && !input_is_stdin && samefile(outputfile, inputfile) elseif isfile(outputfile) && !input_is_stdin && samefile(outputfile, inputfile)

30
test/maintests.jl

@ -324,6 +324,20 @@ function maintests(f::R) where {R}
end end
end end
# runic emptydir/
cdtmp() do
for argv in [["-i", "."], ["-c", "."], ["-c", "-v", "."], ["-c", "-d", "."]]
rc, fd1, fd2 = runic(["-i", "."])
@test rc == 0
@test isempty(fd1) && isempty(fd2)
end
let (rc, fd1, fd2) = runic(["."])
@test rc == 1
@test isempty(fd1)
@test occursin("option `--inplace` or `--check` required with multiple", fd2)
end
end
# Error paths # Error paths
# runic -o # runic -o
let (rc, fd1, fd2) = runic(["-o"]) let (rc, fd1, fd2) = runic(["-o"])
@ -332,11 +346,14 @@ function maintests(f::R) where {R}
@test occursin("expected output file argument after `-o`", fd2) @test occursin("expected output file argument after `-o`", fd2)
end end
# runic in.jl - # - only allowed once and only in first position
let (rc, fd1, fd2) = runic(["in.jl", "-"]) cdtmp() do
for argv in [[".", "-"], ["-", "."], ["in.jl", "-"], ["-", "in.jl"]]
rc, fd1, fd2 = runic(argv)
@test rc == 1 @test rc == 1
@test isempty(fd1) @test isempty(fd1)
@test occursin("input `-` can not be used with multiple files", fd2) @test occursin("input `-` can not be combined with other input", fd2)
end
end end
# runic --inplace --check (TODO: perhaps this should be allowed?) # runic --inplace --check (TODO: perhaps this should be allowed?)
@ -404,10 +421,11 @@ function maintests(f::R) where {R}
# runic doesntexist.jl # runic doesntexist.jl
cdtmp() do cdtmp() do
rc, fd1, fd2 = runic(["doesntexist.jl"]) doesntexist = "doesntexist.jl"
rc, fd1, fd2 = runic([doesntexist])
@test rc == 1 @test rc == 1
@test isempty(fd1) @test isempty(fd1)
@test occursin("input file does not exist", fd2) @test occursin("input path is not a file or directory: `$doesntexist`", fd2)
end end
# runic -o in.jl in.jl # runic -o in.jl in.jl
@ -481,7 +499,7 @@ function maintests(f::R) where {R}
rc, fd1, fd2 = runic(["--lines=3:4"], src) rc, fd1, fd2 = runic(["--lines=3:4"], src)
@test rc == 1 && isempty(fd1) @test rc == 1 && isempty(fd1)
@test occursin("`--lines` range out of bounds", fd2) @test occursin("`--lines` range out of bounds", fd2)
rc, fd1, fd2 = runic(["--lines=3:4", "foo.jl", "bar.jl"], src) rc, fd1, fd2 = runic(["--lines=3:4", "."])
@test rc == 1 && isempty(fd1) @test rc == 1 && isempty(fd1)
@test occursin("option `--lines` can not be used together with multiple input files", fd2) @test occursin("option `--lines` can not be used together with multiple input files", fd2)
end end

Loading…
Cancel
Save