Tcl Library Source Code

Check-in [36387739e6]
Login
Bounty program for improvements to Tcl and certain Tcl packages.
Tcl 2019 Conference, Houston/TX, US, Nov 4-8
Send your abstracts to [email protected]
or submit via the online form by Sep 9.

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

Overview
Comment:Beginnings of effort to rework validation to allow validators to see all results, not just the current value
Timelines: family | ancestors | amg-argparse-validation
Files: files | file ages | folders
SHA3-256:36387739e63e2402a5fc1fe37ac939e00e56f8d3dc30d1750f1cb387a1f6546a
User & Date: andy 2019-04-26 17:52:54
Context
2019-04-26
17:52
Beginnings of effort to rework validation to allow validators to see all results, not just the current value Leaf check-in: 36387739e6 user: andy tags: amg-argparse-validation
17:51
Update to-do list Leaf check-in: 9064b9d02f user: andy tags: amg-argparse
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

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
...
329
330
331
332
333
334
335

336
337
338
339
340
341
342
...
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
...
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
...
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
...
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
...
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
...
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
#
# 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} {
................................................................................
    # 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
................................................................................
        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"
                    }
                }
................................................................................
            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 {}
................................................................................

        # 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
................................................................................
            }
        }
    }
    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 -?
        }
................................................................................
                    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]
................................................................................
    # 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:






|
|




|
|
|
<
<
<

<







 







>







 







<







 







|

<
<
<
<



|



>
>
>
|







>







 







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







 







<







 







|


<
|

|



|

|










|

|




|

|




|



|

|




|

|

|









|



|

|







 







|





|





|



|
|

|






|



|
|

|




|








|
|

|




>
>
>


|

<
>

|



|
|




|
|



|
<
|
|





|
|













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



245

246
247
248
249
250
251
252
...
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
...
423
424
425
426
427
428
429

430
431
432
433
434
435
436
...
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
...
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
...
615
616
617
618
619
620
621

622
623
624
625
626
627
628
...
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
...
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
#
# 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} {
................................................................................
    # 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
................................................................................
        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"
                    }
                }
................................................................................
            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 {}
................................................................................

        # 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
................................................................................
            }
        }
    }
    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 -?
        }
................................................................................
                    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]
................................................................................
    # 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: