Eskil

Check-in [6b850237be]
Login

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

Overview
Comment:Include plugin command line options in command line help. Partially dump plugin with -help.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 6b850237be4af816ce21f5832fc5aec8494196c8
User & Date: peter 2016-07-29 19:56:29.868
Context
2016-07-29
20:14
Restructured plugin dialog. check-in: a8c940bcc5 user: peter tags: trunk
19:56
Include plugin command line options in command line help. Partially dump plugin with -help. check-in: 6b850237be user: peter tags: trunk
11:04
Updated change log with recent changes. check-in: ef18116e0d user: peter tags: trunk
Changes
Unified Diff Ignore Whitespace Patch
Changes to Changes.



1
2
3
4
5
6
7



2016-07-29
 Corrected right side numbering when parsing patch. [288be8f321]

2016-07-06
 Support negative revisions with GIT. Added log view for GIT.

2016-07-01
>
>
>







1
2
3
4
5
6
7
8
9
10
2016-07-29
 Include plugin command line options in command line help.

2016-07-29
 Corrected right side numbering when parsing patch. [288be8f321]

2016-07-06
 Support negative revisions with GIT. Added log view for GIT.

2016-07-01
Changes to htdocs/plugins.wiki.
20
21
22
23
24
25
26


27
28
29
30
31
32
33
  *  -plugin plugin     : Use plugin
  *  -plugininfo info   : Pass info to plugin (plugin specific)
  *  -plugindump plugin : Dump plugin source to stdout
  *  -pluginlist        : List known plugins
  *  -pluginallow       : Allow full access privilege for a plugin.

A plugin may further define command line options that it accepts.



<h1>General Format</h1>

A plugin is a Tcl script file that must start with the verbatim sequence
"##Eskil Plugin :". A plugin is sourced and used in its own safe
interpreter and thus have free access to its own global space. Hookup
points are defined by declaring specifically named procedures as specified







>
>







20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  *  -plugin plugin     : Use plugin
  *  -plugininfo info   : Pass info to plugin (plugin specific)
  *  -plugindump plugin : Dump plugin source to stdout
  *  -pluginlist        : List known plugins
  *  -pluginallow       : Allow full access privilege for a plugin.

A plugin may further define command line options that it accepts.
A way to see the plugin's options is to do:
<pre>eskil -plugin &lt;plg&gt; -help</pre>

<h1>General Format</h1>

A plugin is a Tcl script file that must start with the verbatim sequence
"##Eskil Plugin :". A plugin is sourced and used in its own safe
interpreter and thus have free access to its own global space. Hookup
points are defined by declaring specifically named procedures as specified
52
53
54
55
56
57
58
59


60
61
62
63
64
65
66
A plugin can declare command line options that should be accepted by Eskil.
They will be passed on to the plugin through the ::argv list.
If the initial "##Eskil" line is followed by comments formatted as below,
it adds options. Any empty line will end parsing for such lines.

A line like "## Option -<option>" declares an option that takes a value and
a line like "## Flag -<option>" declares an option without value. The rest of
the line after the option name is ignored and can be used for comments.



<h1>File plugin</h1>

To process the files being compared, the following procedure should be
defined in the plugin file:

<pre>proc PreProcess {side chi cho} {...}</pre>







|
>
>







54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
A plugin can declare command line options that should be accepted by Eskil.
They will be passed on to the plugin through the ::argv list.
If the initial "##Eskil" line is followed by comments formatted as below,
it adds options. Any empty line will end parsing for such lines.

A line like "## Option -<option>" declares an option that takes a value and
a line like "## Flag -<option>" declares an option without value. The rest of
the line after the option name is functionally ignored and can be used for
comments. It is included in command line help, so the rest should preferably
be formatted as " : Explanation" if used.

<h1>File plugin</h1>

To process the files being compared, the following procedure should be
defined in the plugin file:

<pre>proc PreProcess {side chi cho} {...}</pre>
Changes to plugins/backslash.tcl.
1
2



