Tcl Library Source Code

Changes On Branch amg-argparse-validation
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch amg-argparse-validation Excluding Merge-Ins

This is equivalent to a diff from eccc25a0c4 to b8b70870c9

2019-11-24
21:25
Merge amg-argparse Leaf check-in: b8b70870c9 user: andy tags: amg-argparse-validation
21:24
Merge trunk Leaf check-in: eccc25a0c4 user: andy tags: amg-argparse
2019-11-23
05:02
1. Integrated markdown work. Updated local documentation. 2. Reworked the collection and post processing of per-test(suite) timings added in commit [6b2f59f4e4] for faster sorting. Further fixed an issue with the collection of the per-test timings in the face of variable-field data. 3. Switching the 8.6 series from 8.6.9 to 8.6.10 for testing caused failures (hook, string::token::shell) due to changes in command name reporting (FQN in a few places where plain names were reported before). This actually looks to be a bug fix, restoring 8.5 behaviour. These testsuite issues were fixed by extending the test code used to select the expected result by core version. Added a new test utility command `byConstraint` to help with that, allowing for easy multi-way selection. check-in: b99647b031 user: aku tags: trunk
2019-04-26
17:52
Beginnings of effort to rework validation to allow validators to see all results, not just the current value check-in: 36387739e6 user: andy tags: amg-argparse-validation
17:51
Update to-do list check-in: 9064b9d02f user: andy tags: amg-argparse

Changes to modules/argparse/argparse.tcl.

