File: modules.tcl

package info (click to toggle)
moodss 19.7-1
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 6,136 kB
  • ctags: 3,149
  • sloc: tcl: 49,048; ansic: 187; perl: 178; makefile: 166; sh: 109; python: 65
file content (658 lines) | stat: -rw-r--r-- 38,207 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
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
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
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
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
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
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
# copyright (C) 1997-2005 Jean-Luc Fontaine (mailto:jfontain@free.fr)
# this program is free software: please read the COPYRIGHT file enclosed in this package or use the Help Copyright menu

# $Id: modules.tcl,v 2.106 2005/02/21 20:55:11 jfontain Exp $


class modules {

    class instance {

        proc instance {this module index} {
            set ($this,module) $module
            set ($this,loaded) [new module $module $index]
        }

        proc ~instance {this} {
            delete $($this,loaded)
        }

        proc load {this} {                               ;# load module since its switches need to be known for command line parsing
            set loaded $($this,loaded)
            module::load $loaded
            set namespace $module::($loaded,namespace)
            set ($this,namespace) $namespace
            if {[info exists ::${namespace}::data(switches)]} {                                        ;# module may take no options
                array set switch [set ::${namespace}::data(switches)]
                if {[info exists switch(--daemon)] && ($switch(--daemon) != 0)} {
                    error {--daemon option must not take any argument}
                }
                set ($this,switches) [set ::${namespace}::data(switches)]
            }
            set ($this,initialize) $module::($loaded,initialize)
            set ($this,version) $module::($loaded,version)
            initialize $this
        }

        proc initialize {this} {
            set namespace $($this,namespace)
            set ($this,identifier) [set ${namespace}::data(identifier)]                  ;# (always exists, set when loading module)
            if {![modules::validName $($this,identifier)]} {
                foreach {name index} [modules::decoded $namespace] {}
                puts stderr "\"$name\" module identifier: \"$($this,identifier)\" contains invalid characters"
                exit 1
            }
            catch {set ($this,times) [set ${namespace}::data(pollTimes)]}                          ;# may not be available initially
            catch {set ($this,views) [set ${namespace}::data(views)]}                                     ;# there could be no views
        }

        proc synchronize {this} {
            module::synchronize $($this,loaded)
            initialize $this   ;# reinitialize in case a few variables have been set or reset in the module initialization procedure
        }

        proc empty {this} {
            module::clear $($this,loaded)
        }

    }


    set (instances) {}

    proc modules {this} error                                                                                   ;# object-less class

    proc source {interpreter package file} {
        switch [file extension $file] {
            .py {
                # the module is written in Python
                # if we got here, act as if the package was provided
                if {[catch {package require tclpython 3}]} return                                              ;# cannot load Python
                set python [python::interp new]                                             ;# create a temporary Python interpreter
                set code [catch {                                                                         ;# stop at the first error
                    $python exec "import sys\nsys.path.insert(0, '.')"      ;# so that module can be imported from current directory
                    # import the Python module, create a Tcl namespace and eventually copy a few data members:
                    $python exec {import re}                             ;# regular expressions are required by internal Python code
                    $python exec "import $package"
                    $python exec $module::python::utilityFunctions
                    array set data [$python eval formstring($package.form)]                  ;# retrieve the module static data part
                    foreach name {helpText switches updates} {
                        catch {$interpreter eval [list namespace eval $package [list set data($name) $data($name)]]}
                    }
                    $interpreter eval "package provide $package [$python eval $package.__version__]"                   ;# must exist
                } message]
                python::interp delete $python                                                       ;# delete the Python interpreter
                if {$code} {                                                                     ;# there was an error, so report it
                    error $message $::errorInfo $code
                }
            }
            .pm {
                # the module is written in Perl
                # if we got here, act as if the package was provided
                if {[catch {package require tclperl 3}] && [catch {package require tclperl 2}]} return   ;# cannot load Perl modules
                set perl [perl::interp new]                                                   ;# create a temporary Perl interpreter
                set code [catch {                                                                         ;# stop at the first error
                    $perl eval "use $package"  ;# use the Perl module, create a Tcl namespace and eventually copy a few data members
                    $perl eval $module::perl::utilities
                    array set data [$perl eval hash_string(%${package}::data)]               ;# retrieve the module static data part
                    foreach name {helpText switches updates} {
                        catch {$interpreter eval [list namespace eval $package [list set data($name) $data($name)]]}
                    }
                    $interpreter eval "package provide $package [$perl eval \$${package}::VERSION]"                    ;# must exist
                } message]
                perl::interp delete $perl                                                             ;# delete the Perl interpreter
                if {$code} {                                                                     ;# there was an error, so report it
                    error $message $::errorInfo $code
                }
            }
            default {
                # normal sourcing
                $interpreter eval _source [list $file]
            }
        }
    }