3
4
5
6
7
8
9
10
11
12
13
14
15
16
##Eskil Plugin : Compare with backslash-newline removed




# Example file for a plugin.
# A plugin must start exactly like this one.
# The text after : is the summary you can get at the command line

# This plugin replaces any backslash-newline with space, thus
# ignoring restructured lines.

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    set trim 0
    while {[gets $chi line] >= 0} {

|
>
>
>

|


<
<
<







1
2
3
4
5
6
7
8
9



10
11
12
13
14
15
16
##Eskil Plugin : Compare with backslash-newline removed
#
# This plugin replaces any backslash-newline with space, thus
# ignoring restructured lines.

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line




# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    set trim 0
    while {[gets $chi line] >= 0} {
Changes to plugins/binary.tcl.
1
2
3





4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
##Eskil Plugin : Compare binary files, in hex
## Option -binsep : A set of chars to be used as "newline"






# Example file for a plugin.
# A plugin must start exactly like this one.
# The text after : is the summary you can get at the command line

# A plugin may declare command line options that should be allowed through
# to ::argv

# This plugin converts files to hex to be able to compare binary files.
# A set of chars can be defined to be used as "newline". Default "0 10 13".
# Example usage:
# eskil -plugin binary -binsep "0 10 13 32" f1 f2

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    set delimitL [list 0 10 13]
    if {[llength $::Info] > 0} {


|
>
>
>
>
>

|





<
<
<
<
<







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15





16
17
18
19
20
21
22
##Eskil Plugin : Compare binary files, in hex
## Option -binsep : A set of chars to be used as "newline"
#
# This plugin converts files to hex to be able to compare binary files.
# A set of chars can be defined to be used as "newline". Default "0 10 13".
# Example usage:
# eskil -plugin binary -binsep "0 10 13 32" f1 f2

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line

# A plugin may declare command line options that should be allowed through
# to ::argv






# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    set delimitL [list 0 10 13]
    if {[llength $::Info] > 0} {
Changes to plugins/csv.tcl.
1
2
3
4





5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
##Eskil Plugin : Compare comma separated value (CSV) files
## Option -csvignore : A list of columns to ignore
## Option -csvkey    : A list of columns to sort on before comparison
## Flag   -csvheader : First line is a header line defining names of columns






# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line

# A plugin may declare command line options that should be allowed through
# to ::argv

# This plugin compares CSV files with some preprocessing available
# Example usage:
# eskil -plugin csv -csvignore "head3 head5" -csvkey head2 -sep , \
#       examples/dir*/csv1.txt

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    # Look for parameters in command line
    set opts(-sep) ","




>
>
>
>
>








<
<
<
<
<







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17





18
19
20
21
22
23
24
##Eskil Plugin : Compare comma separated value (CSV) files
## Option -csvignore : A list of columns to ignore
## Option -csvkey    : A list of columns to sort on before comparison
## Flag   -csvheader : First line is a header line defining names of columns
#
# This plugin compares CSV files with some preprocessing available
# Example usage:
# eskil -plugin csv -csvignore "head3 head5" -csvkey head2 -sep , \
#       examples/dir*/csv1.txt

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line

# A plugin may declare command line options that should be allowed through
# to ::argv






# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    # Look for parameters in command line
    set opts(-sep) ","
Changes to plugins/grep.tcl.
1
2
3




4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
##Eskil Plugin : Compare after filtering lines
## Option -grepre : Regexp to filter on





# Example file for a plugin.
# A plugin must start exactly like this one.
# The text after : is the summary you can get at the command line

# A plugin may declare command line options that should be allowed through
# to ::argv

# This plugin only compares lines that match a pattern.
# Example usage:
# eskil -plugin grep -grepre "<t>" f1 f2

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    if {[catch {llength $::Info}]} {
        puts $cho "Grep plugin needs -plugininfo parameter to be a list"


|
>
>
>
>

|





<
<
<
<







1
2
3
4
5
6
7
8
9
10
11
12
13
14




15
16
17
18
19
20
21
##Eskil Plugin : Compare after filtering lines
## Option -grepre : Regexp to filter on
#
# This plugin only compares lines that match a regexp pattern.
# Example usage:
# eskil -plugin grep -grepre "<t>" f1 f2

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line

# A plugin may declare command line options that should be allowed through
# to ::argv





# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    if {[catch {llength $::Info}]} {
        puts $cho "Grep plugin needs -plugininfo parameter to be a list"
Changes to plugins/keyword.tcl.
1
2



3
4
5
6
7
8
9
10
11
12
13
14
15
16
##Eskil Plugin : Ignore $Keywords$




# Example file for a plugin.
# A plugin must start exactly like this one.
# The text after : is the summary you can get at the command line

# This plugin ignores keywords like $Revision$, both in file diff
# and in directory diff

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    while {1} {
        # Read data in large chunks for speed

|
>
>
>

|


<
<
<







1
2
3
4
5
6
7
8
9



10
11
12
13
14
15
16
##Eskil Plugin : Ignore $Keywords$
#
# This plugin ignores keywords like $Revision$, both in file diff
# and in directory diff

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line




# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    while {1} {
        # Read data in large chunks for speed
Changes to plugins/nocase.tcl.
1
2



3
4
5
6
7
8
9
10
11
12
13
14
15
16
##Eskil Plugin : Case insensitive matching




# Example file for a plugin.
# A plugin must start exactly like this one.
# The text after : is the summary you can get at the command line

# This plugin implements case insensitive matching, similar to the
# -nocase flag.

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    while {1} {
        # Read data in large chunks for speed

|
>
>
>

|


<
<
<







1
2
3
4
5
6
7
8
9



10
11
12
13
14
15
16
##Eskil Plugin : Case insensitive matching
#
# This plugin implements case insensitive matching, similar to the
# -nocase flag.

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line




# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    while {1} {
        # Read data in large chunks for speed
Changes to plugins/pdf.tcl.
1
2
3
4
5
6
7
8
9
10




11
12
13
14
15
16
17
##Eskil Plugin : Compare text from PDF files. (needs pdftotext)

# Example file for a plugin.
# A plugin must start exactly like this one.
# The text after : is the summary you can get at the command line

# This plugin runs input through the external tool pdftotext.
# Thus it must be run together with the -pluginallow flag.
# Anything given in -plugininfo is passed as parameters to pdftotext.





# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    if {[info commands exec] eq ""} {
        puts $cho "PDF plugin must be run with privilege to be able\

|
<
<
<
<




>
>
>
>







1
2




3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
##Eskil Plugin : Compare text from PDF files. (needs pdftotext)
#




# This plugin runs input through the external tool pdftotext.
# Thus it must be run together with the -pluginallow flag.
# Anything given in -plugininfo is passed as parameters to pdftotext.

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    if {[info commands exec] eq ""} {
        puts $cho "PDF plugin must be run with privilege to be able\
Changes to plugins/sort.tcl.
1
2


3
4
5
6
7
8
9
10
11
12
13
14
15
##Eskil Plugin : Compare files after sorting lines



# Example file for a plugin.
# A plugin must start exactly like this one.
# The text after : is the summary you can get at the command line

# This plugin compares files after sorting the lines in each side

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    set data [read $chi]
    set endingNewLine 0

|
>
>

|


<
<







1
2
3
4
5
6
7
8


9
10
11
12
13
14
15
##Eskil Plugin : Compare files after sorting lines
#
# This plugin compares files after sorting the lines in each side

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line



# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    set data [read $chi]
    set endingNewLine 0
Changes to plugins/swap.tcl.
1
2



3
4
5
6
7
8
9
10
11
12
13
14
15
16
##Eskil Plugin : Swap sides of contents




# Example file for a plugin.
# A plugin must start exactly like this one.
# The text after : is the summary you can get at the command line

# This plugin swaps data between files. A fairly useless thing.
# This is to test and exemplify how to use yield in a plugin.

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    if {[info commands yield] eq ""} {
        puts $cho "Swap plugin must be run with Tcl 8.6 or newer"

|
>
>
>

|


<
<
<







1
2
3
4
5
6
7
8
9



10
11
12
13
14
15
16
##Eskil Plugin : Swap sides of contents
#
# This plugin swaps data between files. A fairly useless thing.
# This is to test and exemplify how to use yield in a plugin.

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line




# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    if {[info commands yield] eq ""} {
        puts $cho "Swap plugin must be run with Tcl 8.6 or newer"
Changes to plugins/words.tcl.
1
2


3
4
5
6
7
8
9
10
11
12
13
14
15
##Eskil Plugin : Compare set of words



# Example file for a plugin.
# A plugin must start exactly like this one.
# The text after : is the summary you can get at the command line

# This plugin compares the set of words in files.

# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    while {[gets $chi line] >= 0} {
        foreach word [regexp -all -inline {\w+} $line] {

|
>
>

|


<
<







1
2
3
4
5
6
7
8


9
10
11
12
13
14
15
##Eskil Plugin : Compare set of words
#
# This plugin compares the set of words in files.

# Example file for a plugin.
# A plugin's first line must start exactly like this one.
# The text after : is the summary you can get at the command line



# A plugin must define this procedure to do the job.
# side: left or right
# chi:  An input channel for reading the original file.
# cho:  An output channel for writing the processed file.
proc PreProcess {side chi cho} {
    while {[gets $chi line] >= 0} {
        foreach word [regexp -all -inline {\w+} $line] {
Changes to src/plugin.tcl.
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108









109
110
111
112
113
114
115
    if {[$pi eval info proc FileCompare] ne ""} {
        dict set pinfo dir 1
    }

    return $pi
}

proc printPlugin {plugin} {
    set src [LocatePlugin $plugin]
    if {$src eq ""} {
        printPlugins
        return
    }
    set ch [open $src]
    puts -nonewline [read $ch]









    close $ch
}

proc listPlugins {} {
    set dirs [PluginSearchPath]
    set result {}








|






|
>
>
>
>
>
>
>
>
>







94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
    if {[$pi eval info proc FileCompare] ne ""} {
        dict set pinfo dir 1
    }

    return $pi
}

proc printPlugin {plugin {short 0}} {
    set src [LocatePlugin $plugin]
    if {$src eq ""} {
        printPlugins
        return
    }
    set ch [open $src]
    set lines [split [read $ch] \n]
    foreach line $lines {
        set line [string trim $line]
        if {$short} {
            if {![string match "#*" $line]} {
                break
            }
        }
        puts $line
    }
    close $ch
}

proc listPlugins {} {
    set dirs [PluginSearchPath]
    set result {}

Changes to src/startup.tcl.
354
355
356
357
358
359
360




361
362
363
364
365
366
367
                set chunk [string range $d 0 $ci-1]
                set d [string trim [string range $d $ci end]]
            }
            puts "$outName$chunk"
            set outName [format %*s $indent ""]
        }
    }




}

#####################################
# Option/flag handling helpers
#####################################
# Validators
proc optValidatePdfColor {opt arg} {







>
>
>
>







354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
                set chunk [string range $d 0 $ci-1]
                set d [string trim [string range $d $ci end]]
            }
            puts "$outName$chunk"
            set outName [format %*s $indent ""]
        }
    }
    if {$::eskil(opts,src) ne ""} {
        puts ""
        printPlugin $::eskil(opts,src) 1
    }
}

#####################################
# Option/flag handling helpers
#####################################
# Validators
proc optValidatePdfColor {opt arg} {
411
412
413
414
415
416
417
418
419


420
421
422


423
424
425
426
427
428
429


430
431
432
433
434
435
436
437
438
439

440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
        exit
    }
    # Look for declarations of command line options
    set ch [open $src r]
    while {[gets $ch line] >= 0} {
        # Only look until empty line
        if {[string trim $line] eq ""} break
        if {[regexp {^\#\# Option\s+(\S+)} $line -> name]} {
            addOpt $name


        }
        if {[regexp {^\#\# Flag\s+(\S+)} $line -> name]} {
            addFlags $name


        }
    }
    close $ch
}
# Option database setup
proc initOpts {} {
    set ::eskil(opts) {}


    set ::eskil(defoptinfo) {
        flag 0
        given 0
        multi 0
        type ""
        validator ""
        filter ""
        sideeffect ""
        shortdescr ""
        longdescr ""

    } 
}
# Add a command line flag that do not take a value
proc addFlags {args} {
    foreach name $args {
        dict set ::eskil(opts) $name 0
        dict set ::eskil(opts,info) $name $::eskil(defoptinfo)
        dict set ::eskil(opts,info) $name flag  1
    }
}
# Document a flag
proc docFlag {name short {long {}}} {
    dict set ::eskil(opts,info) $name shortdescr $short
    dict set ::eskil(opts,info) $name longdescr $long
}

# Flag that affects Pref
proc addPrefFlag {name elem {value 1}} {







|

>
>

|

>
>







>
>










>










|







415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
        exit
    }
    # Look for declarations of command line options
    set ch [open $src r]
    while {[gets $ch line] >= 0} {
        # Only look until empty line
        if {[string trim $line] eq ""} break
        if {[regexp {^\#\# Option\s+(\S+)(.*)} $line -> name rest]} {
            addOpt $name
            docFlag $name "Plugin $arg [string trim $rest]"
            addSource $name $arg
        }
        if {[regexp {^\#\# Flag\s+(\S+)(.*)} $line -> name rest]} {
            addFlags $name
            docFlag $name "Plugin $arg [string trim $rest]"
            addSource $name $arg
        }
    }
    close $ch
}
# Option database setup
proc initOpts {} {
    set ::eskil(opts) {}
    set ::eskil(opts,info) {}
    set ::eskil(opts,src) ""
    set ::eskil(defoptinfo) {
        flag 0
        given 0
        multi 0
        type ""
        validator ""
        filter ""
        sideeffect ""
        shortdescr ""
        longdescr ""
        source ""
    } 
}
# Add a command line flag that do not take a value
proc addFlags {args} {
    foreach name $args {
        dict set ::eskil(opts) $name 0
        dict set ::eskil(opts,info) $name $::eskil(defoptinfo)
        dict set ::eskil(opts,info) $name flag  1
    }
}
# Document a flag or option
proc docFlag {name short {long {}}} {
    dict set ::eskil(opts,info) $name shortdescr $short
    dict set ::eskil(opts,info) $name longdescr $long
}

# Flag that affects Pref
proc addPrefFlag {name elem {value 1}} {
488
489
490
491
492
493
494





495
496
497
498
499
500
501
proc addValidator {name cmd} {
    dict set ::eskil(opts,info) $name validator $cmd
}
# Add a filter command prefix to an Opt
proc addFilter {name cmd} {
    dict set ::eskil(opts,info) $name filter $cmd
}





# Add a sideeffect to an Opt
##nagelfar syntax addSideEffect x c
proc addSideEffect {name script} {
    dict set ::eskil(opts,info) $name sideeffect $script
}
# Add a command line option that takes a value and stores in local opts
proc addOptsOpt {name elem {validator ""}} {







>
>
>
>
>







499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
proc addValidator {name cmd} {
    dict set ::eskil(opts,info) $name validator $cmd
}
# Add a filter command prefix to an Opt
proc addFilter {name cmd} {
    dict set ::eskil(opts,info) $name filter $cmd
}
# Add a source reference to an Opt
proc addSource {name src} {
    set ::eskil(opts,src) $src
    dict set ::eskil(opts,info) $name source $src
}
# Add a sideeffect to an Opt
##nagelfar syntax addSideEffect x c
proc addSideEffect {name script} {
    dict set ::eskil(opts,info) $name sideeffect $script
}
# Add a command line option that takes a value and stores in local opts
proc addOptsOpt {name elem {validator ""}} {