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
}
}
|