    # using Tcl built-in package management facilities, seek available moodss modules
    # in commands string, %M is replaced by module name and %S by switches list from module code
    proc available {{command {}} {scanCommand {}}} {
        set directory [pwd]
        set packages {}
        foreach package [package names] {
            if {[string match *::* $package]} continue                                                    ;# filter out sub-packages
            if {![info exists ::package(directory,$package)]} continue                                       ;# for Tcl, for example
            switch $package {instance - formulas continue}                                                  ;# skip internal modules
            if {!$global::debug && ![string match *moodss* $::package(directory,$package)]} {
                continue                      ;# when not debugging (developing), avoid potentially core crashing non moodss modules
            }
            if {[string length $scanCommand] > 0} {
                regsub -all %M $scanCommand $package string
                uplevel #0 $string                                                          ;# always invoke command at global level
            }
            cd $::package(directory,$package)                                ;# switch to module directory only during loading phase
            set interpreter [interp create]              ;# use a separate interpreter in order not to interfere with loaded modules
            $interpreter eval "set auto_path [list $::auto_path]"                    ;# set packages paths list in child interpreter
            catch {$interpreter eval {package require {}}}  ;# preload all packages locations (many pkgIndex.tcl files sourced here)
            # then intercept source command to be able to detect non Tcl modules:
            $interpreter eval {rename source _source}; $interpreter alias source ::modules::source $interpreter $package
            if {[info exists ::package(exact,$package)]} {                                         ;# a specific version is required
                set error [catch {$interpreter eval "package require -exact $package $::package(version,$package)"}]
            } else {
                set error [catch {$interpreter eval "package require $package"}]
            }
            if {!$error && [$interpreter eval info exists ::${package}::data(updates)]} {                 ;# ignore invalid packages
                lappend packages $package
                set switches {}                                                                          ;# there may be no switches
                # module package name and module namespace are identical
                catch {set switches [$interpreter eval "set ::${package}::data(switches)"]}
                set switches [list $switches]                                                                ;# make it a valid list
                if {[string length $command] > 0} {
                    regsub -all %M $command $package string
                    regsub -all %S $string $switches string
                    uplevel #0 $string                                                      ;# always invoke command at global level
                }
            }
            interp delete $interpreter
        }
        cd $directory                                                                                   ;# restore current directory
        return [lsort $packages]
    }

    proc printAvailable {} {            ;# using Tcl built-in package management facilities, seek and print available moodss modules
        puts {searching for module packages, please wait...}
        foreach package [available] {
            puts -nonewline "$package: possibly in"
            set count 0
            foreach directory $::auto_path {
                if {[file readable [file join $directory $package pkgIndex.tcl]]} {
                    if {$count > 0} {
                        puts -nonewline ,
                    }
                    puts -nonewline " [file join $directory $package]"
                    incr count
                }
            }
            puts {}
        }
    }

    # recursive procedure: eventually initialize next module and its eventual options in command line arguments
    proc parse {arguments} {                  ;# arguments list format is: module [-option [value] -option ...] module [-option ...]
        if {[llength $arguments] == 0} return                                                                                ;# done
        set name [lindex $arguments 0]
        set arguments [lrange $arguments 1 end]                                         ;# point to start of switches or next module
        foreach {name index} [decoded $name] {}    ;# possibly split module into its name and its index (if coming from a save file)
        # note: if coming from a dashboard file from moodss before 19.0, index is empty for the first instance of a module
        if {![info exists ::package(directory,$name)]} {                              ;# not a valid module (usually a wrong switch)
            error "error: \"$name\" is not a valid moodss module name"
        }
        if {![validName $name]} {
            error "\"$name\" module name contains invalid characters"
        }
        switch $name formulas - thresholds {
            error "\"$name\" is a reserved module name"
        }
        if {$global::withGUI} {
            lifoLabel::push $global::messenger [format [mc {loading %s...}] $name]
        } elseif {$global::debug} {
            writeLog "loading $name..."
        }
        set instance [new instance $name $index]
        if {[catch {instance::load $instance} message]} {   ;# load module since its switches need be known for command line parsing
            if {$global::debug} {set information $::errorInfo}
            if {$global::withGUI} {
                lifoLabel::pop $global::messenger
            }
            delete $instance                                                                                             ;# clean up
            if {$global::debug} {
                error $information
            } else {
                error "module \"$name\" load error:\n$message"
            }
        }
        if {$global::withGUI} {
            lifoLabel::pop $global::messenger
        }
        set help [expr {[lsearch -exact $arguments --help] >= 0}]                                       ;# help requested for module
        if {[info exists instance::($instance,switches)]} {                                                  ;# module takes options
            if {[llength $instance::($instance,switches)] == 0} {
                error "module \"$name\" switches are empty"             ;# design error: no need to recover when dynamically loading
            }
            if {$help} {
                displayHelpMessage $name $instance::($instance,switches)
                exit
            }
            if {[catch {set next [parseCommandLineArguments $instance::($instance,switches) $arguments options]} message]} {
                delete $instance                                                                                         ;# clean up
                error "module \"$name\" options error: $message"
            }
            if {!$instance::($instance,initialize)} {
                error "module \"$name\" has no initialize procedure"    ;# design error: no need to recover when dynamically loading
            }
            set instance::($instance,options) [array get options]
            # save module arguments for saving in file if necessary
            set instance::($instance,arguments) [lrange $arguments 0 [expr {[llength $arguments] - [llength $next] - 1}]]
            set arguments $next
        } else {                                                                                          ;# module takes no options
            if {$help} {
                displayHelpMessage $name
                exit
            }
            set instance::($instance,arguments) {}                          ;# save module arguments for saving in file if necessary
        }
        lappend (instances) $instance                                              ;# add module to successfully loaded modules list
        parse $arguments                                                                                         ;# process the rest
        if {$global::withGUI} {
            update idletasks                                   ;# make sure latest loading message is not left showing meaninglessly
        }
    }

    proc helpHTMLData {name} {                                                                          ;# module name with no index
        set noHelpText [mc {no help available}]                                          ;# in case module code does not handle help
        foreach instance $(instances) {
            set namespace $instance::($instance,namespace)
            foreach {module index} [decoded $namespace] {}
            if {[string compare $module $name]} continue
            if {![info exists text]} {                                             ;# retrieve help text from first module namespace
                set text $noHelpText
                catch {set text [set ${namespace}::data(helpText)]}
                set version $instance::($instance,version)
                break
            }
        }
        if {![info exists text]} {                                    ;# no loaded module of that name: retrieve data in another way
            foreach {version text} [versionAndHelpText $name] {}
            if {[string length $text] == 0} {
                set text $noHelpText
            }
        }
        set header [format [mc {<b>%s</b> module version <i>%s</i>}] $name $version]
        append header <br><br>
        if {[regsub -nocase <body> $text <body>$header text] > 0} {                          ;# insert header if HTML formatted help
            # possibly remove title which appears on viewer, since we already generated a title
            regsub -nocase {<title>.*</title>} $text {} text
            return $text
        } else {
            regsub -all \n $text <br> text                                                 ;# regular help, keep original formatting
            return ${header}$text
        }
    }

