From 09e111b9608fde5d6d497f7c25f5bb6dbb200f90 Mon Sep 17 00:00:00 2001 From: MatMasIt <34745996+MatMasIt@users.noreply.github.com> Date: Thu, 17 Jun 2021 01:31:27 +0200 Subject: [PATCH] Add files via upload --- windowsAgent/dist/tcl/auto.tcl | 646 ++++ windowsAgent/dist/tcl/clock.tcl | 4547 +++++++++++++++++++++++++++++ windowsAgent/dist/tcl/history.tcl | 335 +++ windowsAgent/dist/tcl/init.tcl | 819 ++++++ windowsAgent/dist/tcl/package.tcl | 747 +++++ windowsAgent/dist/tcl/parray.tcl | 28 + windowsAgent/dist/tcl/safe.tcl | 1133 +++++++ windowsAgent/dist/tcl/tclIndex | 75 + windowsAgent/dist/tcl/tm.tcl | 375 +++ windowsAgent/dist/tcl/word.tcl | 152 + 10 files changed, 8857 insertions(+) create mode 100644 windowsAgent/dist/tcl/auto.tcl create mode 100644 windowsAgent/dist/tcl/clock.tcl create mode 100644 windowsAgent/dist/tcl/history.tcl create mode 100644 windowsAgent/dist/tcl/init.tcl create mode 100644 windowsAgent/dist/tcl/package.tcl create mode 100644 windowsAgent/dist/tcl/parray.tcl create mode 100644 windowsAgent/dist/tcl/safe.tcl create mode 100644 windowsAgent/dist/tcl/tclIndex create mode 100644 windowsAgent/dist/tcl/tm.tcl create mode 100644 windowsAgent/dist/tcl/word.tcl diff --git a/windowsAgent/dist/tcl/auto.tcl b/windowsAgent/dist/tcl/auto.tcl new file mode 100644 index 0000000..a7a8979 --- /dev/null +++ b/windowsAgent/dist/tcl/auto.tcl @@ -0,0 +1,646 @@ +# auto.tcl -- +# +# utility procs formerly in init.tcl dealing with auto execution of commands +# and can be auto loaded themselves. +# +# Copyright (c) 1991-1993 The Regents of the University of California. +# Copyright (c) 1994-1998 Sun Microsystems, Inc. +# +# See the file "license.terms" for information on usage and redistribution of +# this file, and for a DISCLAIMER OF ALL WARRANTIES. +# + +# auto_reset -- +# +# Destroy all cached information for auto-loading and auto-execution, so that +# the information gets recomputed the next time it's needed. Also delete any +# commands that are listed in the auto-load index. +# +# Arguments: +# None. + +proc auto_reset {} { + global auto_execs auto_index auto_path + if {[array exists auto_index]} { + foreach cmdName [array names auto_index] { + set fqcn [namespace which $cmdName] + if {$fqcn eq ""} { + continue + } + rename $fqcn {} + } + } + unset -nocomplain auto_execs auto_index ::tcl::auto_oldpath + if {[catch {llength $auto_path}]} { + set auto_path [list [info library]] + } elseif {[info library] ni $auto_path} { + lappend auto_path [info library] + } +} + +# tcl_findLibrary -- +# +# This is a utility for extensions that searches for a library directory +# using a canonical searching algorithm. A side effect is to source the +# initialization script and set a global library variable. +# +# Arguments: +# basename Prefix of the directory name, (e.g., "tk") +# version Version number of the package, (e.g., "8.0") +# patch Patchlevel of the package, (e.g., "8.0.3") +# initScript Initialization script to source (e.g., tk.tcl) +# enVarName environment variable to honor (e.g., TK_LIBRARY) +# varName Global variable to set when done (e.g., tk_library) + +proc tcl_findLibrary {basename version patch initScript enVarName varName} { + upvar #0 $varName the_library + global auto_path env tcl_platform + + set dirs {} + set errors {} + + # The C application may have hardwired a path, which we honor + + if {[info exists the_library] && $the_library ne ""} { + lappend dirs $the_library + } else { + # Do the canonical search + + # 1. From an environment variable, if it exists. Placing this first + # gives the end-user ultimate control to work-around any bugs, or + # to customize. + + if {[info exists env($enVarName)]} { + lappend dirs $env($enVarName) + } + + # 2. In the package script directory registered within the + # configuration of the package itself. + + catch { + lappend dirs [::${basename}::pkgconfig get scriptdir,runtime] + } + + # 3. Relative to auto_path directories. This checks relative to the + # Tcl library as well as allowing loading of libraries added to the + # auto_path that is not relative to the core library or binary paths. + foreach d $auto_path { + lappend dirs [file join $d $basename$version] + if {$tcl_platform(platform) eq "unix" + && $tcl_platform(os) eq "Darwin"} { + # 4. On MacOSX, check the Resources/Scripts subdir too + lappend dirs [file join $d $basename$version Resources Scripts] + } + } + + # 3. Various locations relative to the executable + # ../lib/foo1.0 (From bin directory in install hierarchy) + # ../../lib/foo1.0 (From bin/arch directory in install hierarchy) + # ../library (From unix directory in build hierarchy) + # + # Remaining locations are out of date (when relevant, they ought to be + # covered by the $::auto_path seach above) and disabled. + # + # ../../library (From unix/arch directory in build hierarchy) + # ../../foo1.0.1/library + # (From unix directory in parallel build hierarchy) + # ../../../foo1.0.1/library + # (From unix/arch directory in parallel build hierarchy) + + set parentDir [file dirname [file dirname [info nameofexecutable]]] + set grandParentDir [file dirname $parentDir] + lappend dirs [file join $parentDir lib $basename$version] + lappend dirs [file join $grandParentDir lib $basename$version] + lappend dirs [file join $parentDir library] + if {0} { + lappend dirs [file join $grandParentDir library] + lappend dirs [file join $grandParentDir $basename$patch library] + lappend dirs [file join [file dirname $grandParentDir] \ + $basename$patch library] + } + } + # uniquify $dirs in order + array set seen {} + foreach i $dirs { + # Make sure $i is unique under normalization. Avoid repeated [source]. + if {[interp issafe]} { + # Safe interps have no [file normalize]. + set norm $i + } else { + set norm [file normalize $i] + } + if {[info exists seen($norm)]} { + continue + } + set seen($norm) {} + + set the_library $i + set file [file join $i $initScript] + + # source everything when in a safe interpreter because we have a + # source command, but no file exists command + + if {[interp issafe] || [file exists $file]} { + if {![catch {uplevel #0 [list source $file]} msg opts]} { + return + } + append errors "$file: $msg\n" + append errors [dict get $opts -errorinfo]\n + } + } + unset -nocomplain the_library + set msg "Can't find a usable $initScript in the following directories: \n" + append msg " $dirs\n\n" + append msg "$errors\n\n" + append msg "This probably means that $basename wasn't installed properly.\n" + error $msg +} + + +# ---------------------------------------------------------------------- +# auto_mkindex +# ---------------------------------------------------------------------- +# The following procedures are used to generate the tclIndex file from Tcl +# source files. They use a special safe interpreter to parse Tcl source +# files, writing out index entries as "proc" commands are encountered. This +# implementation won't work in a safe interpreter, since a safe interpreter +# can't create the special parser and mess with its commands. + +if {[interp issafe]} { + return ;# Stop sourcing the file here +} + +# auto_mkindex -- +# Regenerate a tclIndex file from Tcl source files. Takes as argument the +# name of the directory in which the tclIndex file is to be placed, followed +# by any number of glob patterns to use in that directory to locate all of the +# relevant files. +# +# Arguments: +# dir - Name of the directory in which to create an index. + +# args - Any number of additional arguments giving the names of files +# within dir. If no additional are given auto_mkindex will look +# for *.tcl. + +proc auto_mkindex {dir args} { + if {[interp issafe]} { + error "can't generate index within safe interpreter" + } + + set oldDir [pwd] + cd $dir + + append index "# Tcl autoload index file, version 2.0\n" + append index "# This file is generated by the \"auto_mkindex\" command\n" + append index "# and sourced to set up indexing information for one or\n" + append index "# more commands. Typically each line is a command that\n" + append index "# sets an element in the auto_index array, where the\n" + append index "# element name is the name of a command and the value is\n" + append index "# a script that loads the command.\n\n" + if {![llength $args]} { + set args *.tcl + } + + auto_mkindex_parser::init + foreach file [lsort [glob -- {*}$args]] { + try { + append index [auto_mkindex_parser::mkindex $file] + } on error {msg opts} { + cd $oldDir + return -options $opts $msg + } + } + auto_mkindex_parser::cleanup + + set fid [open "tclIndex" w] + puts -nonewline $fid $index + close $fid + cd $oldDir +} + +# Original version of auto_mkindex that just searches the source code for +# "proc" at the beginning of the line. + +proc auto_mkindex_old {dir args} { + set oldDir [pwd] + cd $dir + set dir [pwd] + append index "# Tcl autoload index file, version 2.0\n" + append index "# This file is generated by the \"auto_mkindex\" command\n" + append index "# and sourced to set up indexing information for one or\n" + append index "# more commands. Typically each line is a command that\n" + append index "# sets an element in the auto_index array, where the\n" + append index "# element name is the name of a command and the value is\n" + append index "# a script that loads the command.\n\n" + if {![llength $args]} { + set args *.tcl + } + foreach file [lsort [glob -- {*}$args]] { + set f "" + set error [catch { + set f [open $file] + while {[gets $f line] >= 0} { + if {[regexp {^proc[ ]+([^ ]*)} $line match procName]} { + set procName [lindex [auto_qualify $procName "::"] 0] + append index "set [list auto_index($procName)]" + append index " \[list source \[file join \$dir [list $file]\]\]\n" + } + } + close $f + } msg opts] + if {$error} { + catch {close $f} + cd $oldDir + return -options $opts $msg + } + } + set f "" + set error [catch { + set f [open tclIndex w] + puts -nonewline $f $index + close $f + cd $oldDir + } msg opts] + if {$error} { + catch {close $f} + cd $oldDir + error $msg $info $code + return -options $opts $msg + } +} + +# Create a safe interpreter that can be used to parse Tcl source files +# generate a tclIndex file for autoloading. This interp contains commands for +# things that need index entries. Each time a command is executed, it writes +# an entry out to the index file. + +namespace eval auto_mkindex_parser { + variable parser "" ;# parser used to build index + variable index "" ;# maintains index as it is built + variable scriptFile "" ;# name of file being processed + variable contextStack "" ;# stack of namespace scopes + variable imports "" ;# keeps track of all imported cmds + variable initCommands ;# list of commands that create aliases + if {![info exists initCommands]} { + set initCommands [list] + } + + proc init {} { + variable parser + variable initCommands + + if {![interp issafe]} { + set parser [interp create -safe] + $parser hide info + $parser hide rename + $parser hide proc + $parser hide namespace + $parser hide eval + $parser hide puts + foreach ns [$parser invokehidden namespace children ::] { + # MUST NOT DELETE "::tcl" OR BAD THINGS HAPPEN! + if {$ns eq "::tcl"} continue + $parser invokehidden namespace delete $ns + } + foreach cmd [$parser invokehidden info commands ::*] { + $parser invokehidden rename $cmd {} + } + $parser invokehidden proc unknown {args} {} + + # We'll need access to the "namespace" command within the + # interp. Put it back, but move it out of the way. + + $parser expose namespace + $parser invokehidden rename namespace _%@namespace + $parser expose eval + $parser invokehidden rename eval _%@eval + + # Install all the registered psuedo-command implementations + + foreach cmd $initCommands { + eval $cmd + } + } + } + proc cleanup {} { + variable parser + interp delete $parser + unset parser + } +} + +# auto_mkindex_parser::mkindex -- +# +# Used by the "auto_mkindex" command to create a "tclIndex" file for the given +# Tcl source file. Executes the commands in the file, and handles things like +# the "proc" command by adding an entry for the index file. Returns a string +# that represents the index file. +# +# Arguments: +# file Name of Tcl source file to be indexed. + +proc auto_mkindex_parser::mkindex {file} { + variable parser + variable index + variable scriptFile + variable contextStack + variable imports + + set scriptFile $file + + set fid [open $file] + set contents [read $fid] + close $fid + + # There is one problem with sourcing files into the safe interpreter: + # references like "$x" will fail since code is not really being executed + # and variables do not really exist. To avoid this, we replace all $ with + # \0 (literally, the null char) later, when getting proc names we will + # have to reverse this replacement, in case there were any $ in the proc + # name. This will cause a problem if somebody actually tries to have a \0 + # in their proc name. Too bad for them. + set contents [string map [list \$ \0] $contents] + + set index "" + set contextStack "" + set imports "" + + $parser eval $contents + + foreach name $imports { + catch {$parser eval [list _%@namespace forget $name]} + } + return $index +} + +# auto_mkindex_parser::hook command +# +# Registers a Tcl command to evaluate when initializing the slave interpreter +# used by the mkindex parser. The command is evaluated in the master +# interpreter, and can use the variable auto_mkindex_parser::parser to get to +# the slave + +proc auto_mkindex_parser::hook {cmd} { + variable initCommands + + lappend initCommands $cmd +} + +# auto_mkindex_parser::slavehook command +# +# Registers a Tcl command to evaluate when initializing the slave interpreter +# used by the mkindex parser. The command is evaluated in the slave +# interpreter. + +proc auto_mkindex_parser::slavehook {cmd} { + variable initCommands + + # The $parser variable is defined to be the name of the slave interpreter + # when this command is used later. + + lappend initCommands "\$parser eval [list $cmd]" +} + +# auto_mkindex_parser::command -- +# +# Registers a new command with the "auto_mkindex_parser" interpreter that +# parses Tcl files. These commands are fake versions of things like the +# "proc" command. When you execute them, they simply write out an entry to a +# "tclIndex" file for auto-loading. +# +# This procedure allows extensions to register their own commands with the +# auto_mkindex facility. For example, a package like [incr Tcl] might +# register a "class" command so that class definitions could be added to a +# "tclIndex" file for auto-loading. +# +# Arguments: +# name Name of command recognized in Tcl files. +# arglist Argument list for command. +# body Implementation of command to handle indexing. + +proc auto_mkindex_parser::command {name arglist body} { + hook [list auto_mkindex_parser::commandInit $name $arglist $body] +} + +# auto_mkindex_parser::commandInit -- +# +# This does the actual work set up by auto_mkindex_parser::command. This is +# called when the interpreter used by the parser is created. +# +# Arguments: +# name Name of command recognized in Tcl files. +# arglist Argument list for command. +# body Implementation of command to handle indexing. + +proc auto_mkindex_parser::commandInit {name arglist body} { + variable parser + + set ns [namespace qualifiers $name] + set tail [namespace tail $name] + if {$ns eq ""} { + set fakeName [namespace current]::_%@fake_$tail + } else { + set fakeName [namespace current]::[string map {:: _} _%@fake_$name] + } + proc $fakeName $arglist $body + + # YUK! Tcl won't let us alias fully qualified command names, so we can't + # handle names like "::itcl::class". Instead, we have to build procs with + # the fully qualified names, and have the procs point to the aliases. + + if {[string match *::* $name]} { + set exportCmd [list _%@namespace export [namespace tail $name]] + $parser eval [list _%@namespace eval $ns $exportCmd] + + # The following proc definition does not work if you want to tolerate + # space or something else diabolical in the procedure name, (i.e., + # space in $alias). The following does not work: + # "_%@eval {$alias} \$args" + # because $alias gets concat'ed to $args. The following does not work + # because $cmd is somehow undefined + # "set cmd {$alias} \; _%@eval {\$cmd} \$args" + # A gold star to someone that can make test autoMkindex-3.3 work + # properly + + set alias [namespace tail $fakeName] + $parser invokehidden proc $name {args} "_%@eval {$alias} \$args" + $parser alias $alias $fakeName + } else { + $parser alias $name $fakeName + } + return +} + +# auto_mkindex_parser::fullname -- +# +# Used by commands like "proc" within the auto_mkindex parser. Returns the +# qualified namespace name for the "name" argument. If the "name" does not +# start with "::", elements are added from the current namespace stack to +# produce a qualified name. Then, the name is examined to see whether or not +# it should really be qualified. If the name has more than the leading "::", +# it is returned as a fully qualified name. Otherwise, it is returned as a +# simple name. That way, the Tcl autoloader will recognize it properly. +# +# Arguments: +# name - Name that is being added to index. + +proc auto_mkindex_parser::fullname {name} { + variable contextStack + + if {![string match ::* $name]} { + foreach ns $contextStack { + set name "${ns}::$name" + if {[string match ::* $name]} { + break + } + } + } + + if {[namespace qualifiers $name] eq ""} { + set name [namespace tail $name] + } elseif {![string match ::* $name]} { + set name "::$name" + } + + # Earlier, mkindex replaced all $'s with \0. Now, we have to reverse that + # replacement. + return [string map [list \0 \$] $name] +} + +# auto_mkindex_parser::indexEntry -- +# +# Used by commands like "proc" within the auto_mkindex parser to add a +# correctly-quoted entry to the index. This is shared code so it is done +# *right*, in one place. +# +# Arguments: +# name - Name that is being added to index. + +proc auto_mkindex_parser::indexEntry {name} { + variable index + variable scriptFile + + # We convert all metacharacters to their backslashed form, and pre-split + # the file name that we know about (which will be a proper list, and so + # correctly quoted). + + set name [string range [list \}[fullname $name]] 2 end] + set filenameParts [file split $scriptFile] + + append index [format \ + {set auto_index(%s) [list source [file join $dir %s]]%s} \ + $name $filenameParts \n] + return +} + +if {[llength $::auto_mkindex_parser::initCommands]} { + return +} + +# Register all of the procedures for the auto_mkindex parser that will build +# the "tclIndex" file. + +# AUTO MKINDEX: proc name arglist body +# Adds an entry to the auto index list for the given procedure name. + +auto_mkindex_parser::command proc {name args} { + indexEntry $name +} + +# Conditionally add support for Tcl byte code files. There are some tricky +# details here. First, we need to get the tbcload library initialized in the +# current interpreter. We cannot load tbcload into the slave until we have +# done so because it needs access to the tcl_patchLevel variable. Second, +# because the package index file may defer loading the library until we invoke +# a command, we need to explicitly invoke auto_load to force it to be loaded. +# This should be a noop if the package has already been loaded + +auto_mkindex_parser::hook { + try { + package require tbcload + } on error {} { + # OK, don't have it so do nothing + } on ok {} { + if {[namespace which -command tbcload::bcproc] eq ""} { + auto_load tbcload::bcproc + } + load {} tbcload $auto_mkindex_parser::parser + + # AUTO MKINDEX: tbcload::bcproc name arglist body + # Adds an entry to the auto index list for the given pre-compiled + # procedure name. + + auto_mkindex_parser::commandInit tbcload::bcproc {name args} { + indexEntry $name + } + } +} + +# AUTO MKINDEX: namespace eval name command ?arg arg...? +# Adds the namespace name onto the context stack and evaluates the associated +# body of commands. +# +# AUTO MKINDEX: namespace import ?-force? pattern ?pattern...? +# Performs the "import" action in the parser interpreter. This is important +# for any commands contained in a namespace that affect the index. For +# example, a script may say "itcl::class ...", or it may import "itcl::*" and +# then say "class ...". This procedure does the import operation, but keeps +# track of imported patterns so we can remove the imports later. + +auto_mkindex_parser::command namespace {op args} { + switch -- $op { + eval { + variable parser + variable contextStack + + set name [lindex $args 0] + set args [lrange $args 1 end] + + set contextStack [linsert $contextStack 0 $name] + $parser eval [list _%@namespace eval $name] $args + set contextStack [lrange $contextStack 1 end] + } + import { + variable parser + variable imports + foreach pattern $args { + if {$pattern ne "-force"} { + lappend imports $pattern + } + } + catch {$parser eval "_%@namespace import $args"} + } + ensemble { + variable parser + variable contextStack + if {[lindex $args 0] eq "create"} { + set name ::[join [lreverse $contextStack] ::] + catch { + set name [dict get [lrange $args 1 end] -command] + if {![string match ::* $name]} { + set name ::[join [lreverse $contextStack] ::]$name + } + regsub -all ::+ $name :: name + } + # create artifical proc to force an entry in the tclIndex + $parser eval [list ::proc $name {} {}] + } + } + } +} + +# AUTO MKINDEX: oo::class create name ?definition? +# Adds an entry to the auto index list for the given class name. +auto_mkindex_parser::command oo::class {op name {body ""}} { + if {$op eq "create"} { + indexEntry $name + } +} +auto_mkindex_parser::command class {op name {body ""}} { + if {$op eq "create"} { + indexEntry $name + } +} + +return diff --git a/windowsAgent/dist/tcl/clock.tcl b/windowsAgent/dist/tcl/clock.tcl new file mode 100644 index 0000000..8e4b657 --- /dev/null +++ b/windowsAgent/dist/tcl/clock.tcl @@ -0,0 +1,4547 @@ +#---------------------------------------------------------------------- +# +# clock.tcl -- +# +# This file implements the portions of the [clock] ensemble that are +# coded in Tcl. Refer to the users' manual to see the description of +# the [clock] command and its subcommands. +# +# +#---------------------------------------------------------------------- +# +# Copyright (c) 2004,2005,2006,2007 by Kevin B. Kenny +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +#---------------------------------------------------------------------- + +# We must have message catalogs that support the root locale, and we need +# access to the Registry on Windows systems. + +uplevel \#0 { + package require msgcat 1.6 + if { $::tcl_platform(platform) eq {windows} } { + if { [catch { package require registry 1.1 }] } { + namespace eval ::tcl::clock [list variable NoRegistry {}] + } + } +} + +# Put the library directory into the namespace for the ensemble so that the +# library code can find message catalogs and time zone definition files. + +namespace eval ::tcl::clock \ + [list variable LibDir [file dirname [info script]]] + +#---------------------------------------------------------------------- +# +# clock -- +# +# Manipulate times. +# +# The 'clock' command manipulates time. Refer to the user documentation for +# the available subcommands and what they do. +# +#---------------------------------------------------------------------- + +namespace eval ::tcl::clock { + + # Export the subcommands + + namespace export format + namespace export clicks + namespace export microseconds + namespace export milliseconds + namespace export scan + namespace export seconds + namespace export add + + # Import the message catalog commands that we use. + + namespace import ::msgcat::mcload + namespace import ::msgcat::mclocale + namespace import ::msgcat::mc + namespace import ::msgcat::mcpackagelocale + +} + +#---------------------------------------------------------------------- +# +# ::tcl::clock::Initialize -- +# +# Finish initializing the 'clock' subsystem +# +# Results: +# None. +# +# Side effects: +# Namespace variable in the 'clock' subsystem are initialized. +# +# The '::tcl::clock::Initialize' procedure initializes the namespace variables +# and root locale message catalog for the 'clock' subsystem. It is broken +# into a procedure rather than simply evaluated as a script so that it will be +# able to use local variables, avoiding the dangers of 'creative writing' as +# in Bug 1185933. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::Initialize {} { + + rename ::tcl::clock::Initialize {} + + variable LibDir + + # Define the Greenwich time zone + + proc InitTZData {} { + variable TZData + array unset TZData + set TZData(:Etc/GMT) { + {-9223372036854775808 0 0 GMT} + } + set TZData(:GMT) $TZData(:Etc/GMT) + set TZData(:Etc/UTC) { + {-9223372036854775808 0 0 UTC} + } + set TZData(:UTC) $TZData(:Etc/UTC) + set TZData(:localtime) {} + } + InitTZData + + mcpackagelocale set {} + ::msgcat::mcpackageconfig set mcfolder [file join $LibDir msgs] + ::msgcat::mcpackageconfig set unknowncmd "" + ::msgcat::mcpackageconfig set changecmd ChangeCurrentLocale + + # Define the message catalog for the root locale. + + ::msgcat::mcmset {} { + AM {am} + BCE {B.C.E.} + CE {C.E.} + DATE_FORMAT {%m/%d/%Y} + DATE_TIME_FORMAT {%a %b %e %H:%M:%S %Y} + DAYS_OF_WEEK_ABBREV { + Sun Mon Tue Wed Thu Fri Sat + } + DAYS_OF_WEEK_FULL { + Sunday Monday Tuesday Wednesday Thursday Friday Saturday + } + GREGORIAN_CHANGE_DATE 2299161 + LOCALE_DATE_FORMAT {%m/%d/%Y} + LOCALE_DATE_TIME_FORMAT {%a %b %e %H:%M:%S %Y} + LOCALE_ERAS {} + LOCALE_NUMERALS { + 00 01 02 03 04 05 06 07 08 09 + 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 + } + LOCALE_TIME_FORMAT {%H:%M:%S} + LOCALE_YEAR_FORMAT {%EC%Ey} + MONTHS_ABBREV { + Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec + } + MONTHS_FULL { + January February March + April May June + July August September + October November December + } + PM {pm} + TIME_FORMAT {%H:%M:%S} + TIME_FORMAT_12 {%I:%M:%S %P} + TIME_FORMAT_24 {%H:%M} + TIME_FORMAT_24_SECS {%H:%M:%S} + } + + # Define a few Gregorian change dates for other locales. In most cases + # the change date follows a language, because a nation's colonies changed + # at the same time as the nation itself. In many cases, different + # national boundaries existed; the dominating rule is to follow the + # nation's capital. + + # Italy, Spain, Portugal, Poland + + ::msgcat::mcset it GREGORIAN_CHANGE_DATE 2299161 + ::msgcat::mcset es GREGORIAN_CHANGE_DATE 2299161 + ::msgcat::mcset pt GREGORIAN_CHANGE_DATE 2299161 + ::msgcat::mcset pl GREGORIAN_CHANGE_DATE 2299161 + + # France, Austria + + ::msgcat::mcset fr GREGORIAN_CHANGE_DATE 2299227 + + # For Belgium, we follow Southern Netherlands; Liege Diocese changed + # several weeks later. + + ::msgcat::mcset fr_BE GREGORIAN_CHANGE_DATE 2299238 + ::msgcat::mcset nl_BE GREGORIAN_CHANGE_DATE 2299238 + + # Austria + + ::msgcat::mcset de_AT GREGORIAN_CHANGE_DATE 2299527 + + # Hungary + + ::msgcat::mcset hu GREGORIAN_CHANGE_DATE 2301004 + + # Germany, Norway, Denmark (Catholic Germany changed earlier) + + ::msgcat::mcset de_DE GREGORIAN_CHANGE_DATE 2342032 + ::msgcat::mcset nb GREGORIAN_CHANGE_DATE 2342032 + ::msgcat::mcset nn GREGORIAN_CHANGE_DATE 2342032 + ::msgcat::mcset no GREGORIAN_CHANGE_DATE 2342032 + ::msgcat::mcset da GREGORIAN_CHANGE_DATE 2342032 + + # Holland (Brabant, Gelderland, Flanders, Friesland, etc. changed at + # various times) + + ::msgcat::mcset nl GREGORIAN_CHANGE_DATE 2342165 + + # Protestant Switzerland (Catholic cantons changed earlier) + + ::msgcat::mcset fr_CH GREGORIAN_CHANGE_DATE 2361342 + ::msgcat::mcset it_CH GREGORIAN_CHANGE_DATE 2361342 + ::msgcat::mcset de_CH GREGORIAN_CHANGE_DATE 2361342 + + # English speaking countries + + ::msgcat::mcset en GREGORIAN_CHANGE_DATE 2361222 + + # Sweden (had several changes onto and off of the Gregorian calendar) + + ::msgcat::mcset sv GREGORIAN_CHANGE_DATE 2361390 + + # Russia + + ::msgcat::mcset ru GREGORIAN_CHANGE_DATE 2421639 + + # Romania (Transylvania changed earler - perhaps de_RO should show the + # earlier date?) + + ::msgcat::mcset ro GREGORIAN_CHANGE_DATE 2422063 + + # Greece + + ::msgcat::mcset el GREGORIAN_CHANGE_DATE 2423480 + + #------------------------------------------------------------------ + # + # CONSTANTS + # + #------------------------------------------------------------------ + + # Paths at which binary time zone data for the Olson libraries are known + # to reside on various operating systems + + variable ZoneinfoPaths {} + foreach path { + /usr/share/zoneinfo + /usr/share/lib/zoneinfo + /usr/lib/zoneinfo + /usr/local/etc/zoneinfo + } { + if { [file isdirectory $path] } { + lappend ZoneinfoPaths $path + } + } + + # Define the directories for time zone data and message catalogs. + + variable DataDir [file join $LibDir tzdata] + + # Number of days in the months, in common years and leap years. + + variable DaysInRomanMonthInCommonYear \ + { 31 28 31 30 31 30 31 31 30 31 30 31 } + variable DaysInRomanMonthInLeapYear \ + { 31 29 31 30 31 30 31 31 30 31 30 31 } + variable DaysInPriorMonthsInCommonYear [list 0] + variable DaysInPriorMonthsInLeapYear [list 0] + set i 0 + foreach j $DaysInRomanMonthInCommonYear { + lappend DaysInPriorMonthsInCommonYear [incr i $j] + } + set i 0 + foreach j $DaysInRomanMonthInLeapYear { + lappend DaysInPriorMonthsInLeapYear [incr i $j] + } + + # Another epoch (Hi, Jeff!) + + variable Roddenberry 1946 + + # Integer ranges + + variable MINWIDE -9223372036854775808 + variable MAXWIDE 9223372036854775807 + + # Day before Leap Day + + variable FEB_28 58 + + # Translation table to map Windows TZI onto cities, so that the Olson + # rules can apply. In some cases the mapping is ambiguous, so it's wise + # to specify $::env(TCL_TZ) rather than simply depending on the system + # time zone. + + # The keys are long lists of values obtained from the time zone + # information in the Registry. In order, the list elements are: + # Bias StandardBias DaylightBias + # StandardDate.wYear StandardDate.wMonth StandardDate.wDayOfWeek + # StandardDate.wDay StandardDate.wHour StandardDate.wMinute + # StandardDate.wSecond StandardDate.wMilliseconds + # DaylightDate.wYear DaylightDate.wMonth DaylightDate.wDayOfWeek + # DaylightDate.wDay DaylightDate.wHour DaylightDate.wMinute + # DaylightDate.wSecond DaylightDate.wMilliseconds + # The values are the names of time zones where those rules apply. There + # is considerable ambiguity in certain zones; an attempt has been made to + # make a reasonable guess, but this table needs to be taken with a grain + # of salt. + + variable WinZoneInfo [dict create {*}{ + {-43200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Kwajalein + {-39600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Midway + {-36000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Honolulu + {-32400 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Anchorage + {-28800 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Los_Angeles + {-28800 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Tijuana + {-25200 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Denver + {-25200 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Chihuahua + {-25200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Phoenix + {-21600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Regina + {-21600 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Chicago + {-21600 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Mexico_City + {-18000 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/New_York + {-18000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Indianapolis + {-14400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Caracas + {-14400 0 3600 0 3 6 2 23 59 59 999 0 10 6 2 23 59 59 999} + :America/Santiago + {-14400 0 3600 0 2 0 5 2 0 0 0 0 11 0 1 2 0 0 0} :America/Manaus + {-14400 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Halifax + {-12600 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/St_Johns + {-10800 0 3600 0 2 0 2 2 0 0 0 0 10 0 3 2 0 0 0} :America/Sao_Paulo + {-10800 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Godthab + {-10800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Buenos_Aires + {-10800 0 3600 0 2 0 5 2 0 0 0 0 11 0 1 2 0 0 0} :America/Bahia + {-10800 0 3600 0 3 0 2 2 0 0 0 0 10 0 1 2 0 0 0} :America/Montevideo + {-7200 0 3600 0 9 0 5 2 0 0 0 0 3 0 5 2 0 0 0} :America/Noronha + {-3600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Atlantic/Azores + {-3600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Atlantic/Cape_Verde + {0 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :UTC + {0 0 3600 0 10 0 5 2 0 0 0 0 3 0 5 1 0 0 0} :Europe/London + {3600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Africa/Kinshasa + {3600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :CET + {7200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Africa/Harare + {7200 0 3600 0 9 4 5 23 59 59 0 0 4 4 5 23 59 59 0} + :Africa/Cairo + {7200 0 3600 0 10 0 5 4 0 0 0 0 3 0 5 3 0 0 0} :Europe/Helsinki + {7200 0 3600 0 9 0 3 2 0 0 0 0 3 5 5 2 0 0 0} :Asia/Jerusalem + {7200 0 3600 0 9 0 5 1 0 0 0 0 3 0 5 0 0 0 0} :Europe/Bucharest + {7200 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Europe/Athens + {7200 0 3600 0 9 5 5 1 0 0 0 0 3 4 5 0 0 0 0} :Asia/Amman + {7200 0 3600 0 10 6 5 23 59 59 999 0 3 0 5 0 0 0 0} + :Asia/Beirut + {7200 0 -3600 0 4 0 1 2 0 0 0 0 9 0 1 2 0 0 0} :Africa/Windhoek + {10800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Riyadh + {10800 0 3600 0 10 0 1 4 0 0 0 0 4 0 1 3 0 0 0} :Asia/Baghdad + {10800 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Europe/Moscow + {12600 0 3600 0 9 2 4 2 0 0 0 0 3 0 1 2 0 0 0} :Asia/Tehran + {14400 0 3600 0 10 0 5 5 0 0 0 0 3 0 5 4 0 0 0} :Asia/Baku + {14400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Muscat + {14400 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Tbilisi + {16200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Kabul + {18000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Karachi + {18000 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Yekaterinburg + {19800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Calcutta + {20700 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Katmandu + {21600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Dhaka + {21600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Novosibirsk + {23400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Rangoon + {25200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Bangkok + {25200 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Krasnoyarsk + {28800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Chongqing + {28800 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Irkutsk + {32400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Tokyo + {32400 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Yakutsk + {34200 0 3600 0 3 0 5 3 0 0 0 0 10 0 5 2 0 0 0} :Australia/Adelaide + {34200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Australia/Darwin + {36000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Australia/Brisbane + {36000 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Vladivostok + {36000 0 3600 0 3 0 5 3 0 0 0 0 10 0 1 2 0 0 0} :Australia/Hobart + {36000 0 3600 0 3 0 5 3 0 0 0 0 10 0 5 2 0 0 0} :Australia/Sydney + {39600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Noumea + {43200 0 3600 0 3 0 3 3 0 0 0 0 10 0 1 2 0 0 0} :Pacific/Auckland + {43200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Fiji + {46800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Tongatapu + }] + + # Groups of fields that specify the date, priorities, and code bursts that + # determine Julian Day Number given those groups. The code in [clock + # scan] will choose the highest priority (lowest numbered) set of fields + # that determines the date. + + variable DateParseActions { + + { seconds } 0 {} + + { julianDay } 1 {} + + { era century yearOfCentury month dayOfMonth } 2 { + dict set date year [expr { 100 * [dict get $date century] + + [dict get $date yearOfCentury] }] + set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ + $changeover] + } + { era century yearOfCentury dayOfYear } 2 { + dict set date year [expr { 100 * [dict get $date century] + + [dict get $date yearOfCentury] }] + set date [GetJulianDayFromEraYearDay $date[set date {}] \ + $changeover] + } + + { century yearOfCentury month dayOfMonth } 3 { + dict set date era CE + dict set date year [expr { 100 * [dict get $date century] + + [dict get $date yearOfCentury] }] + set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ + $changeover] + } + { century yearOfCentury dayOfYear } 3 { + dict set date era CE + dict set date year [expr { 100 * [dict get $date century] + + [dict get $date yearOfCentury] }] + set date [GetJulianDayFromEraYearDay $date[set date {}] \ + $changeover] + } + { iso8601Century iso8601YearOfCentury iso8601Week dayOfWeek } 3 { + dict set date era CE + dict set date iso8601Year \ + [expr { 100 * [dict get $date iso8601Century] + + [dict get $date iso8601YearOfCentury] }] + set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \ + $changeover] + } + + { yearOfCentury month dayOfMonth } 4 { + set date [InterpretTwoDigitYear $date[set date {}] $baseTime] + dict set date era CE + set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ + $changeover] + } + { yearOfCentury dayOfYear } 4 { + set date [InterpretTwoDigitYear $date[set date {}] $baseTime] + dict set date era CE + set date [GetJulianDayFromEraYearDay $date[set date {}] \ + $changeover] + } + { iso8601YearOfCentury iso8601Week dayOfWeek } 4 { + set date [InterpretTwoDigitYear \ + $date[set date {}] $baseTime \ + iso8601YearOfCentury iso8601Year] + dict set date era CE + set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \ + $changeover] + } + + { month dayOfMonth } 5 { + set date [AssignBaseYear $date[set date {}] \ + $baseTime $timeZone $changeover] + set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ + $changeover] + } + { dayOfYear } 5 { + set date [AssignBaseYear $date[set date {}] \ + $baseTime $timeZone $changeover] + set date [GetJulianDayFromEraYearDay $date[set date {}] \ + $changeover] + } + { iso8601Week dayOfWeek } 5 { + set date [AssignBaseIso8601Year $date[set date {}] \ + $baseTime $timeZone $changeover] + set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \ + $changeover] + } + + { dayOfMonth } 6 { + set date [AssignBaseMonth $date[set date {}] \ + $baseTime $timeZone $changeover] + set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ + $changeover] + } + + { dayOfWeek } 7 { + set date [AssignBaseWeek $date[set date {}] \ + $baseTime $timeZone $changeover] + set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \ + $changeover] + } + + {} 8 { + set date [AssignBaseJulianDay $date[set date {}] \ + $baseTime $timeZone $changeover] + } + } + + # Groups of fields that specify time of day, priorities, and code that + # processes them + + variable TimeParseActions { + + seconds 1 {} + + { hourAMPM minute second amPmIndicator } 2 { + dict set date secondOfDay [InterpretHMSP $date] + } + { hour minute second } 2 { + dict set date secondOfDay [InterpretHMS $date] + } + + { hourAMPM minute amPmIndicator } 3 { + dict set date second 0 + dict set date secondOfDay [InterpretHMSP $date] + } + { hour minute } 3 { + dict set date second 0 + dict set date secondOfDay [InterpretHMS $date] + } + + { hourAMPM amPmIndicator } 4 { + dict set date minute 0 + dict set date second 0 + dict set date secondOfDay [InterpretHMSP $date] + } + { hour } 4 { + dict set date minute 0 + dict set date second 0 + dict set date secondOfDay [InterpretHMS $date] + } + + { } 5 { + dict set date secondOfDay 0 + } + } + + # Legacy time zones, used primarily for parsing RFC822 dates. + + variable LegacyTimeZone [dict create \ + gmt +0000 \ + ut +0000 \ + utc +0000 \ + bst +0100 \ + wet +0000 \ + wat -0100 \ + at -0200 \ + nft -0330 \ + nst -0330 \ + ndt -0230 \ + ast -0400 \ + adt -0300 \ + est -0500 \ + edt -0400 \ + cst -0600 \ + cdt -0500 \ + mst -0700 \ + mdt -0600 \ + pst -0800 \ + pdt -0700 \ + yst -0900 \ + ydt -0800 \ + hst -1000 \ + hdt -0900 \ + cat -1000 \ + ahst -1000 \ + nt -1100 \ + idlw -1200 \ + cet +0100 \ + cest +0200 \ + met +0100 \ + mewt +0100 \ + mest +0200 \ + swt +0100 \ + sst +0200 \ + fwt +0100 \ + fst +0200 \ + eet +0200 \ + eest +0300 \ + bt +0300 \ + it +0330 \ + zp4 +0400 \ + zp5 +0500 \ + ist +0530 \ + zp6 +0600 \ + wast +0700 \ + wadt +0800 \ + jt +0730 \ + cct +0800 \ + jst +0900 \ + kst +0900 \ + cast +0930 \ + jdt +1000 \ + kdt +1000 \ + cadt +1030 \ + east +1000 \ + eadt +1030 \ + gst +1000 \ + nzt +1200 \ + nzst +1200 \ + nzdt +1300 \ + idle +1200 \ + a +0100 \ + b +0200 \ + c +0300 \ + d +0400 \ + e +0500 \ + f +0600 \ + g +0700 \ + h +0800 \ + i +0900 \ + k +1000 \ + l +1100 \ + m +1200 \ + n -0100 \ + o -0200 \ + p -0300 \ + q -0400 \ + r -0500 \ + s -0600 \ + t -0700 \ + u -0800 \ + v -0900 \ + w -1000 \ + x -1100 \ + y -1200 \ + z +0000 \ + ] + + # Caches + + variable LocaleNumeralCache {}; # Dictionary whose keys are locale + # names and whose values are pairs + # comprising regexes matching numerals + # in the given locales and dictionaries + # mapping the numerals to their numeric + # values. + # variable CachedSystemTimeZone; # If 'CachedSystemTimeZone' exists, + # it contains the value of the + # system time zone, as determined from + # the environment. + variable TimeZoneBad {}; # Dictionary whose keys are time zone + # names and whose values are 1 if + # the time zone is unknown and 0 + # if it is known. + variable TZData; # Array whose keys are time zone names + # and whose values are lists of quads + # comprising start time, UTC offset, + # Daylight Saving Time indicator, and + # time zone abbreviation. + variable FormatProc; # Array mapping format group + # and locale to the name of a procedure + # that renders the given format +} +::tcl::clock::Initialize + +#---------------------------------------------------------------------- +# +# clock format -- +# +# Formats a count of seconds since the Posix Epoch as a time of day. +# +# The 'clock format' command formats times of day for output. Refer to the +# user documentation to see what it does. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::format { args } { + + variable FormatProc + variable TZData + + lassign [ParseFormatArgs {*}$args] format locale timezone + set locale [string tolower $locale] + set clockval [lindex $args 0] + + # Get the data for time changes in the given zone + + if {$timezone eq ""} { + set timezone [GetSystemTimeZone] + } + if {![info exists TZData($timezone)]} { + if {[catch {SetupTimeZone $timezone} retval opts]} { + dict unset opts -errorinfo + return -options $opts $retval + } + } + + # Build a procedure to format the result. Cache the built procedure's name + # in the 'FormatProc' array to avoid losing its internal representation, + # which contains the name resolution. + + set procName formatproc'$format'$locale + set procName [namespace current]::[string map {: {\:} \\ {\\}} $procName] + if {[info exists FormatProc($procName)]} { + set procName $FormatProc($procName) + } else { + set FormatProc($procName) \ + [ParseClockFormatFormat $procName $format $locale] + } + + return [$procName $clockval $timezone] + +} + +#---------------------------------------------------------------------- +# +# ParseClockFormatFormat -- +# +# Builds and caches a procedure that formats a time value. +# +# Parameters: +# format -- Format string to use +# locale -- Locale in which the format string is to be interpreted +# +# Results: +# Returns the name of the newly-built procedure. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::ParseClockFormatFormat {procName format locale} { + + if {[namespace which $procName] ne {}} { + return $procName + } + + # Map away the locale-dependent composite format groups + + EnterLocale $locale + + # Change locale if a fresh locale has been given on the command line. + + try { + return [ParseClockFormatFormat2 $format $locale $procName] + } trap CLOCK {result opts} { + dict unset opts -errorinfo + return -options $opts $result + } +} + +proc ::tcl::clock::ParseClockFormatFormat2 {format locale procName} { + set didLocaleEra 0 + set didLocaleNumerals 0 + set preFormatCode \ + [string map [list @GREGORIAN_CHANGE_DATE@ \ + [mc GREGORIAN_CHANGE_DATE]] \ + { + variable TZData + set date [GetDateFields $clockval \ + $TZData($timezone) \ + @GREGORIAN_CHANGE_DATE@] + }] + set formatString {} + set substituents {} + set state {} + + set format [LocalizeFormat $locale $format] + + foreach char [split $format {}] { + switch -exact -- $state { + {} { + if { [string equal % $char] } { + set state percent + } else { + append formatString $char + } + } + percent { # Character following a '%' character + set state {} + switch -exact -- $char { + % { # A literal character, '%' + append formatString %% + } + a { # Day of week, abbreviated + append formatString %s + append substituents \ + [string map \ + [list @DAYS_OF_WEEK_ABBREV@ \ + [list [mc DAYS_OF_WEEK_ABBREV]]] \ + { [lindex @DAYS_OF_WEEK_ABBREV@ \ + [expr {[dict get $date dayOfWeek] \ + % 7}]]}] + } + A { # Day of week, spelt out. + append formatString %s + append substituents \ + [string map \ + [list @DAYS_OF_WEEK_FULL@ \ + [list [mc DAYS_OF_WEEK_FULL]]] \ + { [lindex @DAYS_OF_WEEK_FULL@ \ + [expr {[dict get $date dayOfWeek] \ + % 7}]]}] + } + b - h { # Name of month, abbreviated. + append formatString %s + append substituents \ + [string map \ + [list @MONTHS_ABBREV@ \ + [list [mc MONTHS_ABBREV]]] \ + { [lindex @MONTHS_ABBREV@ \ + [expr {[dict get $date month]-1}]]}] + } + B { # Name of month, spelt out + append formatString %s + append substituents \ + [string map \ + [list @MONTHS_FULL@ \ + [list [mc MONTHS_FULL]]] \ + { [lindex @MONTHS_FULL@ \ + [expr {[dict get $date month]-1}]]}] + } + C { # Century number + append formatString %02d + append substituents \ + { [expr {[dict get $date year] / 100}]} + } + d { # Day of month, with leading zero + append formatString %02d + append substituents { [dict get $date dayOfMonth]} + } + e { # Day of month, without leading zero + append formatString %2d + append substituents { [dict get $date dayOfMonth]} + } + E { # Format group in a locale-dependent + # alternative era + set state percentE + if {!$didLocaleEra} { + append preFormatCode \ + [string map \ + [list @LOCALE_ERAS@ \ + [list [mc LOCALE_ERAS]]] \ + { + set date [GetLocaleEra \ + $date[set date {}] \ + @LOCALE_ERAS@]}] \n + set didLocaleEra 1 + } + if {!$didLocaleNumerals} { + append preFormatCode \ + [list set localeNumerals \ + [mc LOCALE_NUMERALS]] \n + set didLocaleNumerals 1 + } + } + g { # Two-digit year relative to ISO8601 + # week number + append formatString %02d + append substituents \ + { [expr { [dict get $date iso8601Year] % 100 }]} + } + G { # Four-digit year relative to ISO8601 + # week number + append formatString %02d + append substituents { [dict get $date iso8601Year]} + } + H { # Hour in the 24-hour day, leading zero + append formatString %02d + append substituents \ + { [expr { [dict get $date localSeconds] \ + / 3600 % 24}]} + } + I { # Hour AM/PM, with leading zero + append formatString %02d + append substituents \ + { [expr { ( ( ( [dict get $date localSeconds] \ + % 86400 ) \ + + 86400 \ + - 3600 ) \ + / 3600 ) \ + % 12 + 1 }] } + } + j { # Day of year (001-366) + append formatString %03d + append substituents { [dict get $date dayOfYear]} + } + J { # Julian Day Number + append formatString %07ld + append substituents { [dict get $date julianDay]} + } + k { # Hour (0-23), no leading zero + append formatString %2d + append substituents \ + { [expr { [dict get $date localSeconds] + / 3600 + % 24 }]} + } + l { # Hour (12-11), no leading zero + append formatString %2d + append substituents \ + { [expr { ( ( ( [dict get $date localSeconds] + % 86400 ) + + 86400 + - 3600 ) + / 3600 ) + % 12 + 1 }]} + } + m { # Month number, leading zero + append formatString %02d + append substituents { [dict get $date month]} + } + M { # Minute of the hour, leading zero + append formatString %02d + append substituents \ + { [expr { [dict get $date localSeconds] + / 60 + % 60 }]} + } + n { # A literal newline + append formatString \n + } + N { # Month number, no leading zero + append formatString %2d + append substituents { [dict get $date month]} + } + O { # A format group in the locale's + # alternative numerals + set state percentO + if {!$didLocaleNumerals} { + append preFormatCode \ + [list set localeNumerals \ + [mc LOCALE_NUMERALS]] \n + set didLocaleNumerals 1 + } + } + p { # Localized 'AM' or 'PM' indicator + # converted to uppercase + append formatString %s + append preFormatCode \ + [list set AM [string toupper [mc AM]]] \n \ + [list set PM [string toupper [mc PM]]] \n + append substituents \ + { [expr {(([dict get $date localSeconds] + % 86400) < 43200) ? + $AM : $PM}]} + } + P { # Localized 'AM' or 'PM' indicator + append formatString %s + append preFormatCode \ + [list set am [mc AM]] \n \ + [list set pm [mc PM]] \n + append substituents \ + { [expr {(([dict get $date localSeconds] + % 86400) < 43200) ? + $am : $pm}]} + + } + Q { # Hi, Jeff! + append formatString %s + append substituents { [FormatStarDate $date]} + } + s { # Seconds from the Posix Epoch + append formatString %s + append substituents { [dict get $date seconds]} + } + S { # Second of the minute, with + # leading zero + append formatString %02d + append substituents \ + { [expr { [dict get $date localSeconds] + % 60 }]} + } + t { # A literal tab character + append formatString \t + } + u { # Day of the week (1-Monday, 7-Sunday) + append formatString %1d + append substituents { [dict get $date dayOfWeek]} + } + U { # Week of the year (00-53). The + # first Sunday of the year is the + # first day of week 01 + append formatString %02d + append preFormatCode { + set dow [dict get $date dayOfWeek] + if { $dow == 7 } { + set dow 0 + } + incr dow + set UweekNumber \ + [expr { ( [dict get $date dayOfYear] + - $dow + 7 ) + / 7 }] + } + append substituents { $UweekNumber} + } + V { # The ISO8601 week number + append formatString %02d + append substituents { [dict get $date iso8601Week]} + } + w { # Day of the week (0-Sunday, + # 6-Saturday) + append formatString %1d + append substituents \ + { [expr { [dict get $date dayOfWeek] % 7 }]} + } + W { # Week of the year (00-53). The first + # Monday of the year is the first day + # of week 01. + append preFormatCode { + set WweekNumber \ + [expr { ( [dict get $date dayOfYear] + - [dict get $date dayOfWeek] + + 7 ) + / 7 }] + } + append formatString %02d + append substituents { $WweekNumber} + } + y { # The two-digit year of the century + append formatString %02d + append substituents \ + { [expr { [dict get $date year] % 100 }]} + } + Y { # The four-digit year + append formatString %04d + append substituents { [dict get $date year]} + } + z { # The time zone as hours and minutes + # east (+) or west (-) of Greenwich + append formatString %s + append substituents { [FormatNumericTimeZone \ + [dict get $date tzOffset]]} + } + Z { # The name of the time zone + append formatString %s + append substituents { [dict get $date tzName]} + } + % { # A literal percent character + append formatString %% + } + default { # An unknown escape sequence + append formatString %% $char + } + } + } + percentE { # Character following %E + set state {} + switch -exact -- $char { + E { + append formatString %s + append substituents { } \ + [string map \ + [list @BCE@ [list [mc BCE]] \ + @CE@ [list [mc CE]]] \ + {[dict get {BCE @BCE@ CE @CE@} \ + [dict get $date era]]}] + } + C { # Locale-dependent era + append formatString %s + append substituents { [dict get $date localeEra]} + } + y { # Locale-dependent year of the era + append preFormatCode { + set y [dict get $date localeYear] + if { $y >= 0 && $y < 100 } { + set Eyear [lindex $localeNumerals $y] + } else { + set Eyear $y + } + } + append formatString %s + append substituents { $Eyear} + } + default { # Unknown %E format group + append formatString %%E $char + } + } + } + percentO { # Character following %O + set state {} + switch -exact -- $char { + d - e { # Day of the month in alternative + # numerals + append formatString %s + append substituents \ + { [lindex $localeNumerals \ + [dict get $date dayOfMonth]]} + } + H - k { # Hour of the day in alternative + # numerals + append formatString %s + append substituents \ + { [lindex $localeNumerals \ + [expr { [dict get $date localSeconds] + / 3600 + % 24 }]]} + } + I - l { # Hour (12-11) AM/PM in alternative + # numerals + append formatString %s + append substituents \ + { [lindex $localeNumerals \ + [expr { ( ( ( [dict get $date localSeconds] + % 86400 ) + + 86400 + - 3600 ) + / 3600 ) + % 12 + 1 }]]} + } + m { # Month number in alternative numerals + append formatString %s + append substituents \ + { [lindex $localeNumerals [dict get $date month]]} + } + M { # Minute of the hour in alternative + # numerals + append formatString %s + append substituents \ + { [lindex $localeNumerals \ + [expr { [dict get $date localSeconds] + / 60 + % 60 }]]} + } + S { # Second of the minute in alternative + # numerals + append formatString %s + append substituents \ + { [lindex $localeNumerals \ + [expr { [dict get $date localSeconds] + % 60 }]]} + } + u { # Day of the week (Monday=1,Sunday=7) + # in alternative numerals + append formatString %s + append substituents \ + { [lindex $localeNumerals \ + [dict get $date dayOfWeek]]} + } + w { # Day of the week (Sunday=0,Saturday=6) + # in alternative numerals + append formatString %s + append substituents \ + { [lindex $localeNumerals \ + [expr { [dict get $date dayOfWeek] % 7 }]]} + } + y { # Year of the century in alternative + # numerals + append formatString %s + append substituents \ + { [lindex $localeNumerals \ + [expr { [dict get $date year] % 100 }]]} + } + default { # Unknown format group + append formatString %%O $char + } + } + } + } + } + + # Clean up any improperly terminated groups + + switch -exact -- $state { + percent { + append formatString %% + } + percentE { + append retval %%E + } + percentO { + append retval %%O + } + } + + proc $procName {clockval timezone} " + $preFormatCode + return \[::format [list $formatString] $substituents\] + " + + # puts [list $procName [info args $procName] [info body $procName]] + + return $procName +} + +#---------------------------------------------------------------------- +# +# clock scan -- +# +# Inputs a count of seconds since the Posix Epoch as a time of day. +# +# The 'clock format' command scans times of day on input. Refer to the user +# documentation to see what it does. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::scan { args } { + + set format {} + + # Check the count of args + + if { [llength $args] < 1 || [llength $args] % 2 != 1 } { + set cmdName "clock scan" + return -code error \ + -errorcode [list CLOCK wrongNumArgs] \ + "wrong \# args: should be\ + \"$cmdName string\ + ?-base seconds?\ + ?-format string? ?-gmt boolean?\ + ?-locale LOCALE? ?-timezone ZONE?\"" + } + + # Set defaults + + set base [clock seconds] + set string [lindex $args 0] + set format {} + set gmt 0 + set locale c + set timezone [GetSystemTimeZone] + + # Pick up command line options. + + foreach { flag value } [lreplace $args 0 0] { + set saw($flag) {} + switch -exact -- $flag { + -b - -ba - -bas - -base { + set base $value + } + -f - -fo - -for - -form - -forma - -format { + set format $value + } + -g - -gm - -gmt { + set gmt $value + } + -l - -lo - -loc - -loca - -local - -locale { + set locale [string tolower $value] + } + -t - -ti - -tim - -time - -timez - -timezo - -timezon - -timezone { + set timezone $value + } + default { + return -code error \ + -errorcode [list CLOCK badOption $flag] \ + "bad option \"$flag\",\ + must be -base, -format, -gmt, -locale or -timezone" + } + } + } + + # Check options for validity + + if { [info exists saw(-gmt)] && [info exists saw(-timezone)] } { + return -code error \ + -errorcode [list CLOCK gmtWithTimezone] \ + "cannot use -gmt and -timezone in same call" + } + if { [catch { expr { wide($base) } } result] } { + return -code error "expected integer but got \"$base\"" + } + if { ![string is boolean -strict $gmt] } { + return -code error "expected boolean value but got \"$gmt\"" + } elseif { $gmt } { + set timezone :GMT + } + + if { ![info exists saw(-format)] } { + # Perhaps someday we'll localize the legacy code. Right now, it's not + # localized. + if { [info exists saw(-locale)] } { + return -code error \ + -errorcode [list CLOCK flagWithLegacyFormat] \ + "legacy \[clock scan\] does not support -locale" + + } + return [FreeScan $string $base $timezone $locale] + } + + # Change locale if a fresh locale has been given on the command line. + + EnterLocale $locale + + try { + # Map away the locale-dependent composite format groups + + set scanner [ParseClockScanFormat $format $locale] + return [$scanner $string $base $timezone] + } trap CLOCK {result opts} { + # Conceal location of generation of expected errors + dict unset opts -errorinfo + return -options $opts $result + } +} + +#---------------------------------------------------------------------- +# +# FreeScan -- +# +# Scans a time in free format +# +# Parameters: +# string - String containing the time to scan +# base - Base time, expressed in seconds from the Epoch +# timezone - Default time zone in which the time will be expressed +# locale - (Unused) Name of the locale where the time will be scanned. +# +# Results: +# Returns the date and time extracted from the string in seconds from +# the epoch +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::FreeScan { string base timezone locale } { + + variable TZData + + # Get the data for time changes in the given zone + + try { + SetupTimeZone $timezone + } on error {retval opts} { + dict unset opts -errorinfo + return -options $opts $retval + } + + # Extract year, month and day from the base time for the parser to use as + # defaults + + set date [GetDateFields $base $TZData($timezone) 2361222] + dict set date secondOfDay [expr { + [dict get $date localSeconds] % 86400 + }] + + # Parse the date. The parser will return a list comprising date, time, + # time zone, relative month/day/seconds, relative weekday, ordinal month. + + try { + set scanned [Oldscan $string \ + [dict get $date year] \ + [dict get $date month] \ + [dict get $date dayOfMonth]] + lassign $scanned \ + parseDate parseTime parseZone parseRel \ + parseWeekday parseOrdinalMonth + } on error message { + return -code error \ + "unable to convert date-time string \"$string\": $message" + } + + # If the caller supplied a date in the string, update the 'date' dict with + # the value. If the caller didn't specify a time with the date, default to + # midnight. + + if { [llength $parseDate] > 0 } { + lassign $parseDate y m d + if { $y < 100 } { + if { $y >= 39 } { + incr y 1900 + } else { + incr y 2000 + } + } + dict set date era CE + dict set date year $y + dict set date month $m + dict set date dayOfMonth $d + if { $parseTime eq {} } { + set parseTime 0 + } + } + + # If the caller supplied a time zone in the string, it comes back as a + # two-element list; the first element is the number of minutes east of + # Greenwich, and the second is a Daylight Saving Time indicator (1 == yes, + # 0 == no, -1 == unknown). We make it into a time zone indicator of + # +-hhmm. + + if { [llength $parseZone] > 0 } { + lassign $parseZone minEast dstFlag + set timezone [FormatNumericTimeZone \ + [expr { 60 * $minEast + 3600 * $dstFlag }]] + SetupTimeZone $timezone + } + dict set date tzName $timezone + + # Assemble date, time, zone into seconds-from-epoch + + set date [GetJulianDayFromEraYearMonthDay $date[set date {}] 2361222] + if { $parseTime ne {} } { + dict set date secondOfDay $parseTime + } elseif { [llength $parseWeekday] != 0 + || [llength $parseOrdinalMonth] != 0 + || ( [llength $parseRel] != 0 + && ( [lindex $parseRel 0] != 0 + || [lindex $parseRel 1] != 0 ) ) } { + dict set date secondOfDay 0 + } + + dict set date localSeconds [expr { + -210866803200 + + ( 86400 * wide([dict get $date julianDay]) ) + + [dict get $date secondOfDay] + }] + dict set date tzName $timezone + set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) 2361222] + set seconds [dict get $date seconds] + + # Do relative times + + if { [llength $parseRel] > 0 } { + lassign $parseRel relMonth relDay relSecond + set seconds [add $seconds \ + $relMonth months $relDay days $relSecond seconds \ + -timezone $timezone -locale $locale] + } + + # Do relative weekday + + if { [llength $parseWeekday] > 0 } { + lassign $parseWeekday dayOrdinal dayOfWeek + set date2 [GetDateFields $seconds $TZData($timezone) 2361222] + dict set date2 era CE + set jdwkday [WeekdayOnOrBefore $dayOfWeek [expr { + [dict get $date2 julianDay] + 6 + }]] + incr jdwkday [expr { 7 * $dayOrdinal }] + if { $dayOrdinal > 0 } { + incr jdwkday -7 + } + dict set date2 secondOfDay \ + [expr { [dict get $date2 localSeconds] % 86400 }] + dict set date2 julianDay $jdwkday + dict set date2 localSeconds [expr { + -210866803200 + + ( 86400 * wide([dict get $date2 julianDay]) ) + + [dict get $date secondOfDay] + }] + dict set date2 tzName $timezone + set date2 [ConvertLocalToUTC $date2[set date2 {}] $TZData($timezone) \ + 2361222] + set seconds [dict get $date2 seconds] + + } + + # Do relative month + + if { [llength $parseOrdinalMonth] > 0 } { + lassign $parseOrdinalMonth monthOrdinal monthNumber + if { $monthOrdinal > 0 } { + set monthDiff [expr { $monthNumber - [dict get $date month] }] + if { $monthDiff <= 0 } { + incr monthDiff 12 + } + incr monthOrdinal -1 + } else { + set monthDiff [expr { [dict get $date month] - $monthNumber }] + if { $monthDiff >= 0 } { + incr monthDiff -12 + } + incr monthOrdinal + } + set seconds [add $seconds $monthOrdinal years $monthDiff months \ + -timezone $timezone -locale $locale] + } + + return $seconds +} + + +#---------------------------------------------------------------------- +# +# ParseClockScanFormat -- +# +# Parses a format string given to [clock scan -format] +# +# Parameters: +# formatString - The format being parsed +# locale - The current locale +# +# Results: +# Constructs and returns a procedure that accepts the string being +# scanned, the base time, and the time zone. The procedure will either +# return the scanned time or else throw an error that should be rethrown +# to the caller of [clock scan] +# +# Side effects: +# The given procedure is defined in the ::tcl::clock namespace. Scan +# procedures are not deleted once installed. +# +# Why do we parse dates by defining a procedure to parse them? The reason is +# that by doing so, we have one convenient place to cache all the information: +# the regular expressions that match the patterns (which will be compiled), +# the code that assembles the date information, everything lands in one place. +# In this way, when a given format is reused at run time, all the information +# of how to apply it is available in a single place. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::ParseClockScanFormat {formatString locale} { + # Check whether the format has been parsed previously, and return the + # existing recognizer if it has. + + set procName scanproc'$formatString'$locale + set procName [namespace current]::[string map {: {\:} \\ {\\}} $procName] + if { [namespace which $procName] != {} } { + return $procName + } + + variable DateParseActions + variable TimeParseActions + + # Localize the %x, %X, etc. groups + + set formatString [LocalizeFormat $locale $formatString] + + # Condense whitespace + + regsub -all {[[:space:]]+} $formatString { } formatString + + # Walk through the groups of the format string. In this loop, we + # accumulate: + # - a regular expression that matches the string, + # - the count of capturing brackets in the regexp + # - a set of code that post-processes the fields captured by the regexp, + # - a dictionary whose keys are the names of fields that are present + # in the format string. + + set re {^[[:space:]]*} + set captureCount 0 + set postcode {} + set fieldSet [dict create] + set fieldCount 0 + set postSep {} + set state {} + + foreach c [split $formatString {}] { + switch -exact -- $state { + {} { + if { $c eq "%" } { + set state % + } elseif { $c eq " " } { + append re {[[:space:]]+} + } else { + if { ! [string is alnum $c] } { + append re "\\" + } + append re $c + } + } + % { + set state {} + switch -exact -- $c { + % { + append re % + } + { } { + append re "\[\[:space:\]\]*" + } + a - A { # Day of week, in words + set l {} + foreach \ + i {7 1 2 3 4 5 6} \ + abr [mc DAYS_OF_WEEK_ABBREV] \ + full [mc DAYS_OF_WEEK_FULL] { + dict set l [string tolower $abr] $i + dict set l [string tolower $full] $i + incr i + } + lassign [UniquePrefixRegexp $l] regex lookup + append re ( $regex ) + dict set fieldSet dayOfWeek [incr fieldCount] + append postcode "dict set date dayOfWeek \[" \ + "dict get " [list $lookup] " " \ + \[ {string tolower $field} [incr captureCount] \] \ + "\]\n" + } + b - B - h { # Name of month + set i 0 + set l {} + foreach \ + abr [mc MONTHS_ABBREV] \ + full [mc MONTHS_FULL] { + incr i + dict set l [string tolower $abr] $i + dict set l [string tolower $full] $i + } + lassign [UniquePrefixRegexp $l] regex lookup + append re ( $regex ) + dict set fieldSet month [incr fieldCount] + append postcode "dict set date month \[" \ + "dict get " [list $lookup] \ + " " \[ {string tolower $field} \ + [incr captureCount] \] \ + "\]\n" + } + C { # Gregorian century + append re \\s*(\\d\\d?) + dict set fieldSet century [incr fieldCount] + append postcode "dict set date century \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + d - e { # Day of month + append re \\s*(\\d\\d?) + dict set fieldSet dayOfMonth [incr fieldCount] + append postcode "dict set date dayOfMonth \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + E { # Prefix for locale-specific codes + set state %E + } + g { # ISO8601 2-digit year + append re \\s*(\\d\\d) + dict set fieldSet iso8601YearOfCentury \ + [incr fieldCount] + append postcode \ + "dict set date iso8601YearOfCentury \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + G { # ISO8601 4-digit year + append re \\s*(\\d\\d)(\\d\\d) + dict set fieldSet iso8601Century [incr fieldCount] + dict set fieldSet iso8601YearOfCentury \ + [incr fieldCount] + append postcode \ + "dict set date iso8601Century \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" \ + "dict set date iso8601YearOfCentury \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + H - k { # Hour of day + append re \\s*(\\d\\d?) + dict set fieldSet hour [incr fieldCount] + append postcode "dict set date hour \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + I - l { # Hour, AM/PM + append re \\s*(\\d\\d?) + dict set fieldSet hourAMPM [incr fieldCount] + append postcode "dict set date hourAMPM \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + j { # Day of year + append re \\s*(\\d\\d?\\d?) + dict set fieldSet dayOfYear [incr fieldCount] + append postcode "dict set date dayOfYear \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + J { # Julian Day Number + append re \\s*(\\d+) + dict set fieldSet julianDay [incr fieldCount] + append postcode "dict set date julianDay \[" \ + "::scan \$field" [incr captureCount] " %ld" \ + "\]\n" + } + m - N { # Month number + append re \\s*(\\d\\d?) + dict set fieldSet month [incr fieldCount] + append postcode "dict set date month \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + M { # Minute + append re \\s*(\\d\\d?) + dict set fieldSet minute [incr fieldCount] + append postcode "dict set date minute \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + n { # Literal newline + append re \\n + } + O { # Prefix for locale numerics + set state %O + } + p - P { # AM/PM indicator + set l [list [string tolower [mc AM]] 0 \ + [string tolower [mc PM]] 1] + lassign [UniquePrefixRegexp $l] regex lookup + append re ( $regex ) + dict set fieldSet amPmIndicator [incr fieldCount] + append postcode "dict set date amPmIndicator \[" \ + "dict get " [list $lookup] " \[string tolower " \ + "\$field" \ + [incr captureCount] \ + "\]\]\n" + } + Q { # Hi, Jeff! + append re {Stardate\s+([-+]?\d+)(\d\d\d)[.](\d)} + incr captureCount + dict set fieldSet seconds [incr fieldCount] + append postcode {dict set date seconds } \[ \ + {ParseStarDate $field} [incr captureCount] \ + { $field} [incr captureCount] \ + { $field} [incr captureCount] \ + \] \n + } + s { # Seconds from Posix Epoch + # This next case is insanely difficult, because it's + # problematic to determine whether the field is + # actually within the range of a wide integer. + append re {\s*([-+]?\d+)} + dict set fieldSet seconds [incr fieldCount] + append postcode {dict set date seconds } \[ \ + {ScanWide $field} [incr captureCount] \] \n + } + S { # Second + append re \\s*(\\d\\d?) + dict set fieldSet second [incr fieldCount] + append postcode "dict set date second \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + t { # Literal tab character + append re \\t + } + u - w { # Day number within week, 0 or 7 == Sun + # 1=Mon, 6=Sat + append re \\s*(\\d) + dict set fieldSet dayOfWeek [incr fieldCount] + append postcode {::scan $field} [incr captureCount] \ + { %d dow} \n \ + { + if { $dow == 0 } { + set dow 7 + } elseif { $dow > 7 } { + return -code error \ + -errorcode [list CLOCK badDayOfWeek] \ + "day of week is greater than 7" + } + dict set date dayOfWeek $dow + } + } + U { # Week of year. The first Sunday of + # the year is the first day of week + # 01. No scan rule uses this group. + append re \\s*\\d\\d? + } + V { # Week of ISO8601 year + + append re \\s*(\\d\\d?) + dict set fieldSet iso8601Week [incr fieldCount] + append postcode "dict set date iso8601Week \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + W { # Week of the year (00-53). The first + # Monday of the year is the first day + # of week 01. No scan rule uses this + # group. + append re \\s*\\d\\d? + } + y { # Two-digit Gregorian year + append re \\s*(\\d\\d?) + dict set fieldSet yearOfCentury [incr fieldCount] + append postcode "dict set date yearOfCentury \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + Y { # 4-digit Gregorian year + append re \\s*(\\d\\d)(\\d\\d) + dict set fieldSet century [incr fieldCount] + dict set fieldSet yearOfCentury [incr fieldCount] + append postcode \ + "dict set date century \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" \ + "dict set date yearOfCentury \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + z - Z { # Time zone name + append re {(?:([-+]\d\d(?::?\d\d(?::?\d\d)?)?)|([[:alnum:]]{1,4}))} + dict set fieldSet tzName [incr fieldCount] + append postcode \ + {if } \{ { $field} [incr captureCount] \ + { ne "" } \} { } \{ \n \ + {dict set date tzName $field} \ + $captureCount \n \ + \} { else } \{ \n \ + {dict set date tzName } \[ \ + {ConvertLegacyTimeZone $field} \ + [incr captureCount] \] \n \ + \} \n \ + } + % { # Literal percent character + append re % + } + default { + append re % + if { ! [string is alnum $c] } { + append re \\ + } + append re $c + } + } + } + %E { + switch -exact -- $c { + C { # Locale-dependent era + set d {} + foreach triple [mc LOCALE_ERAS] { + lassign $triple t symbol year + dict set d [string tolower $symbol] $year + } + lassign [UniquePrefixRegexp $d] regex lookup + append re (?: $regex ) + } + E { + set l {} + dict set l [string tolower [mc BCE]] BCE + dict set l [string tolower [mc CE]] CE + dict set l b.c.e. BCE + dict set l c.e. CE + dict set l b.c. BCE + dict set l a.d. CE + lassign [UniquePrefixRegexp $l] regex lookup + append re ( $regex ) + dict set fieldSet era [incr fieldCount] + append postcode "dict set date era \["\ + "dict get " [list $lookup] \ + { } \[ {string tolower $field} \ + [incr captureCount] \] \ + "\]\n" + } + y { # Locale-dependent year of the era + lassign [LocaleNumeralMatcher $locale] regex lookup + append re $regex + incr captureCount + } + default { + append re %E + if { ! [string is alnum $c] } { + append re \\ + } + append re $c + } + } + set state {} + } + %O { + switch -exact -- $c { + d - e { + lassign [LocaleNumeralMatcher $locale] regex lookup + append re $regex + dict set fieldSet dayOfMonth [incr fieldCount] + append postcode "dict set date dayOfMonth \[" \ + "dict get " [list $lookup] " \$field" \ + [incr captureCount] \ + "\]\n" + } + H - k { + lassign [LocaleNumeralMatcher $locale] regex lookup + append re $regex + dict set fieldSet hour [incr fieldCount] + append postcode "dict set date hour \[" \ + "dict get " [list $lookup] " \$field" \ + [incr captureCount] \ + "\]\n" + } + I - l { + lassign [LocaleNumeralMatcher $locale] regex lookup + append re $regex + dict set fieldSet hourAMPM [incr fieldCount] + append postcode "dict set date hourAMPM \[" \ + "dict get " [list $lookup] " \$field" \ + [incr captureCount] \ + "\]\n" + } + m { + lassign [LocaleNumeralMatcher $locale] regex lookup + append re $regex + dict set fieldSet month [incr fieldCount] + append postcode "dict set date month \[" \ + "dict get " [list $lookup] " \$field" \ + [incr captureCount] \ + "\]\n" + } + M { + lassign [LocaleNumeralMatcher $locale] regex lookup + append re $regex + dict set fieldSet minute [incr fieldCount] + append postcode "dict set date minute \[" \ + "dict get " [list $lookup] " \$field" \ + [incr captureCount] \ + "\]\n" + } + S { + lassign [LocaleNumeralMatcher $locale] regex lookup + append re $regex + dict set fieldSet second [incr fieldCount] + append postcode "dict set date second \[" \ + "dict get " [list $lookup] " \$field" \ + [incr captureCount] \ + "\]\n" + } + u - w { + lassign [LocaleNumeralMatcher $locale] regex lookup + append re $regex + dict set fieldSet dayOfWeek [incr fieldCount] + append postcode "set dow \[dict get " [list $lookup] \ + { $field} [incr captureCount] \] \n \ + { + if { $dow == 0 } { + set dow 7 + } elseif { $dow > 7 } { + return -code error \ + -errorcode [list CLOCK badDayOfWeek] \ + "day of week is greater than 7" + } + dict set date dayOfWeek $dow + } + } + y { + lassign [LocaleNumeralMatcher $locale] regex lookup + append re $regex + dict set fieldSet yearOfCentury [incr fieldCount] + append postcode {dict set date yearOfCentury } \[ \ + {dict get } [list $lookup] { $field} \ + [incr captureCount] \] \n + } + default { + append re %O + if { ! [string is alnum $c] } { + append re \\ + } + append re $c + } + } + set state {} + } + } + } + + # Clean up any unfinished format groups + + append re $state \\s*\$ + + # Build the procedure + + set procBody {} + append procBody "variable ::tcl::clock::TZData" \n + append procBody "if \{ !\[ regexp -nocase [list $re] \$string ->" + for { set i 1 } { $i <= $captureCount } { incr i } { + append procBody " " field $i + } + append procBody "\] \} \{" \n + append procBody { + return -code error -errorcode [list CLOCK badInputString] \ + {input string does not match supplied format} + } + append procBody \}\n + append procBody "set date \[dict create\]" \n + append procBody {dict set date tzName $timeZone} \n + append procBody $postcode + append procBody [list set changeover [mc GREGORIAN_CHANGE_DATE]] \n + + # Set up the time zone before doing anything with a default base date + # that might need a timezone to interpret it. + + if { ![dict exists $fieldSet seconds] + && ![dict exists $fieldSet starDate] } { + if { [dict exists $fieldSet tzName] } { + append procBody { + set timeZone [dict get $date tzName] + } + } + append procBody { + ::tcl::clock::SetupTimeZone $timeZone + } + } + + # Add code that gets Julian Day Number from the fields. + + append procBody [MakeParseCodeFromFields $fieldSet $DateParseActions] + + # Get time of day + + append procBody [MakeParseCodeFromFields $fieldSet $TimeParseActions] + + # Assemble seconds from the Julian day and second of the day. + # Convert to local time unless epoch seconds or stardate are + # being processed - they're always absolute + + if { ![dict exists $fieldSet seconds] + && ![dict exists $fieldSet starDate] } { + append procBody { + if { [dict get $date julianDay] > 5373484 } { + return -code error -errorcode [list CLOCK dateTooLarge] \ + "requested date too large to represent" + } + dict set date localSeconds [expr { + -210866803200 + + ( 86400 * wide([dict get $date julianDay]) ) + + [dict get $date secondOfDay] + }] + } + + # Finally, convert the date to local time + + append procBody { + set date [::tcl::clock::ConvertLocalToUTC $date[set date {}] \ + $TZData($timeZone) $changeover] + } + } + + # Return result + + append procBody {return [dict get $date seconds]} \n + + proc $procName { string baseTime timeZone } $procBody + + # puts [list proc $procName [list string baseTime timeZone] $procBody] + + return $procName +} + +#---------------------------------------------------------------------- +# +# LocaleNumeralMatcher -- +# +# Composes a regexp that captures the numerals in the given locale, and +# a dictionary to map them to conventional numerals. +# +# Parameters: +# locale - Name of the current locale +# +# Results: +# Returns a two-element list comprising the regexp and the dictionary. +# +# Side effects: +# Caches the result. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::LocaleNumeralMatcher {l} { + variable LocaleNumeralCache + + if { ![dict exists $LocaleNumeralCache $l] } { + set d {} + set i 0 + set sep \( + foreach n [mc LOCALE_NUMERALS] { + dict set d $n $i + regsub -all {[^[:alnum:]]} $n \\\\& subex + append re $sep $subex + set sep | + incr i + } + append re \) + dict set LocaleNumeralCache $l [list $re $d] + } + return [dict get $LocaleNumeralCache $l] +} + + + +#---------------------------------------------------------------------- +# +# UniquePrefixRegexp -- +# +# Composes a regexp that performs unique-prefix matching. The RE +# matches one of a supplied set of strings, or any unique prefix +# thereof. +# +# Parameters: +# data - List of alternating match-strings and values. +# Match-strings with distinct values are considered +# distinct. +# +# Results: +# Returns a two-element list. The first is a regexp that matches any +# unique prefix of any of the strings. The second is a dictionary whose +# keys are match values from the regexp and whose values are the +# corresponding values from 'data'. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::UniquePrefixRegexp { data } { + # The 'successors' dictionary will contain, for each string that is a + # prefix of any key, all characters that may follow that prefix. The + # 'prefixMapping' dictionary will have keys that are prefixes of keys and + # values that correspond to the keys. + + set prefixMapping [dict create] + set successors [dict create {} {}] + + # Walk the key-value pairs + + foreach { key value } $data { + # Construct all prefixes of the key; + + set prefix {} + foreach char [split $key {}] { + set oldPrefix $prefix + dict set successors $oldPrefix $char {} + append prefix $char + + # Put the prefixes in the 'prefixMapping' and 'successors' + # dictionaries + + dict lappend prefixMapping $prefix $value + if { ![dict exists $successors $prefix] } { + dict set successors $prefix {} + } + } + } + + # Identify those prefixes that designate unique values, and those that are + # the full keys + + set uniquePrefixMapping {} + dict for { key valueList } $prefixMapping { + if { [llength $valueList] == 1 } { + dict set uniquePrefixMapping $key [lindex $valueList 0] + } + } + foreach { key value } $data { + dict set uniquePrefixMapping $key $value + } + + # Construct the re. + + return [list \ + [MakeUniquePrefixRegexp $successors $uniquePrefixMapping {}] \ + $uniquePrefixMapping] +} + +#---------------------------------------------------------------------- +# +# MakeUniquePrefixRegexp -- +# +# Service procedure for 'UniquePrefixRegexp' that constructs a regular +# expresison that matches the unique prefixes. +# +# Parameters: +# successors - Dictionary whose keys are all prefixes +# of keys passed to 'UniquePrefixRegexp' and whose +# values are dictionaries whose keys are the characters +# that may follow those prefixes. +# uniquePrefixMapping - Dictionary whose keys are the unique +# prefixes and whose values are not examined. +# prefixString - Current prefix being processed. +# +# Results: +# Returns a constructed regular expression that matches the set of +# unique prefixes beginning with the 'prefixString'. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::MakeUniquePrefixRegexp { successors + uniquePrefixMapping + prefixString } { + + # Get the characters that may follow the current prefix string + + set schars [lsort -ascii [dict keys [dict get $successors $prefixString]]] + if { [llength $schars] == 0 } { + return {} + } + + # If there is more than one successor character, or if the current prefix + # is a unique prefix, surround the generated re with non-capturing + # parentheses. + + set re {} + if { + [dict exists $uniquePrefixMapping $prefixString] + || [llength $schars] > 1 + } then { + append re "(?:" + } + + # Generate a regexp that matches the successors. + + set sep "" + foreach { c } $schars { + set nextPrefix $prefixString$c + regsub -all {[^[:alnum:]]} $c \\\\& rechar + append re $sep $rechar \ + [MakeUniquePrefixRegexp \ + $successors $uniquePrefixMapping $nextPrefix] + set sep | + } + + # If the current prefix is a unique prefix, make all following text + # optional. Otherwise, if there is more than one successor character, + # close the non-capturing parentheses. + + if { [dict exists $uniquePrefixMapping $prefixString] } { + append re ")?" + } elseif { [llength $schars] > 1 } { + append re ")" + } + + return $re +} + +#---------------------------------------------------------------------- +# +# MakeParseCodeFromFields -- +# +# Composes Tcl code to extract the Julian Day Number from a dictionary +# containing date fields. +# +# Parameters: +# dateFields -- Dictionary whose keys are fields of the date, +# and whose values are the rightmost positions +# at which those fields appear. +# parseActions -- List of triples: field set, priority, and +# code to emit. Smaller priorities are better, and +# the list must be in ascending order by priority +# +# Results: +# Returns a burst of code that extracts the day number from the given +# date. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::MakeParseCodeFromFields { dateFields parseActions } { + + set currPrio 999 + set currFieldPos [list] + set currCodeBurst { + error "in ::tcl::clock::MakeParseCodeFromFields: can't happen" + } + + foreach { fieldSet prio parseAction } $parseActions { + # If we've found an answer that's better than any that follow, quit + # now. + + if { $prio > $currPrio } { + break + } + + # Accumulate the field positions that are used in the current field + # grouping. + + set fieldPos [list] + set ok true + foreach field $fieldSet { + if { ! [dict exists $dateFields $field] } { + set ok 0 + break + } + lappend fieldPos [dict get $dateFields $field] + } + + # Quit if we don't have a complete set of fields + if { !$ok } { + continue + } + + # Determine whether the current answer is better than the last. + + set fPos [lsort -integer -decreasing $fieldPos] + + if { $prio == $currPrio } { + foreach currPos $currFieldPos newPos $fPos { + if { + ![string is integer $newPos] + || ![string is integer $currPos] + || $newPos > $currPos + } then { + break + } + if { $newPos < $currPos } { + set ok 0 + break + } + } + } + if { !$ok } { + continue + } + + # Remember the best possibility for extracting date information + + set currPrio $prio + set currFieldPos $fPos + set currCodeBurst $parseAction + } + + return $currCodeBurst +} + +#---------------------------------------------------------------------- +# +# EnterLocale -- +# +# Switch [mclocale] to a given locale if necessary +# +# Parameters: +# locale -- Desired locale +# +# Results: +# Returns the locale that was previously current. +# +# Side effects: +# Does [mclocale]. If necessary, loades the designated locale's files. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::EnterLocale { locale } { + if { $locale eq {system} } { + if { $::tcl_platform(platform) ne {windows} } { + # On a non-windows platform, the 'system' locale is the same as + # the 'current' locale + + set locale current + } else { + # On a windows platform, the 'system' locale is adapted from the + # 'current' locale by applying the date and time formats from the + # Control Panel. First, load the 'current' locale if it's not yet + # loaded + + mcpackagelocale set [mclocale] + + # Make a new locale string for the system locale, and get the + # Control Panel information + + set locale [mclocale]_windows + if { ! [mcpackagelocale present $locale] } { + LoadWindowsDateTimeFormats $locale + } + } + } + if { $locale eq {current}} { + set locale [mclocale] + } + # Eventually load the locale + mcpackagelocale set $locale +} + +#---------------------------------------------------------------------- +# +# LoadWindowsDateTimeFormats -- +# +# Load the date/time formats from the Control Panel in Windows and +# convert them so that they're usable by Tcl. +# +# Parameters: +# locale - Name of the locale in whose message catalog +# the converted formats are to be stored. +# +# Results: +# None. +# +# Side effects: +# Updates the given message catalog with the locale strings. +# +# Presumes that on entry, [mclocale] is set to the current locale, so that +# default strings can be obtained if the Registry query fails. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::LoadWindowsDateTimeFormats { locale } { + # Bail out if we can't find the Registry + + variable NoRegistry + if { [info exists NoRegistry] } return + + if { ![catch { + registry get "HKEY_CURRENT_USER\\Control Panel\\International" \ + sShortDate + } string] } { + set quote {} + set datefmt {} + foreach { unquoted quoted } [split $string '] { + append datefmt $quote [string map { + dddd %A + ddd %a + dd %d + d %e + MMMM %B + MMM %b + MM %m + M %N + yyyy %Y + yy %y + y %y + gg {} + } $unquoted] + if { $quoted eq {} } { + set quote ' + } else { + set quote $quoted + } + } + ::msgcat::mcset $locale DATE_FORMAT $datefmt + } + + if { ![catch { + registry get "HKEY_CURRENT_USER\\Control Panel\\International" \ + sLongDate + } string] } { + set quote {} + set ldatefmt {} + foreach { unquoted quoted } [split $string '] { + append ldatefmt $quote [string map { + dddd %A + ddd %a + dd %d + d %e + MMMM %B + MMM %b + MM %m + M %N + yyyy %Y + yy %y + y %y + gg {} + } $unquoted] + if { $quoted eq {} } { + set quote ' + } else { + set quote $quoted + } + } + ::msgcat::mcset $locale LOCALE_DATE_FORMAT $ldatefmt + } + + if { ![catch { + registry get "HKEY_CURRENT_USER\\Control Panel\\International" \ + sTimeFormat + } string] } { + set quote {} + set timefmt {} + foreach { unquoted quoted } [split $string '] { + append timefmt $quote [string map { + HH %H + H %k + hh %I + h %l + mm %M + m %M + ss %S + s %S + tt %p + t %p + } $unquoted] + if { $quoted eq {} } { + set quote ' + } else { + set quote $quoted + } + } + ::msgcat::mcset $locale TIME_FORMAT $timefmt + } + + catch { + ::msgcat::mcset $locale DATE_TIME_FORMAT "$datefmt $timefmt" + } + catch { + ::msgcat::mcset $locale LOCALE_DATE_TIME_FORMAT "$ldatefmt $timefmt" + } + + return + +} + +#---------------------------------------------------------------------- +# +# LocalizeFormat -- +# +# Map away locale-dependent format groups in a clock format. +# +# Parameters: +# locale -- Current [mclocale] locale, supplied to avoid +# an extra call +# format -- Format supplied to [clock scan] or [clock format] +# +# Results: +# Returns the string with locale-dependent composite format groups +# substituted out. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::LocalizeFormat { locale format } { + + # message catalog key to cache this format + set key FORMAT_$format + + if { [::msgcat::mcexists -exactlocale -exactnamespace $key] } { + return [mc $key] + } + # Handle locale-dependent format groups by mapping them out of the format + # string. Note that the order of the [string map] operations is + # significant because later formats can refer to later ones; for example + # %c can refer to %X, which in turn can refer to %T. + + set list { + %% %% + %D %m/%d/%Y + %+ {%a %b %e %H:%M:%S %Z %Y} + } + lappend list %EY [string map $list [mc LOCALE_YEAR_FORMAT]] + lappend list %T [string map $list [mc TIME_FORMAT_24_SECS]] + lappend list %R [string map $list [mc TIME_FORMAT_24]] + lappend list %r [string map $list [mc TIME_FORMAT_12]] + lappend list %X [string map $list [mc TIME_FORMAT]] + lappend list %EX [string map $list [mc LOCALE_TIME_FORMAT]] + lappend list %x [string map $list [mc DATE_FORMAT]] + lappend list %Ex [string map $list [mc LOCALE_DATE_FORMAT]] + lappend list %c [string map $list [mc DATE_TIME_FORMAT]] + lappend list %Ec [string map $list [mc LOCALE_DATE_TIME_FORMAT]] + set format [string map $list $format] + + ::msgcat::mcset $locale $key $format + return $format +} + +#---------------------------------------------------------------------- +# +# FormatNumericTimeZone -- +# +# Formats a time zone as +hhmmss +# +# Parameters: +# z - Time zone in seconds east of Greenwich +# +# Results: +# Returns the time zone formatted in a numeric form +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::FormatNumericTimeZone { z } { + if { $z < 0 } { + set z [expr { - $z }] + set retval - + } else { + set retval + + } + append retval [::format %02d [expr { $z / 3600 }]] + set z [expr { $z % 3600 }] + append retval [::format %02d [expr { $z / 60 }]] + set z [expr { $z % 60 }] + if { $z != 0 } { + append retval [::format %02d $z] + } + return $retval +} + +#---------------------------------------------------------------------- +# +# FormatStarDate -- +# +# Formats a date as a StarDate. +# +# Parameters: +# date - Dictionary containing 'year', 'dayOfYear', and +# 'localSeconds' fields. +# +# Results: +# Returns the given date formatted as a StarDate. +# +# Side effects: +# None. +# +# Jeff Hobbs put this in to support an atrocious pun about Tcl being +# "Enterprise ready." Now we're stuck with it. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::FormatStarDate { date } { + variable Roddenberry + + # Get day of year, zero based + + set doy [expr { [dict get $date dayOfYear] - 1 }] + + # Determine whether the year is a leap year + + set lp [IsGregorianLeapYear $date] + + # Convert day of year to a fractional year + + if { $lp } { + set fractYear [expr { 1000 * $doy / 366 }] + } else { + set fractYear [expr { 1000 * $doy / 365 }] + } + + # Put together the StarDate + + return [::format "Stardate %02d%03d.%1d" \ + [expr { [dict get $date year] - $Roddenberry }] \ + $fractYear \ + [expr { [dict get $date localSeconds] % 86400 + / ( 86400 / 10 ) }]] +} + +#---------------------------------------------------------------------- +# +# ParseStarDate -- +# +# Parses a StarDate +# +# Parameters: +# year - Year from the Roddenberry epoch +# fractYear - Fraction of a year specifiying the day of year. +# fractDay - Fraction of a day +# +# Results: +# Returns a count of seconds from the Posix epoch. +# +# Side effects: +# None. +# +# Jeff Hobbs put this in to support an atrocious pun about Tcl being +# "Enterprise ready." Now we're stuck with it. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::ParseStarDate { year fractYear fractDay } { + variable Roddenberry + + # Build a tentative date from year and fraction. + + set date [dict create \ + gregorian 1 \ + era CE \ + year [expr { $year + $Roddenberry }] \ + dayOfYear [expr { $fractYear * 365 / 1000 + 1 }]] + set date [GetJulianDayFromGregorianEraYearDay $date[set date {}]] + + # Determine whether the given year is a leap year + + set lp [IsGregorianLeapYear $date] + + # Reconvert the fractional year according to whether the given year is a + # leap year + + if { $lp } { + dict set date dayOfYear \ + [expr { $fractYear * 366 / 1000 + 1 }] + } else { + dict set date dayOfYear \ + [expr { $fractYear * 365 / 1000 + 1 }] + } + dict unset date julianDay + dict unset date gregorian + set date [GetJulianDayFromGregorianEraYearDay $date[set date {}]] + + return [expr { + 86400 * [dict get $date julianDay] + - 210866803200 + + ( 86400 / 10 ) * $fractDay + }] +} + +#---------------------------------------------------------------------- +# +# ScanWide -- +# +# Scans a wide integer from an input +# +# Parameters: +# str - String containing a decimal wide integer +# +# Results: +# Returns the string as a pure wide integer. Throws an error if the +# string is misformatted or out of range. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::ScanWide { str } { + set count [::scan $str {%ld %c} result junk] + if { $count != 1 } { + return -code error -errorcode [list CLOCK notAnInteger $str] \ + "\"$str\" is not an integer" + } + if { [incr result 0] != $str } { + return -code error -errorcode [list CLOCK integervalueTooLarge] \ + "integer value too large to represent" + } + return $result +} + +#---------------------------------------------------------------------- +# +# InterpretTwoDigitYear -- +# +# Given a date that contains only the year of the century, determines +# the target value of a two-digit year. +# +# Parameters: +# date - Dictionary containing fields of the date. +# baseTime - Base time relative to which the date is expressed. +# twoDigitField - Name of the field that stores the two-digit year. +# Default is 'yearOfCentury' +# fourDigitField - Name of the field that will receive the four-digit +# year. Default is 'year' +# +# Results: +# Returns the dictionary augmented with the four-digit year, stored in +# the given key. +# +# Side effects: +# None. +# +# The current rule for interpreting a two-digit year is that the year shall be +# between 1937 and 2037, thus staying within the range of a 32-bit signed +# value for time. This rule may change to a sliding window in future +# versions, so the 'baseTime' parameter (which is currently ignored) is +# provided in the procedure signature. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::InterpretTwoDigitYear { date baseTime + { twoDigitField yearOfCentury } + { fourDigitField year } } { + set yr [dict get $date $twoDigitField] + if { $yr <= 37 } { + dict set date $fourDigitField [expr { $yr + 2000 }] + } else { + dict set date $fourDigitField [expr { $yr + 1900 }] + } + return $date +} + +#---------------------------------------------------------------------- +# +# AssignBaseYear -- +# +# Places the number of the current year into a dictionary. +# +# Parameters: +# date - Dictionary value to update +# baseTime - Base time from which to extract the year, expressed +# in seconds from the Posix epoch +# timezone - the time zone in which the date is being scanned +# changeover - the Julian Day on which the Gregorian calendar +# was adopted in the target locale. +# +# Results: +# Returns the dictionary with the current year assigned. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::AssignBaseYear { date baseTime timezone changeover } { + variable TZData + + # Find the Julian Day Number corresponding to the base time, and + # find the Gregorian year corresponding to that Julian Day. + + set date2 [GetDateFields $baseTime $TZData($timezone) $changeover] + + # Store the converted year + + dict set date era [dict get $date2 era] + dict set date year [dict get $date2 year] + + return $date +} + +#---------------------------------------------------------------------- +# +# AssignBaseIso8601Year -- +# +# Determines the base year in the ISO8601 fiscal calendar. +# +# Parameters: +# date - Dictionary containing the fields of the date that +# is to be augmented with the base year. +# baseTime - Base time expressed in seconds from the Posix epoch. +# timeZone - Target time zone +# changeover - Julian Day of adoption of the Gregorian calendar in +# the target locale. +# +# Results: +# Returns the given date with "iso8601Year" set to the +# base year. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::AssignBaseIso8601Year {date baseTime timeZone changeover} { + variable TZData + + # Find the Julian Day Number corresponding to the base time + + set date2 [GetDateFields $baseTime $TZData($timeZone) $changeover] + + # Calculate the ISO8601 date and transfer the year + + dict set date era CE + dict set date iso8601Year [dict get $date2 iso8601Year] + return $date +} + +#---------------------------------------------------------------------- +# +# AssignBaseMonth -- +# +# Places the number of the current year and month into a +# dictionary. +# +# Parameters: +# date - Dictionary value to update +# baseTime - Time from which the year and month are to be +# obtained, expressed in seconds from the Posix epoch. +# timezone - Name of the desired time zone +# changeover - Julian Day on which the Gregorian calendar was adopted. +# +# Results: +# Returns the dictionary with the base year and month assigned. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::AssignBaseMonth {date baseTime timezone changeover} { + variable TZData + + # Find the year and month corresponding to the base time + + set date2 [GetDateFields $baseTime $TZData($timezone) $changeover] + dict set date era [dict get $date2 era] + dict set date year [dict get $date2 year] + dict set date month [dict get $date2 month] + return $date +} + +#---------------------------------------------------------------------- +# +# AssignBaseWeek -- +# +# Determines the base year and week in the ISO8601 fiscal calendar. +# +# Parameters: +# date - Dictionary containing the fields of the date that +# is to be augmented with the base year and week. +# baseTime - Base time expressed in seconds from the Posix epoch. +# changeover - Julian Day on which the Gregorian calendar was adopted +# in the target locale. +# +# Results: +# Returns the given date with "iso8601Year" set to the +# base year and "iso8601Week" to the week number. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::AssignBaseWeek {date baseTime timeZone changeover} { + variable TZData + + # Find the Julian Day Number corresponding to the base time + + set date2 [GetDateFields $baseTime $TZData($timeZone) $changeover] + + # Calculate the ISO8601 date and transfer the year + + dict set date era CE + dict set date iso8601Year [dict get $date2 iso8601Year] + dict set date iso8601Week [dict get $date2 iso8601Week] + return $date +} + +#---------------------------------------------------------------------- +# +# AssignBaseJulianDay -- +# +# Determines the base day for a time-of-day conversion. +# +# Parameters: +# date - Dictionary that is to get the base day +# baseTime - Base time expressed in seconds from the Posix epoch +# changeover - Julian day on which the Gregorian calendar was +# adpoted in the target locale. +# +# Results: +# Returns the given dictionary augmented with a 'julianDay' field +# that contains the base day. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::AssignBaseJulianDay { date baseTime timeZone changeover } { + variable TZData + + # Find the Julian Day Number corresponding to the base time + + set date2 [GetDateFields $baseTime $TZData($timeZone) $changeover] + dict set date julianDay [dict get $date2 julianDay] + + return $date +} + +#---------------------------------------------------------------------- +# +# InterpretHMSP -- +# +# Interprets a time in the form "hh:mm:ss am". +# +# Parameters: +# date -- Dictionary containing "hourAMPM", "minute", "second" +# and "amPmIndicator" fields. +# +# Results: +# Returns the number of seconds from local midnight. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::InterpretHMSP { date } { + set hr [dict get $date hourAMPM] + if { $hr == 12 } { + set hr 0 + } + if { [dict get $date amPmIndicator] } { + incr hr 12 + } + dict set date hour $hr + return [InterpretHMS $date[set date {}]] +} + +#---------------------------------------------------------------------- +# +# InterpretHMS -- +# +# Interprets a 24-hour time "hh:mm:ss" +# +# Parameters: +# date -- Dictionary containing the "hour", "minute" and "second" +# fields. +# +# Results: +# Returns the given dictionary augmented with a "secondOfDay" +# field containing the number of seconds from local midnight. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::InterpretHMS { date } { + return [expr { + ( [dict get $date hour] * 60 + + [dict get $date minute] ) * 60 + + [dict get $date second] + }] +} + +#---------------------------------------------------------------------- +# +# GetSystemTimeZone -- +# +# Determines the system time zone, which is the default for the +# 'clock' command if no other zone is supplied. +# +# Parameters: +# None. +# +# Results: +# Returns the system time zone. +# +# Side effects: +# Stores the sustem time zone in the 'CachedSystemTimeZone' +# variable, since determining it may be an expensive process. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::GetSystemTimeZone {} { + variable CachedSystemTimeZone + variable TimeZoneBad + + if {[set result [getenv TCL_TZ]] ne {}} { + set timezone $result + } elseif {[set result [getenv TZ]] ne {}} { + set timezone $result + } + if {![info exists timezone]} { + # Cache the time zone only if it was detected by one of the + # expensive methods. + if { [info exists CachedSystemTimeZone] } { + set timezone $CachedSystemTimeZone + } elseif { $::tcl_platform(platform) eq {windows} } { + set timezone [GuessWindowsTimeZone] + } elseif { [file exists /etc/localtime] + && ![catch {ReadZoneinfoFile \ + Tcl/Localtime /etc/localtime}] } { + set timezone :Tcl/Localtime + } else { + set timezone :localtime + } + set CachedSystemTimeZone $timezone + } + if { ![dict exists $TimeZoneBad $timezone] } { + dict set TimeZoneBad $timezone [catch {SetupTimeZone $timezone}] + } + if { [dict get $TimeZoneBad $timezone] } { + return :localtime + } else { + return $timezone + } +} + +#---------------------------------------------------------------------- +# +# ConvertLegacyTimeZone -- +# +# Given an alphanumeric time zone identifier and the system time zone, +# convert the alphanumeric identifier to an unambiguous time zone. +# +# Parameters: +# tzname - Name of the time zone to convert +# +# Results: +# Returns a time zone name corresponding to tzname, but in an +# unambiguous form, generally +hhmm. +# +# This procedure is implemented primarily to allow the parsing of RFC822 +# date/time strings. Processing a time zone name on input is not recommended +# practice, because there is considerable room for ambiguity; for instance, is +# BST Brazilian Standard Time, or British Summer Time? +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::ConvertLegacyTimeZone { tzname } { + variable LegacyTimeZone + + set tzname [string tolower $tzname] + if { ![dict exists $LegacyTimeZone $tzname] } { + return -code error -errorcode [list CLOCK badTZName $tzname] \ + "time zone \"$tzname\" not found" + } + return [dict get $LegacyTimeZone $tzname] +} + +#---------------------------------------------------------------------- +# +# SetupTimeZone -- +# +# Given the name or specification of a time zone, sets up its in-memory +# data. +# +# Parameters: +# tzname - Name of a time zone +# +# Results: +# Unless the time zone is ':localtime', sets the TZData array to contain +# the lookup table for local<->UTC conversion. Returns an error if the +# time zone cannot be parsed. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::SetupTimeZone { timezone } { + variable TZData + + if {! [info exists TZData($timezone)] } { + variable MINWIDE + if { $timezone eq {:localtime} } { + # Nothing to do, we'll convert using the localtime function + + } elseif { + [regexp {^([-+])(\d\d)(?::?(\d\d)(?::?(\d\d))?)?} $timezone \ + -> s hh mm ss] + } then { + # Make a fixed offset + + ::scan $hh %d hh + if { $mm eq {} } { + set mm 0 + } else { + ::scan $mm %d mm + } + if { $ss eq {} } { + set ss 0 + } else { + ::scan $ss %d ss + } + set offset [expr { ( $hh * 60 + $mm ) * 60 + $ss }] + if { $s eq {-} } { + set offset [expr { - $offset }] + } + set TZData($timezone) [list [list $MINWIDE $offset -1 $timezone]] + + } elseif { [string index $timezone 0] eq {:} } { + # Convert using a time zone file + + if { + [catch { + LoadTimeZoneFile [string range $timezone 1 end] + }] && [catch { + LoadZoneinfoFile [string range $timezone 1 end] + }] + } then { + return -code error \ + -errorcode [list CLOCK badTimeZone $timezone] \ + "time zone \"$timezone\" not found" + } + } elseif { ![catch {ParsePosixTimeZone $timezone} tzfields] } { + # This looks like a POSIX time zone - try to process it + + if { [catch {ProcessPosixTimeZone $tzfields} data opts] } { + if { [lindex [dict get $opts -errorcode] 0] eq {CLOCK} } { + dict unset opts -errorinfo + } + return -options $opts $data + } else { + set TZData($timezone) $data + } + + } else { + # We couldn't parse this as a POSIX time zone. Try again with a + # time zone file - this time without a colon + + if { [catch { LoadTimeZoneFile $timezone }] + && [catch { LoadZoneinfoFile $timezone } - opts] } { + dict unset opts -errorinfo + return -options $opts "time zone $timezone not found" + } + set TZData($timezone) $TZData(:$timezone) + } + } + + return +} + +#---------------------------------------------------------------------- +# +# GuessWindowsTimeZone -- +# +# Determines the system time zone on windows. +# +# Parameters: +# None. +# +# Results: +# Returns a time zone specifier that corresponds to the system time zone +# information found in the Registry. +# +# Bugs: +# Fixed dates for DST change are unimplemented at present, because no +# time zone information supplied with Windows actually uses them! +# +# On a Windows system where neither $env(TCL_TZ) nor $env(TZ) is specified, +# GuessWindowsTimeZone looks in the Registry for the system time zone +# information. It then attempts to find an entry in WinZoneInfo for a time +# zone that uses the same rules. If it finds one, it returns it; otherwise, +# it constructs a Posix-style time zone string and returns that. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::GuessWindowsTimeZone {} { + variable WinZoneInfo + variable NoRegistry + variable TimeZoneBad + + if { [info exists NoRegistry] } { + return :localtime + } + + # Dredge time zone information out of the registry + + if { [catch { + set rpath HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\TimeZoneInformation + set data [list \ + [expr { -60 + * [registry get $rpath Bias] }] \ + [expr { -60 + * [registry get $rpath StandardBias] }] \ + [expr { -60 \ + * [registry get $rpath DaylightBias] }]] + set stdtzi [registry get $rpath StandardStart] + foreach ind {0 2 14 4 6 8 10 12} { + binary scan $stdtzi @${ind}s val + lappend data $val + } + set daytzi [registry get $rpath DaylightStart] + foreach ind {0 2 14 4 6 8 10 12} { + binary scan $daytzi @${ind}s val + lappend data $val + } + }] } { + # Missing values in the Registry - bail out + + return :localtime + } + + # Make up a Posix time zone specifier if we can't find one. Check here + # that the tzdata file exists, in case we're running in an environment + # (e.g. starpack) where tzdata is incomplete. (Bug 1237907) + + if { [dict exists $WinZoneInfo $data] } { + set tzname [dict get $WinZoneInfo $data] + if { ! [dict exists $TimeZoneBad $tzname] } { + dict set TimeZoneBad $tzname [catch {SetupTimeZone $tzname}] + } + } else { + set tzname {} + } + if { $tzname eq {} || [dict get $TimeZoneBad $tzname] } { + lassign $data \ + bias stdBias dstBias \ + stdYear stdMonth stdDayOfWeek stdDayOfMonth \ + stdHour stdMinute stdSecond stdMillisec \ + dstYear dstMonth dstDayOfWeek dstDayOfMonth \ + dstHour dstMinute dstSecond dstMillisec + set stdDelta [expr { $bias + $stdBias }] + set dstDelta [expr { $bias + $dstBias }] + if { $stdDelta <= 0 } { + set stdSignum + + set stdDelta [expr { - $stdDelta }] + set dispStdSignum - + } else { + set stdSignum - + set dispStdSignum + + } + set hh [::format %02d [expr { $stdDelta / 3600 }]] + set mm [::format %02d [expr { ($stdDelta / 60 ) % 60 }]] + set ss [::format %02d [expr { $stdDelta % 60 }]] + set tzname {} + append tzname < $dispStdSignum $hh $mm > $stdSignum $hh : $mm : $ss + if { $stdMonth >= 0 } { + if { $dstDelta <= 0 } { + set dstSignum + + set dstDelta [expr { - $dstDelta }] + set dispDstSignum - + } else { + set dstSignum - + set dispDstSignum + + } + set hh [::format %02d [expr { $dstDelta / 3600 }]] + set mm [::format %02d [expr { ($dstDelta / 60 ) % 60 }]] + set ss [::format %02d [expr { $dstDelta % 60 }]] + append tzname < $dispDstSignum $hh $mm > $dstSignum $hh : $mm : $ss + if { $dstYear == 0 } { + append tzname ,M $dstMonth . $dstDayOfMonth . $dstDayOfWeek + } else { + # I have not been able to find any locale on which Windows + # converts time zone on a fixed day of the year, hence don't + # know how to interpret the fields. If someone can inform me, + # I'd be glad to code it up. For right now, we bail out in + # such a case. + return :localtime + } + append tzname / [::format %02d $dstHour] \ + : [::format %02d $dstMinute] \ + : [::format %02d $dstSecond] + if { $stdYear == 0 } { + append tzname ,M $stdMonth . $stdDayOfMonth . $stdDayOfWeek + } else { + # I have not been able to find any locale on which Windows + # converts time zone on a fixed day of the year, hence don't + # know how to interpret the fields. If someone can inform me, + # I'd be glad to code it up. For right now, we bail out in + # such a case. + return :localtime + } + append tzname / [::format %02d $stdHour] \ + : [::format %02d $stdMinute] \ + : [::format %02d $stdSecond] + } + dict set WinZoneInfo $data $tzname + } + + return [dict get $WinZoneInfo $data] +} + +#---------------------------------------------------------------------- +# +# LoadTimeZoneFile -- +# +# Load the data file that specifies the conversion between a +# given time zone and Greenwich. +# +# Parameters: +# fileName -- Name of the file to load +# +# Results: +# None. +# +# Side effects: +# TZData(:fileName) contains the time zone data +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::LoadTimeZoneFile { fileName } { + variable DataDir + variable TZData + + if { [info exists TZData($fileName)] } { + return + } + + # Since an unsafe interp uses the [clock] command in the master, this code + # is security sensitive. Make sure that the path name cannot escape the + # given directory. + + if { ![regexp {^[[.-.][:alpha:]_]+(?:/[[.-.][:alpha:]_]+)*$} $fileName] } { + return -code error \ + -errorcode [list CLOCK badTimeZone $:fileName] \ + "time zone \":$fileName\" not valid" + } + try { + source -encoding utf-8 [file join $DataDir $fileName] + } on error {} { + return -code error \ + -errorcode [list CLOCK badTimeZone :$fileName] \ + "time zone \":$fileName\" not found" + } + return +} + +#---------------------------------------------------------------------- +# +# LoadZoneinfoFile -- +# +# Loads a binary time zone information file in Olson format. +# +# Parameters: +# fileName - Relative path name of the file to load. +# +# Results: +# Returns an empty result normally; returns an error if no Olson file +# was found or the file was malformed in some way. +# +# Side effects: +# TZData(:fileName) contains the time zone data +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::LoadZoneinfoFile { fileName } { + variable ZoneinfoPaths + + # Since an unsafe interp uses the [clock] command in the master, this code + # is security sensitive. Make sure that the path name cannot escape the + # given directory. + + if { ![regexp {^[[.-.][:alpha:]_]+(?:/[[.-.][:alpha:]_]+)*$} $fileName] } { + return -code error \ + -errorcode [list CLOCK badTimeZone $:fileName] \ + "time zone \":$fileName\" not valid" + } + foreach d $ZoneinfoPaths { + set fname [file join $d $fileName] + if { [file readable $fname] && [file isfile $fname] } { + break + } + unset fname + } + ReadZoneinfoFile $fileName $fname +} + +#---------------------------------------------------------------------- +# +# ReadZoneinfoFile -- +# +# Loads a binary time zone information file in Olson format. +# +# Parameters: +# fileName - Name of the time zone (relative path name of the +# file). +# fname - Absolute path name of the file. +# +# Results: +# Returns an empty result normally; returns an error if no Olson file +# was found or the file was malformed in some way. +# +# Side effects: +# TZData(:fileName) contains the time zone data +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::ReadZoneinfoFile {fileName fname} { + variable MINWIDE + variable TZData + if { ![file exists $fname] } { + return -code error "$fileName not found" + } + + if { [file size $fname] > 262144 } { + return -code error "$fileName too big" + } + + # Suck in all the data from the file + + set f [open $fname r] + fconfigure $f -translation binary + set d [read $f] + close $f + + # The file begins with a magic number, sixteen reserved bytes, and then + # six 4-byte integers giving counts of fileds in the file. + + binary scan $d a4a1x15IIIIII \ + magic version nIsGMT nIsStd nLeap nTime nType nChar + set seek 44 + set ilen 4 + set iformat I + if { $magic != {TZif} } { + return -code error "$fileName not a time zone information file" + } + if { $nType > 255 } { + return -code error "$fileName contains too many time types" + } + # Accept only Posix-style zoneinfo. Sorry, 'leaps' bigots. + if { $nLeap != 0 } { + return -code error "$fileName contains leap seconds" + } + + # In a version 2 file, we use the second part of the file, which contains + # 64-bit transition times. + + if {$version eq "2"} { + set seek [expr { + 44 + + 5 * $nTime + + 6 * $nType + + 4 * $nLeap + + $nIsStd + + $nIsGMT + + $nChar + }] + binary scan $d @${seek}a4a1x15IIIIII \ + magic version nIsGMT nIsStd nLeap nTime nType nChar + if {$magic ne {TZif}} { + return -code error "seek address $seek miscomputed, magic = $magic" + } + set iformat W + set ilen 8 + incr seek 44 + } + + # Next come ${nTime} transition times, followed by ${nTime} time type + # codes. The type codes are unsigned 1-byte quantities. We insert an + # arbitrary start time in front of the transitions. + + binary scan $d @${seek}${iformat}${nTime}c${nTime} times tempCodes + incr seek [expr { ($ilen + 1) * $nTime }] + set times [linsert $times 0 $MINWIDE] + set codes {} + foreach c $tempCodes { + lappend codes [expr { $c & 0xff }] + } + set codes [linsert $codes 0 0] + + # Next come ${nType} time type descriptions, each of which has an offset + # (seconds east of GMT), a DST indicator, and an index into the + # abbreviation text. + + for { set i 0 } { $i < $nType } { incr i } { + binary scan $d @${seek}Icc gmtOff isDst abbrInd + lappend types [list $gmtOff $isDst $abbrInd] + incr seek 6 + } + + # Next come $nChar characters of time zone name abbreviations, which are + # null-terminated. + # We build them up into a dictionary indexed by character index, because + # that's what's in the indices above. + + binary scan $d @${seek}a${nChar} abbrs + incr seek ${nChar} + set abbrList [split $abbrs \0] + set i 0 + set abbrevs {} + foreach a $abbrList { + for {set j 0} {$j <= [string length $a]} {incr j} { + dict set abbrevs $i [string range $a $j end] + incr i + } + } + + # Package up a list of tuples, each of which contains transition time, + # seconds east of Greenwich, DST flag and time zone abbreviation. + + set r {} + set lastTime $MINWIDE + foreach t $times c $codes { + if { $t < $lastTime } { + return -code error "$fileName has times out of order" + } + set lastTime $t + lassign [lindex $types $c] gmtoff isDst abbrInd + set abbrev [dict get $abbrevs $abbrInd] + lappend r [list $t $gmtoff $isDst $abbrev] + } + + # In a version 2 file, there is also a POSIX-style time zone description + # at the very end of the file. To get to it, skip over nLeap leap second + # values (8 bytes each), + # nIsStd standard/DST indicators and nIsGMT UTC/local indicators. + + if {$version eq {2}} { + set seek [expr {$seek + 8 * $nLeap + $nIsStd + $nIsGMT + 1}] + set last [string first \n $d $seek] + set posix [string range $d $seek [expr {$last-1}]] + if {[llength $posix] > 0} { + set posixFields [ParsePosixTimeZone $posix] + foreach tuple [ProcessPosixTimeZone $posixFields] { + lassign $tuple t gmtoff isDst abbrev + if {$t > $lastTime} { + lappend r $tuple + } + } + } + } + + set TZData(:$fileName) $r + + return +} + +#---------------------------------------------------------------------- +# +# ParsePosixTimeZone -- +# +# Parses the TZ environment variable in Posix form +# +# Parameters: +# tz Time zone specifier to be interpreted +# +# Results: +# Returns a dictionary whose values contain the various pieces of the +# time zone specification. +# +# Side effects: +# None. +# +# Errors: +# Throws an error if the syntax of the time zone is incorrect. +# +# The following keys are present in the dictionary: +# stdName - Name of the time zone when Daylight Saving Time +# is not in effect. +# stdSignum - Sign (+, -, or empty) of the offset from Greenwich +# to the given (non-DST) time zone. + and the empty +# string denote zones west of Greenwich, - denotes east +# of Greenwich; this is contrary to the ISO convention +# but follows Posix. +# stdHours - Hours part of the offset from Greenwich to the given +# (non-DST) time zone. +# stdMinutes - Minutes part of the offset from Greenwich to the +# given (non-DST) time zone. Empty denotes zero. +# stdSeconds - Seconds part of the offset from Greenwich to the +# given (non-DST) time zone. Empty denotes zero. +# dstName - Name of the time zone when DST is in effect, or the +# empty string if the time zone does not observe Daylight +# Saving Time. +# dstSignum, dstHours, dstMinutes, dstSeconds - +# Fields corresponding to stdSignum, stdHours, stdMinutes, +# stdSeconds for the Daylight Saving Time version of the +# time zone. If dstHours is empty, it is presumed to be 1. +# startDayOfYear - The ordinal number of the day of the year on which +# Daylight Saving Time begins. If this field is +# empty, then DST begins on a given month-week-day, +# as below. +# startJ - The letter J, or an empty string. If a J is present in +# this field, then startDayOfYear does not count February 29 +# even in leap years. +# startMonth - The number of the month in which Daylight Saving Time +# begins, supplied if startDayOfYear is empty. If both +# startDayOfYear and startMonth are empty, then US rules +# are presumed. +# startWeekOfMonth - The number of the week in the month in which +# Daylight Saving Time begins, in the range 1-5. +# 5 denotes the last week of the month even in a +# 4-week month. +# startDayOfWeek - The number of the day of the week (Sunday=0, +# Saturday=6) on which Daylight Saving Time begins. +# startHours - The hours part of the time of day at which Daylight +# Saving Time begins. An empty string is presumed to be 2. +# startMinutes - The minutes part of the time of day at which DST begins. +# An empty string is presumed zero. +# startSeconds - The seconds part of the time of day at which DST begins. +# An empty string is presumed zero. +# endDayOfYear, endJ, endMonth, endWeekOfMonth, endDayOfWeek, +# endHours, endMinutes, endSeconds - +# Specify the end of DST in the same way that the start* fields +# specify the beginning of DST. +# +# This procedure serves only to break the time specifier into fields. No +# attempt is made to canonicalize the fields or supply default values. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::ParsePosixTimeZone { tz } { + if {[regexp -expanded -nocase -- { + ^ + # 1 - Standard time zone name + ([[:alpha:]]+ | <[-+[:alnum:]]+>) + # 2 - Standard time zone offset, signum + ([-+]?) + # 3 - Standard time zone offset, hours + ([[:digit:]]{1,2}) + (?: + # 4 - Standard time zone offset, minutes + : ([[:digit:]]{1,2}) + (?: + # 5 - Standard time zone offset, seconds + : ([[:digit:]]{1,2} ) + )? + )? + (?: + # 6 - DST time zone name + ([[:alpha:]]+ | <[-+[:alnum:]]+>) + (?: + (?: + # 7 - DST time zone offset, signum + ([-+]?) + # 8 - DST time zone offset, hours + ([[:digit:]]{1,2}) + (?: + # 9 - DST time zone offset, minutes + : ([[:digit:]]{1,2}) + (?: + # 10 - DST time zone offset, seconds + : ([[:digit:]]{1,2}) + )? + )? + )? + (?: + , + (?: + # 11 - Optional J in n and Jn form 12 - Day of year + ( J ? ) ( [[:digit:]]+ ) + | M + # 13 - Month number 14 - Week of month 15 - Day of week + ( [[:digit:]] + ) + [.] ( [[:digit:]] + ) + [.] ( [[:digit:]] + ) + ) + (?: + # 16 - Start time of DST - hours + / ( [[:digit:]]{1,2} ) + (?: + # 17 - Start time of DST - minutes + : ( [[:digit:]]{1,2} ) + (?: + # 18 - Start time of DST - seconds + : ( [[:digit:]]{1,2} ) + )? + )? + )? + , + (?: + # 19 - Optional J in n and Jn form 20 - Day of year + ( J ? ) ( [[:digit:]]+ ) + | M + # 21 - Month number 22 - Week of month 23 - Day of week + ( [[:digit:]] + ) + [.] ( [[:digit:]] + ) + [.] ( [[:digit:]] + ) + ) + (?: + # 24 - End time of DST - hours + / ( [[:digit:]]{1,2} ) + (?: + # 25 - End time of DST - minutes + : ( [[:digit:]]{1,2} ) + (?: + # 26 - End time of DST - seconds + : ( [[:digit:]]{1,2} ) + )? + )? + )? + )? + )? + )? + $ + } $tz -> x(stdName) x(stdSignum) x(stdHours) x(stdMinutes) x(stdSeconds) \ + x(dstName) x(dstSignum) x(dstHours) x(dstMinutes) x(dstSeconds) \ + x(startJ) x(startDayOfYear) \ + x(startMonth) x(startWeekOfMonth) x(startDayOfWeek) \ + x(startHours) x(startMinutes) x(startSeconds) \ + x(endJ) x(endDayOfYear) \ + x(endMonth) x(endWeekOfMonth) x(endDayOfWeek) \ + x(endHours) x(endMinutes) x(endSeconds)] } { + # it's a good timezone + + return [array get x] + } + + return -code error\ + -errorcode [list CLOCK badTimeZone $tz] \ + "unable to parse time zone specification \"$tz\"" +} + +#---------------------------------------------------------------------- +# +# ProcessPosixTimeZone -- +# +# Handle a Posix time zone after it's been broken out into fields. +# +# Parameters: +# z - Dictionary returned from 'ParsePosixTimeZone' +# +# Results: +# Returns time zone information for the 'TZData' array. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::ProcessPosixTimeZone { z } { + variable MINWIDE + variable TZData + + # Determine the standard time zone name and seconds east of Greenwich + + set stdName [dict get $z stdName] + if { [string index $stdName 0] eq {<} } { + set stdName [string range $stdName 1 end-1] + } + if { [dict get $z stdSignum] eq {-} } { + set stdSignum +1 + } else { + set stdSignum -1 + } + set stdHours [lindex [::scan [dict get $z stdHours] %d] 0] + if { [dict get $z stdMinutes] ne {} } { + set stdMinutes [lindex [::scan [dict get $z stdMinutes] %d] 0] + } else { + set stdMinutes 0 + } + if { [dict get $z stdSeconds] ne {} } { + set stdSeconds [lindex [::scan [dict get $z stdSeconds] %d] 0] + } else { + set stdSeconds 0 + } + set stdOffset [expr { + (($stdHours * 60 + $stdMinutes) * 60 + $stdSeconds) * $stdSignum + }] + set data [list [list $MINWIDE $stdOffset 0 $stdName]] + + # If there's no daylight zone, we're done + + set dstName [dict get $z dstName] + if { $dstName eq {} } { + return $data + } + if { [string index $dstName 0] eq {<} } { + set dstName [string range $dstName 1 end-1] + } + + # Determine the daylight name + + if { [dict get $z dstSignum] eq {-} } { + set dstSignum +1 + } else { + set dstSignum -1 + } + if { [dict get $z dstHours] eq {} } { + set dstOffset [expr { 3600 + $stdOffset }] + } else { + set dstHours [lindex [::scan [dict get $z dstHours] %d] 0] + if { [dict get $z dstMinutes] ne {} } { + set dstMinutes [lindex [::scan [dict get $z dstMinutes] %d] 0] + } else { + set dstMinutes 0 + } + if { [dict get $z dstSeconds] ne {} } { + set dstSeconds [lindex [::scan [dict get $z dstSeconds] %d] 0] + } else { + set dstSeconds 0 + } + set dstOffset [expr { + (($dstHours*60 + $dstMinutes) * 60 + $dstSeconds) * $dstSignum + }] + } + + # Fill in defaults for European or US DST rules + # US start time is the second Sunday in March + # EU start time is the last Sunday in March + # US end time is the first Sunday in November. + # EU end time is the last Sunday in October + + if { + [dict get $z startDayOfYear] eq {} + && [dict get $z startMonth] eq {} + } then { + if {($stdSignum * $stdHours>=0) && ($stdSignum * $stdHours<=12)} { + # EU + dict set z startWeekOfMonth 5 + if {$stdHours>2} { + dict set z startHours 2 + } else { + dict set z startHours [expr {$stdHours+1}] + } + } else { + # US + dict set z startWeekOfMonth 2 + dict set z startHours 2 + } + dict set z startMonth 3 + dict set z startDayOfWeek 0 + dict set z startMinutes 0 + dict set z startSeconds 0 + } + if { + [dict get $z endDayOfYear] eq {} + && [dict get $z endMonth] eq {} + } then { + if {($stdSignum * $stdHours>=0) && ($stdSignum * $stdHours<=12)} { + # EU + dict set z endMonth 10 + dict set z endWeekOfMonth 5 + if {$stdHours>2} { + dict set z endHours 3 + } else { + dict set z endHours [expr {$stdHours+2}] + } + } else { + # US + dict set z endMonth 11 + dict set z endWeekOfMonth 1 + dict set z endHours 2 + } + dict set z endDayOfWeek 0 + dict set z endMinutes 0 + dict set z endSeconds 0 + } + + # Put DST in effect in all years from 1916 to 2099. + + for { set y 1916 } { $y < 2100 } { incr y } { + set startTime [DeterminePosixDSTTime $z start $y] + incr startTime [expr { - wide($stdOffset) }] + set endTime [DeterminePosixDSTTime $z end $y] + incr endTime [expr { - wide($dstOffset) }] + if { $startTime < $endTime } { + lappend data \ + [list $startTime $dstOffset 1 $dstName] \ + [list $endTime $stdOffset 0 $stdName] + } else { + lappend data \ + [list $endTime $stdOffset 0 $stdName] \ + [list $startTime $dstOffset 1 $dstName] + } + } + + return $data +} + +#---------------------------------------------------------------------- +# +# DeterminePosixDSTTime -- +# +# Determines the time that Daylight Saving Time starts or ends from a +# Posix time zone specification. +# +# Parameters: +# z - Time zone data returned from ParsePosixTimeZone. +# Missing fields are expected to be filled in with +# default values. +# bound - The word 'start' or 'end' +# y - The year for which the transition time is to be determined. +# +# Results: +# Returns the transition time as a count of seconds from the epoch. The +# time is relative to the wall clock, not UTC. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::DeterminePosixDSTTime { z bound y } { + + variable FEB_28 + + # Determine the start or end day of DST + + set date [dict create era CE year $y] + set doy [dict get $z ${bound}DayOfYear] + if { $doy ne {} } { + + # Time was specified as a day of the year + + if { [dict get $z ${bound}J] ne {} + && [IsGregorianLeapYear $y] + && ( $doy > $FEB_28 ) } { + incr doy + } + dict set date dayOfYear $doy + set date [GetJulianDayFromEraYearDay $date[set date {}] 2361222] + } else { + # Time was specified as a day of the week within a month + + dict set date month [dict get $z ${bound}Month] + dict set date dayOfWeek [dict get $z ${bound}DayOfWeek] + set dowim [dict get $z ${bound}WeekOfMonth] + if { $dowim >= 5 } { + set dowim -1 + } + dict set date dayOfWeekInMonth $dowim + set date [GetJulianDayFromEraYearMonthWeekDay $date[set date {}] 2361222] + + } + + set jd [dict get $date julianDay] + set seconds [expr { + wide($jd) * wide(86400) - wide(210866803200) + }] + + set h [dict get $z ${bound}Hours] + if { $h eq {} } { + set h 2 + } else { + set h [lindex [::scan $h %d] 0] + } + set m [dict get $z ${bound}Minutes] + if { $m eq {} } { + set m 0 + } else { + set m [lindex [::scan $m %d] 0] + } + set s [dict get $z ${bound}Seconds] + if { $s eq {} } { + set s 0 + } else { + set s [lindex [::scan $s %d] 0] + } + set tod [expr { ( $h * 60 + $m ) * 60 + $s }] + return [expr { $seconds + $tod }] +} + +#---------------------------------------------------------------------- +# +# GetLocaleEra -- +# +# Given local time expressed in seconds from the Posix epoch, +# determine localized era and year within the era. +# +# Parameters: +# date - Dictionary that must contain the keys, 'localSeconds', +# whose value is expressed as the appropriate local time; +# and 'year', whose value is the Gregorian year. +# etable - Value of the LOCALE_ERAS key in the message catalogue +# for the target locale. +# +# Results: +# Returns the dictionary, augmented with the keys, 'localeEra' and +# 'localeYear'. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::GetLocaleEra { date etable } { + set index [BSearch $etable [dict get $date localSeconds]] + if { $index < 0} { + dict set date localeEra \ + [::format %02d [expr { [dict get $date year] / 100 }]] + dict set date localeYear [expr { + [dict get $date year] % 100 + }] + } else { + dict set date localeEra [lindex $etable $index 1] + dict set date localeYear [expr { + [dict get $date year] - [lindex $etable $index 2] + }] + } + return $date +} + +#---------------------------------------------------------------------- +# +# GetJulianDayFromEraYearDay -- +# +# Given a year, month and day on the Gregorian calendar, determines +# the Julian Day Number beginning at noon on that date. +# +# Parameters: +# date -- A dictionary in which the 'era', 'year', and +# 'dayOfYear' slots are populated. The calendar in use +# is determined by the date itself relative to: +# changeover -- Julian day on which the Gregorian calendar was +# adopted in the current locale. +# +# Results: +# Returns the given dictionary augmented with a 'julianDay' key whose +# value is the desired Julian Day Number, and a 'gregorian' key that +# specifies whether the calendar is Gregorian (1) or Julian (0). +# +# Side effects: +# None. +# +# Bugs: +# This code needs to be moved to the C layer. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::GetJulianDayFromEraYearDay {date changeover} { + # Get absolute year number from the civil year + + switch -exact -- [dict get $date era] { + BCE { + set year [expr { 1 - [dict get $date year] }] + } + CE { + set year [dict get $date year] + } + } + set ym1 [expr { $year - 1 }] + + # Try the Gregorian calendar first. + + dict set date gregorian 1 + set jd [expr { + 1721425 + + [dict get $date dayOfYear] + + ( 365 * $ym1 ) + + ( $ym1 / 4 ) + - ( $ym1 / 100 ) + + ( $ym1 / 400 ) + }] + + # If the date is before the Gregorian change, use the Julian calendar. + + if { $jd < $changeover } { + dict set date gregorian 0 + set jd [expr { + 1721423 + + [dict get $date dayOfYear] + + ( 365 * $ym1 ) + + ( $ym1 / 4 ) + }] + } + + dict set date julianDay $jd + return $date +} + +#---------------------------------------------------------------------- +# +# GetJulianDayFromEraYearMonthWeekDay -- +# +# Determines the Julian Day number corresponding to the nth given +# day-of-the-week in a given month. +# +# Parameters: +# date - Dictionary containing the keys, 'era', 'year', 'month' +# 'weekOfMonth', 'dayOfWeek', and 'dayOfWeekInMonth'. +# changeover - Julian Day of adoption of the Gregorian calendar +# +# Results: +# Returns the given dictionary, augmented with a 'julianDay' key. +# +# Side effects: +# None. +# +# Bugs: +# This code needs to be moved to the C layer. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::GetJulianDayFromEraYearMonthWeekDay {date changeover} { + # Come up with a reference day; either the zeroeth day of the given month + # (dayOfWeekInMonth >= 0) or the seventh day of the following month + # (dayOfWeekInMonth < 0) + + set date2 $date + set week [dict get $date dayOfWeekInMonth] + if { $week >= 0 } { + dict set date2 dayOfMonth 0 + } else { + dict incr date2 month + dict set date2 dayOfMonth 7 + } + set date2 [GetJulianDayFromEraYearMonthDay $date2[set date2 {}] \ + $changeover] + set wd0 [WeekdayOnOrBefore [dict get $date dayOfWeek] \ + [dict get $date2 julianDay]] + dict set date julianDay [expr { $wd0 + 7 * $week }] + return $date +} + +#---------------------------------------------------------------------- +# +# IsGregorianLeapYear -- +# +# Determines whether a given date represents a leap year in the +# Gregorian calendar. +# +# Parameters: +# date -- The date to test. The fields, 'era', 'year' and 'gregorian' +# must be set. +# +# Results: +# Returns 1 if the year is a leap year, 0 otherwise. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::IsGregorianLeapYear { date } { + switch -exact -- [dict get $date era] { + BCE { + set year [expr { 1 - [dict get $date year]}] + } + CE { + set year [dict get $date year] + } + } + if { $year % 4 != 0 } { + return 0 + } elseif { ![dict get $date gregorian] } { + return 1 + } elseif { $year % 400 == 0 } { + return 1 + } elseif { $year % 100 == 0 } { + return 0 + } else { + return 1 + } +} + +#---------------------------------------------------------------------- +# +# WeekdayOnOrBefore -- +# +# Determine the nearest day of week (given by the 'weekday' parameter, +# Sunday==0) on or before a given Julian Day. +# +# Parameters: +# weekday -- Day of the week +# j -- Julian Day number +# +# Results: +# Returns the Julian Day Number of the desired date. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::WeekdayOnOrBefore { weekday j } { + set k [expr { ( $weekday + 6 ) % 7 }] + return [expr { $j - ( $j - $k ) % 7 }] +} + +#---------------------------------------------------------------------- +# +# BSearch -- +# +# Service procedure that does binary search in several places inside the +# 'clock' command. +# +# Parameters: +# list - List of lists, sorted in ascending order by the +# first elements +# key - Value to search for +# +# Results: +# Returns the index of the greatest element in $list that is less than +# or equal to $key. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::BSearch { list key } { + if {[llength $list] == 0} { + return -1 + } + if { $key < [lindex $list 0 0] } { + return -1 + } + + set l 0 + set u [expr { [llength $list] - 1 }] + + while { $l < $u } { + # At this point, we know that + # $k >= [lindex $list $l 0] + # Either $u == [llength $list] or else $k < [lindex $list $u+1 0] + # We find the midpoint of the interval {l,u} rounded UP, compare + # against it, and set l or u to maintain the invariant. Note that the + # interval shrinks at each step, guaranteeing convergence. + + set m [expr { ( $l + $u + 1 ) / 2 }] + if { $key >= [lindex $list $m 0] } { + set l $m + } else { + set u [expr { $m - 1 }] + } + } + + return $l +} + +#---------------------------------------------------------------------- +# +# clock add -- +# +# Adds an offset to a given time. +# +# Syntax: +# clock add clockval ?count unit?... ?-option value? +# +# Parameters: +# clockval -- Starting time value +# count -- Amount of a unit of time to add +# unit -- Unit of time to add, must be one of: +# years year months month weeks week +# days day hours hour minutes minute +# seconds second +# +# Options: +# -gmt BOOLEAN +# (Deprecated) Flag synonymous with '-timezone :GMT' +# -timezone ZONE +# Name of the time zone in which calculations are to be done. +# -locale NAME +# Name of the locale in which calculations are to be done. +# Used to determine the Gregorian change date. +# +# Results: +# Returns the given time adjusted by the given offset(s) in +# order. +# +# Notes: +# It is possible that adding a number of months or years will adjust the +# day of the month as well. For instance, the time at one month after +# 31 January is either 28 or 29 February, because February has fewer +# than 31 days. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::add { clockval args } { + if { [llength $args] % 2 != 0 } { + set cmdName "clock add" + return -code error \ + -errorcode [list CLOCK wrongNumArgs] \ + "wrong \# args: should be\ + \"$cmdName clockval ?number units?...\ + ?-gmt boolean? ?-locale LOCALE? ?-timezone ZONE?\"" + } + if { [catch { expr {wide($clockval)} } result] } { + return -code error $result + } + + set offsets {} + set gmt 0 + set locale c + set timezone [GetSystemTimeZone] + + foreach { a b } $args { + if { [string is integer -strict $a] } { + lappend offsets $a $b + } else { + switch -exact -- $a { + -g - -gm - -gmt { + set gmt $b + } + -l - -lo - -loc - -loca - -local - -locale { + set locale [string tolower $b] + } + -t - -ti - -tim - -time - -timez - -timezo - -timezon - + -timezone { + set timezone $b + } + default { + throw [list CLOCK badOption $a] \ + "bad option \"$a\",\ + must be -gmt, -locale or -timezone" + } + } + } + } + + # Check options for validity + + if { [info exists saw(-gmt)] && [info exists saw(-timezone)] } { + return -code error \ + -errorcode [list CLOCK gmtWithTimezone] \ + "cannot use -gmt and -timezone in same call" + } + if { [catch { expr { wide($clockval) } } result] } { + return -code error "expected integer but got \"$clockval\"" + } + if { ![string is boolean -strict $gmt] } { + return -code error "expected boolean value but got \"$gmt\"" + } elseif { $gmt } { + set timezone :GMT + } + + EnterLocale $locale + + set changeover [mc GREGORIAN_CHANGE_DATE] + + if {[catch {SetupTimeZone $timezone} retval opts]} { + dict unset opts -errorinfo + return -options $opts $retval + } + + try { + foreach { quantity unit } $offsets { + switch -exact -- $unit { + years - year { + set clockval [AddMonths [expr { 12 * $quantity }] \ + $clockval $timezone $changeover] + } + months - month { + set clockval [AddMonths $quantity $clockval $timezone \ + $changeover] + } + + weeks - week { + set clockval [AddDays [expr { 7 * $quantity }] \ + $clockval $timezone $changeover] + } + days - day { + set clockval [AddDays $quantity $clockval $timezone \ + $changeover] + } + + hours - hour { + set clockval [expr { 3600 * $quantity + $clockval }] + } + minutes - minute { + set clockval [expr { 60 * $quantity + $clockval }] + } + seconds - second { + set clockval [expr { $quantity + $clockval }] + } + + default { + throw [list CLOCK badUnit $unit] \ + "unknown unit \"$unit\", must be \ + years, months, weeks, days, hours, minutes or seconds" + } + } + } + return $clockval + } trap CLOCK {result opts} { + # Conceal the innards of [clock] when it's an expected error + dict unset opts -errorinfo + return -options $opts $result + } +} + +#---------------------------------------------------------------------- +# +# AddMonths -- +# +# Add a given number of months to a given clock value in a given +# time zone. +# +# Parameters: +# months - Number of months to add (may be negative) +# clockval - Seconds since the epoch before the operation +# timezone - Time zone in which the operation is to be performed +# +# Results: +# Returns the new clock value as a number of seconds since +# the epoch. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::AddMonths { months clockval timezone changeover } { + variable DaysInRomanMonthInCommonYear + variable DaysInRomanMonthInLeapYear + variable TZData + + # Convert the time to year, month, day, and fraction of day. + + set date [GetDateFields $clockval $TZData($timezone) $changeover] + dict set date secondOfDay [expr { + [dict get $date localSeconds] % 86400 + }] + dict set date tzName $timezone + + # Add the requisite number of months + + set m [dict get $date month] + incr m $months + incr m -1 + set delta [expr { $m / 12 }] + set mm [expr { $m % 12 }] + dict set date month [expr { $mm + 1 }] + dict incr date year $delta + + # If the date doesn't exist in the current month, repair it + + if { [IsGregorianLeapYear $date] } { + set hath [lindex $DaysInRomanMonthInLeapYear $mm] + } else { + set hath [lindex $DaysInRomanMonthInCommonYear $mm] + } + if { [dict get $date dayOfMonth] > $hath } { + dict set date dayOfMonth $hath + } + + # Reconvert to a number of seconds + + set date [GetJulianDayFromEraYearMonthDay \ + $date[set date {}]\ + $changeover] + dict set date localSeconds [expr { + -210866803200 + + ( 86400 * wide([dict get $date julianDay]) ) + + [dict get $date secondOfDay] + }] + set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) \ + $changeover] + + return [dict get $date seconds] + +} + +#---------------------------------------------------------------------- +# +# AddDays -- +# +# Add a given number of days to a given clock value in a given time +# zone. +# +# Parameters: +# days - Number of days to add (may be negative) +# clockval - Seconds since the epoch before the operation +# timezone - Time zone in which the operation is to be performed +# changeover - Julian Day on which the Gregorian calendar was adopted +# in the target locale. +# +# Results: +# Returns the new clock value as a number of seconds since the epoch. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::AddDays { days clockval timezone changeover } { + variable TZData + + # Convert the time to Julian Day + + set date [GetDateFields $clockval $TZData($timezone) $changeover] + dict set date secondOfDay [expr { + [dict get $date localSeconds] % 86400 + }] + dict set date tzName $timezone + + # Add the requisite number of days + + dict incr date julianDay $days + + # Reconvert to a number of seconds + + dict set date localSeconds [expr { + -210866803200 + + ( 86400 * wide([dict get $date julianDay]) ) + + [dict get $date secondOfDay] + }] + set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) \ + $changeover] + + return [dict get $date seconds] + +} + +#---------------------------------------------------------------------- +# +# ChangeCurrentLocale -- +# +# The global locale was changed within msgcat. +# Clears the buffered parse functions of the current locale. +# +# Parameters: +# loclist (ignored) +# +# Results: +# None. +# +# Side effects: +# Buffered parse functions are cleared. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::ChangeCurrentLocale {args} { + variable FormatProc + variable LocaleNumeralCache + variable CachedSystemTimeZone + variable TimeZoneBad + + foreach p [info procs [namespace current]::scanproc'*'current] { + rename $p {} + } + foreach p [info procs [namespace current]::formatproc'*'current] { + rename $p {} + } + + catch {array unset FormatProc *'current} + set LocaleNumeralCache {} +} + +#---------------------------------------------------------------------- +# +# ClearCaches -- +# +# Clears all caches to reclaim the memory used in [clock] +# +# Parameters: +# None. +# +# Results: +# None. +# +# Side effects: +# Caches are cleared. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::ClearCaches {} { + variable FormatProc + variable LocaleNumeralCache + variable CachedSystemTimeZone + variable TimeZoneBad + + foreach p [info procs [namespace current]::scanproc'*] { + rename $p {} + } + foreach p [info procs [namespace current]::formatproc'*] { + rename $p {} + } + + catch {unset FormatProc} + set LocaleNumeralCache {} + catch {unset CachedSystemTimeZone} + set TimeZoneBad {} + InitTZData +} diff --git a/windowsAgent/dist/tcl/history.tcl b/windowsAgent/dist/tcl/history.tcl new file mode 100644 index 0000000..ef9099b --- /dev/null +++ b/windowsAgent/dist/tcl/history.tcl @@ -0,0 +1,335 @@ +# history.tcl -- +# +# Implementation of the history command. +# +# Copyright (c) 1997 Sun Microsystems, Inc. +# +# See the file "license.terms" for information on usage and redistribution of +# this file, and for a DISCLAIMER OF ALL WARRANTIES. +# + +# The tcl::history array holds the history list and some additional +# bookkeeping variables. +# +# nextid the index used for the next history list item. +# keep the max size of the history list +# oldest the index of the oldest item in the history. + +namespace eval ::tcl { + variable history + if {![info exists history]} { + array set history { + nextid 0 + keep 20 + oldest -20 + } + } + + namespace ensemble create -command ::tcl::history -map { + add ::tcl::HistAdd + change ::tcl::HistChange + clear ::tcl::HistClear + event ::tcl::HistEvent + info ::tcl::HistInfo + keep ::tcl::HistKeep + nextid ::tcl::HistNextID + redo ::tcl::HistRedo + } +} + +# history -- +# +# This is the main history command. See the man page for its interface. +# This does some argument checking and calls the helper ensemble in the +# tcl namespace. + +proc ::history {args} { + # If no command given, we're doing 'history info'. Can't be done with an + # ensemble unknown handler, as those don't fire when no subcommand is + # given at all. + + if {![llength $args]} { + set args info + } + + # Tricky stuff needed to make stack and errors come out right! + tailcall apply {arglist {tailcall history {*}$arglist} ::tcl} $args +} + +# (unnamed) -- +# +# Callback when [::history] is destroyed. Destroys the implementation. +# +# Parameters: +# oldName what the command was called. +# newName what the command is now called (an empty string). +# op the operation (= delete). +# +# Results: +# none +# +# Side Effects: +# The implementation of the [::history] command ceases to exist. + +trace add command ::history delete [list apply {{oldName newName op} { + variable history + unset -nocomplain history + foreach c [info procs ::tcl::Hist*] { + rename $c {} + } + rename ::tcl::history {} +} ::tcl}] + +# tcl::HistAdd -- +# +# Add an item to the history, and optionally eval it at the global scope +# +# Parameters: +# event the command to add +# exec (optional) a substring of "exec" causes the command to +# be evaled. +# Results: +# If executing, then the results of the command are returned +# +# Side Effects: +# Adds to the history list + +proc ::tcl::HistAdd {event {exec {}}} { + variable history + + if { + [prefix longest {exec {}} $exec] eq "" + && [llength [info level 0]] == 3 + } then { + return -code error "bad argument \"$exec\": should be \"exec\"" + } + + # Do not add empty commands to the history + if {[string trim $event] eq ""} { + return "" + } + + # Maintain the history + set history([incr history(nextid)]) $event + unset -nocomplain history([incr history(oldest)]) + + # Only execute if 'exec' (or non-empty prefix of it) given + if {$exec eq ""} { + return "" + } + tailcall eval $event +} + +# tcl::HistKeep -- +# +# Set or query the limit on the length of the history list +# +# Parameters: +# limit (optional) the length of the history list +# +# Results: +# If no limit is specified, the current limit is returned +# +# Side Effects: +# Updates history(keep) if a limit is specified + +proc ::tcl::HistKeep {{count {}}} { + variable history + if {[llength [info level 0]] == 1} { + return $history(keep) + } + if {![string is integer -strict $count] || ($count < 0)} { + return -code error "illegal keep count \"$count\"" + } + set oldold $history(oldest) + set history(oldest) [expr {$history(nextid) - $count}] + for {} {$oldold <= $history(oldest)} {incr oldold} { + unset -nocomplain history($oldold) + } + set history(keep) $count +} + +# tcl::HistClear -- +# +# Erase the history list +# +# Parameters: +# none +# +# Results: +# none +# +# Side Effects: +# Resets the history array, except for the keep limit + +proc ::tcl::HistClear {} { + variable history + set keep $history(keep) + unset history + array set history [list \ + nextid 0 \ + keep $keep \ + oldest -$keep \ + ] +} + +# tcl::HistInfo -- +# +# Return a pretty-printed version of the history list +# +# Parameters: +# num (optional) the length of the history list to return +# +# Results: +# A formatted history list + +proc ::tcl::HistInfo {{count {}}} { + variable history + if {[llength [info level 0]] == 1} { + set count [expr {$history(keep) + 1}] + } elseif {![string is integer -strict $count]} { + return -code error "bad integer \"$count\"" + } + set result {} + set newline "" + for {set i [expr {$history(nextid) - $count + 1}]} \ + {$i <= $history(nextid)} {incr i} { + if {![info exists history($i)]} { + continue + } + set cmd [string map [list \n \n\t] [string trimright $history($i) \ \n]] + append result $newline[format "%6d %s" $i $cmd] + set newline \n + } + return $result +} + +# tcl::HistRedo -- +# +# Fetch the previous or specified event, execute it, and then replace +# the current history item with that event. +# +# Parameters: +# event (optional) index of history item to redo. Defaults to -1, +# which means the previous event. +# +# Results: +# Those of the command being redone. +# +# Side Effects: +# Replaces the current history list item with the one being redone. + +proc ::tcl::HistRedo {{event -1}} { + variable history + + set i [HistIndex $event] + if {$i == $history(nextid)} { + return -code error "cannot redo the current event" + } + set cmd $history($i) + HistChange $cmd 0 + tailcall eval $cmd +} + +# tcl::HistIndex -- +# +# Map from an event specifier to an index in the history list. +# +# Parameters: +# event index of history item to redo. +# If this is a positive number, it is used directly. +# If it is a negative number, then it counts back to a previous +# event, where -1 is the most recent event. +# A string can be matched, either by being the prefix of a +# command or by matching a command with string match. +# +# Results: +# The index into history, or an error if the index didn't match. + +proc ::tcl::HistIndex {event} { + variable history + if {![string is integer -strict $event]} { + for {set i [expr {$history(nextid)-1}]} {[info exists history($i)]} \ + {incr i -1} { + if {[string match $event* $history($i)]} { + return $i + } + if {[string match $event $history($i)]} { + return $i + } + } + return -code error "no event matches \"$event\"" + } elseif {$event <= 0} { + set i [expr {$history(nextid) + $event}] + } else { + set i $event + } + if {$i <= $history(oldest)} { + return -code error "event \"$event\" is too far in the past" + } + if {$i > $history(nextid)} { + return -code error "event \"$event\" hasn't occured yet" + } + return $i +} + +# tcl::HistEvent -- +# +# Map from an event specifier to the value in the history list. +# +# Parameters: +# event index of history item to redo. See index for a description of +# possible event patterns. +# +# Results: +# The value from the history list. + +proc ::tcl::HistEvent {{event -1}} { + variable history + set i [HistIndex $event] + if {![info exists history($i)]} { + return "" + } + return [string trimright $history($i) \ \n] +} + +# tcl::HistChange -- +# +# Replace a value in the history list. +# +# Parameters: +# newValue The new value to put into the history list. +# event (optional) index of history item to redo. See index for a +# description of possible event patterns. This defaults to 0, +# which specifies the current event. +# +# Side Effects: +# Changes the history list. + +proc ::tcl::HistChange {newValue {event 0}} { + variable history + set i [HistIndex $event] + set history($i) $newValue +} + +# tcl::HistNextID -- +# +# Returns the number of the next history event. +# +# Parameters: +# None. +# +# Side Effects: +# None. + +proc ::tcl::HistNextID {} { + variable history + return [expr {$history(nextid) + 1}] +} + +return + +# Local Variables: +# mode: tcl +# fill-column: 78 +# End: diff --git a/windowsAgent/dist/tcl/init.tcl b/windowsAgent/dist/tcl/init.tcl new file mode 100644 index 0000000..8952172 --- /dev/null +++ b/windowsAgent/dist/tcl/init.tcl @@ -0,0 +1,819 @@ +# init.tcl -- +# +# Default system startup file for Tcl-based applications. Defines +# "unknown" procedure and auto-load facilities. +# +# Copyright (c) 1991-1993 The Regents of the University of California. +# Copyright (c) 1994-1996 Sun Microsystems, Inc. +# Copyright (c) 1998-1999 Scriptics Corporation. +# Copyright (c) 2004 by Kevin B. Kenny. All rights reserved. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# + +# This test intentionally written in pre-7.5 Tcl +if {[info commands package] == ""} { + error "version mismatch: library\nscripts expect Tcl version 7.5b1 or later but the loaded version is\nonly [info patchlevel]" +} +package require -exact Tcl 8.6.9 + +# Compute the auto path to use in this interpreter. +# The values on the path come from several locations: +# +# The environment variable TCLLIBPATH +# +# tcl_library, which is the directory containing this init.tcl script. +# [tclInit] (Tcl_Init()) searches around for the directory containing this +# init.tcl and defines tcl_library to that location before sourcing it. +# +# The parent directory of tcl_library. Adding the parent +# means that packages in peer directories will be found automatically. +# +# Also add the directory ../lib relative to the directory where the +# executable is located. This is meant to find binary packages for the +# same architecture as the current executable. +# +# tcl_pkgPath, which is set by the platform-specific initialization routines +# On UNIX it is compiled in +# On Windows, it is not used + +if {![info exists auto_path]} { + if {[info exists env(TCLLIBPATH)]} { + set auto_path $env(TCLLIBPATH) + } else { + set auto_path "" + } +} +namespace eval tcl { + variable Dir + foreach Dir [list $::tcl_library [file dirname $::tcl_library]] { + if {$Dir ni $::auto_path} { + lappend ::auto_path $Dir + } + } + set Dir [file join [file dirname [file dirname \ + [info nameofexecutable]]] lib] + if {$Dir ni $::auto_path} { + lappend ::auto_path $Dir + } + catch { + foreach Dir $::tcl_pkgPath { + if {$Dir ni $::auto_path} { + lappend ::auto_path $Dir + } + } + } + + if {![interp issafe]} { + variable Path [encoding dirs] + set Dir [file join $::tcl_library encoding] + if {$Dir ni $Path} { + lappend Path $Dir + encoding dirs $Path + } + } + + # TIP #255 min and max functions + namespace eval mathfunc { + proc min {args} { + if {![llength $args]} { + return -code error \ + "too few arguments to math function \"min\"" + } + set val Inf + foreach arg $args { + # This will handle forcing the numeric value without + # ruining the internal type of a numeric object + if {[catch {expr {double($arg)}} err]} { + return -code error $err + } + if {$arg < $val} {set val $arg} + } + return $val + } + proc max {args} { + if {![llength $args]} { + return -code error \ + "too few arguments to math function \"max\"" + } + set val -Inf + foreach arg $args { + # This will handle forcing the numeric value without + # ruining the internal type of a numeric object + if {[catch {expr {double($arg)}} err]} { + return -code error $err + } + if {$arg > $val} {set val $arg} + } + return $val + } + namespace export min max + } +} + +# Windows specific end of initialization + +if {(![interp issafe]) && ($tcl_platform(platform) eq "windows")} { + namespace eval tcl { + proc EnvTraceProc {lo n1 n2 op} { + global env + set x $env($n2) + set env($lo) $x + set env([string toupper $lo]) $x + } + proc InitWinEnv {} { + global env tcl_platform + foreach p [array names env] { + set u [string toupper $p] + if {$u ne $p} { + switch -- $u { + COMSPEC - + PATH { + set temp $env($p) + unset env($p) + set env($u) $temp + trace add variable env($p) write \ + [namespace code [list EnvTraceProc $p]] + trace add variable env($u) write \ + [namespace code [list EnvTraceProc $p]] + } + } + } + } + if {![info exists env(COMSPEC)]} { + set env(COMSPEC) cmd.exe + } + } + InitWinEnv + } +} + +# Setup the unknown package handler + + +if {[interp issafe]} { + package unknown {::tcl::tm::UnknownHandler ::tclPkgUnknown} +} else { + # Set up search for Tcl Modules (TIP #189). + # and setup platform specific unknown package handlers + if {$tcl_platform(os) eq "Darwin" + && $tcl_platform(platform) eq "unix"} { + package unknown {::tcl::tm::UnknownHandler \ + {::tcl::MacOSXPkgUnknown ::tclPkgUnknown}} + } else { + package unknown {::tcl::tm::UnknownHandler ::tclPkgUnknown} + } + + # Set up the 'clock' ensemble + + namespace eval ::tcl::clock [list variable TclLibDir $::tcl_library] + + proc ::tcl::initClock {} { + # Auto-loading stubs for 'clock.tcl' + + foreach cmd {add format scan} { + proc ::tcl::clock::$cmd args { + variable TclLibDir + source -encoding utf-8 [file join $TclLibDir clock.tcl] + return [uplevel 1 [info level 0]] + } + } + + rename ::tcl::initClock {} + } + ::tcl::initClock +} + +# Conditionalize for presence of exec. + +if {[namespace which -command exec] eq ""} { + + # Some machines do not have exec. Also, on all + # platforms, safe interpreters do not have exec. + + set auto_noexec 1 +} + +# Define a log command (which can be overwitten to log errors +# differently, specially when stderr is not available) + +if {[namespace which -command tclLog] eq ""} { + proc tclLog {string} { + catch {puts stderr $string} + } +} + +# unknown -- +# This procedure is called when a Tcl command is invoked that doesn't +# exist in the interpreter. It takes the following steps to make the +# command available: +# +# 1. See if the autoload facility can locate the command in a +# Tcl script file. If so, load it and execute it. +# 2. If the command was invoked interactively at top-level: +# (a) see if the command exists as an executable UNIX program. +# If so, "exec" the command. +# (b) see if the command requests csh-like history substitution +# in one of the common forms !!, !, or ^old^new. If +# so, emulate csh's history substitution. +# (c) see if the command is a unique abbreviation for another +# command. If so, invoke the command. +# +# Arguments: +# args - A list whose elements are the words of the original +# command, including the command name. + +proc unknown args { + variable ::tcl::UnknownPending + global auto_noexec auto_noload env tcl_interactive errorInfo errorCode + + if {[info exists errorInfo]} { + set savedErrorInfo $errorInfo + } + if {[info exists errorCode]} { + set savedErrorCode $errorCode + } + + set name [lindex $args 0] + if {![info exists auto_noload]} { + # + # Make sure we're not trying to load the same proc twice. + # + if {[info exists UnknownPending($name)]} { + return -code error "self-referential recursion\ + in \"unknown\" for command \"$name\"" + } + set UnknownPending($name) pending + set ret [catch { + auto_load $name [uplevel 1 {::namespace current}] + } msg opts] + unset UnknownPending($name) + if {$ret != 0} { + dict append opts -errorinfo "\n (autoloading \"$name\")" + return -options $opts $msg + } + if {![array size UnknownPending]} { + unset UnknownPending + } + if {$msg} { + if {[info exists savedErrorCode]} { + set ::errorCode $savedErrorCode + } else { + unset -nocomplain ::errorCode + } + if {[info exists savedErrorInfo]} { + set errorInfo $savedErrorInfo + } else { + unset -nocomplain errorInfo + } + set code [catch {uplevel 1 $args} msg opts] + if {$code == 1} { + # + # Compute stack trace contribution from the [uplevel]. + # Note the dependence on how Tcl_AddErrorInfo, etc. + # construct the stack trace. + # + set errInfo [dict get $opts -errorinfo] + set errCode [dict get $opts -errorcode] + set cinfo $args + if {[string bytelength $cinfo] > 150} { + set cinfo [string range $cinfo 0 150] + while {[string bytelength $cinfo] > 150} { + set cinfo [string range $cinfo 0 end-1] + } + append cinfo ... + } + set tail "\n (\"uplevel\" body line 1)\n invoked\ + from within\n\"uplevel 1 \$args\"" + set expect "$msg\n while executing\n\"$cinfo\"$tail" + if {$errInfo eq $expect} { + # + # The stack has only the eval from the expanded command + # Do not generate any stack trace here. + # + dict unset opts -errorinfo + dict incr opts -level + return -options $opts $msg + } + # + # Stack trace is nested, trim off just the contribution + # from the extra "eval" of $args due to the "catch" above. + # + set last [string last $tail $errInfo] + if {$last + [string length $tail] != [string length $errInfo]} { + # Very likely cannot happen + return -options $opts $msg + } + set errInfo [string range $errInfo 0 $last-1] + set tail "\"$cinfo\"" + set last [string last $tail $errInfo] + if {$last + [string length $tail] != [string length $errInfo]} { + return -code error -errorcode $errCode \ + -errorinfo $errInfo $msg + } + set errInfo [string range $errInfo 0 $last-1] + set tail "\n invoked from within\n" + set last [string last $tail $errInfo] + if {$last + [string length $tail] == [string length $errInfo]} { + return -code error -errorcode $errCode \ + -errorinfo [string range $errInfo 0 $last-1] $msg + } + set tail "\n while executing\n" + set last [string last $tail $errInfo] + if {$last + [string length $tail] == [string length $errInfo]} { + return -code error -errorcode $errCode \ + -errorinfo [string range $errInfo 0 $last-1] $msg + } + return -options $opts $msg + } else { + dict incr opts -level + return -options $opts $msg + } + } + } + + if {([info level] == 1) && ([info script] eq "") + && [info exists tcl_interactive] && $tcl_interactive} { + if {![info exists auto_noexec]} { + set new [auto_execok $name] + if {$new ne ""} { + set redir "" + if {[namespace which -command console] eq ""} { + set redir ">&@stdout <@stdin" + } + uplevel 1 [list ::catch \ + [concat exec $redir $new [lrange $args 1 end]] \ + ::tcl::UnknownResult ::tcl::UnknownOptions] + dict incr ::tcl::UnknownOptions -level + return -options $::tcl::UnknownOptions $::tcl::UnknownResult + } + } + if {$name eq "!!"} { + set newcmd [history event] + } elseif {[regexp {^!(.+)$} $name -> event]} { + set newcmd [history event $event] + } elseif {[regexp {^\^([^^]*)\^([^^]*)\^?$} $name -> old new]} { + set newcmd [history event -1] + catch {regsub -all -- $old $newcmd $new newcmd} + } + if {[info exists newcmd]} { + tclLog $newcmd + history change $newcmd 0 + uplevel 1 [list ::catch $newcmd \ + ::tcl::UnknownResult ::tcl::UnknownOptions] + dict incr ::tcl::UnknownOptions -level + return -options $::tcl::UnknownOptions $::tcl::UnknownResult + } + + set ret [catch {set candidates [info commands $name*]} msg] + if {$name eq "::"} { + set name "" + } + if {$ret != 0} { + dict append opts -errorinfo \ + "\n (expanding command prefix \"$name\" in unknown)" + return -options $opts $msg + } + # Filter out bogus matches when $name contained + # a glob-special char [Bug 946952] + if {$name eq ""} { + # Handle empty $name separately due to strangeness + # in [string first] (See RFE 1243354) + set cmds $candidates + } else { + set cmds [list] + foreach x $candidates { + if {[string first $name $x] == 0} { + lappend cmds $x + } + } + } + if {[llength $cmds] == 1} { + uplevel 1 [list ::catch [lreplace $args 0 0 [lindex $cmds 0]] \ + ::tcl::UnknownResult ::tcl::UnknownOptions] + dict incr ::tcl::UnknownOptions -level + return -options $::tcl::UnknownOptions $::tcl::UnknownResult + } + if {[llength $cmds]} { + return -code error "ambiguous command name \"$name\": [lsort $cmds]" + } + } + return -code error -errorcode [list TCL LOOKUP COMMAND $name] \ + "invalid command name \"$name\"" +} + +# auto_load -- +# Checks a collection of library directories to see if a procedure +# is defined in one of them. If so, it sources the appropriate +# library file to create the procedure. Returns 1 if it successfully +# loaded the procedure, 0 otherwise. +# +# Arguments: +# cmd - Name of the command to find and load. +# namespace (optional) The namespace where the command is being used - must be +# a canonical namespace as returned [namespace current] +# for instance. If not given, namespace current is used. + +proc auto_load {cmd {namespace {}}} { + global auto_index auto_path + + if {$namespace eq ""} { + set namespace [uplevel 1 [list ::namespace current]] + } + set nameList [auto_qualify $cmd $namespace] + # workaround non canonical auto_index entries that might be around + # from older auto_mkindex versions + lappend nameList $cmd + foreach name $nameList { + if {[info exists auto_index($name)]} { + namespace eval :: $auto_index($name) + # There's a couple of ways to look for a command of a given + # name. One is to use + # info commands $name + # Unfortunately, if the name has glob-magic chars in it like * + # or [], it may not match. For our purposes here, a better + # route is to use + # namespace which -command $name + if {[namespace which -command $name] ne ""} { + return 1 + } + } + } + if {![info exists auto_path]} { + return 0 + } + + if {![auto_load_index]} { + return 0 + } + foreach name $nameList { + if {[info exists auto_index($name)]} { + namespace eval :: $auto_index($name) + if {[namespace which -command $name] ne ""} { + return 1 + } + } + } + return 0 +} + +# auto_load_index -- +# Loads the contents of tclIndex files on the auto_path directory +# list. This is usually invoked within auto_load to load the index +# of available commands. Returns 1 if the index is loaded, and 0 if +# the index is already loaded and up to date. +# +# Arguments: +# None. + +proc auto_load_index {} { + variable ::tcl::auto_oldpath + global auto_index auto_path + + if {[info exists auto_oldpath] && ($auto_oldpath eq $auto_path)} { + return 0 + } + set auto_oldpath $auto_path + + # Check if we are a safe interpreter. In that case, we support only + # newer format tclIndex files. + + set issafe [interp issafe] + for {set i [expr {[llength $auto_path] - 1}]} {$i >= 0} {incr i -1} { + set dir [lindex $auto_path $i] + set f "" + if {$issafe} { + catch {source [file join $dir tclIndex]} + } elseif {[catch {set f [open [file join $dir tclIndex]]}]} { + continue + } else { + set error [catch { + set id [gets $f] + if {$id eq "# Tcl autoload index file, version 2.0"} { + eval [read $f] + } elseif {$id eq "# Tcl autoload index file: each line identifies a Tcl"} { + while {[gets $f line] >= 0} { + if {([string index $line 0] eq "#") \ + || ([llength $line] != 2)} { + continue + } + set name [lindex $line 0] + set auto_index($name) \ + "source [file join $dir [lindex $line 1]]" + } + } else { + error "[file join $dir tclIndex] isn't a proper Tcl index file" + } + } msg opts] + if {$f ne ""} { + close $f + } + if {$error} { + return -options $opts $msg + } + } + } + return 1 +} + +# auto_qualify -- +# +# Compute a fully qualified names list for use in the auto_index array. +# For historical reasons, commands in the global namespace do not have leading +# :: in the index key. The list has two elements when the command name is +# relative (no leading ::) and the namespace is not the global one. Otherwise +# only one name is returned (and searched in the auto_index). +# +# Arguments - +# cmd The command name. Can be any name accepted for command +# invocations (Like "foo::::bar"). +# namespace The namespace where the command is being used - must be +# a canonical namespace as returned by [namespace current] +# for instance. + +proc auto_qualify {cmd namespace} { + + # count separators and clean them up + # (making sure that foo:::::bar will be treated as foo::bar) + set n [regsub -all {::+} $cmd :: cmd] + + # Ignore namespace if the name starts with :: + # Handle special case of only leading :: + + # Before each return case we give an example of which category it is + # with the following form : + # (inputCmd, inputNameSpace) -> output + + if {[string match ::* $cmd]} { + if {$n > 1} { + # (::foo::bar , *) -> ::foo::bar + return [list $cmd] + } else { + # (::global , *) -> global + return [list [string range $cmd 2 end]] + } + } + + # Potentially returning 2 elements to try : + # (if the current namespace is not the global one) + + if {$n == 0} { + if {$namespace eq "::"} { + # (nocolons , ::) -> nocolons + return [list $cmd] + } else { + # (nocolons , ::sub) -> ::sub::nocolons nocolons + return [list ${namespace}::$cmd $cmd] + } + } elseif {$namespace eq "::"} { + # (foo::bar , ::) -> ::foo::bar + return [list ::$cmd] + } else { + # (foo::bar , ::sub) -> ::sub::foo::bar ::foo::bar + return [list ${namespace}::$cmd ::$cmd] + } +} + +# auto_import -- +# +# Invoked during "namespace import" to make see if the imported commands +# reside in an autoloaded library. If so, the commands are loaded so +# that they will be available for the import links. If not, then this +# procedure does nothing. +# +# Arguments - +# pattern The pattern of commands being imported (like "foo::*") +# a canonical namespace as returned by [namespace current] + +proc auto_import {pattern} { + global auto_index + + # If no namespace is specified, this will be an error case + + if {![string match *::* $pattern]} { + return + } + + set ns [uplevel 1 [list ::namespace current]] + set patternList [auto_qualify $pattern $ns] + + auto_load_index + + foreach pattern $patternList { + foreach name [array names auto_index $pattern] { + if {([namespace which -command $name] eq "") + && ([namespace qualifiers $pattern] eq [namespace qualifiers $name])} { + namespace eval :: $auto_index($name) + } + } + } +} + +# auto_execok -- +# +# Returns string that indicates name of program to execute if +# name corresponds to a shell builtin or an executable in the +# Windows search path, or "" otherwise. Builds an associative +# array auto_execs that caches information about previous checks, +# for speed. +# +# Arguments: +# name - Name of a command. + +if {$tcl_platform(platform) eq "windows"} { +# Windows version. +# +# Note that file executable doesn't work under Windows, so we have to +# look for files with .exe, .com, or .bat extensions. Also, the path +# may be in the Path or PATH environment variables, and path +# components are separated with semicolons, not colons as under Unix. +# +proc auto_execok name { + global auto_execs env tcl_platform + + if {[info exists auto_execs($name)]} { + return $auto_execs($name) + } + set auto_execs($name) "" + + set shellBuiltins [list assoc cls copy date del dir echo erase ftype \ + md mkdir mklink move rd ren rename rmdir start time type ver vol] + if {[info exists env(PATHEXT)]} { + # Add an initial ; to have the {} extension check first. + set execExtensions [split ";$env(PATHEXT)" ";"] + } else { + set execExtensions [list {} .com .exe .bat .cmd] + } + + if {[string tolower $name] in $shellBuiltins} { + # When this is command.com for some reason on Win2K, Tcl won't + # exec it unless the case is right, which this corrects. COMSPEC + # may not point to a real file, so do the check. + set cmd $env(COMSPEC) + if {[file exists $cmd]} { + set cmd [file attributes $cmd -shortname] + } + return [set auto_execs($name) [list $cmd /c $name]] + } + + if {[llength [file split $name]] != 1} { + foreach ext $execExtensions { + set file ${name}${ext} + if {[file exists $file] && ![file isdirectory $file]} { + return [set auto_execs($name) [list $file]] + } + } + return "" + } + + set path "[file dirname [info nameof]];.;" + if {[info exists env(WINDIR)]} { + set windir $env(WINDIR) + } + if {[info exists windir]} { + if {$tcl_platform(os) eq "Windows NT"} { + append path "$windir/system32;" + } + append path "$windir/system;$windir;" + } + + foreach var {PATH Path path} { + if {[info exists env($var)]} { + append path ";$env($var)" + } + } + + foreach ext $execExtensions { + unset -nocomplain checked + foreach dir [split $path {;}] { + # Skip already checked directories + if {[info exists checked($dir)] || ($dir eq "")} { + continue + } + set checked($dir) {} + set file [file join $dir ${name}${ext}] + if {[file exists $file] && ![file isdirectory $file]} { + return [set auto_execs($name) [list $file]] + } + } + } + return "" +} + +} else { +# Unix version. +# +proc auto_execok name { + global auto_execs env + + if {[info exists auto_execs($name)]} { + return $auto_execs($name) + } + set auto_execs($name) "" + if {[llength [file split $name]] != 1} { + if {[file executable $name] && ![file isdirectory $name]} { + set auto_execs($name) [list $name] + } + return $auto_execs($name) + } + foreach dir [split $env(PATH) :] { + if {$dir eq ""} { + set dir . + } + set file [file join $dir $name] + if {[file executable $file] && ![file isdirectory $file]} { + set auto_execs($name) [list $file] + return $auto_execs($name) + } + } + return "" +} + +} + +# ::tcl::CopyDirectory -- +# +# This procedure is called by Tcl's core when attempts to call the +# filesystem's copydirectory function fail. The semantics of the call +# are that 'dest' does not yet exist, i.e. dest should become the exact +# image of src. If dest does exist, we throw an error. +# +# Note that making changes to this procedure can change the results +# of running Tcl's tests. +# +# Arguments: +# action - "renaming" or "copying" +# src - source directory +# dest - destination directory +proc tcl::CopyDirectory {action src dest} { + set nsrc [file normalize $src] + set ndest [file normalize $dest] + + if {$action eq "renaming"} { + # Can't rename volumes. We could give a more precise + # error message here, but that would break the test suite. + if {$nsrc in [file volumes]} { + return -code error "error $action \"$src\" to\ + \"$dest\": trying to rename a volume or move a directory\ + into itself" + } + } + if {[file exists $dest]} { + if {$nsrc eq $ndest} { + return -code error "error $action \"$src\" to\ + \"$dest\": trying to rename a volume or move a directory\ + into itself" + } + if {$action eq "copying"} { + # We used to throw an error here, but, looking more closely + # at the core copy code in tclFCmd.c, if the destination + # exists, then we should only call this function if -force + # is true, which means we just want to over-write. So, + # the following code is now commented out. + # + # return -code error "error $action \"$src\" to\ + # \"$dest\": file already exists" + } else { + # Depending on the platform, and on the current + # working directory, the directories '.', '..' + # can be returned in various combinations. Anyway, + # if any other file is returned, we must signal an error. + set existing [glob -nocomplain -directory $dest * .*] + lappend existing {*}[glob -nocomplain -directory $dest \ + -type hidden * .*] + foreach s $existing { + if {[file tail $s] ni {. ..}} { + return -code error "error $action \"$src\" to\ + \"$dest\": file already exists" + } + } + } + } else { + if {[string first $nsrc $ndest] != -1} { + set srclen [expr {[llength [file split $nsrc]] - 1}] + set ndest [lindex [file split $ndest] $srclen] + if {$ndest eq [file tail $nsrc]} { + return -code error "error $action \"$src\" to\ + \"$dest\": trying to rename a volume or move a directory\ + into itself" + } + } + file mkdir $dest + } + # Have to be careful to capture both visible and hidden files. + # We will also be more generous to the file system and not + # assume the hidden and non-hidden lists are non-overlapping. + # + # On Unix 'hidden' files begin with '.'. On other platforms + # or filesystems hidden files may have other interpretations. + set filelist [concat [glob -nocomplain -directory $src *] \ + [glob -nocomplain -directory $src -types hidden *]] + + foreach s [lsort -unique $filelist] { + if {[file tail $s] ni {. ..}} { + file copy -force -- $s [file join $dest [file tail $s]] + } + } + return +} diff --git a/windowsAgent/dist/tcl/package.tcl b/windowsAgent/dist/tcl/package.tcl new file mode 100644 index 0000000..44e3b28 --- /dev/null +++ b/windowsAgent/dist/tcl/package.tcl @@ -0,0 +1,747 @@ +# package.tcl -- +# +# utility procs formerly in init.tcl which can be loaded on demand +# for package management. +# +# Copyright (c) 1991-1993 The Regents of the University of California. +# Copyright (c) 1994-1998 Sun Microsystems, Inc. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# + +namespace eval tcl::Pkg {} + +# ::tcl::Pkg::CompareExtension -- +# +# Used internally by pkg_mkIndex to compare the extension of a file to a given +# extension. On Windows, it uses a case-insensitive comparison because the +# file system can be file insensitive. +# +# Arguments: +# fileName name of a file whose extension is compared +# ext (optional) The extension to compare against; you must +# provide the starting dot. +# Defaults to [info sharedlibextension] +# +# Results: +# Returns 1 if the extension matches, 0 otherwise + +proc tcl::Pkg::CompareExtension {fileName {ext {}}} { + global tcl_platform + if {$ext eq ""} {set ext [info sharedlibextension]} + if {$tcl_platform(platform) eq "windows"} { + return [string equal -nocase [file extension $fileName] $ext] + } else { + # Some unices add trailing numbers after the .so, so + # we could have something like '.so.1.2'. + set root $fileName + while {1} { + set currExt [file extension $root] + if {$currExt eq $ext} { + return 1 + } + + # The current extension does not match; if it is not a numeric + # value, quit, as we are only looking to ignore version number + # extensions. Otherwise we might return 1 in this case: + # tcl::Pkg::CompareExtension foo.so.bar .so + # which should not match. + + if {![string is integer -strict [string range $currExt 1 end]]} { + return 0 + } + set root [file rootname $root] + } + } +} + +# pkg_mkIndex -- +# This procedure creates a package index in a given directory. The package +# index consists of a "pkgIndex.tcl" file whose contents are a Tcl script that +# sets up package information with "package require" commands. The commands +# describe all of the packages defined by the files given as arguments. +# +# Arguments: +# -direct (optional) If this flag is present, the generated +# code in pkgMkIndex.tcl will cause the package to be +# loaded when "package require" is executed, rather +# than lazily when the first reference to an exported +# procedure in the package is made. +# -verbose (optional) Verbose output; the name of each file that +# was successfully rocessed is printed out. Additionally, +# if processing of a file failed a message is printed. +# -load pat (optional) Preload any packages whose names match +# the pattern. Used to handle DLLs that depend on +# other packages during their Init procedure. +# dir - Name of the directory in which to create the index. +# args - Any number of additional arguments, each giving +# a glob pattern that matches the names of one or +# more shared libraries or Tcl script files in +# dir. + +proc pkg_mkIndex {args} { + set usage {"pkg_mkIndex ?-direct? ?-lazy? ?-load pattern? ?-verbose? ?--? dir ?pattern ...?"} + + set argCount [llength $args] + if {$argCount < 1} { + return -code error "wrong # args: should be\n$usage" + } + + set more "" + set direct 1 + set doVerbose 0 + set loadPat "" + for {set idx 0} {$idx < $argCount} {incr idx} { + set flag [lindex $args $idx] + switch -glob -- $flag { + -- { + # done with the flags + incr idx + break + } + -verbose { + set doVerbose 1 + } + -lazy { + set direct 0 + append more " -lazy" + } + -direct { + append more " -direct" + } + -load { + incr idx + set loadPat [lindex $args $idx] + append more " -load $loadPat" + } + -* { + return -code error "unknown flag $flag: should be\n$usage" + } + default { + # done with the flags + break + } + } + } + + set dir [lindex $args $idx] + set patternList [lrange $args [expr {$idx + 1}] end] + if {![llength $patternList]} { + set patternList [list "*.tcl" "*[info sharedlibextension]"] + } + + try { + set fileList [glob -directory $dir -tails -types {r f} -- \ + {*}$patternList] + } on error {msg opt} { + return -options $opt $msg + } + foreach file $fileList { + # For each file, figure out what commands and packages it provides. + # To do this, create a child interpreter, load the file into the + # interpreter, and get a list of the new commands and packages that + # are defined. + + if {$file eq "pkgIndex.tcl"} { + continue + } + + set c [interp create] + + # Load into the child any packages currently loaded in the parent + # interpreter that match the -load pattern. + + if {$loadPat ne ""} { + if {$doVerbose} { + tclLog "currently loaded packages: '[info loaded]'" + tclLog "trying to load all packages matching $loadPat" + } + if {![llength [info loaded]]} { + tclLog "warning: no packages are currently loaded, nothing" + tclLog "can possibly match '$loadPat'" + } + } + foreach pkg [info loaded] { + if {![string match -nocase $loadPat [lindex $pkg 1]]} { + continue + } + if {$doVerbose} { + tclLog "package [lindex $pkg 1] matches '$loadPat'" + } + try { + load [lindex $pkg 0] [lindex $pkg 1] $c + } on error err { + if {$doVerbose} { + tclLog "warning: load [lindex $pkg 0]\ + [lindex $pkg 1]\nfailed with: $err" + } + } on ok {} { + if {$doVerbose} { + tclLog "loaded [lindex $pkg 0] [lindex $pkg 1]" + } + } + if {[lindex $pkg 1] eq "Tk"} { + # Withdraw . if Tk was loaded, to avoid showing a window. + $c eval [list wm withdraw .] + } + } + + $c eval { + # Stub out the package command so packages can require other + # packages. + + rename package __package_orig + proc package {what args} { + switch -- $what { + require { + return; # Ignore transitive requires + } + default { + __package_orig $what {*}$args + } + } + } + proc tclPkgUnknown args {} + package unknown tclPkgUnknown + + # Stub out the unknown command so package can call into each other + # during their initialilzation. + + proc unknown {args} {} + + # Stub out the auto_import mechanism + + proc auto_import {args} {} + + # reserve the ::tcl namespace for support procs and temporary + # variables. This might make it awkward to generate a + # pkgIndex.tcl file for the ::tcl namespace. + + namespace eval ::tcl { + variable dir ;# Current directory being processed + variable file ;# Current file being processed + variable direct ;# -direct flag value + variable x ;# Loop variable + variable debug ;# For debugging + variable type ;# "load" or "source", for -direct + variable namespaces ;# Existing namespaces (e.g., ::tcl) + variable packages ;# Existing packages (e.g., Tcl) + variable origCmds ;# Existing commands + variable newCmds ;# Newly created commands + variable newPkgs {} ;# Newly created packages + } + } + + $c eval [list set ::tcl::dir $dir] + $c eval [list set ::tcl::file $file] + $c eval [list set ::tcl::direct $direct] + + # Download needed procedures into the slave because we've just deleted + # the unknown procedure. This doesn't handle procedures with default + # arguments. + + foreach p {::tcl::Pkg::CompareExtension} { + $c eval [list namespace eval [namespace qualifiers $p] {}] + $c eval [list proc $p [info args $p] [info body $p]] + } + + try { + $c eval { + set ::tcl::debug "loading or sourcing" + + # we need to track command defined by each package even in the + # -direct case, because they are needed internally by the + # "partial pkgIndex.tcl" step above. + + proc ::tcl::GetAllNamespaces {{root ::}} { + set list $root + foreach ns [namespace children $root] { + lappend list {*}[::tcl::GetAllNamespaces $ns] + } + return $list + } + + # init the list of existing namespaces, packages, commands + + foreach ::tcl::x [::tcl::GetAllNamespaces] { + set ::tcl::namespaces($::tcl::x) 1 + } + foreach ::tcl::x [package names] { + if {[package provide $::tcl::x] ne ""} { + set ::tcl::packages($::tcl::x) 1 + } + } + set ::tcl::origCmds [info commands] + + # Try to load the file if it has the shared library extension, + # otherwise source it. It's important not to try to load + # files that aren't shared libraries, because on some systems + # (like SunOS) the loader will abort the whole application + # when it gets an error. + + if {[::tcl::Pkg::CompareExtension $::tcl::file [info sharedlibextension]]} { + # The "file join ." command below is necessary. Without + # it, if the file name has no \'s and we're on UNIX, the + # load command will invoke the LD_LIBRARY_PATH search + # mechanism, which could cause the wrong file to be used. + + set ::tcl::debug loading + load [file join $::tcl::dir $::tcl::file] + set ::tcl::type load + } else { + set ::tcl::debug sourcing + source [file join $::tcl::dir $::tcl::file] + set ::tcl::type source + } + + # As a performance optimization, if we are creating direct + # load packages, don't bother figuring out the set of commands + # created by the new packages. We only need that list for + # setting up the autoloading used in the non-direct case. + if {!$::tcl::direct} { + # See what new namespaces appeared, and import commands + # from them. Only exported commands go into the index. + + foreach ::tcl::x [::tcl::GetAllNamespaces] { + if {![info exists ::tcl::namespaces($::tcl::x)]} { + namespace import -force ${::tcl::x}::* + } + + # Figure out what commands appeared + + foreach ::tcl::x [info commands] { + set ::tcl::newCmds($::tcl::x) 1 + } + foreach ::tcl::x $::tcl::origCmds { + unset -nocomplain ::tcl::newCmds($::tcl::x) + } + foreach ::tcl::x [array names ::tcl::newCmds] { + # determine which namespace a command comes from + + set ::tcl::abs [namespace origin $::tcl::x] + + # special case so that global names have no + # leading ::, this is required by the unknown + # command + + set ::tcl::abs \ + [lindex [auto_qualify $::tcl::abs ::] 0] + + if {$::tcl::x ne $::tcl::abs} { + # Name changed during qualification + + set ::tcl::newCmds($::tcl::abs) 1 + unset ::tcl::newCmds($::tcl::x) + } + } + } + } + + # Look through the packages that appeared, and if there is a + # version provided, then record it + + foreach ::tcl::x [package names] { + if {[package provide $::tcl::x] ne "" + && ![info exists ::tcl::packages($::tcl::x)]} { + lappend ::tcl::newPkgs \ + [list $::tcl::x [package provide $::tcl::x]] + } + } + } + } on error msg { + set what [$c eval set ::tcl::debug] + if {$doVerbose} { + tclLog "warning: error while $what $file: $msg" + } + } on ok {} { + set what [$c eval set ::tcl::debug] + if {$doVerbose} { + tclLog "successful $what of $file" + } + set type [$c eval set ::tcl::type] + set cmds [lsort [$c eval array names ::tcl::newCmds]] + set pkgs [$c eval set ::tcl::newPkgs] + if {$doVerbose} { + if {!$direct} { + tclLog "commands provided were $cmds" + } + tclLog "packages provided were $pkgs" + } + if {[llength $pkgs] > 1} { + tclLog "warning: \"$file\" provides more than one package ($pkgs)" + } + foreach pkg $pkgs { + # cmds is empty/not used in the direct case + lappend files($pkg) [list $file $type $cmds] + } + + if {$doVerbose} { + tclLog "processed $file" + } + } + interp delete $c + } + + append index "# Tcl package index file, version 1.1\n" + append index "# This file is generated by the \"pkg_mkIndex$more\" command\n" + append index "# and sourced either when an application starts up or\n" + append index "# by a \"package unknown\" script. It invokes the\n" + append index "# \"package ifneeded\" command to set up package-related\n" + append index "# information so that packages will be loaded automatically\n" + append index "# in response to \"package require\" commands. When this\n" + append index "# script is sourced, the variable \$dir must contain the\n" + append index "# full path name of this file's directory.\n" + + foreach pkg [lsort [array names files]] { + set cmd {} + lassign $pkg name version + lappend cmd ::tcl::Pkg::Create -name $name -version $version + foreach spec [lsort -index 0 $files($pkg)] { + foreach {file type procs} $spec { + if {$direct} { + set procs {} + } + lappend cmd "-$type" [list $file $procs] + } + } + append index "\n[eval $cmd]" + } + + set f [open [file join $dir pkgIndex.tcl] w] + puts $f $index + close $f +} + +# tclPkgSetup -- +# This is a utility procedure use by pkgIndex.tcl files. It is invoked as +# part of a "package ifneeded" script. It calls "package provide" to indicate +# that a package is available, then sets entries in the auto_index array so +# that the package's files will be auto-loaded when the commands are used. +# +# Arguments: +# dir - Directory containing all the files for this package. +# pkg - Name of the package (no version number). +# version - Version number for the package, such as 2.1.3. +# files - List of files that constitute the package. Each +# element is a sub-list with three elements. The first +# is the name of a file relative to $dir, the second is +# "load" or "source", indicating whether the file is a +# loadable binary or a script to source, and the third +# is a list of commands defined by this file. + +proc tclPkgSetup {dir pkg version files} { + global auto_index + + package provide $pkg $version + foreach fileInfo $files { + set f [lindex $fileInfo 0] + set type [lindex $fileInfo 1] + foreach cmd [lindex $fileInfo 2] { + if {$type eq "load"} { + set auto_index($cmd) [list load [file join $dir $f] $pkg] + } else { + set auto_index($cmd) [list source [file join $dir $f]] + } + } + } +} + +# tclPkgUnknown -- +# This procedure provides the default for the "package unknown" function. It +# is invoked when a package that's needed can't be found. It scans the +# auto_path directories and their immediate children looking for pkgIndex.tcl +# files and sources any such files that are found to setup the package +# database. As it searches, it will recognize changes to the auto_path and +# scan any new directories. +# +# Arguments: +# name - Name of desired package. Not used. +# version - Version of desired package. Not used. +# exact - Either "-exact" or omitted. Not used. + +proc tclPkgUnknown {name args} { + global auto_path env + + if {![info exists auto_path]} { + return + } + # Cache the auto_path, because it may change while we run through the + # first set of pkgIndex.tcl files + set old_path [set use_path $auto_path] + while {[llength $use_path]} { + set dir [lindex $use_path end] + + # Make sure we only scan each directory one time. + if {[info exists tclSeenPath($dir)]} { + set use_path [lrange $use_path 0 end-1] + continue + } + set tclSeenPath($dir) 1 + + # we can't use glob in safe interps, so enclose the following in a + # catch statement, where we get the pkgIndex files out of the + # subdirectories + catch { + foreach file [glob -directory $dir -join -nocomplain \ + * pkgIndex.tcl] { + set dir [file dirname $file] + if {![info exists procdDirs($dir)]} { + try { + source $file + } trap {POSIX EACCES} {} { + # $file was not readable; silently ignore + continue + } on error msg { + tclLog "error reading package index file $file: $msg" + } on ok {} { + set procdDirs($dir) 1 + } + } + } + } + set dir [lindex $use_path end] + if {![info exists procdDirs($dir)]} { + set file [file join $dir pkgIndex.tcl] + # safe interps usually don't have "file exists", + if {([interp issafe] || [file exists $file])} { + try { + source $file + } trap {POSIX EACCES} {} { + # $file was not readable; silently ignore + continue + } on error msg { + tclLog "error reading package index file $file: $msg" + } on ok {} { + set procdDirs($dir) 1 + } + } + } + + set use_path [lrange $use_path 0 end-1] + + # Check whether any of the index scripts we [source]d above set a new + # value for $::auto_path. If so, then find any new directories on the + # $::auto_path, and lappend them to the $use_path we are working from. + # This gives index scripts the (arguably unwise) power to expand the + # index script search path while the search is in progress. + set index 0 + if {[llength $old_path] == [llength $auto_path]} { + foreach dir $auto_path old $old_path { + if {$dir ne $old} { + # This entry in $::auto_path has changed. + break + } + incr index + } + } + + # $index now points to the first element of $auto_path that has + # changed, or the beginning if $auto_path has changed length Scan the + # new elements of $auto_path for directories to add to $use_path. + # Don't add directories we've already seen, or ones already on the + # $use_path. + foreach dir [lrange $auto_path $index end] { + if {![info exists tclSeenPath($dir)] && ($dir ni $use_path)} { + lappend use_path $dir + } + } + set old_path $auto_path + } +} + +# tcl::MacOSXPkgUnknown -- +# This procedure extends the "package unknown" function for MacOSX. It scans +# the Resources/Scripts directories of the immediate children of the auto_path +# directories for pkgIndex files. +# +# Arguments: +# original - original [package unknown] procedure +# name - Name of desired package. Not used. +# version - Version of desired package. Not used. +# exact - Either "-exact" or omitted. Not used. + +proc tcl::MacOSXPkgUnknown {original name args} { + # First do the cross-platform default search + uplevel 1 $original [linsert $args 0 $name] + + # Now do MacOSX specific searching + global auto_path + + if {![info exists auto_path]} { + return + } + # Cache the auto_path, because it may change while we run through the + # first set of pkgIndex.tcl files + set old_path [set use_path $auto_path] + while {[llength $use_path]} { + set dir [lindex $use_path end] + + # Make sure we only scan each directory one time. + if {[info exists tclSeenPath($dir)]} { + set use_path [lrange $use_path 0 end-1] + continue + } + set tclSeenPath($dir) 1 + + # get the pkgIndex files out of the subdirectories + foreach file [glob -directory $dir -join -nocomplain \ + * Resources Scripts pkgIndex.tcl] { + set dir [file dirname $file] + if {![info exists procdDirs($dir)]} { + try { + source $file + } trap {POSIX EACCES} {} { + # $file was not readable; silently ignore + continue + } on error msg { + tclLog "error reading package index file $file: $msg" + } on ok {} { + set procdDirs($dir) 1 + } + } + } + set use_path [lrange $use_path 0 end-1] + + # Check whether any of the index scripts we [source]d above set a new + # value for $::auto_path. If so, then find any new directories on the + # $::auto_path, and lappend them to the $use_path we are working from. + # This gives index scripts the (arguably unwise) power to expand the + # index script search path while the search is in progress. + set index 0 + if {[llength $old_path] == [llength $auto_path]} { + foreach dir $auto_path old $old_path { + if {$dir ne $old} { + # This entry in $::auto_path has changed. + break + } + incr index + } + } + + # $index now points to the first element of $auto_path that has + # changed, or the beginning if $auto_path has changed length Scan the + # new elements of $auto_path for directories to add to $use_path. + # Don't add directories we've already seen, or ones already on the + # $use_path. + foreach dir [lrange $auto_path $index end] { + if {![info exists tclSeenPath($dir)] && ($dir ni $use_path)} { + lappend use_path $dir + } + } + set old_path $auto_path + } +} + +# ::tcl::Pkg::Create -- +# +# Given a package specification generate a "package ifneeded" statement +# for the package, suitable for inclusion in a pkgIndex.tcl file. +# +# Arguments: +# args arguments used by the Create function: +# -name packageName +# -version packageVersion +# -load {filename ?{procs}?} +# ... +# -source {filename ?{procs}?} +# ... +# +# Any number of -load and -source parameters may be +# specified, so long as there is at least one -load or +# -source parameter. If the procs component of a module +# specifier is left off, that module will be set up for +# direct loading; otherwise, it will be set up for lazy +# loading. If both -source and -load are specified, the +# -load'ed files will be loaded first, followed by the +# -source'd files. +# +# Results: +# An appropriate "package ifneeded" statement for the package. + +proc ::tcl::Pkg::Create {args} { + append err(usage) "[lindex [info level 0] 0] " + append err(usage) "-name packageName -version packageVersion" + append err(usage) "?-load {filename ?{procs}?}? ... " + append err(usage) "?-source {filename ?{procs}?}? ..." + + set err(wrongNumArgs) "wrong # args: should be \"$err(usage)\"" + set err(valueMissing) "value for \"%s\" missing: should be \"$err(usage)\"" + set err(unknownOpt) "unknown option \"%s\": should be \"$err(usage)\"" + set err(noLoadOrSource) "at least one of -load and -source must be given" + + # process arguments + set len [llength $args] + if {$len < 6} { + error $err(wrongNumArgs) + } + + # Initialize parameters + array set opts {-name {} -version {} -source {} -load {}} + + # process parameters + for {set i 0} {$i < $len} {incr i} { + set flag [lindex $args $i] + incr i + switch -glob -- $flag { + "-name" - + "-version" { + if {$i >= $len} { + error [format $err(valueMissing) $flag] + } + set opts($flag) [lindex $args $i] + } + "-source" - + "-load" { + if {$i >= $len} { + error [format $err(valueMissing) $flag] + } + lappend opts($flag) [lindex $args $i] + } + default { + error [format $err(unknownOpt) [lindex $args $i]] + } + } + } + + # Validate the parameters + if {![llength $opts(-name)]} { + error [format $err(valueMissing) "-name"] + } + if {![llength $opts(-version)]} { + error [format $err(valueMissing) "-version"] + } + + if {!([llength $opts(-source)] || [llength $opts(-load)])} { + error $err(noLoadOrSource) + } + + # OK, now everything is good. Generate the package ifneeded statment. + set cmdline "package ifneeded $opts(-name) $opts(-version) " + + set cmdList {} + set lazyFileList {} + + # Handle -load and -source specs + foreach key {load source} { + foreach filespec $opts(-$key) { + lassign $filespec filename proclist + + if { [llength $proclist] == 0 } { + set cmd "\[list $key \[file join \$dir [list $filename]\]\]" + lappend cmdList $cmd + } else { + lappend lazyFileList [list $filename $key $proclist] + } + } + } + + if {[llength $lazyFileList]} { + lappend cmdList "\[list tclPkgSetup \$dir $opts(-name)\ + $opts(-version) [list $lazyFileList]\]" + } + append cmdline [join $cmdList "\\n"] + return $cmdline +} + +interp alias {} ::pkg::create {} ::tcl::Pkg::Create diff --git a/windowsAgent/dist/tcl/parray.tcl b/windowsAgent/dist/tcl/parray.tcl new file mode 100644 index 0000000..a9c2cb1 --- /dev/null +++ b/windowsAgent/dist/tcl/parray.tcl @@ -0,0 +1,28 @@ +# parray: +# Print the contents of a global array on stdout. +# +# Copyright (c) 1991-1993 The Regents of the University of California. +# Copyright (c) 1994 Sun Microsystems, Inc. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# + +proc parray {a {pattern *}} { + upvar 1 $a array + if {![array exists array]} { + return -code error "\"$a\" isn't an array" + } + set maxl 0 + set names [lsort [array names array $pattern]] + foreach name $names { + if {[string length $name] > $maxl} { + set maxl [string length $name] + } + } + set maxl [expr {$maxl + [string length $a] + 2}] + foreach name $names { + set nameString [format %s(%s) $a $name] + puts stdout [format "%-*s = %s" $maxl $nameString $array($name)] + } +} diff --git a/windowsAgent/dist/tcl/safe.tcl b/windowsAgent/dist/tcl/safe.tcl new file mode 100644 index 0000000..ea6391d --- /dev/null +++ b/windowsAgent/dist/tcl/safe.tcl @@ -0,0 +1,1133 @@ +# safe.tcl -- +# +# This file provide a safe loading/sourcing mechanism for safe interpreters. +# It implements a virtual path mecanism to hide the real pathnames from the +# slave. It runs in a master interpreter and sets up data structure and +# aliases that will be invoked when used from a slave interpreter. +# +# See the safe.n man page for details. +# +# Copyright (c) 1996-1997 Sun Microsystems, Inc. +# +# See the file "license.terms" for information on usage and redistribution of +# this file, and for a DISCLAIMER OF ALL WARRANTIES. + +# +# The implementation is based on namespaces. These naming conventions are +# followed: +# Private procs starts with uppercase. +# Public procs are exported and starts with lowercase +# + +# Needed utilities package +package require opt 0.4.1 + +# Create the safe namespace +namespace eval ::safe { + # Exported API: + namespace export interpCreate interpInit interpConfigure interpDelete \ + interpAddToAccessPath interpFindInAccessPath setLogCmd +} + +# Helper function to resolve the dual way of specifying staticsok (either +# by -noStatics or -statics 0) +proc ::safe::InterpStatics {} { + foreach v {Args statics noStatics} { + upvar $v $v + } + set flag [::tcl::OptProcArgGiven -noStatics] + if {$flag && (!$noStatics == !$statics) + && ([::tcl::OptProcArgGiven -statics])} { + return -code error\ + "conflicting values given for -statics and -noStatics" + } + if {$flag} { + return [expr {!$noStatics}] + } else { + return $statics + } +} + +# Helper function to resolve the dual way of specifying nested loading +# (either by -nestedLoadOk or -nested 1) +proc ::safe::InterpNested {} { + foreach v {Args nested nestedLoadOk} { + upvar $v $v + } + set flag [::tcl::OptProcArgGiven -nestedLoadOk] + # note that the test here is the opposite of the "InterpStatics" one + # (it is not -noNested... because of the wanted default value) + if {$flag && (!$nestedLoadOk != !$nested) + && ([::tcl::OptProcArgGiven -nested])} { + return -code error\ + "conflicting values given for -nested and -nestedLoadOk" + } + if {$flag} { + # another difference with "InterpStatics" + return $nestedLoadOk + } else { + return $nested + } +} + +#### +# +# API entry points that needs argument parsing : +# +#### + +# Interface/entry point function and front end for "Create" +proc ::safe::interpCreate {args} { + set Args [::tcl::OptKeyParse ::safe::interpCreate $args] + InterpCreate $slave $accessPath \ + [InterpStatics] [InterpNested] $deleteHook +} + +proc ::safe::interpInit {args} { + set Args [::tcl::OptKeyParse ::safe::interpIC $args] + if {![::interp exists $slave]} { + return -code error "\"$slave\" is not an interpreter" + } + InterpInit $slave $accessPath \ + [InterpStatics] [InterpNested] $deleteHook +} + +# Check that the given slave is "one of us" +proc ::safe::CheckInterp {slave} { + namespace upvar ::safe S$slave state + if {![info exists state] || ![::interp exists $slave]} { + return -code error \ + "\"$slave\" is not an interpreter managed by ::safe::" + } +} + +# Interface/entry point function and front end for "Configure". This code +# is awfully pedestrian because it would need more coupling and support +# between the way we store the configuration values in safe::interp's and +# the Opt package. Obviously we would like an OptConfigure to avoid +# duplicating all this code everywhere. +# -> TODO (the app should share or access easily the program/value stored +# by opt) + +# This is even more complicated by the boolean flags with no values that +# we had the bad idea to support for the sake of user simplicity in +# create/init but which makes life hard in configure... +# So this will be hopefully written and some integrated with opt1.0 +# (hopefully for tcl8.1 ?) +proc ::safe::interpConfigure {args} { + switch [llength $args] { + 1 { + # If we have exactly 1 argument the semantic is to return all + # the current configuration. We still call OptKeyParse though + # we know that "slave" is our given argument because it also + # checks for the "-help" option. + set Args [::tcl::OptKeyParse ::safe::interpIC $args] + CheckInterp $slave + namespace upvar ::safe S$slave state + + return [join [list \ + [list -accessPath $state(access_path)] \ + [list -statics $state(staticsok)] \ + [list -nested $state(nestedok)] \ + [list -deleteHook $state(cleanupHook)]]] + } + 2 { + # If we have exactly 2 arguments the semantic is a "configure + # get" + lassign $args slave arg + + # get the flag sub program (we 'know' about Opt's internal + # representation of data) + set desc [lindex [::tcl::OptKeyGetDesc ::safe::interpIC] 2] + set hits [::tcl::OptHits desc $arg] + if {$hits > 1} { + return -code error [::tcl::OptAmbigous $desc $arg] + } elseif {$hits == 0} { + return -code error [::tcl::OptFlagUsage $desc $arg] + } + CheckInterp $slave + namespace upvar ::safe S$slave state + + set item [::tcl::OptCurDesc $desc] + set name [::tcl::OptName $item] + switch -exact -- $name { + -accessPath { + return [list -accessPath $state(access_path)] + } + -statics { + return [list -statics $state(staticsok)] + } + -nested { + return [list -nested $state(nestedok)] + } + -deleteHook { + return [list -deleteHook $state(cleanupHook)] + } + -noStatics { + # it is most probably a set in fact but we would need + # then to jump to the set part and it is not *sure* + # that it is a set action that the user want, so force + # it to use the unambigous -statics ?value? instead: + return -code error\ + "ambigous query (get or set -noStatics ?)\ + use -statics instead" + } + -nestedLoadOk { + return -code error\ + "ambigous query (get or set -nestedLoadOk ?)\ + use -nested instead" + } + default { + return -code error "unknown flag $name (bug)" + } + } + } + default { + # Otherwise we want to parse the arguments like init and + # create did + set Args [::tcl::OptKeyParse ::safe::interpIC $args] + CheckInterp $slave + namespace upvar ::safe S$slave state + + # Get the current (and not the default) values of whatever has + # not been given: + if {![::tcl::OptProcArgGiven -accessPath]} { + set doreset 1 + set accessPath $state(access_path) + } else { + set doreset 0 + } + if { + ![::tcl::OptProcArgGiven -statics] + && ![::tcl::OptProcArgGiven -noStatics] + } then { + set statics $state(staticsok) + } else { + set statics [InterpStatics] + } + if { + [::tcl::OptProcArgGiven -nested] || + [::tcl::OptProcArgGiven -nestedLoadOk] + } then { + set nested [InterpNested] + } else { + set nested $state(nestedok) + } + if {![::tcl::OptProcArgGiven -deleteHook]} { + set deleteHook $state(cleanupHook) + } + # we can now reconfigure : + InterpSetConfig $slave $accessPath $statics $nested $deleteHook + # auto_reset the slave (to completly synch the new access_path) + if {$doreset} { + if {[catch {::interp eval $slave {auto_reset}} msg]} { + Log $slave "auto_reset failed: $msg" + } else { + Log $slave "successful auto_reset" NOTICE + } + } + } + } +} + +#### +# +# Functions that actually implements the exported APIs +# +#### + +# +# safe::InterpCreate : doing the real job +# +# This procedure creates a safe slave and initializes it with the safe +# base aliases. +# NB: slave name must be simple alphanumeric string, no spaces, no (), no +# {},... {because the state array is stored as part of the name} +# +# Returns the slave name. +# +# Optional Arguments : +# + slave name : if empty, generated name will be used +# + access_path: path list controlling where load/source can occur, +# if empty: the master auto_path will be used. +# + staticsok : flag, if 0 :no static package can be loaded (load {} Xxx) +# if 1 :static packages are ok. +# + nestedok: flag, if 0 :no loading to sub-sub interps (load xx xx sub) +# if 1 : multiple levels are ok. + +# use the full name and no indent so auto_mkIndex can find us +proc ::safe::InterpCreate { + slave + access_path + staticsok + nestedok + deletehook + } { + # Create the slave. + if {$slave ne ""} { + ::interp create -safe $slave + } else { + # empty argument: generate slave name + set slave [::interp create -safe] + } + Log $slave "Created" NOTICE + + # Initialize it. (returns slave name) + InterpInit $slave $access_path $staticsok $nestedok $deletehook +} + +# +# InterpSetConfig (was setAccessPath) : +# Sets up slave virtual auto_path and corresponding structure within +# the master. Also sets the tcl_library in the slave to be the first +# directory in the path. +# NB: If you change the path after the slave has been initialized you +# probably need to call "auto_reset" in the slave in order that it gets +# the right auto_index() array values. + +proc ::safe::InterpSetConfig {slave access_path staticsok nestedok deletehook} { + global auto_path + + # determine and store the access path if empty + if {$access_path eq ""} { + set access_path $auto_path + + # Make sure that tcl_library is in auto_path and at the first + # position (needed by setAccessPath) + set where [lsearch -exact $access_path [info library]] + if {$where == -1} { + # not found, add it. + set access_path [linsert $access_path 0 [info library]] + Log $slave "tcl_library was not in auto_path,\ + added it to slave's access_path" NOTICE + } elseif {$where != 0} { + # not first, move it first + set access_path [linsert \ + [lreplace $access_path $where $where] \ + 0 [info library]] + Log $slave "tcl_libray was not in first in auto_path,\ + moved it to front of slave's access_path" NOTICE + } + + # Add 1st level sub dirs (will searched by auto loading from tcl + # code in the slave using glob and thus fail, so we add them here + # so by default it works the same). + set access_path [AddSubDirs $access_path] + } + + Log $slave "Setting accessPath=($access_path) staticsok=$staticsok\ + nestedok=$nestedok deletehook=($deletehook)" NOTICE + + namespace upvar ::safe S$slave state + + # clear old autopath if it existed + # build new one + # Extend the access list with the paths used to look for Tcl Modules. + # We save the virtual form separately as well, as syncing it with the + # slave has to be defered until the necessary commands are present for + # setup. + + set norm_access_path {} + set slave_access_path {} + set map_access_path {} + set remap_access_path {} + set slave_tm_path {} + + set i 0 + foreach dir $access_path { + set token [PathToken $i] + lappend slave_access_path $token + lappend map_access_path $token $dir + lappend remap_access_path $dir $token + lappend norm_access_path [file normalize $dir] + incr i + } + + set morepaths [::tcl::tm::list] + while {[llength $morepaths]} { + set addpaths $morepaths + set morepaths {} + + foreach dir $addpaths { + # Prevent the addition of dirs on the tm list to the + # result if they are already known. + if {[dict exists $remap_access_path $dir]} { + continue + } + + set token [PathToken $i] + lappend access_path $dir + lappend slave_access_path $token + lappend map_access_path $token $dir + lappend remap_access_path $dir $token + lappend norm_access_path [file normalize $dir] + lappend slave_tm_path $token + incr i + + # [Bug 2854929] + # Recursively find deeper paths which may contain + # modules. Required to handle modules with names like + # 'platform::shell', which translate into + # 'platform/shell-X.tm', i.e arbitrarily deep + # subdirectories. + lappend morepaths {*}[glob -nocomplain -directory $dir -type d *] + } + } + + set state(access_path) $access_path + set state(access_path,map) $map_access_path + set state(access_path,remap) $remap_access_path + set state(access_path,norm) $norm_access_path + set state(access_path,slave) $slave_access_path + set state(tm_path_slave) $slave_tm_path + set state(staticsok) $staticsok + set state(nestedok) $nestedok + set state(cleanupHook) $deletehook + + SyncAccessPath $slave +} + +# +# +# FindInAccessPath: +# Search for a real directory and returns its virtual Id (including the +# "$") +proc ::safe::interpFindInAccessPath {slave path} { + namespace upvar ::safe S$slave state + + if {![dict exists $state(access_path,remap) $path]} { + return -code error "$path not found in access path $access_path" + } + + return [dict get $state(access_path,remap) $path] +} + +# +# addToAccessPath: +# add (if needed) a real directory to access path and return its +# virtual token (including the "$"). +proc ::safe::interpAddToAccessPath {slave path} { + # first check if the directory is already in there + # (inlined interpFindInAccessPath). + namespace upvar ::safe S$slave state + + if {[dict exists $state(access_path,remap) $path]} { + return [dict get $state(access_path,remap) $path] + } + + # new one, add it: + set token [PathToken [llength $state(access_path)]] + + lappend state(access_path) $path + lappend state(access_path,slave) $token + lappend state(access_path,map) $token $path + lappend state(access_path,remap) $path $token + lappend state(access_path,norm) [file normalize $path] + + SyncAccessPath $slave + return $token +} + +# This procedure applies the initializations to an already existing +# interpreter. It is useful when you want to install the safe base aliases +# into a preexisting safe interpreter. +proc ::safe::InterpInit { + slave + access_path + staticsok + nestedok + deletehook + } { + # Configure will generate an access_path when access_path is empty. + InterpSetConfig $slave $access_path $staticsok $nestedok $deletehook + + # NB we need to add [namespace current], aliases are always absolute + # paths. + + # These aliases let the slave load files to define new commands + # This alias lets the slave use the encoding names, convertfrom, + # convertto, and system, but not "encoding system " to set the + # system encoding. + # Handling Tcl Modules, we need a restricted form of Glob. + # This alias interposes on the 'exit' command and cleanly terminates + # the slave. + + foreach {command alias} { + source AliasSource + load AliasLoad + encoding AliasEncoding + exit interpDelete + glob AliasGlob + } { + ::interp alias $slave $command {} [namespace current]::$alias $slave + } + + # This alias lets the slave have access to a subset of the 'file' + # command functionality. + + ::interp expose $slave file + foreach subcommand {dirname extension rootname tail} { + ::interp alias $slave ::tcl::file::$subcommand {} \ + ::safe::AliasFileSubcommand $slave $subcommand + } + foreach subcommand { + atime attributes copy delete executable exists isdirectory isfile + link lstat mtime mkdir nativename normalize owned readable readlink + rename size stat tempfile type volumes writable + } { + ::interp alias $slave ::tcl::file::$subcommand {} \ + ::safe::BadSubcommand $slave file $subcommand + } + + # Subcommands of info + foreach {subcommand alias} { + nameofexecutable AliasExeName + } { + ::interp alias $slave ::tcl::info::$subcommand \ + {} [namespace current]::$alias $slave + } + + # The allowed slave variables already have been set by Tcl_MakeSafe(3) + + # Source init.tcl and tm.tcl into the slave, to get auto_load and + # other procedures defined: + + if {[catch {::interp eval $slave { + source [file join $tcl_library init.tcl] + }} msg opt]} { + Log $slave "can't source init.tcl ($msg)" + return -options $opt "can't source init.tcl into slave $slave ($msg)" + } + + if {[catch {::interp eval $slave { + source [file join $tcl_library tm.tcl] + }} msg opt]} { + Log $slave "can't source tm.tcl ($msg)" + return -options $opt "can't source tm.tcl into slave $slave ($msg)" + } + + # Sync the paths used to search for Tcl modules. This can be done only + # now, after tm.tcl was loaded. + namespace upvar ::safe S$slave state + if {[llength $state(tm_path_slave)] > 0} { + ::interp eval $slave [list \ + ::tcl::tm::add {*}[lreverse $state(tm_path_slave)]] + } + return $slave +} + +# Add (only if needed, avoid duplicates) 1 level of sub directories to an +# existing path list. Also removes non directories from the returned +# list. +proc ::safe::AddSubDirs {pathList} { + set res {} + foreach dir $pathList { + if {[file isdirectory $dir]} { + # check that we don't have it yet as a children of a previous + # dir + if {$dir ni $res} { + lappend res $dir + } + foreach sub [glob -directory $dir -nocomplain *] { + if {[file isdirectory $sub] && ($sub ni $res)} { + # new sub dir, add it ! + lappend res $sub + } + } + } + } + return $res +} + +# This procedure deletes a safe slave managed by Safe Tcl and cleans up +# associated state: + +proc ::safe::interpDelete {slave} { + Log $slave "About to delete" NOTICE + + namespace upvar ::safe S$slave state + + # If the slave has a cleanup hook registered, call it. Check the + # existance because we might be called to delete an interp which has + # not been registered with us at all + + if {[info exists state(cleanupHook)]} { + set hook $state(cleanupHook) + if {[llength $hook]} { + # remove the hook now, otherwise if the hook calls us somehow, + # we'll loop + unset state(cleanupHook) + try { + {*}$hook $slave + } on error err { + Log $slave "Delete hook error ($err)" + } + } + } + + # Discard the global array of state associated with the slave, and + # delete the interpreter. + + if {[info exists state]} { + unset state + } + + # if we have been called twice, the interp might have been deleted + # already + if {[::interp exists $slave]} { + ::interp delete $slave + Log $slave "Deleted" NOTICE + } + + return +} + +# Set (or get) the logging mecanism + +proc ::safe::setLogCmd {args} { + variable Log + set la [llength $args] + if {$la == 0} { + return $Log + } elseif {$la == 1} { + set Log [lindex $args 0] + } else { + set Log $args + } + + if {$Log eq ""} { + # Disable logging completely. Calls to it will be compiled out + # of all users. + proc ::safe::Log {args} {} + } else { + # Activate logging, define proper command. + + proc ::safe::Log {slave msg {type ERROR}} { + variable Log + {*}$Log "$type for slave $slave : $msg" + return + } + } +} + +# ------------------- END OF PUBLIC METHODS ------------ + +# +# Sets the slave auto_path to the master recorded value. Also sets +# tcl_library to the first token of the virtual path. +# +proc ::safe::SyncAccessPath {slave} { + namespace upvar ::safe S$slave state + + set slave_access_path $state(access_path,slave) + ::interp eval $slave [list set auto_path $slave_access_path] + + Log $slave "auto_path in $slave has been set to $slave_access_path"\ + NOTICE + + # This code assumes that info library is the first element in the + # list of auto_path's. See -> InterpSetConfig for the code which + # ensures this condition. + + ::interp eval $slave [list \ + set tcl_library [lindex $slave_access_path 0]] +} + +# Returns the virtual token for directory number N. +proc ::safe::PathToken {n} { + # We need to have a ":" in the token string so [file join] on the + # mac won't turn it into a relative path. + return "\$p(:$n:)" ;# Form tested by case 7.2 +} + +# +# translate virtual path into real path +# +proc ::safe::TranslatePath {slave path} { + namespace upvar ::safe S$slave state + + # somehow strip the namespaces 'functionality' out (the danger is that + # we would strip valid macintosh "../" queries... : + if {[string match "*::*" $path] || [string match "*..*" $path]} { + return -code error "invalid characters in path $path" + } + + # Use a cached map instead of computed local vars and subst. + + return [string map $state(access_path,map) $path] +} + +# file name control (limit access to files/resources that should be a +# valid tcl source file) +proc ::safe::CheckFileName {slave file} { + # This used to limit what can be sourced to ".tcl" and forbid files + # with more than 1 dot and longer than 14 chars, but I changed that + # for 8.4 as a safe interp has enough internal protection already to + # allow sourcing anything. - hobbs + + if {![file exists $file]} { + # don't tell the file path + return -code error "no such file or directory" + } + + if {![file readable $file]} { + # don't tell the file path + return -code error "not readable" + } +} + +# AliasFileSubcommand handles selected subcommands of [file] in safe +# interpreters that are *almost* safe. In particular, it just acts to +# prevent discovery of what home directories exist. + +proc ::safe::AliasFileSubcommand {slave subcommand name} { + if {[string match ~* $name]} { + set name ./$name + } + tailcall ::interp invokehidden $slave tcl:file:$subcommand $name +} + +# AliasGlob is the target of the "glob" alias in safe interpreters. + +proc ::safe::AliasGlob {slave args} { + Log $slave "GLOB ! $args" NOTICE + set cmd {} + set at 0 + array set got { + -directory 0 + -nocomplain 0 + -join 0 + -tails 0 + -- 0 + } + + if {$::tcl_platform(platform) eq "windows"} { + set dirPartRE {^(.*)[\\/]([^\\/]*)$} + } else { + set dirPartRE {^(.*)/([^/]*)$} + } + + set dir {} + set virtualdir {} + + while {$at < [llength $args]} { + switch -glob -- [set opt [lindex $args $at]] { + -nocomplain - -- - -join - -tails { + lappend cmd $opt + set got($opt) 1 + incr at + } + -types - -type { + lappend cmd -types [lindex $args [incr at]] + incr at + } + -directory { + if {$got($opt)} { + return -code error \ + {"-directory" cannot be used with "-path"} + } + set got($opt) 1 + set virtualdir [lindex $args [incr at]] + incr at + } + pkgIndex.tcl { + # Oops, this is globbing a subdirectory in regular package + # search. That is not wanted. Abort, handler does catch + # already (because glob was not defined before). See + # package.tcl, lines 484ff in tclPkgUnknown. + return -code error "unknown command glob" + } + -* { + Log $slave "Safe base rejecting glob option '$opt'" + return -code error "Safe base rejecting glob option '$opt'" + } + default { + break + } + } + if {$got(--)} break + } + + # Get the real path from the virtual one and check that the path is in the + # access path of that slave. Done after basic argument processing so that + # we know if -nocomplain is set. + if {$got(-directory)} { + try { + set dir [TranslatePath $slave $virtualdir] + DirInAccessPath $slave $dir + } on error msg { + Log $slave $msg + if {$got(-nocomplain)} return + return -code error "permission denied" + } + lappend cmd -directory $dir + } + + # Apply the -join semantics ourselves + if {$got(-join)} { + set args [lreplace $args $at end [join [lrange $args $at end] "/"]] + } + + # Process remaining pattern arguments + set firstPattern [llength $cmd] + foreach opt [lrange $args $at end] { + if {![regexp $dirPartRE $opt -> thedir thefile]} { + set thedir . + } elseif {[string match ~* $thedir]} { + set thedir ./$thedir + } + if {$thedir eq "*" && + ($thefile eq "pkgIndex.tcl" || $thefile eq "*.tm")} { + set mapped 0 + foreach d [glob -directory [TranslatePath $slave $virtualdir] \ + -types d -tails *] { + catch { + DirInAccessPath $slave \ + [TranslatePath $slave [file join $virtualdir $d]] + lappend cmd [file join $d $thefile] + set mapped 1 + } + } + if {$mapped} continue + } + try { + DirInAccessPath $slave [TranslatePath $slave \ + [file join $virtualdir $thedir]] + } on error msg { + Log $slave $msg + if {$got(-nocomplain)} continue + return -code error "permission denied" + } + lappend cmd $opt + } + + Log $slave "GLOB = $cmd" NOTICE + + if {$got(-nocomplain) && [llength $cmd] eq $firstPattern} { + return + } + try { + set entries [::interp invokehidden $slave glob {*}$cmd] + } on error msg { + Log $slave $msg + return -code error "script error" + } + + Log $slave "GLOB < $entries" NOTICE + + # Translate path back to what the slave should see. + set res {} + set l [string length $dir] + foreach p $entries { + if {[string equal -length $l $dir $p]} { + set p [string replace $p 0 [expr {$l-1}] $virtualdir] + } + lappend res $p + } + + Log $slave "GLOB > $res" NOTICE + return $res +} + +# AliasSource is the target of the "source" alias in safe interpreters. + +proc ::safe::AliasSource {slave args} { + set argc [llength $args] + # Extended for handling of Tcl Modules to allow not only "source + # filename", but "source -encoding E filename" as well. + if {[lindex $args 0] eq "-encoding"} { + incr argc -2 + set encoding [lindex $args 1] + set at 2 + if {$encoding eq "identity"} { + Log $slave "attempt to use the identity encoding" + return -code error "permission denied" + } + } else { + set at 0 + set encoding {} + } + if {$argc != 1} { + set msg "wrong # args: should be \"source ?-encoding E? fileName\"" + Log $slave "$msg ($args)" + return -code error $msg + } + set file [lindex $args $at] + + # get the real path from the virtual one. + if {[catch { + set realfile [TranslatePath $slave $file] + } msg]} { + Log $slave $msg + return -code error "permission denied" + } + + # check that the path is in the access path of that slave + if {[catch { + FileInAccessPath $slave $realfile + } msg]} { + Log $slave $msg + return -code error "permission denied" + } + + # do the checks on the filename : + if {[catch { + CheckFileName $slave $realfile + } msg]} { + Log $slave "$realfile:$msg" + return -code error $msg + } + + # Passed all the tests, lets source it. Note that we do this all manually + # because we want to control [info script] in the slave so information + # doesn't leak so much. [Bug 2913625] + set old [::interp eval $slave {info script}] + set replacementMsg "script error" + set code [catch { + set f [open $realfile] + fconfigure $f -eofchar \032 + if {$encoding ne ""} { + fconfigure $f -encoding $encoding + } + set contents [read $f] + close $f + ::interp eval $slave [list info script $file] + } msg opt] + if {$code == 0} { + set code [catch {::interp eval $slave $contents} msg opt] + set replacementMsg $msg + } + catch {interp eval $slave [list info script $old]} + # Note that all non-errors are fine result codes from [source], so we must + # take a little care to do it properly. [Bug 2923613] + if {$code == 1} { + Log $slave $msg + return -code error $replacementMsg + } + return -code $code -options $opt $msg +} + +# AliasLoad is the target of the "load" alias in safe interpreters. + +proc ::safe::AliasLoad {slave file args} { + set argc [llength $args] + if {$argc > 2} { + set msg "load error: too many arguments" + Log $slave "$msg ($argc) {$file $args}" + return -code error $msg + } + + # package name (can be empty if file is not). + set package [lindex $args 0] + + namespace upvar ::safe S$slave state + + # Determine where to load. load use a relative interp path and {} + # means self, so we can directly and safely use passed arg. + set target [lindex $args 1] + if {$target ne ""} { + # we will try to load into a sub sub interp; check that we want to + # authorize that. + if {!$state(nestedok)} { + Log $slave "loading to a sub interp (nestedok)\ + disabled (trying to load $package to $target)" + return -code error "permission denied (nested load)" + } + } + + # Determine what kind of load is requested + if {$file eq ""} { + # static package loading + if {$package eq ""} { + set msg "load error: empty filename and no package name" + Log $slave $msg + return -code error $msg + } + if {!$state(staticsok)} { + Log $slave "static packages loading disabled\ + (trying to load $package to $target)" + return -code error "permission denied (static package)" + } + } else { + # file loading + + # get the real path from the virtual one. + try { + set file [TranslatePath $slave $file] + } on error msg { + Log $slave $msg + return -code error "permission denied" + } + + # check the translated path + try { + FileInAccessPath $slave $file + } on error msg { + Log $slave $msg + return -code error "permission denied (path)" + } + } + + try { + return [::interp invokehidden $slave load $file $package $target] + } on error msg { + Log $slave $msg + return -code error $msg + } +} + +# FileInAccessPath raises an error if the file is not found in the list of +# directories contained in the (master side recorded) slave's access path. + +# the security here relies on "file dirname" answering the proper +# result... needs checking ? +proc ::safe::FileInAccessPath {slave file} { + namespace upvar ::safe S$slave state + set access_path $state(access_path) + + if {[file isdirectory $file]} { + return -code error "\"$file\": is a directory" + } + set parent [file dirname $file] + + # Normalize paths for comparison since lsearch knows nothing of + # potential pathname anomalies. + set norm_parent [file normalize $parent] + + namespace upvar ::safe S$slave state + if {$norm_parent ni $state(access_path,norm)} { + return -code error "\"$file\": not in access_path" + } +} + +proc ::safe::DirInAccessPath {slave dir} { + namespace upvar ::safe S$slave state + set access_path $state(access_path) + + if {[file isfile $dir]} { + return -code error "\"$dir\": is a file" + } + + # Normalize paths for comparison since lsearch knows nothing of + # potential pathname anomalies. + set norm_dir [file normalize $dir] + + namespace upvar ::safe S$slave state + if {$norm_dir ni $state(access_path,norm)} { + return -code error "\"$dir\": not in access_path" + } +} + +# This procedure is used to report an attempt to use an unsafe member of an +# ensemble command. + +proc ::safe::BadSubcommand {slave command subcommand args} { + set msg "not allowed to invoke subcommand $subcommand of $command" + Log $slave $msg + return -code error -errorcode {TCL SAFE SUBCOMMAND} $msg +} + +# AliasEncoding is the target of the "encoding" alias in safe interpreters. + +proc ::safe::AliasEncoding {slave option args} { + # Note that [encoding dirs] is not supported in safe slaves at all + set subcommands {convertfrom convertto names system} + try { + set option [tcl::prefix match -error [list -level 1 -errorcode \ + [list TCL LOOKUP INDEX option $option]] $subcommands $option] + # Special case: [encoding system] ok, but [encoding system foo] not + if {$option eq "system" && [llength $args]} { + return -code error -errorcode {TCL WRONGARGS} \ + "wrong # args: should be \"encoding system\"" + } + } on error {msg options} { + Log $slave $msg + return -options $options $msg + } + tailcall ::interp invokehidden $slave encoding $option {*}$args +} + +# Various minor hiding of platform features. [Bug 2913625] + +proc ::safe::AliasExeName {slave} { + return "" +} + +proc ::safe::Setup {} { + #### + # + # Setup the arguments parsing + # + #### + + # Share the descriptions + set temp [::tcl::OptKeyRegister { + {-accessPath -list {} "access path for the slave"} + {-noStatics "prevent loading of statically linked pkgs"} + {-statics true "loading of statically linked pkgs"} + {-nestedLoadOk "allow nested loading"} + {-nested false "nested loading"} + {-deleteHook -script {} "delete hook"} + }] + + # create case (slave is optional) + ::tcl::OptKeyRegister { + {?slave? -name {} "name of the slave (optional)"} + } ::safe::interpCreate + + # adding the flags sub programs to the command program (relying on Opt's + # internal implementation details) + lappend ::tcl::OptDesc(::safe::interpCreate) $::tcl::OptDesc($temp) + + # init and configure (slave is needed) + ::tcl::OptKeyRegister { + {slave -name {} "name of the slave"} + } ::safe::interpIC + + # adding the flags sub programs to the command program (relying on Opt's + # internal implementation details) + lappend ::tcl::OptDesc(::safe::interpIC) $::tcl::OptDesc($temp) + + # temp not needed anymore + ::tcl::OptKeyDelete $temp + + #### + # + # Default: No logging. + # + #### + + setLogCmd {} + + # Log eventually. + # To enable error logging, set Log to {puts stderr} for instance, + # via setLogCmd. + return +} + +namespace eval ::safe { + # internal variables + + # Log command, set via 'setLogCmd'. Logging is disabled when empty. + variable Log {} + + # The package maintains a state array per slave interp under its + # control. The name of this array is S. This array is + # brought into scope where needed, using 'namespace upvar'. The S + # prefix is used to avoid that a slave interp called "Log" smashes + # the "Log" variable. + # + # The array's elements are: + # + # access_path : List of paths accessible to the slave. + # access_path,norm : Ditto, in normalized form. + # access_path,slave : Ditto, as the path tokens as seen by the slave. + # access_path,map : dict ( token -> path ) + # access_path,remap : dict ( path -> token ) + # tm_path_slave : List of TM root directories, as tokens seen by the slave. + # staticsok : Value of option -statics + # nestedok : Value of option -nested + # cleanupHook : Value of option -deleteHook +} + +::safe::Setup diff --git a/windowsAgent/dist/tcl/tclIndex b/windowsAgent/dist/tcl/tclIndex new file mode 100644 index 0000000..26603c1 --- /dev/null +++ b/windowsAgent/dist/tcl/tclIndex @@ -0,0 +1,75 @@ +# Tcl autoload index file, version 2.0 +# -*- tcl -*- +# This file is generated by the "auto_mkindex" command +# and sourced to set up indexing information for one or +# more commands. Typically each line is a command that +# sets an element in the auto_index array, where the +# element name is the name of a command and the value is +# a script that loads the command. + +set auto_index(auto_reset) [list source [file join $dir auto.tcl]] +set auto_index(tcl_findLibrary) [list source [file join $dir auto.tcl]] +set auto_index(auto_mkindex) [list source [file join $dir auto.tcl]] +set auto_index(auto_mkindex_old) [list source [file join $dir auto.tcl]] +set auto_index(::auto_mkindex_parser::init) [list source [file join $dir auto.tcl]] +set auto_index(::auto_mkindex_parser::cleanup) [list source [file join $dir auto.tcl]] +set auto_index(::auto_mkindex_parser::mkindex) [list source [file join $dir auto.tcl]] +set auto_index(::auto_mkindex_parser::hook) [list source [file join $dir auto.tcl]] +set auto_index(::auto_mkindex_parser::slavehook) [list source [file join $dir auto.tcl]] +set auto_index(::auto_mkindex_parser::command) [list source [file join $dir auto.tcl]] +set auto_index(::auto_mkindex_parser::commandInit) [list source [file join $dir auto.tcl]] +set auto_index(::auto_mkindex_parser::fullname) [list source [file join $dir auto.tcl]] +set auto_index(history) [list source [file join $dir history.tcl]] +set auto_index(::tcl::HistAdd) [list source [file join $dir history.tcl]] +set auto_index(::tcl::HistKeep) [list source [file join $dir history.tcl]] +set auto_index(::tcl::HistClear) [list source [file join $dir history.tcl]] +set auto_index(::tcl::HistInfo) [list source [file join $dir history.tcl]] +set auto_index(::tcl::HistRedo) [list source [file join $dir history.tcl]] +set auto_index(::tcl::HistIndex) [list source [file join $dir history.tcl]] +set auto_index(::tcl::HistEvent) [list source [file join $dir history.tcl]] +set auto_index(::tcl::HistChange) [list source [file join $dir history.tcl]] +set auto_index(pkg_mkIndex) [list source [file join $dir package.tcl]] +set auto_index(tclPkgSetup) [list source [file join $dir package.tcl]] +set auto_index(tclPkgUnknown) [list source [file join $dir package.tcl]] +set auto_index(::tcl::MacOSXPkgUnknown) [list source [file join $dir package.tcl]] +set auto_index(::pkg::create) [list source [file join $dir package.tcl]] +set auto_index(parray) [list source [file join $dir parray.tcl]] +set auto_index(::safe::InterpStatics) [list source [file join $dir safe.tcl]] +set auto_index(::safe::InterpNested) [list source [file join $dir safe.tcl]] +set auto_index(::safe::interpCreate) [list source [file join $dir safe.tcl]] +set auto_index(::safe::interpInit) [list source [file join $dir safe.tcl]] +set auto_index(::safe::CheckInterp) [list source [file join $dir safe.tcl]] +set auto_index(::safe::interpConfigure) [list source [file join $dir safe.tcl]] +set auto_index(::safe::InterpCreate) [list source [file join $dir safe.tcl]] +set auto_index(::safe::InterpSetConfig) [list source [file join $dir safe.tcl]] +set auto_index(::safe::interpFindInAccessPath) [list source [file join $dir safe.tcl]] +set auto_index(::safe::interpAddToAccessPath) [list source [file join $dir safe.tcl]] +set auto_index(::safe::InterpInit) [list source [file join $dir safe.tcl]] +set auto_index(::safe::AddSubDirs) [list source [file join $dir safe.tcl]] +set auto_index(::safe::interpDelete) [list source [file join $dir safe.tcl]] +set auto_index(::safe::setLogCmd) [list source [file join $dir safe.tcl]] +set auto_index(::safe::SyncAccessPath) [list source [file join $dir safe.tcl]] +set auto_index(::safe::PathToken) [list source [file join $dir safe.tcl]] +set auto_index(::safe::TranslatePath) [list source [file join $dir safe.tcl]] +set auto_index(::safe::Log) [list source [file join $dir safe.tcl]] +set auto_index(::safe::CheckFileName) [list source [file join $dir safe.tcl]] +set auto_index(::safe::AliasGlob) [list source [file join $dir safe.tcl]] +set auto_index(::safe::AliasSource) [list source [file join $dir safe.tcl]] +set auto_index(::safe::AliasLoad) [list source [file join $dir safe.tcl]] +set auto_index(::safe::FileInAccessPath) [list source [file join $dir safe.tcl]] +set auto_index(::safe::DirInAccessPath) [list source [file join $dir safe.tcl]] +set auto_index(::safe::Subset) [list source [file join $dir safe.tcl]] +set auto_index(::safe::AliasSubset) [list source [file join $dir safe.tcl]] +set auto_index(::safe::AliasEncoding) [list source [file join $dir safe.tcl]] +set auto_index(tcl_wordBreakAfter) [list source [file join $dir word.tcl]] +set auto_index(tcl_wordBreakBefore) [list source [file join $dir word.tcl]] +set auto_index(tcl_endOfWord) [list source [file join $dir word.tcl]] +set auto_index(tcl_startOfNextWord) [list source [file join $dir word.tcl]] +set auto_index(tcl_startOfPreviousWord) [list source [file join $dir word.tcl]] +set auto_index(::tcl::tm::add) [list source [file join $dir tm.tcl]] +set auto_index(::tcl::tm::remove) [list source [file join $dir tm.tcl]] +set auto_index(::tcl::tm::list) [list source [file join $dir tm.tcl]] +set auto_index(::tcl::tm::Defaults) [list source [file join $dir tm.tcl]] +set auto_index(::tcl::tm::UnknownHandler) [list source [file join $dir tm.tcl]] +set auto_index(::tcl::tm::roots) [list source [file join $dir tm.tcl]] +set auto_index(::tcl::tm::path) [list source [file join $dir tm.tcl]] diff --git a/windowsAgent/dist/tcl/tm.tcl b/windowsAgent/dist/tcl/tm.tcl new file mode 100644 index 0000000..66c56a1 --- /dev/null +++ b/windowsAgent/dist/tcl/tm.tcl @@ -0,0 +1,375 @@ +# -*- tcl -*- +# +# Searching for Tcl Modules. Defines a procedure, declares it as the primary +# command for finding packages, however also uses the former 'package unknown' +# command as a fallback. +# +# Locates all possible packages in a directory via a less restricted glob. The +# targeted directory is derived from the name of the requested package, i.e. +# the TM scan will look only at directories which can contain the requested +# package. It will register all packages it found in the directory so that +# future requests have a higher chance of being fulfilled by the ifneeded +# database without having to come to us again. +# +# We do not remember where we have been and simply rescan targeted directories +# when invoked again. The reasoning is this: +# +# - The only way we get back to the same directory is if someone is trying to +# [package require] something that wasn't there on the first scan. +# +# Either +# 1) It is there now: If we rescan, you get it; if not you don't. +# +# This covers the possibility that the application asked for a package +# late, and the package was actually added to the installation after the +# application was started. It shoukld still be able to find it. +# +# 2) It still is not there: Either way, you don't get it, but the rescan +# takes time. This is however an error case and we dont't care that much +# about it +# +# 3) It was there the first time; but for some reason a "package forget" has +# been run, and "package" doesn't know about it anymore. +# +# This can be an indication that the application wishes to reload some +# functionality. And should work as well. +# +# Note that this also strikes a balance between doing a glob targeting a +# single package, and thus most likely requiring multiple globs of the same +# directory when the application is asking for many packages, and trying to +# glob for _everything_ in all subdirectories when looking for a package, +# which comes with a heavy startup cost. +# +# We scan for regular packages only if no satisfying module was found. + +namespace eval ::tcl::tm { + # Default paths. None yet. + + variable paths {} + + # The regex pattern a file name has to match to make it a Tcl Module. + + set pkgpattern {^([_[:alpha:]][:_[:alnum:]]*)-([[:digit:]].*)[.]tm$} + + # Export the public API + + namespace export path + namespace ensemble create -command path -subcommands {add remove list} +} + +# ::tcl::tm::path implementations -- +# +# Public API to the module path. See specification. +# +# Arguments +# cmd - The subcommand to execute +# args - The paths to add/remove. Must not appear querying the +# path with 'list'. +# +# Results +# No result for subcommands 'add' and 'remove'. A list of paths for +# 'list'. +# +# Sideeffects +# The subcommands 'add' and 'remove' manipulate the list of paths to +# search for Tcl Modules. The subcommand 'list' has no sideeffects. + +proc ::tcl::tm::add {args} { + # PART OF THE ::tcl::tm::path ENSEMBLE + # + # The path is added at the head to the list of module paths. + # + # The command enforces the restriction that no path may be an ancestor + # directory of any other path on the list. If the new path violates this + # restriction an error wil be raised. + # + # If the path is already present as is no error will be raised and no + # action will be taken. + + variable paths + + # We use a copy of the path as source during validation, and extend it as + # well. Because we not only have to detect if the new paths are bogus with + # respect to the existing paths, but also between themselves. Otherwise we + # can still add bogus paths, by specifying them in a single call. This + # makes the use of the new paths simpler as well, a trivial assignment of + # the collected paths to the official state var. + + set newpaths $paths + foreach p $args { + if {$p in $newpaths} { + # Ignore a path already on the list. + continue + } + + # Search for paths which are subdirectories of the new one. If there + # are any then the new path violates the restriction about ancestors. + + set pos [lsearch -glob $newpaths ${p}/*] + # Cannot use "in", we need the position for the message. + if {$pos >= 0} { + return -code error \ + "$p is ancestor of existing module path [lindex $newpaths $pos]." + } + + # Now look for existing paths which are ancestors of the new one. This + # reverse question forces us to loop over the existing paths, as each + # element is the pattern, not the new path :( + + foreach ep $newpaths { + if {[string match ${ep}/* $p]} { + return -code error \ + "$p is subdirectory of existing module path $ep." + } + } + + set newpaths [linsert $newpaths 0 $p] + } + + # The validation of the input is complete and successful, and everything + # in newpaths is either an old path, or added. We can now extend the + # official list of paths, a simple assignment is sufficient. + + set paths $newpaths + return +} + +proc ::tcl::tm::remove {args} { + # PART OF THE ::tcl::tm::path ENSEMBLE + # + # Removes the path from the list of module paths. The command is silently + # ignored if the path is not on the list. + + variable paths + + foreach p $args { + set pos [lsearch -exact $paths $p] + if {$pos >= 0} { + set paths [lreplace $paths $pos $pos] + } + } +} + +proc ::tcl::tm::list {} { + # PART OF THE ::tcl::tm::path ENSEMBLE + + variable paths + return $paths +} + +# ::tcl::tm::UnknownHandler -- +# +# Unknown handler for Tcl Modules, i.e. packages in module form. +# +# Arguments +# original - Original [package unknown] procedure. +# name - Name of desired package. +# version - Version of desired package. Can be the +# empty string. +# exact - Either -exact or ommitted. +# +# Name, version, and exact are used to determine satisfaction. The +# original is called iff no satisfaction was achieved. The name is also +# used to compute the directory to target in the search. +# +# Results +# None. +# +# Sideeffects +# May populate the package ifneeded database with additional provide +# scripts. + +proc ::tcl::tm::UnknownHandler {original name args} { + # Import the list of paths to search for packages in module form. + # Import the pattern used to check package names in detail. + + variable paths + variable pkgpattern + + # Without paths to search we can do nothing. (Except falling back to the + # regular search). + + if {[llength $paths]} { + set pkgpath [string map {:: /} $name] + set pkgroot [file dirname $pkgpath] + if {$pkgroot eq "."} { + set pkgroot "" + } + + # We don't remember a copy of the paths while looping. Tcl Modules are + # unable to change the list while we are searching for them. This also + # simplifies the loop, as we cannot get additional directories while + # iterating over the list. A simple foreach is sufficient. + + set satisfied 0 + foreach path $paths { + if {![interp issafe] && ![file exists $path]} { + continue + } + set currentsearchpath [file join $path $pkgroot] + if {![interp issafe] && ![file exists $currentsearchpath]} { + continue + } + set strip [llength [file split $path]] + + # We can't use glob in safe interps, so enclose the following in a + # catch statement, where we get the module files out of the + # subdirectories. In other words, Tcl Modules are not-functional + # in such an interpreter. This is the same as for the command + # "tclPkgUnknown", i.e. the search for regular packages. + + catch { + # We always look for _all_ possible modules in the current + # path, to get the max result out of the glob. + + foreach file [glob -nocomplain -directory $currentsearchpath *.tm] { + set pkgfilename [join [lrange [file split $file] $strip end] ::] + + if {![regexp -- $pkgpattern $pkgfilename --> pkgname pkgversion]} { + # Ignore everything not matching our pattern for + # package names. + continue + } + try { + package vcompare $pkgversion 0 + } on error {} { + # Ignore everything where the version part is not + # acceptable to "package vcompare". + continue + } + + if {[package ifneeded $pkgname $pkgversion] ne {}} { + # There's already a provide script registered for + # this version of this package. Since all units of + # code claiming to be the same version of the same + # package ought to be identical, just stick with + # the one we already have. + continue + } + + # We have found a candidate, generate a "provide script" + # for it, and remember it. Note that we are using ::list + # to do this; locally [list] means something else without + # the namespace specifier. + + # NOTE. When making changes to the format of the provide + # command generated below CHECK that the 'LOCATE' + # procedure in core file 'platform/shell.tcl' still + # understands it, or, if not, update its implementation + # appropriately. + # + # Right now LOCATE's implementation assumes that the path + # of the package file is the last element in the list. + + package ifneeded $pkgname $pkgversion \ + "[::list package provide $pkgname $pkgversion];[::list source -encoding utf-8 $file]" + + # We abort in this unknown handler only if we got a + # satisfying candidate for the requested package. + # Otherwise we still have to fallback to the regular + # package search to complete the processing. + + if {($pkgname eq $name) + && [package vsatisfies $pkgversion {*}$args]} { + set satisfied 1 + + # We do not abort the loop, and keep adding provide + # scripts for every candidate in the directory, just + # remember to not fall back to the regular search + # anymore. + } + } + } + } + + if {$satisfied} { + return + } + } + + # Fallback to previous command, if existing. See comment above about + # ::list... + + if {[llength $original]} { + uplevel 1 $original [::linsert $args 0 $name] + } +} + +# ::tcl::tm::Defaults -- +# +# Determines the default search paths. +# +# Arguments +# None +# +# Results +# None. +# +# Sideeffects +# May add paths to the list of defaults. + +proc ::tcl::tm::Defaults {} { + global env tcl_platform + + lassign [split [info tclversion] .] major minor + set exe [file normalize [info nameofexecutable]] + + # Note that we're using [::list], not [list] because [list] means + # something other than [::list] in this namespace. + roots [::list \ + [file dirname [info library]] \ + [file join [file dirname [file dirname $exe]] lib] \ + ] + + if {$tcl_platform(platform) eq "windows"} { + set sep ";" + } else { + set sep ":" + } + for {set n $minor} {$n >= 0} {incr n -1} { + foreach ev [::list \ + TCL${major}.${n}_TM_PATH \ + TCL${major}_${n}_TM_PATH \ + ] { + if {![info exists env($ev)]} continue + foreach p [split $env($ev) $sep] { + path add $p + } + } + } + return +} + +# ::tcl::tm::roots -- +# +# Public API to the module path. See specification. +# +# Arguments +# paths - List of 'root' paths to derive search paths from. +# +# Results +# No result. +# +# Sideeffects +# Calls 'path add' to paths to the list of module search paths. + +proc ::tcl::tm::roots {paths} { + regexp {^(\d+)\.(\d+)} [package present Tcl] - major minor + foreach pa $paths { + set p [file join $pa tcl$major] + for {set n $minor} {$n >= 0} {incr n -1} { + set px [file join $p ${major}.${n}] + if {![interp issafe]} {set px [file normalize $px]} + path add $px + } + set px [file join $p site-tcl] + if {![interp issafe]} {set px [file normalize $px]} + path add $px + } + return +} + +# Initialization. Set up the default paths, then insert the new handler into +# the chain. + +if {![interp issafe]} {::tcl::tm::Defaults} diff --git a/windowsAgent/dist/tcl/word.tcl b/windowsAgent/dist/tcl/word.tcl new file mode 100644 index 0000000..3e4bc3a --- /dev/null +++ b/windowsAgent/dist/tcl/word.tcl @@ -0,0 +1,152 @@ +# word.tcl -- +# +# This file defines various procedures for computing word boundaries in +# strings. This file is primarily needed so Tk text and entry widgets behave +# properly for different platforms. +# +# Copyright (c) 1996 by Sun Microsystems, Inc. +# Copyright (c) 1998 by Scritpics Corporation. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. + +# The following variables are used to determine which characters are +# interpreted as white space. + +if {$::tcl_platform(platform) eq "windows"} { + # Windows style - any but a unicode space char + if {![info exists ::tcl_wordchars]} { + set ::tcl_wordchars {\S} + } + if {![info exists ::tcl_nonwordchars]} { + set ::tcl_nonwordchars {\s} + } +} else { + # Motif style - any unicode word char (number, letter, or underscore) + if {![info exists ::tcl_wordchars]} { + set ::tcl_wordchars {\w} + } + if {![info exists ::tcl_nonwordchars]} { + set ::tcl_nonwordchars {\W} + } +} + +# Arrange for caches of the real matcher REs to be kept, which enables the REs +# themselves to be cached for greater performance (and somewhat greater +# clarity too). + +namespace eval ::tcl { + variable WordBreakRE + array set WordBreakRE {} + + proc UpdateWordBreakREs args { + # Ignores the arguments + global tcl_wordchars tcl_nonwordchars + variable WordBreakRE + + # To keep the RE strings short... + set letter $tcl_wordchars + set space $tcl_nonwordchars + + set WordBreakRE(after) "$letter$space|$space$letter" + set WordBreakRE(before) "^.*($letter$space|$space$letter)" + set WordBreakRE(end) "$space*$letter+$space" + set WordBreakRE(next) "$letter*$space+$letter" + set WordBreakRE(previous) "$space*($letter+)$space*\$" + } + + # Initialize the cache + UpdateWordBreakREs + trace add variable ::tcl_wordchars write ::tcl::UpdateWordBreakREs + trace add variable ::tcl_nonwordchars write ::tcl::UpdateWordBreakREs +} + +# tcl_wordBreakAfter -- +# +# This procedure returns the index of the first word boundary after the +# starting point in the given string, or -1 if there are no more boundaries in +# the given string. The index returned refers to the first character of the +# pair that comprises a boundary. +# +# Arguments: +# str - String to search. +# start - Index into string specifying starting point. + +proc tcl_wordBreakAfter {str start} { + variable ::tcl::WordBreakRE + set result {-1 -1} + regexp -indices -start $start -- $WordBreakRE(after) $str result + return [lindex $result 1] +} + +# tcl_wordBreakBefore -- +# +# This procedure returns the index of the first word boundary before the +# starting point in the given string, or -1 if there are no more boundaries in +# the given string. The index returned refers to the second character of the +# pair that comprises a boundary. +# +# Arguments: +# str - String to search. +# start - Index into string specifying starting point. + +proc tcl_wordBreakBefore {str start} { + variable ::tcl::WordBreakRE + set result {-1 -1} + regexp -indices -- $WordBreakRE(before) [string range $str 0 $start] result + return [lindex $result 1] +} + +# tcl_endOfWord -- +# +# This procedure returns the index of the first end-of-word location after a +# starting index in the given string. An end-of-word location is defined to be +# the first whitespace character following the first non-whitespace character +# after the starting point. Returns -1 if there are no more words after the +# starting point. +# +# Arguments: +# str - String to search. +# start - Index into string specifying starting point. + +proc tcl_endOfWord {str start} { + variable ::tcl::WordBreakRE + set result {-1 -1} + regexp -indices -start $start -- $WordBreakRE(end) $str result + return [lindex $result 1] +} + +# tcl_startOfNextWord -- +# +# This procedure returns the index of the first start-of-word location after a +# starting index in the given string. A start-of-word location is defined to +# be a non-whitespace character following a whitespace character. Returns -1 +# if there are no more start-of-word locations after the starting point. +# +# Arguments: +# str - String to search. +# start - Index into string specifying starting point. + +proc tcl_startOfNextWord {str start} { + variable ::tcl::WordBreakRE + set result {-1 -1} + regexp -indices -start $start -- $WordBreakRE(next) $str result + return [lindex $result 1] +} + +# tcl_startOfPreviousWord -- +# +# This procedure returns the index of the first start-of-word location before +# a starting index in the given string. +# +# Arguments: +# str - String to search. +# start - Index into string specifying starting point. + +proc tcl_startOfPreviousWord {str start} { + variable ::tcl::WordBreakRE + set word {-1 -1} + regexp -indices -- $WordBreakRE(previous) [string range $str 0 $start-1] \ + result word + return [lindex $word 0] +}