Repository: comonicon/Comonicon.jl (julia)

Issue:
[BUG] Using optional optional types from Configurations fails with an error.
**Describe the bug**
Using optional optional [types from Configurations](https://comonicon.org/stable/conventions/#Working-with-option-types-from-Configurations) fails with an error.

**Version of the package:**
1.0.0, julia 1.8.3

**To Reproduce**
```julia
module M

using Comonicon
using Configurations

@option struct Opts
    x::String = "x"
end

"""
# Options
- `-c, --config <path/to/option/or/specific field>`: config.
"""
@main function main(;config::Opts=Opts())
    @show config
end

end

M.command_main()
```
> ERROR: KeyError: key :config not found
Stacktrace:
 [1] getindex(h::Dict{Symbol, Any}, key::Symbol)
   @ Base ./dict.jl:498
 [2] command_main(ARGS::Vector{String})
   @ Main.M ~/.julia/dev/Comonicon/src/codegen/julia.jl:277
 [3] command_main()
   @ Main.M ~/.julia/dev/Comonicon/src/codegen/julia.jl:90
 [4] top-level scope
   @ REPL[2]:1

**Expected behavior**
The function should show `config = Main.M.Opts("x")`

**Reason**
Accessing `kwargs` [in `emit_leaf_call`](https://github.com/comonicon/Comonicon.jl/blob/master/src/codegen/julia.jl#L278) assumes that the option was already stored, which happens only for required options (afaik).

Pull request:
PR fixes https://github.com/comonicon/Comonicon.jl/issues/228 and adds tests that fail on the current version, but work fine with the fix.

Patch:
diff --git a/src/codegen/julia.jl b/src/codegen/julia.jl
index 758a266..ce0cdc9 100644
--- a/src/codegen/julia.jl
+++ b/src/codegen/julia.jl
@@ -274,8 +274,10 @@ function emit_leaf_call(cmd::LeafCommand, ctx::EmitContext, args::Symbol, kwargs
         for option in values(cmd.options)
             Configurations.is_option(option.type) || continue
             push!(ret.args, quote
-                $kwargs[$(QuoteNode(option.sym))] =
-                    $Configurations.from_dict($(option.type), $kwargs[$(QuoteNode(option.sym))])
+                if $(QuoteNode(option.sym)) in Base.keys($kwargs)
+                    $kwargs[$(QuoteNode(option.sym))] =
+                        $Configurations.from_dict($(option.type), $kwargs[$(QuoteNode(option.sym))])
+                end
             end)
         end
     end
@@ -533,7 +535,7 @@ function emit_option_with_option_type(
     assign_field_to_dict = foreach_leaf_field(option.type, value, fields) do dict_ex, type
         quote
             $field_value = $(emit_parse_value(option, ctx, type, field_value))
-            $dict_ex = $field_value 
+            $dict_ex = $field_value
         end
     end
     assign_field_to_dict.otherwise = emit_error(option, ctx, :("cannot find field $($fields)"))


Test patch:
diff --git a/test/codegen/julia/config.jl b/test/codegen/julia/config.jl
index 2421413..bd4161f 100644
--- a/test/codegen/julia/config.jl
+++ b/test/codegen/julia/config.jl
@@ -5,8 +5,8 @@ using Comonicon
 using Configurations
 
 @option struct OptionA
-    a::Int
-    b::Int
+    a::Int = 2
+    b::Int = 2
 end
 
 @option struct OptionB
@@ -19,21 +19,31 @@ end
 
 - `-c, --config <path/to/option/or/specific field>`: config.
 """
-@main function run(;config::OptionB)
+@cast function run(;config::OptionB)
     @test config == OptionB(OptionA(1, 1), 1)
 end
 
+@cast function rundef(;config::OptionA=OptionA())
+    @test config == OptionA(2, 2)
+end
+
+@main
+
 @testset "config options" begin
-    TestConfigOption.command_main(["--config.c=1", "--config.option.a=1", "--config.option.b=1"])
+    TestConfigOption.command_main(["run", "--config.c=1", "--config.option.a=1", "--config.option.b=1"])
 
     opt = TestConfigOption.OptionB(TestConfigOption.OptionA(1, 1), 1)
     to_toml("config.toml", opt)
-    TestConfigOption.command_main(["--config", "config.toml"])
-    TestConfigOption.command_main(["-c", "config.toml"])
+    TestConfigOption.command_main(["run", "--config", "config.toml"])
+    TestConfigOption.command_main(["run", "-c", "config.toml"])
 
     opt = TestConfigOption.OptionB(TestConfigOption.OptionA(1, 1), 2)
     to_toml("config.toml", opt)
-    TestConfigOption.command_main(["--config", "config.toml", "--config.c=1"])
+    TestConfigOption.command_main(["run", "--config", "config.toml", "--config.c=1"])
+
+    TestConfigOption.command_main(["rundef"])
+    TestConfigOption.command_main(["rundef", "--config.a=2"])
+    TestConfigOption.command_main(["rundef", "--config.a=2", "--config.b=2"])
 end
 
 end