    proc versionAndHelpText {name} {                                               ;# returns module version and help text in a list
        set directory [pwd]
        cd $::package(directory,$name)                                       ;# switch to module directory only during loading phase
        set interpreter [interp create]                  ;# use a separate interpreter in order not to interfere with loaded modules
        $interpreter eval "set auto_path [list $::auto_path]"         ;# duplicate in case module interpreter requires some packages
        catch {$interpreter eval {package require {}}}                                                            ;# see available{}
        $interpreter eval {rename source _source}; $interpreter alias source ::modules::source $interpreter $name
        $interpreter eval "package require $name"
        set version [$interpreter eval "package provide $name"]
        set text {}                                                                           ;# there may be no help for the module
        catch {set text [$interpreter eval "namespace eval $name {set data(helpText)}"]}
        interp delete $interpreter
        cd $directory                                                                                   ;# restore current directory
        return [list $version $text]
    }

    # Invoke modules initialization procedure, if any, once. modules must be loaded first (see parse{}).
    # In case of an error in a module initialization phase, the module is unloaded and proper cleanup occurs, and if an error
    # command is specified, it is invoked and initialization continues for other modules, otherwise an error is thrown.
    proc initialize {{daemon 0} {errorCommand {}}} {
        foreach instance $(instances) {
            set namespace $instance::($instance,namespace)
            set error 0
            if {$instance::($instance,initialize)} {
                regsub {<0>$} $namespace {} string                 ;# remove trailing namespace index for first instance of a module
                if {$global::withGUI} {
                    lifoLabel::push $global::messenger [format [mc {initializing %s...}] $string]
                } elseif {$global::debug} {
                    writeLog "initializing $string module..."
                }
                catch {unset options}
                catch {array set options $instance::($instance,options)}                           ;# module may have no options set
                if {$daemon && [info exists instance::($instance,switches)]} {               ;# daemon mode and module takes options
                    array set switch $instance::($instance,switches)
                    if {![info exists option(--daemon)] && [info exists switch(--daemon)]} {
                        # module supports the daemon mode and corresponding option is not already set
                        set options(--daemon) {}                                     ;# force daemon option, which takes no argument
                    }
                    unset switch
                }
                if {[info exists options]} {
                    if {[catch {::${namespace}::initialize [array get options]} message]} {
                        if {$global::debug} {set information $::errorInfo}
                        set error 1
                    }
                } else {                                                                                  ;# module takes no options
                    if {[catch ::${namespace}::initialize message]} {
                        if {$global::debug} {set information $::errorInfo}
                        set error 1
                    }
                }
                if {$global::withGUI} {
                    lifoLabel::pop $global::messenger
                }
            }
            if {!$error} {
                instance::synchronize $instance                                                          ;# in case data was updated
                set 64BitsName ::${namespace}::data(64Bits)
                if {([package vcompare $::tcl_version 8.4] < 0) && [info exists $64BitsName] && [set $64BitsName]} {
                    set message {Tcl/Tk core version 8.4 or above is required for 64 bits support}
                    set information $message
                    set error 1
                }
            }
            if {$error} {
                unload $instance                                                                                         ;# clean up
                regsub {<0>$} $namespace {} namespace              ;# remove trailing namespace index for first instance of a module
                set message "module \"$namespace\" initialize error:\n$message"
                if {$global::debug} {
                    error $information
                } elseif {[string length $errorCommand] > 0} {
                    uplevel #0 $errorCommand $namespace [list $message]
                } else {
                    error $message
                }
            }
            set instance::($instance,initialize) 0                                ;# a module instance must be initialized once only
        }
        if {$global::withGUI} {
            update idletasks                            ;# make sure latest initialization message is not left showing meaninglessly
        }
    }