229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
#
# After switch processing, parameter allocation determines how many arguments to
# assign to each parameter.  Arguments assigned to switches are not used in
# parameter processing.  First, arguments are allocated to required parameters;
# second, to optional, non-catchall parameters; and last to catchall parameters.
# Finally, each parameter is assigned the allocated number of arguments.
proc ::argparse {args} {
    # Common validation helper routine.
    set validateHelper {apply {{name opt args} {
        if {[dict exists $opt enum]} {
            set command [list tcl::prefix match -message "$name value"\
                    {*}[if {[uplevel 1 {info exists exact}]} {list -exact}]\
                    [dict get $opt enum]]
            set args [lmap arg $args {{*}$command $arg}]
        } elseif {[dict exists $opt validate]} {
            foreach arg $args [list if [dict get $opt validate] {} else {
                return -code error -level 2\
                        "$name value \"$arg\" fails [dict get $opt validateMsg]"
            }]
        }
        return $args
    }}}

    # Process arguments.
    set level 1
    set enum {}
    set validate {}
    for {set i 0} {$i < [llength $args]} {incr i} {







|
|




|
|
<
|
<
<

<







229
230
231
232
233
234
235
236
237
238
239
240
241
242
243

244


245

246
247
248
249
250
251
252
#
# After switch processing, parameter allocation determines how many arguments to
# assign to each parameter.  Arguments assigned to switches are not used in
# parameter processing.  First, arguments are allocated to required parameters;
# second, to optional, non-catchall parameters; and last to catchall parameters.
# Finally, each parameter is assigned the allocated number of arguments.
proc ::argparse {args} {
    # Validation and enumeration processing helper routine.
    set enumHelper {apply {{name opt args} {
        if {[dict exists $opt enum]} {
            set command [list tcl::prefix match -message "$name value"\
                    {*}[if {[uplevel 1 {info exists exact}]} {list -exact}]\
                    [dict get $opt enum]]
            lmap arg $args {{*}$command $arg}
        } else {

            return $args


        }

    }}}

    # Process arguments.
    set level 1
    set enum {}
    set validate {}
    for {set i 0} {$i < [llength $args]} {incr i} {
329
330
331
332
333
334
335

336
337
338
339
340
341
342
    # Parse element definition list.
    set def {}
    set aliases {}
    set order {}
    set switches {}
    set upvars {}
    set omitted {}

    foreach elem $definition {
        # Read element definition switches.
        set opt {}
        for {set i 1} {$i < [llength $elem]} {incr i} {
            if {[set switch [regsub {^-} [tcl::prefix match {
                -alias -argument -boolean -catchall -default -enum -forbid
                -ignore -imply -keep -key -level -optional -parameter -pass







>







325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
    # Parse element definition list.
    set def {}
    set aliases {}
    set order {}
    set switches {}
    set upvars {}
    set omitted {}
    set validations {}
    foreach elem $definition {
        # Read element definition switches.
        set opt {}
        for {set i 1} {$i < [llength $elem]} {incr i} {
            if {[set switch [regsub {^-} [tcl::prefix match {
                -alias -argument -boolean -catchall -default -enum -forbid
                -ignore -imply -keep -key -level -optional -parameter -pass
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
        foreach {switch others} {
            parameter {alias boolean value argument imply}
            ignore    {key pass}
            required  {boolean default}
            argument  {boolean value}
            upvar     {boolean inline catchall}
            boolean   {default value}
            enum      validate
        } {
            if {[dict exists $opt $switch]} {
                foreach other $others {
                    if {[dict exists $opt $other]} {
                        return -code error "-$switch and -$other conflict"
                    }
                }







<







423
424
425
426
427
428
429

430
431
432
433
434
435
436
        foreach {switch others} {
            parameter {alias boolean value argument imply}
            ignore    {key pass}
            required  {boolean default}
            argument  {boolean value}
            upvar     {boolean inline catchall}
            boolean   {default value}

        } {
            if {[dict exists $opt $switch]} {
                foreach other $others {
                    if {[dict exists $opt $other]} {
                        return -code error "-$switch and -$other conflict"
                    }
                }
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529



530
531
532
533
534
535
536
537

538
539
540
541
542
543
544
            return -code error "element alias collision: [dict get $opt alias]"
        } else {
            # Build list of switches (with aliases), and link switch aliases.
            dict set aliases [dict get $opt alias] $name
            lappend switches -[dict get $opt alias]|$name
        }

        # Map from upvar keys back to element names, and forbid collisions.
        if {[dict exists $opt upvar] && [dict exists $opt key]} {
            if {[dict exists $upvars [dict get $opt key]]} {
                return -code error "multiple upvars to the same variable:\
                        [dict get $upvars [dict get $opt key]] $name"
            }
            dict set upvars [dict get $opt key] $name
        }

        # Look up named enumeration lists and validation expressions.
        if {[dict exists $opt enum]
         && [dict exists $enum [dict get $opt enum]]} {
            dict set opt enum [dict get $enum [dict get $opt enum]]



        } elseif {[dict exists $opt validate]} {
            if {[dict exists $validate [dict get $opt validate]]} {
                dict set opt validateMsg "[dict get $opt validate] validation"
                dict set opt validate [dict get $validate\
                        [dict get $opt validate]]
            } else {
                dict set opt validateMsg "validation: [dict get $opt validate]"
            }

        }

        # Save element definition.
        dict set def $name $opt

        # Prepare to identify omitted elements.
        dict set omitted $name {}







|

<
<
<
<



|



>
>
>
|







>







506
507
508
509
510
511
512
513
514




515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
            return -code error "element alias collision: [dict get $opt alias]"
        } else {
            # Build list of switches (with aliases), and link switch aliases.
            dict set aliases [dict get $opt alias] $name
            lappend switches -[dict get $opt alias]|$name
        }

        # Map from upvar keys back to element names.
        if {[dict exists $opt upvar] && [dict exists $opt key]} {




            dict set upvars [dict get $opt key] $name
        }

        # Resolve named enumeration lists.
        if {[dict exists $opt enum]
         && [dict exists $enum [dict get $opt enum]]} {
            dict set opt enum [dict get $enum [dict get $opt enum]]
        }

        # Resolve validation expressions.
        if {[dict exists $opt validate]} {
            if {[dict exists $validate [dict get $opt validate]]} {
                dict set opt validateMsg "[dict get $opt validate] validation"
                dict set opt validate [dict get $validate\
                        [dict get $opt validate]]
            } else {
                dict set opt validateMsg "validation: [dict get $opt validate]"
            }
            dict set validations $name {}
        }

        # Save element definition.
        dict set def $name $opt

        # Prepare to identify omitted elements.
        dict set omitted $name {}
570
571
572
573
574
575
576

577
578
579
580
581
582
583
584


585
586
587
588
589

590
591
592
593
594
595
596

        # Perform shared key logic.
        if {[dict exists $opt key]} {
            dict for {otherName otherOpt} $def {
                if {$name ne $otherName && [dict exists $otherOpt key]
                 && [dict get $otherOpt key] eq [dict get $opt key]} {
                    # Limit when shared keys may be used.

                    if {[dict exists $opt parameter]} {
                        return -code error "$name cannot be a parameter because\
                                it shares a key with $otherName"
                    } elseif {[dict exists $opt argument]} {
                        return -code error "$name cannot use -argument because\
                                it shares a key with $otherName"
                    } elseif {[dict exists $opt catchall]} {
                        return -code error "$name cannot use -catchall because\


                                it shares a key with $otherName"
                    } elseif {[dict exists $opt default]
                           && [dict exists $otherOpt default]} {
                        return -code error "$name and $otherName cannot both\
                                use -default because they share a key"

                    }

                    # Create forbid constraints on shared keys.
                    if {![dict exists $otherOpt forbid]
                     || $name ni [dict get $otherOpt forbid]} {
                        dict update def $otherName otherOpt {
                            dict lappend otherOpt forbid $name







>
|
<
<
<
|
|
<
<
>
>
|
|
|
|
|
>







566
567
568
569
570
571
572
573
574



575
576


577
578
579
580
581
582
583
584
585
586
587
588
589
590
591

        # Perform shared key logic.
        if {[dict exists $opt key]} {
            dict for {otherName otherOpt} $def {
                if {$name ne $otherName && [dict exists $otherOpt key]
                 && [dict get $otherOpt key] eq [dict get $opt key]} {
                    # Limit when shared keys may be used.
                    foreach key {parameter argument catchall} {
                        if {[dict exists $opt $key]} {



                            return -code error "$name cannot use -$key because\
                                    it shares a key with $otherName"


                        }
                    }
                    foreach key {default validate upvar} {
                        if {[dict exists $opt $key]
                         && [dict exists $otherOpt $key]} {
                            return -code error "$name and $otherName cannot both\
                                    use -$key because they share a key"
                        }
                    }

                    # Create forbid constraints on shared keys.
                    if {![dict exists $otherOpt forbid]
                     || $name ni [dict get $otherOpt forbid]} {
                        dict update def $otherName otherOpt {
                            dict lappend otherOpt forbid $name
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
            }
        }
    }
    set force [lreplace $argv 0 $end]
    set argv [lrange $argv 0 $end]

    # Perform switch logic.
    set result {}
    set missing {}
    if {$switches ne {}} {
        # Build regular expression to match switches.
        set re ^-
        if {[info exists long]} {
            append re -?
        }







<







615
616
617
618
619
620
621

622
623
624
625
626
627
628
            }
        }
    }
    set force [lreplace $argv 0 $end]
    set argv [lrange $argv 0 $end]

    # Perform switch logic.

    set missing {}
    if {$switches ne {}} {
        # Build regular expression to match switches.
        set re ^-
        if {[info exists long]} {
            append re -?
        }
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
                    set $var [dict get $def $name $var]
                }
            }

            # Keep track of which switches have been seen.
            dict unset omitted $name

            # Validate switch arguments and store values into the result dict.
            if {[dict exists $def $name catchall]} {
                # The switch is catchall, so store all remaining arguments.
                set argv [{*}$validateHelper $normal\
                        [dict get $def $name] {*}$argv]
                if {[info exists key]} {
                    dict set result $key $argv
                }
                if {[info exists pass]} {
                    if {[info exists normalize]} {
                        dict lappend result $pass $normal {*}$argv
                    } else {
                        dict lappend result $pass $arg {*}$argv
                    }
                }
                break
            } elseif {![dict exists $def $name argument]} {
                # The switch expects no arguments.
                if {$equal eq "="} {
                    return -code error "$normal doesn't allow an argument"
                }
                if {[info exists key]} {
                    if {[dict exists $def $name value]} {
                        dict set result $key [dict get $def $name value]
                    } else {
                        dict set result $key {}
                    }
                }
                if {[info exists pass]} {
                    if {[info exists normalize]} {
                        dict lappend result $pass $normal
                    } else {
                        dict lappend result $pass $arg
                    }
                }
            } elseif {$argv ne {}} {
                # The switch was given the expected argument.
                set argv0 [lindex [{*}$validateHelper $normal\
                        [dict get $def $name] [lindex $argv 0]] 0]
                if {[info exists key]} {
                    if {[dict exists $def $name optional]} {
                        dict set result $key [list {} $argv0]
                    } else {
                        dict set result $key $argv0
                    }
                }
                if {[info exists pass]} {
                    if {[info exists normalize]} {
                        dict lappend result $pass $normal $argv0
                    } elseif {$equal eq "="} {
                        dict lappend result $pass $arg
                    } else {
                        dict lappend result $pass $arg [lindex $argv 0]
                    }
                }
                set argv [lrange $argv 1 end]
            } else {
                # The switch was not given the expected argument.
                if {![dict exists $def $name optional]} {
                    return -code error "$normal requires an argument"
                }
                if {[info exists key]} {
                    dict set result $key {}
                }
                if {[info exists pass]} {
                    if {[info exists normalize]} {
                        dict lappend result $pass $normal
                    } else {
                        dict lappend result $pass $arg
                    }
                }
            }

            # Insert this switch's implied arguments into the argument list.
            if {[dict exists $def $name imply]} {
                set argv [concat [dict get $def $name imply] $argv]







|


<
|

|



|

|










|

|




|

|




|



|

|




|

|

|









|



|

|







722
723
724
725
726
727
728
729
730
731

732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
                    set $var [dict get $def $name $var]
                }
            }

            # Keep track of which switches have been seen.
            dict unset omitted $name

            # Validate switch arguments and store values into the result array.
            if {[dict exists $def $name catchall]} {
                # The switch is catchall, so store all remaining arguments.

                set argv [{*}$enumHelper $normal [dict get $def $name] {*}$argv]
                if {[info exists key]} {
                    set result($key) $argv
                }
                if {[info exists pass]} {
                    if {[info exists normalize]} {
                        lappend result($pass) $normal {*}$argv
                    } else {
                        lappend result($pass) $arg {*}$argv
                    }
                }
                break
            } elseif {![dict exists $def $name argument]} {
                # The switch expects no arguments.
                if {$equal eq "="} {
                    return -code error "$normal doesn't allow an argument"
                }
                if {[info exists key]} {
                    if {[dict exists $def $name value]} {
                        set result($key) [dict get $def $name value]
                    } else {
                        set result($key) {}
                    }
                }
                if {[info exists pass]} {
                    if {[info exists normalize]} {
                        lappend result($pass) $normal
                    } else {
                        lappend result($pass) $arg
                    }
                }
            } elseif {$argv ne {}} {
                # The switch was given the expected argument.
                set argv0 [lindex [{*}$enumHelper $normal\
                        [dict get $def $name] [lindex $argv 0]] 0]
                if {[info exists key]} {
                    if {[dict exists $def $name optional]} {
                        set result($key) [list {} $argv0]
                    } else {
                        set result($key) $argv0
                    }
                }
                if {[info exists pass]} {
                    if {[info exists normalize]} {
                        lappend result($pass) $normal $argv0
                    } elseif {$equal eq "="} {
                        lappend result($pass) $arg
                    } else {
                        lappend result($pass) $arg [lindex $argv 0]
                    }
                }
                set argv [lrange $argv 1 end]
            } else {
                # The switch was not given the expected argument.
                if {![dict exists $def $name optional]} {
                    return -code error "$normal requires an argument"
                }
                if {[info exists key]} {
                    set result($key) {}
                }
                if {[info exists pass]} {
                    if {[info exists normalize]} {
                        lappend result($pass) $normal
                    } else {
                        lappend result($pass) $arg
                    }
                }
            }

            # Insert this switch's implied arguments into the argument list.
            if {[dict exists $def $name imply]} {
                set argv [concat [dict get $def $name imply] $argv]
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987



988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
    # all omitted switches that have a pass-through key, accept an argument, and
    # have a default value.
    if {[info exists normalize]} {
        dict for {name opt} $def {
            if {[dict exists $opt switch] && [dict exists $opt pass]
             && [dict exists $opt argument] && [dict exists $opt default]
             && [dict exists $omitted $name]} {
                dict lappend result [dict get $opt pass]\
                        -$name [dict get $opt default]
            }
        }
    }

    # Validate parameters and store in result dict.
    set i 0
    foreach name $order {
        set opt [dict get $def $name]
        if {[dict exists $alloc $name]} {
            if {![dict exists $opt catchall] && $name ne {}} {
                set val [lindex [{*}$validateHelper $name\
                        $opt [lindex $params $i]] 0]
                if {[dict exists $opt pass]} {
                    if {[string index $val 0] eq "-"
                     && ![dict exists $result [dict get $opt pass]]} {
                        dict lappend result [dict get $opt pass] --
                    }
                    dict lappend result [dict get $opt pass] $val
                }
                incr i
            } else {
                set step [dict get $alloc $name]
                set val [lrange $params $i [expr {$i + $step - 1}]]
                if {$name ne {}} {
                    set val [{*}$validateHelper $name $opt {*}$val]
                }
                if {[dict exists $opt pass]} {
                    if {[string index [lindex $val 0] 0] eq "-"
                     && ![dict exists $result [dict get $opt pass]]} {
                        dict lappend result [dict get $opt pass] --
                    }
                    dict lappend result [dict get $opt pass] {*}$val
                }
                incr i $step
            }
            if {[dict exists $opt key]} {
                dict set result [dict get $opt key] $val
            }
        } elseif {[info exists normalize] && [dict exists $opt default]
               && [dict exists $opt pass]} {
            # If normalization is enabled and this omitted parameter has both a
            # default value and a pass-through key, explicitly store the default
            # value in the pass-through key, located in the correct position so
            # that it can be recognized again later.
            if {[string index [dict get $opt default] 0] eq "-"
             && ![dict exists $result [dict get $opt pass]]} {
                dict lappend result [dict get $opt pass] --
            }
            dict lappend result [dict get $opt pass] [dict get $opt default]
        }
    }

    # Create default values for missing elements.



    dict for {name opt} $def {
        if {[dict exists $opt key]
         && ![dict exists $result [dict get $opt key]]} {
            if {[dict exists $opt default]} {
                dict set result [dict get $opt key] [dict get $opt default]
            } elseif {[dict exists $opt catchall]} {
                dict set result [dict get $opt key] {}
            }
        }
        if {[dict exists $opt pass]
         && ![dict exists $result [dict get $opt pass]]} {
            dict set result [dict get $opt pass] {}
        }
    }

    if {[info exists inline]} {
        # Return result dict.
        return $result
    } else {
        # Unless -keep was used, unset caller variables for omitted elements.
        if {![info exists keep]} {
            dict for {name val} $omitted {
                set opt [dict get $def $name]
                if {![dict exists $opt keep] && [dict exists $opt key]
                 && ![dict exists $result [dict get $opt key]]} {
                    uplevel 1 [list ::unset -nocomplain [dict get $opt key]]
                }
            }
        }

        # Process results.
        dict for {key val} $result {
            if {[dict exists $upvars $key]} {
                # If this element uses -upvar, link to the named variable.
                uplevel 1 [list ::upvar\
                        [dict get $def [dict get $upvars $key] level] $val $key]
            } else {
                # Store result into caller variables.
                uplevel 1 [list ::set $key $val]
            }
        }
    }
}

# vim: set sts=4 sw=4 tw=80 et ft=tcl:







|





|





|



|
|

|






|



|
|

|




|








|
|

|




>
>
>


|

|

|



|
|




|
|



|
<
|
|





|
|













919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005

1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
    # all omitted switches that have a pass-through key, accept an argument, and
    # have a default value.
    if {[info exists normalize]} {
        dict for {name opt} $def {
            if {[dict exists $opt switch] && [dict exists $opt pass]
             && [dict exists $opt argument] && [dict exists $opt default]
             && [dict exists $omitted $name]} {
                lappend result([dict get $opt pass])\
                        -$name [dict get $opt default]
            }
        }
    }

    # Apply enumeration logic and store parameters in result array.
    set i 0
    foreach name $order {
        set opt [dict get $def $name]
        if {[dict exists $alloc $name]} {
            if {![dict exists $opt catchall] && $name ne {}} {
                set val [lindex [{*}$enumHelper $name\
                        $opt [lindex $params $i]] 0]
                if {[dict exists $opt pass]} {
                    if {[string index $val 0] eq "-"
                     && ![info exists result([dict get $opt pass])]} {
                        lappend result([dict get $opt pass]) --
                    }
                    lappend result([dict get $opt pass]) $val
                }
                incr i
            } else {
                set step [dict get $alloc $name]
                set val [lrange $params $i [expr {$i + $step - 1}]]
                if {$name ne {}} {
                    set val [{*}$enumHelper $name $opt {*}$val]
                }
                if {[dict exists $opt pass]} {
                    if {[string index [lindex $val 0] 0] eq "-"
                     && ![info exists result([dict get $opt pass])]} {
                        lappend result([dict get $opt pass]) --
                    }
                    lappend result([dict get $opt pass]) {*}$val
                }
                incr i $step
            }
            if {[dict exists $opt key]} {
                set result([dict get $opt key]) $val
            }
        } elseif {[info exists normalize] && [dict exists $opt default]
               && [dict exists $opt pass]} {
            # If normalization is enabled and this omitted parameter has both a
            # default value and a pass-through key, explicitly store the default
            # value in the pass-through key, located in the correct position so
            # that it can be recognized again later.
            if {[string index [dict get $opt default] 0] eq "-"
             && ![info exists result([dict get $opt pass])]} {
                lappend result([dict get $opt pass]) --
            }
            lappend result([dict get $opt pass]) [dict get $opt default]
        }
    }

    # Create default values for missing elements.
    # TODO: Build list of defaults that were added.  Skip validation for these
    # keys because defaults may well be intended to be outside the allowable
    # range for explicitly specified values.
    dict for {name opt} $def {
        if {[dict exists $opt key]
         && ![info exists result([dict get $opt key])]} {
            if {[dict exists $opt default]} {
                set result([dict get $opt key]) [dict get $opt default]
            } elseif {[dict exists $opt catchall]} {
                set result([dict get $opt key]) {}
            }
        }
        if {[dict exists $opt pass]
         && ![info exists result([dict get $opt pass])]} {
            set result([dict get $opt pass]) {}
        }
    }

    if {[info exists inline]} {
        # When -inline is used, return result array, converted to be a dict.
        array get result
    } else {
        # Unless -keep was used, unset caller variables for omitted elements.
        if {![info exists keep]} {
            dict for {name opt} $def {

                if {[dict exists $opt key] && ![dict exists $opt keep]
                 && ![info exists result([dict get $opt key])]} {
                    uplevel 1 [list ::unset -nocomplain [dict get $opt key]]
                }
            }
        }

        # Update caller variables to store results.
        foreach {key val} [array get result] {
            if {[dict exists $upvars $key]} {
                # If this element uses -upvar, link to the named variable.
                uplevel 1 [list ::upvar\
                        [dict get $def [dict get $upvars $key] level] $val $key]
            } else {
                # Store result into caller variables.
                uplevel 1 [list ::set $key $val]
            }
        }
    }
}

# vim: set sts=4 sw=4 tw=80 et ft=tcl: