Skip to content

Commit

Permalink
Allow default values convertible to arg_type (#133)
Browse files Browse the repository at this point in the history
Fix #123
  • Loading branch information
carlobaldassi authored May 10, 2024
1 parent ab9d3cf commit 2b988a3
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 51 deletions.
89 changes: 47 additions & 42 deletions src/settings.jl
Original file line number Diff line number Diff line change
Expand Up @@ -512,41 +512,55 @@ function check_for_duplicates(args::Vector{ArgParseField}, new_arg::ArgParseFiel
return true
end

check_default_type(default::Nothing, arg_type::Type) = true
function check_default_type(default::D, arg_type::Type) where D
D <: arg_type || serror("typeof(default)=$D is incompatible with arg_type=$arg_type)")
return true
function typecompatible(default::D, arg_type::Type) where D
D <: arg_type && return true
try
applicable(convert, arg_type, default) ? convert(arg_type, default) : arg_type(default)
return true
catch
end
return false
end

check_default_type_multi_action(default::Nothing, arg_type::Type) = true
function check_default_type_multi_action(default::Vector{D}, arg_type::Type) where D
arg_type <: D || serror("typeof(default)=Vector{$D} can't hold arguments of type arg_type=$arg_type)")
all(x->(x isa arg_type), default) || serror("all elements of the default value must be of type $arg_type)")
check_default_type(default::Nothing, arg_type::Type) = true
function check_default_type(default::D, arg_type::Type) where D
typecompatible(default, arg_type) || serror("typeof(default)=$D is incompatible with arg_type=$arg_type)")
return true
end
check_default_type_multi_action(default::D, arg_type::Type) where D =
serror("typeof(default)=$D is incompatible with the action, it should be a Vector")

check_default_type_multi_nargs(default::Nothing, arg_type::Type) = true
function check_default_type_multi_nargs(default::Vector, arg_type::Type)
all(x->(x isa arg_type), default) || serror("all elements of the default value must be of type $arg_type")
check_default_type_multi(default::Nothing, arg_type::Type) = true
function check_default_type_multi(default::Vector, arg_type::Type)
all(x->typecompatible(x, arg_type), default) || serror("all elements of the default value must be of type $arg_type or convertible to it")
return true
end
check_default_type_multi_nargs(default::D, arg_type::Type) where D =
check_default_type_multi(default::D, arg_type::Type) where D =
serror("typeof(default)=$D is incompatible with nargs, it should be a Vector")

check_default_type_multi2(default::Nothing, arg_type::Type) = true
function check_default_type_multi2(default::Vector{D}, arg_type::Type) where D
Vector{arg_type} <: D || serror("typeof(default)=Vector{$D} can't hold Vectors of arguments " *
"of type arg_type=$arg_type)")
all(y->(y isa Vector), default) ||
serror("the default $(default) is incompatible with the action and nargs, it should be a Vector of Vectors")
all(y->all(x->(x isa arg_type), y), default) || serror("all elements of the default value must be of type $arg_type")
all(y->all(x->typecompatible(x, arg_type), y), default) || serror("all elements of the default value must be of type $arg_type or convertible to it")
return true
end
check_default_type_multi2(default::D, arg_type::Type) where D =
serror("the default $(default) is incompatible with the action and nargs, it should be a Vector of Vectors")

function _convert_default(arg_type::Type, default::D) where D
D <: arg_type && return default
applicable(convert, arg_type, default) ? convert(arg_type, default) : arg_type(default)
end

convert_default(arg_type::Type, default::Nothing) = nothing
convert_default(arg_type::Type, default) = _convert_default(arg_type, default)

convert_default_multi(arg_type::Type, default::Nothing) = Array{arg_type}(undef, 0)
convert_default_multi(arg_type::Type, default::Vector) = arg_type[_convert_default(arg_type, x) for x in default]

convert_default_multi2(arg_type::Type, default::Nothing) = Array{Vector{arg_type}}(undef, 0)
convert_default_multi2(arg_type::Type, default::Vector) = Vector{arg_type}[arg_type[_convert_default(arg_type, x) for x in y] for y in default]


check_range_default(default::Nothing, range_tester::Function) = true
function check_range_default(default, range_tester::Function)
local res::Bool
Expand Down Expand Up @@ -1033,20 +1047,17 @@ function add_arg_field!(settings::ArgParseSettings, name::ArgName; desc...)
elseif action == :count_invocations
new_arg.arg_type = Int
new_arg.default = 0
elseif action (:store_const, :append_const)
if :arg_type supplied_opts
check_default_type(new_arg.default, new_arg.arg_type)
check_default_type(new_arg.constant, new_arg.arg_type)
else
if typeof(new_arg.default) == typeof(new_arg.constant)
new_arg.arg_type = typeof(new_arg.default)
else
new_arg.arg_type = Any
end
end
if action == :append_const && (new_arg.default nothing || new_arg.default == [])
new_arg.default = Array{new_arg.arg_type}(undef, 0)
elseif action == :store_const
check_default_type(new_arg.default, new_arg.arg_type)
check_default_type(new_arg.constant, new_arg.arg_type)
new_arg.default = convert_default(new_arg.arg_type, new_arg.default)
elseif action == :append_const
check_default_type(new_arg.constant, new_arg.arg_type)
if :arg_type supplied_opts
new_arg.arg_type = typeof(new_arg.constant)
end
check_default_type_multi(new_arg.default, new_arg.arg_type)
new_arg.default = convert_default_multi(new_arg.arg_type, new_arg.default)
elseif action == :command_flag
# nothing to do
elseif action == :show_help || action == :show_version
Expand All @@ -1062,28 +1073,22 @@ function add_arg_field!(settings::ArgParseSettings, name::ArgName; desc...)
if !is_multi_action(new_arg.action) && !is_multi_nargs(new_arg.nargs)
check_default_type(default, arg_type)
check_range_default(default, range_tester)
elseif !is_multi_action(new_arg.action)
check_default_type_multi_nargs(default, arg_type)
check_range_default_multi(default, range_tester)
elseif !is_multi_nargs(new_arg.nargs)
check_default_type_multi_action(default, arg_type)
new_arg.default = convert_default(arg_type, default)
elseif !is_multi_action(new_arg.action) || !is_multi_nargs(new_arg.nargs)
check_default_type_multi(default, arg_type)
check_range_default_multi(default, range_tester)
new_arg.default = convert_default_multi(arg_type, default)
else
check_default_type_multi2(default, arg_type)
check_range_default_multi2(default, range_tester)
end
if (is_multi_action(new_arg.action) && is_multi_nargs(new_arg.nargs)) &&
(default nothing || default == [])
new_arg.default = Array{Vector{arg_type}}(undef, 0)
elseif (is_multi_action(new_arg.action) || is_multi_nargs(new_arg.nargs)) &&
(default nothing || default == [])
new_arg.default = Array{arg_type}(undef, 0)
new_arg.default = convert_default_multi2(arg_type, default)
end

if is_opt && nargs.desc == :?
constant = new_arg.constant
check_default_type(constant, arg_type)
check_range_default(constant, range_tester)
new_arg.constant = convert_default(arg_type, constant)
end
end

Expand Down
2 changes: 1 addition & 1 deletion test/argparse_test02.jl
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ for s = [ap_settings2(), ap_settings2b(), ap_settings2c(), ap_settings2d(), ap_s
@aps_test_throws @add_arg_table!(s, "required_arg_after_optional_args", required = true)
# wrong default
@aps_test_throws @add_arg_table!(s, "--opt", arg_type = Int, default = 1.5)
@aps_test_throws @add_arg_table!(s, "--opt3", arg_type = Symbol, default = "string")
@aps_test_throws @add_arg_table!(s, "--opt3", arg_type = Function, default = "string")
# wrong range tester
@aps_test_throws @add_arg_table!(s, "--opt", arg_type = Int, range_tester = x->string(x), default = 1)
@aps_test_throws @add_arg_table!(s, "--opt", arg_type = Int, range_tester = x->sqrt(x)<1, default = -1)
Expand Down
14 changes: 7 additions & 7 deletions test/argparse_test03.jl
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function ap_settings3()
# called repeatedly
dest_name = "awk"
range_tester = (x->x=="X"||x=="Y") # each argument must be either "X" or "Y"
default = Any[Any["X"]]
default = [["X"]]
metavar = "XY"
help = "either X or Y; all XY's are " *
"stored in chunks"
Expand Down Expand Up @@ -113,7 +113,7 @@ let s = ap_settings3()
--collect C collect things (type: $Int)
--awkward-option XY [XY...]
either X or Y; all XY's are stored in chunks
(default: Any[Any["X"]])
(default: $(Vector{Any})[["X"]])
"""

Expand All @@ -134,8 +134,8 @@ let s = ap_settings3()
@aps_test_throws @add_arg_table!(s, "--opt", action = :store_const, arg_type = Int, default = 1, constant = 1.5)
@aps_test_throws @add_arg_table!(s, "--opt", action = :append_const, arg_type = Int, constant = 1.5)
# wrong defaults
@aps_test_throws @add_arg_table!(s, "--opt", action = :append_arg, arg_type = Int, default = Float64[])
@aps_test_throws @add_arg_table!(s, "--opt", action = :append_arg, nargs = '+', arg_type = Int, default = Vector{Float64}[])
@aps_test_throws @add_arg_table!(s, "--opt", action = :append_arg, arg_type = Int, default = String["hello"])
@aps_test_throws @add_arg_table!(s, "--opt", action = :append_arg, nargs = '+', arg_type = Int, default = Vector{Float64}[[1.5]])
@aps_test_throws @add_arg_table!(s, "--opt", action = :store_arg, nargs = '+', arg_type = Int, default = [1.5])
@aps_test_throws @add_arg_table!(s, "--opt", action = :store_arg, nargs = '+', arg_type = Int, default = 1)
@aps_test_throws @add_arg_table!(s, "--opt", action = :append_arg, arg_type = Int, range_tester=x->x<=1, default = Int[0, 1, 2])
Expand All @@ -156,9 +156,9 @@ let s = ap_settings3()
# allow ambiguous options
s.allow_ambiguous_opts = true
@add_arg_table!(s, "-2", action = :store_true)
@test ap_test3([]) == Dict{String,Any}("O_stack"=>String[], "k"=>0, "u"=>0, "array"=>[7, 3, 2], "custom"=>CustomType(), "oddint"=>1, "collect"=>[], "awk"=>Any[Any["X"]], "2"=>false)
@test ap_test3(["-2"]) == Dict{String,Any}("O_stack"=>String[], "k"=>0, "u"=>0, "array"=>[7, 3, 2], "custom"=>CustomType(), "oddint"=>1, "collect"=>[], "awk"=>Any[["X"]], "2"=>true)
@test ap_test3(["--awk", "X", "-2"]) == Dict{String,Any}("O_stack"=>String[], "k"=>0, "u"=>0, "array"=>[7, 3, 2], "custom"=>CustomType(), "oddint"=>1, "collect"=>[], "awk"=>Any[Any["X"], Any["X"]], "2"=>true)
@test ap_test3([]) == Dict{String,Any}("O_stack"=>String[], "k"=>0, "u"=>0, "array"=>[7, 3, 2], "custom"=>CustomType(), "oddint"=>1, "collect"=>[], "awk"=>Vector{Any}[["X"]], "2"=>false)
@test ap_test3(["-2"]) == Dict{String,Any}("O_stack"=>String[], "k"=>0, "u"=>0, "array"=>[7, 3, 2], "custom"=>CustomType(), "oddint"=>1, "collect"=>[], "awk"=>Vector{Any}[["X"]], "2"=>true)
@test ap_test3(["--awk", "X", "-2"]) == Dict{String,Any}("O_stack"=>String[], "k"=>0, "u"=>0, "array"=>[7, 3, 2], "custom"=>CustomType(), "oddint"=>1, "collect"=>[], "awk"=>Vector{Any}[["X"], ["X"]], "2"=>true)
@ap_test_throws ap_test3(["--awk", "X", "-3"])

end
Expand Down
74 changes: 74 additions & 0 deletions test/argparse_test14.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# test 14: default values converted to arg_type

@testset "test 14" begin

function ap_settings14()

s = ArgParseSettings(description = "Test 14 for ArgParse.jl",
exc_handler = ArgParse.debug_handler)

@add_arg_table! s begin
"--opt1"
nargs = '?'
arg_type = Int
default = 0.0
constant = 1.0
help = "an option"
"-O"
arg_type = Symbol
default = "xyz"
help = "another option"
"--opt2"
nargs = '+'
arg_type = Int
default = [0.0]
help = "another option, many args"
"--opt3"
action = :append_arg
arg_type = Int
default = [0.0]
help = "another option, appends arg"
"--opt4"
action = :append_arg
nargs = '+'
arg_type = Int
default = [[0.0]]
help = "another option, appends many args"
end

return s
end

let s = ap_settings14()
ap_test14(args) = parse_args(args, s)

@test stringhelp(s) == """
usage: $(basename(Base.source_path())) [--opt1 [OPT1]] [-O O]
[--opt2 OPT2 [OPT2...]] [--opt3 OPT3]
[--opt4 OPT4 [OPT4...]]
Test 14 for ArgParse.jl
optional arguments:
--opt1 [OPT1] an option (type: $(Int), default: 0, without
arg: 1)
-O O another option (type: Symbol, default: :xyz)
--opt2 OPT2 [OPT2...]
another option, many args (type: $(Int),
default: [0])
--opt3 OPT3 another option, appends arg (type: $(Int),
default: [0])
--opt4 OPT4 [OPT4...]
another option, appends many args (type:
$(Int), default: $([[0]]))
"""

@test ap_test14([]) == Dict{String,Any}("opt1"=>0, "O"=>:xyz, "opt2"=>[0], "opt3"=>[0], "opt4"=>[[0]])
@test ap_test14(["--opt1"]) == Dict{String,Any}("opt1"=>1, "O"=>:xyz, "opt2"=>[0], "opt3"=>[0], "opt4"=>[[0]])
@test ap_test14(["--opt1", "33", "--opt2", "5", "7"]) == Dict{String,Any}("opt1"=>33, "O"=>:xyz, "opt2"=>[5, 7], "opt3"=>[0], "opt4"=>[[0]])
@test ap_test14(["--opt3", "5", "--opt3", "7"]) == Dict{String,Any}("opt1"=>0, "O"=>:xyz, "opt2"=>[0], "opt3"=>[0, 5, 7], "opt4"=>[[0]])
@test ap_test14(["--opt4", "5", "7", "--opt4", "11", "13", "17"]) == Dict{String,Any}("opt1"=>0, "O"=>:xyz, "opt2"=>[0], "opt3"=>[0], "opt4"=>[[0], [5, 7], [11, 13, 17]])
end

end
2 changes: 1 addition & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module ArgParseTests

include("common.jl")

for i = 1:13
for i = 1:14
try
s_i = lpad(string(i), 2, "0")
include("argparse_test$s_i.jl")
Expand Down

0 comments on commit 2b988a3

Please sign in to comment.