    proc setPollTimes {{override {}}} {
        if {[llength $(instances)] == 0} {
            set global::pollTimes {}
            set global::pollTime 0
            return
        }
        set default 0
        set minimum 0
        foreach instance $(instances) {
            set times $instance::($instance,times)
            if {[llength $times] == 0} {
                error "module $instance::($instance,namespace) poll times list is empty"
            }
            # for an asynchronous module, the sole time value would be negative and is used as graph interval, for example
            set time [lindex $times 0]
            if {$time < 0} {                                        ;# asynchronous module, poll time is a viewer interval (negated)
                set intervals($time) {}
                continue
            }
            if {$time > $default} {                                                            ;# default value is the first in list
                set default $time                                                    ;# keep the greater default time of all modules
            }
            set times [lsort -integer $times]                                                                     ;# sort poll times
            set time [lindex $times 0]
            if {$time > $minimum} {
                set minimum $time                                                    ;# keep the greater minimum time of all modules
                set minimumModule $instance::($instance,namespace)
            }
            foreach time $times {                                    ;# poll times list is the combination of all modules poll times
                set data($time) {}
            }
        }
        # sort and restrict poll times above maximum module minimum poll time
        set global::pollTimes [lsort -integer [array names data]]
        set global::pollTimes [lrange $global::pollTimes [lsearch -exact $global::pollTimes $minimum] end]
        if {$global::pollTime < $default} {                 ;# do not override existing poll time that may have been set by the user
            set global::pollTime $default
        }
        if {[string length $override] > 0} {                                                ;# validate command line override if any
            if {$override < $minimum} {
                puts stderr "$::argv0: minimum poll time is $minimum seconds for module $minimumModule"
                exit 1
            }
            set global::pollTime $override
        }
        if {($global::pollTime == 0) && [info exists intervals]} {
            # all modules are asynchronous, so use an average time as a viewer interval for viewers that need it, such as graphs.
            # the poll times list is empty at this point so the user cannot change the poll time.
            # note that the viewer interval can still be forced by the command line poll time option.
            set sum 0
            set number 0
            foreach interval [array names intervals] {
                incr sum $interval
                incr number
            }
            set global::pollTime [expr {round(double($sum) / -$number)}]
        }                                                        ;# else if poll time is 0, it must all be database instance modules
    }

    proc identifier {array} {                            ;# from an array name, return the module identifier (used in viewer labels)
        set namespace [namespaceFromArray $array]
        foreach instance $(instances) {
            if {[string equal $namespace $instance::($instance,namespace)]} {                              ;# this is a module array
                return $instance::($instance,identifier)                                                         ;# favor identifier
            }
        }
        return {}                                                                   ;# not a module array or identification unneeded
    }

    proc asynchronous {array} {                              ;# from an array name, tell whether the module instance is asynchronous
        set namespace [namespaceFromArray $array]
        foreach instance $(instances) {
            if {[string equal $namespace $instance::($instance,namespace)]} {
                return [expr {[lindex $instance::($instance,times) 0] < 0}]              ;# sole time value negative if asynchronous
            }
        }
        error "could not find module instance for array $array"          ;# important: user code may rely on error being thrown here
    }

