diff --git a/src/settings.jl b/src/settings.jl index 22f38cd..68ba286 100644 --- a/src/settings.jl +++ b/src/settings.jl @@ -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 @@ -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 @@ -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 diff --git a/test/argparse_test02.jl b/test/argparse_test02.jl index 381c412..a9132cf 100644 --- a/test/argparse_test02.jl +++ b/test/argparse_test02.jl @@ -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) diff --git a/test/argparse_test03.jl b/test/argparse_test03.jl index dc056be..2096743 100644 --- a/test/argparse_test03.jl +++ b/test/argparse_test03.jl @@ -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" @@ -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"]]) """ @@ -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]) @@ -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 diff --git a/test/argparse_test14.jl b/test/argparse_test14.jl new file mode 100644 index 0000000..3494661 --- /dev/null +++ b/test/argparse_test14.jl @@ -0,0 +1,71 @@ +# 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" + "--opt3" + action = :append_arg + arg_type = Int + default = [0.0] + help = "another option" + "--opt4" + action = :append_arg + nargs = '+' + arg_type = Int + default = [[0.0]] + help = "another option" + 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: Int64, default: 0, without + arg: 1) + -O O another option (type: Symbol, default: :xyz) + --opt2 OPT2 [OPT2...] + another option (type: Int64, default: [0]) + --opt3 OPT3 another option (type: Int64, default: [0]) + --opt4 OPT4 [OPT4...] + another option (type: Int64, 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 diff --git a/test/runtests.jl b/test/runtests.jl index dbcd279..bd030ba 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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")