    proc instanceData {array} {                                              ;# return instance data needed by database storage code
        variable instanceData                                                                                ;# instances data cache

        set namespace [namespaceFromArray $array]
        foreach identifier $(instances) {
            if {[string equal $namespace $instance::($identifier,namespace)]} {                            ;# this is a module array
                set instance $identifier
                break                                                                                              ;# found instance
            }
        }
        if {![info exists instance]} {                   ;# in case of derived data, in its own namespace, such as in summary tables
            return {}
        }
        if {[info exists instanceData($instance)]} {                                                             ;# already in cache
            return $instanceData($instance)
        }
        foreach {data(module) dummy} [modules::decoded $namespace] {}
        set data(identifier) $instance::($instance,identifier)
        set data(version) $instance::($instance,version)
        catch {set data(options) $instance::($instance,options)}                                     ;# module switches are optional
        upvar 1 ::${namespace}::data module                                                          ;# actual module namespace data
        set columns {}
        foreach name [array names module *,label] {                                                         ;# gather column numbers
            if {[scan $name %u column] > 0} {lappend columns $column}
        }
        set list {}
        foreach column [lsort -integer $columns] {                           ;# in indexed order since a list is used as data holder
            lappend list $module($column,label) $module($column,type) $module($column,message)
            if {[catch {lappend list $module($column,anchor)}]} {lappend list {}}                              ;# anchor is optional
        }
        set data(data) $list
        set data(indexColumns) 0; catch {set data(indexColumns) $module(indexColumns)}             ;# optional (default is column 0)
        return [set instanceData($instance) [array get data]]                                     ;# return array in serialized form
    }

    proc decoded {name} {     ;# return module and index list (index may be empty if module is not indexed: name instead of name<N>)
        set index {}
        scan $name {%[^<]<%u>} name index                                         ;# split module into its name and its index if any
        return [list $name $index]
    }

    proc validName {string} {                                                                               ;# includes iso-8859 set
        return [regexp {^[\w ,<>@%&*()=+:.-]+$} $string]
    }

    proc displayHelpMessage {name {switches {}}} {
        puts -nonewline "$name module usage:"
        if {[llength $switches] == 0} {
            puts -nonewline { <no arguments allowed>}
        } else {
            foreach {switch argument} $switches {
                puts -nonewline " \[$switch"
                if {$argument} {                                                                        ;# option takes one argument
                    puts -nonewline { argument}
                }
                puts -nonewline \]
            }
        }
        puts {}
    }

    # return a list of namespaces with identifier and options, options being of list of switch, argument required and argument
    proc loaded {} {
        if {[llength $(instances)] == 0} {
            return {}
        }
        foreach instance $(instances) {
            lappend list [list $instance $instance::($instance,namespace)]
        }
        set return {}
        foreach list [lsort -dictionary -index 1 $list] {                                    ;# sort in namespace alphabetical order
            foreach {instance namespace} $list {}
            lappend return $namespace $instance::($instance,identifier)
            set switches {}                                                                       ;# in case module takes no options
            catch {set switches $instance::($instance,switches)}
            if {[llength $switches] == 0} {
                lappend return {}
            } else {
                set arguments $instance::($instance,arguments)
                set list {}
                foreach {switch required} $switches {
                    lappend list $switch $required
                    set index [lsearch -exact $arguments $switch]                             ;# look for switch in module arguments
                    if {$required} {
                        if {$index < 0} {                                                                     ;# option was not used
                            lappend list {}
                        } else {
                            lappend list [lindex $arguments [incr index]]                             ;# option value follows switch
                        }
                    } else {
                        lappend list [expr {$index >= 0}]                    ;# value is true for boolean options if switch was used
                    }
                }
                lappend return $list
            }
        }
        return $return
    }

    proc instancesWithout {{modules {}}} {
        foreach module $modules {set skip($module) {}}
        set instances {}
        foreach instance $(instances) {                                      ;# note: in modules list, modules are in creation order
            if {[info exists skip($instance::($instance,module))]} continue
            lappend instances $instance
        }
        return $instances
    }

    proc namesWithout {modules} {   ;# list of all different loaded module names (a name is unique even if several instances loaded)
        set list {}
        foreach instance [instancesWithout $modules] {
            set module $instance::($instance,module)
            if {[lsearch -exact $list $module] < 0} {
                lappend list $module
            }
        }
        return $list
    }

    proc unload {instance} {
        ldelete (instances) $instance
        delete $instance
        if {$global::withGUI} {
            pages::monitorActiveCells     ;# refresh pages monitored cells since tables cells with thresholds could have disappeared
            thresholdLabel::monitorActiveCells                                           ;# refresh global thresholds viewer as well
        }
    }

    proc loadedNamespace {string} {                       ;# whether the module corresponding with the namespace is currently loaded
        foreach instance $(instances) {
            if {[string equal $string $instance::($instance,namespace)]} {
                return 1
            }
        }
        return 0
    }

    proc namespaceFromArray {name} {
        return [string trimleft [namespace qualifiers [namespace which -variable $name]] :]
    }

    proc loadResidentTraceModule {} {                                       ;# load resident trace module: must be invoked once only
        if {[info exists (trace)]} {error {trying to load several resident trace modules}}
        set (trace) [new instance trace {}]
        instance::load $(trace)
        set namespace $instance::($(trace),namespace)
        ::${namespace}::initialize [list --rows $global::traceNumberOfRows]
    }

    proc trace {module identifier message} {             ;# destined to resident trace and possibly other instantiated trace modules
        regsub {<0>$} $identifier {} identifier                                ;# remove trailing namespace index for first instance
        set namespace $instance::($(trace),namespace)                                                                    ;# resident
        ::${namespace}::update $module $identifier $message
        foreach instance $(instances) {                                                                                    ;# others
            if {[string equal $instance::($instance,module) trace]} {
                set namespace $instance::($instance,namespace)
                ::${namespace}::update $module $identifier $message
            }
        }
    }

    proc loadFormulasModule {index object category} {                                                ;# based on parse{}, simplified
        set instance [new instance formulas $index]
        instance::load $instance
        set namespace $instance::($instance,namespace)
        set options {}
        if {[string length $object] > 0} {lappend options --object $object}
        if {[string length $category] > 0} {lappend options --category $category}
        set instance::($instance,options) $options
        ::${namespace}::initialize $options
        set instance::($instance,initialize) 0                 ;# a module instance must be initialized once only (see initialize{})
        set instance::($instance,arguments) {}                                       ;# needed by instance code but unimportant here
        instance::synchronize $instance
        lappend (instances) $instance                                              ;# add module to successfully loaded modules list
        return $instance
    }

    proc flashMessage {module namespace message {seconds 1}} {
        # use identifier set by the module code (defaults to namespace otherwise) so that user knows better which module
        regsub {<0>$} [set ::${namespace}::data(identifier)] {} identifier     ;# remove trailing namespace index for first instance
        if {$global::withGUI} {
            ::lifoLabel::flash $::global::messenger "$identifier: $message" $seconds
            switched::configure [moduleFromNamespace $namespace] -state error    ;### consider these type of messages errors for now
        } else {
            writeLog "$identifier: $message"
        }
        trace $module $identifier $message                                                         ;# also possibly display in trace
    }

    proc pushMessage {module namespace message} {
        # use identifier set by the module code (defaults to namespace otherwise) so that user knows better which module
        regsub {<0>$} [set ::${namespace}::data(identifier)] {} identifier     ;# remove trailing namespace index for first instance
        if {$global::withGUI} {
            ::lifoLabel::push $::global::messenger "$identifier: $message"
        } else {
            writeLog "$identifier: $message"
        }
        trace $module $identifier $message                                                         ;# also possibly display in trace
    }

    proc popMessage {} {
        if {$global::withGUI} {
            ::lifoLabel::pop $::global::messenger
        }
    }

    proc moduleFromNamespace {string} {                                                   ;# returns the module object, not the name
        foreach instance $(instances) {
            if {[string equal $instance::($instance,namespace) $string]} {
                return $instance::($instance,loaded)
            }
        }
        return 0                                                                                                        ;# not found
    }

}