summaryrefslogtreecommitdiffstats
path: root/library
diff options
context:
space:
mode:
Diffstat (limited to 'library')
-rw-r--r--library/auto.tcl76
-rw-r--r--library/clock.tcl2957
-rw-r--r--library/cookiejar/cookiejar.tcl746
-rw-r--r--library/cookiejar/idna.tcl292
-rw-r--r--library/cookiejar/pkgIndex.tcl3
-rw-r--r--library/cookiejar/public_suffix_list.dat.gzbin0 -> 70835 bytes
-rw-r--r--library/dde/pkgIndex.tcl6
-rw-r--r--library/encoding/ascii.enc2
-rw-r--r--library/encoding/big5.enc4
-rw-r--r--library/encoding/cp1250.enc4
-rw-r--r--library/encoding/cp1251.enc2
-rw-r--r--library/encoding/cp1252.enc4
-rw-r--r--library/encoding/cp1253.enc4
-rw-r--r--library/encoding/cp1254.enc4
-rw-r--r--library/encoding/cp1255.enc4
-rw-r--r--library/encoding/cp1257.enc4
-rw-r--r--library/encoding/cp1258.enc4
-rw-r--r--library/encoding/cp864.enc2
-rw-r--r--library/encoding/cp869.enc4
-rw-r--r--library/encoding/cp874.enc4
-rw-r--r--library/encoding/cp932.enc2
-rw-r--r--library/encoding/cp949.enc2
-rw-r--r--library/encoding/cp950.enc4
-rw-r--r--library/encoding/dingbats.enc4
-rw-r--r--library/encoding/ebcdic.enc1
-rw-r--r--library/encoding/euc-cn.enc4
-rw-r--r--library/encoding/euc-jp.enc4
-rw-r--r--library/encoding/euc-kr.enc4
-rw-r--r--library/encoding/gb1988.enc4
-rw-r--r--library/encoding/jis0201.enc4
-rw-r--r--library/encoding/macDingbats.enc4
-rw-r--r--library/encoding/macJapan.enc2
-rw-r--r--library/encoding/shiftjis.enc2
-rw-r--r--library/encoding/symbol.enc4
-rw-r--r--library/encoding/tis-620.enc2
-rw-r--r--library/foreachline.tcl25
-rw-r--r--library/history.tcl2
-rw-r--r--library/http/http.tcl2757
-rw-r--r--library/http/pkgIndex.tcl2
-rw-r--r--library/http1.0/http.tcl377
-rw-r--r--library/http1.0/pkgIndex.tcl11
-rw-r--r--library/init.tcl141
-rw-r--r--library/install.tcl247
-rw-r--r--library/manifest.txt19
-rw-r--r--library/msgcat/msgcat.tcl336
-rw-r--r--library/msgcat/pkgIndex.tcl4
-rw-r--r--library/msgs/ar.msg84
-rw-r--r--library/msgs/ar_jo.msg62
-rw-r--r--library/msgs/ar_lb.msg62
-rw-r--r--library/msgs/ar_sy.msg62
-rw-r--r--library/msgs/be.msg80
-rw-r--r--library/msgs/bg.msg56
-rw-r--r--library/msgs/bn.msg80
-rw-r--r--library/msgs/ca.msg4
-rw-r--r--library/msgs/cs.msg34
-rw-r--r--library/msgs/da.msg8
-rw-r--r--library/msgs/de.msg2
-rw-r--r--library/msgs/de_at.msg8
-rw-r--r--library/msgs/de_be.msg4
-rw-r--r--library/msgs/el.msg80
-rw-r--r--library/msgs/eo.msg10
-rw-r--r--library/msgs/es.msg8
-rw-r--r--library/msgs/et.msg16
-rw-r--r--library/msgs/fa.msg76
-rw-r--r--library/msgs/fa_in.msg80
-rw-r--r--library/msgs/fa_ir.msg8
-rw-r--r--library/msgs/fi.msg8
-rw-r--r--library/msgs/fo.msg18
-rw-r--r--library/msgs/fr.msg12
-rw-r--r--library/msgs/ga.msg50
-rw-r--r--library/msgs/gl.msg12
-rw-r--r--library/msgs/he.msg80
-rw-r--r--library/msgs/hi.msg64
-rw-r--r--library/msgs/hr.msg12
-rw-r--r--library/msgs/hu.msg34
-rw-r--r--library/msgs/is.msg44
-rw-r--r--library/msgs/it.msg10
-rw-r--r--library/msgs/ja.msg68
-rw-r--r--library/msgs/ko.msg86
-rw-r--r--library/msgs/ko_kr.msg4
-rw-r--r--library/msgs/kok.msg66
-rw-r--r--library/msgs/lt.msg20
-rw-r--r--library/msgs/lv.msg22
-rw-r--r--library/msgs/mk.msg80
-rw-r--r--library/msgs/mr.msg62
-rw-r--r--library/msgs/mt.msg8
-rw-r--r--library/msgs/nb.msg8
-rw-r--r--library/msgs/nn.msg4
-rw-r--r--library/msgs/pl.msg22
-rw-r--r--library/msgs/pt.msg8
-rw-r--r--library/msgs/ro.msg8
-rw-r--r--library/msgs/ru.msg80
-rw-r--r--library/msgs/sh.msg4
-rw-r--r--library/msgs/sk.msg26
-rw-r--r--library/msgs/sl.msg6
-rw-r--r--library/msgs/sq.msg16
-rw-r--r--library/msgs/sr.msg80
-rw-r--r--library/msgs/sv.msg12
-rw-r--r--library/msgs/ta.msg66
-rw-r--r--library/msgs/te.msg76
-rw-r--r--library/msgs/te_in.msg4
-rw-r--r--library/msgs/th.msg84
-rw-r--r--library/msgs/tr.msg24
-rw-r--r--library/msgs/uk.msg80
-rw-r--r--library/msgs/vi.msg38
-rw-r--r--library/msgs/zh.msg92
-rw-r--r--library/msgs/zh_cn.msg2
-rw-r--r--library/msgs/zh_hk.msg42
-rw-r--r--library/msgs/zh_sg.msg4
-rw-r--r--library/msgs/zh_tw.msg4
-rw-r--r--library/opt/optparse.tcl494
-rw-r--r--library/package.tcl25
-rw-r--r--library/parray.tcl4
-rw-r--r--library/readfile.tcl23
-rw-r--r--library/reg/pkgIndex.tcl9
-rw-r--r--library/registry/pkgIndex.tcl9
-rw-r--r--library/safe.tcl417
-rw-r--r--library/tclIndex180
-rw-r--r--library/tcltest/tcltest.tcl140
-rw-r--r--library/tm.tcl11
-rw-r--r--library/word.tcl41
-rw-r--r--library/writefile.tcl37
122 files changed, 6327 insertions, 5531 deletions
diff --git a/library/auto.tcl b/library/auto.tcl
index bae44c3..83f61a1 100644
--- a/library/auto.tcl
+++ b/library/auto.tcl
@@ -3,8 +3,8 @@
# 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.
+# Copyright © 1991-1993 The Regents of the University of California.
+# Copyright © 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.
@@ -74,6 +74,67 @@ proc tcl_findLibrary {basename version patch initScript enVarName varName} {
lappend dirs $env($enVarName)
}
+ catch {
+ set found 0
+ set root [zipfs root]
+ set mountpoint [file join $root lib $basename]
+ lappend dirs [file join $root app ${basename}_library]
+ lappend dirs [file join $root lib ${basename} ${basename}_library]
+ lappend dirs [file join $root lib ${basename}]
+ if {![zipfs exists [file join $root app ${basename}_library]] \
+ && ![zipfs exists $mountpoint]} {
+ set found 0
+ foreach pkgdat [info loaded] {
+ lassign $pkgdat dllfile dllpkg
+ if {$dllpkg ne $basename} continue
+ if {$dllfile eq {}} {
+ # Loaded statically
+ break
+ }
+ set found 1
+ zipfs mount $dllfile $mountpoint
+ break
+ }
+ if {!$found} {
+ set paths {}
+ if {![catch {::${basename}::pkgconfig get libdir,runtime} dir]} {
+ lappend paths $dir
+ } else {
+ catch {lappend paths [::tcl::pkgconfig get libdir,runtime]}
+ }
+ if {![catch {::${basename}::pkgconfig get bindir,runtime} dir]} {
+ lappend paths $dir
+ } else {
+ catch {lappend paths [::tcl::pkgconfig get bindir,runtime]}
+ }
+ if {[catch {::${basename}::pkgconfig get dllfile,runtime} dllfile]} {
+ set dllfile "lib${basename}${version}[info sharedlibextension]"
+ }
+ set dir [file dirname [file join [pwd] [info nameofexecutable]]]
+ lappend paths $dir
+ lappend paths [file join [file dirname $dir] lib]
+ foreach path $paths {
+ set archive [file join $path $dllfile]
+ if {![file exists $archive]} {
+ continue
+ }
+ zipfs mount $archive $mountpoint
+ if {[zipfs exists [file join $mountpoint ${basename}_library $initScript]]} {
+ lappend dirs [file join $mountpoint ${basename}_library]
+ set found 1
+ break
+ } elseif {[zipfs exists [file join $mountpoint $initScript]]} {
+ lappend dirs [file join $mountpoint $initScript]
+ set found 1
+ break
+ } else {
+ catch {zipfs unmount $mountpoint}
+ }
+ }
+ }
+ }
+ }
+
# 2. In the package script directory registered within the
# configuration of the package itself.
@@ -214,7 +275,7 @@ proc auto_mkindex {dir args} {
auto_mkindex_parser::cleanup
set fid [open "tclIndex" w]
- fconfigure $fid -encoding utf-8
+ fconfigure $fid -encoding utf-8 -translation lf
puts -nonewline $fid $index
close $fid
cd $oldDir
@@ -241,7 +302,7 @@ proc auto_mkindex_old {dir args} {
set f ""
set error [catch {
set f [open $file]
- fconfigure $f -eofchar "\x1A {}"
+ fconfigure $f -encoding utf-8 -eofchar "\x1A {}"
while {[gets $f line] >= 0} {
if {[regexp {^proc[ ]+([^ ]*)} $line match procName]} {
set procName [lindex [auto_qualify $procName "::"] 0]
@@ -260,6 +321,7 @@ proc auto_mkindex_old {dir args} {
set f ""
set error [catch {
set f [open tclIndex w]
+ fconfigure $f -encoding utf-8 -translation lf
puts -nonewline $f $index
close $f
cd $oldDir
@@ -352,7 +414,7 @@ proc auto_mkindex_parser::mkindex {file} {
set scriptFile $file
set fid [open $file]
- fconfigure $fid -eofchar "\x1A {}"
+ fconfigure $fid -encoding utf-8 -eofchar "\x1A {}"
set contents [read $fid]
close $fid
@@ -390,13 +452,13 @@ proc auto_mkindex_parser::hook {cmd} {
lappend initCommands $cmd
}
-# auto_mkindex_parser::slavehook command
+# auto_mkindex_parser::childhook command
#
# Registers a Tcl command to evaluate when initializing the child interpreter
# used by the mkindex parser. The command is evaluated in the child
# interpreter.
-proc auto_mkindex_parser::slavehook {cmd} {
+proc auto_mkindex_parser::childhook {cmd} {
variable initCommands
# The $parser variable is defined to be the name of the child interpreter
diff --git a/library/clock.tcl b/library/clock.tcl
index e578f4d..ef82372 100644
--- a/library/clock.tcl
+++ b/library/clock.tcl
@@ -9,23 +9,16 @@
#
#----------------------------------------------------------------------
#
-# Copyright (c) 2004-2007 Kevin B. Kenny
+# Copyright © 2004-2007 Kevin B. Kenny
+# Copyright © 2015 Sergey G. Brester aka sebres.
# 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.
+# msgcat 1.7 features are used.
-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 {}]
- }
- }
-}
+package require msgcat 1.7
# Put the library directory into the namespace for the ensemble so that the
# library code can find message catalogs and time zone definition files.
@@ -58,9 +51,7 @@ namespace eval ::tcl::clock {
# Import the message catalog commands that we use.
- namespace import ::msgcat::mcload
namespace import ::msgcat::mclocale
- namespace import ::msgcat::mc
namespace import ::msgcat::mcpackagelocale
}
@@ -287,6 +278,13 @@ proc ::tcl::clock::Initialize {} {
variable FEB_28 58
+ # Default configuration
+
+ ::tcl::unsupported::clock::configure -current-locale [mclocale]
+ #::tcl::unsupported::clock::configure -default-locale C
+ #::tcl::unsupported::clock::configure -year-century 2000 \
+ # -century-switch 38
+
# 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
@@ -383,152 +381,6 @@ proc ::tcl::clock::Initialize {} {
{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 \
@@ -625,17 +477,9 @@ proc ::tcl::clock::Initialize {} {
# 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
+ variable LocFmtMap [dict create]; # Dictionary with localized format maps
+
+ variable TimeZoneBad [dict create]; # Dictionary whose keys are time zone
# names and whose values are 1 if
# the time zone is unknown and 0
# if it is known.
@@ -644,1698 +488,204 @@ proc ::tcl::clock::Initialize {} {
# 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
+
+ variable mcLocales [dict create]; # Dictionary with loaded locales
+ variable mcMergedCat [dict create]; # Dictionary with merged locale catalogs
}
::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]
-
-}
-
-#----------------------------------------------------------------------
+# mcget --
#
-# ParseClockFormatFormat --
+# Return the merged translation catalog for the ::tcl::clock namespace
+# Searching of catalog is similar to "msgcat::mc".
#
-# Builds and caches a procedure that formats a time value.
+# Contrary to "msgcat::mc" may additionally load a package catalog
+# on demand.
#
-# Parameters:
-# format -- Format string to use
-# locale -- Locale in which the format string is to be interpreted
+# Arguments:
+# loc The locale used for translation.
#
# 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 scan' command scans times of day on input. Refer to the user
-# documentation to see what it does.
+# Returns the dictionary object as whole catalog of the package/locale.
#
-#----------------------------------------------------------------------
-
-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?\""
+proc ::tcl::clock::mcget {loc} {
+ variable mcMergedCat
+ switch -- $loc system {
+ set loc [GetSystemLocale]
+ } current {
+ set loc [mclocale]
}
-
- # 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] {
- switch -exact -- $flag {
- -b - -ba - -bas - -base {
- set base $value
- }
- -f - -fo - -for - -form - -forma - -format {
- set saw(-format) {}
- set format $value
- }
- -g - -gm - -gmt {
- set saw(-gmt) {}
- set gmt $value
- }
- -l - -lo - -loc - -loca - -local - -locale {
- set saw(-locale) {}
- set locale [string tolower $value]
- }
- -t - -ti - -tim - -time - -timez - -timezo - -timezon - -timezone {
- set saw(-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 {$loc ne {}} {
+ set loc [string tolower $loc]
}
- 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]
+ # try to retrieve now if already available:
+ if {[dict exists $mcMergedCat $loc]} {
+ return [dict get $mcMergedCat $loc]
}
- # 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
- }
+ # get locales list for given locale (de_de -> {de_de de {}})
+ variable mcLocales
+ if {[dict exists $mcLocales $loc]} {
+ set loclist [dict get $mcLocales $loc]
+ } else {
+ # save current locale:
+ set prevloc [mclocale]
+ # lazy load catalog on demand (set it will load the catalog)
+ mcpackagelocale set $loc
+ set loclist [msgcat::mcutil::getpreferences $loc]
+ dict set $mcLocales $loc $loclist
+ # restore:
+ if {$prevloc ne $loc} {
+ mcpackagelocale set $prevloc
+ }
+ }
+ # get whole catalog:
+ mcMerge $loclist
}
-#----------------------------------------------------------------------
+# mcMerge --
#
-# FreeScan --
+# Merge message catalog dictionaries to one dictionary.
#
-# 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.
+# Arguments:
+# locales List of locales to merge.
#
# 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
+# Returns the (weak pointer) to merged dictionary of message catalog.
+#
+proc ::tcl::clock::mcMerge {locales} {
+ variable mcMergedCat
+ if {[dict exists $mcMergedCat [set loc [lindex $locales 0]]]} {
+ return [dict get $mcMergedCat $loc]
+ }
+ # package msgcat currently does not provide possibility to get whole catalog:
+ upvar ::msgcat::Msgs Msgs
+ set ns ::tcl::clock
+ # Merge sequential locales (in reverse order, e. g. {} -> en -> en_en):
+ if {[llength $locales] > 1} {
+ set mrgcat [mcMerge [lrange $locales 1 end]]
+ if {[dict exists $Msgs $ns $loc]} {
+ set mrgcat [dict merge $mrgcat [dict get $Msgs $ns $loc]]
+ dict set mrgcat L $loc
} 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 {}
- }
+ # be sure a duplicate is created, don't overwrite {} (common) locale:
+ set mrgcat [dict merge $mrgcat [dict create L $loc]]
}
- }
-
- # 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
+ } else {
+ if {[dict exists $Msgs $ns $loc]} {
+ set mrgcat [dict get $Msgs $ns $loc]
+ dict set mrgcat L $loc
+ } else {
+ # be sure a duplicate is created, don't overwrite {} (common) locale:
+ set mrgcat [dict create L $loc]
}
- append re \)
- dict set LocaleNumeralCache $l [list $re $d]
}
- return [dict get $LocaleNumeralCache $l]
+ dict set mcMergedCat $loc $mrgcat
+ # return smart reference (shared dict as object with exact one ref-counter)
+ return $mrgcat
}
-
-
#----------------------------------------------------------------------
#
-# UniquePrefixRegexp --
+# GetSystemLocale --
#
-# Composes a regexp that performs unique-prefix matching. The RE
-# matches one of a supplied set of strings, or any unique prefix
-# thereof.
+# Determines the system locale, which corresponds to "system"
+# keyword for locale parameter of 'clock' command.
#
# 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'.
+# Returns the system locale.
#
# Side effects:
# None.
#
#----------------------------------------------------------------------
-proc ::tcl::clock::MakeUniquePrefixRegexp { successors
- uniquePrefixMapping
- prefixString } {
+proc ::tcl::clock::GetSystemLocale {} {
+ if { $::tcl_platform(platform) ne {windows} } {
+ # On a non-windows platform, the 'system' locale is the same as
+ # the 'current' locale
- # 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 {}
+ return [mclocale]
}
- # 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.
+ # 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
- 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 |
- }
+ mcpackagelocale set [mclocale]
- # 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.
+ # Make a new locale string for the system locale, and get the
+ # Control Panel information
- if { [dict exists $uniquePrefixMapping $prefixString] } {
- append re ")?"
- } elseif { [llength $schars] > 1 } {
- append re ")"
+ set locale [mclocale]_windows
+ if { ! [mcpackagelocale present $locale] } {
+ LoadWindowsDateTimeFormats $locale
}
- return $re
+ return $locale
}
#----------------------------------------------------------------------
#
-# MakeParseCodeFromFields --
+# EnterLocale --
#
-# Composes Tcl code to extract the Julian Day Number from a dictionary
-# containing date fields.
+# Switch [mclocale] to a given locale if necessary
#
# 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
+# locale -- Desired locale
#
# Results:
-# Returns a burst of code that extracts the day number from the given
-# date.
+# Returns the locale that was previously current.
#
# Side effects:
-# None.
+# Does [mclocale]. If necessary, loades the designated locale's files.
#
#----------------------------------------------------------------------
-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
+proc ::tcl::clock::EnterLocale { locale } {
+ switch -- $locale system {
+ set locale [GetSystemLocale]
+ } current {
+ set locale [mclocale]
}
-
- return $currCodeBurst
+ # Select the locale, eventually load it
+ mcpackagelocale set $locale
+ return $locale
}
#----------------------------------------------------------------------
#
-# EnterLocale --
-#
-# Switch [mclocale] to a given locale if necessary
-#
-# Parameters:
-# locale -- Desired locale
-#
-# Results:
-# Returns the locale that was previously current.
+# _hasRegistry --
#
-# Side effects:
-# Does [mclocale]. If necessary, loads the designated locale's files.
+# Helper that checks whether registry module is available (Windows only)
+# and loads it on demand.
#
#----------------------------------------------------------------------
-
-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
- }
+proc ::tcl::clock::_hasRegistry {} {
+ set res 0
+ if { $::tcl_platform(platform) eq {windows} } {
+ if { [catch { package require registry 1.1 }] } {
+ # try to load registry directly from root (if uninstalled / development env):
+ if {[regexp {[/\\]library$} [info library]]} {catch {
+ load [lindex \
+ [glob -tails -directory [file dirname [info nameofexecutable]] \
+ tclreg*[expr {[::tcl::pkgconfig get debug] ? {g} : {}}].dll] 0 \
+ ] registry
+ }}
+ }
+ if { [namespace which -command ::registry] ne "" } {
+ set res 1
}
}
- if { $locale eq {current}} {
- set locale [mclocale]
- }
- # Eventually load the locale
- mcpackagelocale set $locale
+ proc ::tcl::clock::_hasRegistry {} [list return $res]
+ return $res
}
#----------------------------------------------------------------------
@@ -2363,8 +713,7 @@ proc ::tcl::clock::EnterLocale { locale } {
proc ::tcl::clock::LoadWindowsDateTimeFormats { locale } {
# Bail out if we can't find the Registry
- variable NoRegistry
- if { [info exists NoRegistry] } return
+ if { ![_hasRegistry] } return
if { ![catch {
registry get "HKEY_CURRENT_USER\\Control Panel\\International" \
@@ -2475,6 +824,8 @@ proc ::tcl::clock::LoadWindowsDateTimeFormats { locale } {
# locale -- Current [mclocale] locale, supplied to avoid
# an extra call
# format -- Format supplied to [clock scan] or [clock format]
+# mcd -- Message catalog dictionary for current locale (read-only,
+# don't store it to avoid shared references).
#
# Results:
# Returns the string with locale-dependent composite format groups
@@ -2485,485 +836,51 @@ proc ::tcl::clock::LoadWindowsDateTimeFormats { locale } {
#
#----------------------------------------------------------------------
-proc ::tcl::clock::LocalizeFormat { locale format } {
+proc ::tcl::clock::LocalizeFormat { locale format mcd } {
+ variable LocFmtMap
- # 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 -
+ # get map list cached or build it:
+ if {[dict exists $LocFmtMap $locale]} {
+ set mlst [dict get $LocFmtMap $locale]
} 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 specifying 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]
- }]
+ # 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 mlst {
+ %% %%
+ %D %m/%d/%Y
+ %+ {%a %b %e %H:%M:%S %Z %Y}
+ }
+ lappend mlst %EY [string map $mlst [dict get $mcd LOCALE_YEAR_FORMAT]]
+ lappend mlst %T [string map $mlst [dict get $mcd TIME_FORMAT_24_SECS]]
+ lappend mlst %R [string map $mlst [dict get $mcd TIME_FORMAT_24]]
+ lappend mlst %r [string map $mlst [dict get $mcd TIME_FORMAT_12]]
+ lappend mlst %X [string map $mlst [dict get $mcd TIME_FORMAT]]
+ lappend mlst %EX [string map $mlst [dict get $mcd LOCALE_TIME_FORMAT]]
+ lappend mlst %x [string map $mlst [dict get $mcd DATE_FORMAT]]
+ lappend mlst %Ex [string map $mlst [dict get $mcd LOCALE_DATE_FORMAT]]
+ lappend mlst %c [string map $mlst [dict get $mcd DATE_TIME_FORMAT]]
+ lappend mlst %Ec [string map $mlst [dict get $mcd LOCALE_DATE_TIME_FORMAT]]
+
+ dict set LocFmtMap $locale $mlst
+ }
+
+ # translate copy of format (don't use format object here, because otherwise
+ # it can lose its internal representation (string map - convert to unicode)
+ set locfmt [string map $mlst [string range " $format" 1 end]]
+
+ # Save original format as long as possible, because of internal
+ # representation (performance).
+ # Note that in this case such format will be never localized (also
+ # using another locales). To prevent this return a duplicate (but
+ # it may be slower).
+ if {$locfmt eq $format} {
+ set locfmt $format
+ }
+
+ return $locfmt
}
#----------------------------------------------------------------------
@@ -2980,13 +897,12 @@ proc ::tcl::clock::InterpretHMS { date } {
# Returns the system time zone.
#
# Side effects:
-# Stores the system time zone in the 'CachedSystemTimeZone'
-# variable, since determining it may be an expensive process.
+# Stores the system time zone in engine configuration, since
+# determining it may be an expensive process.
#
#----------------------------------------------------------------------
proc ::tcl::clock::GetSystemTimeZone {} {
- variable CachedSystemTimeZone
variable TimeZoneBad
if {[set result [getenv TCL_TZ]] ne {}} {
@@ -2994,11 +910,12 @@ proc ::tcl::clock::GetSystemTimeZone {} {
} elseif {[set result [getenv TZ]] ne {}} {
set timezone $result
} else {
- # 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} } {
+ # ask engine for the cached timezone:
+ set timezone [::tcl::unsupported::clock::configure -system-tz]
+ if { $timezone ne "" } {
+ return $timezone
+ }
+ if { $::tcl_platform(platform) eq {windows} } {
set timezone [GuessWindowsTimeZone]
} elseif { [file exists /etc/localtime]
&& ![catch {ReadZoneinfoFile \
@@ -3007,48 +924,19 @@ proc ::tcl::clock::GetSystemTimeZone {} {
} 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
+ catch {set timezone [SetupTimeZone $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?
-#
-#----------------------------------------------------------------------
+ if { [dict exists $TimeZoneBad $timezone] } {
+ set timezone :localtime
+ }
-proc ::tcl::clock::ConvertLegacyTimeZone { tzname } {
- variable LegacyTimeZone
+ # tell backend - current system timezone:
+ ::tcl::unsupported::clock::configure -system-tz $timezone
- 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]
+ return $timezone
}
#----------------------------------------------------------------------
@@ -3068,15 +956,19 @@ proc ::tcl::clock::ConvertLegacyTimeZone { tzname } {
#
#----------------------------------------------------------------------
-proc ::tcl::clock::SetupTimeZone { timezone } {
+proc ::tcl::clock::SetupTimeZone { timezone {alias {}} } {
variable TZData
if {! [info exists TZData($timezone)] } {
- variable MINWIDE
- if { $timezone eq {:localtime} } {
- # Nothing to do, we'll convert using the localtime function
- } elseif {
+ variable TimeZoneBad
+ if { [dict exists $TimeZoneBad $timezone] } {
+ return -code error \
+ -errorcode [list CLOCK badTimeZone $timezone] \
+ "time zone \"$timezone\" not found"
+ }
+ variable MINWIDE
+ if {
[regexp {^([-+])(\d\d)(?::?(\d\d)(?::?(\d\d))?)?} $timezone \
-> s hh mm ss]
} then {
@@ -3109,6 +1001,7 @@ proc ::tcl::clock::SetupTimeZone { timezone } {
LoadZoneinfoFile [string range $timezone 1 end]
}]
} then {
+ dict set TimeZoneBad $timezone 1
return -code error \
-errorcode [list CLOCK badTimeZone $timezone] \
"time zone \"$timezone\" not found"
@@ -3120,25 +1013,43 @@ proc ::tcl::clock::SetupTimeZone { timezone } {
if { [lindex [dict get $opts -errorcode] 0] eq {CLOCK} } {
dict unset opts -errorinfo
}
+ dict set TimeZoneBad $timezone 1
return -options $opts $data
} else {
set TZData($timezone) $data
}
} else {
+
+ variable LegacyTimeZone
+
# 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] } {
+
+ # Check may be a legacy zone:
+
+ if { $alias eq {} && ![catch {
+ set tzname [dict get $LegacyTimeZone [string tolower $timezone]]
+ }] } {
+ set tzname [::tcl::clock::SetupTimeZone $tzname $timezone]
+ set TZData($timezone) $TZData($tzname)
+ # tell backend - timezone is initialized and return shared timezone object:
+ return [::tcl::unsupported::clock::configure -setup-tz $timezone]
+ }
+
dict unset opts -errorinfo
+ dict set TimeZoneBad $timezone 1
return -options $opts "time zone $timezone not found"
}
set TZData($timezone) $TZData(:$timezone)
}
}
- return
+ # tell backend - timezone is initialized and return shared timezone object:
+ ::tcl::unsupported::clock::configure -setup-tz $timezone
}
#----------------------------------------------------------------------
@@ -3168,10 +1079,9 @@ proc ::tcl::clock::SetupTimeZone { timezone } {
proc ::tcl::clock::GuessWindowsTimeZone {} {
variable WinZoneInfo
- variable NoRegistry
variable TimeZoneBad
- if { [info exists NoRegistry] } {
+ if { ![_hasRegistry] } {
return :localtime
}
@@ -3209,12 +1119,12 @@ proc ::tcl::clock::GuessWindowsTimeZone {} {
if { [dict exists $WinZoneInfo $data] } {
set tzname [dict get $WinZoneInfo $data]
if { ! [dict exists $TimeZoneBad $tzname] } {
- dict set TimeZoneBad $tzname [catch {SetupTimeZone $tzname}]
+ catch {set tzname [SetupTimeZone $tzname]}
}
} else {
set tzname {}
}
- if { $tzname eq {} || [dict get $TimeZoneBad $tzname] } {
+ if { $tzname eq {} || [dict exists $TimeZoneBad $tzname] } {
lassign $data \
bias stdBias dstBias \
stdYear stdMonth stdDayOfWeek stdDayOfMonth \
@@ -3318,7 +1228,7 @@ proc ::tcl::clock::LoadTimeZoneFile { fileName } {
"time zone \":$fileName\" not valid"
}
try {
- source -encoding utf-8 [file join $DataDir $fileName]
+ source [file join $DataDir $fileName]
} on error {} {
return -code error \
-errorcode [list CLOCK badTimeZone :$fileName] \
@@ -3885,14 +1795,14 @@ proc ::tcl::clock::DeterminePosixDSTTime { z bound y } {
# Determine the start or end day of DST
- set date [dict create era CE year $y]
+ set date [dict create era CE year $y gregorian 1]
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]
+ && [IsGregorianLeapYear $date]
&& ( $doy > $FEB_28 ) } {
incr doy
}
@@ -3941,43 +1851,6 @@ proc ::tcl::clock::DeterminePosixDSTTime { z bound y } {
#----------------------------------------------------------------------
#
-# 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
@@ -4155,333 +2028,6 @@ proc ::tcl::clock::WeekdayOnOrBefore { weekday j } {
#----------------------------------------------------------------------
#
-# 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 saw(-gmt) {}
- set gmt $b
- }
- -l - -lo - -loc - -loca - -local - -locale {
- set locale [string tolower $b]
- }
- -t - -ti - -tim - -time - -timez - -timezo - -timezon -
- -timezone {
- set saw(-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.
@@ -4499,20 +2045,7 @@ proc ::tcl::clock::AddDays { days clockval timezone changeover } {
#----------------------------------------------------------------------
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 {}
+ ::tcl::unsupported::clock::configure -current-locale [lindex $args 0]
}
#----------------------------------------------------------------------
@@ -4533,21 +2066,17 @@ proc ::tcl::clock::ChangeCurrentLocale {args} {
#----------------------------------------------------------------------
proc ::tcl::clock::ClearCaches {} {
- variable FormatProc
- variable LocaleNumeralCache
- variable CachedSystemTimeZone
+ variable LocFmtMap
+ variable mcMergedCat
variable TimeZoneBad
- foreach p [info procs [namespace current]::scanproc'*] {
- rename $p {}
- }
- foreach p [info procs [namespace current]::formatproc'*] {
- rename $p {}
- }
+ # tell backend - should invalidate:
+ ::tcl::unsupported::clock::configure -clear
+
+ # clear msgcat cache:
+ set mcMergedCat [dict create]
- catch {unset FormatProc}
- set LocaleNumeralCache {}
- catch {unset CachedSystemTimeZone}
+ set LocFmtMap {}
set TimeZoneBad {}
InitTZData
}
diff --git a/library/cookiejar/cookiejar.tcl b/library/cookiejar/cookiejar.tcl
new file mode 100644
index 0000000..85f73b4
--- /dev/null
+++ b/library/cookiejar/cookiejar.tcl
@@ -0,0 +1,746 @@
+# cookiejar.tcl --
+#
+# Implementation of an HTTP cookie storage engine using SQLite. The
+# implementation is done as a TclOO class, and includes a punycode
+# encoder and decoder (though only the encoder is currently used).
+#
+# See the file "license.terms" for information on usage and redistribution of
+# this file, and for a DISCLAIMER OF ALL WARRANTIES.
+
+# Dependencies
+package require Tcl 8.6
+package require http 2.8.4
+package require sqlite3
+package require tcl::idna 1.0
+
+#
+# Configuration for the cookiejar package, plus basic support procedures.
+#
+
+# This is the class that we are creating
+if {![llength [info commands ::http::cookiejar]]} {
+ ::oo::class create ::http::cookiejar
+}
+
+namespace eval [info object namespace ::http::cookiejar] {
+ proc setInt {*var val} {
+ upvar 1 ${*var} var
+ if {[catch {incr dummy $val} msg]} {
+ return -code error $msg
+ }
+ set var $val
+ }
+ proc setInterval {trigger *var val} {
+ upvar 1 ${*var} var
+ if {![string is integer -strict $val] || $val < 1} {
+ return -code error "expected positive integer but got \"$val\""
+ }
+ set var $val
+ {*}$trigger
+ }
+ proc setBool {*var val} {
+ upvar 1 ${*var} var
+ if {[catch {if {$val} {}} msg]} {
+ return -code error $msg
+ }
+ set var [expr {!!$val}]
+ }
+
+ proc setLog {*var val} {
+ upvar 1 ${*var} var
+ set var [::tcl::prefix match -message "log level" \
+ {debug info warn error} $val]
+ }
+
+ # Keep this in sync with pkgIndex.tcl and with the install directories in
+ # Makefiles
+ variable version 0.2.0
+
+ variable domainlist \
+ https://publicsuffix.org/list/public_suffix_list.dat
+ variable domainfile \
+ [file join [file dirname [info script]] public_suffix_list.dat.gz]
+ # The list is directed to from http://publicsuffix.org/list/
+ variable loglevel info
+ variable vacuumtrigger 200
+ variable retainlimit 100
+ variable offline false
+ variable purgeinterval 60000
+ variable refreshinterval 10000000
+ variable domaincache {}
+
+ # Some support procedures, none particularly useful in general
+ namespace eval support {
+ # Set up a logger if the http package isn't actually loaded yet.
+ if {![llength [info commands ::http::Log]]} {
+ proc ::http::Log args {
+ # Do nothing by default...
+ }
+ }
+
+ namespace export *
+ proc locn {secure domain path {key ""}} {
+ if {$key eq ""} {
+ format "%s://%s%s" [expr {$secure?"https":"http"}] \
+ [::tcl::idna encode $domain] $path
+ } else {
+ format "%s://%s%s?%s" \
+ [expr {$secure?"https":"http"}] [::tcl::idna encode $domain] \
+ $path $key
+ }
+ }
+ proc splitDomain domain {
+ set pieces [split $domain "."]
+ for {set i [llength $pieces]} {[incr i -1] >= 0} {} {
+ lappend result [join [lrange $pieces $i end] "."]
+ }
+ return $result
+ }
+ proc splitPath path {
+ set pieces [split [string trimleft $path "/"] "/"]
+ set result /
+ for {set j 0} {$j < [llength $pieces]} {incr j} {
+ lappend result /[join [lrange $pieces 0 $j] "/"]
+ }
+ return $result
+ }
+ proc isoNow {} {
+ set ms [clock milliseconds]
+ set ts [expr {$ms / 1000}]
+ set ms [format %03d [expr {$ms % 1000}]]
+ clock format $ts -format "%Y%m%dT%H%M%S.${ms}Z" -gmt 1
+ }
+ proc log {level msg args} {
+ namespace upvar [info object namespace ::http::cookiejar] \
+ loglevel loglevel
+ set who [uplevel 1 self class]
+ set mth [uplevel 1 self method]
+ set map {debug 0 info 1 warn 2 error 3}
+ if {[string map $map $level] >= [string map $map $loglevel]} {
+ set msg [format $msg {*}$args]
+ set LVL [string toupper $level]
+ ::http::Log "[isoNow] $LVL $who $mth - $msg"
+ }
+ }
+ }
+}
+
+# Now we have enough information to provide the package.
+package provide cookiejar \
+ [set [info object namespace ::http::cookiejar]::version]
+
+# The implementation of the cookiejar package
+::oo::define ::http::cookiejar {
+ self {
+ method configure {{optionName "\x00\x00"} {optionValue "\x00\x00"}} {
+ set tbl {
+ -domainfile {domainfile set}
+ -domainlist {domainlist set}
+ -domainrefresh {refreshinterval setInterval}
+ -loglevel {loglevel setLog}
+ -offline {offline setBool}
+ -purgeold {purgeinterval setInterval}
+ -retain {retainlimit setInt}
+ -vacuumtrigger {vacuumtrigger setInt}
+ }
+ dict lappend tbl -domainrefresh [namespace code {
+ my IntervalTrigger PostponeRefresh
+ }]
+ dict lappend tbl -purgeold [namespace code {
+ my IntervalTrigger PostponePurge
+ }]
+ if {$optionName eq "\x00\x00"} {
+ return [dict keys $tbl]
+ }
+ set opt [::tcl::prefix match -message "option" \
+ [dict keys $tbl] $optionName]
+ set setter [lassign [dict get $tbl $opt] varname]
+ namespace upvar [namespace current] $varname var
+ if {$optionValue ne "\x00\x00"} {
+ {*}$setter var $optionValue
+ }
+ return $var
+ }
+
+ method IntervalTrigger {method} {
+ # TODO: handle subclassing
+ foreach obj [info class instances [self]] {
+ [info object namespace $obj]::my $method
+ }
+ }
+ }
+
+ variable purgeTimer deletions refreshTimer
+ constructor {{path ""}} {
+ namespace import [info object namespace [self class]]::support::*
+
+ if {$path eq ""} {
+ sqlite3 [namespace current]::db :memory:
+ set storeorigin "constructed cookie store in memory"
+ } else {
+ sqlite3 [namespace current]::db $path
+ db timeout 500
+ set storeorigin "loaded cookie store from $path"
+ }
+
+ set deletions 0
+ db transaction {
+ db eval {
+ --;# Store the persistent cookies in this table.
+ --;# Deletion policy: once they expire, or if explicitly
+ --;# killed.
+ CREATE TABLE IF NOT EXISTS persistentCookies (
+ id INTEGER PRIMARY KEY,
+ secure INTEGER NOT NULL,
+ domain TEXT NOT NULL COLLATE NOCASE,
+ path TEXT NOT NULL,
+ key TEXT NOT NULL,
+ value TEXT NOT NULL,
+ originonly INTEGER NOT NULL,
+ expiry INTEGER NOT NULL,
+ lastuse INTEGER NOT NULL,
+ creation INTEGER NOT NULL);
+ CREATE UNIQUE INDEX IF NOT EXISTS persistentUnique
+ ON persistentCookies (domain, path, key);
+ CREATE INDEX IF NOT EXISTS persistentLookup
+ ON persistentCookies (domain, path);
+
+ --;# Store the session cookies in this table.
+ --;# Deletion policy: at cookiejar instance deletion, if
+ --;# explicitly killed, or if the number of session cookies is
+ --;# too large and the cookie has not been used recently.
+ CREATE TEMP TABLE sessionCookies (
+ id INTEGER PRIMARY KEY,
+ secure INTEGER NOT NULL,
+ domain TEXT NOT NULL COLLATE NOCASE,
+ path TEXT NOT NULL,
+ key TEXT NOT NULL,
+ originonly INTEGER NOT NULL,
+ value TEXT NOT NULL,
+ lastuse INTEGER NOT NULL,
+ creation INTEGER NOT NULL);
+ CREATE UNIQUE INDEX sessionUnique
+ ON sessionCookies (domain, path, key);
+ CREATE INDEX sessionLookup ON sessionCookies (domain, path);
+
+ --;# View to allow for simple looking up of a cookie.
+ --;# Deletion policy: NOT SUPPORTED via this view.
+ CREATE TEMP VIEW cookies AS
+ SELECT id, domain, (
+ CASE originonly WHEN 1 THEN path ELSE '.' || path END
+ ) AS path, key, value, secure, 1 AS persistent
+ FROM persistentCookies
+ UNION
+ SELECT id, domain, (
+ CASE originonly WHEN 1 THEN path ELSE '.' || path END
+ ) AS path, key, value, secure, 0 AS persistent
+ FROM sessionCookies;
+
+ --;# Encoded domain permission policy; if forbidden is 1, no
+ --;# cookie may be ever set for the domain, and if forbidden
+ --;# is 0, cookies *may* be created for the domain (overriding
+ --;# the forbiddenSuper table).
+ --;# Deletion policy: normally not modified.
+ CREATE TABLE IF NOT EXISTS domains (
+ domain TEXT PRIMARY KEY NOT NULL,
+ forbidden INTEGER NOT NULL);
+
+ --;# Domains that may not have a cookie defined for direct
+ --;# child domains of them.
+ --;# Deletion policy: normally not modified.
+ CREATE TABLE IF NOT EXISTS forbiddenSuper (
+ domain TEXT PRIMARY KEY);
+
+ --;# When we last retrieved the domain list.
+ CREATE TABLE IF NOT EXISTS domainCacheMetadata (
+ id INTEGER PRIMARY KEY,
+ retrievalDate INTEGER,
+ installDate INTEGER);
+ }
+
+ set cookieCount "no"
+ db eval {
+ SELECT COUNT(*) AS cookieCount FROM persistentCookies
+ }
+ log info "%s with %s entries" $storeorigin $cookieCount
+
+ my PostponePurge
+
+ if {$path ne ""} {
+ if {[db exists {SELECT 1 FROM domains}]} {
+ my RefreshDomains
+ } else {
+ my InitDomainList
+ my PostponeRefresh
+ }
+ } else {
+ set data [my GetDomainListOffline metadata]
+ my InstallDomainData $data $metadata
+ my PostponeRefresh
+ }
+ }
+ }
+
+ method PostponePurge {} {
+ namespace upvar [info object namespace [self class]] \
+ purgeinterval interval
+ catch {after cancel $purgeTimer}
+ set purgeTimer [after $interval [namespace code {my PurgeCookies}]]
+ }
+
+ method PostponeRefresh {} {
+ namespace upvar [info object namespace [self class]] \
+ refreshinterval interval
+ catch {after cancel $refreshTimer}
+ set refreshTimer [after $interval [namespace code {my RefreshDomains}]]
+ }
+
+ method RefreshDomains {} {
+ # TODO: domain list refresh policy
+ my PostponeRefresh
+ }
+
+ method HttpGet {url {timeout 0} {maxRedirects 5}} {
+ for {set r 0} {$r < $maxRedirects} {incr r} {
+ set tok [::http::geturl $url -timeout $timeout]
+ try {
+ if {[::http::status $tok] eq "timeout"} {
+ return -code error "connection timed out"
+ } elseif {[::http::ncode $tok] == 200} {
+ return [::http::data $tok]
+ } elseif {[::http::ncode $tok] >= 400} {
+ return -code error [::http::error $tok]
+ } elseif {[dict exists [::http::meta $tok] Location]} {
+ set url [dict get [::http::meta $tok] Location]
+ continue
+ }
+ return -code error \
+ "unexpected state: [::http::code $tok]"
+ } finally {
+ ::http::cleanup $tok
+ }
+ }
+ return -code error "too many redirects"
+ }
+ method GetDomainListOnline {metaVar} {
+ upvar 1 $metaVar meta
+ namespace upvar [info object namespace [self class]] \
+ domainlist url domaincache cache
+ lassign $cache when data
+ if {$when > [clock seconds] - 3600} {
+ log debug "using cached value created at %s" \
+ [clock format $when -format {%Y%m%dT%H%M%SZ} -gmt 1]
+ dict set meta retrievalDate $when
+ return $data
+ }
+ log debug "loading domain list from %s" $url
+ try {
+ set when [clock seconds]
+ set data [my HttpGet $url]
+ set cache [list $when $data]
+ # TODO: Should we use the Last-Modified header instead?
+ dict set meta retrievalDate $when
+ return $data
+ } on error msg {
+ log error "failed to fetch list of forbidden cookie domains from %s: %s" \
+ $url $msg
+ return {}
+ }
+ }
+ method GetDomainListOffline {metaVar} {
+ upvar 1 $metaVar meta
+ namespace upvar [info object namespace [self class]] \
+ domainfile filename
+ log debug "loading domain list from %s" $filename
+ try {
+ set f [open $filename]
+ try {
+ if {[string match *.gz $filename]} {
+ zlib push gunzip $f
+ }
+ fconfigure $f -encoding utf-8
+ dict set meta retrievalDate [file mtime $filename]
+ return [read $f]
+ } finally {
+ close $f
+ }
+ } on error {msg opt} {
+ log error "failed to read list of forbidden cookie domains from %s: %s" \
+ $filename $msg
+ return -options $opt $msg
+ }
+ }
+ method InitDomainList {} {
+ namespace upvar [info object namespace [self class]] \
+ offline offline
+ if {!$offline} {
+ try {
+ set data [my GetDomainListOnline metadata]
+ if {[string length $data]} {
+ my InstallDomainData $data $metadata
+ return
+ }
+ } on error {} {
+ log warn "attempting to fall back to built in version"
+ }
+ }
+ set data [my GetDomainListOffline metadata]
+ my InstallDomainData $data $metadata
+ }
+
+ method InstallDomainData {data meta} {
+ set n [db total_changes]
+ db transaction {
+ foreach line [split $data "\n"] {
+ if {[string trim $line] eq ""} {
+ continue
+ } elseif {[string match //* $line]} {
+ continue
+ } elseif {[string match !* $line]} {
+ set line [string range $line 1 end]
+ set idna [string tolower [::tcl::idna encode $line]]
+ set utf [::tcl::idna decode [string tolower $line]]
+ db eval {
+ INSERT OR REPLACE INTO domains (domain, forbidden)
+ VALUES ($utf, 0);
+ }
+ if {$idna ne $utf} {
+ db eval {
+ INSERT OR REPLACE INTO domains (domain, forbidden)
+ VALUES ($idna, 0);
+ }
+ }
+ } else {
+ if {[string match {\*.*} $line]} {
+ set line [string range $line 2 end]
+ set idna [string tolower [::tcl::idna encode $line]]
+ set utf [::tcl::idna decode [string tolower $line]]
+ db eval {
+ INSERT OR REPLACE INTO forbiddenSuper (domain)
+ VALUES ($utf);
+ }
+ if {$idna ne $utf} {
+ db eval {
+ INSERT OR REPLACE INTO forbiddenSuper (domain)
+ VALUES ($idna);
+ }
+ }
+ } else {
+ set idna [string tolower [::tcl::idna encode $line]]
+ set utf [::tcl::idna decode [string tolower $line]]
+ }
+ db eval {
+ INSERT OR REPLACE INTO domains (domain, forbidden)
+ VALUES ($utf, 1);
+ }
+ if {$idna ne $utf} {
+ db eval {
+ INSERT OR REPLACE INTO domains (domain, forbidden)
+ VALUES ($idna, 1);
+ }
+ }
+ }
+ if {$utf ne [::tcl::idna decode [string tolower $idna]]} {
+ log warn "mismatch in IDNA handling for %s (%d, %s, %s)" \
+ $idna $line $utf [::tcl::idna decode $idna]
+ }
+ }
+
+ dict with meta {
+ set installDate [clock seconds]
+ db eval {
+ INSERT OR REPLACE INTO domainCacheMetadata
+ (id, retrievalDate, installDate)
+ VALUES (1, $retrievalDate, $installDate);
+ }
+ }
+ }
+ set n [expr {[db total_changes] - $n}]
+ log info "constructed domain info with %d entries" $n
+ }
+
+ # This forces the rebuild of the domain data, loading it from
+ method forceLoadDomainData {} {
+ db transaction {
+ db eval {
+ DELETE FROM domains;
+ DELETE FROM forbiddenSuper;
+ INSERT OR REPLACE INTO domainCacheMetadata
+ (id, retrievalDate, installDate)
+ VALUES (1, -1, -1);
+ }
+ my InitDomainList
+ }
+ }
+
+ destructor {
+ catch {
+ after cancel $purgeTimer
+ }
+ catch {
+ after cancel $refreshTimer
+ }
+ catch {
+ db close
+ }
+ return
+ }
+
+ method GetCookiesForHostAndPath {listVar secure host path fullhost} {
+ upvar 1 $listVar result
+ log debug "check for cookies for %s" [locn $secure $host $path]
+ set exact [expr {$host eq $fullhost}]
+ db eval {
+ SELECT key, value FROM persistentCookies
+ WHERE domain = $host AND path = $path AND secure <= $secure
+ AND (NOT originonly OR domain = $fullhost)
+ AND originonly = $exact
+ } {
+ lappend result $key $value
+ db eval {
+ UPDATE persistentCookies SET lastuse = $now WHERE id = $id
+ }
+ }
+ set now [clock seconds]
+ db eval {
+ SELECT id, key, value FROM sessionCookies
+ WHERE domain = $host AND path = $path AND secure <= $secure
+ AND (NOT originonly OR domain = $fullhost)
+ AND originonly = $exact
+ } {
+ lappend result $key $value
+ db eval {
+ UPDATE sessionCookies SET lastuse = $now WHERE id = $id
+ }
+ }
+ }
+
+ method getCookies {proto host path} {
+ set result {}
+ set paths [splitPath $path]
+ if {[regexp {[^0-9.]} $host]} {
+ set domains [splitDomain [string tolower [::tcl::idna encode $host]]]
+ } else {
+ # Ugh, it's a numeric domain! Restrict it to just itself...
+ set domains [list $host]
+ }
+ set secure [string equal -nocase $proto "https"]
+ # Open question: how to move these manipulations into the database
+ # engine (if that's where they *should* be).
+ #
+ # Suggestion from kbk:
+ #LENGTH(theColumn) <= LENGTH($queryStr) AND
+ #SUBSTR(theColumn, LENGTH($queryStr) LENGTH(theColumn)+1) = $queryStr
+ #
+ # However, we instead do most of the work in Tcl because that lets us
+ # do the splitting exactly right, and it's far easier to work with
+ # strings in Tcl than in SQL.
+ db transaction {
+ foreach domain $domains {
+ foreach p $paths {
+ my GetCookiesForHostAndPath result $secure $domain $p $host
+ }
+ }
+ return $result
+ }
+ }
+
+ method BadDomain options {
+ if {![dict exists $options domain]} {
+ log error "no domain present in options"
+ return 0
+ }
+ dict with options {}
+ if {$domain ne $origin} {
+ log debug "cookie domain varies from origin (%s, %s)" \
+ $domain $origin
+ if {[string match .* $domain]} {
+ set dotd $domain
+ } else {
+ set dotd .$domain
+ }
+ if {![string equal -length [string length $dotd] \
+ [string reverse $dotd] [string reverse $origin]]} {
+ log warn "bad cookie: domain not suffix of origin"
+ return 1
+ }
+ }
+ if {![regexp {[^0-9.]} $domain]} {
+ if {$domain eq $origin} {
+ # May set for itself
+ return 0
+ }
+ log warn "bad cookie: for a numeric address"
+ return 1
+ }
+ db eval {
+ SELECT forbidden FROM domains WHERE domain = $domain
+ } {
+ if {$forbidden} {
+ log warn "bad cookie: for a forbidden address"
+ }
+ return $forbidden
+ }
+ if {[regexp {^[^.]+\.(.+)$} $domain -> super] && [db exists {
+ SELECT 1 FROM forbiddenSuper WHERE domain = $super
+ }]} then {
+ log warn "bad cookie: for a forbidden address"
+ return 1
+ }
+ return 0
+ }
+
+ # A defined extension point to allow users to easily impose extra policies
+ # on whether to accept cookies from a particular domain and path.
+ method policyAllow {operation domain path} {
+ return true
+ }
+
+ method storeCookie {options} {
+ db transaction {
+ if {[my BadDomain $options]} {
+ return
+ }
+ set now [clock seconds]
+ set persistent [dict exists $options expires]
+ dict with options {}
+ if {!$persistent} {
+ if {![my policyAllow session $domain $path]} {
+ log warn "bad cookie: $domain prohibited by user policy"
+ return
+ }
+ db eval {
+ INSERT OR REPLACE INTO sessionCookies (
+ secure, domain, path, key, value, originonly, creation,
+ lastuse)
+ VALUES ($secure, $domain, $path, $key, $value, $hostonly,
+ $now, $now);
+ DELETE FROM persistentCookies
+ WHERE domain = $domain AND path = $path AND key = $key
+ AND secure <= $secure AND originonly = $hostonly
+ }
+ incr deletions [db changes]
+ log debug "defined session cookie for %s" \
+ [locn $secure $domain $path $key]
+ } elseif {$expires < $now} {
+ if {![my policyAllow delete $domain $path]} {
+ log warn "bad cookie: $domain prohibited by user policy"
+ return
+ }
+ db eval {
+ DELETE FROM persistentCookies
+ WHERE domain = $domain AND path = $path AND key = $key
+ AND secure <= $secure AND originonly = $hostonly
+ }
+ set del [db changes]
+ db eval {
+ DELETE FROM sessionCookies
+ WHERE domain = $domain AND path = $path AND key = $key
+ AND secure <= $secure AND originonly = $hostonly
+ }
+ incr deletions [incr del [db changes]]
+ log debug "deleted %d cookies for %s" \
+ $del [locn $secure $domain $path $key]
+ } else {
+ if {![my policyAllow set $domain $path]} {
+ log warn "bad cookie: $domain prohibited by user policy"
+ return
+ }
+ db eval {
+ INSERT OR REPLACE INTO persistentCookies (
+ secure, domain, path, key, value, originonly, expiry,
+ creation, lastuse)
+ VALUES ($secure, $domain, $path, $key, $value, $hostonly,
+ $expires, $now, $now);
+ DELETE FROM sessionCookies
+ WHERE domain = $domain AND path = $path AND key = $key
+ AND secure <= $secure AND originonly = $hostonly
+ }
+ incr deletions [db changes]
+ log debug "defined persistent cookie for %s, expires at %s" \
+ [locn $secure $domain $path $key] \
+ [clock format $expires]
+ }
+ }
+ }
+
+ method PurgeCookies {} {
+ namespace upvar [info object namespace [self class]] \
+ vacuumtrigger trigger retainlimit retain
+ my PostponePurge
+ set now [clock seconds]
+ log debug "purging cookies that expired before %s" [clock format $now]
+ db transaction {
+ db eval {
+ DELETE FROM persistentCookies WHERE expiry < $now
+ }
+ incr deletions [db changes]
+ db eval {
+ DELETE FROM persistentCookies WHERE id IN (
+ SELECT id FROM persistentCookies ORDER BY lastuse ASC
+ LIMIT -1 OFFSET $retain)
+ }
+ incr deletions [db changes]
+ db eval {
+ DELETE FROM sessionCookies WHERE id IN (
+ SELECT id FROM sessionCookies ORDER BY lastuse
+ LIMIT -1 OFFSET $retain)
+ }
+ incr deletions [db changes]
+ }
+
+ # Once we've deleted a fair bit, vacuum the database. Must be done
+ # outside a transaction.
+ if {$deletions > $trigger} {
+ set deletions 0
+ log debug "vacuuming cookie database"
+ catch {
+ db eval {
+ VACUUM
+ }
+ }
+ }
+ }
+
+ forward Database db
+
+ method lookup {{host ""} {key ""}} {
+ set host [string tolower [::tcl::idna encode $host]]
+ db transaction {
+ if {$host eq ""} {
+ set result {}
+ db eval {
+ SELECT DISTINCT domain FROM cookies
+ ORDER BY domain
+ } {
+ lappend result [::tcl::idna decode [string tolower $domain]]
+ }
+ return $result
+ } elseif {$key eq ""} {
+ set result {}
+ db eval {
+ SELECT DISTINCT key FROM cookies
+ WHERE domain = $host
+ ORDER BY key
+ } {
+ lappend result $key
+ }
+ return $result
+ } else {
+ db eval {
+ SELECT value FROM cookies
+ WHERE domain = $host AND key = $key
+ LIMIT 1
+ } {
+ return $value
+ }
+ return -code error "no such key for that host"
+ }
+ }
+ }
+}
+
+# Local variables:
+# mode: tcl
+# fill-column: 78
+# End:
diff --git a/library/cookiejar/idna.tcl b/library/cookiejar/idna.tcl
new file mode 100644
index 0000000..dc25cd8
--- /dev/null
+++ b/library/cookiejar/idna.tcl
@@ -0,0 +1,292 @@
+# idna.tcl --
+#
+# Implementation of IDNA (Internationalized Domain Names for
+# Applications) encoding/decoding system, built on a punycode engine
+# developed directly from the code in RFC 3492, Appendix C (with
+# substantial modifications).
+#
+# This implementation includes code from that RFC, translated to Tcl; the
+# other parts are:
+# Copyright © 2014 Donal K. Fellows
+#
+# See the file "license.terms" for information on usage and redistribution of
+# this file, and for a DISCLAIMER OF ALL WARRANTIES.
+
+namespace eval ::tcl::idna {
+ namespace ensemble create -command puny -map {
+ encode punyencode
+ decode punydecode
+ }
+ namespace ensemble create -command ::tcl::idna -map {
+ encode IDNAencode
+ decode IDNAdecode
+ puny puny
+ version {::apply {{} {package present tcl::idna} ::}}
+ }
+
+ proc IDNAencode hostname {
+ set parts {}
+ # Split term from RFC 3490, Sec 3.1
+ foreach part [split $hostname "\x2E\u3002\uFF0E\uFF61"] {
+ if {[regexp {[^-A-Za-z0-9]} $part]} {
+ if {[regexp {[^-A-Za-z0-9\xA1-\uFFFF]} $part ch]} {
+ scan $ch %c c
+ if {$ch < "!" || $ch > "~"} {
+ set ch [format "\\u%04x" $c]
+ }
+ throw [list IDNA INVALID_NAME_CHARACTER $ch] \
+ "bad character \"$ch\" in DNS name"
+ }
+ set part xn--[punyencode $part]
+ # Length restriction from RFC 5890, Sec 2.3.1
+ if {[string length $part] > 63} {
+ throw [list IDNA OVERLONG_PART $part] \
+ "hostname part too long"
+ }
+ }
+ lappend parts $part
+ }
+ return [join $parts .]
+ }
+ proc IDNAdecode hostname {
+ set parts {}
+ # Split term from RFC 3490, Sec 3.1
+ foreach part [split $hostname "\x2E\u3002\uFF0E\uFF61"] {
+ if {[string match -nocase "xn--*" $part]} {
+ set part [punydecode [string range $part 4 end]]
+ }
+ lappend parts $part
+ }
+ return [join $parts .]
+ }
+
+ variable digits [split "abcdefghijklmnopqrstuvwxyz0123456789" ""]
+ # Bootstring parameters for Punycode
+ variable base 36
+ variable tmin 1
+ variable tmax 26
+ variable skew 38
+ variable damp 700
+ variable initial_bias 72
+ variable initial_n 0x80
+
+ variable max_codepoint 0x10FFFF
+
+ proc adapt {delta first numchars} {
+ variable base
+ variable tmin
+ variable tmax
+ variable damp
+ variable skew
+
+ set delta [expr {$delta / ($first ? $damp : 2)}]
+ incr delta [expr {$delta / $numchars}]
+ set k 0
+ while {$delta > ($base - $tmin) * $tmax / 2} {
+ set delta [expr {$delta / ($base-$tmin)}]
+ incr k $base
+ }
+ return [expr {$k + ($base-$tmin+1) * $delta / ($delta+$skew)}]
+ }
+
+ # Main punycode encoding function
+ proc punyencode {string {case ""}} {
+ variable digits
+ variable tmin
+ variable tmax
+ variable base
+ variable initial_n
+ variable initial_bias
+
+ if {![string is boolean $case]} {
+ return -code error "\"$case\" must be boolean"
+ }
+
+ set in {}
+ foreach char [set string [split $string ""]] {
+ scan $char "%c" ch
+ lappend in $ch
+ }
+ set output {}
+
+ # Initialize the state:
+ set n $initial_n
+ set delta 0
+ set bias $initial_bias
+
+ # Handle the basic code points:
+ foreach ch $string {
+ if {$ch < "\x80"} {
+ if {$case eq ""} {
+ append output $ch
+ } elseif {[string is true $case]} {
+ append output [string toupper $ch]
+ } elseif {[string is false $case]} {
+ append output [string tolower $ch]
+ }
+ }
+ }
+
+ set b [string length $output]
+
+ # h is the number of code points that have been handled, b is the
+ # number of basic code points.
+
+ if {$b > 0} {
+ append output "-"
+ }
+
+ # Main encoding loop:
+
+ for {set h $b} {$h < [llength $in]} {incr delta; incr n} {
+ # All non-basic code points < n have been handled already. Find
+ # the next larger one:
+
+ set m inf
+ foreach ch $in {
+ if {$ch >= $n && $ch < $m} {
+ set m $ch
+ }
+ }
+
+ # Increase delta enough to advance the decoder's <n,i> state to
+ # <m,0>, but guard against overflow:
+
+ if {$m-$n > (0xFFFFFFFF-$delta)/($h+1)} {
+ throw {PUNYCODE OVERFLOW} "overflow in delta computation"
+ }
+ incr delta [expr {($m-$n) * ($h+1)}]
+ set n $m
+
+ foreach ch $in {
+ if {$ch < $n && ([incr delta] & 0xFFFFFFFF) == 0} {
+ throw {PUNYCODE OVERFLOW} "overflow in delta computation"
+ }
+
+ if {$ch != $n} {
+ continue
+ }
+
+ # Represent delta as a generalized variable-length integer:
+
+ for {set q $delta; set k $base} true {incr k $base} {
+ set t [expr {min(max($k-$bias, $tmin), $tmax)}]
+ if {$q < $t} {
+ break
+ }
+ append output \
+ [lindex $digits [expr {$t + ($q-$t)%($base-$t)}]]
+ set q [expr {($q-$t) / ($base-$t)}]
+ }
+
+ append output [lindex $digits $q]
+ set bias [adapt $delta [expr {$h==$b}] [expr {$h+1}]]
+ set delta 0
+ incr h
+ }
+ }
+
+ return $output
+ }
+
+ # Main punycode decode function
+ proc punydecode {string {case ""}} {
+ variable tmin
+ variable tmax
+ variable base
+ variable initial_n
+ variable initial_bias
+ variable max_codepoint
+
+ if {![string is boolean $case]} {
+ return -code error "\"$case\" must be boolean"
+ }
+
+ # Initialize the state:
+
+ set n $initial_n
+ set i 0
+ set first 1
+ set bias $initial_bias
+
+ # Split the string into the "real" ASCII characters and the ones to
+ # feed into the main decoder. Note that we don't need to check the
+ # result of [regexp] because that RE will technically match any string
+ # at all.
+
+ regexp {^(?:(.*)-)?([^-]*)$} $string -> pre post
+ if {[string is true -strict $case]} {
+ set pre [string toupper $pre]
+ } elseif {[string is false -strict $case]} {
+ set pre [string tolower $pre]
+ }
+ set output [split $pre ""]
+ set out [llength $output]
+
+ # Main decoding loop:
+
+ for {set in 0} {$in < [string length $post]} {incr in} {
+ # Decode a generalized variable-length integer into delta, which
+ # gets added to i. The overflow checking is easier if we increase
+ # i as we go, then subtract off its starting value at the end to
+ # obtain delta.
+
+ for {set oldi $i; set w 1; set k $base} 1 {incr in} {
+ if {[set ch [string index $post $in]] eq ""} {
+ throw {PUNYCODE BAD_INPUT LENGTH} "exceeded input data"
+ }
+ if {[string match -nocase {[a-z]} $ch]} {
+ scan [string toupper $ch] %c digit
+ incr digit -65
+ } elseif {[string match {[0-9]} $ch]} {
+ set digit [expr {$ch + 26}]
+ } else {
+ throw {PUNYCODE BAD_INPUT CHAR} \
+ "bad decode character \"$ch\""
+ }
+ incr i [expr {$digit * $w}]
+ set t [expr {min(max($tmin, $k-$bias), $tmax)}]
+ if {$digit < $t} {
+ set bias [adapt [expr {$i-$oldi}] $first [incr out]]
+ set first 0
+ break
+ }
+ if {[set w [expr {$w * ($base - $t)}]] > 0x7FFFFFFF} {
+ throw {PUNYCODE OVERFLOW} \
+ "excessively large integer computed in digit decode"
+ }
+ incr k $base
+ }
+
+ # i was supposed to wrap around from out+1 to 0, incrementing n
+ # each time, so we'll fix that now:
+
+ if {[incr n [expr {$i / $out}]] > 0x7FFFFFFF} {
+ throw {PUNYCODE OVERFLOW} \
+ "excessively large integer computed in character choice"
+ } elseif {$n > $max_codepoint} {
+ if {$n >= 0x00D800 && $n < 0x00E000} {
+ # Bare surrogate?!
+ throw {PUNYCODE NON_BMP} \
+ [format "unsupported character U+%06x" $n]
+ }
+ throw {PUNYCODE NON_UNICODE} "bad codepoint $n"
+ }
+ set i [expr {$i % $out}]
+
+ # Insert n at position i of the output:
+
+ set output [linsert $output $i [format "%c" $n]]
+ incr i
+ }
+
+ return [join $output ""]
+ }
+}
+
+package provide tcl::idna 1.0.1
+
+# Local variables:
+# mode: tcl
+# fill-column: 78
+# End:
diff --git a/library/cookiejar/pkgIndex.tcl b/library/cookiejar/pkgIndex.tcl
new file mode 100644
index 0000000..b1853aa
--- /dev/null
+++ b/library/cookiejar/pkgIndex.tcl
@@ -0,0 +1,3 @@
+if {![package vsatisfies [package provide Tcl] 8.6-]} {return}
+package ifneeded cookiejar 0.2.0 [list source [file join $dir cookiejar.tcl]]
+package ifneeded tcl::idna 1.0.1 [list source [file join $dir idna.tcl]]
diff --git a/library/cookiejar/public_suffix_list.dat.gz b/library/cookiejar/public_suffix_list.dat.gz
new file mode 100644
index 0000000..65bf75a
--- /dev/null
+++ b/library/cookiejar/public_suffix_list.dat.gz
Binary files differ
diff --git a/library/dde/pkgIndex.tcl b/library/dde/pkgIndex.tcl
index 18ac517..ace1681 100644
--- a/library/dde/pkgIndex.tcl
+++ b/library/dde/pkgIndex.tcl
@@ -1,12 +1,12 @@
if {[info sharedlibextension] != ".dll"} return
if {[package vsatisfies [package provide Tcl] 9.0-]} {
- package ifneeded dde 1.4.4 \
+ package ifneeded dde 1.4.5 \
[list load [file join $dir tcl9dde14.dll] Dde]
} elseif {![package vsatisfies [package provide Tcl] 8.7]
&& [::tcl::pkgconfig get debug]} {
- package ifneeded dde 1.4.4 \
+ package ifneeded dde 1.4.5 \
[list load [file join $dir tcldde14g.dll] Dde]
} else {
- package ifneeded dde 1.4.4 \
+ package ifneeded dde 1.4.5 \
[list load [file join $dir tcldde14.dll] Dde]
}
diff --git a/library/encoding/ascii.enc b/library/encoding/ascii.enc
index e0320b8..284a9f5 100644
--- a/library/encoding/ascii.enc
+++ b/library/encoding/ascii.enc
@@ -9,7 +9,7 @@ S
0040004100420043004400450046004700480049004A004B004C004D004E004F
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
-0070007100720073007400750076007700780079007A007B007C007D007E0000
+0070007100720073007400750076007700780079007A007B007C007D007E007F
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
diff --git a/library/encoding/big5.enc b/library/encoding/big5.enc
index 26179f4..d6ff760 100644
--- a/library/encoding/big5.enc
+++ b/library/encoding/big5.enc
@@ -10,8 +10,8 @@ M
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
-0080008100820083008400850086008700880089008A008B008C008D008E008F
-0090009100920093009400950096009700980099009A009B009C009D009E009F
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
diff --git a/library/encoding/cp1250.enc b/library/encoding/cp1250.enc
index 070ad90..f40b485 100644
--- a/library/encoding/cp1250.enc
+++ b/library/encoding/cp1250.enc
@@ -10,8 +10,8 @@ S
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
-20AC0081201A0083201E2026202020210088203001602039015A0164017D0179
-009020182019201C201D202220132014009821220161203A015B0165017E017A
+20AC0000201A0000201E2026202020210000203001602039015A0164017D0179
+000020182019201C201D202220132014000021220161203A015B0165017E017A
00A002C702D8014100A4010400A600A700A800A9015E00AB00AC00AD00AE017B
00B000B102DB014200B400B500B600B700B80105015F00BB013D02DD013E017C
015400C100C2010200C40139010600C7010C00C9011800CB011A00CD00CE010E
diff --git a/library/encoding/cp1251.enc b/library/encoding/cp1251.enc
index 376b1b4..f9513c2 100644
--- a/library/encoding/cp1251.enc
+++ b/library/encoding/cp1251.enc
@@ -11,7 +11,7 @@ S
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
04020403201A0453201E20262020202120AC203004092039040A040C040B040F
-045220182019201C201D202220132014009821220459203A045A045C045B045F
+045220182019201C201D202220132014000021220459203A045A045C045B045F
00A0040E045E040800A4049000A600A7040100A9040400AB00AC00AD00AE0407
00B000B104060456049100B500B600B704512116045400BB0458040504550457
0410041104120413041404150416041704180419041A041B041C041D041E041F
diff --git a/library/encoding/cp1252.enc b/library/encoding/cp1252.enc
index dd525ea..b45a7f8 100644
--- a/library/encoding/cp1252.enc
+++ b/library/encoding/cp1252.enc
@@ -10,8 +10,8 @@ S
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
-20AC0081201A0192201E20262020202102C62030016020390152008D017D008F
-009020182019201C201D20222013201402DC21220161203A0153009D017E0178
+20AC0000201A0192201E20262020202102C620300160203901520000017D0000
+000020182019201C201D20222013201402DC21220161203A01530000017E0178
00A000A100A200A300A400A500A600A700A800A900AA00AB00AC00AD00AE00AF
00B000B100B200B300B400B500B600B700B800B900BA00BB00BC00BD00BE00BF
00C000C100C200C300C400C500C600C700C800C900CA00CB00CC00CD00CE00CF
diff --git a/library/encoding/cp1253.enc b/library/encoding/cp1253.enc
index a8754c3..dcc8084 100644
--- a/library/encoding/cp1253.enc
+++ b/library/encoding/cp1253.enc
@@ -10,8 +10,8 @@ S
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
-20AC0081201A0192201E20262020202100882030008A2039008C008D008E008F
-009020182019201C201D20222013201400982122009A203A009C009D009E009F
+20AC0000201A0192201E20262020202100002030000020390000000000000000
+000020182019201C201D202220132014000021220000203A0000000000000000
00A00385038600A300A400A500A600A700A800A9000000AB00AC00AD00AE2015
00B000B100B200B3038400B500B600B703880389038A00BB038C00BD038E038F
0390039103920393039403950396039703980399039A039B039C039D039E039F
diff --git a/library/encoding/cp1254.enc b/library/encoding/cp1254.enc
index b9e3b3c..4922f3c 100644
--- a/library/encoding/cp1254.enc
+++ b/library/encoding/cp1254.enc
@@ -10,8 +10,8 @@ S
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
-20AC0081201A0192201E20262020202102C62030016020390152008D008E008F
-009020182019201C201D20222013201402DC21220161203A0153009D009E0178
+20AC0000201A0192201E20262020202102C62030016020390152000000000000
+000020182019201C201D20222013201402DC21220161203A0153000000000178
00A000A100A200A300A400A500A600A700A800A900AA00AB00AC00AD00AE00AF
00B000B100B200B300B400B500B600B700B800B900BA00BB00BC00BD00BE00BF
00C000C100C200C300C400C500C600C700C800C900CA00CB00CC00CD00CE00CF
diff --git a/library/encoding/cp1255.enc b/library/encoding/cp1255.enc
index 6e78b95..74ef0c1 100644
--- a/library/encoding/cp1255.enc
+++ b/library/encoding/cp1255.enc
@@ -10,8 +10,8 @@ S
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
-20AC0081201A0192201E20262020202102C62030008A2039008C008D008E008F
-009020182019201C201D20222013201402DC2122009A203A009C009D009E009F
+20AC0000201A0192201E20262020202102C62030000020390000000000000000
+000020182019201C201D20222013201402DC21220000203A0000000000000000
00A000A100A200A320AA00A500A600A700A800A900D700AB00AC00AD00AE00AF
00B000B100B200B300B400B500B600B700B800B900F700BB00BC00BD00BE00BF
05B005B105B205B305B405B505B605B705B805B9000005BB05BC05BD05BE05BF
diff --git a/library/encoding/cp1257.enc b/library/encoding/cp1257.enc
index 4aa135d..42c6905 100644
--- a/library/encoding/cp1257.enc
+++ b/library/encoding/cp1257.enc
@@ -10,8 +10,8 @@ S
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
-20AC0081201A0083201E20262020202100882030008A2039008C00A802C700B8
-009020182019201C201D20222013201400982122009A203A009C00AF02DB009F
+20AC0000201A0000201E2026202020210000203000002039000000A802C700B8
+000020182019201C201D202220132014000021220000203A000000AF02DB0000
00A0000000A200A300A4000000A600A700D800A9015600AB00AC00AD00AE00C6
00B000B100B200B300B400B500B600B700F800B9015700BB00BC00BD00BE00E6
0104012E0100010600C400C501180112010C00C90179011601220136012A013B
diff --git a/library/encoding/cp1258.enc b/library/encoding/cp1258.enc
index 95fdef8..bbe2b12 100644
--- a/library/encoding/cp1258.enc
+++ b/library/encoding/cp1258.enc
@@ -10,8 +10,8 @@ S
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
-20AC0081201A0192201E20262020202102C62030008A20390152008D008E008F
-009020182019201C201D20222013201402DC2122009A203A0153009D009E0178
+20AC0000201A0192201E20262020202102C62030000020390152000000000000
+000020182019201C201D20222013201402DC21220000203A0153000000000178
00A000A100A200A300A400A500A600A700A800A900AA00AB00AC00AD00AE00AF
00B000B100B200B300B400B500B600B700B800B900BA00BB00BC00BD00BE00BF
00C000C100C2010200C400C500C600C700C800C900CA00CB030000CD00CE00CF
diff --git a/library/encoding/cp864.enc b/library/encoding/cp864.enc
index 71f9e62..dad7c20 100644
--- a/library/encoding/cp864.enc
+++ b/library/encoding/cp864.enc
@@ -11,7 +11,7 @@ S
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
00B000B72219221A259225002502253C2524252C251C25342510250C25142518
-03B2221E03C600B100BD00BC224800AB00BBFEF7FEF8009B009CFEFBFEFC009F
+03B2221E03C600B100BD00BC224800AB00BBFEF7FEF800000000FEFBFEFC0000
00A000ADFE8200A300A4FE8400000000FE8EFE8FFE95FE99060CFE9DFEA1FEA5
0660066106620663066406650666066706680669FED1061BFEB1FEB5FEB9061F
00A2FE80FE81FE83FE85FECAFE8BFE8DFE91FE93FE97FE9BFE9FFEA3FEA7FEA9
diff --git a/library/encoding/cp869.enc b/library/encoding/cp869.enc
index 9fd2929..4670826 100644
--- a/library/encoding/cp869.enc
+++ b/library/encoding/cp869.enc
@@ -10,8 +10,8 @@ S
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
-0080008100820083008400850386008700B700AC00A620182019038820150389
-038A03AA038C00930094038E03AB00A9038F00B200B303AC00A303AD03AE03AF
+0000000000000000000000000386000000B700AC00A620182019038820150389
+038A03AA038C00000000038E03AB00A9038F00B200B303AC00A303AD03AE03AF
03CA039003CC03CD039103920393039403950396039700BD0398039900AB00BB
25912592259325022524039A039B039C039D256325512557255D039E039F2510
25142534252C251C2500253C03A003A1255A25542569256625602550256C03A3
diff --git a/library/encoding/cp874.enc b/library/encoding/cp874.enc
index 0487b97..e2e8433 100644
--- a/library/encoding/cp874.enc
+++ b/library/encoding/cp874.enc
@@ -10,8 +10,8 @@ S
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
-20AC008100820083008420260086008700880089008A008B008C008D008E008F
-009020182019201C201D20222013201400980099009A009B009C009D009E009F
+20AC000000000000000020260000000000000000000000000000000000000000
+000020182019201C201D20222013201400000000000000000000000000000000
00A00E010E020E030E040E050E060E070E080E090E0A0E0B0E0C0E0D0E0E0E0F
0E100E110E120E130E140E150E160E170E180E190E1A0E1B0E1C0E1D0E1E0E1F
0E200E210E220E230E240E250E260E270E280E290E2A0E2B0E2C0E2D0E2E0E2F
diff --git a/library/encoding/cp932.enc b/library/encoding/cp932.enc
index 8da8cd6..0699000 100644
--- a/library/encoding/cp932.enc
+++ b/library/encoding/cp932.enc
@@ -10,7 +10,7 @@ M
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
-0080000000000000000000850086000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000FF61FF62FF63FF64FF65FF66FF67FF68FF69FF6AFF6BFF6CFF6DFF6EFF6F
FF70FF71FF72FF73FF74FF75FF76FF77FF78FF79FF7AFF7BFF7CFF7DFF7EFF7F
diff --git a/library/encoding/cp949.enc b/library/encoding/cp949.enc
index 2f3ec39..459dbd9 100644
--- a/library/encoding/cp949.enc
+++ b/library/encoding/cp949.enc
@@ -10,7 +10,7 @@ M
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
-0080000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
diff --git a/library/encoding/cp950.enc b/library/encoding/cp950.enc
index f33d785..f582bd9 100644
--- a/library/encoding/cp950.enc
+++ b/library/encoding/cp950.enc
@@ -10,8 +10,8 @@ M
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
-0080008100820083008400850086008700880089008A008B008C008D008E008F
-0090009100920093009400950096009700980099009A009B009C009D009E009F
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
diff --git a/library/encoding/dingbats.enc b/library/encoding/dingbats.enc
index 9729487..bd466b2 100644
--- a/library/encoding/dingbats.enc
+++ b/library/encoding/dingbats.enc
@@ -10,8 +10,8 @@ S
2730273127322733273427352736273727382739273A273B273C273D273E273F
2740274127422743274427452746274727482749274A274B25CF274D25A0274F
27502751275225B225BC25C6275625D727582759275A275B275C275D275E007F
-0080008100820083008400850086008700880089008A008B008C008D008E008F
-0090009100920093009400950096009700980099009A009B009C009D009E009F
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
0000276127622763276427652766276726632666266526602460246124622463
2464246524662467246824692776277727782779277A277B277C277D277E277F
2780278127822783278427852786278727882789278A278B278C278D278E278F
diff --git a/library/encoding/ebcdic.enc b/library/encoding/ebcdic.enc
index f451de5..f83ce7d 100644
--- a/library/encoding/ebcdic.enc
+++ b/library/encoding/ebcdic.enc
@@ -1,3 +1,4 @@
+# Encoding file: ebcdic, single-byte
S
006F 0 1
00
diff --git a/library/encoding/euc-cn.enc b/library/encoding/euc-cn.enc
index 4b2f8c7..ff0f984 100644
--- a/library/encoding/euc-cn.enc
+++ b/library/encoding/euc-cn.enc
@@ -10,8 +10,8 @@ M
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
-0080008100820083008400850086008700880089008A008B008C008D008E008F
-0090009100920093009400950096009700980099009A009B009C009D009E009F
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
diff --git a/library/encoding/euc-jp.enc b/library/encoding/euc-jp.enc
index db56c88..d4337d9 100644
--- a/library/encoding/euc-jp.enc
+++ b/library/encoding/euc-jp.enc
@@ -10,8 +10,8 @@ M
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
-0080008100820083008400850086008700880089008A008B008C008D0000008F
-0090009100920093009400950096009700980099009A009B009C009D009E009F
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
diff --git a/library/encoding/euc-kr.enc b/library/encoding/euc-kr.enc
index 5e9bb93..0433260 100644
--- a/library/encoding/euc-kr.enc
+++ b/library/encoding/euc-kr.enc
@@ -10,8 +10,8 @@ M
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
-0080008100820083008400850086008700880089008A008B008C008D008E008F
-0090009100920093009400950096009700980099009A009B009C009D009E009F
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
diff --git a/library/encoding/gb1988.enc b/library/encoding/gb1988.enc
index 298732c..8254684 100644
--- a/library/encoding/gb1988.enc
+++ b/library/encoding/gb1988.enc
@@ -10,8 +10,8 @@ S
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D203E007F
-0080008100820083008400850086008700880089008A008B008C008D008E008F
-0090009100920093009400950096009700980099009A009B009C009D009E009F
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
0000FF61FF62FF63FF64FF65FF66FF67FF68FF69FF6AFF6BFF6CFF6DFF6EFF6F
FF70FF71FF72FF73FF74FF75FF76FF77FF78FF79FF7AFF7BFF7CFF7DFF7EFF7F
FF80FF81FF82FF83FF84FF85FF86FF87FF88FF89FF8AFF8BFF8CFF8DFF8EFF8F
diff --git a/library/encoding/jis0201.enc b/library/encoding/jis0201.enc
index 64f423f..70e099d 100644
--- a/library/encoding/jis0201.enc
+++ b/library/encoding/jis0201.enc
@@ -10,8 +10,8 @@ S
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D203E007F
-0080008100820083008400850086008700880089008A008B008C008D008E008F
-0090009100920093009400950096009700980099009A009B009C009D009E009F
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
0000FF61FF62FF63FF64FF65FF66FF67FF68FF69FF6AFF6BFF6CFF6DFF6EFF6F
FF70FF71FF72FF73FF74FF75FF76FF77FF78FF79FF7AFF7BFF7CFF7DFF7EFF7F
FF80FF81FF82FF83FF84FF85FF86FF87FF88FF89FF8AFF8BFF8CFF8DFF8EFF8F
diff --git a/library/encoding/macDingbats.enc b/library/encoding/macDingbats.enc
index 28449cd..9fa47b5 100644
--- a/library/encoding/macDingbats.enc
+++ b/library/encoding/macDingbats.enc
@@ -10,8 +10,8 @@ S
2730273127322733273427352736273727382739273A273B273C273D273E273F
2740274127422743274427452746274727482749274A274B25CF274D25A0274F
27502751275225B225BC25C6275625D727582759275A275B275C275D275E007F
-F8D7F8D8F8D9F8DAF8DBF8DCF8DDF8DEF8DFF8E0F8E1F8E2F8E3F8E4008E008F
-0090009100920093009400950096009700980099009A009B009C009D009E009F
+F8D7F8D8F8D9F8DAF8DBF8DCF8DDF8DEF8DFF8E0F8E1F8E2F8E3F8E400000000
+0000000000000000000000000000000000000000000000000000000000000000
0000276127622763276427652766276726632666266526602460246124622463
2464246524662467246824692776277727782779277A277B277C277D277E277F
2780278127822783278427852786278727882789278A278B278C278D278E278F
diff --git a/library/encoding/macJapan.enc b/library/encoding/macJapan.enc
index dba24bd..9f3f03b 100644
--- a/library/encoding/macJapan.enc
+++ b/library/encoding/macJapan.enc
@@ -10,7 +10,7 @@ M
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
-0080000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
00A0FF61FF62FF63FF64FF65FF66FF67FF68FF69FF6AFF6BFF6CFF6DFF6EFF6F
FF70FF71FF72FF73FF74FF75FF76FF77FF78FF79FF7AFF7BFF7CFF7DFF7EFF7F
diff --git a/library/encoding/shiftjis.enc b/library/encoding/shiftjis.enc
index 140aec4..3ba972e 100644
--- a/library/encoding/shiftjis.enc
+++ b/library/encoding/shiftjis.enc
@@ -10,7 +10,7 @@ M
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
0070007100720073007400750076007700780079007A007B007C007D007E007F
-0080000000000000000000850086008700000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000FF61FF62FF63FF64FF65FF66FF67FF68FF69FF6AFF6BFF6CFF6DFF6EFF6F
FF70FF71FF72FF73FF74FF75FF76FF77FF78FF79FF7AFF7BFF7CFF7DFF7EFF7F
diff --git a/library/encoding/symbol.enc b/library/encoding/symbol.enc
index ffda9e3..ebd2f49 100644
--- a/library/encoding/symbol.enc
+++ b/library/encoding/symbol.enc
@@ -10,8 +10,8 @@ S
03A0039803A103A303A403A503C203A9039E03A80396005B2234005D22A5005F
F8E503B103B203C703B403B503C603B303B703B903D503BA03BB03BC03BD03BF
03C003B803C103C303C403C503D603C903BE03C803B6007B007C007D223C007F
-0080008100820083008400850086008700880089008A008B008C008D008E008F
-0090009100920093009400950096009700980099009A009B009C009D009E009F
+0000000000000000000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000000000000000000
000003D2203222642044221E0192266326662665266021942190219121922193
00B000B12033226500D7221D2202202200F72260226122482026F8E6F8E721B5
21352111211C21182297229522052229222A2283228722842282228622082209
diff --git a/library/encoding/tis-620.enc b/library/encoding/tis-620.enc
index 2e9142a..af77326 100644
--- a/library/encoding/tis-620.enc
+++ b/library/encoding/tis-620.enc
@@ -9,7 +9,7 @@ S
0040004100420043004400450046004700480049004A004B004C004D004E004F
0050005100520053005400550056005700580059005A005B005C005D005E005F
0060006100620063006400650066006700680069006A006B006C006D006E006F
-0070007100720073007400750076007700780079007A007B007C007D007E0000
+0070007100720073007400750076007700780079007A007B007C007D007E007F
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
00000E010E020E030E040E050E060E070E080E090E0A0E0B0E0C0E0D0E0E0E0F
diff --git a/library/foreachline.tcl b/library/foreachline.tcl
new file mode 100644
index 0000000..aacbd5b
--- /dev/null
+++ b/library/foreachline.tcl
@@ -0,0 +1,25 @@
+# foreachLine:
+# Iterate over the contents of a file, a line at a time.
+# The body script is run for each, with variable varName set to the line
+# contents.
+#
+# Copyright © 2023 Donal K Fellows.
+#
+# See the file "license.terms" for information on usage and redistribution
+# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+#
+
+proc foreachLine {varName filename body} {
+ upvar 1 $varName line
+ set f [open $filename "r"]
+ try {
+ while {[gets $f line] >= 0} {
+ uplevel 1 $body
+ }
+ } on return {msg opt} {
+ dict incr opt -level
+ return -options $opt $msg
+ } finally {
+ close $f
+ }
+}
diff --git a/library/history.tcl b/library/history.tcl
index 79b7604..87f26c5 100644
--- a/library/history.tcl
+++ b/library/history.tcl
@@ -2,7 +2,7 @@
#
# Implementation of the history command.
#
-# Copyright (c) 1997 Sun Microsystems, Inc.
+# Copyright © 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.
diff --git a/library/http/http.tcl b/library/http/http.tcl
index 5dcd76c..cbbcead 100644
--- a/library/http/http.tcl
+++ b/library/http/http.tcl
@@ -11,7 +11,7 @@
package require Tcl 8.6-
# Keep this in sync with pkgIndex.tcl and with the install directories in
# Makefiles
-package provide http 2.9.8
+package provide http 2.10b2
namespace eval http {
# Allow resourcing to not clobber existing data
@@ -20,12 +20,16 @@ namespace eval http {
if {![info exists http]} {
array set http {
-accept */*
+ -cookiejar {}
-pipeline 1
-postfresh 0
-proxyhost {}
-proxyport {}
-proxyfilter http::ProxyRequired
+ -proxynot {}
+ -proxyauth {}
-repost 0
+ -threadlevel 0
-urlencoding utf-8
-zip 1
}
@@ -69,8 +73,11 @@ namespace eval http {
variable socketWrState
variable socketRdQueue
variable socketWrQueue
+ variable socketPhQueue
variable socketClosing
variable socketPlayCmd
+ variable socketCoEvent
+ variable socketProxyId
if {[info exists socketMapping]} {
# Close open sockets on re-init. Do not permit retries.
foreach {url sock} [array get socketMapping] {
@@ -91,21 +98,28 @@ namespace eval http {
array unset socketWrState
array unset socketRdQueue
array unset socketWrQueue
+ array unset socketPhQueue
array unset socketClosing
array unset socketPlayCmd
+ array unset socketCoEvent
+ array unset socketProxyId
array set socketMapping {}
array set socketRdState {}
array set socketWrState {}
array set socketRdQueue {}
array set socketWrQueue {}
+ array set socketPhQueue {}
array set socketClosing {}
array set socketPlayCmd {}
+ array set socketCoEvent {}
+ array set socketProxyId {}
+ return
}
init
variable urlTypes
if {![info exists urlTypes]} {
- set urlTypes(http) [list 80 ::socket]
+ set urlTypes(http) [list 80 ::http::AltSocket {} 1 0]
}
variable encodings [string tolower [encoding names]]
@@ -127,13 +141,130 @@ namespace eval http {
set defaultKeepalive 0
}
- namespace export geturl config reset wait formatQuery quoteString
+ # Regular expression used to parse cookies
+ variable CookieRE {(?x) # EXPANDED SYNTAX
+ \s* # Ignore leading spaces
+ ([^][\u0000- ()<>@,;:\\""/?={}\u007f-\uffff]+) # Match the name
+ = # LITERAL: Equal sign
+ ([!\u0023-+\u002D-:<-\u005B\u005D-~]*) # Match the value
+ (?:
+ \s* ; \s* # LITERAL: semicolon
+ ([^\u0000]+) # Match the options
+ )?
+ }
+
+ variable TmpSockCounter 0
+ variable ThreadCounter 0
+
+ variable reasonDict [dict create {*}{
+ 100 Continue
+ 101 {Switching Protocols}
+ 102 Processing
+ 103 {Early Hints}
+ 200 OK
+ 201 Created
+ 202 Accepted
+ 203 {Non-Authoritative Information}
+ 204 {No Content}
+ 205 {Reset Content}
+ 206 {Partial Content}
+ 207 Multi-Status
+ 208 {Already Reported}
+ 226 {IM Used}
+ 300 {Multiple Choices}
+ 301 {Moved Permanently}
+ 302 Found
+ 303 {See Other}
+ 304 {Not Modified}
+ 305 {Use Proxy}
+ 306 (Unused)
+ 307 {Temporary Redirect}
+ 308 {Permanent Redirect}
+ 400 {Bad Request}
+ 401 Unauthorized
+ 402 {Payment Required}
+ 403 Forbidden
+ 404 {Not Found}
+ 405 {Method Not Allowed}
+ 406 {Not Acceptable}
+ 407 {Proxy Authentication Required}
+ 408 {Request Timeout}
+ 409 Conflict
+ 410 Gone
+ 411 {Length Required}
+ 412 {Precondition Failed}
+ 413 {Content Too Large}
+ 414 {URI Too Long}
+ 415 {Unsupported Media Type}
+ 416 {Range Not Satisfiable}
+ 417 {Expectation Failed}
+ 418 (Unused)
+ 421 {Misdirected Request}
+ 422 {Unprocessable Content}
+ 423 Locked
+ 424 {Failed Dependency}
+ 425 {Too Early}
+ 426 {Upgrade Required}
+ 428 {Precondition Required}
+ 429 {Too Many Requests}
+ 431 {Request Header Fields Too Large}
+ 451 {Unavailable For Legal Reasons}
+ 500 {Internal Server Error}
+ 501 {Not Implemented}
+ 502 {Bad Gateway}
+ 503 {Service Unavailable}
+ 504 {Gateway Timeout}
+ 505 {HTTP Version Not Supported}
+ 506 {Variant Also Negotiates}
+ 507 {Insufficient Storage}
+ 508 {Loop Detected}
+ 510 {Not Extended (OBSOLETED)}
+ 511 {Network Authentication Required}
+ }]
+
+ variable failedProxyValues {
+ binary
+ body
+ charset
+ coding
+ connection
+ connectionRespFlag
+ currentsize
+ host
+ http
+ httpResponse
+ meta
+ method
+ querylength
+ queryoffset
+ reasonPhrase
+ requestHeaders
+ requestLine
+ responseCode
+ state
+ status
+ tid
+ totalsize
+ transfer
+ type
+ }
+
+ namespace export geturl config reset wait formatQuery postError quoteString
namespace export register unregister registerError
- # - Useful, but not exported: data, size, status, code, cleanup, error,
- # meta, ncode, mapReply, init. Comments suggest that "init" can be used
- # for re-initialisation, although the command is undocumented.
- # - Not exported, probably should be upper-case initial letter as part
- # of the internals: getTextLine, make-transformation-chunked.
+ namespace export requestLine requestHeaders requestHeaderValue
+ namespace export responseLine responseHeaders responseHeaderValue
+ namespace export responseCode responseBody responseInfo reasonPhrase
+ # - Legacy aliases, were never exported:
+ # data, code, mapReply, meta, ncode
+ # - Callable from outside (e.g. from TLS) by fully-qualified name, but
+ # not exported:
+ # socket
+ # - Useful, but never exported (and likely to have naming collisions):
+ # size, status, cleanup, error, init
+ # Comments suggest that "init" can be used for re-initialisation,
+ # although the command is undocumented.
+ # - Never exported, renamed from lower-case names:
+ # GetTextLine, MakeTransformationChunked.
}
# http::Log --
@@ -151,15 +282,34 @@ if {[info command http::Log] eq {}} {proc http::Log {args} {}}
# See documentation for details.
#
# Arguments:
-# proto URL protocol prefix, e.g. https
-# port Default port for protocol
-# command Command to use to create socket
+# proto URL protocol prefix, e.g. https
+# port Default port for protocol
+# command Command to use to create socket
+# socketCmdVarName (optional) name of variable provided by the protocol
+# handler whose value is the callback used by argument
+# "command" to open a socket. The default value "::socket"
+# will be overwritten by http.
+# useSockThread (optional, boolean)
+# endToEndProxy (optional, boolean)
# Results:
-# list of port and command that was registered.
+# list of port, command, variable name, (boolean) threadability,
+# and (boolean) endToEndProxy that was registered.
-proc http::register {proto port command} {
+proc http::register {proto port command {socketCmdVarName {}} {useSockThread 0} {endToEndProxy 0}} {
variable urlTypes
- set urlTypes([string tolower $proto]) [list $port $command]
+ set lower [string tolower $proto]
+ if {[info exists urlTypes($lower)]} {
+ unregister $lower
+ }
+ set urlTypes($lower) [list $port $command $socketCmdVarName $useSockThread $endToEndProxy]
+
+ # If the external handler for protocol $proto has given $socketCmdVarName the expected
+ # value "::socket", overwrite it with the new value.
+ if {($socketCmdVarName ne {}) && ([set $socketCmdVarName] eq {::socket})} {
+ set $socketCmdVarName ::http::socketAsCallback
+ }
+
+ return $urlTypes($lower)
}
# http::unregister --
@@ -169,7 +319,8 @@ proc http::register {proto port command} {
# Arguments:
# proto URL protocol prefix, e.g. https
# Results:
-# list of port and command that was unregistered.
+# list of port, command, variable name, (boolean) useSockThread,
+# and (boolean) endToEndProxy that was unregistered.
proc http::unregister {proto} {
variable urlTypes
@@ -178,6 +329,13 @@ proc http::unregister {proto} {
return -code error "unsupported url type \"$proto\""
}
set old $urlTypes($lower)
+
+ # Restore the external handler's original value for $socketCmdVarName.
+ lassign $old defport defcmd socketCmdVarName useSockThread endToEndProxy
+ if {($socketCmdVarName ne {}) && ([set $socketCmdVarName] eq {::http::socketAsCallback})} {
+ set $socketCmdVarName ::socket
+ }
+
unset urlTypes($lower)
return $old
}
@@ -189,7 +347,7 @@ proc http::unregister {proto} {
# Arguments:
# args Options parsed by the procedure.
# Results:
-# TODO
+# TODO
proc http::config {args} {
variable http
@@ -210,14 +368,48 @@ proc http::config {args} {
return -code error "Unknown option $flag, must be: $usage"
}
return $http($flag)
+ } elseif {[llength $args] % 2} {
+ return -code error "If more than one argument is supplied, the\
+ number of arguments must be even"
} else {
foreach {flag value} $args {
if {![regexp -- $pat $flag]} {
return -code error "Unknown option $flag, must be: $usage"
}
+ if {($flag eq {-threadlevel}) && ($value ni {0 1 2})} {
+ return -code error {Option -threadlevel must be 0, 1 or 2}
+ }
set http($flag) $value
}
+ return
+ }
+}
+
+# ------------------------------------------------------------------------------
+# Proc http::reasonPhrase
+# ------------------------------------------------------------------------------
+# Command to return the IANA-recommended "reason phrase" for a HTTP Status Code.
+# Information obtained from:
+# https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+#
+# Arguments:
+# code - A valid HTTP Status Code (integer from 100 to 599)
+#
+# Return Value: the reason phrase
+# ------------------------------------------------------------------------------
+
+proc http::reasonPhrase {code} {
+ variable reasonDict
+ if {![regexp -- {^[1-5][0-9][0-9]$} $code]} {
+ set msg {argument must be a three-digit integer from 100 to 599}
+ return -code error $msg
}
+ if {[dict exists $reasonDict $code]} {
+ set reason [dict get $reasonDict $code]
+ } else {
+ set reason Unassigned
+ }
+ return $reason
}
# http::Finish --
@@ -233,7 +425,7 @@ proc http::config {args} {
# reported to two places.
#
# Side Effects:
-# May close the socket.
+# May close the socket.
proc http::Finish {token {errormsg ""} {skipCB 0}} {
variable socketMapping
@@ -241,8 +433,11 @@ proc http::Finish {token {errormsg ""} {skipCB 0}} {
variable socketWrState
variable socketRdQueue
variable socketWrQueue
+ variable socketPhQueue
variable socketClosing
variable socketPlayCmd
+ variable socketCoEvent
+ variable socketProxyId
variable $token
upvar 0 $token state
@@ -252,16 +447,29 @@ proc http::Finish {token {errormsg ""} {skipCB 0}} {
set state(error) [list $errormsg $errorInfo $errorCode]
set state(status) "error"
}
- if {[info commands ${token}EventCoroutine] ne {}} {
- rename ${token}EventCoroutine {}
+ if {[info commands ${token}--EventCoroutine] ne {}} {
+ rename ${token}--EventCoroutine {}
+ }
+ if {[info commands ${token}--SocketCoroutine] ne {}} {
+ rename ${token}--SocketCoroutine {}
+ }
+ if {[info exists state(socketcoro)]} {
+ Log $token Cancel socket after-idle event (Finish)
+ after cancel $state(socketcoro)
+ unset state(socketcoro)
}
# Is this an upgrade request/response?
set upgradeResponse \
- [expr { [info exists state(upgradeRequest)] && $state(upgradeRequest)
- && [info exists state(http)] && [ncode $token] eq {101}
- && [info exists state(connection)] && "upgrade" in $state(connection)
- && [info exists state(upgrade)] && "" ne $state(upgrade)}]
+ [expr { [info exists state(upgradeRequest)]
+ && $state(upgradeRequest)
+ && [info exists state(http)]
+ && ([ncode $token] eq {101})
+ && [info exists state(connection)]
+ && ("upgrade" in $state(connection))
+ && [info exists state(upgrade)]
+ && ("" ne $state(upgrade))
+ }]
if { ($state(status) eq "timeout")
|| ($state(status) eq "error")
@@ -269,8 +477,22 @@ proc http::Finish {token {errormsg ""} {skipCB 0}} {
} {
set closeQueue 1
set connId $state(socketinfo)
- set sock $state(sock)
- CloseSocket $state(sock) $token
+ if {[info exists state(sock)]} {
+ set sock $state(sock)
+ CloseSocket $state(sock) $token
+ } else {
+ # When opening the socket and calling http::reset
+ # immediately, the socket may not yet exist.
+ # Test http-4.11 may come here.
+ }
+ if {$state(tid) ne {}} {
+ # When opening the socket in a thread, and calling http::reset
+ # immediately, the thread may still exist.
+ # Test http-4.11 may come here.
+ thread::release $state(tid)
+ set state(tid) {}
+ } else {
+ }
} elseif {$upgradeResponse} {
# Special handling for an upgrade request/response.
# - geturl ensures that this is not a "persistent" socket used for
@@ -282,12 +504,18 @@ proc http::Finish {token {errormsg ""} {skipCB 0}} {
catch {fileevent $state(sock) readable {}}
catch {fileevent $state(sock) writable {}}
} elseif {([info exists state(-keepalive)] && !$state(-keepalive))
- || ([info exists state(connection)] && ("close" in $state(connection)))
+ || ([info exists state(connection)] && ("close" in $state(connection)))
} {
set closeQueue 1
set connId $state(socketinfo)
- set sock $state(sock)
- CloseSocket $state(sock) $token
+ if {[info exists state(sock)]} {
+ set sock $state(sock)
+ CloseSocket $state(sock) $token
+ } else {
+ # When opening the socket and calling http::reset
+ # immediately, the socket may not yet exist.
+ # Test http-4.11 may come here.
+ }
} elseif {
([info exists state(-keepalive)] && $state(-keepalive))
&& ([info exists state(connection)] && ("close" ni $state(connection)))
@@ -301,7 +529,9 @@ proc http::Finish {token {errormsg ""} {skipCB 0}} {
if {[info exists state(-command)] && (!$skipCB)
&& (![info exists state(done-command-cb)])} {
set state(done-command-cb) yes
- if {[catch {eval $state(-command) {$token}} err] && $errormsg eq ""} {
+ if { [catch {namespace eval :: $state(-command) $token} err]
+ && ($errormsg eq "")
+ } {
set state(error) [list $err $errorInfo $errorCode]
set state(status) error
}
@@ -312,7 +542,9 @@ proc http::Finish {token {errormsg ""} {skipCB 0}} {
&& ($socketMapping($connId) eq $sock)
} {
http::CloseQueuedQueries $connId $token
+ # This calls Unset. Other cases do not need the call.
}
+ return
}
# http::KeepSocket -
@@ -334,8 +566,11 @@ proc http::KeepSocket {token} {
variable socketWrState
variable socketRdQueue
variable socketWrQueue
+ variable socketPhQueue
variable socketClosing
variable socketPlayCmd
+ variable socketCoEvent
+ variable socketProxyId
variable $token
upvar 0 $token state
@@ -370,9 +605,6 @@ proc http::KeepSocket {token} {
# queued, arrange to read it.
set token3 [lindex $socketRdQueue($connId) 0]
set socketRdQueue($connId) [lrange $socketRdQueue($connId) 1 end]
- variable $token3
- upvar 0 $token3 state3
- set tk2 [namespace tail $token3]
#Log pipelined, GRANT read access to $token3 in KeepSocket
set socketRdState($connId) $token3
@@ -411,8 +643,7 @@ proc http::KeepSocket {token} {
# first item in the write queue, a non-pipelined request that is
# waiting for the read queue to empty. That has now happened: so
# give that request read and write access.
- variable $token3
- set conn [set ${token3}(tmpConnArgs)]
+ set conn [set ${token3}(connArgs)]
#Log nonpipeline, GRANT r/w access to $token3 in KeepSocket
set socketRdState($connId) $token3
set socketWrState($connId) $token3
@@ -456,8 +687,7 @@ proc http::KeepSocket {token} {
# Code:
# - The code is the same as the code below for the nonpipelined
# case with a queued request.
- variable $token3
- set conn [set ${token3}(tmpConnArgs)]
+ set conn [set ${token3}(connArgs)]
#Log nonpipeline, GRANT r/w access to $token3 in KeepSocket
set socketRdState($connId) $token3
set socketWrState($connId) $token3
@@ -478,8 +708,7 @@ proc http::KeepSocket {token} {
# If the next request is pipelined, it receives premature read
# access to the socket. This is not a problem.
set token3 [lindex $socketWrQueue($connId) 0]
- variable $token3
- set conn [set ${token3}(tmpConnArgs)]
+ set conn [set ${token3}(connArgs)]
#Log nonpipeline, GRANT r/w access to $token3 in KeepSocket
set socketRdState($connId) $token3
set socketWrState($connId) $token3
@@ -498,6 +727,7 @@ proc http::KeepSocket {token} {
# There is no socketMapping($state(socketinfo)), so it does not matter
# that CloseQueuedQueries is not called.
}
+ return
}
# http::CheckEof -
@@ -523,6 +753,7 @@ proc http::CheckEof {sock} {
# will then be error-handled.
CloseSocket $sock
}
+ return
}
# http::CloseSocket -
@@ -538,8 +769,11 @@ proc http::CloseSocket {s {token {}}} {
variable socketWrState
variable socketRdQueue
variable socketWrQueue
+ variable socketPhQueue
variable socketClosing
variable socketPlayCmd
+ variable socketCoEvent
+ variable socketProxyId
set tk [namespace tail $token]
@@ -566,18 +800,22 @@ proc http::CloseSocket {s {token {}}} {
Log "Closing connection $connId (sock $socketMapping($connId))"
if {[catch {close $socketMapping($connId)} err]} {
Log "Error closing connection: $err"
+ } else {
}
if {$token eq {}} {
# Cases with a non-empty token are handled by Finish, so the tokens
# are finished in connection order.
http::CloseQueuedQueries $connId
+ } else {
}
} else {
Log "Closing socket $s (no connection info)"
if {[catch {close $s} err]} {
Log "Error closing socket: $err"
+ } else {
}
}
+ return
}
# http::CloseQueuedQueries
@@ -594,9 +832,13 @@ proc http::CloseQueuedQueries {connId {token {}}} {
variable socketWrState
variable socketRdQueue
variable socketWrQueue
+ variable socketPhQueue
variable socketClosing
variable socketPlayCmd
+ variable socketCoEvent
+ variable socketProxyId
+ ##Log CloseQueuedQueries $connId $token
if {![info exists socketMapping($connId)]} {
# Command has already been called.
# Don't come here again - especially recursively.
@@ -620,6 +862,7 @@ proc http::CloseQueuedQueries {connId {token {}}} {
# - Also clear the queues to prevent calls to Finish that would set the
# state for the requests that will be retried to "finished with error
# status".
+ # - At this stage socketPhQueue is empty.
set unfinished $socketPlayCmd($connId)
set socketRdQueue($connId) {}
set socketWrQueue($connId) {}
@@ -631,9 +874,11 @@ proc http::CloseQueuedQueries {connId {token {}}} {
if {$unfinished ne {}} {
Log ^R$tk Any unfinished transactions (excluding $token) failed \
- - token $token
+ - token $token - unfinished $unfinished
{*}$unfinished
+ # Calls ReplayIfClose.
}
+ return
}
# http::Unset
@@ -649,8 +894,11 @@ proc http::Unset {connId} {
variable socketWrState
variable socketRdQueue
variable socketWrQueue
+ variable socketPhQueue
variable socketClosing
variable socketPlayCmd
+ variable socketCoEvent
+ variable socketProxyId
unset socketMapping($connId)
unset socketRdState($connId)
@@ -659,6 +907,8 @@ proc http::Unset {connId} {
unset -nocomplain socketWrQueue($connId)
unset -nocomplain socketClosing($connId)
unset -nocomplain socketPlayCmd($connId)
+ unset -nocomplain socketProxyId($connId)
+ return
}
# http::reset --
@@ -670,7 +920,7 @@ proc http::Unset {connId} {
# why Status info.
#
# Side Effects:
-# See Finish
+# See Finish
proc http::reset {token {why reset}} {
variable $token
@@ -683,7 +933,9 @@ proc http::reset {token {why reset}} {
set errorlist $state(error)
unset state
eval ::error $errorlist
+ # i.e. error msg errorInfo errorCode
}
+ return
}
# http::geturl --
@@ -699,15 +951,99 @@ proc http::reset {token {why reset}} {
# array that the caller should unset to garbage collect the state.
proc http::geturl {url args} {
+ variable urlTypes
+
+ # - If ::tls::socketCmd has its default value "::socket", change it to the
+ # new value ::http::socketAsCallback.
+ # - If the old value is different, then it has been modified either by the
+ # script or by the Tcl installation, and replaced by a new command. The
+ # script or installation that modified ::tls::socketCmd is also
+ # responsible for integrating ::http::socketAsCallback into its own "new"
+ # command, if it wishes to do so.
+ # - Commands that open a socket:
+ # - ::socket - basic
+ # - ::http::AltSocket - can use a thread to avoid blockage by slow
+ # DNS lookup. See http::config option
+ # -threadlevel.
+ # - ::http::socketAsCallback - as ::http::AltSocket, but can also open a
+ # socket for HTTPS/TLS through a proxy.
+
+ set token [CreateToken $url {*}$args]
+ variable $token
+ upvar 0 $token state
+
+ AsyncTransaction $token
+
+ # --------------------------------------------------------------------------
+ # Synchronous Call to http::geturl
+ # --------------------------------------------------------------------------
+ # - If the call to http::geturl is asynchronous, it is now complete (apart
+ # from delivering the return value).
+ # - If the call to http::geturl is synchronous, the command must now wait
+ # for the HTTP transaction to be completed. The call to http::wait uses
+ # vwait, which may be inappropriate if the caller makes other HTTP
+ # requests in the background.
+ # --------------------------------------------------------------------------
+
+ if {![info exists state(-command)]} {
+ # geturl does EVERYTHING asynchronously, so if the user
+ # calls it synchronously, we just do a wait here.
+ http::wait $token
+
+ if {![info exists state]} {
+ # If we timed out then Finish has been called and the users
+ # command callback may have cleaned up the token. If so we end up
+ # here with nothing left to do.
+ return $token
+ } elseif {$state(status) eq "error"} {
+ # Something went wrong while trying to establish the connection.
+ # Clean up after events and such, but DON'T call the command
+ # callback (if available) because we're going to throw an
+ # exception from here instead.
+ set err [lindex $state(error) 0]
+ cleanup $token
+ return -code error $err
+ }
+ }
+
+ return $token
+}
+
+# ------------------------------------------------------------------------------
+# Proc http::CreateToken
+# ------------------------------------------------------------------------------
+# Command to convert arguments into an initialised request token.
+# The return value is the variable name of the token.
+#
+# Other effects:
+# - Sets ::http::http(usingThread) if not already done
+# - Sets ::http::http(uid) if not already done
+# - Increments ::http::http(uid)
+# - May increment ::http::TmpSockCounter
+# - Alters ::http::socketPlayCmd, ::http::socketWrQueue if a -keepalive 1
+# request is appended to the queue of a persistent socket that is already
+# scheduled to close.
+# This also sets state(alreadyQueued) to 1.
+# - Alters ::http::socketPhQueue if a -keepalive 1 request is appended to the
+# queue of a persistent socket that has not yet been created (and is therefore
+# represented by a placeholder).
+# This also sets state(ReusingPlaceholder) to 1.
+# ------------------------------------------------------------------------------
+
+proc http::CreateToken {url args} {
variable http
variable urlTypes
variable defaultCharset
variable defaultKeepalive
variable strict
+ variable TmpSockCounter
# Initialize the state variable, an array. We'll return the name of this
# array as the token for the transaction.
+ if {![info exists http(usingThread)]} {
+ set http(usingThread) 0
+ }
if {![info exists http(uid)]} {
set http(uid) 0
}
@@ -731,6 +1067,7 @@ proc http::geturl {url args} {
-type application/x-www-form-urlencoded
-queryprogress {}
-protocol 1.1
+ -guesstype 0
binary 0
state created
meta {}
@@ -740,11 +1077,21 @@ proc http::geturl {url args} {
totalsize 0
querylength 0
queryoffset 0
- type text/html
+ type application/octet-stream
body {}
status ""
http ""
+ httpResponse {}
+ responseCode {}
+ reasonPhrase {}
connection keep-alive
+ tid {}
+ requestHeaders {}
+ requestLine {}
+ transfer {}
+ proxyUsed none
+ protoSockThread 0
+ protoProxyConn 0
}
set state(-keepalive) $defaultKeepalive
set state(-strict) $strict
@@ -752,6 +1099,7 @@ proc http::geturl {url args} {
array set type {
-binary boolean
-blocksize integer
+ -guesstype boolean
-queryblocksize integer
-strict boolean
-timeout integer
@@ -760,7 +1108,7 @@ proc http::geturl {url args} {
}
set state(charset) $defaultCharset
set options {
- -binary -blocksize -channel -command -handler -headers -keepalive
+ -binary -blocksize -channel -command -guesstype -handler -headers -keepalive
-method -myaddr -progress -protocol -query -queryblocksize
-querychannel -queryprogress -strict -timeout -type -validate
}
@@ -771,7 +1119,7 @@ proc http::geturl {url args} {
if {[regexp -- $pat $flag]} {
# Validate numbers
if { [info exists type($flag)]
- && (![string is $type($flag) -strict $value])
+ && (![string is $type($flag) -strict $value])
} {
unset $token
return -code error \
@@ -779,8 +1127,8 @@ proc http::geturl {url args} {
}
if {($flag eq "-headers") && ([llength $value] % 2 != 0)} {
unset $token
- return -code error \
- "Bad value for $flag ($value), number of list elements must be even"
+ return -code error "Bad value for $flag ($value), number\
+ of list elements must be even"
}
set state($flag) $value
} else {
@@ -832,6 +1180,9 @@ proc http::geturl {url args} {
# Note that the RE actually combines the user and password parts, as
# recommended in RFC 3986. Indeed, that RFC states that putting passwords
# in URLs is a Really Bad Idea, something with which I would agree utterly.
+ # RFC 9110 Sec 4.2.4 goes further than this, and deprecates the format
+ # "user:password@". It is retained here for backward compatibility,
+ # but its use is not recommended.
#
# From a validation perspective, we need to ensure that the parts of the
# URL that are going to the server are correctly encoded. This is only
@@ -935,8 +1286,16 @@ proc http::geturl {url args} {
unset $token
return -code error "Unsupported URL type \"$proto\""
}
- set defport [lindex $urlTypes($lower) 0]
- set defcmd [lindex $urlTypes($lower) 1]
+ lassign $urlTypes($lower) defport defcmd socketCmdVarName useSockThread end2EndProxy
+
+ # If the external handler for protocol $proto has given $socketCmdVarName the expected
+ # value "::socket", overwrite it with the new value.
+ if {($socketCmdVarName ne {}) && ([set $socketCmdVarName] eq {::socket})} {
+ set $socketCmdVarName ::http::socketAsCallback
+ }
+
+ set state(protoSockThread) $useSockThread
+ set state(protoProxyConn) $end2EndProxy
if {$port eq ""} {
set port $defport
@@ -944,6 +1303,9 @@ proc http::geturl {url args} {
if {![catch {$http(-proxyfilter) $host} proxy]} {
set phost [lindex $proxy 0]
set pport [lindex $proxy 1]
+ } else {
+ set phost {}
+ set pport {}
}
# OK, now reassemble into a full URL
@@ -957,20 +1319,9 @@ proc http::geturl {url args} {
append url : $port
}
append url $srvurl
- # Don't append the fragment!
+ # Don't append the fragment! RFC 7230 Sec 5.1
set state(url) $url
- set sockopts [list -async]
-
- # If we are using the proxy, we must pass in the full URL that includes
- # the server name.
-
- if {[info exists phost] && ($phost ne "")} {
- set srvurl $url
- set targetAddr [list $phost $pport]
- } else {
- set targetAddr [list $host $port]
- }
# Proxy connections aren't shared among different hosts.
set state(socketinfo) $host:$port
@@ -986,6 +1337,7 @@ proc http::geturl {url args} {
[GetFieldValue $state(-headers) Upgrade]]
set state(upgradeRequest) [expr { "upgrade" in $connectionValues
&& [llength $upgradeValues] >= 1}]
+ set state(connectionValues) $connectionValues
if {$isQuery || $isQueryChannel} {
# It's a POST.
@@ -1024,6 +1376,40 @@ proc http::geturl {url args} {
set state(-keepalive) 0
}
+ # Handle proxy requests here for http:// but not for https://
+ # The proxying for https is done in the ::http::socketAsCallback command.
+ # A proxy request for http:// needs the full URL in the HTTP request line,
+ # including the server name.
+ # The *tls* test below attempts to describe protocols in addition to
+ # "https on port 443" that use HTTP over TLS.
+ if {($phost ne "") && (!$end2EndProxy)} {
+ set srvurl $url
+ set targetAddr [list $phost $pport]
+ set state(proxyUsed) HttpProxy
+ # The value of state(proxyUsed) none|HttpProxy depends only on the
+ # all-transactions http::config settings and on the target URL.
+ # Even if this is a persistent socket there is no need to change the
+ # value of state(proxyUsed) for other transactions that use the socket:
+ # they have the same value already.
+ } else {
+ set targetAddr [list $host $port]
+ }
+
+ set sockopts [list -async]
+
+ # Pass -myaddr directly to the socket command
+ if {[info exists state(-myaddr)]} {
+ lappend sockopts -myaddr $state(-myaddr)
+ }
+
+ if {$useSockThread} {
+ set targs [list -type $token]
+ } else {
+ set targs {}
+ }
+ set state(connArgs) [list $proto $phost $srvurl]
+ set state(openCmd) [list {*}$defcmd {*}$sockopts {*}$targs {*}$targetAddr]
+
# See if we are supposed to use a previously opened channel.
# - In principle, ANY call to http::geturl could use a previously opened
# channel if it is available - the "Connection: keep-alive" header is a
@@ -1033,15 +1419,19 @@ proc http::geturl {url args} {
# $state(socketinfo). This property simplifies the mapping of open
# channels.
set reusing 0
- set alreadyQueued 0
+ set state(alreadyQueued) 0
+ set state(ReusingPlaceholder) 0
if {$state(-keepalive)} {
variable socketMapping
variable socketRdState
variable socketWrState
variable socketRdQueue
variable socketWrQueue
+ variable socketPhQueue
variable socketClosing
variable socketPlayCmd
+ variable socketCoEvent
+ variable socketProxyId
if {[info exists socketMapping($state(socketinfo))]} {
# - If the connection is idle, it has a "fileevent readable" binding
@@ -1064,14 +1454,21 @@ proc http::geturl {url args} {
# causes a call to Finish.
set reusing 1
set sock $socketMapping($state(socketinfo))
- Log "reusing socket $sock for $state(socketinfo) - token $token"
+ set state(proxyUsed) $socketProxyId($state(socketinfo))
+ Log "reusing closing socket $sock for $state(socketinfo) - token $token"
- set alreadyQueued 1
+ set state(alreadyQueued) 1
lassign $socketPlayCmd($state(socketinfo)) com0 com1 com2 com3
lappend com3 $token
set socketPlayCmd($state(socketinfo)) [list $com0 $com1 $com2 $com3]
lappend socketWrQueue($state(socketinfo)) $token
- } elseif {[catch {fconfigure $socketMapping($state(socketinfo))}]} {
+ ##Log socketPlayCmd($state(socketinfo)) is $socketPlayCmd($state(socketinfo))
+ ##Log socketWrQueue($state(socketinfo)) is $socketWrQueue($state(socketinfo))
+ } elseif {
+ [catch {fconfigure $socketMapping($state(socketinfo))}]
+ && (![SockIsPlaceHolder $socketMapping($state(socketinfo))])
+ } {
+ ###Log "Socket $socketMapping($state(socketinfo)) for $state(socketinfo)"
# FIXME Is it still possible for this code to be executed? If
# so, this could be another place to call TestForReplay,
# rather than discarding the queued transactions.
@@ -1085,43 +1482,115 @@ proc http::geturl {url args} {
Unset $state(socketinfo)
} else {
# Use the persistent socket.
- # The socket may not be ready to write: an earlier request might
- # still be still writing (in the pipelined case) or
- # writing/reading (in the nonpipeline case). This possibility
- # is handled by socketWrQueue later in this command.
+ # - The socket may not be ready to write: an earlier request might
+ # still be still writing (in the pipelined case) or
+ # writing/reading (in the nonpipeline case). This possibility
+ # is handled by socketWrQueue later in this command.
+ # - The socket may not yet exist, and be defined with a placeholder.
set reusing 1
set sock $socketMapping($state(socketinfo))
- Log "reusing socket $sock for $state(socketinfo) - token $token"
-
+ set state(proxyUsed) $socketProxyId($state(socketinfo))
+ if {[SockIsPlaceHolder $sock]} {
+ set state(ReusingPlaceholder) 1
+ lappend socketPhQueue($sock) $token
+ } else {
+ }
+ Log "reusing open socket $sock for $state(socketinfo) - token $token"
}
# Do not automatically close the connection socket.
set state(connection) keep-alive
}
}
- if {$reusing} {
- # Define state(tmpState) and state(tmpOpenCmd) for use
- # by http::ReplayIfDead if the persistent connection has died.
- set state(tmpState) [array get state]
+ set state(reusing) $reusing
+ unset reusing
- # Pass -myaddr directly to the socket command
- if {[info exists state(-myaddr)]} {
- lappend sockopts -myaddr $state(-myaddr)
- }
+ if {![info exists sock]} {
+ # N.B. At this point ([info exists sock] == $state(reusing)).
+ # This will no longer be true after we set a value of sock here.
+ # Give the socket a placeholder name.
+ set sock HTTP_PLACEHOLDER_[incr TmpSockCounter]
+ }
+ set state(sock) $sock
- set state(tmpOpenCmd) [list {*}$defcmd {*}$sockopts {*}$targetAddr]
+ if {$state(reusing)} {
+ # Define these for use (only) by http::ReplayIfDead if the persistent
+ # connection has died.
+ set state(tmpConnArgs) $state(connArgs)
+ set state(tmpState) [array get state]
+ set state(tmpOpenCmd) $state(openCmd)
}
+ return $token
+}
- set state(reusing) $reusing
- # Excluding ReplayIfDead and the decision whether to call it, there are four
- # places outside http::geturl where state(reusing) is used:
- # - Connected - if reusing and not pipelined, start the state(-timeout)
- # timeout (when writing).
- # - DoneRequest - if reusing and pipelined, send the next pipelined write
- # - Event - if reusing and pipelined, start the state(-timeout)
- # timeout (when reading).
- # - Event - if (not reusing) and pipelined, send the next pipelined
- # write
+
+# ------------------------------------------------------------------------------
+# Proc ::http::SockIsPlaceHolder
+# ------------------------------------------------------------------------------
+# Command to return 0 if the argument is a genuine socket handle, or 1 if is a
+# placeholder value generated by geturl or ReplayCore before the real socket is
+# created.
+#
+# Arguments:
+# sock - either a valid socket handle or a placeholder value
+#
+# Return Value: 0 or 1
+# ------------------------------------------------------------------------------
+
+proc http::SockIsPlaceHolder {sock} {
+ expr {[string range $sock 0 16] eq {HTTP_PLACEHOLDER_}}
+}
+
+
+# ------------------------------------------------------------------------------
+# state(reusing)
+# ------------------------------------------------------------------------------
+# - state(reusing) is set by geturl, ReplayCore
+# - state(reusing) is used by geturl, AsyncTransaction, OpenSocket,
+# ConfigureNewSocket, and ScheduleRequest when creating and configuring the
+# connection.
+# - state(reusing) is used by Connect, Connected, Event x 2 when deciding
+# whether to call TestForReplay.
+# - Other places where state(reusing) is used:
+# - Connected - if reusing and not pipelined, start the state(-timeout)
+# timeout (when writing).
+# - DoneRequest - if reusing and pipelined, send the next pipelined write
+# - Event - if reusing and pipelined, start the state(-timeout)
+# timeout (when reading).
+# - Event - if (not reusing) and pipelined, send the next pipelined
+# write.
+# ------------------------------------------------------------------------------
+
+
+# ------------------------------------------------------------------------------
+# Proc http::AsyncTransaction
+# ------------------------------------------------------------------------------
+# This command is called by geturl and ReplayCore to prepare the HTTP
+# transaction prescribed by a suitably prepared token.
+#
+# Arguments:
+# token - connection token (name of an array)
+#
+# Return Value: none
+# ------------------------------------------------------------------------------
+
+proc http::AsyncTransaction {token} {
+ variable $token
+ upvar 0 $token state
+ set tk [namespace tail $token]
+
+ variable socketMapping
+ variable socketRdState
+ variable socketWrState
+ variable socketRdQueue
+ variable socketWrQueue
+ variable socketPhQueue
+ variable socketClosing
+ variable socketPlayCmd
+ variable socketCoEvent
+ variable socketProxyId
+
+ set sock $state(sock)
# See comments above re the start of this timeout in other cases.
if {(!$state(reusing)) && ($state(-timeout) > 0)} {
@@ -1129,26 +1598,183 @@ proc http::geturl {url args} {
[list http::reset $token timeout]]
}
- if {![info exists sock]} {
- # Pass -myaddr directly to the socket command
- if {[info exists state(-myaddr)]} {
- lappend sockopts -myaddr $state(-myaddr)
- }
- set pre [clock milliseconds]
- ##Log pre socket opened, - token $token
- ##Log [concat $defcmd $sockopts $targetAddr] - token $token
- if {[catch {eval $defcmd $sockopts $targetAddr} sock errdict]} {
- # Something went wrong while trying to establish the connection.
- # Clean up after events and such, but DON'T call the command
- # callback (if available) because we're going to throw an
- # exception from here instead.
+ if { $state(-keepalive)
+ && (![info exists socketMapping($state(socketinfo))])
+ } {
+ # This code is executed only for the first -keepalive request on a
+ # socket. It makes the socket persistent.
+ ##Log " PreparePersistentConnection" $token -- $sock -- DO
+ set DoLater [PreparePersistentConnection $token]
+ } else {
+ ##Log " PreparePersistentConnection" $token -- $sock -- SKIP
+ set DoLater {-traceread 0 -tracewrite 0}
+ }
- set state(sock) NONE
- Finish $token $sock 1
- cleanup $token
- dict unset errdict -level
- return -options $errdict $sock
+ if {$state(ReusingPlaceholder)} {
+ # - This request was added to the socketPhQueue of a persistent
+ # connection.
+ # - But the connection has not yet been created and is a placeholder;
+ # - And the placeholder was created by an earlier request.
+ # - When that earlier request calls OpenSocket, its placeholder is
+ # replaced with a true socket, and it then executes the equivalent of
+ # OpenSocket for any subsequent requests that have
+ # $state(ReusingPlaceholder).
+ Log >J$tk after idle coro NO - ReusingPlaceholder
+ } elseif {$state(alreadyQueued)} {
+ # - This request was added to the socketWrQueue and socketPlayCmd
+ # of a persistent connection that will close at the end of its current
+ # read operation.
+ Log >J$tk after idle coro NO - alreadyQueued
+ } else {
+ Log >J$tk after idle coro YES
+ set CoroName ${token}--SocketCoroutine
+ set cancel [after idle [list coroutine $CoroName ::http::OpenSocket \
+ $token $DoLater]]
+ dict set socketCoEvent($state(socketinfo)) $token $cancel
+ set state(socketcoro) $cancel
+ }
+
+ return
+}
+
+
+# ------------------------------------------------------------------------------
+# Proc http::PreparePersistentConnection
+# ------------------------------------------------------------------------------
+# This command is called by AsyncTransaction to initialise a "persistent
+# connection" based upon a socket placeholder. It is called the first time the
+# socket is associated with a "-keepalive" request.
+#
+# Arguments:
+# token - connection token (name of an array)
+#
+# Return Value: - DoLater, a dictionary of boolean values listing unfinished
+# tasks; to be passed to ConfigureNewSocket via OpenSocket.
+# ------------------------------------------------------------------------------
+
+proc http::PreparePersistentConnection {token} {
+ variable $token
+ upvar 0 $token state
+
+ variable socketMapping
+ variable socketRdState
+ variable socketWrState
+ variable socketRdQueue
+ variable socketWrQueue
+ variable socketPhQueue
+ variable socketClosing
+ variable socketPlayCmd
+ variable socketCoEvent
+ variable socketProxyId
+
+ set DoLater {-traceread 0 -tracewrite 0}
+ set socketMapping($state(socketinfo)) $state(sock)
+ set socketProxyId($state(socketinfo)) $state(proxyUsed)
+ # - The value of state(proxyUsed) was set in http::CreateToken to either
+ # "none" or "HttpProxy".
+ # - $token is the first transaction to use this placeholder, so there are
+ # no other tokens whose (proxyUsed) must be modified.
+
+ if {![info exists socketRdState($state(socketinfo))]} {
+ set socketRdState($state(socketinfo)) {}
+ # set varName ::http::socketRdState($state(socketinfo))
+ # trace add variable $varName unset ::http::CancelReadPipeline
+ dict set DoLater -traceread 1
+ }
+ if {![info exists socketWrState($state(socketinfo))]} {
+ set socketWrState($state(socketinfo)) {}
+ # set varName ::http::socketWrState($state(socketinfo))
+ # trace add variable $varName unset ::http::CancelWritePipeline
+ dict set DoLater -tracewrite 1
+ }
+
+ if {$state(-pipeline)} {
+ #Log new, init for pipelined, GRANT write access to $token in geturl
+ # Also grant premature read access to the socket. This is OK.
+ set socketRdState($state(socketinfo)) $token
+ set socketWrState($state(socketinfo)) $token
+ } else {
+ # socketWrState is not used by this non-pipelined transaction.
+ # We cannot leave it as "Wready" because the next call to
+ # http::geturl with a pipelined transaction would conclude that the
+ # socket is available for writing.
+ #Log new, init for nonpipeline, GRANT r/w access to $token in geturl
+ set socketRdState($state(socketinfo)) $token
+ set socketWrState($state(socketinfo)) $token
+ }
+
+ # Value of socketPhQueue() may have already been set by ReplayCore.
+ if {![info exists socketPhQueue($state(sock))]} {
+ set socketPhQueue($state(sock)) {}
+ }
+ set socketRdQueue($state(socketinfo)) {}
+ set socketWrQueue($state(socketinfo)) {}
+ set socketClosing($state(socketinfo)) 0
+ set socketPlayCmd($state(socketinfo)) {ReplayIfClose Wready {} {}}
+ set socketCoEvent($state(socketinfo)) {}
+ set socketProxyId($state(socketinfo)) {}
+
+ return $DoLater
+}
+
+# ------------------------------------------------------------------------------
+# Proc ::http::OpenSocket
+# ------------------------------------------------------------------------------
+# This command is called as a coroutine idletask to start the asynchronous HTTP
+# transaction in most cases. For the exceptions, see the calling code in
+# command AsyncTransaction.
+#
+# Arguments:
+# token - connection token (name of an array)
+# DoLater - dictionary of boolean values listing unfinished tasks
+#
+# Return Value: none
+# ------------------------------------------------------------------------------
+
+proc http::OpenSocket {token DoLater} {
+ variable $token
+ upvar 0 $token state
+ set tk [namespace tail $token]
+
+ variable socketMapping
+ variable socketRdState
+ variable socketWrState
+ variable socketRdQueue
+ variable socketWrQueue
+ variable socketPhQueue
+ variable socketClosing
+ variable socketPlayCmd
+ variable socketCoEvent
+ variable socketProxyId
+
+ Log >K$tk Start OpenSocket coroutine
+
+ if {![info exists state(-keepalive)]} {
+ # The request has already been cancelled by the calling script.
+ return
+ }
+
+ set sockOld $state(sock)
+
+ dict unset socketCoEvent($state(socketinfo)) $token
+ unset -nocomplain state(socketcoro)
+
+ if {[catch {
+ if {$state(reusing)} {
+ # If ($state(reusing)) is true, then we do not need to create a new
+ # socket, even if $sockOld is only a placeholder for a socket.
+ set sock $sockOld
} else {
+ # set sock in the [catch] below.
+ set pre [clock milliseconds]
+ ##Log pre socket opened, - token $token
+ ##Log $state(openCmd) - token $token
+ set sock [namespace eval :: $state(openCmd)]
+ set state(sock) $sock
+ # Normal return from $state(openCmd) always returns a valid socket.
+ # A TLS proxy connection with 407 or other failure from the
+ # proxy server raises an error.
+
# Initialisation of a new socket.
##Log post socket opened, - token $token
##Log socket opened, now fconfigure - token $token
@@ -1158,86 +1784,270 @@ proc http::geturl {url args} {
}
fconfigure $sock -translation {auto crlf} \
-buffersize $state(-blocksize)
+ if {[package vsatisfies [package provide Tcl] 9.0-]} {
+ fconfigure $sock -profile tcl8
+ }
##Log socket opened, DONE fconfigure - token $token
}
+
+ Log "Using $sock for $state(socketinfo) - token $token" \
+ [expr {$state(-keepalive)?"keepalive":""}]
+
+ # Code above has set state(sock) $sock
+ ConfigureNewSocket $token $sockOld $DoLater
+ ##Log OpenSocket success $sock - token $token
+ } result errdict]} {
+ ##Log OpenSocket failed $result - token $token
+ # There may be other requests in the socketPhQueue.
+ # Prepare socketPlayCmd so that Finish will replay them.
+ if { ($state(-keepalive)) && (!$state(reusing))
+ && [info exists socketPhQueue($sockOld)]
+ && ($socketPhQueue($sockOld) ne {})
+ } {
+ if {$socketMapping($state(socketinfo)) ne $sockOld} {
+ Log "WARNING: this code should not be reached.\
+ {$socketMapping($state(socketinfo)) ne $sockOld}"
+ }
+ set socketPlayCmd($state(socketinfo)) [list ReplayIfClose Wready {} $socketPhQueue($sockOld)]
+ set socketPhQueue($sockOld) {}
+ }
+ if {[string range $result 0 20] eq {proxy connect failed:}} {
+ # - The HTTPS proxy did not create a socket. The pre-existing value
+ # (a "placeholder socket") is unchanged.
+ # - The proxy returned a valid HTTP response to the failed CONNECT
+ # request, and http::SecureProxyConnect copied this to $token,
+ # and also set ${token}(connection) set to "close".
+ # - Remove the error message $result so that Finish delivers this
+ # HTTP response to the caller.
+ set result {}
+ }
+ Finish $token $result
+ # Because socket creation failed, the placeholder "socket" must be
+ # "closed" and (if persistent) removed from the persistent sockets
+ # table. In the {proxy connect failed:} case Finish does this because
+ # the value of ${token}(connection) is "close". In the other cases here,
+ # it does so because $result is non-empty.
}
- # Command [socket] is called with -async, but takes 5s to 5.1s to return,
- # with probability of order 1 in 10,000. This may be a bizarre scheduling
- # issue with my (KJN's) system (Fedora Linux).
- # This does not cause a problem (unless the request times out when this
- # command returns).
+ ##Log Leaving http::OpenSocket coroutine [info coroutine] - token $token
+ return
+}
- set state(sock) $sock
- Log "Using $sock for $state(socketinfo) - token $token" \
- [expr {$state(-keepalive)?"keepalive":""}]
- if { $state(-keepalive)
- && (![info exists socketMapping($state(socketinfo))])
- } {
- # Freshly-opened socket that we would like to become persistent.
- set socketMapping($state(socketinfo)) $sock
+# ------------------------------------------------------------------------------
+# Proc ::http::ConfigureNewSocket
+# ------------------------------------------------------------------------------
+# Command to initialise a newly-created socket. Called only from OpenSocket.
+#
+# This command is called by OpenSocket whenever a genuine socket (sockNew) has
+# been opened for for use by HTTP. It does two things:
+# (1) If $token uses a placeholder socket, this command replaces the placeholder
+# socket with the real socket, not only in $token but in all other requests
+# that use the same placeholder.
+# (2) It calls ScheduleRequest to schedule each request that uses the socket.
+#
+#
+# Value of sockOld/sockNew can be "sock" (genuine socket) or "ph" (placeholder).
+# sockNew is ${token}(sock)
+# sockOld sockNew CASES
+# sock sock (if $reusing, and sockOld is sock)
+# ph sock (if (not $reusing), and sockOld is ph)
+# ph ph (if $reusing, and sockOld is ph) - not called in this case
+# sock ph (cannot occur unless a bug) - not called in this case
+# (if (not $reusing), and sockOld is sock) - illogical
+#
+# Arguments:
+# token - connection token (name of an array)
+# sockOld - handle or placeholder used for a socket before the call to
+# OpenSocket
+# DoLater - dictionary of boolean values listing unfinished tasks
+#
+# Return Value: none
+# ------------------------------------------------------------------------------
+
+proc http::ConfigureNewSocket {token sockOld DoLater} {
+ variable $token
+ upvar 0 $token state
+ set tk [namespace tail $token]
+
+ variable socketMapping
+ variable socketRdState
+ variable socketWrState
+ variable socketRdQueue
+ variable socketWrQueue
+ variable socketPhQueue
+ variable socketClosing
+ variable socketPlayCmd
+ variable socketCoEvent
+ variable socketProxyId
+
+ set reusing $state(reusing)
+ set sock $state(sock)
+ set proxyUsed $state(proxyUsed)
+ ##Log " ConfigureNewSocket" $token $sockOld ... -- $reusing $sock $proxyUsed
+
+ if {(!$reusing) && ($sock ne $sockOld)} {
+ # Replace the placeholder value sockOld with sock.
+
+ if { [info exists socketMapping($state(socketinfo))]
+ && ($socketMapping($state(socketinfo)) eq $sockOld)
+ } {
+ set socketMapping($state(socketinfo)) $sock
+ set socketProxyId($state(socketinfo)) $proxyUsed
+ # tokens that use the placeholder $sockOld are updated below.
+ ##Log set socketMapping($state(socketinfo)) $sock
+ }
+
+ # Now finish any tasks left over from PreparePersistentConnection on
+ # the connection.
+ #
+ # The "unset" traces are fired by init (clears entire arrays), and
+ # by http::Unset.
+ # Unset is called by CloseQueuedQueries and (possibly never) by geturl.
+ #
+ # CancelReadPipeline, CancelWritePipeline call http::Finish for each
+ # token.
+ #
+ # FIXME If Finish is placeholder-aware, these traces can be set earlier,
+ # in PreparePersistentConnection.
- if {![info exists socketRdState($state(socketinfo))]} {
- set socketRdState($state(socketinfo)) {}
+ if {[dict get $DoLater -traceread]} {
set varName ::http::socketRdState($state(socketinfo))
trace add variable $varName unset ::http::CancelReadPipeline
}
- if {![info exists socketWrState($state(socketinfo))]} {
- set socketWrState($state(socketinfo)) {}
+ if {[dict get $DoLater -tracewrite]} {
set varName ::http::socketWrState($state(socketinfo))
trace add variable $varName unset ::http::CancelWritePipeline
}
+ }
- if {$state(-pipeline)} {
- #Log new, init for pipelined, GRANT write access to $token in geturl
- # Also grant premature read access to the socket. This is OK.
- set socketRdState($state(socketinfo)) $token
- set socketWrState($state(socketinfo)) $token
- } else {
- # socketWrState is not used by this non-pipelined transaction.
- # We cannot leave it as "Wready" because the next call to
- # http::geturl with a pipelined transaction would conclude that the
- # socket is available for writing.
- #Log new, init for nonpipeline, GRANT r/w access to $token in geturl
- set socketRdState($state(socketinfo)) $token
- set socketWrState($state(socketinfo)) $token
- }
+ # Do this in all cases.
+ ScheduleRequest $token
- set socketRdQueue($state(socketinfo)) {}
- set socketWrQueue($state(socketinfo)) {}
- set socketClosing($state(socketinfo)) 0
- set socketPlayCmd($state(socketinfo)) {ReplayIfClose Wready {} {}}
- }
+ # Now look at all other tokens that use the placeholder $sockOld.
+ if { (!$reusing)
+ && ($sock ne $sockOld)
+ && [info exists socketPhQueue($sockOld)]
+ } {
+ ##Log " ConfigureNewSocket" $token scheduled, now do $socketPhQueue($sockOld)
+ foreach tok $socketPhQueue($sockOld) {
+ # 1. Amend the token's (sock).
+ ##Log set ${tok}(sock) $sock
+ set ${tok}(sock) $sock
+ set ${tok}(proxyUsed) $proxyUsed
- if {![info exists phost]} {
- set phost ""
- }
- if {$reusing} {
- # For use by http::ReplayIfDead if the persistent connection has died.
- # Also used by NextPipelinedWrite.
- set state(tmpConnArgs) [list $proto $phost $srvurl]
+ # 2. Schedule the token's HTTP request.
+ # Every token in socketPhQueue(*) has reusing 1 alreadyQueued 0.
+ set ${tok}(reusing) 1
+ set ${tok}(alreadyQueued) 0
+ ScheduleRequest $tok
+ }
+ set socketPhQueue($sockOld) {}
}
+ ##Log " ConfigureNewSocket" $token DONE
- # The element socketWrState($connId) has a value which is either the name of
- # the token that is permitted to write to the socket, or "Wready" if no
- # token is permitted to write.
- #
- # The code that sets the value to Wready immediately calls
- # http::NextPipelinedWrite, which examines socketWrQueue($connId) and
- # processes the next request in the queue, if there is one. The value
- # Wready is not found when the interpreter is in the event loop unless the
- # socket is idle.
- #
- # The element socketRdState($connId) has a value which is either the name of
- # the token that is permitted to read from the socket, or "Rready" if no
- # token is permitted to read.
- #
- # The code that sets the value to Rready then examines
- # socketRdQueue($connId) and processes the next request in the queue, if
- # there is one. The value Rready is not found when the interpreter is in
- # the event loop unless the socket is idle.
+ return
+}
+
+
+# ------------------------------------------------------------------------------
+# The values of array variables socketMapping etc.
+# ------------------------------------------------------------------------------
+# connId "$host:$port"
+# socketMapping($connId) the handle or placeholder for the socket that is used
+# for "-keepalive 1" requests to $connId.
+# socketRdState($connId) the token that is currently reading from the socket.
+# Other values: Rready (ready for next token to read).
+# socketWrState($connId) the token that is currently writing to the socket.
+# Other values: Wready (ready for next token to write),
+# peNding (would be ready for next write, except that
+# the integrity of a non-pipelined transaction requires
+# waiting until the read(s) in progress are finished).
+# socketRdQueue($connId) List of tokens that are queued for reading later.
+# socketWrQueue($connId) List of tokens that are queued for writing later.
+# socketPhQueue($sock) List of tokens that are queued to use a placeholder
+# socket, when the real socket has not yet been created.
+# socketClosing($connId) (boolean) true iff a server response header indicates
+# that the server will close the connection at the end of
+# the current response.
+# socketPlayCmd($connId) The command to execute to replay pending and
+# part-completed transactions if the socket closes early.
+# socketCoEvent($connId) Identifier for the "after idle" event that will launch
+# an OpenSocket coroutine to open or re-use a socket.
+# socketProxyId($connId) The type of proxy that this socket uses: values are
+# those of state(proxyUsed) i.e. none, HttpProxy,
+# SecureProxy, and SecureProxyFailed.
+# The value is not used for anything by http, its purpose
+# is to set the value of state() for caller information.
+# ------------------------------------------------------------------------------
+
+
+# ------------------------------------------------------------------------------
+# Using socketWrState(*), socketWrQueue(*), socketRdState(*), socketRdQueue(*)
+# ------------------------------------------------------------------------------
+# The element socketWrState($connId) has a value which is either the name of
+# the token that is permitted to write to the socket, or "Wready" if no
+# token is permitted to write.
+#
+# The code that sets the value to Wready immediately calls
+# http::NextPipelinedWrite, which examines socketWrQueue($connId) and
+# processes the next request in the queue, if there is one. The value
+# Wready is not found when the interpreter is in the event loop unless the
+# socket is idle.
+#
+# The element socketRdState($connId) has a value which is either the name of
+# the token that is permitted to read from the socket, or "Rready" if no
+# token is permitted to read.
+#
+# The code that sets the value to Rready then examines
+# socketRdQueue($connId) and processes the next request in the queue, if
+# there is one. The value Rready is not found when the interpreter is in
+# the event loop unless the socket is idle.
+# ------------------------------------------------------------------------------
+
+
+# ------------------------------------------------------------------------------
+# Proc http::ScheduleRequest
+# ------------------------------------------------------------------------------
+# Command to either begin the HTTP request, or add it to the appropriate queue.
+# Called from two places in ConfigureNewSocket.
+#
+# Arguments:
+# token - connection token (name of an array)
+#
+# Return Value: none
+# ------------------------------------------------------------------------------
+
+proc http::ScheduleRequest {token} {
+ variable $token
+ upvar 0 $token state
+ set tk [namespace tail $token]
+
+ Log >L$tk ScheduleRequest
+
+ variable socketMapping
+ variable socketRdState
+ variable socketWrState
+ variable socketRdQueue
+ variable socketWrQueue
+ variable socketPhQueue
+ variable socketClosing
+ variable socketPlayCmd
+ variable socketCoEvent
+ variable socketProxyId
+
+ set Unfinished 0
+
+ set reusing $state(reusing)
+ set sockNew $state(sock)
- if {$alreadyQueued} {
+ # The "if" tests below: must test against the current values of
+ # socketWrState, socketRdState, and so the tests must be done here,
+ # not earlier in PreparePersistentConnection.
+
+ if {$state(alreadyQueued)} {
+ # The request has been appended to the queue of a persistent socket
+ # (that is scheduled to close and have its queue replayed).
+ #
# A write may or may not be in progress. There is no need to set
# socketWrState to prevent another call stealing write access - all
# subsequent calls on this socket will come here because the socket
@@ -1270,53 +2080,78 @@ proc http::geturl {url args} {
# pipelined request jumping the queue.
##Log "HTTP request for token $token is queued for nonpipeline use"
#Log re-use nonpipeline, GRANT delayed write access to $token in geturl
-
set socketWrState($state(socketinfo)) peNding
lappend socketWrQueue($state(socketinfo)) $token
} else {
if {$reusing && $state(-pipeline)} {
- #Log re-use pipelined, GRANT write access to $token in geturl
+ #Log new, init for pipelined, GRANT write access to $token in geturl
+ # DO NOT grant premature read access to the socket.
+ # set socketRdState($state(socketinfo)) $token
set socketWrState($state(socketinfo)) $token
-
} elseif {$reusing} {
- # Cf tests above - both are ready.
- #Log re-use nonpipeline, GRANT r/w access to $token in geturl
+ # socketWrState is not used by this non-pipelined transaction.
+ # We cannot leave it as "Wready" because the next call to
+ # http::geturl with a pipelined transaction would conclude that the
+ # socket is available for writing.
+ #Log new, init for nonpipeline, GRANT r/w access to $token in geturl
set socketRdState($state(socketinfo)) $token
set socketWrState($state(socketinfo)) $token
+ } else {
}
- # All (!$reusing) cases come here, and also some $reusing cases if the
- # connection is ready.
+ # Process the request now.
+ # - Command is not called unless $state(sock) is a real socket handle
+ # and not a placeholder.
+ # - All (!$reusing) cases come here.
+ # - Some $reusing cases come here too if the connection is
+ # marked as ready. Those $reusing cases are:
+ # $reusing && ($socketWrState($state(socketinfo)) eq "Wready") &&
+ # EITHER !$pipeline && ($socketRdState($state(socketinfo)) eq "Rready")
+ # OR $pipeline
+ #
#Log ---- $state(socketinfo) << conn to $token for HTTP request (a)
+ ##Log " ScheduleRequest" $token -- fileevent $state(sock) writable for $token
# Connect does its own fconfigure.
- fileevent $sock writable \
- [list http::Connect $token $proto $phost $srvurl]
- }
- # Wait for the connection to complete.
- if {![info exists state(-command)]} {
- # geturl does EVERYTHING asynchronously, so if the user
- # calls it synchronously, we just do a wait here.
- http::wait $token
+ lassign $state(connArgs) proto phost srvurl
- if {![info exists state]} {
- # If we timed out then Finish has been called and the users
- # command callback may have cleaned up the token. If so we end up
- # here with nothing left to do.
- return $token
- } elseif {$state(status) eq "error"} {
- # Something went wrong while trying to establish the connection.
- # Clean up after events and such, but DON'T call the command
- # callback (if available) because we're going to throw an
- # exception from here instead.
- set err [lindex $state(error) 0]
- cleanup $token
- return -code error $err
+ if {[catch {
+ fileevent $state(sock) writable \
+ [list http::Connect $token $proto $phost $srvurl]
+ } res opts]} {
+ # The socket no longer exists.
+ ##Log bug -- socket gone -- $res -- $opts
}
+
}
- ##Log Leaving http::geturl - token $token
- return $token
+
+ return
+}
+
+
+# ------------------------------------------------------------------------------
+# Proc http::SendHeader
+# ------------------------------------------------------------------------------
+# Command to send a request header, and keep a copy in state(requestHeaders)
+# for debugging purposes.
+#
+# Arguments:
+# token - connection token (name of an array)
+# key - header name
+# value - header value
+#
+# Return Value: none
+# ------------------------------------------------------------------------------
+
+proc http::SendHeader {token key value} {
+ variable $token
+ upvar 0 $token state
+ set tk [namespace tail $token]
+ set sock $state(sock)
+ lappend state(requestHeaders) [string tolower $key] $value
+ puts $sock "$key: $value"
+ return
}
# http::Connected --
@@ -1340,8 +2175,11 @@ proc http::Connected {token proto phost srvurl} {
variable socketWrState
variable socketRdQueue
variable socketWrQueue
+ variable socketPhQueue
variable socketClosing
variable socketPlayCmd
+ variable socketCoEvent
+ variable socketProxyId
variable $token
upvar 0 $token state
@@ -1367,6 +2205,9 @@ proc http::Connected {token proto phost srvurl} {
lassign [fconfigure $sock -translation] trRead trWrite
fconfigure $sock -translation [list $trRead crlf] \
-buffersize $state(-blocksize)
+ if {[package vsatisfies [package provide Tcl] 9.0-]} {
+ fconfigure $sock -profile tcl8
+ }
# The following is disallowed in safe interpreters, but the socket is
# already in non-blocking mode in that case.
@@ -1400,44 +2241,58 @@ proc http::Connected {token proto phost srvurl} {
Log ^B$tk begin sending request - token $token
if {[catch {
- set state(method) $how
- puts $sock "$how $srvurl HTTP/$state(-protocol)"
+ if {[info exists state(bypass)]} {
+ set state(method) [lindex [split $state(bypass) { }] 0]
+ set state(requestHeaders) {}
+ set state(requestLine) $state(bypass)
+ } else {
+ set state(method) $how
+ set state(requestHeaders) {}
+ set state(requestLine) "$how $srvurl HTTP/$state(-protocol)"
+ }
+ puts $sock $state(requestLine)
set hostValue [GetFieldValue $state(-headers) Host]
if {$hostValue ne {}} {
# Allow Host spoofing. [Bug 928154]
regexp {^[^:]+} $hostValue state(host)
- puts $sock "Host: $hostValue"
+ SendHeader $token Host $hostValue
} elseif {$port == $defport} {
# Don't add port in this case, to handle broken servers. [Bug
# #504508]
set state(host) $host
- puts $sock "Host: $host"
+ SendHeader $token Host $host
} else {
set state(host) $host
- puts $sock "Host: $host:$port"
+ SendHeader $token Host "$host:$port"
}
- puts $sock "User-Agent: $http(-useragent)"
+ SendHeader $token User-Agent $http(-useragent)
if {($state(-protocol) > 1.0) && $state(-keepalive)} {
# Send this header, because a 1.1 server is not compelled to treat
# this as the default.
- puts $sock "Connection: keep-alive"
- }
- if {($state(-protocol) > 1.0) && !$state(-keepalive)} {
- puts $sock "Connection: close" ;# RFC2616 sec 8.1.2.1
- }
- if {($state(-protocol) < 1.1)} {
+ set ConnVal keep-alive
+ } elseif {($state(-protocol) > 1.0)} {
+ # RFC2616 sec 8.1.2.1
+ set ConnVal close
+ } else {
+ # ($state(-protocol) <= 1.0)
# RFC7230 A.1
# Some server implementations of HTTP/1.0 have a faulty
# implementation of RFC 2068 Keep-Alive.
# Don't leave this to chance.
# For HTTP/1.0 we have already "set state(connection) close"
# and "state(-keepalive) 0".
- puts $sock "Connection: close"
+ set ConnVal close
+ }
+ # Proxy authorisation (cf. mod by Anders Ramdahl to autoproxy by
+ # Pat Thoyts).
+ if {($http(-proxyauth) ne {}) && ($state(proxyUsed) eq {HttpProxy})} {
+ SendHeader $token Proxy-Authorization $http(-proxyauth)
}
# RFC7230 A.1 - "clients are encouraged not to send the
# Proxy-Connection header field in any requests"
set accept_encoding_seen 0
set content_type_seen 0
+ set connection_seen 0
foreach {key value} $state(-headers) {
set value [string map [list \n "" \r ""] $value]
set key [string map {" " -} [string trim $key]]
@@ -1457,20 +2312,49 @@ proc http::Connected {token proto phost srvurl} {
set contDone 1
set state(querylength) $value
}
+ if { [string equal -nocase $key "connection"]
+ && [info exists state(bypass)]
+ } {
+ # Value supplied in -headers overrides $ConnVal.
+ set connection_seen 1
+ } elseif {[string equal -nocase $key "connection"]} {
+ # Remove "close" or "keep-alive" and use our own value.
+ # In an upgrade request, the upgrade is not guaranteed.
+ # Value "close" or "keep-alive" tells the server what to do
+ # if it refuses the upgrade. We send a single "Connection"
+ # header because some websocket servers, e.g. civetweb, reject
+ # multiple headers. Bug [d01de3281f] of tcllib/websocket.
+ set connection_seen 1
+ set listVal $state(connectionValues)
+ if {[set pos [lsearch $listVal close]] != -1} {
+ set listVal [lreplace $listVal $pos $pos]
+ }
+ if {[set pos [lsearch $listVal keep-alive]] != -1} {
+ set listVal [lreplace $listVal $pos $pos]
+ }
+ lappend listVal $ConnVal
+ set value [join $listVal {, }]
+ }
if {[string length $key]} {
- puts $sock "$key: $value"
+ SendHeader $token $key $value
}
}
# Allow overriding the Accept header on a per-connection basis. Useful
# for working with REST services. [Bug c11a51c482]
if {!$accept_types_seen} {
- puts $sock "Accept: $state(accept-types)"
+ SendHeader $token Accept $state(accept-types)
}
if { (!$accept_encoding_seen)
&& (![info exists state(-handler)])
&& $http(-zip)
} {
- puts $sock "Accept-Encoding: gzip,deflate,compress"
+ SendHeader $token Accept-Encoding gzip,deflate
+ } elseif {!$accept_encoding_seen} {
+ SendHeader $token Accept-Encoding identity
+ } else {
+ }
+ if {!$connection_seen} {
+ SendHeader $token Connection $ConnVal
}
if {$isQueryChannel && ($state(querylength) == 0)} {
# Try to determine size of data in channel. If we cannot seek, the
@@ -1483,6 +2367,22 @@ proc http::Connected {token proto phost srvurl} {
seek $state(-querychannel) $start
}
+ # Note that we don't do Cookie2; that's much nastier and not normally
+ # observed in practice either. It also doesn't fix the multitude of
+ # bugs in the basic cookie spec.
+ if {$http(-cookiejar) ne ""} {
+ set cookies ""
+ set separator ""
+ foreach {key value} [{*}$http(-cookiejar) \
+ getCookies $proto $host $state(path)] {
+ append cookies $separator $key = $value
+ set separator "; "
+ }
+ if {$cookies ne ""} {
+ SendHeader $token Cookie $cookies
+ }
+ }
+
# Flush the request header and set up the fileevent that will either
# push the POST data or read the response.
#
@@ -1503,10 +2403,10 @@ proc http::Connected {token proto phost srvurl} {
if {$isQuery || $isQueryChannel} {
# POST method.
if {!$content_type_seen} {
- puts $sock "Content-Type: $state(-type)"
+ SendHeader $token Content-Type $state(-type)
}
if {!$contDone} {
- puts $sock "Content-Length: $state(querylength)"
+ SendHeader $token Content-Length $state(querylength)
}
puts $sock ""
flush $sock
@@ -1560,7 +2460,8 @@ proc http::Connected {token proto phost srvurl} {
# If any other requests are in flight or pipelined/queued, they will
# be discarded.
} elseif {$state(status) eq ""} {
- # ...https handshake errors come here.
+ # https handshake errors come here, for
+ # Tcl 8.7 without http::SecureProxyConnect, and for Tcl 8.6.
set msg [registerError $sock]
registerError $sock {}
if {$msg eq {}} {
@@ -1571,6 +2472,7 @@ proc http::Connected {token proto phost srvurl} {
Finish $token $err
}
}
+ return
}
# http::registerError
@@ -1616,8 +2518,11 @@ proc http::DoneRequest {token} {
variable socketWrState
variable socketRdQueue
variable socketWrQueue
+ variable socketPhQueue
variable socketClosing
variable socketPlayCmd
+ variable socketCoEvent
+ variable socketProxyId
variable $token
upvar 0 $token state
@@ -1676,6 +2581,7 @@ proc http::DoneRequest {token} {
# In the nonpipeline case, connection for reading always occurs.
ReceiveResponse $token
}
+ return
}
# http::ReceiveResponse
@@ -1692,13 +2598,16 @@ proc http::ReceiveResponse {token} {
lassign [fconfigure $sock -translation] trRead trWrite
fconfigure $sock -translation [list auto $trWrite] \
-buffersize $state(-blocksize)
+ if {[package vsatisfies [package provide Tcl] 9.0-]} {
+ fconfigure $sock -profile tcl8
+ }
Log ^D$tk begin receiving response - token $token
- coroutine ${token}EventCoroutine http::Event $sock $token
+ coroutine ${token}--EventCoroutine http::Event $sock $token
if {[info exists state(-handler)] || [info exists state(-progress)]} {
fileevent $sock readable [list http::EventGateway $sock $token]
} else {
- fileevent $sock readable ${token}EventCoroutine
+ fileevent $sock readable ${token}--EventCoroutine
}
return
}
@@ -1722,14 +2631,14 @@ proc http::EventGateway {sock token} {
variable $token
upvar 0 $token state
fileevent $sock readable {}
- catch {${token}EventCoroutine} res opts
- if {[info commands ${token}EventCoroutine] ne {}} {
+ catch {${token}--EventCoroutine} res opts
+ if {[info commands ${token}--EventCoroutine] ne {}} {
# The coroutine can be deleted by completion (a non-yield return), by
# http::Finish (when there is a premature end to the transaction), by
# http::reset or http::cleanup, or if the caller set option -channel
# but not option -handler: in the last case reading from the socket is
# now managed by commands ::http::Copy*, http::ReceiveChunked, and
- # http::make-transformation-chunked.
+ # http::MakeTransformationChunked.
#
# Catch in case the coroutine has closed the socket.
catch {fileevent $sock readable [list http::EventGateway $sock $token]}
@@ -1791,7 +2700,7 @@ proc http::NextPipelinedWrite {token} {
} {
# - The usual case for a pipelined connection, ready for a new request.
#Log pipelined, GRANT write access to $token2 in NextPipelinedWrite
- set conn [set ${token2}(tmpConnArgs)]
+ set conn [set ${token2}(connArgs)]
set socketWrState($connId) $token2
set socketWrQueue($connId) [lrange $socketWrQueue($connId) 1 end]
# Connect does its own fconfigure.
@@ -1816,9 +2725,7 @@ proc http::NextPipelinedWrite {token} {
# The case in which the next request will be non-pipelined, and the read
# and write queues is ready: which is the condition for a non-pipelined
# write.
- variable $token3
- upvar 0 $token3 state3
- set conn [set ${token3}(tmpConnArgs)]
+ set conn [set ${token3}(connArgs)]
#Log nonpipeline, GRANT r/w access to $token3 in NextPipelinedWrite
set socketRdState($connId) $token3
set socketWrState($connId) $token3
@@ -1850,6 +2757,7 @@ proc http::NextPipelinedWrite {token} {
#Log re-use nonpipeline, GRANT delayed write access to $token in NextP..
set socketWrState($connId) peNding
}
+ return
}
# http::CancelReadPipeline
@@ -1882,6 +2790,7 @@ proc http::CancelReadPipeline {name1 connId op} {
}
set socketRdQueue($connId) {}
}
+ return
}
# http::CancelWritePipeline
@@ -1915,6 +2824,7 @@ proc http::CancelWritePipeline {name1 connId op} {
}
set socketWrQueue($connId) {}
}
+ return
}
# http::ReplayIfDead --
@@ -1937,19 +2847,22 @@ proc http::CancelWritePipeline {name1 connId op} {
# Side Effects:
# Use the same token, but try to open a new socket.
-proc http::ReplayIfDead {tokenArg doing} {
+proc http::ReplayIfDead {token doing} {
variable socketMapping
variable socketRdState
variable socketWrState
variable socketRdQueue
variable socketWrQueue
+ variable socketPhQueue
variable socketClosing
variable socketPlayCmd
+ variable socketCoEvent
+ variable socketProxyId
- variable $tokenArg
- upvar 0 $tokenArg stateArg
+ variable $token
+ upvar 0 $token state
- Log running http::ReplayIfDead for $tokenArg $doing
+ Log running http::ReplayIfDead for $token $doing
# 1. Merge the tokens for transactions in flight, the read (response) queue,
# and the write (request) queue.
@@ -1958,85 +2871,86 @@ proc http::ReplayIfDead {tokenArg doing} {
set InFlightW {}
# Obtain the tokens for transactions in flight.
- if {$stateArg(-pipeline)} {
+ if {$state(-pipeline)} {
# Two transactions may be in flight. The "read" transaction was first.
# It is unlikely that the server would close the socket if a response
# was pending; however, an earlier request (as well as the present
# request) may have been sent and ignored if the socket was half-closed
# by the server.
- if { [info exists socketRdState($stateArg(socketinfo))]
- && ($socketRdState($stateArg(socketinfo)) ne "Rready")
+ if { [info exists socketRdState($state(socketinfo))]
+ && ($socketRdState($state(socketinfo)) ne "Rready")
} {
- lappend InFlightR $socketRdState($stateArg(socketinfo))
+ lappend InFlightR $socketRdState($state(socketinfo))
} elseif {($doing eq "read")} {
- lappend InFlightR $tokenArg
+ lappend InFlightR $token
}
- if { [info exists socketWrState($stateArg(socketinfo))]
- && $socketWrState($stateArg(socketinfo)) ni {Wready peNding}
+ if { [info exists socketWrState($state(socketinfo))]
+ && $socketWrState($state(socketinfo)) ni {Wready peNding}
} {
- lappend InFlightW $socketWrState($stateArg(socketinfo))
+ lappend InFlightW $socketWrState($state(socketinfo))
} elseif {($doing eq "write")} {
- lappend InFlightW $tokenArg
+ lappend InFlightW $token
}
- # Report any inconsistency of $tokenArg with socket*state.
+ # Report any inconsistency of $token with socket*state.
if { ($doing eq "read")
- && [info exists socketRdState($stateArg(socketinfo))]
- && ($tokenArg ne $socketRdState($stateArg(socketinfo)))
+ && [info exists socketRdState($state(socketinfo))]
+ && ($token ne $socketRdState($state(socketinfo)))
} {
- Log WARNING - ReplayIfDead pipelined tokenArg $tokenArg $doing \
- ne socketRdState($stateArg(socketinfo)) \
- $socketRdState($stateArg(socketinfo))
+ Log WARNING - ReplayIfDead pipelined token $token $doing \
+ ne socketRdState($state(socketinfo)) \
+ $socketRdState($state(socketinfo))
} elseif {
($doing eq "write")
- && [info exists socketWrState($stateArg(socketinfo))]
- && ($tokenArg ne $socketWrState($stateArg(socketinfo)))
+ && [info exists socketWrState($state(socketinfo))]
+ && ($token ne $socketWrState($state(socketinfo)))
} {
- Log WARNING - ReplayIfDead pipelined tokenArg $tokenArg $doing \
- ne socketWrState($stateArg(socketinfo)) \
- $socketWrState($stateArg(socketinfo))
+ Log WARNING - ReplayIfDead pipelined token $token $doing \
+ ne socketWrState($state(socketinfo)) \
+ $socketWrState($state(socketinfo))
}
} else {
# One transaction should be in flight.
# socketRdState, socketWrQueue are used.
# socketRdQueue should be empty.
- # Report any inconsistency of $tokenArg with socket*state.
- if {$tokenArg ne $socketRdState($stateArg(socketinfo))} {
- Log WARNING - ReplayIfDead nonpipeline tokenArg $tokenArg $doing \
- ne socketRdState($stateArg(socketinfo)) \
- $socketRdState($stateArg(socketinfo))
+ # Report any inconsistency of $token with socket*state.
+ if {$token ne $socketRdState($state(socketinfo))} {
+ Log WARNING - ReplayIfDead nonpipeline token $token $doing \
+ ne socketRdState($state(socketinfo)) \
+ $socketRdState($state(socketinfo))
}
# Report the inconsistency that socketRdQueue is non-empty.
- if { [info exists socketRdQueue($stateArg(socketinfo))]
- && ($socketRdQueue($stateArg(socketinfo)) ne {})
+ if { [info exists socketRdQueue($state(socketinfo))]
+ && ($socketRdQueue($state(socketinfo)) ne {})
} {
- Log WARNING - ReplayIfDead nonpipeline tokenArg $tokenArg $doing \
- has read queue socketRdQueue($stateArg(socketinfo)) \
- $socketRdQueue($stateArg(socketinfo)) ne {}
+ Log WARNING - ReplayIfDead nonpipeline token $token $doing \
+ has read queue socketRdQueue($state(socketinfo)) \
+ $socketRdQueue($state(socketinfo)) ne {}
}
- lappend InFlightW $socketRdState($stateArg(socketinfo))
- set socketRdQueue($stateArg(socketinfo)) {}
+ lappend InFlightW $socketRdState($state(socketinfo))
+ set socketRdQueue($state(socketinfo)) {}
}
set newQueue {}
lappend newQueue {*}$InFlightR
- lappend newQueue {*}$socketRdQueue($stateArg(socketinfo))
+ lappend newQueue {*}$socketRdQueue($state(socketinfo))
lappend newQueue {*}$InFlightW
- lappend newQueue {*}$socketWrQueue($stateArg(socketinfo))
+ lappend newQueue {*}$socketWrQueue($state(socketinfo))
- # 2. Tidy up tokenArg. This is a cut-down form of Finish/CloseSocket.
+ # 2. Tidy up token. This is a cut-down form of Finish/CloseSocket.
# Do not change state(status).
- # No need to after cancel stateArg(after) - either this is done in
+ # No need to after cancel state(after) - either this is done in
# ReplayCore/ReInit, or Finish is called.
- catch {close $stateArg(sock)}
+ catch {close $state(sock)}
+ Unset $state(socketinfo)
# 2a. Tidy the tokens in the queues - this is done in ReplayCore/ReInit.
# - Transactions, if any, that are awaiting responses cannot be completed.
@@ -2048,6 +2962,7 @@ proc http::ReplayIfDead {tokenArg doing} {
# to new values in ReplayCore.
ReplayCore $newQueue
+ return
}
# http::ReplayIfClose --
@@ -2078,7 +2993,7 @@ proc http::ReplayIfClose {Wstate Rqueue Wqueue} {
if {$Wstate ni {Wready peNding}} {
lappend InFlightW $Wstate
}
-
+ ##Log $Rqueue -- $InFlightW -- $Wqueue
set newQueue {}
lappend newQueue {*}$Rqueue
lappend newQueue {*}$InFlightW
@@ -2087,6 +3002,7 @@ proc http::ReplayIfClose {Wstate Rqueue Wqueue} {
# 2. Cleanup - none needed, done by the caller.
ReplayCore $newQueue
+ return
}
# http::ReInit --
@@ -2130,6 +3046,11 @@ proc http::ReInit {token} {
after cancel $state(after)
unset state(after)
}
+ if {[info exists state(socketcoro)]} {
+ Log $token Cancel socket after-idle event (ReInit)
+ after cancel $state(socketcoro)
+ unset state(socketcoro)
+ }
# Don't alter state(status) - this would trigger http::wait if it is in use.
set tmpState $state(tmpState)
@@ -2169,13 +3090,18 @@ proc http::ReInit {token} {
# Use existing tokens, but try to open a new socket.
proc http::ReplayCore {newQueue} {
+ variable TmpSockCounter
+
variable socketMapping
variable socketRdState
variable socketWrState
variable socketRdQueue
variable socketWrQueue
+ variable socketPhQueue
variable socketClosing
variable socketPlayCmd
+ variable socketCoEvent
+ variable socketProxyId
if {[llength $newQueue] == 0} {
# Nothing to do.
@@ -2195,6 +3121,7 @@ proc http::ReplayCore {newQueue} {
if {![ReInit $token]} {
Log FAILED in http::ReplayCore - NO tmp vars
+ Log ReplayCore reject $token
Finish $token {cannot send this request again}
return
}
@@ -2207,92 +3134,33 @@ proc http::ReplayCore {newQueue} {
unset state(tmpConnArgs)
set state(reusing) 0
+ set state(ReusingPlaceholder) 0
+ set state(alreadyQueued) 0
+ Log ReplayCore replay $token
- if {$state(-timeout) > 0} {
- set resetCmd [list http::reset $token timeout]
- set state(after) [after $state(-timeout) $resetCmd]
- }
-
- set pre [clock milliseconds]
- ##Log pre socket opened, - token $token
- ##Log $tmpOpenCmd - token $token
- # 4. Open a socket.
- if {[catch {eval $tmpOpenCmd} sock]} {
- # Something went wrong while trying to establish the connection.
- Log FAILED - $sock
- set state(sock) NONE
- Finish $token $sock
- return
- }
- ##Log post socket opened, - token $token
- set delay [expr {[clock milliseconds] - $pre}]
- if {$delay > 3000} {
- Log socket delay $delay - token $token
- }
- # Command [socket] is called with -async, but takes 5s to 5.1s to return,
- # with probability of order 1 in 10,000. This may be a bizarre scheduling
- # issue with my (KJN's) system (Fedora Linux).
- # This does not cause a problem (unless the request times out when this
- # command returns).
-
- # 5. Configure the persistent socket data.
- if {$state(-keepalive)} {
- set socketMapping($state(socketinfo)) $sock
-
- if {![info exists socketRdState($state(socketinfo))]} {
- set socketRdState($state(socketinfo)) {}
- set varName ::http::socketRdState($state(socketinfo))
- trace add variable $varName unset ::http::CancelReadPipeline
- }
-
- if {![info exists socketWrState($state(socketinfo))]} {
- set socketWrState($state(socketinfo)) {}
- set varName ::http::socketWrState($state(socketinfo))
- trace add variable $varName unset ::http::CancelWritePipeline
- }
-
- if {$state(-pipeline)} {
- #Log new, init for pipelined, GRANT write acc to $token ReplayCore
- set socketRdState($state(socketinfo)) $token
- set socketWrState($state(socketinfo)) $token
- } else {
- #Log new, init for nonpipeline, GRANT r/w acc to $token ReplayCore
- set socketRdState($state(socketinfo)) $token
- set socketWrState($state(socketinfo)) $token
- }
-
- set socketRdQueue($state(socketinfo)) {}
- set socketWrQueue($state(socketinfo)) $newQueue
- set socketClosing($state(socketinfo)) 0
- set socketPlayCmd($state(socketinfo)) {ReplayIfClose Wready {} {}}
- }
+ # Give the socket a placeholder name before it is created.
+ set sock HTTP_PLACEHOLDER_[incr TmpSockCounter]
+ set state(sock) $sock
- ##Log pre newQueue ReInit, - token $token
- # 6. Configure sockets in the queue.
+ # Move the $newQueue into the placeholder socket's socketPhQueue.
+ set socketPhQueue($sock) {}
foreach tok $newQueue {
if {[ReInit $tok]} {
set ${tok}(reusing) 1
set ${tok}(sock) $sock
+ lappend socketPhQueue($sock) $tok
+ Log ReplayCore replay $tok
} else {
+ Log ReplayCore reject $tok
set ${tok}(reusing) 1
set ${tok}(sock) NONE
- Finish $token {cannot send this request again}
+ Finish $tok {cannot send this request again}
}
}
- # 7. Configure the socket for newToken to send a request.
- set state(sock) $sock
- Log "Using $sock for $state(socketinfo) - token $token" \
- [expr {$state(-keepalive)?"keepalive":""}]
-
- # Initialisation of a new socket.
- ##Log socket opened, now fconfigure - token $token
- fconfigure $sock -translation {auto crlf} -buffersize $state(-blocksize)
- ##Log socket opened, DONE fconfigure - token $token
+ AsyncTransaction $token
- # Connect does its own fconfigure.
- fileevent $sock writable [list http::Connect $token {*}$tmpConnArgs]
- #Log ---- $sock << conn to $token for HTTP request (e)
+ return
}
# Data access functions:
@@ -2301,7 +3169,7 @@ proc http::ReplayCore {newQueue} {
# Code - the HTTP transaction code, e.g., 200
# Size - the size of the URL data
-proc http::data {token} {
+proc http::responseBody {token} {
variable $token
upvar 0 $token state
return $state(body)
@@ -2314,12 +3182,17 @@ proc http::status {token} {
upvar 0 $token state
return $state(status)
}
-proc http::code {token} {
+proc http::responseLine {token} {
variable $token
upvar 0 $token state
return $state(http)
}
-proc http::ncode {token} {
+proc http::requestLine {token} {
+ variable $token
+ upvar 0 $token state
+ return $state(requestLine)
+}
+proc http::responseCode {token} {
variable $token
upvar 0 $token state
if {[regexp {[0-9]{3}} $state(http) numeric_code]} {
@@ -2333,10 +3206,134 @@ proc http::size {token} {
upvar 0 $token state
return $state(currentsize)
}
-proc http::meta {token} {
+proc http::requestHeaders {token args} {
+ set lenny [llength $args]
+ if {$lenny > 1} {
+ return -code error {usage: ::http::requestHeaders token ?headerName?}
+ } else {
+ return [Meta $token request {*}$args]
+ }
+}
+proc http::responseHeaders {token args} {
+ set lenny [llength $args]
+ if {$lenny > 1} {
+ return -code error {usage: ::http::responseHeaders token ?headerName?}
+ } else {
+ return [Meta $token response {*}$args]
+ }
+}
+proc http::requestHeaderValue {token header} {
+ Meta $token request $header VALUE
+}
+proc http::responseHeaderValue {token header} {
+ Meta $token response $header VALUE
+}
+proc http::Meta {token who args} {
+ variable $token
+ upvar 0 $token state
+
+ if {$who eq {request}} {
+ set whom requestHeaders
+ } elseif {$who eq {response}} {
+ set whom meta
+ } else {
+ return -code error {usage: ::http::Meta token request|response ?headerName ?VALUE??}
+ }
+
+ set header [string tolower [lindex $args 0]]
+ set how [string tolower [lindex $args 1]]
+ set lenny [llength $args]
+ if {$lenny == 0} {
+ return $state($whom)
+ } elseif {($lenny > 2) || (($lenny == 2) && ($how ne {value}))} {
+ return -code error {usage: ::http::Meta token request|response ?headerName ?VALUE??}
+ } else {
+ set result {}
+ set combined {}
+ foreach {key value} $state($whom) {
+ if {$key eq $header} {
+ lappend result $key $value
+ append combined $value {, }
+ }
+ }
+ if {$lenny == 1} {
+ return $result
+ } else {
+ return [string range $combined 0 end-2]
+ }
+ }
+}
+
+
+# ------------------------------------------------------------------------------
+# Proc http::responseInfo
+# ------------------------------------------------------------------------------
+# Command to return a dictionary of the most useful metadata of a HTTP
+# response.
+#
+# Arguments:
+# token - connection token (name of an array)
+#
+# Return Value: a dict. See man page http(n) for a description of each item.
+# ------------------------------------------------------------------------------
+
+proc http::responseInfo {token} {
variable $token
upvar 0 $token state
- return $state(meta)
+ set result {}
+ foreach {key origin name} {
+ stage STATE state
+ status STATE status
+ responseCode STATE responseCode
+ reasonPhrase STATE reasonPhrase
+ contentType STATE type
+ binary STATE binary
+ redirection RESP location
+ upgrade STATE upgrade
+ error ERROR -
+ postError STATE posterror
+ method STATE method
+ charset STATE charset
+ compression STATE coding
+ httpRequest STATE -protocol
+ httpResponse STATE httpResponse
+ url STATE url
+ connectionRequest REQ connection
+ connectionResponse RESP connection
+ connectionActual STATE connection
+ transferEncoding STATE transfer
+ totalPost STATE querylength
+ currentPost STATE queryoffset
+ totalSize STATE totalsize
+ currentSize STATE currentsize
+ proxyUsed STATE proxyUsed
+ } {
+ if {$origin eq {STATE}} {
+ if {[info exists state($name)]} {
+ dict set result $key $state($name)
+ } else {
+ # Should never come here
+ dict set result $key {}
+ }
+ } elseif {$origin eq {REQ}} {
+ dict set result $key [requestHeaderValue $token $name]
+ } elseif {$origin eq {RESP}} {
+ dict set result $key [responseHeaderValue $token $name]
+ } elseif {$origin eq {ERROR}} {
+ # Don't flood the dict with data. The command ::http::error is
+ # available.
+ if {[info exists state(error)]} {
+ set msg [lindex $state(error) 0]
+ } else {
+ set msg {}
+ }
+ dict set result $key $msg
+ } else {
+ # Should never come here
+ dict set result $key {}
+ }
+ }
+ return $result
}
proc http::error {token} {
variable $token
@@ -2344,7 +3341,15 @@ proc http::error {token} {
if {[info exists state(error)]} {
return $state(error)
}
- return ""
+ return
+}
+proc http::postError {token} {
+ variable $token
+ upvar 0 $token state
+ if {[info exists state(postErrorFull)]} {
+ return $state(postErrorFull)
+ }
+ return
}
# http::cleanup
@@ -2360,16 +3365,25 @@ proc http::error {token} {
proc http::cleanup {token} {
variable $token
upvar 0 $token state
- if {[info commands ${token}EventCoroutine] ne {}} {
- rename ${token}EventCoroutine {}
+ if {[info commands ${token}--EventCoroutine] ne {}} {
+ rename ${token}--EventCoroutine {}
+ }
+ if {[info commands ${token}--SocketCoroutine] ne {}} {
+ rename ${token}--SocketCoroutine {}
}
if {[info exists state(after)]} {
after cancel $state(after)
unset state(after)
}
+ if {[info exists state(socketcoro)]} {
+ Log $token Cancel socket after-idle event (cleanup)
+ after cancel $state(socketcoro)
+ unset state(socketcoro)
+ }
if {[info exists state]} {
unset state
}
+ return
}
# http::Connect
@@ -2387,11 +3401,20 @@ proc http::Connect {token proto phost srvurl} {
variable $token
upvar 0 $token state
set tk [namespace tail $token]
- set err "due to unexpected EOF"
- if {
- [eof $state(sock)] ||
- [set err [fconfigure $state(sock) -error]] ne ""
- } {
+
+ if {[catch {eof $state(sock)} tmp] || $tmp} {
+ set err "due to unexpected EOF"
+ } elseif {[set err [fconfigure $state(sock) -error]] ne ""} {
+ # set err is done in test
+ } else {
+ # All OK
+ set state(state) connecting
+ fileevent $state(sock) writable {}
+ ::http::Connected $token $proto $phost $srvurl
+ return
+ }
+
+ # Error cases.
Log "WARNING - if testing, pay special attention to this\
case (GJ) which is seldom executed - token $token"
if {[info exists state(reusing)] && $state(reusing)} {
@@ -2407,12 +3430,8 @@ proc http::Connect {token proto phost srvurl} {
# If any other requests are in flight or pipelined/queued, they will
# be discarded.
}
- Finish $token "connect failed $err"
- } else {
- set state(state) connecting
- fileevent $state(sock) writable {}
- ::http::Connected $token $proto $phost $srvurl
- }
+ Finish $token "connect failed: $err"
+ return
}
# http::Write
@@ -2432,8 +3451,11 @@ proc http::Write {token} {
variable socketWrState
variable socketRdQueue
variable socketWrQueue
+ variable socketPhQueue
variable socketClosing
variable socketPlayCmd
+ variable socketCoEvent
+ variable socketProxyId
variable $token
upvar 0 $token state
@@ -2494,11 +3516,13 @@ proc http::Write {token} {
set done 1
}
}
- } err]} {
+ } err opts]} {
# Do not call Finish here, but instead let the read half of the socket
# process whatever server reply there is to get.
-
set state(posterror) $err
+ set info [dict get $opts -errorinfo]
+ set code [dict get $opts -code]
+ set state(postErrorFull) [list $err $info $code]
set done 1
}
@@ -2514,15 +3538,16 @@ proc http::Write {token} {
# Callback to the client after we've completely handled everything.
if {[string length $state(-queryprogress)]} {
- eval $state(-queryprogress) \
+ namespace eval :: $state(-queryprogress) \
[list $token $state(querylength) $state(queryoffset)]
}
+ return
}
# http::Event
#
# Handle input on the socket. This command is the core of
-# the coroutine commands ${token}EventCoroutine that are
+# the coroutine commands ${token}--EventCoroutine that are
# bound to "fileevent $sock readable" and process input.
#
# Arguments
@@ -2539,8 +3564,11 @@ proc http::Event {sock token} {
variable socketWrState
variable socketRdQueue
variable socketWrQueue
+ variable socketPhQueue
variable socketClosing
variable socketPlayCmd
+ variable socketCoEvent
+ variable socketProxyId
variable $token
upvar 0 $token state
@@ -2551,15 +3579,18 @@ proc http::Event {sock token} {
if {![info exists state]} {
Log "Event $sock with invalid token '$token' - remote close?"
- if {![eof $sock]} {
+ if {!([catch {eof $sock} tmp] || $tmp)} {
if {[set d [read $sock]] ne ""} {
Log "WARNING: additional data left on closed socket\
- token $token"
+ } else {
}
+ } else {
}
Log ^X$tk end of response (token error) - token $token
CloseSocket $sock
return
+ } else {
}
if {$state(state) eq "connecting"} {
##Log - connecting - token $token
@@ -2570,6 +3601,7 @@ proc http::Event {sock token} {
} {
set state(after) [after $state(-timeout) \
[list http::reset $token timeout]]
+ } else {
}
if {[catch {gets $sock state(http)} nsl]} {
@@ -2581,22 +3613,29 @@ proc http::Event {sock token} {
if {[TestForReplay $token read $nsl c]} {
return
+ } else {
}
-
# else:
# This is NOT a persistent socket that has been closed since
# its last use.
# If any other requests are in flight or pipelined/queued,
# they will be discarded.
} else {
+ # https handshake errors come here, for
+ # Tcl 8.7 with http::SecureProxyConnect.
+ set msg [registerError $sock]
+ registerError $sock {}
+ if {$msg eq {}} {
+ set msg $nsl
+ }
Log ^X$tk end of response (error) - token $token
- Finish $token $nsl
+ Finish $token $msg
return
}
} elseif {$nsl >= 0} {
##Log - connecting 1 - token $token
set state(state) "header"
- } elseif { [eof $sock]
+ } elseif { ([catch {eof $sock} tmp] || $tmp)
&& [info exists state(reusing)]
&& $state(reusing)
} {
@@ -2606,6 +3645,7 @@ proc http::Event {sock token} {
if {[TestForReplay $token read {} d]} {
return
+ } else {
}
# else:
@@ -2613,6 +3653,7 @@ proc http::Event {sock token} {
# last use.
# If any other requests are in flight or pipelined/queued, they
# will be discarded.
+ } else {
}
} elseif {$state(state) eq "header"} {
if {[catch {gets $sock line} nhl]} {
@@ -2631,6 +3672,20 @@ proc http::Event {sock token} {
set state(state) "connecting"
continue
# This was a "return" in the pre-coroutine code.
+ } else {
+ }
+
+ # We have $state(http) so let's split it into its components.
+ if {[regexp {^HTTP/(\S+) ([0-9]{3}) (.*)$} $state(http) \
+ -> httpResponse responseCode reasonPhrase]
+ } {
+ set state(httpResponse) $httpResponse
+ set state(responseCode) $responseCode
+ set state(reasonPhrase) $reasonPhrase
+ } else {
+ set state(httpResponse) $state(http)
+ set state(responseCode) $state(http)
+ set state(reasonPhrase) $state(http)
}
if { ([info exists state(connection)])
@@ -2646,6 +3701,7 @@ proc http::Event {sock token} {
# Previous value is $token. It cannot be "pending".
set socketWrState($state(socketinfo)) Wready
http::NextPipelinedWrite $token
+ } else {
}
# Once a "close" has been signaled, the client MUST NOT send any
@@ -2664,6 +3720,21 @@ proc http::Event {sock token} {
# response.
##Log WARNING - socket will close after response for $token
# Prepare data for a call to ReplayIfClose.
+ Log $token socket will close after this transaction
+ # 1. Cancel socket-assignment coro events that have not yet
+ # launched, and add the tokens to the write queue.
+ if {[info exists socketCoEvent($state(socketinfo))]} {
+ foreach {tok can} $socketCoEvent($state(socketinfo)) {
+ lappend socketWrQueue($state(socketinfo)) $tok
+ unset -nocomplain ${tok}(socketcoro)
+ after cancel $can
+ Log $tok Cancel socket after-idle event (Event)
+ Log Move $tok from socketCoEvent to socketWrQueue and cancel its after idle coro
+ }
+ set socketCoEvent($state(socketinfo)) {}
+ } else {
+ }
+
if { ($socketRdQueue($state(socketinfo)) ne {})
|| ($socketWrQueue($state(socketinfo)) ne {})
|| ($socketWrState($state(socketinfo)) ni
@@ -2676,7 +3747,6 @@ proc http::Event {sock token} {
set msg "token ${InFlightW} is InFlightW"
##Log $msg - token $token
}
-
set socketPlayCmd($state(socketinfo)) \
[list ReplayIfClose $InFlightW \
$socketRdQueue($state(socketinfo)) \
@@ -2691,16 +3761,20 @@ proc http::Event {sock token} {
if {[info exists ${tokenVal}(after)]} {
after cancel [set ${tokenVal}(after)]
unset ${tokenVal}(after)
+ } else {
}
+ # Tokens in the read queue have no (socketcoro) to
+ # cancel.
}
-
} else {
set socketPlayCmd($state(socketinfo)) \
{ReplayIfClose Wready {} {}}
}
- # Do not allow further connections on this socket.
+ # Do not allow further connections on this socket (but
+ # geturl can add new requests to the replay).
set socketClosing($state(socketinfo)) 1
+ } else {
}
set state(state) body
@@ -2716,6 +3790,7 @@ proc http::Event {sock token} {
&& ("keep-alive" ni $state(connection))
} {
lappend state(connection) "keep-alive"
+ } else {
}
# If doing a HEAD, then we won't get any body
@@ -2724,6 +3799,46 @@ proc http::Event {sock token} {
set state(state) complete
Eot $token
return
+ } elseif {
+ ($state(method) eq {CONNECT})
+ && [string is integer -strict $state(responseCode)]
+ && ($state(responseCode) >= 200)
+ && ($state(responseCode) < 300)
+ } {
+ # A successful CONNECT response has no body.
+ # (An unsuccessful CONNECT has headers and body.)
+ # The code below is abstracted from Eot/Finish, but
+ # keeps the socket open.
+ catch {fileevent $state(sock) readable {}}
+ catch {fileevent $state(sock) writable {}}
+ set state(state) complete
+ set state(status) ok
+ if {[info commands ${token}--EventCoroutine] ne {}} {
+ rename ${token}--EventCoroutine {}
+ }
+ if {[info commands ${token}--SocketCoroutine] ne {}} {
+ rename ${token}--SocketCoroutine {}
+ }
+ if {[info exists state(socketcoro)]} {
+ Log $token Cancel socket after-idle event (Finish)
+ after cancel $state(socketcoro)
+ unset state(socketcoro)
+ }
+ if {[info exists state(after)]} {
+ after cancel $state(after)
+ unset state(after)
+ }
+ if { [info exists state(-command)]
+ && (![info exists state(done-command-cb)])
+ } {
+ set state(done-command-cb) yes
+ if {[catch {namespace eval :: $state(-command) $token} err]} {
+ set state(error) [list $err $errorInfo $errorCode]
+ set state(status) error
+ }
+ }
+ return
+ } else {
}
# - For non-chunked transfer we may have no body - in this case
@@ -2744,7 +3859,7 @@ proc http::Event {sock token} {
&& ("close" in $state(connection))
)
)
- && (![info exists state(transfer)])
+ && ($state(transfer) eq {})
&& ($state(totalsize) == 0)
} {
set msg {body size is 0 and no events likely - complete}
@@ -2754,6 +3869,7 @@ proc http::Event {sock token} {
set state(state) complete
Eot $token
return
+ } else {
}
# We have to use binary translation to count bytes properly.
@@ -2765,24 +3881,29 @@ proc http::Event {sock token} {
} {
# Turn off conversions for non-text data.
set state(binary) 1
+ } else {
}
if {[info exists state(-channel)]} {
if {$state(binary) || [llength [ContentEncoding $token]]} {
fconfigure $state(-channel) -translation binary
+ } else {
}
if {![info exists state(-handler)]} {
# Initiate a sequence of background fcopies.
fileevent $sock readable {}
- rename ${token}EventCoroutine {}
+ rename ${token}--EventCoroutine {}
CopyStart $sock $token
return
+ } else {
}
+ } else {
}
} elseif {$nhl > 0} {
# Process header lines.
##Log header - token $token - $line
if {[regexp -nocase {^([^:]+):(.+)$} $line x key value]} {
- switch -- [string tolower $key] {
+ set key [string tolower $key]
+ switch -- $key {
content-type {
set state(type) [string trim [string tolower $value]]
# Grab the optional charset information.
@@ -2809,6 +3930,12 @@ proc http::Event {sock token} {
connection {
# RFC 7230 Section 6.1 states that a comma-separated
# list is an acceptable value.
+ if {![info exists state(connectionRespFlag)]} {
+ # This is the first "Connection" response header.
+ # Scrub the earlier value set by iniitialisation.
+ set state(connectionRespFlag) {}
+ set state(connection) {}
+ }
foreach el [SplitCommaSeparatedFieldValue $value] {
lappend state(connection) [string tolower $el]
}
@@ -2816,16 +3943,24 @@ proc http::Event {sock token} {
upgrade {
set state(upgrade) [string trim $value]
}
+ set-cookie {
+ if {$http(-cookiejar) ne ""} {
+ ParseCookie $token [string trim $value]
+ } else {
+ }
+ }
}
lappend state(meta) $key [string trim $value]
+ } else {
}
+ } else {
}
} else {
# Now reading body
##Log body - token $token
if {[catch {
if {[info exists state(-handler)]} {
- set n [eval $state(-handler) [list $sock $token]]
+ set n [namespace eval :: $state(-handler) [list $sock $token]]
##Log handler $n - token $token
# N.B. the protocol has been set to 1.0 because the -handler
# logic is not expected to handle chunked encoding.
@@ -2834,6 +3969,7 @@ proc http::Event {sock token} {
# We know the transfer is complete only when the server
# closes the connection - i.e. eof is not an error.
set state(state) complete
+ } else {
}
if {![string is integer -strict $n]} {
if 1 {
@@ -2863,10 +3999,11 @@ proc http::Event {sock token} {
set n 0
set state(state) complete
}
+ } else {
}
} elseif {[info exists state(transfer_final)]} {
# This code forgives EOF in place of the final CRLF.
- set line [getTextLine $sock]
+ set line [GetTextLine $sock]
set n [string length $line]
set state(state) complete
if {$n > 0} {
@@ -2889,7 +4026,7 @@ proc http::Event {sock token} {
} {
##Log chunked - token $token
set size 0
- set hexLenChunk [getTextLine $sock]
+ set hexLenChunk [GetTextLine $sock]
#set ntl [string length $hexLenChunk]
if {[string trim $hexLenChunk] ne ""} {
scan $hexLenChunk %x size
@@ -2902,6 +4039,7 @@ proc http::Event {sock token} {
incr state(log_size) [string length $chunk]
##Log chunk $n cumul $state(log_size) -\
token $token
+ } else {
}
if {$size != [string length $chunk]} {
Log "WARNING: mis-sized chunk:\
@@ -2914,10 +4052,11 @@ proc http::Event {sock token} {
set msg {error in chunked encoding - fetch\
terminated}
Eot $token $msg
+ } else {
}
# CRLF that follows chunk.
# If eof, this is handled at the end of this proc.
- getTextLine $sock
+ GetTextLine $sock
} else {
set n 0
set state(transfer_final) {}
@@ -2961,6 +4100,7 @@ proc http::Event {sock token} {
append state(body) $block
##Log non-chunk [string length $state(body)] -\
token $token
+ } else {
}
}
# This calculation uses n from the -handler, chunked, or
@@ -2972,6 +4112,7 @@ proc http::Event {sock token} {
set t $state(totalsize)
##Log another $n currentsize $c totalsize $t -\
token $token
+ } else {
}
# If Content-Length - check for end of data.
if {
@@ -2982,7 +4123,9 @@ proc http::Event {sock token} {
token $token
set state(state) complete
Eot $token
+ } else {
}
+ } else {
}
} err]} {
Log ^X$tk end of response (error ${err}) - token $token
@@ -2990,15 +4133,17 @@ proc http::Event {sock token} {
return
} else {
if {[info exists state(-progress)]} {
- eval $state(-progress) \
+ namespace eval :: $state(-progress) \
[list $token $state(totalsize) $state(currentsize)]
+ } else {
}
}
}
# catch as an Eot above may have closed the socket already
# $state(state) may be connecting, header, body, or complete
- if {![set cc [catch {eof $sock} eof]] && $eof} {
+ if {(![catch {eof $sock} eof]) && $eof} {
+ # [eof sock] succeeded and the result was 1
##Log eof - token $token
if {[info exists $token]} {
set state(connection) close
@@ -3020,10 +4165,12 @@ proc http::Event {sock token} {
Log ^X$tk end of response (token error) - token $token
CloseSocket $sock
}
- } elseif {$cc} {
- return
+ } else {
+ # EITHER [eof sock] failed - presumed done by Eot
+ # OR [eof sock] succeeded and the result was 0
}
}
+ return
}
# http::TestForReplay
@@ -3113,10 +4260,88 @@ proc http::IsBinaryContentType {type} {
return true
}
-# http::getTextLine --
+proc http::ParseCookie {token value} {
+ variable http
+ variable CookieRE
+ variable $token
+ upvar 0 $token state
+
+ if {![regexp $CookieRE $value -> cookiename cookieval opts]} {
+ # Bad cookie! No biscuit!
+ return
+ }
+
+ # Convert the options into a list before feeding into the cookie store;
+ # ugly, but quite easy.
+ set realopts {hostonly 1 path / secure 0 httponly 0}
+ dict set realopts origin $state(host)
+ dict set realopts domain $state(host)
+ foreach option [split [regsub -all {;\s+} $opts \u0000] \u0000] {
+ regexp {^(.*?)(?:=(.*))?$} $option -> optname optval
+ switch -exact -- [string tolower $optname] {
+ expires {
+ if {[catch {
+ #Sun, 06 Nov 1994 08:49:37 GMT
+ dict set realopts expires \
+ [clock scan $optval -format "%a, %d %b %Y %T %Z"]
+ }] && [catch {
+ # Google does this one
+ #Mon, 01-Jan-1990 00:00:00 GMT
+ dict set realopts expires \
+ [clock scan $optval -format "%a, %d-%b-%Y %T %Z"]
+ }] && [catch {
+ # This is in the RFC, but it is also in the original
+ # Netscape cookie spec, now online at:
+ # <URL:http://curl.haxx.se/rfc/cookie_spec.html>
+ #Sunday, 06-Nov-94 08:49:37 GMT
+ dict set realopts expires \
+ [clock scan $optval -format "%A, %d-%b-%y %T %Z"]
+ }]} {catch {
+ #Sun Nov 6 08:49:37 1994
+ dict set realopts expires \
+ [clock scan $optval -gmt 1 -format "%a %b %d %T %Y"]
+ }}
+ }
+ max-age {
+ # Normalize
+ if {[string is integer -strict $optval]} {
+ dict set realopts expires [expr {[clock seconds] + $optval}]
+ }
+ }
+ domain {
+ # From the domain-matches definition [RFC 2109, section 2]:
+ # Host A's name domain-matches host B's if [...]
+ # A is a FQDN string and has the form NB, where N is a
+ # non-empty name string, B has the form .B', and B' is a
+ # FQDN string. (So, x.y.com domain-matches .y.com but
+ # not y.com.)
+ if {$optval ne "" && ![string match *. $optval]} {
+ dict set realopts domain [string trimleft $optval "."]
+ dict set realopts hostonly [expr {
+ ! [string match .* $optval]
+ }]
+ }
+ }
+ path {
+ if {[string match /* $optval]} {
+ dict set realopts path $optval
+ }
+ }
+ secure - httponly {
+ dict set realopts [string tolower $optname] 1
+ }
+ }
+ }
+ dict set realopts key $cookiename
+ dict set realopts value $cookieval
+ {*}$http(-cookiejar) storeCookie $realopts
+}
+
+# http::GetTextLine --
#
# Get one line with the stream in crlf mode.
-# Used if Transfer-Encoding is chunked.
+# Used if Transfer-Encoding is chunked, to read the line that
+# reports the size of the following chunk.
# Empty line is not distinguished from eof. The caller must
# be able to handle this.
#
@@ -3126,7 +4351,7 @@ proc http::IsBinaryContentType {type} {
# Results:
# The line of text, without trailing newline
-proc http::getTextLine {sock} {
+proc http::GetTextLine {sock} {
set tr [fconfigure $sock -translation]
lassign $tr trRead trWrite
fconfigure $sock -translation [list crlf $trWrite]
@@ -3139,6 +4364,8 @@ proc http::getTextLine {sock} {
#
# Replacement for a blocking read.
# The caller must be a coroutine.
+# Used when we expect to read a chunked-encoding
+# chunk of known size.
proc http::BlockingRead {sock size} {
if {$size < 1} {
@@ -3148,7 +4375,7 @@ proc http::BlockingRead {sock size} {
while 1 {
set need [expr {$size - [string length $result]}]
set block [read $sock $need]
- set eof [eof $sock]
+ set eof [expr {[catch {eof $sock} tmp] || $tmp}]
append result $block
if {[string length $result] >= $size || $eof} {
return $result
@@ -3168,7 +4395,7 @@ proc http::BlockingRead {sock size} {
proc http::BlockingGets {sock} {
while 1 {
set count [gets $sock line]
- set eof [eof $sock]
+ set eof [expr {[catch {eof $sock} tmp] || $tmp}]
if {$count >= 0 || $eof} {
return $line
} else {
@@ -3189,16 +4416,28 @@ proc http::BlockingGets {sock} {
# This closes the connection upon error
proc http::CopyStart {sock token {initial 1}} {
- upvar #0 $token state
+ upvar 0 $token state
if {[info exists state(transfer)] && $state(transfer) eq "chunked"} {
foreach coding [ContentEncoding $token] {
- lappend state(zlib) [zlib stream $coding]
+ if {$coding eq {deflateX}} {
+ # Use the standards-compliant choice.
+ set coding2 decompress
+ } else {
+ set coding2 $coding
+ }
+ lappend state(zlib) [zlib stream $coding2]
}
- make-transformation-chunked $sock [namespace code [list CopyChunk $token]]
+ MakeTransformationChunked $sock [namespace code [list CopyChunk $token]]
} else {
if {$initial} {
foreach coding [ContentEncoding $token] {
- zlib push $coding $sock
+ if {$coding eq {deflateX}} {
+ # Use the standards-compliant choice.
+ set coding2 decompress
+ } else {
+ set coding2 $coding
+ }
+ zlib push $coding2 $sock
}
}
if {[catch {
@@ -3212,6 +4451,7 @@ proc http::CopyStart {sock token {initial 1}} {
Finish $token $err
}
}
+ return
}
proc http::CopyChunk {token chunk} {
@@ -3225,7 +4465,7 @@ proc http::CopyChunk {token chunk} {
}
puts -nonewline $state(-channel) $chunk
if {[info exists state(-progress)]} {
- eval [linsert $state(-progress) end \
+ namespace eval :: [linsert $state(-progress) end \
$token $state(totalsize) $state(currentsize)]
}
} else {
@@ -3233,7 +4473,12 @@ proc http::CopyChunk {token chunk} {
if {[info exists state(zlib)]} {
set excess ""
foreach stream $state(zlib) {
- catch {set excess [$stream add -finalize $excess]}
+ catch {
+ $stream put -finalize $excess
+ set excess ""
+ set overflood ""
+ while {[set overflood [$stream get]] ne ""} { append excess $overflood }
+ }
}
puts -nonewline $state(-channel) $excess
foreach stream $state(zlib) { $stream close }
@@ -3241,6 +4486,7 @@ proc http::CopyChunk {token chunk} {
}
Eot $token ;# FIX ME: pipelining.
}
+ return
}
# http::CopyDone
@@ -3260,7 +4506,7 @@ proc http::CopyDone {token count {error {}}} {
set sock $state(sock)
incr state(currentsize) $count
if {[info exists state(-progress)]} {
- eval $state(-progress) \
+ namespace eval :: $state(-progress) \
[list $token $state(totalsize) $state(currentsize)]
}
# At this point the token may have been reset.
@@ -3271,6 +4517,7 @@ proc http::CopyDone {token count {error {}}} {
} else {
CopyStart $sock $token 0
}
+ return
}
# http::Eot
@@ -3316,7 +4563,20 @@ proc http::Eot {token {reason {}}} {
if {[string length $state(body)] > 0} {
if {[catch {
foreach coding [ContentEncoding $token] {
- set state(body) [zlib $coding $state(body)]
+ if {$coding eq {deflateX}} {
+ # First try the standards-compliant choice.
+ set coding2 decompress
+ if {[catch {zlib $coding2 $state(body)} result]} {
+ # If that fails, try the MS non-compliant choice.
+ set coding2 inflate
+ set state(body) [zlib $coding2 $state(body)]
+ } else {
+ # error {failed at standards-compliant deflate}
+ set state(body) $result
+ }
+ } else {
+ set state(body) [zlib $coding $state(body)]
+ }
}
} err]} {
Log "error doing decompression for token $token: $err"
@@ -3332,16 +4592,106 @@ proc http::Eot {token {reason {}}} {
set enc [CharsetToEncoding $state(charset)]
if {$enc ne "binary"} {
- set state(body) [encoding convertfrom $enc $state(body)]
+ if {[package vsatisfies [package provide Tcl] 9.0-]} {
+ set state(body) [encoding convertfrom -profile tcl8 $enc $state(body)]
+ } else {
+ set state(body) [encoding convertfrom $enc $state(body)]
+ }
}
# Translate text line endings.
set state(body) [string map {\r\n \n \r \n} $state(body)]
}
+ if {[info exists state(-guesstype)] && $state(-guesstype)} {
+ GuessType $token
+ }
}
Finish $token $reason
+ return
}
+
+# ------------------------------------------------------------------------------
+# Proc http::GuessType
+# ------------------------------------------------------------------------------
+# Command to attempt limited analysis of a resource with undetermined
+# Content-Type, i.e. "application/octet-stream". This value can be set for two
+# reasons:
+# (a) by the server, in a Content-Type header
+# (b) by http::geturl, as the default value if the server does not supply a
+# Content-Type header.
+#
+# This command converts a resource if:
+# (1) it has type application/octet-stream
+# (2) it begins with an XML declaration "<?xml name="value" ... >?"
+# (3) one tag is named "encoding" and has a recognised value; or no "encoding"
+# tag exists (defaulting to utf-8)
+#
+# RFC 9110 Sec. 8.3 states:
+# "If a Content-Type header field is not present, the recipient MAY either
+# assume a media type of "application/octet-stream" ([RFC2046], Section 4.5.1)
+# or examine the data to determine its type."
+#
+# The RFC goes on to describe the pitfalls of "MIME sniffing", including
+# possible security risks.
+#
+# Arguments:
+# token - connection token
+#
+# Return Value: (boolean) true iff a change has been made
+# ------------------------------------------------------------------------------
+
+proc http::GuessType {token} {
+ variable $token
+ upvar 0 $token state
+
+ if {$state(type) ne {application/octet-stream}} {
+ return 0
+ }
+
+ set body $state(body)
+ # e.g. {<?xml version="1.0" encoding="utf-8"?> ...}
+
+ if {![regexp -nocase -- {^<[?]xml[[:space:]][^>?]*[?]>} $body match]} {
+ return 0
+ }
+ # e.g. {<?xml version="1.0" encoding="utf-8"?>}
+
+ set contents [regsub -- {[[:space:]]+} $match { }]
+ set contents [string range [string tolower $contents] 6 end-2]
+ # e.g. {version="1.0" encoding="utf-8"}
+ # without excess whitespace or upper-case letters
+
+ if {![regexp -- {^([^=" ]+="[^"]+" )+$} "$contents "]} {
+ return 0
+ }
+ # The application/xml default encoding:
+ set res utf-8
+
+ set tagList [regexp -all -inline -- {[^=" ]+="[^"]+"} $contents]
+ foreach tag $tagList {
+ regexp -- {([^=" ]+)="([^"]+)"} $tag -> name value
+ if {$name eq {encoding}} {
+ set res $value
+ }
+ }
+ set enc [CharsetToEncoding $res]
+ if {$enc eq "binary"} {
+ return 0
+ }
+ if {[package vsatisfies [package provide Tcl] 9.0-]} {
+ set state(body) [encoding convertfrom -profile tcl8 $enc $state(body)]
+ } else {
+ set state(body) [encoding convertfrom $enc $state(body)]
+ }
+ set state(body) [string map {\r\n \n \r \n} $state(body)]
+ set state(type) application/xml
+ set state(binary) 0
+ set state(charset) $res
+ return 1
+}
+
+
# http::wait --
#
# See documentation for details.
@@ -3379,14 +4729,14 @@ proc http::wait {token} {
proc http::formatQuery {args} {
if {[llength $args] % 2} {
return \
- -code error \
- -errorcode [list HTTP BADARGCNT $args] \
- {Incorrect number of arguments, must be an even number.}
+ -code error \
+ -errorcode [list HTTP BADARGCNT $args] \
+ {Incorrect number of arguments, must be an even number.}
}
set result ""
set sep ""
foreach i $args {
- append result $sep [mapReply $i]
+ append result $sep [quoteString $i]
if {$sep eq "="} {
set sep &
} else {
@@ -3396,7 +4746,7 @@ proc http::formatQuery {args} {
return $result
}
-# http::mapReply --
+# http::quoteString --
#
# Do x-www-urlencoded character mapping
#
@@ -3406,7 +4756,7 @@ proc http::formatQuery {args} {
# Results:
# The encoded string
-proc http::mapReply {string} {
+proc http::quoteString {string} {
variable http
variable formMap
@@ -3414,20 +4764,13 @@ proc http::mapReply {string} {
# a pre-computed map and [string map] to do the conversion (much faster
# than [regsub]/[subst]). [Bug 1020491]
- if {$http(-urlencoding) ne ""} {
+ if {[package vsatisfies [package provide Tcl] 9.0-]} {
+ set string [encoding convertto -profile tcl8 $http(-urlencoding) $string]
+ } else {
set string [encoding convertto $http(-urlencoding) $string]
- return [string map $formMap $string]
- }
- set converted [string map $formMap $string]
- if {[string match "*\[\u0100-\uffff\]*" $converted]} {
- regexp "\[\u0100-\uffff\]" $converted badChar
- # Return this error message for maximum compatibility... :^/
- return -code error \
- "can't read \"formMap($badChar)\": no such element in array"
}
- return $converted
+ return [string map $formMap $string]
}
-interp alias {} http::quoteString {} http::mapReply
# http::ProxyRequired --
# Default proxy filter.
@@ -3440,15 +4783,23 @@ interp alias {} http::quoteString {} http::mapReply
proc http::ProxyRequired {host} {
variable http
- if {[info exists http(-proxyhost)] && [string length $http(-proxyhost)]} {
- if {
- ![info exists http(-proxyport)] ||
- ![string length $http(-proxyport)]
- } {
- set http(-proxyport) 8080
+ if {(![info exists http(-proxyhost)]) || ($http(-proxyhost) eq {})} {
+ return
+ }
+ if {![info exists http(-proxyport)] || ($http(-proxyport) eq {})} {
+ set port 8080
+ } else {
+ set port $http(-proxyport)
+ }
+
+ # Simple test (cf. autoproxy) for hosts that must be accessed directly,
+ # not through the proxy server.
+ foreach domain $http(-proxynot) {
+ if {[string match -nocase $domain $host]} {
+ return {}
}
- return [list $http(-proxyhost) $http(-proxyport)]
}
+ return [list $http(-proxyhost) $port]
}
# http::CharsetToEncoding --
@@ -3493,16 +4844,41 @@ proc http::CharsetToEncoding {charset} {
}
}
+
+# ------------------------------------------------------------------------------
+# Proc http::ContentEncoding
+# ------------------------------------------------------------------------------
# Return the list of content-encoding transformations we need to do in order.
+#
+ # --------------------------------------------------------------------------
+ # Options for Accept-Encoding, Content-Encoding: the switch command
+ # --------------------------------------------------------------------------
+ # The symbol deflateX allows http to attempt both versions of "deflate",
+ # unless there is a -channel - for a -channel, only "decompress" is tried.
+ # Alternative/extra lines for switch:
+ # The standards-compliant version of "deflate" can be chosen with:
+ # deflate { lappend r decompress }
+ # The Microsoft non-compliant version of "deflate" can be chosen with:
+ # deflate { lappend r inflate }
+ # The previously used implementation of "compress", which appears to be
+ # incorrect and is rarely used by web servers, can be chosen with:
+ # compress - x-compress { lappend r decompress }
+ # --------------------------------------------------------------------------
+#
+# Arguments:
+# token - Connection token.
+#
+# Return Value: list
+# ------------------------------------------------------------------------------
+
proc http::ContentEncoding {token} {
upvar 0 $token state
set r {}
if {[info exists state(coding)]} {
foreach coding [split $state(coding) ,] {
switch -exact -- $coding {
- deflate { lappend r inflate }
+ deflate { lappend r deflateX }
gzip - x-gzip { lappend r gunzip }
- compress - x-compress { lappend r decompress }
identity {}
br {
return -code error\
@@ -3593,9 +4969,544 @@ proc http::GetFieldValue {headers fieldName} {
return $r
}
-proc http::make-transformation-chunked {chan command} {
+proc http::MakeTransformationChunked {chan command} {
coroutine [namespace current]::dechunk$chan ::http::ReceiveChunked $chan $command
chan event $chan readable [namespace current]::dechunk$chan
+ return
+}
+
+interp alias {} http::data {} http::responseBody
+interp alias {} http::code {} http::responseLine
+interp alias {} http::mapReply {} http::quoteString
+interp alias {} http::meta {} http::responseHeaders
+interp alias {} http::metaValue {} http::responseHeaderValue
+interp alias {} http::ncode {} http::responseCode
+
+
+# ------------------------------------------------------------------------------
+# Proc http::socketAsCallback
+# ------------------------------------------------------------------------------
+# Command to use in place of ::socket as the value of ::tls::socketCmd.
+# This command does the same as http::AltSocket, and also handles https
+# connections through a proxy server.
+#
+# Notes.
+# - The proxy server works differently for https and http. This implementation
+# is for https. The proxy for http is implemented in http::CreateToken (in
+# code that was previously part of http::geturl).
+# - This code implicitly uses the tls options set for https in a call to
+# http::register, and does not need to call commands tls::*. This simple
+# implementation is possible because tls uses a callback to ::socket that can
+# be redirected by changing the value of ::tls::socketCmd.
+#
+# Arguments:
+# args - as for ::socket
+#
+# Return Value: a socket identifier
+# ------------------------------------------------------------------------------
+
+proc http::socketAsCallback {args} {
+ variable http
+
+ set targ [lsearch -exact $args -type]
+ if {$targ != -1} {
+ set token [lindex $args $targ+1]
+ upvar 0 ${token} state
+ set protoProxyConn $state(protoProxyConn)
+ } else {
+ set protoProxyConn 0
+ }
+
+ set host [lindex $args end-1]
+ set port [lindex $args end]
+ if { ($http(-proxyfilter) ne {})
+ && (![catch {$http(-proxyfilter) $host} proxy])
+ && $protoProxyConn
+ } {
+ set phost [lindex $proxy 0]
+ set pport [lindex $proxy 1]
+ } else {
+ set phost {}
+ set pport {}
+ }
+ if {$phost eq ""} {
+ set sock [::http::AltSocket {*}$args]
+ } else {
+ set sock [::http::SecureProxyConnect {*}$args $phost $pport]
+ }
+ return $sock
+}
+
+
+# ------------------------------------------------------------------------------
+# Proc http::SecureProxyConnect
+# ------------------------------------------------------------------------------
+# Command to open a socket through a proxy server to a remote server for use by
+# tls. The caller must perform the tls handshake.
+#
+# Notes
+# - Based on patch supplied by Melissa Chawla in ticket 1173760, and
+# Proxy-Authorization header cf. autoproxy by Pat Thoyts.
+# - Rewritten as a call to http::geturl, because response headers and body are
+# needed if the CONNECT request fails. CONNECT is implemented for this case
+# only, by state(bypass).
+# - FUTURE WORK: give http::geturl a -connect option for a general CONNECT.
+# - The request header Proxy-Connection is discouraged in RFC 7230 (June 2014),
+# RFC 9112 (June 2022).
+#
+# Arguments:
+# args - as for ::socket, ending in host, port; with proxy host, proxy
+# port appended.
+#
+# Return Value: a socket identifier
+# ------------------------------------------------------------------------------
+
+proc http::SecureProxyConnect {args} {
+ variable http
+ variable ConnectVar
+ variable ConnectCounter
+ variable failedProxyValues
+ set varName ::http::ConnectVar([incr ConnectCounter])
+
+ # Extract (non-proxy) target from args.
+ set host [lindex $args end-3]
+ set port [lindex $args end-2]
+ set args [lreplace $args end-3 end-2]
+
+ # Proxy server URL for connection.
+ # This determines where the socket is opened.
+ set phost [lindex $args end-1]
+ set pport [lindex $args end]
+ if {[string first : $phost] != -1} {
+ # IPv6 address, wrap it in [] so we can append :pport
+ set phost "\[${phost}\]"
+ }
+ set url http://${phost}:${pport}
+ # Elements of args other than host and port are not used when
+ # AsyncTransaction opens a socket. Those elements are -async and the
+ # -type $tokenName for the https transaction. Option -async is used by
+ # AsyncTransaction anyway, and -type $tokenName should not be
+ # propagated: the proxy request adds its own -type value.
+
+ set targ [lsearch -exact $args -type]
+ if {$targ != -1} {
+ # Record in the token that this is a proxy call.
+ set token [lindex $args $targ+1]
+ upvar 0 ${token} state
+ set tim $state(-timeout)
+ set state(proxyUsed) SecureProxyFailed
+ # This value is overwritten with "SecureProxy" below if the CONNECT is
+ # successful. If it is unsuccessful, the socket will be closed
+ # below, and so in this unsuccessful case there are no other transactions
+ # whose (proxyUsed) must be updated.
+ } else {
+ set tim 0
+ }
+ if {$tim == 0} {
+ # Do not use infinite timeout for the proxy.
+ set tim 30000
+ }
+
+ # Prepare and send a CONNECT request to the proxy, using
+ # code similar to http::geturl.
+ set requestHeaders [list Host $host]
+ lappend requestHeaders Connection keep-alive
+ if {$http(-proxyauth) != {}} {
+ lappend requestHeaders Proxy-Authorization $http(-proxyauth)
+ }
+
+ set token2 [CreateToken $url -keepalive 0 -timeout $tim \
+ -headers $requestHeaders -command [list http::AllDone $varName]]
+ variable $token2
+ upvar 0 $token2 state2
+
+ # Kludges:
+ # Setting this variable overrides the HTTP request line and also allows
+ # -headers to override the Connection: header set by -keepalive.
+ # The arguments "-keepalive 0" ensure that when Finish is called for an
+ # unsuccessful request, the socket is always closed.
+ set state2(bypass) "CONNECT $host:$port HTTP/1.1"
+
+ AsyncTransaction $token2
+
+ if {[info coroutine] ne {}} {
+ # All callers in the http package are coroutines launched by
+ # the event loop.
+ # The cwait command requires a coroutine because it yields
+ # to the caller; $varName is traced and the coroutine resumes
+ # when the variable is written.
+ cwait $varName
+ } else {
+ return -code error {code must run in a coroutine}
+ # For testing with a non-coroutine caller outside the http package.
+ # vwait $varName
+ }
+ unset $varName
+
+ if { ($state2(state) ne "complete")
+ || ($state2(status) ne "ok")
+ || (![string is integer -strict $state2(responseCode)])
+ } {
+ set msg {the HTTP request to the proxy server did not return a valid\
+ and complete response}
+ if {[info exists state2(error)]} {
+ append msg ": " [lindex $state2(error) 0]
+ }
+ cleanup $token2
+ return -code error $msg
+ }
+
+ set code $state2(responseCode)
+
+ if {($code >= 200) && ($code < 300)} {
+ # All OK. The caller in package tls will now call "tls::import $sock".
+ # The cleanup command does not close $sock.
+ # Other tidying was done in http::Event.
+
+ # If this is a persistent socket, any other transactions that are
+ # already marked to use the socket will have their (proxyUsed) updated
+ # when http::OpenSocket calls http::ConfigureNewSocket.
+ set state(proxyUsed) SecureProxy
+ set sock $state2(sock)
+ cleanup $token2
+ return $sock
+ }
+
+ if {$targ != -1} {
+ # Non-OK HTTP status code; token is known because option -type
+ # (cf. targ) was passed through tcltls, and so the useful
+ # parts of the proxy's response can be copied to state(*).
+ # Do not copy state2(sock).
+ # Return the proxy response to the caller of geturl.
+ foreach name $failedProxyValues {
+ if {[info exists state2($name)]} {
+ set state($name) $state2($name)
+ }
+ }
+ set state(connection) close
+ set msg "proxy connect failed: $code"
+ # - This error message will be detected by http::OpenSocket and will
+ # cause it to present the proxy's HTTP response as that of the
+ # original $token transaction, identified only by state(proxyUsed)
+ # as the response of the proxy.
+ # - The cases where this would mislead the caller of http::geturl are
+ # given a different value of msg (below) so that http::OpenSocket will
+ # treat them as errors, but will preserve the $token array for
+ # inspection by the caller.
+ # - Status code 305 (Proxy Required) was deprecated for security reasons
+ # in RFC 2616 (June 1999) and in any case should never be served by a
+ # proxy.
+ # - Other 3xx responses from the proxy are inappropriate, and should not
+ # occur.
+ # - A 401 response from the proxy is inappropriate, and should not
+ # occur. It would be confusing if returned to the caller.
+
+ if {($code >= 300) && ($code < 400)} {
+ set msg "the proxy server responded to the HTTP request with an\
+ inappropriate $code redirect"
+ set loc [responseHeaderValue $token2 location]
+ if {$loc ne {}} {
+ append msg "to " $loc
+ }
+ } elseif {($code == 401)} {
+ set msg "the proxy server responded to the HTTP request with an\
+ inappropriate 401 request for target-host credentials"
+ } else {
+ }
+ } else {
+ set msg "connection to proxy failed with status code $code"
+ }
+
+ # - ${token2}(sock) has already been closed because -keepalive 0.
+ # - Error return does not pass the socket ID to the
+ # $token transaction, which retains its socket placeholder.
+ cleanup $token2
+ return -code error $msg
+}
+
+proc http::AllDone {varName args} {
+ set $varName done
+ return
+}
+
+
+# ------------------------------------------------------------------------------
+# Proc http::AltSocket
+# ------------------------------------------------------------------------------
+# This command is a drop-in replacement for ::socket.
+# Arguments and return value as for ::socket.
+#
+# Notes.
+# - http::AltSocket is specified in place of ::socket by the definition of
+# urlTypes in the namespace header of this file (http.tcl).
+# - The command makes a simple call to ::socket unless the user has called
+# http::config to change the value of -threadlevel from the default value 0.
+# - For -threadlevel 1 or 2, if the Thread package is available, the command
+# waits in the event loop while the socket is opened in another thread. This
+# is a workaround for bug [824251] - it prevents http::geturl from blocking
+# the event loop if the DNS lookup or server connection is slow.
+# - FIXME Use a thread pool if connections are very frequent.
+# - FIXME The peer thread can transfer the socket only to the main interpreter
+# in the present thread. Therefore this code works only if this script runs
+# in the main interpreter. In a child interpreter, the parent must alias a
+# command to ::http::AltSocket in the child, run http::AltSocket in the
+# parent, and then transfer the socket to the child.
+# - The http::AltSocket command is simple, and can easily be replaced with an
+# alternative command that uses a different technique to open a socket while
+# entering the event loop.
+# - Unexpected behaviour by thread::send -async (Thread 2.8.6).
+# An error in thread::send -async causes return of just the error message
+# (not the expected 3 elements), and raises a bgerror in the main thread.
+# Hence wrap the command with catch as a precaution.
+# - Bug in Thread 2.8.8 - on Windows, read/write operations fail on a socket
+# moved from another thread by thread::transfer.
+# ------------------------------------------------------------------------------
+
+proc http::AltSocket {args} {
+ variable ThreadVar
+ variable ThreadCounter
+ variable http
+
+ LoadThreadIfNeeded
+
+ set targ [lsearch -exact $args -type]
+ if {$targ != -1} {
+ set token [lindex $args $targ+1]
+ set args [lreplace $args $targ $targ+1]
+ upvar 0 $token state
+ }
+
+ if {$http(usingThread) && [info exists state] && $state(protoSockThread)} {
+ } else {
+ # Use plain "::socket". This is the default.
+ return [eval ::socket $args]
+ }
+
+ set defcmd ::socket
+ set sockargs $args
+ set script "
+ set code \[catch {
+ [list proc ::SockInThread {caller defcmd sockargs} [info body ::http::SockInThread]]
+ [list ::SockInThread [thread::id] $defcmd $sockargs]
+ } result opts\]
+ list \$code \$opts \$result
+ "
+
+ set state(tid) [thread::create]
+ set varName ::http::ThreadVar([incr ThreadCounter])
+ thread::send -async $state(tid) $script $varName
+ Log >T Thread Start Wait $args -- coro [info coroutine] $varName
+ if {[info coroutine] ne {}} {
+ # All callers in the http package are coroutines launched by
+ # the event loop.
+ # The cwait command requires a coroutine because it yields
+ # to the caller; $varName is traced and the coroutine resumes
+ # when the variable is written.
+ cwait $varName
+ } else {
+ return -code error {code must run in a coroutine}
+ # For testing with a non-coroutine caller outside the http package.
+ # vwait $varName
+ }
+ Log >U Thread End Wait $args -- coro [info coroutine] $varName [set $varName]
+ thread::release $state(tid)
+ set state(tid) {}
+ set result [set $varName]
+ unset $varName
+ if {(![string is list $result]) || ([llength $result] != 3)} {
+ return -code error "result from peer thread is not a list of\
+ length 3: it is \n$result"
+ }
+ lassign $result threadCode threadDict threadResult
+ if {($threadCode != 0)} {
+ # This is an error in thread::send. Return the lot.
+ return -options $threadDict -code error $threadResult
+ }
+
+ # Now the results of the catch in the peer thread.
+ lassign $threadResult catchCode errdict sock
+
+ if {($catchCode == 0) && ($sock ni [chan names])} {
+ return -code error {Transfer of socket from peer thread failed.\
+ Check that this script is not running in a child interpreter.}
+ }
+ return -options $errdict -code $catchCode $sock
+}
+
+# The commands below are dependencies of http::AltSocket and
+# http::SecureProxyConnect and are not used elsewhere.
+
+# ------------------------------------------------------------------------------
+# Proc http::LoadThreadIfNeeded
+# ------------------------------------------------------------------------------
+# Command to load the Thread package if it is needed. If it is needed and not
+# loadable, the outcome depends on $http(-threadlevel):
+# value 0 => Thread package not required, no problem
+# value 1 => operate as if -threadlevel 0
+# value 2 => error return
+#
+# The command assigns a value to http(usingThread), which records whether
+# command http::AltSocket can use a separate thread.
+#
+# Arguments: none
+# Return Value: none
+# ------------------------------------------------------------------------------
+
+proc http::LoadThreadIfNeeded {} {
+ variable http
+ if {$http(-threadlevel) == 0} {
+ set http(usingThread) 0
+ return
+ }
+ if {[catch {package require Thread}]} {
+ if {$http(-threadlevel) == 2} {
+ set msg {[http::config -threadlevel] has value 2,\
+ but the Thread package is not available}
+ return -code error $msg
+ }
+ set http(usingThread) 0
+ return
+ }
+ set http(usingThread) 1
+ return
+}
+
+
+# ------------------------------------------------------------------------------
+# Proc http::SockInThread
+# ------------------------------------------------------------------------------
+# Command http::AltSocket is a ::socket replacement. It defines and runs this
+# command, http::SockInThread, in a peer thread.
+#
+# Arguments:
+# caller
+# defcmd
+# sockargs
+#
+# Return value: list of values that describe the outcome. The return is
+# intended to be a normal (non-error) return in all cases.
+# ------------------------------------------------------------------------------
+
+proc http::SockInThread {caller defcmd sockargs} {
+ package require Thread
+
+ set catchCode [catch {eval $defcmd $sockargs} sock errdict]
+ if {$catchCode == 0} {
+ set catchCode [catch {thread::transfer $caller $sock; set sock} sock errdict]
+ }
+ return [list $catchCode $errdict $sock]
+}
+
+
+# ------------------------------------------------------------------------------
+# Proc http::cwaiter::cwait
+# ------------------------------------------------------------------------------
+# Command to substitute for vwait, without the ordering issues.
+# A command that uses cwait must be a coroutine that is launched by an event,
+# e.g. fileevent or after idle, and has no calling code to be resumed upon
+# "yield". It cannot return a value.
+#
+# Arguments:
+# varName - fully-qualified name of the variable that the calling script
+# will write to resume the coroutine. Any scalar variable or
+# array element is permitted.
+# coroName - (optional) name of the coroutine to be called when varName is
+# written - defaults to this coroutine
+# timeout - (optional) timeout value in ms
+# timeoutValue - (optional) value to assign to varName if there is a timeout
+#
+# Return Value: none
+# ------------------------------------------------------------------------------
+
+namespace eval http::cwaiter {
+ namespace export cwait
+ variable log {}
+ variable logOn 0
+}
+
+proc http::cwaiter::cwait {
+ varName {coroName {}} {timeout {}} {timeoutValue {}}
+} {
+ set thisCoro [info coroutine]
+ if {$thisCoro eq {}} {
+ return -code error {cwait cannot be called outside a coroutine}
+ }
+ if {$coroName eq {}} {
+ set coroName $thisCoro
+ }
+ if {[string range $varName 0 1] ne {::}} {
+ return -code error {argument varName must be fully qualified}
+ }
+ if {$timeout eq {}} {
+ set toe {}
+ } elseif {[string is integer -strict $timeout] && ($timeout > 0)} {
+ set toe [after $timeout [list set $varName $timeoutValue]]
+ } else {
+ return -code error {if timeout is supplied it must be a positive integer}
+ }
+
+ set cmd [list ::http::cwaiter::CwaitHelper $varName $coroName $toe]
+ trace add variable $varName write $cmd
+ CoLog "Yield $varName $coroName"
+ yield
+ CoLog "Resume $varName $coroName"
+ return
+}
+
+
+# ------------------------------------------------------------------------------
+# Proc http::cwaiter::CwaitHelper
+# ------------------------------------------------------------------------------
+# Helper command called by the trace set by cwait.
+# - Ignores the arguments added by trace.
+# - A simple call to $coroName works, and in error cases gives a suitable stack
+# trace, but because it is inside a trace the headline error message is
+# something like {can't set "::Result(6)": error}, not the actual
+# error. So let the trace command return.
+# - Remove the trace immediately. We don't want multiple calls.
+# ------------------------------------------------------------------------------
+
+proc http::cwaiter::CwaitHelper {varName coroName toe args} {
+ CoLog "got $varName for $coroName"
+ set cmd [list ::http::cwaiter::CwaitHelper $varName $coroName $toe]
+ trace remove variable $varName write $cmd
+ after cancel $toe
+
+ after 0 $coroName
+ return
+}
+
+
+# ------------------------------------------------------------------------------
+# Proc http::cwaiter::LogInit
+# ------------------------------------------------------------------------------
+# Call this command to initiate debug logging and clear the log.
+# ------------------------------------------------------------------------------
+
+proc http::cwaiter::LogInit {} {
+ variable log
+ variable logOn
+ set log {}
+ set logOn 1
+ return
+}
+
+proc http::cwaiter::LogRead {} {
+ variable log
+ return $log
+}
+
+proc http::cwaiter::CoLog {msg} {
+ variable log
+ variable logOn
+ if {$logOn} {
+ append log $msg \n
+ }
+ return
+}
+
+namespace eval http {
+ namespace import ::http::cwaiter::*
}
# Local variables:
diff --git a/library/http/pkgIndex.tcl b/library/http/pkgIndex.tcl
index bb742fd..2428d53 100644
--- a/library/http/pkgIndex.tcl
+++ b/library/http/pkgIndex.tcl
@@ -1,2 +1,2 @@
if {![package vsatisfies [package provide Tcl] 8.6-]} {return}
-package ifneeded http 2.9.8 [list tclPkgSetup $dir http 2.9.8 {{http.tcl source {::http::config ::http::formatQuery ::http::geturl ::http::reset ::http::wait ::http::register ::http::unregister ::http::mapReply}}}]
+package ifneeded http 2.10b2 [list tclPkgSetup $dir http 2.10b2 {{http.tcl source {::http::config ::http::formatQuery ::http::geturl ::http::reset ::http::wait ::http::register ::http::unregister ::http::mapReply}}}]
diff --git a/library/http1.0/http.tcl b/library/http1.0/http.tcl
deleted file mode 100644
index ce112d8..0000000
--- a/library/http1.0/http.tcl
+++ /dev/null
@@ -1,377 +0,0 @@
-# http.tcl
-# Client-side HTTP for GET, POST, and HEAD commands.
-# These routines can be used in untrusted code that uses the Safesock
-# security policy.
-# These procedures use a callback interface to avoid using vwait,
-# which is not defined in the safe base.
-#
-# See the http.n man page for documentation
-
-package provide http 1.0
-
-array set http {
- -accept */*
- -proxyhost {}
- -proxyport {}
- -useragent {Tcl http client package 1.0}
- -proxyfilter httpProxyRequired
-}
-proc http_config {args} {
- global http
- set options [lsort [array names http -*]]
- set usage [join $options ", "]
- if {[llength $args] == 0} {
- set result {}
- foreach name $options {
- lappend result $name $http($name)
- }
- return $result
- }
- regsub -all -- - $options {} options
- set pat ^-([join $options |])$
- if {[llength $args] == 1} {
- set flag [lindex $args 0]
- if {[regexp -- $pat $flag]} {
- return $http($flag)
- } else {
- return -code error "Unknown option $flag, must be: $usage"
- }
- } else {
- foreach {flag value} $args {
- if {[regexp -- $pat $flag]} {
- set http($flag) $value
- } else {
- return -code error "Unknown option $flag, must be: $usage"
- }
- }
- }
-}
-
- proc httpFinish { token {errormsg ""} } {
- upvar #0 $token state
- global errorInfo errorCode
- if {[string length $errormsg] != 0} {
- set state(error) [list $errormsg $errorInfo $errorCode]
- set state(status) error
- }
- catch {close $state(sock)}
- catch {after cancel $state(after)}
- if {[info exists state(-command)]} {
- if {[catch {eval $state(-command) {$token}} err]} {
- if {[string length $errormsg] == 0} {
- set state(error) [list $err $errorInfo $errorCode]
- set state(status) error
- }
- }
- unset state(-command)
- }
-}
-proc http_reset { token {why reset} } {
- upvar #0 $token state
- set state(status) $why
- catch {fileevent $state(sock) readable {}}
- httpFinish $token
- if {[info exists state(error)]} {
- set errorlist $state(error)
- unset state(error)
- eval error $errorlist
- }
-}
-proc http_get { url args } {
- global http
- if {![info exists http(uid)]} {
- set http(uid) 0
- }
- set token http#[incr http(uid)]
- upvar #0 $token state
- http_reset $token
- array set state {
- -blocksize 8192
- -validate 0
- -headers {}
- -timeout 0
- state header
- meta {}
- currentsize 0
- totalsize 0
- type text/html
- body {}
- status ""
- }
- set options {-blocksize -channel -command -handler -headers \
- -progress -query -validate -timeout}
- set usage [join $options ", "]
- regsub -all -- - $options {} options
- set pat ^-([join $options |])$
- foreach {flag value} $args {
- if {[regexp $pat $flag]} {
- # Validate numbers
- if {[info exists state($flag)] && \
- [regexp {^[0-9]+$} $state($flag)] && \
- ![regexp {^[0-9]+$} $value]} {
- return -code error "Bad value for $flag ($value), must be integer"
- }
- set state($flag) $value
- } else {
- return -code error "Unknown option $flag, can be: $usage"
- }
- }
- if {! [regexp -nocase {^(http://)?([^/:]+)(:([0-9]+))?(/.*)?$} $url \
- x proto host y port srvurl]} {
- error "Unsupported URL: $url"
- }
- if {[string length $port] == 0} {
- set port 80
- }
- if {[string length $srvurl] == 0} {
- set srvurl /
- }
- if {[string length $proto] == 0} {
- set url http://$url
- }
- set state(url) $url
- if {![catch {$http(-proxyfilter) $host} proxy]} {
- set phost [lindex $proxy 0]
- set pport [lindex $proxy 1]
- }
- if {$state(-timeout) > 0} {
- set state(after) [after $state(-timeout) [list http_reset $token timeout]]
- }
- if {[info exists phost] && [string length $phost]} {
- set srvurl $url
- set s [socket $phost $pport]
- } else {
- set s [socket $host $port]
- }
- set state(sock) $s
-
- # Send data in cr-lf format, but accept any line terminators
-
- fconfigure $s -translation {auto crlf} -buffersize $state(-blocksize)
-
- # The following is disallowed in safe interpreters, but the socket
- # is already in non-blocking mode in that case.
-
- catch {fconfigure $s -blocking off}
- set len 0
- set how GET
- if {[info exists state(-query)]} {
- set len [string length $state(-query)]
- if {$len > 0} {
- set how POST
- }
- } elseif {$state(-validate)} {
- set how HEAD
- }
- puts $s "$how $srvurl HTTP/1.0"
- puts $s "Accept: $http(-accept)"
- puts $s "Host: $host"
- puts $s "User-Agent: $http(-useragent)"
- foreach {key value} $state(-headers) {
- regsub -all \[\n\r\] $value {} value
- set key [string trim $key]
- if {[string length $key]} {
- puts $s "$key: $value"
- }
- }
- if {$len > 0} {
- puts $s "Content-Length: $len"
- puts $s "Content-Type: application/x-www-form-urlencoded"
- puts $s ""
- fconfigure $s -translation {auto binary}
- puts -nonewline $s $state(-query)
- } else {
- puts $s ""
- }
- flush $s
- fileevent $s readable [list httpEvent $token]
- if {! [info exists state(-command)]} {
- http_wait $token
- }
- return $token
-}
-proc http_data {token} {
- upvar #0 $token state
- return $state(body)
-}
-proc http_status {token} {
- upvar #0 $token state
- return $state(status)
-}
-proc http_code {token} {
- upvar #0 $token state
- return $state(http)
-}
-proc http_size {token} {
- upvar #0 $token state
- return $state(currentsize)
-}
-
- proc httpEvent {token} {
- upvar #0 $token state
- set s $state(sock)
-
- if {[eof $s]} {
- httpEof $token
- return
- }
- if {$state(state) == "header"} {
- set n [gets $s line]
- if {$n == 0} {
- set state(state) body
- if {![regexp -nocase ^text $state(type)]} {
- # Turn off conversions for non-text data
- fconfigure $s -translation binary
- if {[info exists state(-channel)]} {
- fconfigure $state(-channel) -translation binary
- }
- }
- if {[info exists state(-channel)] &&
- ![info exists state(-handler)]} {
- # Initiate a sequence of background fcopies
- fileevent $s readable {}
- httpCopyStart $s $token
- }
- } elseif {$n > 0} {
- if {[regexp -nocase {^content-type:(.+)$} $line x type]} {
- set state(type) [string trim $type]
- }
- if {[regexp -nocase {^content-length:(.+)$} $line x length]} {
- set state(totalsize) [string trim $length]
- }
- if {[regexp -nocase {^([^:]+):(.+)$} $line x key value]} {
- lappend state(meta) $key $value
- } elseif {[regexp ^HTTP $line]} {
- set state(http) $line
- }
- }
- } else {
- if {[catch {
- if {[info exists state(-handler)]} {
- set n [eval $state(-handler) {$s $token}]
- } else {
- set block [read $s $state(-blocksize)]
- set n [string length $block]
- if {$n >= 0} {
- append state(body) $block
- }
- }
- if {$n >= 0} {
- incr state(currentsize) $n
- }
- } err]} {
- httpFinish $token $err
- } else {
- if {[info exists state(-progress)]} {
- eval $state(-progress) {$token $state(totalsize) $state(currentsize)}
- }
- }
- }
-}
- proc httpCopyStart {s token} {
- upvar #0 $token state
- if {[catch {
- fcopy $s $state(-channel) -size $state(-blocksize) -command \
- [list httpCopyDone $token]
- } err]} {
- httpFinish $token $err
- }
-}
- proc httpCopyDone {token count {error {}}} {
- upvar #0 $token state
- set s $state(sock)
- incr state(currentsize) $count
- if {[info exists state(-progress)]} {
- eval $state(-progress) {$token $state(totalsize) $state(currentsize)}
- }
- if {([string length $error] != 0)} {
- httpFinish $token $error
- } elseif {[eof $s]} {
- httpEof $token
- } else {
- httpCopyStart $s $token
- }
-}
- proc httpEof {token} {
- upvar #0 $token state
- if {$state(state) == "header"} {
- # Premature eof
- set state(status) eof
- } else {
- set state(status) ok
- }
- set state(state) eof
- httpFinish $token
-}
-proc http_wait {token} {
- upvar #0 $token state
- if {![info exists state(status)] || [string length $state(status)] == 0} {
- vwait $token\(status)
- }
- if {[info exists state(error)]} {
- set errorlist $state(error)
- unset state(error)
- eval error $errorlist
- }
- return $state(status)
-}
-
-# Call http_formatQuery with an even number of arguments, where the first is
-# a name, the second is a value, the third is another name, and so on.
-
-proc http_formatQuery {args} {
- set result ""
- set sep ""
- foreach i $args {
- append result $sep [httpMapReply $i]
- if {$sep != "="} {
- set sep =
- } else {
- set sep &
- }
- }
- return $result
-}
-
-# do x-www-urlencoded character mapping
-# The spec says: "non-alphanumeric characters are replaced by '%HH'"
-# 1 leave alphanumerics characters alone
-# 2 Convert every other character to an array lookup
-# 3 Escape constructs that are "special" to the tcl parser
-# 4 "subst" the result, doing all the array substitutions
-
- proc httpMapReply {string} {
- global httpFormMap
- set alphanumeric a-zA-Z0-9
- if {![info exists httpFormMap]} {
-
- for {set i 1} {$i <= 256} {incr i} {
- set c [format %c $i]
- if {![string match \[$alphanumeric\] $c]} {
- set httpFormMap($c) %[format %.2x $i]
- }
- }
- # These are handled specially
- array set httpFormMap {
- " " + \n %0d%0a
- }
- }
- regsub -all \[^$alphanumeric\] $string {$httpFormMap(&)} string
- regsub -all \n $string {\\n} string
- regsub -all \t $string {\\t} string
- regsub -all {[][{})\\]\)} $string {\\&} string
- return [subst $string]
-}
-
-# Default proxy filter.
- proc httpProxyRequired {host} {
- global http
- if {[info exists http(-proxyhost)] && [string length $http(-proxyhost)]} {
- if {![info exists http(-proxyport)] || ![string length $http(-proxyport)]} {
- set http(-proxyport) 8080
- }
- return [list $http(-proxyhost) $http(-proxyport)]
- } else {
- return {}
- }
-}
diff --git a/library/http1.0/pkgIndex.tcl b/library/http1.0/pkgIndex.tcl
deleted file mode 100644
index ab6170f..0000000
--- a/library/http1.0/pkgIndex.tcl
+++ /dev/null
@@ -1,11 +0,0 @@
-# Tcl package index file, version 1.0
-# This file is generated by the "pkg_mkIndex" command
-# and sourced either when an application starts up or
-# by a "package unknown" script. It invokes the
-# "package ifneeded" command to set up package-related
-# information so that packages will be loaded automatically
-# in response to "package require" commands. When this
-# script is sourced, the variable $dir must contain the
-# full path name of this file's directory.
-
-package ifneeded http 1.0 [list tclPkgSetup $dir http 1.0 {{http.tcl source {httpCopyDone httpCopyStart httpEof httpEvent httpFinish httpMapReply httpProxyRequired http_code http_config http_data http_formatQuery http_get http_reset http_size http_status http_wait}}}]
diff --git a/library/init.tcl b/library/init.tcl
index e4e184f..09a02ad 100644
--- a/library/init.tcl
+++ b/library/init.tcl
@@ -3,10 +3,11 @@
# 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 Kevin B. Kenny.
+# Copyright © 1991-1993 The Regents of the University of California.
+# Copyright © 1994-1996 Sun Microsystems, Inc.
+# Copyright © 1998-1999 Scriptics Corporation.
+# Copyright © 2004 Kevin B. Kenny.
+# Copyright © 2018 Sean Woods
#
# All rights reserved.
#
@@ -14,11 +15,7 @@
# 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.14
+package require -exact tcl 8.7b1
# Compute the auto path to use in this interpreter.
# The values on the path come from several locations:
@@ -81,81 +78,10 @@ namespace eval tcl {
}
unset Dir Path
}
-
- # TIP #255 min and max functions
- namespace eval mathfunc {
- proc min {args} {
- if {![llength $args]} {
- return -code error \
- "not enough 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 \
- "not enough 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
- }
-}
+namespace eval tcl::Pkg {}
+
# Setup the unknown package handler
@@ -175,22 +101,15 @@ if {[interp issafe]} {
# 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]]
- }
+ apply {{} {
+ set cmdmap [dict create]
+ foreach cmd {add clicks format microseconds milliseconds scan seconds} {
+ dict set cmdmap $cmd ::tcl::clock::$cmd
}
-
- rename ::tcl::initClock {}
- }
- ::tcl::initClock
+ namespace inscope ::tcl::clock [list namespace ensemble create -command \
+ ::clock -map $cmdmap]
+ ::tcl::unsupported::clock::configure -init-complete
+ }}
}
# Conditionalize for presence of exec.
@@ -285,9 +204,9 @@ proc unknown args {
set errInfo [dict get $opts -errorinfo]
set errCode [dict get $opts -errorcode]
set cinfo $args
- if {[string bytelength $cinfo] > 150} {
+ if {[string length [encoding convertto utf-8 $cinfo]] > 150} {
set cinfo [string range $cinfo 0 150]
- while {[string bytelength $cinfo] > 150} {
+ while {[string length [encoding convertto utf-8 $cinfo]] > 150} {
set cinfo [string range $cinfo 0 end-1]
}
append cinfo ...
@@ -467,6 +386,22 @@ proc auto_load {cmd {namespace {}}} {
return 0
}
+# ::tcl::Pkg::source --
+# This procedure provides an alternative "source" command, which doesn't
+# register the file for the "package files" command. Safe interpreters
+# don't have to do anything special.
+#
+# Arguments:
+# filename
+
+proc ::tcl::Pkg::source {filename} {
+ if {[interp issafe]} {
+ uplevel 1 [list ::source $filename]
+ } else {
+ uplevel 1 [list ::source -nopkg $filename]
+ }
+}
+
# 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
@@ -498,7 +433,7 @@ proc auto_load_index {} {
continue
} else {
set error [catch {
- fconfigure $f -eofchar "\x1A {}" -encoding utf-8
+ fconfigure $f -encoding utf-8 -eofchar "\x1A {}"
set id [gets $f]
if {$id eq "# Tcl autoload index file, version 2.0"} {
eval [read $f]
@@ -510,7 +445,7 @@ proc auto_load_index {} {
}
set name [lindex $line 0]
set auto_index($name) \
- "source -encoding utf-8 [file join $dir [lindex $line 1]]"
+ "::tcl::Pkg::source [file join $dir [lindex $line 1]]"
}
} else {
error "[file join $dir tclIndex] isn't a proper Tcl index file"
@@ -782,7 +717,7 @@ proc tcl::CopyDirectory {action src dest} {
# the following code is now commented out.
#
# return -code error "error $action \"$src\" to\
- # \"$dest\": file already exists"
+ # \"$dest\": file exists"
} else {
# Depending on the platform, and on the current
# working directory, the directories '.', '..'
@@ -794,7 +729,7 @@ proc tcl::CopyDirectory {action src dest} {
foreach s $existing {
if {[file tail $s] ni {. ..}} {
return -code error "error $action \"$src\" to\
- \"$dest\": file already exists"
+ \"$dest\": file exists"
}
}
}
diff --git a/library/install.tcl b/library/install.tcl
new file mode 100644
index 0000000..50e40df
--- /dev/null
+++ b/library/install.tcl
@@ -0,0 +1,247 @@
+###
+# Installer actions built into tclsh and invoked
+# if the first command line argument is "install"
+###
+if {[llength $argv] < 2} {
+ exit 0
+}
+namespace eval ::practcl {}
+###
+# Installer tools
+###
+proc ::practcl::_isdirectory name {
+ return [file isdirectory $name]
+}
+###
+# Return true if the pkgindex file contains
+# any statement other than "package ifneeded"
+# and/or if any package ifneeded loads a DLL
+###
+proc ::practcl::_pkgindex_directory {path} {
+ set buffer {}
+ set pkgidxfile [file join $path pkgIndex.tcl]
+ if {![file exists $pkgidxfile]} {
+ # No pkgIndex file, read the source
+ foreach file [glob -nocomplain $path/*.tm] {
+ set file [file normalize $file]
+ set fname [file rootname [file tail $file]]
+ ###
+ # We used to be able to ... Assume the package is correct in the filename
+ # No hunt for a "package provides"
+ ###
+ set package [lindex [split $fname -] 0]
+ set version [lindex [split $fname -] 1]
+ ###
+ # Read the file, and override assumptions as needed
+ ###
+ set fin [open $file r]
+ fconfigure $fin -encoding utf-8 -eofchar "\x1A {}"
+ set dat [read $fin]
+ close $fin
+ # Look for a teapot style Package statement
+ foreach line [split $dat \n] {
+ set line [string trim $line]
+ if { [string range $line 0 9] != "# Package " } continue
+ set package [lindex $line 2]
+ set version [lindex $line 3]
+ break
+ }
+ # Look for a package provide statement
+ foreach line [split $dat \n] {
+ set line [string trim $line]
+ if { [string range $line 0 14] != "package provide" } continue
+ set package [lindex $line 2]
+ set version [lindex $line 3]
+ break
+ }
+ append buffer "package ifneeded $package $version \[list source \[file join \$dir [file tail $file]\]\]" \n
+ }
+ foreach file [glob -nocomplain $path/*.tcl] {
+ if { [file tail $file] == "version_info.tcl" } continue
+ set fin [open $file r]
+ fconfigure $fin -encoding utf-8 -eofchar "\x1A {}"
+ set dat [read $fin]
+ close $fin
+ if {![regexp "package provide" $dat]} continue
+ set fname [file rootname [file tail $file]]
+ # Look for a package provide statement
+ foreach line [split $dat \n] {
+ set line [string trim $line]
+ if { [string range $line 0 14] != "package provide" } continue
+ set package [lindex $line 2]
+ set version [lindex $line 3]
+ if {[string index $package 0] in "\$ \[ @"} continue
+ if {[string index $version 0] in "\$ \[ @"} continue
+ append buffer "package ifneeded $package $version \[list source \[file join \$dir [file tail $file]\]\]" \n
+ break
+ }
+ }
+ return $buffer
+ }
+ set fin [open $pkgidxfile r]
+ fconfigure $fin -encoding utf-8 -eofchar "\x1A {}"
+ set dat [read $fin]
+ close $fin
+ set trace 0
+ #if {[file tail $path] eq "tool"} {
+ # set trace 1
+ #}
+ set thisline {}
+ foreach line [split $dat \n] {
+ append thisline $line \n
+ if {![info complete $thisline]} continue
+ set line [string trim $line]
+ if {[string length $line]==0} {
+ set thisline {} ; continue
+ }
+ if {[string index $line 0] eq "#"} {
+ set thisline {} ; continue
+ }
+ if {[regexp "if.*catch.*package.*Tcl.*return" $thisline]} {
+ if {$trace} {puts "[file dirname $pkgidxfile] Ignoring $thisline"}
+ set thisline {} ; continue
+ }
+ if {[regexp "if.*package.*vsatisfies.*package.*provide.*return" $thisline]} {
+ if {$trace} { puts "[file dirname $pkgidxfile] Ignoring $thisline" }
+ set thisline {} ; continue
+ }
+ if {![regexp "package.*ifneeded" $thisline]} {
+ # This package index contains arbitrary code
+ # source instead of trying to add it to the main
+ # package index
+ if {$trace} { puts "[file dirname $pkgidxfile] Arbitrary code $thisline" }
+ return {source [file join $dir pkgIndex.tcl]}
+ }
+ append buffer $thisline \n
+ set thisline {}
+ }
+ if {$trace} {puts [list [file dirname $pkgidxfile] $buffer]}
+ return $buffer
+}
+
+
+proc ::practcl::_pkgindex_path_subdir {path} {
+ set result {}
+ foreach subpath [glob -nocomplain [file join $path *]] {
+ if {[file isdirectory $subpath]} {
+ lappend result $subpath {*}[_pkgindex_path_subdir $subpath]
+ }
+ }
+ return $result
+}
+###
+# Index all paths given as though they will end up in the same
+# virtual file system
+###
+proc ::practcl::pkgindex_path args {
+ set stack {}
+ set buffer {
+lappend ::PATHSTACK $dir
+ }
+ foreach base $args {
+ set base [file normalize $base]
+ set paths {}
+ foreach dir [glob -nocomplain [file join $base *]] {
+ if {[file tail $dir] eq "teapot"} continue
+ lappend paths $dir {*}[::practcl::_pkgindex_path_subdir $dir]
+ }
+ set i [string length $base]
+ # Build a list of all of the paths
+ if {[llength $paths]} {
+ foreach path $paths {
+ if {$path eq $base} continue
+ set path_indexed($path) 0
+ }
+ } else {
+ puts [list WARNING: NO PATHS FOUND IN $base]
+ }
+ set path_indexed($base) 1
+ set path_indexed([file join $base boot tcl]) 1
+ foreach teapath [glob -nocomplain [file join $base teapot *]] {
+ set pkg [file tail $teapath]
+ append buffer [list set pkg $pkg]
+ append buffer {
+set pkginstall [file join $::g(HOME) teapot $pkg]
+if {![file exists $pkginstall]} {
+ installDir [file join $dir teapot $pkg] $pkginstall
+}
+}
+ }
+ foreach path $paths {
+ if {$path_indexed($path)} continue
+ set thisdir [file_relative $base $path]
+ set idxbuf [::practcl::_pkgindex_directory $path]
+ if {[string length $idxbuf]} {
+ incr path_indexed($path)
+ append buffer "set dir \[set PKGDIR \[file join \[lindex \$::PATHSTACK end\] $thisdir\]\]" \n
+ append buffer [string map {$dir $PKGDIR} [string trimright $idxbuf]] \n
+ }
+ }
+ }
+ append buffer {
+set dir [lindex $::PATHSTACK end]
+set ::PATHSTACK [lrange $::PATHSTACK 0 end-1]
+}
+ return $buffer
+}
+
+###
+# topic: 64319f4600fb63c82b2258d908f9d066
+# description: Script to build the VFS file system
+###
+proc ::practcl::installDir {d1 d2} {
+
+ puts [format {%*sCreating %s} [expr {4 * [info level]}] {} [file tail $d2]]
+ file delete -force -- $d2
+ file mkdir $d2
+
+ foreach ftail [glob -directory $d1 -nocomplain -tails *] {
+ set f [file join $d1 $ftail]
+ if {[file isdirectory $f] && [string compare CVS $ftail]} {
+ installDir $f [file join $d2 $ftail]
+ } elseif {[file isfile $f]} {
+ file copy -force $f [file join $d2 $ftail]
+ if {$::tcl_platform(platform) eq {unix}} {
+ file attributes [file join $d2 $ftail] -permissions 0o644
+ } else {
+ file attributes [file join $d2 $ftail] -readonly 1
+ }
+ }
+ }
+
+ if {$::tcl_platform(platform) eq {unix}} {
+ file attributes $d2 -permissions 0o755
+ } else {
+ file attributes $d2 -readonly 1
+ }
+}
+
+proc ::practcl::copyDir {d1 d2 {toplevel 1}} {
+ #if {$toplevel} {
+ # puts [list ::practcl::copyDir $d1 -> $d2]
+ #}
+ #file delete -force -- $d2
+ file mkdir $d2
+
+ foreach ftail [glob -directory $d1 -nocomplain -tails *] {
+ set f [file join $d1 $ftail]
+ if {[file isdirectory $f] && [string compare CVS $ftail]} {
+ copyDir $f [file join $d2 $ftail] 0
+ } elseif {[file isfile $f]} {
+ file copy -force $f [file join $d2 $ftail]
+ }
+ }
+}
+
+switch [lindex $argv 1] {
+ mkzip {
+ zipfs mkzip {*}[lrange $argv 2 end]
+ }
+ mkzip {
+ zipfs mkimg {*}[lrange $argv 2 end]
+ }
+ default {
+ ::practcl::[lindex $argv 1] {*}[lrange $argv 2 end]
+ }
+}
+exit 0
diff --git a/library/manifest.txt b/library/manifest.txt
new file mode 100644
index 0000000..ab8bb15
--- /dev/null
+++ b/library/manifest.txt
@@ -0,0 +1,19 @@
+###
+# Package manifest for all Tcl packages included in the /library file system
+###
+apply {{dir} {
+ set isafe [interp issafe]
+ foreach {safe package version file} {
+ 0 http 2.10b2 {http http.tcl}
+ 1 msgcat 1.7.1 {msgcat msgcat.tcl}
+ 1 opt 0.4.9 {opt optparse.tcl}
+ 0 cookiejar 0.2.0 {cookiejar cookiejar.tcl}
+ 0 tcl::idna 1.0.1 {cookiejar idna.tcl}
+ 0 platform 1.0.19 {platform platform.tcl}
+ 0 platform::shell 1.1.4 {platform shell.tcl}
+ 1 tcltest 2.5.8 {tcltest tcltest.tcl}
+ } {
+ if {$isafe && !$safe} continue
+ package ifneeded $package $version [list source [file join $dir {*}$file]]
+ }
+}} $dir
diff --git a/library/msgcat/msgcat.tcl b/library/msgcat/msgcat.tcl
index e112470..eaaafa8 100644
--- a/library/msgcat/msgcat.tcl
+++ b/library/msgcat/msgcat.tcl
@@ -4,22 +4,24 @@
# message catalog facility for Tcl programs. It should be
# loaded with the command "package require msgcat".
#
-# Copyright (c) 2010-2015 Harald Oehlmann.
-# Copyright (c) 1998-2000 Ajuba Solutions.
-# Copyright (c) 1998 Mark Harrison.
+# Copyright © 2010-2018 Harald Oehlmann.
+# Copyright © 1998-2000 Ajuba Solutions.
+# Copyright © 1998 Mark Harrison.
#
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
-package require Tcl 8.5-
+# We use oo::define::self, which is new in Tcl 8.7
+package require Tcl 8.7-
# When the version number changes, be sure to update the pkgIndex.tcl file,
# and the installation directory in the Makefiles.
-package provide msgcat 1.6.1
+package provide msgcat 1.7.1
namespace eval msgcat {
- namespace export mc mcexists mcload mclocale mcmax mcmset mcpreferences mcset\
+ namespace export mc mcn mcexists mcload mclocale mcmax\
+ mcmset mcpreferences mcset\
mcunknown mcflset mcflmset mcloadedlocales mcforgetpackage\
- mcpackageconfig mcpackagelocale
+ mcpackagenamespaceget mcpackageconfig mcpackagelocale mcutil
# Records the list of locales to search
variable Loclist {}
@@ -41,6 +43,12 @@ namespace eval msgcat {
# namespace should be themselves dict values and the value is
# the translated string.
variable Msgs [dict create]
+}
+
+# create ensemble namespace for mcutil command
+namespace eval msgcat::mcutil {
+ namespace export getsystemlocale getpreferences
+ namespace ensemble create -prefix 0
# Map of language codes used in Windows registry to those of ISO-639
if {[info sharedlibextension] eq ".dll"} {
@@ -192,10 +200,31 @@ namespace eval msgcat {
# Returns the translated string. Propagates errors thrown by the
# format command.
-proc msgcat::mc {src args} {
- # this may be replaced by:
- # return [mcget -namespace [uplevel 1 [list ::namespace current]] --\
- # $src {*}$args]
+proc msgcat::mc {args} {
+ tailcall mcn [PackageNamespaceGet] {*}$args
+}
+
+# msgcat::mcn --
+#
+# Find the translation for the given string based on the current
+# locale setting. Check the passed namespace first, then look in each
+# parent namespace until the source is found. If additional args are
+# specified, use the format command to work them into the translated
+# string.
+#
+# If no catalog item is found, mcunknown is called in the caller frame
+# and its result is returned.
+#
+# Arguments:
+# ns Package namespace of the translation
+# src The string to translate.
+# args Args to pass to the format command
+#
+# Results:
+# Returns the translated string. Propagates errors thrown by the
+# format command.
+
+proc msgcat::mcn {ns src args} {
# Check for the src in each namespace starting from the local and
# ending in the global.
@@ -203,7 +232,6 @@ proc msgcat::mc {src args} {
variable Msgs
variable Loclist
- set ns [uplevel 1 [list ::namespace current]]
set loclist [PackagePreferences $ns]
set nscur $ns
@@ -219,7 +247,7 @@ proc msgcat::mc {src args} {
# call package local or default unknown command
set args [linsert $args 0 [lindex $loclist 0] $src]
switch -exact -- [Invoke unknowncmd $args $ns result 1] {
- 0 { return [uplevel 1 [linsert $args 0 [namespace origin mcunknown]]] }
+ 0 { tailcall mcunknown {*}$args }
1 { return [DefaultUnknown {*}$args] }
default { return $result }
}
@@ -245,31 +273,39 @@ proc msgcat::mcexists {args} {
variable Loclist
variable PackageConfig
- set ns [uplevel 1 [list ::namespace current]]
- set loclist [PackagePreferences $ns]
-
while {[llength $args] != 1} {
set args [lassign $args option]
switch -glob -- $option {
- -exactnamespace { set exactnamespace 1 }
- -exactlocale { set loclist [lrange $loclist 0 0] }
+ -exactnamespace - -exactlocale { set $option 1 }
+ -namespace {
+ if {[llength $args] < 2} {
+ return -code error\
+ "Argument missing for switch \"-namespace\""
+ }
+ set args [lassign $args ns]
+ }
-* { return -code error "unknown option \"$option\"" }
default {
return -code error "wrong # args: should be\
\"[lindex [info level 0] 0] ?-exactnamespace?\
- ?-exactlocale? src\""
+ ?-exactlocale? ?-namespace ns? src\""
}
}
}
set src [lindex $args 0]
+ if {![info exists ns]} { set ns [PackageNamespaceGet] }
+
+ set loclist [PackagePreferences $ns]
+ if {[info exists -exactlocale]} { set loclist [lrange $loclist 0 0] }
+
while {$ns ne ""} {
foreach loc $loclist {
if {[dict exists $Msgs $ns $loc $src]} {
return 1
}
}
- if {[info exists exactnamespace]} {return 0}
+ if {[info exists -exactnamespace]} {return 0}
set ns [namespace parent $ns]
}
return 0
@@ -303,44 +339,41 @@ proc msgcat::mclocale {args} {
return -code error "invalid newLocale value \"$newLocale\":\
could be path to unsafe code."
}
- if {[lindex $Loclist 0] ne $newLocale} {
- set Loclist [GetPreferences $newLocale]
-
- # locale not loaded jet
- LoadAll $Loclist
- # Invoke callback
- Invoke changecmd $Loclist
- }
+ mcpreferences {*}[mcutil getpreferences $newLocale]
}
return [lindex $Loclist 0]
}
-# msgcat::GetPreferences --
+# msgcat::mcutil::getpreferences --
#
# Get list of locales from a locale.
# The first element is always the lowercase locale.
# Other elements have one component separated by "_" less.
# Multiple "_" are seen as one separator: de__ch_spec de__ch de {}
#
+# This method is part of the ensemble mcutil
+#
# Arguments:
# Locale.
#
# Results:
# Locale list
-proc msgcat::GetPreferences {locale} {
+proc msgcat::mcutil::getpreferences {locale} {
set locale [string tolower $locale]
- set loclist [list $locale]
- while {-1 !=[set pos [string last "_" $locale]]} {
- set locale [string range $locale 0 $pos-1]
- if { "_" ne [string index $locale end] } {
- lappend loclist $locale
+ set result [list {}]
+ set el {}
+ foreach e [split $locale _] {
+ if {$el eq {}} {
+ set el ${e}
+ } else {
+ set el ${el}_${e}
+ }
+ if {[string index $el end] != {_}} {
+ set result [linsert $result 0 $el]
}
}
- if {"" ne [lindex $loclist end]} {
- lappend loclist {}
- }
- return $loclist
+ return $result
}
# msgcat::mcpreferences --
@@ -349,16 +382,51 @@ proc msgcat::GetPreferences {locale} {
# most preferred to least preferred.
#
# Arguments:
-# None.
+# New location list
#
# Results:
# Returns an ordered list of the locales preferred by the user.
-proc msgcat::mcpreferences {} {
+proc msgcat::mcpreferences {args} {
variable Loclist
+
+ if {[llength $args] > 0} {
+ # args is the new loclist
+ if {![ListEqualString $args $Loclist]} {
+ set Loclist $args
+
+ # locale not loaded jet
+ LoadAll $Loclist
+ # Invoke callback
+ Invoke changecmd $Loclist
+ }
+ }
return $Loclist
}
+# msgcat::ListStringEqual --
+#
+# Compare two strings for equal string contents
+#
+# Arguments:
+# list1 first list
+# list2 second list
+#
+# Results:
+# 1 if lists of strings are identical, 0 otherwise
+
+proc msgcat::ListEqualString {list1 list2} {
+ if {[llength $list1] != [llength $list2]} {
+ return 0
+ }
+ foreach item1 $list1 item2 $list2 {
+ if {$item1 ne $item2} {
+ return 0
+ }
+ }
+ return 1
+}
+
# msgcat::mcloadedlocales --
#
# Get or change the list of currently loaded default locales
@@ -442,7 +510,7 @@ proc msgcat::mcloadedlocales {subcommand} {
# Results:
# Empty string, if not stated differently for the subcommand
-proc msgcat::mcpackagelocale {subcommand {locale ""}} {
+proc msgcat::mcpackagelocale {subcommand args} {
# todo: implement using an ensemble
variable Loclist
variable LoadedLocales
@@ -450,27 +518,39 @@ proc msgcat::mcpackagelocale {subcommand {locale ""}} {
variable PackageConfig
# Check option
# check if required item is exactly provided
- if {[llength [info level 0]] == 2} {
- # locale not given
- unset locale
- } else {
- # locale given
- if {$subcommand in
- {"get" "isset" "unset" "preferences" "loaded" "clear"} } {
- return -code error "wrong # args: should be\
- \"[lrange [info level 0] 0 1]\""
- }
- set locale [string tolower $locale]
+ if { [llength $args] > 0
+ && $subcommand in {"get" "isset" "unset" "loaded" "clear"} } {
+ return -code error "wrong # args: should be\
+ \"[lrange [info level 0] 0 1]\""
}
- set ns [uplevel 1 {::namespace current}]
+ set ns [PackageNamespaceGet]
switch -exact -- $subcommand {
get { return [lindex [PackagePreferences $ns] 0] }
- preferences { return [PackagePreferences $ns] }
loaded { return [PackageLocales $ns] }
- present { return [expr {$locale in [PackageLocales $ns]} ]}
+ present {
+ if {[llength $args] != 1} {
+ return -code error "wrong # args: should be\
+ \"[lrange [info level 0] 0 1] locale\""
+ }
+ return [expr {[string tolower [lindex $args 0]]
+ in [PackageLocales $ns]} ]
+ }
isset { return [dict exists $PackageConfig loclist $ns] }
- set { # set a package locale or add a package locale
+ set - preferences {
+ # set a package locale or add a package locale
+ set fSet [expr {$subcommand eq "set"}]
+
+ # Check parameter
+ if {$fSet && 1 < [llength $args] } {
+ return -code error "wrong # args: should be\
+ \"[lrange [info level 0] 0 1] ?locale?\""
+ }
+
+ # > Return preferences if no parameter
+ if {!$fSet && 0 == [llength $args] } {
+ return [PackagePreferences $ns]
+ }
# Copy the default locale if no package locale set so far
if {![dict exists $PackageConfig loclist $ns]} {
@@ -478,25 +558,43 @@ proc msgcat::mcpackagelocale {subcommand {locale ""}} {
dict set PackageConfig loadedlocales $ns $LoadedLocales
}
- # Check if changed
- set loclist [dict get $PackageConfig loclist $ns]
- if {! [info exists locale] || $locale eq [lindex $loclist 0] } {
- return [lindex $loclist 0]
+ # No argument for set: return current package locale
+ # The difference to no argument and subcommand "preferences" is,
+ # that "preferences" does not set the package locale property.
+ # This case is processed above, so no check for fSet here
+ if { 0 == [llength $args] } {
+ return [lindex [dict get $PackageConfig loclist $ns] 0]
+ }
+
+ # Get new loclist
+ if {$fSet} {
+ set loclist [mcutil getpreferences [lindex $args 0]]
+ } else {
+ set loclist $args
+ }
+
+ # Check if not changed to return imediately
+ if { [ListEqualString $loclist\
+ [dict get $PackageConfig loclist $ns]] } {
+ if {$fSet} {
+ return [lindex $loclist 0]
+ }
+ return $loclist
}
# Change loclist
- set loclist [GetPreferences $locale]
- set locale [lindex $loclist 0]
dict set PackageConfig loclist $ns $loclist
# load eventual missing locales
set loadedLocales [dict get $PackageConfig loadedlocales $ns]
- if {$locale in $loadedLocales} { return $locale }
set loadLocales [ListComplement $loadedLocales $loclist]
dict set PackageConfig loadedlocales $ns\
[concat $loadedLocales $loadLocales]
Load $ns $loadLocales
- return $locale
+ if {$fSet} {
+ return [lindex $loclist 0]
+ }
+ return $loclist
}
clear { # Remove all locales not contained in Loclist
if {![dict exists $PackageConfig loclist $ns]} {
@@ -551,7 +649,7 @@ proc msgcat::mcforgetpackage {} {
# todo: this may be implemented using an ensemble
variable PackageConfig
variable Msgs
- set ns [uplevel 1 {::namespace current}]
+ set ns [PackageNamespaceGet]
# Remove MC items
dict unset Msgs $ns
# Remove config items
@@ -561,6 +659,15 @@ proc msgcat::mcforgetpackage {} {
return
}
+# msgcat::mcgetmynamespace --
+#
+# Return the package namespace of the caller
+# This consideres to be called from a class or object.
+
+proc msgcat::mcpackagenamespaceget {} {
+ return [PackageNamespaceGet]
+}
+
# msgcat::mcpackageconfig --
#
# Get or modify the per caller namespace (e.g. packages) config options.
@@ -616,7 +723,7 @@ proc msgcat::mcforgetpackage {} {
proc msgcat::mcpackageconfig {subcommand option {value ""}} {
variable PackageConfig
# get namespace
- set ns [uplevel 1 {::namespace current}]
+ set ns [PackageNamespaceGet]
if {$option ni {"mcfolder" "loadcmd" "changecmd" "unknowncmd"}} {
return -code error "bad option \"$option\": must be mcfolder, loadcmd,\
@@ -756,8 +863,7 @@ proc msgcat::ListComplement {list1 list2 {inlistname ""}} {
# Returns the number of message catalogs that were loaded.
proc msgcat::mcload {langdir} {
- return [uplevel 1 [list\
- [namespace origin mcpackageconfig] set mcfolder $langdir]]
+ tailcall mcpackageconfig set mcfolder $langdir
}
# msgcat::LoadAll --
@@ -923,7 +1029,7 @@ proc msgcat::mcset {locale src {dest ""}} {
set dest $src
}
- set ns [uplevel 1 [list ::namespace current]]
+ set ns [PackageNamespaceGet]
set locale [string tolower $locale]
@@ -951,7 +1057,7 @@ proc msgcat::mcflset {src {dest ""}} {
return -code error "must only be used inside a message catalog loaded\
with ::msgcat::mcload"
}
- return [uplevel 1 [list [namespace origin mcset] $FileLocale $src $dest]]
+ tailcall mcset $FileLocale $src $dest
}
# msgcat::mcmset --
@@ -975,7 +1081,7 @@ proc msgcat::mcmset {locale pairs} {
}
set locale [string tolower $locale]
- set ns [uplevel 1 [list ::namespace current]]
+ set ns [PackageNamespaceGet]
foreach {src dest} $pairs {
dict set Msgs $ns $locale $src $dest
@@ -1002,7 +1108,7 @@ proc msgcat::mcflmset {pairs} {
return -code error "must only be used inside a message catalog loaded\
with ::msgcat::mcload"
}
- return [uplevel 1 [list [namespace origin mcmset] $FileLocale $pairs]]
+ tailcall mcmset $FileLocale $pairs
}
# msgcat::mcunknown --
@@ -1024,7 +1130,7 @@ proc msgcat::mcflmset {pairs} {
# Returns the translated value.
proc msgcat::mcunknown {args} {
- return [uplevel 1 [list [namespace origin DefaultUnknown] {*}$args]]
+ tailcall DefaultUnknown {*}$args
}
# msgcat::DefaultUnknown --
@@ -1067,8 +1173,9 @@ proc msgcat::DefaultUnknown {locale src args} {
proc msgcat::mcmax {args} {
set max 0
+ set ns [PackageNamespaceGet]
foreach string $args {
- set translated [uplevel 1 [list [namespace origin mc] $string]]
+ set translated [uplevel 1 [list [namespace origin mcn] $ns $string]]
set len [string length $translated]
if {$len>$max} {
set max $len
@@ -1079,7 +1186,7 @@ proc msgcat::mcmax {args} {
# Convert the locale values stored in environment variables to a form
# suitable for passing to [mclocale]
-proc msgcat::ConvertLocale {value} {
+proc msgcat::mcutil::ConvertLocale {value} {
# Assume $value is of form: $language[_$territory][.$codeset][@modifier]
# Convert to form: $language[_$territory][_$modifier]
#
@@ -1106,8 +1213,45 @@ proc msgcat::ConvertLocale {value} {
return $ret
}
+# helper function to find package namespace of stack-frame -2
+# There are 4 possibilities:
+# - called from a proc
+# - called within a class definition script
+# - called from an class defined oo object
+# - called from a classless oo object
+proc ::msgcat::PackageNamespaceGet {} {
+ set ns [uplevel 2 { namespace current }]
+
+ if {![string match {::oo::*} $ns]} {
+ # Not in object environment
+ return $ns
+ }
+
+ # Check self namespace to determine environment
+ switch -exact -- [uplevel 2 { namespace which -command self }] {
+ {::oo::define::self} {
+ # We are within a class definition
+ return [namespace qualifiers [uplevel 2 { self }]]
+ }
+ {::oo::Helpers::self} {
+ # We are within an object
+ set Class [info object class [uplevel 2 { self }]]
+ # Check for classless defined object
+ if {$Class eq {::oo::object}} {
+ return [namespace qualifiers [uplevel 2 { self }]]
+ }
+ # Class defined object
+ return [namespace qualifiers $Class]
+ }
+ default {
+ # Not in object environment
+ return $ns
+ }
+ }
+}
+
# Initialize the default locale
-proc msgcat::Init {} {
+proc msgcat::mcutil::getsystemlocale {} {
global env
#
@@ -1115,10 +1259,8 @@ proc msgcat::Init {} {
#
foreach varName {LC_ALL LC_MESSAGES LANG} {
if {[info exists env($varName)] && ("" ne $env($varName))} {
- if {![catch {
- mclocale [ConvertLocale $env($varName)]
- }]} {
- return
+ if {![catch { ConvertLocale $env($varName) } locale]} {
+ return $locale
}
}
}
@@ -1126,10 +1268,8 @@ proc msgcat::Init {} {
# On Darwin, fallback to current CFLocale identifier if available.
#
if {[info exists ::tcl::mac::locale] && $::tcl::mac::locale ne ""} {
- if {![catch {
- mclocale [ConvertLocale $::tcl::mac::locale]
- }]} {
- return
+ if {![catch { ConvertLocale $::tcl::mac::locale } locale]} {
+ return $locale
}
}
#
@@ -1138,8 +1278,7 @@ proc msgcat::Init {} {
#
if {([info sharedlibextension] ne ".dll")
|| [catch {package require registry}]} {
- mclocale C
- return
+ return C
}
#
# On Windows or Cygwin, try to set locale depending on registry
@@ -1148,7 +1287,7 @@ proc msgcat::Init {} {
# On Vista and later:
# HCU/Control Panel/Desktop : PreferredUILanguages is for language packs,
- # HCU/Control Pannel/International : localName is the default locale.
+ # HCU/Control Panel/International : localName is the default locale.
#
# They contain the local string as RFC5646, composed of:
# [a-z]{2,3} : language
@@ -1170,8 +1309,8 @@ proc msgcat::Init {} {
if {[dict exists $modifierDict $script]} {
append locale @ [dict get $modifierDict $script]
}
- if {![catch {mclocale [ConvertLocale $locale]}]} {
- return
+ if {![catch {ConvertLocale $locale} locale]} {
+ return $locale
}
}
}
@@ -1180,13 +1319,12 @@ proc msgcat::Init {} {
if {[catch {
set locale [registry get $key "locale"]
}]} {
- mclocale C
- return
+ return C
}
#
# Keep trying to match against smaller and smaller suffixes
- # of the registry value, since the latter hexadigits appear
- # to determine general language and earlier hexadigits determine
+ # of the registry value, since the latter hexdigits appear
+ # to determine general language and earlier hexdigits determine
# more precise information, such as territory. For example,
# 0409 - English - United States
# 0809 - English - United Kingdom
@@ -1196,15 +1334,15 @@ proc msgcat::Init {} {
set locale [string tolower $locale]
while {[string length $locale]} {
if {![catch {
- mclocale [ConvertLocale [dict get $WinRegToISO639 $locale]]
- }]} {
- return
+ ConvertLocale [dict get $WinRegToISO639 $locale]
+ } localeOut]} {
+ return $localeOut
}
set locale [string range $locale 1 end]
}
#
# No translation known. Fall back on "C" locale
#
- mclocale C
+ return C
}
-msgcat::Init
+msgcat::mclocale [msgcat::mcutil getsystemlocale]
diff --git a/library/msgcat/pkgIndex.tcl b/library/msgcat/pkgIndex.tcl
index 5a55190..e141c67 100644
--- a/library/msgcat/pkgIndex.tcl
+++ b/library/msgcat/pkgIndex.tcl
@@ -1,2 +1,2 @@
-if {![package vsatisfies [package provide Tcl] 8.5-]} {return}
-package ifneeded msgcat 1.6.1 [list source -encoding utf-8 [file join $dir msgcat.tcl]]
+if {![package vsatisfies [package provide Tcl] 8.7-]} {return}
+package ifneeded msgcat 1.7.1 [list source -encoding utf-8 [file join $dir msgcat.tcl]]
diff --git a/library/msgs/ar.msg b/library/msgs/ar.msg
index 257157f..2d403ec 100644
--- a/library/msgs/ar.msg
+++ b/library/msgs/ar.msg
@@ -1,53 +1,53 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset ar DAYS_OF_WEEK_ABBREV [list \
- "\u062d"\
- "\u0646"\
- "\u062b"\
- "\u0631"\
- "\u062e"\
- "\u062c"\
- "\u0633"]
+ "ح"\
+ "ن"\
+ "ث"\
+ "ر"\
+ "خ"\
+ "ج"\
+ "س"]
::msgcat::mcset ar DAYS_OF_WEEK_FULL [list \
- "\u0627\u0644\u0623\u062d\u062f"\
- "\u0627\u0644\u0627\u062b\u0646\u064a\u0646"\
- "\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621"\
- "\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621"\
- "\u0627\u0644\u062e\u0645\u064a\u0633"\
- "\u0627\u0644\u062c\u0645\u0639\u0629"\
- "\u0627\u0644\u0633\u0628\u062a"]
+ "الأحد"\
+ "الاثنين"\
+ "الثلاثاء"\
+ "الأربعاء"\
+ "الخميس"\
+ "الجمعة"\
+ "السبت"]
::msgcat::mcset ar MONTHS_ABBREV [list \
- "\u064a\u0646\u0627"\
- "\u0641\u0628\u0631"\
- "\u0645\u0627\u0631"\
- "\u0623\u0628\u0631"\
- "\u0645\u0627\u064a"\
- "\u064a\u0648\u0646"\
- "\u064a\u0648\u0644"\
- "\u0623\u063a\u0633"\
- "\u0633\u0628\u062a"\
- "\u0623\u0643\u062a"\
- "\u0646\u0648\u0641"\
- "\u062f\u064a\u0633"\
+ "ينا"\
+ "فبر"\
+ "مار"\
+ "أبر"\
+ "ماي"\
+ "يون"\
+ "يول"\
+ "أغس"\
+ "سبت"\
+ "أكت"\
+ "نوف"\
+ "ديس"\
""]
::msgcat::mcset ar MONTHS_FULL [list \
- "\u064a\u0646\u0627\u064a\u0631"\
- "\u0641\u0628\u0631\u0627\u064a\u0631"\
- "\u0645\u0627\u0631\u0633"\
- "\u0623\u0628\u0631\u064a\u0644"\
- "\u0645\u0627\u064a\u0648"\
- "\u064a\u0648\u0646\u064a\u0648"\
- "\u064a\u0648\u0644\u064a\u0648"\
- "\u0623\u063a\u0633\u0637\u0633"\
- "\u0633\u0628\u062a\u0645\u0628\u0631"\
- "\u0623\u0643\u062a\u0648\u0628\u0631"\
- "\u0646\u0648\u0641\u0645\u0628\u0631"\
- "\u062f\u064a\u0633\u0645\u0628\u0631"\
+ "يناير"\
+ "فبراير"\
+ "مارس"\
+ "أبريل"\
+ "مايو"\
+ "يونيو"\
+ "يوليو"\
+ "أغسطس"\
+ "سبتمبر"\
+ "أكتوبر"\
+ "نوفمبر"\
+ "ديسمبر"\
""]
- ::msgcat::mcset ar BCE "\u0642.\u0645"
- ::msgcat::mcset ar CE "\u0645"
- ::msgcat::mcset ar AM "\u0635"
- ::msgcat::mcset ar PM "\u0645"
+ ::msgcat::mcset ar BCE "ق.م"
+ ::msgcat::mcset ar CE "م"
+ ::msgcat::mcset ar AM "ص"
+ ::msgcat::mcset ar PM "م"
::msgcat::mcset ar DATE_FORMAT "%d/%m/%Y"
::msgcat::mcset ar TIME_FORMAT_12 "%I:%M:%S %P"
::msgcat::mcset ar DATE_TIME_FORMAT "%d/%m/%Y %I:%M:%S %P %z"
diff --git a/library/msgs/ar_jo.msg b/library/msgs/ar_jo.msg
index 0f5e269..9a9dda0 100644
--- a/library/msgs/ar_jo.msg
+++ b/library/msgs/ar_jo.msg
@@ -1,39 +1,39 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset ar_JO DAYS_OF_WEEK_ABBREV [list \
- "\u0627\u0644\u0623\u062d\u062f"\
- "\u0627\u0644\u0627\u062b\u0646\u064a\u0646"\
- "\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621"\
- "\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621"\
- "\u0627\u0644\u062e\u0645\u064a\u0633"\
- "\u0627\u0644\u062c\u0645\u0639\u0629"\
- "\u0627\u0644\u0633\u0628\u062a"]
+ "الأحد"\
+ "الاثنين"\
+ "الثلاثاء"\
+ "الأربعاء"\
+ "الخميس"\
+ "الجمعة"\
+ "السبت"]
::msgcat::mcset ar_JO MONTHS_ABBREV [list \
- "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062b\u0627\u0646\u064a"\
- "\u0634\u0628\u0627\u0637"\
- "\u0622\u0630\u0627\u0631"\
- "\u0646\u064a\u0633\u0627\u0646"\
- "\u0646\u0648\u0627\u0631"\
- "\u062d\u0632\u064a\u0631\u0627\u0646"\
- "\u062a\u0645\u0648\u0632"\
- "\u0622\u0628"\
- "\u0623\u064a\u0644\u0648\u0644"\
- "\u062a\u0634\u0631\u064a\u0646 \u0627\u0644\u0623\u0648\u0644"\
- "\u062a\u0634\u0631\u064a\u0646 \u0627\u0644\u062b\u0627\u0646\u064a"\
- "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u0623\u0648\u0644"\
+ "كانون الثاني"\
+ "شباط"\
+ "آذار"\
+ "نيسان"\
+ "نوار"\
+ "حزيران"\
+ "تموز"\
+ "آب"\
+ "أيلول"\
+ "تشرين الأول"\
+ "تشرين الثاني"\
+ "كانون الأول"\
""]
::msgcat::mcset ar_JO MONTHS_FULL [list \
- "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062b\u0627\u0646\u064a"\
- "\u0634\u0628\u0627\u0637"\
- "\u0622\u0630\u0627\u0631"\
- "\u0646\u064a\u0633\u0627\u0646"\
- "\u0646\u0648\u0627\u0631"\
- "\u062d\u0632\u064a\u0631\u0627\u0646"\
- "\u062a\u0645\u0648\u0632"\
- "\u0622\u0628"\
- "\u0623\u064a\u0644\u0648\u0644"\
- "\u062a\u0634\u0631\u064a\u0646 \u0627\u0644\u0623\u0648\u0644"\
- "\u062a\u0634\u0631\u064a\u0646 \u0627\u0644\u062b\u0627\u0646\u064a"\
- "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u0623\u0648\u0644"\
+ "كانون الثاني"\
+ "شباط"\
+ "آذار"\
+ "نيسان"\
+ "نوار"\
+ "حزيران"\
+ "تموز"\
+ "آب"\
+ "أيلول"\
+ "تشرين الأول"\
+ "تشرين الثاني"\
+ "كانون الأول"\
""]
}
diff --git a/library/msgs/ar_lb.msg b/library/msgs/ar_lb.msg
index e62acd3..c23aa2c 100644
--- a/library/msgs/ar_lb.msg
+++ b/library/msgs/ar_lb.msg
@@ -1,39 +1,39 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset ar_LB DAYS_OF_WEEK_ABBREV [list \
- "\u0627\u0644\u0623\u062d\u062f"\
- "\u0627\u0644\u0627\u062b\u0646\u064a\u0646"\
- "\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621"\
- "\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621"\
- "\u0627\u0644\u062e\u0645\u064a\u0633"\
- "\u0627\u0644\u062c\u0645\u0639\u0629"\
- "\u0627\u0644\u0633\u0628\u062a"]
+ "الأحد"\
+ "الاثنين"\
+ "الثلاثاء"\
+ "الأربعاء"\
+ "الخميس"\
+ "الجمعة"\
+ "السبت"]
::msgcat::mcset ar_LB MONTHS_ABBREV [list \
- "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062b\u0627\u0646\u064a"\
- "\u0634\u0628\u0627\u0637"\
- "\u0622\u0630\u0627\u0631"\
- "\u0646\u064a\u0633\u0627\u0646"\
- "\u0646\u0648\u0627\u0631"\
- "\u062d\u0632\u064a\u0631\u0627\u0646"\
- "\u062a\u0645\u0648\u0632"\
- "\u0622\u0628"\
- "\u0623\u064a\u0644\u0648\u0644"\
- "\u062a\u0634\u0631\u064a\u0646 \u0627\u0644\u0623\u0648\u0644"\
- "\u062a\u0634\u0631\u064a\u0646 \u0627\u0644\u062b\u0627\u0646\u064a"\
- "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u0623\u0648\u0644"\
+ "كانون الثاني"\
+ "شباط"\
+ "آذار"\
+ "نيسان"\
+ "نوار"\
+ "حزيران"\
+ "تموز"\
+ "آب"\
+ "أيلول"\
+ "تشرين الأول"\
+ "تشرين الثاني"\
+ "كانون الأول"\
""]
::msgcat::mcset ar_LB MONTHS_FULL [list \
- "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062b\u0627\u0646\u064a"\
- "\u0634\u0628\u0627\u0637"\
- "\u0622\u0630\u0627\u0631"\
- "\u0646\u064a\u0633\u0627\u0646"\
- "\u0646\u0648\u0627\u0631"\
- "\u062d\u0632\u064a\u0631\u0627\u0646"\
- "\u062a\u0645\u0648\u0632"\
- "\u0622\u0628"\
- "\u0623\u064a\u0644\u0648\u0644"\
- "\u062a\u0634\u0631\u064a\u0646 \u0627\u0644\u0623\u0648\u0644"\
- "\u062a\u0634\u0631\u064a\u0646 \u0627\u0644\u062b\u0627\u0646\u064a"\
- "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u0623\u0648\u0644"\
+ "كانون الثاني"\
+ "شباط"\
+ "آذار"\
+ "نيسان"\
+ "نوار"\
+ "حزيران"\
+ "تموز"\
+ "آب"\
+ "أيلول"\
+ "تشرين الأول"\
+ "تشرين الثاني"\
+ "كانون الأول"\
""]
}
diff --git a/library/msgs/ar_sy.msg b/library/msgs/ar_sy.msg
index d5e1c87..f0daec0 100644
--- a/library/msgs/ar_sy.msg
+++ b/library/msgs/ar_sy.msg
@@ -1,39 +1,39 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset ar_SY DAYS_OF_WEEK_ABBREV [list \
- "\u0627\u0644\u0623\u062d\u062f"\
- "\u0627\u0644\u0627\u062b\u0646\u064a\u0646"\
- "\u0627\u0644\u062b\u0644\u0627\u062b\u0627\u0621"\
- "\u0627\u0644\u0623\u0631\u0628\u0639\u0627\u0621"\
- "\u0627\u0644\u062e\u0645\u064a\u0633"\
- "\u0627\u0644\u062c\u0645\u0639\u0629"\
- "\u0627\u0644\u0633\u0628\u062a"]
+ "الأحد"\
+ "الاثنين"\
+ "الثلاثاء"\
+ "الأربعاء"\
+ "الخميس"\
+ "الجمعة"\
+ "السبت"]
::msgcat::mcset ar_SY MONTHS_ABBREV [list \
- "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062b\u0627\u0646\u064a"\
- "\u0634\u0628\u0627\u0637"\
- "\u0622\u0630\u0627\u0631"\
- "\u0646\u064a\u0633\u0627\u0646"\
- "\u0646\u0648\u0627\u0631"\
- "\u062d\u0632\u064a\u0631\u0627\u0646"\
- "\u062a\u0645\u0648\u0632"\
- "\u0622\u0628"\
- "\u0623\u064a\u0644\u0648\u0644"\
- "\u062a\u0634\u0631\u064a\u0646 \u0627\u0644\u0623\u0648\u0644"\
- "\u062a\u0634\u0631\u064a\u0646 \u0627\u0644\u062b\u0627\u0646\u064a"\
- "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u0623\u0648\u0644"\
+ "كانون الثاني"\
+ "شباط"\
+ "آذار"\
+ "نيسان"\
+ "نوار"\
+ "حزيران"\
+ "تموز"\
+ "آب"\
+ "أيلول"\
+ "تشرين الأول"\
+ "تشرين الثاني"\
+ "كانون الأول"\
""]
::msgcat::mcset ar_SY MONTHS_FULL [list \
- "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u062b\u0627\u0646\u064a"\
- "\u0634\u0628\u0627\u0637"\
- "\u0622\u0630\u0627\u0631"\
- "\u0646\u064a\u0633\u0627\u0646"\
- "\u0646\u0648\u0627\u0631\u0627\u0646"\
- "\u062d\u0632\u064a\u0631"\
- "\u062a\u0645\u0648\u0632"\
- "\u0622\u0628"\
- "\u0623\u064a\u0644\u0648\u0644"\
- "\u062a\u0634\u0631\u064a\u0646 \u0627\u0644\u0623\u0648\u0644"\
- "\u062a\u0634\u0631\u064a\u0646 \u0627\u0644\u062b\u0627\u0646\u064a"\
- "\u0643\u0627\u0646\u0648\u0646 \u0627\u0644\u0623\u0648\u0644"\
+ "كانون الثاني"\
+ "شباط"\
+ "آذار"\
+ "نيسان"\
+ "نواران"\
+ "حزير"\
+ "تموز"\
+ "آب"\
+ "أيلول"\
+ "تشرين الأول"\
+ "تشرين الثاني"\
+ "كانون الأول"\
""]
}
diff --git a/library/msgs/be.msg b/library/msgs/be.msg
index 379a1d7..a0aceed 100644
--- a/library/msgs/be.msg
+++ b/library/msgs/be.msg
@@ -1,51 +1,51 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset be DAYS_OF_WEEK_ABBREV [list \
- "\u043d\u0434"\
- "\u043f\u043d"\
- "\u0430\u0442"\
- "\u0441\u0440"\
- "\u0447\u0446"\
- "\u043f\u0442"\
- "\u0441\u0431"]
+ "нд"\
+ "пн"\
+ "ат"\
+ "ср"\
+ "чц"\
+ "пт"\
+ "сб"]
::msgcat::mcset be DAYS_OF_WEEK_FULL [list \
- "\u043d\u044f\u0434\u0437\u0435\u043b\u044f"\
- "\u043f\u0430\u043d\u044f\u0434\u0437\u0435\u043b\u0430\u043a"\
- "\u0430\u045e\u0442\u043e\u0440\u0430\u043a"\
- "\u0441\u0435\u0440\u0430\u0434\u0430"\
- "\u0447\u0430\u0446\u0432\u0435\u0440"\
- "\u043f\u044f\u0442\u043d\u0456\u0446\u0430"\
- "\u0441\u0443\u0431\u043e\u0442\u0430"]
+ "нядзеля"\
+ "панядзелак"\
+ "аўторак"\
+ "серада"\
+ "чацвер"\
+ "пятніца"\
+ "субота"]
::msgcat::mcset be MONTHS_ABBREV [list \
- "\u0441\u0442\u0434"\
- "\u043b\u044e\u0442"\
- "\u0441\u043a\u0432"\
- "\u043a\u0440\u0441"\
- "\u043c\u0430\u0439"\
- "\u0447\u0440\u0432"\
- "\u043b\u043f\u043d"\
- "\u0436\u043d\u0432"\
- "\u0432\u0440\u0441"\
- "\u043a\u0441\u0442"\
- "\u043b\u0441\u0442"\
- "\u0441\u043d\u0436"\
+ "стд"\
+ "лют"\
+ "скв"\
+ "крс"\
+ "май"\
+ "чрв"\
+ "лпн"\
+ "жнв"\
+ "врс"\
+ "кст"\
+ "лст"\
+ "снж"\
""]
::msgcat::mcset be MONTHS_FULL [list \
- "\u0441\u0442\u0443\u0434\u0437\u0435\u043d\u044f"\
- "\u043b\u044e\u0442\u0430\u0433\u0430"\
- "\u0441\u0430\u043a\u0430\u0432\u0456\u043a\u0430"\
- "\u043a\u0440\u0430\u0441\u0430\u0432\u0456\u043a\u0430"\
- "\u043c\u0430\u044f"\
- "\u0447\u0440\u0432\u0435\u043d\u044f"\
- "\u043b\u0456\u043f\u0435\u043d\u044f"\
- "\u0436\u043d\u0456\u045e\u043d\u044f"\
- "\u0432\u0435\u0440\u0430\u0441\u043d\u044f"\
- "\u043a\u0430\u0441\u0442\u0440\u044b\u0447\u043d\u0456\u043a\u0430"\
- "\u043b\u0438\u0441\u0442\u0430\u043f\u0430\u0434\u0430"\
- "\u0441\u043d\u0435\u0436\u043d\u044f"\
+ "студзеня"\
+ "лютага"\
+ "сакавіка"\
+ "красавіка"\
+ "мая"\
+ "чрвеня"\
+ "ліпеня"\
+ "жніўня"\
+ "верасня"\
+ "кастрычніка"\
+ "листапада"\
+ "снежня"\
""]
- ::msgcat::mcset be BCE "\u0434\u0430 \u043d.\u0435."
- ::msgcat::mcset be CE "\u043d.\u0435."
+ ::msgcat::mcset be BCE "да н.е."
+ ::msgcat::mcset be CE "н.е."
::msgcat::mcset be DATE_FORMAT "%e.%m.%Y"
::msgcat::mcset be TIME_FORMAT "%k.%M.%S"
::msgcat::mcset be DATE_TIME_FORMAT "%e.%m.%Y %k.%M.%S %z"
diff --git a/library/msgs/bg.msg b/library/msgs/bg.msg
index ff17759..2e7730d 100644
--- a/library/msgs/bg.msg
+++ b/library/msgs/bg.msg
@@ -1,21 +1,21 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset bg DAYS_OF_WEEK_ABBREV [list \
- "\u041d\u0434"\
- "\u041f\u043d"\
- "\u0412\u0442"\
- "\u0421\u0440"\
- "\u0427\u0442"\
- "\u041f\u0442"\
- "\u0421\u0431"]
+ "Нд"\
+ "Пн"\
+ "Вт"\
+ "Ср"\
+ "Чт"\
+ "Пт"\
+ "Сб"]
::msgcat::mcset bg DAYS_OF_WEEK_FULL [list \
- "\u041d\u0435\u0434\u0435\u043b\u044f"\
- "\u041f\u043e\u043d\u0435\u0434\u0435\u043b\u043d\u0438\u043a"\
- "\u0412\u0442\u043e\u0440\u043d\u0438\u043a"\
- "\u0421\u0440\u044f\u0434\u0430"\
- "\u0427\u0435\u0442\u0432\u044a\u0440\u0442\u044a\u043a"\
- "\u041f\u0435\u0442\u044a\u043a"\
- "\u0421\u044a\u0431\u043e\u0442\u0430"]
+ "Неделя"\
+ "Понеделник"\
+ "Вторник"\
+ "Сряда"\
+ "Четвъртък"\
+ "Петък"\
+ "Събота"]
::msgcat::mcset bg MONTHS_ABBREV [list \
"I"\
"II"\
@@ -31,21 +31,21 @@ namespace eval ::tcl::clock {
"XII"\
""]
::msgcat::mcset bg MONTHS_FULL [list \
- "\u042f\u043d\u0443\u0430\u0440\u0438"\
- "\u0424\u0435\u0432\u0440\u0443\u0430\u0440\u0438"\
- "\u041c\u0430\u0440\u0442"\
- "\u0410\u043f\u0440\u0438\u043b"\
- "\u041c\u0430\u0439"\
- "\u042e\u043d\u0438"\
- "\u042e\u043b\u0438"\
- "\u0410\u0432\u0433\u0443\u0441\u0442"\
- "\u0421\u0435\u043f\u0442\u0435\u043c\u0432\u0440\u0438"\
- "\u041e\u043a\u0442\u043e\u043c\u0432\u0440\u0438"\
- "\u041d\u043e\u0435\u043c\u0432\u0440\u0438"\
- "\u0414\u0435\u043a\u0435\u043c\u0432\u0440\u0438"\
+ "Януари"\
+ "Февруари"\
+ "Март"\
+ "Април"\
+ "Май"\
+ "Юни"\
+ "Юли"\
+ "Август"\
+ "Септември"\
+ "Октомври"\
+ "Ноември"\
+ "Декември"\
""]
- ::msgcat::mcset bg BCE "\u043f\u0440.\u043d.\u0435."
- ::msgcat::mcset bg CE "\u043d.\u0435."
+ ::msgcat::mcset bg BCE "пр.н.е."
+ ::msgcat::mcset bg CE "н.е."
::msgcat::mcset bg DATE_FORMAT "%Y-%m-%e"
::msgcat::mcset bg TIME_FORMAT "%k:%M:%S"
::msgcat::mcset bg DATE_TIME_FORMAT "%Y-%m-%e %k:%M:%S %z"
diff --git a/library/msgs/bn.msg b/library/msgs/bn.msg
index 664b9d8..a0aef13 100644
--- a/library/msgs/bn.msg
+++ b/library/msgs/bn.msg
@@ -1,49 +1,49 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset bn DAYS_OF_WEEK_ABBREV [list \
- "\u09b0\u09ac\u09bf"\
- "\u09b8\u09cb\u09ae"\
- "\u09ae\u0999\u0997\u09b2"\
- "\u09ac\u09c1\u09a7"\
- "\u09ac\u09c3\u09b9\u09b8\u09cd\u09aa\u09a4\u09bf"\
- "\u09b6\u09c1\u0995\u09cd\u09b0"\
- "\u09b6\u09a8\u09bf"]
+ "রবি"\
+ "সোম"\
+ "মঙগল"\
+ "বুধ"\
+ "বৃহস্পতি"\
+ "শুক্র"\
+ "শনি"]
::msgcat::mcset bn DAYS_OF_WEEK_FULL [list \
- "\u09b0\u09ac\u09bf\u09ac\u09be\u09b0"\
- "\u09b8\u09cb\u09ae\u09ac\u09be\u09b0"\
- "\u09ae\u0999\u0997\u09b2\u09ac\u09be\u09b0"\
- "\u09ac\u09c1\u09a7\u09ac\u09be\u09b0"\
- "\u09ac\u09c3\u09b9\u09b8\u09cd\u09aa\u09a4\u09bf\u09ac\u09be\u09b0"\
- "\u09b6\u09c1\u0995\u09cd\u09b0\u09ac\u09be\u09b0"\
- "\u09b6\u09a8\u09bf\u09ac\u09be\u09b0"]
+ "রবিবার"\
+ "সোমবার"\
+ "মঙগলবার"\
+ "বুধবার"\
+ "বৃহস্পতিবার"\
+ "শুক্রবার"\
+ "শনিবার"]
::msgcat::mcset bn MONTHS_ABBREV [list \
- "\u099c\u09be\u09a8\u09c1\u09df\u09be\u09b0\u09c0"\
- "\u09ab\u09c7\u09ac\u09cd\u09b0\u09c1\u09df\u09be\u09b0\u09c0"\
- "\u09ae\u09be\u09b0\u09cd\u099a"\
- "\u098f\u09aa\u09cd\u09b0\u09bf\u09b2"\
- "\u09ae\u09c7"\
- "\u099c\u09c1\u09a8"\
- "\u099c\u09c1\u09b2\u09be\u0987"\
- "\u0986\u0997\u09b8\u09cd\u099f"\
- "\u09b8\u09c7\u09aa\u09cd\u099f\u09c7\u09ae\u09cd\u09ac\u09b0"\
- "\u0985\u0995\u09cd\u099f\u09cb\u09ac\u09b0"\
- "\u09a8\u09ad\u09c7\u09ae\u09cd\u09ac\u09b0"\
- "\u09a1\u09bf\u09b8\u09c7\u09ae\u09cd\u09ac\u09b0"\
+ "জানুয়ারী"\
+ "ফেব্রুয়ারী"\
+ "মার্চ"\
+ "এপ্রিল"\
+ "মে"\
+ "জুন"\
+ "জুলাই"\
+ "আগস্ট"\
+ "সেপ্টেম্বর"\
+ "অক্টোবর"\
+ "নভেম্বর"\
+ "ডিসেম্বর"\
""]
::msgcat::mcset bn MONTHS_FULL [list \
- "\u099c\u09be\u09a8\u09c1\u09df\u09be\u09b0\u09c0"\
- "\u09ab\u09c7\u09ac\u09cd\u09b0\u09c1\u09df\u09be\u09b0\u09c0"\
- "\u09ae\u09be\u09b0\u09cd\u099a"\
- "\u098f\u09aa\u09cd\u09b0\u09bf\u09b2"\
- "\u09ae\u09c7"\
- "\u099c\u09c1\u09a8"\
- "\u099c\u09c1\u09b2\u09be\u0987"\
- "\u0986\u0997\u09b8\u09cd\u099f"\
- "\u09b8\u09c7\u09aa\u09cd\u099f\u09c7\u09ae\u09cd\u09ac\u09b0"\
- "\u0985\u0995\u09cd\u099f\u09cb\u09ac\u09b0"\
- "\u09a8\u09ad\u09c7\u09ae\u09cd\u09ac\u09b0"\
- "\u09a1\u09bf\u09b8\u09c7\u09ae\u09cd\u09ac\u09b0"\
+ "জানুয়ারী"\
+ "ফেব্রুয়ারী"\
+ "মার্চ"\
+ "এপ্রিল"\
+ "মে"\
+ "জুন"\
+ "জুলাই"\
+ "আগস্ট"\
+ "সেপ্টেম্বর"\
+ "অক্টোবর"\
+ "নভেম্বর"\
+ "ডিসেম্বর"\
""]
- ::msgcat::mcset bn AM "\u09aa\u09c2\u09b0\u09cd\u09ac\u09be\u09b9\u09cd\u09a3"
- ::msgcat::mcset bn PM "\u0985\u09aa\u09b0\u09be\u09b9\u09cd\u09a3"
+ ::msgcat::mcset bn AM "পূর্বাহ্ণ"
+ ::msgcat::mcset bn PM "অপরাহ্ণ"
}
diff --git a/library/msgs/ca.msg b/library/msgs/ca.msg
index 36c9772..272f682 100644
--- a/library/msgs/ca.msg
+++ b/library/msgs/ca.msg
@@ -19,7 +19,7 @@ namespace eval ::tcl::clock {
::msgcat::mcset ca MONTHS_ABBREV [list \
"gen."\
"feb."\
- "mar\u00e7"\
+ "març"\
"abr."\
"maig"\
"juny"\
@@ -33,7 +33,7 @@ namespace eval ::tcl::clock {
::msgcat::mcset ca MONTHS_FULL [list \
"gener"\
"febrer"\
- "mar\u00e7"\
+ "març"\
"abril"\
"maig"\
"juny"\
diff --git a/library/msgs/cs.msg b/library/msgs/cs.msg
index 8db8bdd..4673cd4 100644
--- a/library/msgs/cs.msg
+++ b/library/msgs/cs.msg
@@ -3,18 +3,18 @@ namespace eval ::tcl::clock {
::msgcat::mcset cs DAYS_OF_WEEK_ABBREV [list \
"Ne"\
"Po"\
- "\u00dat"\
+ "Út"\
"St"\
- "\u010ct"\
- "P\u00e1"\
+ "Čt"\
+ "Pá"\
"So"]
::msgcat::mcset cs DAYS_OF_WEEK_FULL [list \
- "Ned\u011ble"\
- "Pond\u011bl\u00ed"\
- "\u00dater\u00fd"\
- "St\u0159eda"\
- "\u010ctvrtek"\
- "P\u00e1tek"\
+ "Neděle"\
+ "Pondělí"\
+ "Úterý"\
+ "Středa"\
+ "Čtvrtek"\
+ "Pátek"\
"Sobota"]
::msgcat::mcset cs MONTHS_ABBREV [list \
"I"\
@@ -32,19 +32,19 @@ namespace eval ::tcl::clock {
""]
::msgcat::mcset cs MONTHS_FULL [list \
"leden"\
- "\u00fanor"\
- "b\u0159ezen"\
+ "únor"\
+ "březen"\
"duben"\
- "kv\u011bten"\
- "\u010derven"\
- "\u010dervenec"\
+ "květen"\
+ "červen"\
+ "červenec"\
"srpen"\
- "z\u00e1\u0159\u00ed"\
- "\u0159\u00edjen"\
+ "září"\
+ "říjen"\
"listopad"\
"prosinec"\
""]
- ::msgcat::mcset cs BCE "p\u0159.Kr."
+ ::msgcat::mcset cs BCE "př.Kr."
::msgcat::mcset cs CE "po Kr."
::msgcat::mcset cs AM "dop."
::msgcat::mcset cs PM "odp."
diff --git a/library/msgs/da.msg b/library/msgs/da.msg
index e4fec7f..abed3c5 100644
--- a/library/msgs/da.msg
+++ b/library/msgs/da.msg
@@ -1,21 +1,21 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset da DAYS_OF_WEEK_ABBREV [list \
- "s\u00f8"\
+ "sø"\
"ma"\
"ti"\
"on"\
"to"\
"fr"\
- "l\u00f8"]
+ "lø"]
::msgcat::mcset da DAYS_OF_WEEK_FULL [list \
- "s\u00f8ndag"\
+ "søndag"\
"mandag"\
"tirsdag"\
"onsdag"\
"torsdag"\
"fredag"\
- "l\u00f8rdag"]
+ "lørdag"]
::msgcat::mcset da MONTHS_ABBREV [list \
"jan"\
"feb"\
diff --git a/library/msgs/de.msg b/library/msgs/de.msg
index 9eb3145..0bb7399 100644
--- a/library/msgs/de.msg
+++ b/library/msgs/de.msg
@@ -33,7 +33,7 @@ namespace eval ::tcl::clock {
::msgcat::mcset de MONTHS_FULL [list \
"Januar"\
"Februar"\
- "M\u00e4rz"\
+ "März"\
"April"\
"Mai"\
"Juni"\
diff --git a/library/msgs/de_at.msg b/library/msgs/de_at.msg
index 61bc266..1a0a0f5 100644
--- a/library/msgs/de_at.msg
+++ b/library/msgs/de_at.msg
@@ -1,9 +1,9 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset de_AT MONTHS_ABBREV [list \
- "J\u00e4n"\
+ "Jän"\
"Feb"\
- "M\u00e4r"\
+ "Mär"\
"Apr"\
"Mai"\
"Jun"\
@@ -15,9 +15,9 @@ namespace eval ::tcl::clock {
"Dez"\
""]
::msgcat::mcset de_AT MONTHS_FULL [list \
- "J\u00e4nner"\
+ "Jänner"\
"Februar"\
- "M\u00e4rz"\
+ "März"\
"April"\
"Mai"\
"Juni"\
diff --git a/library/msgs/de_be.msg b/library/msgs/de_be.msg
index 3614763..04cf88c 100644
--- a/library/msgs/de_be.msg
+++ b/library/msgs/de_be.msg
@@ -19,7 +19,7 @@ namespace eval ::tcl::clock {
::msgcat::mcset de_BE MONTHS_ABBREV [list \
"Jan"\
"Feb"\
- "M\u00e4r"\
+ "Mär"\
"Apr"\
"Mai"\
"Jun"\
@@ -33,7 +33,7 @@ namespace eval ::tcl::clock {
::msgcat::mcset de_BE MONTHS_FULL [list \
"Januar"\
"Februar"\
- "M\u00e4rz"\
+ "März"\
"April"\
"Mai"\
"Juni"\
diff --git a/library/msgs/el.msg b/library/msgs/el.msg
index ac19f62..26bdfe9 100644
--- a/library/msgs/el.msg
+++ b/library/msgs/el.msg
@@ -1,51 +1,51 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset el DAYS_OF_WEEK_ABBREV [list \
- "\u039a\u03c5\u03c1"\
- "\u0394\u03b5\u03c5"\
- "\u03a4\u03c1\u03b9"\
- "\u03a4\u03b5\u03c4"\
- "\u03a0\u03b5\u03bc"\
- "\u03a0\u03b1\u03c1"\
- "\u03a3\u03b1\u03b2"]
+ "Κυρ"\
+ "Δευ"\
+ "Τρι"\
+ "Τετ"\
+ "Πεμ"\
+ "Παρ"\
+ "Σαβ"]
::msgcat::mcset el DAYS_OF_WEEK_FULL [list \
- "\u039a\u03c5\u03c1\u03b9\u03b1\u03ba\u03ae"\
- "\u0394\u03b5\u03c5\u03c4\u03ad\u03c1\u03b1"\
- "\u03a4\u03c1\u03af\u03c4\u03b7"\
- "\u03a4\u03b5\u03c4\u03ac\u03c1\u03c4\u03b7"\
- "\u03a0\u03ad\u03bc\u03c0\u03c4\u03b7"\
- "\u03a0\u03b1\u03c1\u03b1\u03c3\u03ba\u03b5\u03c5\u03ae"\
- "\u03a3\u03ac\u03b2\u03b2\u03b1\u03c4\u03bf"]
+ "Κυριακή"\
+ "Δευτέρα"\
+ "Τρίτη"\
+ "Τετάρτη"\
+ "Πέμπτη"\
+ "Παρασκευή"\
+ "Σάββατο"]
::msgcat::mcset el MONTHS_ABBREV [list \
- "\u0399\u03b1\u03bd"\
- "\u03a6\u03b5\u03b2"\
- "\u039c\u03b1\u03c1"\
- "\u0391\u03c0\u03c1"\
- "\u039c\u03b1\u03ca"\
- "\u0399\u03bf\u03c5\u03bd"\
- "\u0399\u03bf\u03c5\u03bb"\
- "\u0391\u03c5\u03b3"\
- "\u03a3\u03b5\u03c0"\
- "\u039f\u03ba\u03c4"\
- "\u039d\u03bf\u03b5"\
- "\u0394\u03b5\u03ba"\
+ "Ιαν"\
+ "Φεβ"\
+ "Μαρ"\
+ "Απρ"\
+ "Μαϊ"\
+ "Ιουν"\
+ "Ιουλ"\
+ "Αυγ"\
+ "Σεπ"\
+ "Οκτ"\
+ "Νοε"\
+ "Δεκ"\
""]
::msgcat::mcset el MONTHS_FULL [list \
- "\u0399\u03b1\u03bd\u03bf\u03c5\u03ac\u03c1\u03b9\u03bf\u03c2"\
- "\u03a6\u03b5\u03b2\u03c1\u03bf\u03c5\u03ac\u03c1\u03b9\u03bf\u03c2"\
- "\u039c\u03ac\u03c1\u03c4\u03b9\u03bf\u03c2"\
- "\u0391\u03c0\u03c1\u03af\u03bb\u03b9\u03bf\u03c2"\
- "\u039c\u03ac\u03ca\u03bf\u03c2"\
- "\u0399\u03bf\u03cd\u03bd\u03b9\u03bf\u03c2"\
- "\u0399\u03bf\u03cd\u03bb\u03b9\u03bf\u03c2"\
- "\u0391\u03cd\u03b3\u03bf\u03c5\u03c3\u03c4\u03bf\u03c2"\
- "\u03a3\u03b5\u03c0\u03c4\u03ad\u03bc\u03b2\u03c1\u03b9\u03bf\u03c2"\
- "\u039f\u03ba\u03c4\u03ce\u03b2\u03c1\u03b9\u03bf\u03c2"\
- "\u039d\u03bf\u03ad\u03bc\u03b2\u03c1\u03b9\u03bf\u03c2"\
- "\u0394\u03b5\u03ba\u03ad\u03bc\u03b2\u03c1\u03b9\u03bf\u03c2"\
+ "Ιανουάριος"\
+ "Φεβρουάριος"\
+ "Μάρτιος"\
+ "Απρίλιος"\
+ "Μάϊος"\
+ "Ιούνιος"\
+ "Ιούλιος"\
+ "Αύγουστος"\
+ "Σεπτέμβριος"\
+ "Οκτώβριος"\
+ "Νοέμβριος"\
+ "Δεκέμβριος"\
""]
- ::msgcat::mcset el AM "\u03c0\u03bc"
- ::msgcat::mcset el PM "\u03bc\u03bc"
+ ::msgcat::mcset el AM "πμ"
+ ::msgcat::mcset el PM "μμ"
::msgcat::mcset el DATE_FORMAT "%e/%m/%Y"
::msgcat::mcset el TIME_FORMAT_12 "%l:%M:%S %P"
::msgcat::mcset el DATE_TIME_FORMAT "%e/%m/%Y %l:%M:%S %P %z"
diff --git a/library/msgs/eo.msg b/library/msgs/eo.msg
index 1d2a24f..b9b1500 100644
--- a/library/msgs/eo.msg
+++ b/library/msgs/eo.msg
@@ -5,15 +5,15 @@ namespace eval ::tcl::clock {
"lu"\
"ma"\
"me"\
- "\u0135a"\
+ "ĵa"\
"ve"\
"sa"]
::msgcat::mcset eo DAYS_OF_WEEK_FULL [list \
- "diman\u0109o"\
+ "dimanĉo"\
"lundo"\
"mardo"\
"merkredo"\
- "\u0135a\u016ddo"\
+ "ĵaŭdo"\
"vendredo"\
"sabato"]
::msgcat::mcset eo MONTHS_ABBREV [list \
@@ -24,7 +24,7 @@ namespace eval ::tcl::clock {
"maj"\
"jun"\
"jul"\
- "a\u016dg"\
+ "aŭg"\
"sep"\
"okt"\
"nov"\
@@ -38,7 +38,7 @@ namespace eval ::tcl::clock {
"majo"\
"junio"\
"julio"\
- "a\u016dgusto"\
+ "aŭgusto"\
"septembro"\
"oktobro"\
"novembro"\
diff --git a/library/msgs/es.msg b/library/msgs/es.msg
index a24f0a1..6090eab 100644
--- a/library/msgs/es.msg
+++ b/library/msgs/es.msg
@@ -4,18 +4,18 @@ namespace eval ::tcl::clock {
"dom"\
"lun"\
"mar"\
- "mi\u00e9"\
+ "mié"\
"jue"\
"vie"\
- "s\u00e1b"]
+ "sáb"]
::msgcat::mcset es DAYS_OF_WEEK_FULL [list \
"domingo"\
"lunes"\
"martes"\
- "mi\u00e9rcoles"\
+ "miércoles"\
"jueves"\
"viernes"\
- "s\u00e1bado"]
+ "sábado"]
::msgcat::mcset es MONTHS_ABBREV [list \
"ene"\
"feb"\
diff --git a/library/msgs/et.msg b/library/msgs/et.msg
index 8d32e9e..a782f9b 100644
--- a/library/msgs/et.msg
+++ b/library/msgs/et.msg
@@ -9,17 +9,17 @@ namespace eval ::tcl::clock {
"R"\
"L"]
::msgcat::mcset et DAYS_OF_WEEK_FULL [list \
- "p\u00fchap\u00e4ev"\
- "esmasp\u00e4ev"\
- "teisip\u00e4ev"\
- "kolmap\u00e4ev"\
- "neljap\u00e4ev"\
+ "pühapäev"\
+ "esmaspäev"\
+ "teisipäev"\
+ "kolmapäev"\
+ "neljapäev"\
"reede"\
- "laup\u00e4ev"]
+ "laupäev"]
::msgcat::mcset et MONTHS_ABBREV [list \
"Jaan"\
"Veebr"\
- "M\u00e4rts"\
+ "Märts"\
"Apr"\
"Mai"\
"Juuni"\
@@ -33,7 +33,7 @@ namespace eval ::tcl::clock {
::msgcat::mcset et MONTHS_FULL [list \
"Jaanuar"\
"Veebruar"\
- "M\u00e4rts"\
+ "Märts"\
"Aprill"\
"Mai"\
"Juuni"\
diff --git a/library/msgs/fa.msg b/library/msgs/fa.msg
index 89b2f90..6166e28 100644
--- a/library/msgs/fa.msg
+++ b/library/msgs/fa.msg
@@ -1,47 +1,47 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset fa DAYS_OF_WEEK_ABBREV [list \
- "\u06cc\u2214"\
- "\u062f\u2214"\
- "\u0633\u2214"\
- "\u0686\u2214"\
- "\u067e\u2214"\
- "\u062c\u2214"\
- "\u0634\u2214"]
+ "ی∔"\
+ "د∔"\
+ "س∔"\
+ "چ∔"\
+ "پ∔"\
+ "ج∔"\
+ "ش∔"]
::msgcat::mcset fa DAYS_OF_WEEK_FULL [list \
- "\u06cc\u06cc\u200c\u0634\u0646\u0628\u0647"\
- "\u062f\u0648\u0634\u0646\u0628\u0647"\
- "\u0633\u0647\u200c\u0634\u0646\u0628\u0647"\
- "\u0686\u0647\u0627\u0631\u0634\u0646\u0628\u0647"\
- "\u067e\u0646\u062c\u200c\u0634\u0646\u0628\u0647"\
- "\u062c\u0645\u0639\u0647"\
- "\u0634\u0646\u0628\u0647"]
+ "یی‌شنبه"\
+ "دوشنبه"\
+ "سه‌شنبه"\
+ "چهارشنبه"\
+ "پنج‌شنبه"\
+ "جمعه"\
+ "شنبه"]
::msgcat::mcset fa MONTHS_ABBREV [list \
- "\u0698\u0627\u0646"\
- "\u0641\u0648\u0631"\
- "\u0645\u0627\u0631"\
- "\u0622\u0648\u0631"\
- "\u0645\u0640\u0647"\
- "\u0698\u0648\u0646"\
- "\u0698\u0648\u06cc"\
- "\u0627\u0648\u062a"\
- "\u0633\u067e\u062a"\
- "\u0627\u0643\u062a"\
- "\u0646\u0648\u0627"\
- "\u062f\u0633\u0627"\
+ "ژان"\
+ "فور"\
+ "مار"\
+ "آور"\
+ "مـه"\
+ "ژون"\
+ "ژوی"\
+ "اوت"\
+ "سپت"\
+ "اكت"\
+ "نوا"\
+ "دسا"\
""]
::msgcat::mcset fa MONTHS_FULL [list \
- "\u0698\u0627\u0646\u0648\u06cc\u0647"\
- "\u0641\u0648\u0631\u0648\u06cc\u0647"\
- "\u0645\u0627\u0631\u0633"\
- "\u0622\u0648\u0631\u06cc\u0644"\
- "\u0645\u0647"\
- "\u0698\u0648\u0626\u0646"\
- "\u0698\u0648\u0626\u06cc\u0647"\
- "\u0627\u0648\u062a"\
- "\u0633\u067e\u062a\u0627\u0645\u0628\u0631"\
- "\u0627\u0643\u062a\u0628\u0631"\
- "\u0646\u0648\u0627\u0645\u0628\u0631"\
- "\u062f\u0633\u0627\u0645\u0628\u0631"\
+ "ژانویه"\
+ "فورویه"\
+ "مارس"\
+ "آوریل"\
+ "مه"\
+ "ژوئن"\
+ "ژوئیه"\
+ "اوت"\
+ "سپتامبر"\
+ "اكتبر"\
+ "نوامبر"\
+ "دسامبر"\
""]
}
diff --git a/library/msgs/fa_in.msg b/library/msgs/fa_in.msg
index adc9e91..ce32f99 100644
--- a/library/msgs/fa_in.msg
+++ b/library/msgs/fa_in.msg
@@ -1,51 +1,51 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset fa_IN DAYS_OF_WEEK_ABBREV [list \
- "\u06cc\u2214"\
- "\u062f\u2214"\
- "\u0633\u2214"\
- "\u0686\u2214"\
- "\u067e\u2214"\
- "\u062c\u2214"\
- "\u0634\u2214"]
+ "ی∔"\
+ "د∔"\
+ "س∔"\
+ "چ∔"\
+ "پ∔"\
+ "ج∔"\
+ "ش∔"]
::msgcat::mcset fa_IN DAYS_OF_WEEK_FULL [list \
- "\u06cc\u06cc\u200c\u0634\u0646\u0628\u0647"\
- "\u062f\u0648\u0634\u0646\u0628\u0647"\
- "\u0633\u0647\u200c\u0634\u0646\u0628\u0647"\
- "\u0686\u0647\u0627\u0631\u0634\u0646\u0628\u0647"\
- "\u067e\u0646\u062c\u200c\u0634\u0646\u0628\u0647"\
- "\u062c\u0645\u0639\u0647"\
- "\u0634\u0646\u0628\u0647"]
+ "یی‌شنبه"\
+ "دوشنبه"\
+ "سه‌شنبه"\
+ "چهارشنبه"\
+ "پنج‌شنبه"\
+ "جمعه"\
+ "شنبه"]
::msgcat::mcset fa_IN MONTHS_ABBREV [list \
- "\u0698\u0627\u0646"\
- "\u0641\u0648\u0631"\
- "\u0645\u0627\u0631"\
- "\u0622\u0648\u0631"\
- "\u0645\u0640\u0647"\
- "\u0698\u0648\u0646"\
- "\u0698\u0648\u06cc"\
- "\u0627\u0648\u062a"\
- "\u0633\u067e\u062a"\
- "\u0627\u0643\u062a"\
- "\u0646\u0648\u0627"\
- "\u062f\u0633\u0627"\
+ "ژان"\
+ "فور"\
+ "مار"\
+ "آور"\
+ "مـه"\
+ "ژون"\
+ "ژوی"\
+ "اوت"\
+ "سپت"\
+ "اكت"\
+ "نوا"\
+ "دسا"\
""]
::msgcat::mcset fa_IN MONTHS_FULL [list \
- "\u0698\u0627\u0646\u0648\u06cc\u0647"\
- "\u0641\u0648\u0631\u0648\u06cc\u0647"\
- "\u0645\u0627\u0631\u0633"\
- "\u0622\u0648\u0631\u06cc\u0644"\
- "\u0645\u0647"\
- "\u0698\u0648\u0626\u0646"\
- "\u0698\u0648\u0626\u06cc\u0647"\
- "\u0627\u0648\u062a"\
- "\u0633\u067e\u062a\u0627\u0645\u0628\u0631"\
- "\u0627\u0643\u062a\u0628\u0631"\
- "\u0646\u0648\u0627\u0645\u0628\u0631"\
- "\u062f\u0633\u0627\u0645\u0628\u0631"\
+ "ژانویه"\
+ "فورویه"\
+ "مارس"\
+ "آوریل"\
+ "مه"\
+ "ژوئن"\
+ "ژوئیه"\
+ "اوت"\
+ "سپتامبر"\
+ "اكتبر"\
+ "نوامبر"\
+ "دسامبر"\
""]
- ::msgcat::mcset fa_IN AM "\u0635\u0628\u062d"
- ::msgcat::mcset fa_IN PM "\u0639\u0635\u0631"
+ ::msgcat::mcset fa_IN AM "صبح"
+ ::msgcat::mcset fa_IN PM "عصر"
::msgcat::mcset fa_IN DATE_FORMAT "%A %d %B %Y"
::msgcat::mcset fa_IN TIME_FORMAT_12 "%I:%M:%S %z"
::msgcat::mcset fa_IN DATE_TIME_FORMAT "%A %d %B %Y %I:%M:%S %z %z"
diff --git a/library/msgs/fa_ir.msg b/library/msgs/fa_ir.msg
index 597ce9d..9ce9284 100644
--- a/library/msgs/fa_ir.msg
+++ b/library/msgs/fa_ir.msg
@@ -1,9 +1,9 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
- ::msgcat::mcset fa_IR AM "\u0635\u0628\u062d"
- ::msgcat::mcset fa_IR PM "\u0639\u0635\u0631"
- ::msgcat::mcset fa_IR DATE_FORMAT "%d\u2044%m\u2044%Y"
+ ::msgcat::mcset fa_IR AM "صبح"
+ ::msgcat::mcset fa_IR PM "عصر"
+ ::msgcat::mcset fa_IR DATE_FORMAT "%d⁄%m⁄%Y"
::msgcat::mcset fa_IR TIME_FORMAT "%S:%M:%H"
::msgcat::mcset fa_IR TIME_FORMAT_12 "%S:%M:%l %P"
- ::msgcat::mcset fa_IR DATE_TIME_FORMAT "%d\u2044%m\u2044%Y %S:%M:%H %z"
+ ::msgcat::mcset fa_IR DATE_TIME_FORMAT "%d⁄%m⁄%Y %S:%M:%H %z"
}
diff --git a/library/msgs/fi.msg b/library/msgs/fi.msg
index acabba0..69be367 100644
--- a/library/msgs/fi.msg
+++ b/library/msgs/fi.msg
@@ -22,8 +22,8 @@ namespace eval ::tcl::clock {
"maalis"\
"huhti"\
"touko"\
- "kes\u00e4"\
- "hein\u00e4"\
+ "kesä"\
+ "heinä"\
"elo"\
"syys"\
"loka"\
@@ -36,8 +36,8 @@ namespace eval ::tcl::clock {
"maaliskuu"\
"huhtikuu"\
"toukokuu"\
- "kes\u00e4kuu"\
- "hein\u00e4kuu"\
+ "kesäkuu"\
+ "heinäkuu"\
"elokuu"\
"syyskuu"\
"lokakuu"\
diff --git a/library/msgs/fo.msg b/library/msgs/fo.msg
index 4696e62..1f1794d 100644
--- a/library/msgs/fo.msg
+++ b/library/msgs/fo.msg
@@ -2,19 +2,19 @@
namespace eval ::tcl::clock {
::msgcat::mcset fo DAYS_OF_WEEK_ABBREV [list \
"sun"\
- "m\u00e1n"\
- "t\u00fds"\
+ "mán"\
+ "týs"\
"mik"\
- "h\u00f3s"\
- "fr\u00ed"\
+ "hós"\
+ "frí"\
"ley"]
::msgcat::mcset fo DAYS_OF_WEEK_FULL [list \
"sunnudagur"\
- "m\u00e1nadagur"\
- "t\u00fdsdagur"\
+ "mánadagur"\
+ "týsdagur"\
"mikudagur"\
- "h\u00f3sdagur"\
- "fr\u00edggjadagur"\
+ "hósdagur"\
+ "fríggjadagur"\
"leygardagur"]
::msgcat::mcset fo MONTHS_ABBREV [list \
"jan"\
@@ -34,7 +34,7 @@ namespace eval ::tcl::clock {
"januar"\
"februar"\
"mars"\
- "apr\u00edl"\
+ "apríl"\
"mai"\
"juni"\
"juli"\
diff --git a/library/msgs/fr.msg b/library/msgs/fr.msg
index 55b19bf..a274468 100644
--- a/library/msgs/fr.msg
+++ b/library/msgs/fr.msg
@@ -18,31 +18,31 @@ namespace eval ::tcl::clock {
"samedi"]
::msgcat::mcset fr MONTHS_ABBREV [list \
"janv."\
- "f\u00e9vr."\
+ "févr."\
"mars"\
"avr."\
"mai"\
"juin"\
"juil."\
- "ao\u00fbt"\
+ "août"\
"sept."\
"oct."\
"nov."\
- "d\u00e9c."\
+ "déc."\
""]
::msgcat::mcset fr MONTHS_FULL [list \
"janvier"\
- "f\u00e9vrier"\
+ "février"\
"mars"\
"avril"\
"mai"\
"juin"\
"juillet"\
- "ao\u00fbt"\
+ "août"\
"septembre"\
"octobre"\
"novembre"\
- "d\u00e9cembre"\
+ "décembre"\
""]
::msgcat::mcset fr BCE "av. J.-C."
::msgcat::mcset fr CE "ap. J.-C."
diff --git a/library/msgs/ga.msg b/library/msgs/ga.msg
index 6edf13a..056c9a0 100644
--- a/library/msgs/ga.msg
+++ b/library/msgs/ga.msg
@@ -3,45 +3,45 @@ namespace eval ::tcl::clock {
::msgcat::mcset ga DAYS_OF_WEEK_ABBREV [list \
"Domh"\
"Luan"\
- "M\u00e1irt"\
- "C\u00e9ad"\
- "D\u00e9ar"\
+ "Máirt"\
+ "Céad"\
+ "Déar"\
"Aoine"\
"Sath"]
::msgcat::mcset ga DAYS_OF_WEEK_FULL [list \
- "D\u00e9 Domhnaigh"\
- "D\u00e9 Luain"\
- "D\u00e9 M\u00e1irt"\
- "D\u00e9 C\u00e9adaoin"\
- "D\u00e9ardaoin"\
- "D\u00e9 hAoine"\
- "D\u00e9 Sathairn"]
+ "Dé Domhnaigh"\
+ "Dé Luain"\
+ "Dé Máirt"\
+ "Dé Céadaoin"\
+ "Déardaoin"\
+ "Dé hAoine"\
+ "Dé Sathairn"]
::msgcat::mcset ga MONTHS_ABBREV [list \
"Ean"\
"Feabh"\
- "M\u00e1rta"\
+ "Márta"\
"Aib"\
"Beal"\
"Meith"\
- "I\u00fail"\
- "L\u00fan"\
- "MF\u00f3mh"\
- "DF\u00f3mh"\
+ "Iúil"\
+ "Lún"\
+ "MFómh"\
+ "DFómh"\
"Samh"\
"Noll"\
""]
::msgcat::mcset ga MONTHS_FULL [list \
- "Ean\u00e1ir"\
+ "Eanáir"\
"Feabhra"\
- "M\u00e1rta"\
- "Aibre\u00e1n"\
- "M\u00ed na Bealtaine"\
+ "Márta"\
+ "Aibreán"\
+ "Mí na Bealtaine"\
"Meith"\
- "I\u00fail"\
- "L\u00fanasa"\
- "Me\u00e1n F\u00f3mhair"\
- "Deireadh F\u00f3mhair"\
- "M\u00ed na Samhna"\
- "M\u00ed na Nollag"\
+ "Iúil"\
+ "Lúnasa"\
+ "Meán Fómhair"\
+ "Deireadh Fómhair"\
+ "Mí na Samhna"\
+ "Mí na Nollag"\
""]
}
diff --git a/library/msgs/gl.msg b/library/msgs/gl.msg
index 4b869e8..c2fefc9 100644
--- a/library/msgs/gl.msg
+++ b/library/msgs/gl.msg
@@ -4,25 +4,25 @@ namespace eval ::tcl::clock {
"Dom"\
"Lun"\
"Mar"\
- "M\u00e9r"\
+ "Mér"\
"Xov"\
"Ven"\
- "S\u00e1b"]
+ "Sáb"]
::msgcat::mcset gl DAYS_OF_WEEK_FULL [list \
"Domingo"\
"Luns"\
"Martes"\
- "M\u00e9rcores"\
+ "Mércores"\
"Xoves"\
"Venres"\
- "S\u00e1bado"]
+ "Sábado"]
::msgcat::mcset gl MONTHS_ABBREV [list \
"Xan"\
"Feb"\
"Mar"\
"Abr"\
"Mai"\
- "Xu\u00f1"\
+ "Xuñ"\
"Xul"\
"Ago"\
"Set"\
@@ -36,7 +36,7 @@ namespace eval ::tcl::clock {
"Marzo"\
"Abril"\
"Maio"\
- "Xu\u00f1o"\
+ "Xuño"\
"Xullo"\
"Agosto"\
"Setembro"\
diff --git a/library/msgs/he.msg b/library/msgs/he.msg
index 4fd921d..13a81b7 100644
--- a/library/msgs/he.msg
+++ b/library/msgs/he.msg
@@ -1,51 +1,51 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset he DAYS_OF_WEEK_ABBREV [list \
- "\u05d0"\
- "\u05d1"\
- "\u05d2"\
- "\u05d3"\
- "\u05d4"\
- "\u05d5"\
- "\u05e9"]
+ "א"\
+ "ב"\
+ "ג"\
+ "ד"\
+ "ה"\
+ "ו"\
+ "ש"]
::msgcat::mcset he DAYS_OF_WEEK_FULL [list \
- "\u05d9\u05d5\u05dd \u05e8\u05d0\u05e9\u05d5\u05df"\
- "\u05d9\u05d5\u05dd \u05e9\u05e0\u05d9"\
- "\u05d9\u05d5\u05dd \u05e9\u05dc\u05d9\u05e9\u05d9"\
- "\u05d9\u05d5\u05dd \u05e8\u05d1\u05d9\u05e2\u05d9"\
- "\u05d9\u05d5\u05dd \u05d7\u05de\u05d9\u05e9\u05d9"\
- "\u05d9\u05d5\u05dd \u05e9\u05d9\u05e9\u05d9"\
- "\u05e9\u05d1\u05ea"]
+ "יום ראשון"\
+ "יום שני"\
+ "יום שלישי"\
+ "יום רביעי"\
+ "יום חמישי"\
+ "יום שישי"\
+ "שבת"]
::msgcat::mcset he MONTHS_ABBREV [list \
- "\u05d9\u05e0\u05d5"\
- "\u05e4\u05d1\u05e8"\
- "\u05de\u05e8\u05e5"\
- "\u05d0\u05e4\u05e8"\
- "\u05de\u05d0\u05d9"\
- "\u05d9\u05d5\u05e0"\
- "\u05d9\u05d5\u05dc"\
- "\u05d0\u05d5\u05d2"\
- "\u05e1\u05e4\u05d8"\
- "\u05d0\u05d5\u05e7"\
- "\u05e0\u05d5\u05d1"\
- "\u05d3\u05e6\u05de"\
+ "ינו"\
+ "פבר"\
+ "מרץ"\
+ "אפר"\
+ "מאי"\
+ "יונ"\
+ "יול"\
+ "אוג"\
+ "ספט"\
+ "אוק"\
+ "נוב"\
+ "דצמ"\
""]
::msgcat::mcset he MONTHS_FULL [list \
- "\u05d9\u05e0\u05d5\u05d0\u05e8"\
- "\u05e4\u05d1\u05e8\u05d5\u05d0\u05e8"\
- "\u05de\u05e8\u05e5"\
- "\u05d0\u05e4\u05e8\u05d9\u05dc"\
- "\u05de\u05d0\u05d9"\
- "\u05d9\u05d5\u05e0\u05d9"\
- "\u05d9\u05d5\u05dc\u05d9"\
- "\u05d0\u05d5\u05d2\u05d5\u05e1\u05d8"\
- "\u05e1\u05e4\u05d8\u05de\u05d1\u05e8"\
- "\u05d0\u05d5\u05e7\u05d8\u05d5\u05d1\u05e8"\
- "\u05e0\u05d5\u05d1\u05de\u05d1\u05e8"\
- "\u05d3\u05e6\u05de\u05d1\u05e8"\
+ "ינואר"\
+ "פברואר"\
+ "מרץ"\
+ "אפריל"\
+ "מאי"\
+ "יוני"\
+ "יולי"\
+ "אוגוסט"\
+ "ספטמבר"\
+ "אוקטובר"\
+ "נובמבר"\
+ "דצמבר"\
""]
- ::msgcat::mcset he BCE "\u05dc\u05e1\u05d4\u0022\u05e0"
- ::msgcat::mcset he CE "\u05dc\u05e4\u05e1\u05d4\u0022\u05e0"
+ ::msgcat::mcset he BCE "לסה"נ"
+ ::msgcat::mcset he CE "לפסה"נ"
::msgcat::mcset he DATE_FORMAT "%d/%m/%Y"
::msgcat::mcset he TIME_FORMAT "%H:%M:%S"
::msgcat::mcset he DATE_TIME_FORMAT "%d/%m/%Y %H:%M:%S %z"
diff --git a/library/msgs/hi.msg b/library/msgs/hi.msg
index 50c9fb8..18c8bf0 100644
--- a/library/msgs/hi.msg
+++ b/library/msgs/hi.msg
@@ -1,39 +1,39 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset hi DAYS_OF_WEEK_FULL [list \
- "\u0930\u0935\u093f\u0935\u093e\u0930"\
- "\u0938\u094b\u092e\u0935\u093e\u0930"\
- "\u092e\u0902\u0917\u0932\u0935\u093e\u0930"\
- "\u092c\u0941\u0927\u0935\u093e\u0930"\
- "\u0917\u0941\u0930\u0941\u0935\u093e\u0930"\
- "\u0936\u0941\u0915\u094d\u0930\u0935\u093e\u0930"\
- "\u0936\u0928\u093f\u0935\u093e\u0930"]
+ "रविवार"\
+ "सोमवार"\
+ "मंगलवार"\
+ "बुधवार"\
+ "गुरुवार"\
+ "शुक्रवार"\
+ "शनिवार"]
::msgcat::mcset hi MONTHS_ABBREV [list \
- "\u091c\u0928\u0935\u0930\u0940"\
- "\u092b\u093c\u0930\u0935\u0930\u0940"\
- "\u092e\u093e\u0930\u094d\u091a"\
- "\u0905\u092a\u094d\u0930\u0947\u0932"\
- "\u092e\u0908"\
- "\u091c\u0942\u0928"\
- "\u091c\u0941\u0932\u093e\u0908"\
- "\u0905\u0917\u0938\u094d\u0924"\
- "\u0938\u093f\u0924\u092e\u094d\u092c\u0930"\
- "\u0905\u0915\u094d\u091f\u0942\u092c\u0930"\
- "\u0928\u0935\u092e\u094d\u092c\u0930"\
- "\u0926\u093f\u0938\u092e\u094d\u092c\u0930"]
+ "जनवरी"\
+ "फ़रवरी"\
+ "मार्च"\
+ "अप्रेल"\
+ "मई"\
+ "जून"\
+ "जुलाई"\
+ "अगस्त"\
+ "सितम्बर"\
+ "अक्टूबर"\
+ "नवम्बर"\
+ "दिसम्बर"]
::msgcat::mcset hi MONTHS_FULL [list \
- "\u091c\u0928\u0935\u0930\u0940"\
- "\u092b\u093c\u0930\u0935\u0930\u0940"\
- "\u092e\u093e\u0930\u094d\u091a"\
- "\u0905\u092a\u094d\u0930\u0947\u0932"\
- "\u092e\u0908"\
- "\u091c\u0942\u0928"\
- "\u091c\u0941\u0932\u093e\u0908"\
- "\u0905\u0917\u0938\u094d\u0924"\
- "\u0938\u093f\u0924\u092e\u094d\u092c\u0930"\
- "\u0905\u0915\u094d\u091f\u0942\u092c\u0930"\
- "\u0928\u0935\u092e\u094d\u092c\u0930"\
- "\u0926\u093f\u0938\u092e\u094d\u092c\u0930"]
- ::msgcat::mcset hi AM "\u0908\u0938\u093e\u092a\u0942\u0930\u094d\u0935"
+ "जनवरी"\
+ "फ़रवरी"\
+ "मार्च"\
+ "अप्रेल"\
+ "मई"\
+ "जून"\
+ "जुलाई"\
+ "अगस्त"\
+ "सितम्बर"\
+ "अक्टूबर"\
+ "नवम्बर"\
+ "दिसम्बर"]
+ ::msgcat::mcset hi AM "ईसापूर्व"
::msgcat::mcset hi PM "."
}
diff --git a/library/msgs/hr.msg b/library/msgs/hr.msg
index cec145b..30491e1 100644
--- a/library/msgs/hr.msg
+++ b/library/msgs/hr.msg
@@ -5,7 +5,7 @@ namespace eval ::tcl::clock {
"pon"\
"uto"\
"sri"\
- "\u010det"\
+ "čet"\
"pet"\
"sub"]
::msgcat::mcset hr DAYS_OF_WEEK_FULL [list \
@@ -13,13 +13,13 @@ namespace eval ::tcl::clock {
"ponedjeljak"\
"utorak"\
"srijeda"\
- "\u010detvrtak"\
+ "četvrtak"\
"petak"\
"subota"]
::msgcat::mcset hr MONTHS_ABBREV [list \
"sij"\
"vel"\
- "o\u017eu"\
+ "ožu"\
"tra"\
"svi"\
"lip"\
@@ -31,9 +31,9 @@ namespace eval ::tcl::clock {
"pro"\
""]
::msgcat::mcset hr MONTHS_FULL [list \
- "sije\u010danj"\
- "velja\u010da"\
- "o\u017eujak"\
+ "siječanj"\
+ "veljača"\
+ "ožujak"\
"travanj"\
"svibanj"\
"lipanj"\
diff --git a/library/msgs/hu.msg b/library/msgs/hu.msg
index e5e68d9..46776dd 100644
--- a/library/msgs/hu.msg
+++ b/library/msgs/hu.msg
@@ -9,21 +9,21 @@ namespace eval ::tcl::clock {
"P"\
"Szo"]
::msgcat::mcset hu DAYS_OF_WEEK_FULL [list \
- "vas\u00e1rnap"\
- "h\u00e9tf\u0151"\
+ "vasárnap"\
+ "hétfő"\
"kedd"\
"szerda"\
- "cs\u00fct\u00f6rt\u00f6k"\
- "p\u00e9ntek"\
+ "csütörtök"\
+ "péntek"\
"szombat"]
::msgcat::mcset hu MONTHS_ABBREV [list \
"jan."\
"febr."\
- "m\u00e1rc."\
- "\u00e1pr."\
- "m\u00e1j."\
- "j\u00fan."\
- "j\u00fal."\
+ "márc."\
+ "ápr."\
+ "máj."\
+ "jún."\
+ "júl."\
"aug."\
"szept."\
"okt."\
@@ -31,16 +31,16 @@ namespace eval ::tcl::clock {
"dec."\
""]
::msgcat::mcset hu MONTHS_FULL [list \
- "janu\u00e1r"\
- "febru\u00e1r"\
- "m\u00e1rcius"\
- "\u00e1prilis"\
- "m\u00e1jus"\
- "j\u00fanius"\
- "j\u00falius"\
+ "január"\
+ "február"\
+ "március"\
+ "április"\
+ "május"\
+ "június"\
+ "július"\
"augusztus"\
"szeptember"\
- "okt\u00f3ber"\
+ "október"\
"november"\
"december"\
""]
diff --git a/library/msgs/is.msg b/library/msgs/is.msg
index adc2d2a..a369b89 100644
--- a/library/msgs/is.msg
+++ b/library/msgs/is.msg
@@ -2,46 +2,46 @@
namespace eval ::tcl::clock {
::msgcat::mcset is DAYS_OF_WEEK_ABBREV [list \
"sun."\
- "m\u00e1n."\
- "\u00feri."\
- "mi\u00f0."\
+ "mán."\
+ "þri."\
+ "mið."\
"fim."\
- "f\u00f6s."\
+ "fös."\
"lau."]
::msgcat::mcset is DAYS_OF_WEEK_FULL [list \
"sunnudagur"\
- "m\u00e1nudagur"\
- "\u00feri\u00f0judagur"\
- "mi\u00f0vikudagur"\
+ "mánudagur"\
+ "þriðjudagur"\
+ "miðvikudagur"\
"fimmtudagur"\
- "f\u00f6studagur"\
+ "föstudagur"\
"laugardagur"]
::msgcat::mcset is MONTHS_ABBREV [list \
"jan."\
"feb."\
"mar."\
"apr."\
- "ma\u00ed"\
- "j\u00fan."\
- "j\u00fal."\
- "\u00e1g\u00fa."\
+ "maí"\
+ "jún."\
+ "júl."\
+ "ágú."\
"sep."\
"okt."\
- "n\u00f3v."\
+ "nóv."\
"des."\
""]
::msgcat::mcset is MONTHS_FULL [list \
- "jan\u00faar"\
- "febr\u00faar"\
+ "janúar"\
+ "febrúar"\
"mars"\
- "apr\u00edl"\
- "ma\u00ed"\
- "j\u00fan\u00ed"\
- "j\u00fal\u00ed"\
- "\u00e1g\u00fast"\
+ "apríl"\
+ "maí"\
+ "júní"\
+ "júlí"\
+ "ágúst"\
"september"\
- "okt\u00f3ber"\
- "n\u00f3vember"\
+ "október"\
+ "nóvember"\
"desember"\
""]
::msgcat::mcset is DATE_FORMAT "%e.%m.%Y"
diff --git a/library/msgs/it.msg b/library/msgs/it.msg
index b641cde..e51aee2 100644
--- a/library/msgs/it.msg
+++ b/library/msgs/it.msg
@@ -10,11 +10,11 @@ namespace eval ::tcl::clock {
"sab"]
::msgcat::mcset it DAYS_OF_WEEK_FULL [list \
"domenica"\
- "luned\u00ec"\
- "marted\u00ec"\
- "mercoled\u00ec"\
- "gioved\u00ec"\
- "venerd\u00ec"\
+ "lunedì"\
+ "martedì"\
+ "mercoledì"\
+ "giovedì"\
+ "venerdì"\
"sabato"]
::msgcat::mcset it MONTHS_ABBREV [list \
"gen"\
diff --git a/library/msgs/ja.msg b/library/msgs/ja.msg
index cf70c2f..dac690b 100644
--- a/library/msgs/ja.msg
+++ b/library/msgs/ja.msg
@@ -1,44 +1,44 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset ja DAYS_OF_WEEK_ABBREV [list \
- "\u65e5"\
- "\u6708"\
- "\u706b"\
- "\u6c34"\
- "\u6728"\
- "\u91d1"\
- "\u571f"]
+ "日"\
+ "月"\
+ "火"\
+ "水"\
+ "木"\
+ "金"\
+ "土"]
::msgcat::mcset ja DAYS_OF_WEEK_FULL [list \
- "\u65e5\u66dc\u65e5"\
- "\u6708\u66dc\u65e5"\
- "\u706b\u66dc\u65e5"\
- "\u6c34\u66dc\u65e5"\
- "\u6728\u66dc\u65e5"\
- "\u91d1\u66dc\u65e5"\
- "\u571f\u66dc\u65e5"]
+ "日曜日"\
+ "月曜日"\
+ "火曜日"\
+ "水曜日"\
+ "木曜日"\
+ "金曜日"\
+ "土曜日"]
::msgcat::mcset ja MONTHS_FULL [list \
- "1\u6708"\
- "2\u6708"\
- "3\u6708"\
- "4\u6708"\
- "5\u6708"\
- "6\u6708"\
- "7\u6708"\
- "8\u6708"\
- "9\u6708"\
- "10\u6708"\
- "11\u6708"\
- "12\u6708"]
- ::msgcat::mcset ja BCE "\u7d00\u5143\u524d"
- ::msgcat::mcset ja CE "\u897f\u66a6"
- ::msgcat::mcset ja AM "\u5348\u524d"
- ::msgcat::mcset ja PM "\u5348\u5f8c"
+ "1月"\
+ "2月"\
+ "3月"\
+ "4月"\
+ "5月"\
+ "6月"\
+ "7月"\
+ "8月"\
+ "9月"\
+ "10月"\
+ "11月"\
+ "12月"]
+ ::msgcat::mcset ja BCE "紀元前"
+ ::msgcat::mcset ja CE "西暦"
+ ::msgcat::mcset ja AM "午前"
+ ::msgcat::mcset ja PM "午後"
::msgcat::mcset ja DATE_FORMAT "%Y/%m/%d"
::msgcat::mcset ja TIME_FORMAT "%k:%M:%S"
::msgcat::mcset ja TIME_FORMAT_12 "%P %I:%M:%S"
::msgcat::mcset ja DATE_TIME_FORMAT "%Y/%m/%d %k:%M:%S %z"
- ::msgcat::mcset ja LOCALE_DATE_FORMAT "%EY\u5e74%m\u6708%d\u65e5"
- ::msgcat::mcset ja LOCALE_TIME_FORMAT "%H\u6642%M\u5206%S\u79d2"
- ::msgcat::mcset ja LOCALE_DATE_TIME_FORMAT "%EY\u5e74%m\u6708%d\u65e5 (%a) %H\u6642%M\u5206%S\u79d2 %z"
- ::msgcat::mcset ja LOCALE_ERAS "{-9223372036854775808 \u897f\u66a6 0} {-3061011600 \u660e\u6cbb 1867} {-1812186000 \u5927\u6b63 1911} {-1357635600 \u662d\u548c 1925} {600220800 \u5e73\u6210 1988} {1556668800 \u4ee4\u548c 2018}"
+ ::msgcat::mcset ja LOCALE_DATE_FORMAT "%EY年%m月%d日"
+ ::msgcat::mcset ja LOCALE_TIME_FORMAT "%H時%M分%S秒"
+ ::msgcat::mcset ja LOCALE_DATE_TIME_FORMAT "%EY年%m月%d日 (%a) %H時%M分%S秒 %z"
+ ::msgcat::mcset ja LOCALE_ERAS "{-9223372036854775808 西暦 0} {-3061011600 明治 1867} {-1812186000 大正 1911} {-1357635600 昭和 1925} {600220800 平成 1988} {1556668800 令和 2018}"
}
diff --git a/library/msgs/ko.msg b/library/msgs/ko.msg
index 0cd17a1..817c2e7 100644
--- a/library/msgs/ko.msg
+++ b/library/msgs/ko.msg
@@ -1,55 +1,55 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset ko DAYS_OF_WEEK_ABBREV [list \
- "\uc77c"\
- "\uc6d4"\
- "\ud654"\
- "\uc218"\
- "\ubaa9"\
- "\uae08"\
- "\ud1a0"]
+ "일"\
+ "월"\
+ "화"\
+ "수"\
+ "목"\
+ "금"\
+ "토"]
::msgcat::mcset ko DAYS_OF_WEEK_FULL [list \
- "\uc77c\uc694\uc77c"\
- "\uc6d4\uc694\uc77c"\
- "\ud654\uc694\uc77c"\
- "\uc218\uc694\uc77c"\
- "\ubaa9\uc694\uc77c"\
- "\uae08\uc694\uc77c"\
- "\ud1a0\uc694\uc77c"]
+ "일요일"\
+ "월요일"\
+ "화요일"\
+ "수요일"\
+ "목요일"\
+ "금요일"\
+ "토요일"]
::msgcat::mcset ko MONTHS_ABBREV [list \
- "1\uc6d4"\
- "2\uc6d4"\
- "3\uc6d4"\
- "4\uc6d4"\
- "5\uc6d4"\
- "6\uc6d4"\
- "7\uc6d4"\
- "8\uc6d4"\
- "9\uc6d4"\
- "10\uc6d4"\
- "11\uc6d4"\
- "12\uc6d4"\
+ "1월"\
+ "2월"\
+ "3월"\
+ "4월"\
+ "5월"\
+ "6월"\
+ "7월"\
+ "8월"\
+ "9월"\
+ "10월"\
+ "11월"\
+ "12월"\
""]
::msgcat::mcset ko MONTHS_FULL [list \
- "1\uc6d4"\
- "2\uc6d4"\
- "3\uc6d4"\
- "4\uc6d4"\
- "5\uc6d4"\
- "6\uc6d4"\
- "7\uc6d4"\
- "8\uc6d4"\
- "9\uc6d4"\
- "10\uc6d4"\
- "11\uc6d4"\
- "12\uc6d4"\
+ "1월"\
+ "2월"\
+ "3월"\
+ "4월"\
+ "5월"\
+ "6월"\
+ "7월"\
+ "8월"\
+ "9월"\
+ "10월"\
+ "11월"\
+ "12월"\
""]
- ::msgcat::mcset ko AM "\uc624\uc804"
- ::msgcat::mcset ko PM "\uc624\ud6c4"
+ ::msgcat::mcset ko AM "오전"
+ ::msgcat::mcset ko PM "오후"
::msgcat::mcset ko DATE_FORMAT "%Y-%m-%d"
::msgcat::mcset ko TIME_FORMAT_12 "%P %l:%M:%S"
::msgcat::mcset ko DATE_TIME_FORMAT "%Y-%m-%d %P %l:%M:%S %z"
- ::msgcat::mcset ko LOCALE_DATE_FORMAT "%Y\ub144%B%Od\uc77c"
- ::msgcat::mcset ko LOCALE_TIME_FORMAT "%H\uc2dc%M\ubd84%S\ucd08"
- ::msgcat::mcset ko LOCALE_DATE_TIME_FORMAT "%A %Y\ub144%B%Od\uc77c%H\uc2dc%M\ubd84%S\ucd08 %z"
+ ::msgcat::mcset ko LOCALE_DATE_FORMAT "%Y년%B%Od일"
+ ::msgcat::mcset ko LOCALE_TIME_FORMAT "%H시%M분%S초"
+ ::msgcat::mcset ko LOCALE_DATE_TIME_FORMAT "%A %Y년%B%Od일%H시%M분%S초 %z"
}
diff --git a/library/msgs/ko_kr.msg b/library/msgs/ko_kr.msg
index ea5bbd7..f23bd6b 100644
--- a/library/msgs/ko_kr.msg
+++ b/library/msgs/ko_kr.msg
@@ -1,7 +1,7 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
- ::msgcat::mcset ko_KR BCE "\uae30\uc6d0\uc804"
- ::msgcat::mcset ko_KR CE "\uc11c\uae30"
+ ::msgcat::mcset ko_KR BCE "기원전"
+ ::msgcat::mcset ko_KR CE "서기"
::msgcat::mcset ko_KR DATE_FORMAT "%Y.%m.%d"
::msgcat::mcset ko_KR TIME_FORMAT_12 "%P %l:%M:%S"
::msgcat::mcset ko_KR DATE_TIME_FORMAT "%Y.%m.%d %P %l:%M:%S %z"
diff --git a/library/msgs/kok.msg b/library/msgs/kok.msg
index 0869f20..231853b 100644
--- a/library/msgs/kok.msg
+++ b/library/msgs/kok.msg
@@ -1,39 +1,39 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset kok DAYS_OF_WEEK_FULL [list \
- "\u0906\u0926\u093f\u0924\u094d\u092f\u0935\u093e\u0930"\
- "\u0938\u094b\u092e\u0935\u093e\u0930"\
- "\u092e\u0902\u0917\u0933\u093e\u0930"\
- "\u092c\u0941\u0927\u0935\u093e\u0930"\
- "\u0917\u0941\u0930\u0941\u0935\u093e\u0930"\
- "\u0936\u0941\u0915\u094d\u0930\u0935\u093e\u0930"\
- "\u0936\u0928\u093f\u0935\u093e\u0930"]
+ "आदित्यवार"\
+ "सोमवार"\
+ "मंगळार"\
+ "बुधवार"\
+ "गुरुवार"\
+ "शुक्रवार"\
+ "शनिवार"]
::msgcat::mcset kok MONTHS_ABBREV [list \
- "\u091c\u093e\u0928\u0947\u0935\u093e\u0930\u0940"\
- "\u092b\u0947\u092c\u0943\u0935\u093e\u0930\u0940"\
- "\u092e\u093e\u0930\u094d\u091a"\
- "\u090f\u092a\u094d\u0930\u093f\u0932"\
- "\u092e\u0947"\
- "\u091c\u0942\u0928"\
- "\u091c\u0941\u0932\u0948"\
- "\u0913\u0917\u0938\u094d\u091f"\
- "\u0938\u0947\u092a\u094d\u091f\u0947\u0902\u092c\u0930"\
- "\u0913\u0915\u094d\u091f\u094b\u092c\u0930"\
- "\u0928\u094b\u0935\u094d\u0939\u0947\u0902\u092c\u0930"\
- "\u0921\u093f\u0938\u0947\u0902\u092c\u0930"]
+ "जानेवारी"\
+ "फेबृवारी"\
+ "मार्च"\
+ "एप्रिल"\
+ "मे"\
+ "जून"\
+ "जुलै"\
+ "ओगस्ट"\
+ "सेप्टेंबर"\
+ "ओक्टोबर"\
+ "नोव्हेंबर"\
+ "डिसेंबर"]
::msgcat::mcset kok MONTHS_FULL [list \
- "\u091c\u093e\u0928\u0947\u0935\u093e\u0930\u0940"\
- "\u092b\u0947\u092c\u094d\u0930\u0941\u0935\u093e\u0930\u0940"\
- "\u092e\u093e\u0930\u094d\u091a"\
- "\u090f\u092a\u094d\u0930\u093f\u0932"\
- "\u092e\u0947"\
- "\u091c\u0942\u0928"\
- "\u091c\u0941\u0932\u0948"\
- "\u0913\u0917\u0938\u094d\u091f"\
- "\u0938\u0947\u092a\u094d\u091f\u0947\u0902\u092c\u0930"\
- "\u0913\u0915\u094d\u091f\u094b\u092c\u0930"\
- "\u0928\u094b\u0935\u094d\u0939\u0947\u0902\u092c\u0930"\
- "\u0921\u093f\u0938\u0947\u0902\u092c\u0930"]
- ::msgcat::mcset kok AM "\u0915\u094d\u0930\u093f\u0938\u094d\u0924\u092a\u0942\u0930\u094d\u0935"
- ::msgcat::mcset kok PM "\u0915\u094d\u0930\u093f\u0938\u094d\u0924\u0936\u0916\u093e"
+ "जानेवारी"\
+ "फेब्रुवारी"\
+ "मार्च"\
+ "एप्रिल"\
+ "मे"\
+ "जून"\
+ "जुलै"\
+ "ओगस्ट"\
+ "सेप्टेंबर"\
+ "ओक्टोबर"\
+ "नोव्हेंबर"\
+ "डिसेंबर"]
+ ::msgcat::mcset kok AM "क्रिस्तपूर्व"
+ ::msgcat::mcset kok PM "क्रिस्तशखा"
}
diff --git a/library/msgs/lt.msg b/library/msgs/lt.msg
index 27b0985..15829a9 100644
--- a/library/msgs/lt.msg
+++ b/library/msgs/lt.msg
@@ -7,15 +7,15 @@ namespace eval ::tcl::clock {
"Tr"\
"Kt"\
"Pn"\
- "\u0160t"]
+ "Št"]
::msgcat::mcset lt DAYS_OF_WEEK_FULL [list \
"Sekmadienis"\
"Pirmadienis"\
"Antradienis"\
- "Tre\u010diadienis"\
+ "Trečiadienis"\
"Ketvirtadienis"\
"Penktadienis"\
- "\u0160e\u0161tadienis"]
+ "Šeštadienis"]
::msgcat::mcset lt MONTHS_ABBREV [list \
"Sau"\
"Vas"\
@@ -34,15 +34,15 @@ namespace eval ::tcl::clock {
"Sausio"\
"Vasario"\
"Kovo"\
- "Baland\u017eio"\
- "Gegu\u017e\u0117s"\
- "Bir\u017eelio"\
+ "Balandžio"\
+ "Gegužės"\
+ "Birželio"\
"Liepos"\
- "Rugpj\u016b\u010dio"\
- "Rugs\u0117jo"\
+ "Rugpjūčio"\
+ "Rugsėjo"\
"Spalio"\
- "Lapkri\u010dio"\
- "Gruod\u017eio"\
+ "Lapkričio"\
+ "Gruodžio"\
""]
::msgcat::mcset lt BCE "pr.Kr."
::msgcat::mcset lt CE "po.Kr."
diff --git a/library/msgs/lv.msg b/library/msgs/lv.msg
index a037b15..730fd33 100644
--- a/library/msgs/lv.msg
+++ b/library/msgs/lv.msg
@@ -9,10 +9,10 @@ namespace eval ::tcl::clock {
"Pk"\
"S"]
::msgcat::mcset lv DAYS_OF_WEEK_FULL [list \
- "sv\u0113tdiena"\
+ "svētdiena"\
"pirmdiena"\
"otrdiena"\
- "tre\u0161diena"\
+ "trešdiena"\
"ceturdien"\
"piektdiena"\
"sestdiena"]
@@ -22,8 +22,8 @@ namespace eval ::tcl::clock {
"Mar"\
"Apr"\
"Maijs"\
- "J\u016bn"\
- "J\u016bl"\
+ "Jūn"\
+ "Jūl"\
"Aug"\
"Sep"\
"Okt"\
@@ -31,21 +31,21 @@ namespace eval ::tcl::clock {
"Dec"\
""]
::msgcat::mcset lv MONTHS_FULL [list \
- "janv\u0101ris"\
- "febru\u0101ris"\
+ "janvāris"\
+ "februāris"\
"marts"\
- "apr\u012blis"\
+ "aprīlis"\
"maijs"\
- "j\u016bnijs"\
- "j\u016blijs"\
+ "jūnijs"\
+ "jūlijs"\
"augusts"\
"septembris"\
"oktobris"\
"novembris"\
"decembris"\
""]
- ::msgcat::mcset lv BCE "pm\u0113"
- ::msgcat::mcset lv CE "m\u0113"
+ ::msgcat::mcset lv BCE "pmē"
+ ::msgcat::mcset lv CE "mē"
::msgcat::mcset lv DATE_FORMAT "%Y.%e.%m"
::msgcat::mcset lv TIME_FORMAT "%H:%M:%S"
::msgcat::mcset lv DATE_TIME_FORMAT "%Y.%e.%m %H:%M:%S %z"
diff --git a/library/msgs/mk.msg b/library/msgs/mk.msg
index 41cf60d..9b7bd9d 100644
--- a/library/msgs/mk.msg
+++ b/library/msgs/mk.msg
@@ -1,51 +1,51 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset mk DAYS_OF_WEEK_ABBREV [list \
- "\u043d\u0435\u0434."\
- "\u043f\u043e\u043d."\
- "\u0432\u0442."\
- "\u0441\u0440\u0435."\
- "\u0447\u0435\u0442."\
- "\u043f\u0435\u0442."\
- "\u0441\u0430\u0431."]
+ "нед."\
+ "пон."\
+ "вт."\
+ "сре."\
+ "чет."\
+ "пет."\
+ "саб."]
::msgcat::mcset mk DAYS_OF_WEEK_FULL [list \
- "\u043d\u0435\u0434\u0435\u043b\u0430"\
- "\u043f\u043e\u043d\u0435\u0434\u0435\u043b\u043d\u0438\u043a"\
- "\u0432\u0442\u043e\u0440\u043d\u0438\u043a"\
- "\u0441\u0440\u0435\u0434\u0430"\
- "\u0447\u0435\u0442\u0432\u0440\u0442\u043e\u043a"\
- "\u043f\u0435\u0442\u043e\u043a"\
- "\u0441\u0430\u0431\u043e\u0442\u0430"]
+ "недела"\
+ "понеделник"\
+ "вторник"\
+ "среда"\
+ "четврток"\
+ "петок"\
+ "сабота"]
::msgcat::mcset mk MONTHS_ABBREV [list \
- "\u0458\u0430\u043d."\
- "\u0444\u0435\u0432."\
- "\u043c\u0430\u0440."\
- "\u0430\u043f\u0440."\
- "\u043c\u0430\u0458."\
- "\u0458\u0443\u043d."\
- "\u0458\u0443\u043b."\
- "\u0430\u0432\u0433."\
- "\u0441\u0435\u043f\u0442."\
- "\u043e\u043a\u0442."\
- "\u043d\u043e\u0435\u043c."\
- "\u0434\u0435\u043a\u0435\u043c."\
+ "јан."\
+ "фев."\
+ "мар."\
+ "апр."\
+ "мај."\
+ "јун."\
+ "јул."\
+ "авг."\
+ "септ."\
+ "окт."\
+ "ноем."\
+ "декем."\
""]
::msgcat::mcset mk MONTHS_FULL [list \
- "\u0458\u0430\u043d\u0443\u0430\u0440\u0438"\
- "\u0444\u0435\u0432\u0440\u0443\u0430\u0440\u0438"\
- "\u043c\u0430\u0440\u0442"\
- "\u0430\u043f\u0440\u0438\u043b"\
- "\u043c\u0430\u0458"\
- "\u0458\u0443\u043d\u0438"\
- "\u0458\u0443\u043b\u0438"\
- "\u0430\u0432\u0433\u0443\u0441\u0442"\
- "\u0441\u0435\u043f\u0442\u0435\u043c\u0432\u0440\u0438"\
- "\u043e\u043a\u0442\u043e\u043c\u0432\u0440\u0438"\
- "\u043d\u043e\u0435\u043c\u0432\u0440\u0438"\
- "\u0434\u0435\u043a\u0435\u043c\u0432\u0440\u0438"\
+ "јануари"\
+ "февруари"\
+ "март"\
+ "април"\
+ "мај"\
+ "јуни"\
+ "јули"\
+ "август"\
+ "септември"\
+ "октомври"\
+ "ноември"\
+ "декември"\
""]
- ::msgcat::mcset mk BCE "\u043f\u0440.\u043d.\u0435."
- ::msgcat::mcset mk CE "\u0430\u0435."
+ ::msgcat::mcset mk BCE "пр.н.е."
+ ::msgcat::mcset mk CE "ае."
::msgcat::mcset mk DATE_FORMAT "%e.%m.%Y"
::msgcat::mcset mk TIME_FORMAT "%H:%M:%S %z"
::msgcat::mcset mk DATE_TIME_FORMAT "%e.%m.%Y %H:%M:%S %z %z"
diff --git a/library/msgs/mr.msg b/library/msgs/mr.msg
index cea427a..e475615 100644
--- a/library/msgs/mr.msg
+++ b/library/msgs/mr.msg
@@ -1,39 +1,39 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset mr DAYS_OF_WEEK_FULL [list \
- "\u0930\u0935\u093f\u0935\u093e\u0930"\
- "\u0938\u094b\u092e\u0935\u093e\u0930"\
- "\u092e\u0902\u0917\u0933\u0935\u093e\u0930"\
- "\u092e\u0902\u0917\u0933\u0935\u093e\u0930"\
- "\u0917\u0941\u0930\u0941\u0935\u093e\u0930"\
- "\u0936\u0941\u0915\u094d\u0930\u0935\u093e\u0930"\
- "\u0936\u0928\u093f\u0935\u093e\u0930"]
+ "रविवार"\
+ "सोमवार"\
+ "मंगळवार"\
+ "मंगळवार"\
+ "गुरुवार"\
+ "शुक्रवार"\
+ "शनिवार"]
::msgcat::mcset mr MONTHS_ABBREV [list \
- "\u091c\u093e\u0928\u0947\u0935\u093e\u0930\u0940"\
- "\u092b\u0947\u092c\u0943\u0935\u093e\u0930\u0940"\
- "\u092e\u093e\u0930\u094d\u091a"\
- "\u090f\u092a\u094d\u0930\u093f\u0932"\
- "\u092e\u0947"\
- "\u091c\u0942\u0928"\
- "\u091c\u0941\u0932\u0948"\
- "\u0913\u0917\u0938\u094d\u091f"\
- "\u0938\u0947\u092a\u094d\u091f\u0947\u0902\u092c\u0930"\
- "\u0913\u0915\u094d\u091f\u094b\u092c\u0930"\
- "\u0928\u094b\u0935\u094d\u0939\u0947\u0902\u092c\u0930"\
- "\u0921\u093f\u0938\u0947\u0902\u092c\u0930"]
+ "जानेवारी"\
+ "फेबृवारी"\
+ "मार्च"\
+ "एप्रिल"\
+ "मे"\
+ "जून"\
+ "जुलै"\
+ "ओगस्ट"\
+ "सेप्टेंबर"\
+ "ओक्टोबर"\
+ "नोव्हेंबर"\
+ "डिसेंबर"]
::msgcat::mcset mr MONTHS_FULL [list \
- "\u091c\u093e\u0928\u0947\u0935\u093e\u0930\u0940"\
- "\u092b\u0947\u092c\u0943\u0935\u093e\u0930\u0940"\
- "\u092e\u093e\u0930\u094d\u091a"\
- "\u090f\u092a\u094d\u0930\u093f\u0932"\
- "\u092e\u0947"\
- "\u091c\u0942\u0928"\
- "\u091c\u0941\u0932\u0948"\
- "\u0913\u0917\u0938\u094d\u091f"\
- "\u0938\u0947\u092a\u094d\u091f\u0947\u0902\u092c\u0930"\
- "\u0913\u0915\u094d\u091f\u094b\u092c\u0930"\
- "\u0928\u094b\u0935\u094d\u0939\u0947\u0902\u092c\u0930"\
- "\u0921\u093f\u0938\u0947\u0902\u092c\u0930"]
+ "जानेवारी"\
+ "फेबृवारी"\
+ "मार्च"\
+ "एप्रिल"\
+ "मे"\
+ "जून"\
+ "जुलै"\
+ "ओगस्ट"\
+ "सेप्टेंबर"\
+ "ओक्टोबर"\
+ "नोव्हेंबर"\
+ "डिसेंबर"]
::msgcat::mcset mr AM "BC"
::msgcat::mcset mr PM "AD"
}
diff --git a/library/msgs/mt.msg b/library/msgs/mt.msg
index ddd5446..c479e47 100644
--- a/library/msgs/mt.msg
+++ b/library/msgs/mt.msg
@@ -1,19 +1,19 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset mt DAYS_OF_WEEK_ABBREV [list \
- "\u0126ad"\
+ "Ħad"\
"Tne"\
"Tli"\
"Erb"\
- "\u0126am"\
- "\u0120im"]
+ "Ħam"\
+ "Ġim"]
::msgcat::mcset mt MONTHS_ABBREV [list \
"Jan"\
"Fra"\
"Mar"\
"Apr"\
"Mej"\
- "\u0120un"\
+ "Ġun"\
"Lul"\
"Awi"\
"Set"\
diff --git a/library/msgs/nb.msg b/library/msgs/nb.msg
index 90d49a3..4dd76c7 100644
--- a/library/msgs/nb.msg
+++ b/library/msgs/nb.msg
@@ -1,21 +1,21 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset nb DAYS_OF_WEEK_ABBREV [list \
- "s\u00f8"\
+ "sø"\
"ma"\
"ti"\
"on"\
"to"\
"fr"\
- "l\u00f8"]
+ "lø"]
::msgcat::mcset nb DAYS_OF_WEEK_FULL [list \
- "s\u00f8ndag"\
+ "søndag"\
"mandag"\
"tirsdag"\
"onsdag"\
"torsdag"\
"fredag"\
- "l\u00f8rdag"]
+ "lørdag"]
::msgcat::mcset nb MONTHS_ABBREV [list \
"jan"\
"feb"\
diff --git a/library/msgs/nn.msg b/library/msgs/nn.msg
index bd61ac9..b61a2dd 100644
--- a/library/msgs/nn.msg
+++ b/library/msgs/nn.msg
@@ -2,7 +2,7 @@
namespace eval ::tcl::clock {
::msgcat::mcset nn DAYS_OF_WEEK_ABBREV [list \
"su"\
- "m\u00e5"\
+ "må"\
"ty"\
"on"\
"to"\
@@ -10,7 +10,7 @@ namespace eval ::tcl::clock {
"lau"]
::msgcat::mcset nn DAYS_OF_WEEK_FULL [list \
"sundag"\
- "m\u00e5ndag"\
+ "måndag"\
"tysdag"\
"onsdag"\
"torsdag"\
diff --git a/library/msgs/pl.msg b/library/msgs/pl.msg
index d206f4b..821eea7 100644
--- a/library/msgs/pl.msg
+++ b/library/msgs/pl.msg
@@ -4,17 +4,17 @@ namespace eval ::tcl::clock {
"N"\
"Pn"\
"Wt"\
- "\u015ar"\
+ "Śr"\
"Cz"\
"Pt"\
"So"]
::msgcat::mcset pl DAYS_OF_WEEK_FULL [list \
"niedziela"\
- "poniedzia\u0142ek"\
+ "poniedziałek"\
"wtorek"\
- "\u015broda"\
+ "środa"\
"czwartek"\
- "pi\u0105tek"\
+ "piątek"\
"sobota"]
::msgcat::mcset pl MONTHS_ABBREV [list \
"sty"\
@@ -26,23 +26,23 @@ namespace eval ::tcl::clock {
"lip"\
"sie"\
"wrz"\
- "pa\u017a"\
+ "paź"\
"lis"\
"gru"\
""]
::msgcat::mcset pl MONTHS_FULL [list \
- "stycze\u0144"\
+ "styczeń"\
"luty"\
"marzec"\
- "kwiecie\u0144"\
+ "kwiecień"\
"maj"\
"czerwiec"\
"lipiec"\
- "sierpie\u0144"\
- "wrzesie\u0144"\
- "pa\u017adziernik"\
+ "sierpień"\
+ "wrzesień"\
+ "październik"\
"listopad"\
- "grudzie\u0144"\
+ "grudzień"\
""]
::msgcat::mcset pl BCE "p.n.e."
::msgcat::mcset pl CE "n.e."
diff --git a/library/msgs/pt.msg b/library/msgs/pt.msg
index 96fdb35..425c1f6 100644
--- a/library/msgs/pt.msg
+++ b/library/msgs/pt.msg
@@ -7,15 +7,15 @@ namespace eval ::tcl::clock {
"Qua"\
"Qui"\
"Sex"\
- "S\u00e1b"]
+ "Sáb"]
::msgcat::mcset pt DAYS_OF_WEEK_FULL [list \
"Domingo"\
"Segunda-feira"\
- "Ter\u00e7a-feira"\
+ "Terça-feira"\
"Quarta-feira"\
"Quinta-feira"\
"Sexta-feira"\
- "S\u00e1bado"]
+ "Sábado"]
::msgcat::mcset pt MONTHS_ABBREV [list \
"Jan"\
"Fev"\
@@ -33,7 +33,7 @@ namespace eval ::tcl::clock {
::msgcat::mcset pt MONTHS_FULL [list \
"Janeiro"\
"Fevereiro"\
- "Mar\u00e7o"\
+ "Março"\
"Abril"\
"Maio"\
"Junho"\
diff --git a/library/msgs/ro.msg b/library/msgs/ro.msg
index bdd7c61..f4452ba 100644
--- a/library/msgs/ro.msg
+++ b/library/msgs/ro.msg
@@ -9,13 +9,13 @@ namespace eval ::tcl::clock {
"V"\
"S"]
::msgcat::mcset ro DAYS_OF_WEEK_FULL [list \
- "duminic\u0103"\
+ "duminică"\
"luni"\
- "mar\u0163i"\
+ "marţi"\
"miercuri"\
"joi"\
"vineri"\
- "s\u00eemb\u0103t\u0103"]
+ "sîmbătă"]
::msgcat::mcset ro MONTHS_ABBREV [list \
"Ian"\
"Feb"\
@@ -45,7 +45,7 @@ namespace eval ::tcl::clock {
"decembrie"\
""]
::msgcat::mcset ro BCE "d.C."
- ::msgcat::mcset ro CE "\u00ee.d.C."
+ ::msgcat::mcset ro CE "î.d.C."
::msgcat::mcset ro DATE_FORMAT "%d.%m.%Y"
::msgcat::mcset ro TIME_FORMAT "%H:%M:%S"
::msgcat::mcset ro DATE_TIME_FORMAT "%d.%m.%Y %H:%M:%S %z"
diff --git a/library/msgs/ru.msg b/library/msgs/ru.msg
index 65b075d..983a253 100644
--- a/library/msgs/ru.msg
+++ b/library/msgs/ru.msg
@@ -1,51 +1,51 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset ru DAYS_OF_WEEK_ABBREV [list \
- "\u0412\u0441"\
- "\u041f\u043d"\
- "\u0412\u0442"\
- "\u0421\u0440"\
- "\u0427\u0442"\
- "\u041f\u0442"\
- "\u0421\u0431"]
+ "Вс"\
+ "Пн"\
+ "Вт"\
+ "Ср"\
+ "Чт"\
+ "Пт"\
+ "Сб"]
::msgcat::mcset ru DAYS_OF_WEEK_FULL [list \
- "\u0432\u043e\u0441\u043a\u0440\u0435\u0441\u0435\u043d\u044c\u0435"\
- "\u043f\u043e\u043d\u0435\u0434\u0435\u043b\u044c\u043d\u0438\u043a"\
- "\u0432\u0442\u043e\u0440\u043d\u0438\u043a"\
- "\u0441\u0440\u0435\u0434\u0430"\
- "\u0447\u0435\u0442\u0432\u0435\u0440\u0433"\
- "\u043f\u044f\u0442\u043d\u0438\u0446\u0430"\
- "\u0441\u0443\u0431\u0431\u043e\u0442\u0430"]
+ "воскресенье"\
+ "понедельник"\
+ "вторник"\
+ "среда"\
+ "четверг"\
+ "пятница"\
+ "суббота"]
::msgcat::mcset ru MONTHS_ABBREV [list \
- "\u044f\u043d\u0432"\
- "\u0444\u0435\u0432"\
- "\u043c\u0430\u0440"\
- "\u0430\u043f\u0440"\
- "\u043c\u0430\u0439"\
- "\u0438\u044e\u043d"\
- "\u0438\u044e\u043b"\
- "\u0430\u0432\u0433"\
- "\u0441\u0435\u043d"\
- "\u043e\u043a\u0442"\
- "\u043d\u043e\u044f"\
- "\u0434\u0435\u043a"\
+ "янв"\
+ "фев"\
+ "мар"\
+ "апр"\
+ "май"\
+ "июн"\
+ "июл"\
+ "авг"\
+ "сен"\
+ "окт"\
+ "ноя"\
+ "дек"\
""]
::msgcat::mcset ru MONTHS_FULL [list \
- "\u042f\u043d\u0432\u0430\u0440\u044c"\
- "\u0424\u0435\u0432\u0440\u0430\u043b\u044c"\
- "\u041c\u0430\u0440\u0442"\
- "\u0410\u043f\u0440\u0435\u043b\u044c"\
- "\u041c\u0430\u0439"\
- "\u0418\u044e\u043d\u044c"\
- "\u0418\u044e\u043b\u044c"\
- "\u0410\u0432\u0433\u0443\u0441\u0442"\
- "\u0421\u0435\u043d\u0442\u044f\u0431\u0440\u044c"\
- "\u041e\u043a\u0442\u044f\u0431\u0440\u044c"\
- "\u041d\u043e\u044f\u0431\u0440\u044c"\
- "\u0414\u0435\u043a\u0430\u0431\u0440\u044c"\
+ "Январь"\
+ "Февраль"\
+ "Март"\
+ "Апрель"\
+ "Май"\
+ "Июнь"\
+ "Июль"\
+ "Август"\
+ "Сентябрь"\
+ "Октябрь"\
+ "Ноябрь"\
+ "Декабрь"\
""]
- ::msgcat::mcset ru BCE "\u0434\u043e \u043d.\u044d."
- ::msgcat::mcset ru CE "\u043d.\u044d."
+ ::msgcat::mcset ru BCE "до н.э."
+ ::msgcat::mcset ru CE "н.э."
::msgcat::mcset ru DATE_FORMAT "%d.%m.%Y"
::msgcat::mcset ru TIME_FORMAT "%k:%M:%S"
::msgcat::mcset ru DATE_TIME_FORMAT "%d.%m.%Y %k:%M:%S %z"
diff --git a/library/msgs/sh.msg b/library/msgs/sh.msg
index 6ee0fc7..2e4143d 100644
--- a/library/msgs/sh.msg
+++ b/library/msgs/sh.msg
@@ -5,7 +5,7 @@ namespace eval ::tcl::clock {
"Pon"\
"Uto"\
"Sre"\
- "\u010cet"\
+ "Čet"\
"Pet"\
"Sub"]
::msgcat::mcset sh DAYS_OF_WEEK_FULL [list \
@@ -13,7 +13,7 @@ namespace eval ::tcl::clock {
"Ponedeljak"\
"Utorak"\
"Sreda"\
- "\u010cetvrtak"\
+ "Četvrtak"\
"Petak"\
"Subota"]
::msgcat::mcset sh MONTHS_ABBREV [list \
diff --git a/library/msgs/sk.msg b/library/msgs/sk.msg
index 9b2f0aa..dc6f6b6 100644
--- a/library/msgs/sk.msg
+++ b/library/msgs/sk.msg
@@ -5,15 +5,15 @@ namespace eval ::tcl::clock {
"Po"\
"Ut"\
"St"\
- "\u0160t"\
+ "Št"\
"Pa"\
"So"]
::msgcat::mcset sk DAYS_OF_WEEK_FULL [list \
- "Nede\u013ee"\
+ "Nedeľe"\
"Pondelok"\
"Utorok"\
"Streda"\
- "\u0160tvrtok"\
+ "Štvrtok"\
"Piatok"\
"Sobota"]
::msgcat::mcset sk MONTHS_ABBREV [list \
@@ -21,9 +21,9 @@ namespace eval ::tcl::clock {
"feb"\
"mar"\
"apr"\
- "m\u00e1j"\
- "j\u00fan"\
- "j\u00fal"\
+ "máj"\
+ "jún"\
+ "júl"\
"aug"\
"sep"\
"okt"\
@@ -31,16 +31,16 @@ namespace eval ::tcl::clock {
"dec"\
""]
::msgcat::mcset sk MONTHS_FULL [list \
- "janu\u00e1r"\
- "febru\u00e1r"\
+ "január"\
+ "február"\
"marec"\
- "apr\u00edl"\
- "m\u00e1j"\
- "j\u00fan"\
- "j\u00fal"\
+ "apríl"\
+ "máj"\
+ "jún"\
+ "júl"\
"august"\
"september"\
- "okt\u00f3ber"\
+ "október"\
"november"\
"december"\
""]
diff --git a/library/msgs/sl.msg b/library/msgs/sl.msg
index 42bc509..2ee0a03 100644
--- a/library/msgs/sl.msg
+++ b/library/msgs/sl.msg
@@ -5,7 +5,7 @@ namespace eval ::tcl::clock {
"Pon"\
"Tor"\
"Sre"\
- "\u010cet"\
+ "Čet"\
"Pet"\
"Sob"]
::msgcat::mcset sl DAYS_OF_WEEK_FULL [list \
@@ -13,7 +13,7 @@ namespace eval ::tcl::clock {
"Ponedeljek"\
"Torek"\
"Sreda"\
- "\u010cetrtek"\
+ "Četrtek"\
"Petek"\
"Sobota"]
::msgcat::mcset sl MONTHS_ABBREV [list \
@@ -44,7 +44,7 @@ namespace eval ::tcl::clock {
"november"\
"december"\
""]
- ::msgcat::mcset sl BCE "pr.n.\u0161."
+ ::msgcat::mcset sl BCE "pr.n.š."
::msgcat::mcset sl CE "po Kr."
::msgcat::mcset sl DATE_FORMAT "%Y.%m.%e"
::msgcat::mcset sl TIME_FORMAT "%k:%M:%S"
diff --git a/library/msgs/sq.msg b/library/msgs/sq.msg
index 8fb1fce..65da407 100644
--- a/library/msgs/sq.msg
+++ b/library/msgs/sq.msg
@@ -2,20 +2,20 @@
namespace eval ::tcl::clock {
::msgcat::mcset sq DAYS_OF_WEEK_ABBREV [list \
"Die"\
- "H\u00ebn"\
+ "Hën"\
"Mar"\
- "M\u00ebr"\
+ "Mër"\
"Enj"\
"Pre"\
"Sht"]
::msgcat::mcset sq DAYS_OF_WEEK_FULL [list \
"e diel"\
- "e h\u00ebn\u00eb"\
- "e mart\u00eb"\
- "e m\u00ebrkur\u00eb"\
+ "e hënë"\
+ "e martë"\
+ "e mërkurë"\
"e enjte"\
"e premte"\
- "e shtun\u00eb"]
+ "e shtunë"]
::msgcat::mcset sq MONTHS_ABBREV [list \
"Jan"\
"Shk"\
@@ -27,7 +27,7 @@ namespace eval ::tcl::clock {
"Gsh"\
"Sht"\
"Tet"\
- "N\u00ebn"\
+ "Nën"\
"Dhj"\
""]
::msgcat::mcset sq MONTHS_FULL [list \
@@ -41,7 +41,7 @@ namespace eval ::tcl::clock {
"gusht"\
"shtator"\
"tetor"\
- "n\u00ebntor"\
+ "nëntor"\
"dhjetor"\
""]
::msgcat::mcset sq BCE "p.e.r."
diff --git a/library/msgs/sr.msg b/library/msgs/sr.msg
index 7576668..3d84d6c 100644
--- a/library/msgs/sr.msg
+++ b/library/msgs/sr.msg
@@ -1,51 +1,51 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset sr DAYS_OF_WEEK_ABBREV [list \
- "\u041d\u0435\u0434"\
- "\u041f\u043e\u043d"\
- "\u0423\u0442\u043e"\
- "\u0421\u0440\u0435"\
- "\u0427\u0435\u0442"\
- "\u041f\u0435\u0442"\
- "\u0421\u0443\u0431"]
+ "Нед"\
+ "Пон"\
+ "Уто"\
+ "Сре"\
+ "Чет"\
+ "Пет"\
+ "Суб"]
::msgcat::mcset sr DAYS_OF_WEEK_FULL [list \
- "\u041d\u0435\u0434\u0435\u0459\u0430"\
- "\u041f\u043e\u043d\u0435\u0434\u0435\u0459\u0430\u043a"\
- "\u0423\u0442\u043e\u0440\u0430\u043a"\
- "\u0421\u0440\u0435\u0434\u0430"\
- "\u0427\u0435\u0442\u0432\u0440\u0442\u0430\u043a"\
- "\u041f\u0435\u0442\u0430\u043a"\
- "\u0421\u0443\u0431\u043e\u0442\u0430"]
+ "Недеља"\
+ "Понедељак"\
+ "Уторак"\
+ "Среда"\
+ "Четвртак"\
+ "Петак"\
+ "Субота"]
::msgcat::mcset sr MONTHS_ABBREV [list \
- "\u0408\u0430\u043d"\
- "\u0424\u0435\u0431"\
- "\u041c\u0430\u0440"\
- "\u0410\u043f\u0440"\
- "\u041c\u0430\u0458"\
- "\u0408\u0443\u043d"\
- "\u0408\u0443\u043b"\
- "\u0410\u0432\u0433"\
- "\u0421\u0435\u043f"\
- "\u041e\u043a\u0442"\
- "\u041d\u043e\u0432"\
- "\u0414\u0435\u0446"\
+ "Јан"\
+ "Феб"\
+ "Мар"\
+ "Апр"\
+ "Мај"\
+ "Јун"\
+ "Јул"\
+ "Авг"\
+ "Сеп"\
+ "Окт"\
+ "Нов"\
+ "Дец"\
""]
::msgcat::mcset sr MONTHS_FULL [list \
- "\u0408\u0430\u043d\u0443\u0430\u0440"\
- "\u0424\u0435\u0431\u0440\u0443\u0430\u0440"\
- "\u041c\u0430\u0440\u0442"\
- "\u0410\u043f\u0440\u0438\u043b"\
- "\u041c\u0430\u0458"\
- "\u0408\u0443\u043d\u0438"\
- "\u0408\u0443\u043b\u0438"\
- "\u0410\u0432\u0433\u0443\u0441\u0442"\
- "\u0421\u0435\u043f\u0442\u0435\u043c\u0431\u0430\u0440"\
- "\u041e\u043a\u0442\u043e\u0431\u0430\u0440"\
- "\u041d\u043e\u0432\u0435\u043c\u0431\u0430\u0440"\
- "\u0414\u0435\u0446\u0435\u043c\u0431\u0430\u0440"\
+ "Јануар"\
+ "Фебруар"\
+ "Март"\
+ "Април"\
+ "Мај"\
+ "Јуни"\
+ "Јули"\
+ "Август"\
+ "Септембар"\
+ "Октобар"\
+ "Новембар"\
+ "Децембар"\
""]
- ::msgcat::mcset sr BCE "\u043f. \u043d. \u0435."
- ::msgcat::mcset sr CE "\u043d. \u0435"
+ ::msgcat::mcset sr BCE "п. н. е."
+ ::msgcat::mcset sr CE "н. е"
::msgcat::mcset sr DATE_FORMAT "%Y.%m.%e"
::msgcat::mcset sr TIME_FORMAT "%k.%M.%S"
::msgcat::mcset sr DATE_TIME_FORMAT "%Y.%m.%e %k.%M.%S %z"
diff --git a/library/msgs/sv.msg b/library/msgs/sv.msg
index f7a67c6..5716092 100644
--- a/library/msgs/sv.msg
+++ b/library/msgs/sv.msg
@@ -1,21 +1,21 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset sv DAYS_OF_WEEK_ABBREV [list \
- "s\u00f6"\
- "m\u00e5"\
+ "sö"\
+ "må"\
"ti"\
"on"\
"to"\
"fr"\
- "l\u00f6"]
+ "lö"]
::msgcat::mcset sv DAYS_OF_WEEK_FULL [list \
- "s\u00f6ndag"\
- "m\u00e5ndag"\
+ "söndag"\
+ "måndag"\
"tisdag"\
"onsdag"\
"torsdag"\
"fredag"\
- "l\u00f6rdag"]
+ "lördag"]
::msgcat::mcset sv MONTHS_ABBREV [list \
"jan"\
"feb"\
diff --git a/library/msgs/ta.msg b/library/msgs/ta.msg
index 4abb90c..ea62552 100644
--- a/library/msgs/ta.msg
+++ b/library/msgs/ta.msg
@@ -1,39 +1,39 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset ta DAYS_OF_WEEK_FULL [list \
- "\u0b9e\u0bbe\u0baf\u0bbf\u0bb1\u0bc1"\
- "\u0ba4\u0bbf\u0b99\u0bcd\u0b95\u0bb3\u0bcd"\
- "\u0b9a\u0bc6\u0bb5\u0bcd\u0bb5\u0bbe\u0baf\u0bcd"\
- "\u0baa\u0bc1\u0ba4\u0ba9\u0bcd"\
- "\u0bb5\u0bbf\u0baf\u0bbe\u0bb4\u0ba9\u0bcd"\
- "\u0bb5\u0bc6\u0bb3\u0bcd\u0bb3\u0bbf"\
- "\u0b9a\u0ba9\u0bbf"]
+ "ஞாயிறு"\
+ "திங்கள்"\
+ "செவ்வாய்"\
+ "புதன்"\
+ "வியாழன்"\
+ "வெள்ளி"\
+ "சனி"]
::msgcat::mcset ta MONTHS_ABBREV [list \
- "\u0b9c\u0ba9\u0bb5\u0bb0\u0bbf"\
- "\u0baa\u0bc6\u0baa\u0bcd\u0bb0\u0bb5\u0bb0\u0bbf"\
- "\u0bae\u0bbe\u0bb0\u0bcd\u0b9a\u0bcd"\
- "\u0b8f\u0baa\u0bcd\u0bb0\u0bb2\u0bcd"\
- "\u0bae\u0bc7"\
- "\u0b9c\u0bc2\u0ba9\u0bcd"\
- "\u0b9c\u0bc2\u0bb2\u0bc8"\
- "\u0b86\u0b95\u0bb8\u0bcd\u0b9f\u0bcd"\
- "\u0b9a\u0bc6\u0baa\u0bcd\u0b9f\u0bae\u0bcd\u0baa\u0bb0\u0bcd"\
- "\u0b85\u0b95\u0bcd\u0b9f\u0bcb\u0baa\u0bb0\u0bcd"\
- "\u0ba8\u0bb5\u0bae\u0bcd\u0baa\u0bb0\u0bcd"\
- "\u0b9f\u0bbf\u0b9a\u0bae\u0bcd\u0baa\u0bb0\u0bcdr"]
+ "ஜனவரி"\
+ "பெப்ரவரி"\
+ "மார்ச்"\
+ "ஏப்ரல்"\
+ "மே"\
+ "ஜூன்"\
+ "ஜூலை"\
+ "ஆகஸ்ட்"\
+ "செப்டம்பர்"\
+ "அக்டோபர்"\
+ "நவம்பர்"\
+ "டிசம்பர்r"]
::msgcat::mcset ta MONTHS_FULL [list \
- "\u0b9c\u0ba9\u0bb5\u0bb0\u0bbf"\
- "\u0baa\u0bc6\u0baa\u0bcd\u0bb0\u0bb5\u0bb0\u0bbf"\
- "\u0bae\u0bbe\u0bb0\u0bcd\u0b9a\u0bcd"\
- "\u0b8f\u0baa\u0bcd\u0bb0\u0bb2\u0bcd"\
- "\u0bae\u0bc7"\
- "\u0b9c\u0bc2\u0ba9\u0bcd"\
- "\u0b9c\u0bc2\u0bb2\u0bc8"\
- "\u0b86\u0b95\u0bb8\u0bcd\u0b9f\u0bcd"\
- "\u0b9a\u0bc6\u0baa\u0bcd\u0b9f\u0bae\u0bcd\u0baa\u0bb0\u0bcd"\
- "\u0b85\u0b95\u0bcd\u0b9f\u0bcb\u0baa\u0bb0\u0bcd"\
- "\u0ba8\u0bb5\u0bae\u0bcd\u0baa\u0bb0\u0bcd"\
- "\u0b9f\u0bbf\u0b9a\u0bae\u0bcd\u0baa\u0bb0\u0bcdr"]
- ::msgcat::mcset ta AM "\u0b95\u0bbf\u0bae\u0bc1"
- ::msgcat::mcset ta PM "\u0b95\u0bbf\u0baa\u0bbf"
+ "ஜனவரி"\
+ "பெப்ரவரி"\
+ "மார்ச்"\
+ "ஏப்ரல்"\
+ "மே"\
+ "ஜூன்"\
+ "ஜூலை"\
+ "ஆகஸ்ட்"\
+ "செப்டம்பர்"\
+ "அக்டோபர்"\
+ "நவம்பர்"\
+ "டிசம்பர்r"]
+ ::msgcat::mcset ta AM "கிமு"
+ ::msgcat::mcset ta PM "கிபி"
}
diff --git a/library/msgs/te.msg b/library/msgs/te.msg
index 6111473..f35ece4 100644
--- a/library/msgs/te.msg
+++ b/library/msgs/te.msg
@@ -1,47 +1,47 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset te DAYS_OF_WEEK_ABBREV [list \
- "\u0c06\u0c26\u0c3f"\
- "\u0c38\u0c4b\u0c2e"\
- "\u0c2e\u0c02\u0c17\u0c33"\
- "\u0c2c\u0c41\u0c27"\
- "\u0c17\u0c41\u0c30\u0c41"\
- "\u0c36\u0c41\u0c15\u0c4d\u0c30"\
- "\u0c36\u0c28\u0c3f"]
+ "ఆది"\
+ "సోమ"\
+ "మంగళ"\
+ "బుధ"\
+ "గురు"\
+ "శుక్ర"\
+ "శని"]
::msgcat::mcset te DAYS_OF_WEEK_FULL [list \
- "\u0c06\u0c26\u0c3f\u0c35\u0c3e\u0c30\u0c02"\
- "\u0c38\u0c4b\u0c2e\u0c35\u0c3e\u0c30\u0c02"\
- "\u0c2e\u0c02\u0c17\u0c33\u0c35\u0c3e\u0c30\u0c02"\
- "\u0c2c\u0c41\u0c27\u0c35\u0c3e\u0c30\u0c02"\
- "\u0c17\u0c41\u0c30\u0c41\u0c35\u0c3e\u0c30\u0c02"\
- "\u0c36\u0c41\u0c15\u0c4d\u0c30\u0c35\u0c3e\u0c30\u0c02"\
- "\u0c36\u0c28\u0c3f\u0c35\u0c3e\u0c30\u0c02"]
+ "ఆదివారం"\
+ "సోమవారం"\
+ "మంగళవారం"\
+ "బుధవారం"\
+ "గురువారం"\
+ "శుక్రవారం"\
+ "శనివారం"]
::msgcat::mcset te MONTHS_ABBREV [list \
- "\u0c1c\u0c28\u0c35\u0c30\u0c3f"\
- "\u0c2b\u0c3f\u0c2c\u0c4d\u0c30\u0c35\u0c30\u0c3f"\
- "\u0c2e\u0c3e\u0c30\u0c4d\u0c1a\u0c3f"\
- "\u0c0f\u0c2a\u0c4d\u0c30\u0c3f\u0c32\u0c4d"\
- "\u0c2e\u0c47"\
- "\u0c1c\u0c42\u0c28\u0c4d"\
- "\u0c1c\u0c42\u0c32\u0c48"\
- "\u0c06\u0c17\u0c38\u0c4d\u0c1f\u0c41"\
- "\u0c38\u0c46\u0c2a\u0c4d\u0c1f\u0c46\u0c02\u0c2c\u0c30\u0c4d"\
- "\u0c05\u0c15\u0c4d\u0c1f\u0c4b\u0c2c\u0c30\u0c4d"\
- "\u0c28\u0c35\u0c02\u0c2c\u0c30\u0c4d"\
- "\u0c21\u0c3f\u0c38\u0c46\u0c02\u0c2c\u0c30\u0c4d"\
+ "జనవరి"\
+ "ఫిబ్రవరి"\
+ "మార్చి"\
+ "ఏప్రిల్"\
+ "మే"\
+ "జూన్"\
+ "జూలై"\
+ "ఆగస్టు"\
+ "సెప్టెంబర్"\
+ "అక్టోబర్"\
+ "నవంబర్"\
+ "డిసెంబర్"\
""]
::msgcat::mcset te MONTHS_FULL [list \
- "\u0c1c\u0c28\u0c35\u0c30\u0c3f"\
- "\u0c2b\u0c3f\u0c2c\u0c4d\u0c30\u0c35\u0c30\u0c3f"\
- "\u0c2e\u0c3e\u0c30\u0c4d\u0c1a\u0c3f"\
- "\u0c0f\u0c2a\u0c4d\u0c30\u0c3f\u0c32\u0c4d"\
- "\u0c2e\u0c47"\
- "\u0c1c\u0c42\u0c28\u0c4d"\
- "\u0c1c\u0c42\u0c32\u0c48"\
- "\u0c06\u0c17\u0c38\u0c4d\u0c1f\u0c41"\
- "\u0c38\u0c46\u0c2a\u0c4d\u0c1f\u0c46\u0c02\u0c2c\u0c30\u0c4d"\
- "\u0c05\u0c15\u0c4d\u0c1f\u0c4b\u0c2c\u0c30\u0c4d"\
- "\u0c28\u0c35\u0c02\u0c2c\u0c30\u0c4d"\
- "\u0c21\u0c3f\u0c38\u0c46\u0c02\u0c2c\u0c30\u0c4d"\
+ "జనవరి"\
+ "ఫిబ్రవరి"\
+ "మార్చి"\
+ "ఏప్రిల్"\
+ "మే"\
+ "జూన్"\
+ "జూలై"\
+ "ఆగస్టు"\
+ "సెప్టెంబర్"\
+ "అక్టోబర్"\
+ "నవంబర్"\
+ "డిసెంబర్"\
""]
}
diff --git a/library/msgs/te_in.msg b/library/msgs/te_in.msg
index 61638b5..84dd2b3 100644
--- a/library/msgs/te_in.msg
+++ b/library/msgs/te_in.msg
@@ -1,7 +1,7 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
- ::msgcat::mcset te_IN AM "\u0c2a\u0c42\u0c30\u0c4d\u0c35\u0c3e\u0c39\u0c4d\u0c28"
- ::msgcat::mcset te_IN PM "\u0c05\u0c2a\u0c30\u0c3e\u0c39\u0c4d\u0c28"
+ ::msgcat::mcset te_IN AM "పూర్వాహ్న"
+ ::msgcat::mcset te_IN PM "అపరాహ్న"
::msgcat::mcset te_IN DATE_FORMAT "%d/%m/%Y"
::msgcat::mcset te_IN TIME_FORMAT_12 "%I:%M:%S %P"
::msgcat::mcset te_IN DATE_TIME_FORMAT "%d/%m/%Y %I:%M:%S %P %z"
diff --git a/library/msgs/th.msg b/library/msgs/th.msg
index 7486c35..edaa149 100644
--- a/library/msgs/th.msg
+++ b/library/msgs/th.msg
@@ -1,53 +1,53 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset th DAYS_OF_WEEK_ABBREV [list \
- "\u0e2d\u0e32."\
- "\u0e08."\
- "\u0e2d."\
- "\u0e1e."\
- "\u0e1e\u0e24."\
- "\u0e28."\
- "\u0e2a."]
+ "อา."\
+ "จ."\
+ "อ."\
+ "พ."\
+ "พฤ."\
+ "ศ."\
+ "ส."]
::msgcat::mcset th DAYS_OF_WEEK_FULL [list \
- "\u0e27\u0e31\u0e19\u0e2d\u0e32\u0e17\u0e34\u0e15\u0e22\u0e4c"\
- "\u0e27\u0e31\u0e19\u0e08\u0e31\u0e19\u0e17\u0e23\u0e4c"\
- "\u0e27\u0e31\u0e19\u0e2d\u0e31\u0e07\u0e04\u0e32\u0e23"\
- "\u0e27\u0e31\u0e19\u0e1e\u0e38\u0e18"\
- "\u0e27\u0e31\u0e19\u0e1e\u0e24\u0e2b\u0e31\u0e2a\u0e1a\u0e14\u0e35"\
- "\u0e27\u0e31\u0e19\u0e28\u0e38\u0e01\u0e23\u0e4c"\
- "\u0e27\u0e31\u0e19\u0e40\u0e2a\u0e32\u0e23\u0e4c"]
+ "วันอาทิตย์"\
+ "วันจันทร์"\
+ "วันอังคาร"\
+ "วันพุธ"\
+ "วันพฤหัสบดี"\
+ "วันศุกร์"\
+ "วันเสาร์"]
::msgcat::mcset th MONTHS_ABBREV [list \
- "\u0e21.\u0e04."\
- "\u0e01.\u0e1e."\
- "\u0e21\u0e35.\u0e04."\
- "\u0e40\u0e21.\u0e22."\
- "\u0e1e.\u0e04."\
- "\u0e21\u0e34.\u0e22."\
- "\u0e01.\u0e04."\
- "\u0e2a.\u0e04."\
- "\u0e01.\u0e22."\
- "\u0e15.\u0e04."\
- "\u0e1e.\u0e22."\
- "\u0e18.\u0e04."\
+ "ม.ค."\
+ "ก.พ."\
+ "มี.ค."\
+ "เม.ย."\
+ "พ.ค."\
+ "มิ.ย."\
+ "ก.ค."\
+ "ส.ค."\
+ "ก.ย."\
+ "ต.ค."\
+ "พ.ย."\
+ "ธ.ค."\
""]
::msgcat::mcset th MONTHS_FULL [list \
- "\u0e21\u0e01\u0e23\u0e32\u0e04\u0e21"\
- "\u0e01\u0e38\u0e21\u0e20\u0e32\u0e1e\u0e31\u0e19\u0e18\u0e4c"\
- "\u0e21\u0e35\u0e19\u0e32\u0e04\u0e21"\
- "\u0e40\u0e21\u0e29\u0e32\u0e22\u0e19"\
- "\u0e1e\u0e24\u0e29\u0e20\u0e32\u0e04\u0e21"\
- "\u0e21\u0e34\u0e16\u0e38\u0e19\u0e32\u0e22\u0e19"\
- "\u0e01\u0e23\u0e01\u0e0e\u0e32\u0e04\u0e21"\
- "\u0e2a\u0e34\u0e07\u0e2b\u0e32\u0e04\u0e21"\
- "\u0e01\u0e31\u0e19\u0e22\u0e32\u0e22\u0e19"\
- "\u0e15\u0e38\u0e25\u0e32\u0e04\u0e21"\
- "\u0e1e\u0e24\u0e28\u0e08\u0e34\u0e01\u0e32\u0e22\u0e19"\
- "\u0e18\u0e31\u0e19\u0e27\u0e32\u0e04\u0e21"\
+ "มกราคม"\
+ "กุมภาพันธ์"\
+ "มีนาคม"\
+ "เมษายน"\
+ "พฤษภาคม"\
+ "มิถุนายน"\
+ "กรกฎาคม"\
+ "สิงหาคม"\
+ "กันยายน"\
+ "ตุลาคม"\
+ "พฤศจิกายน"\
+ "ธันวาคม"\
""]
- ::msgcat::mcset th BCE "\u0e25\u0e17\u0e35\u0e48"
- ::msgcat::mcset th CE "\u0e04.\u0e28."
- ::msgcat::mcset th AM "\u0e01\u0e48\u0e2d\u0e19\u0e40\u0e17\u0e35\u0e48\u0e22\u0e07"
- ::msgcat::mcset th PM "\u0e2b\u0e25\u0e31\u0e07\u0e40\u0e17\u0e35\u0e48\u0e22\u0e07"
+ ::msgcat::mcset th BCE "ลที่"
+ ::msgcat::mcset th CE "ค.ศ."
+ ::msgcat::mcset th AM "ก่อนเที่ยง"
+ ::msgcat::mcset th PM "หลังเที่ยง"
::msgcat::mcset th DATE_FORMAT "%e/%m/%Y"
::msgcat::mcset th TIME_FORMAT "%k:%M:%S"
::msgcat::mcset th DATE_TIME_FORMAT "%e/%m/%Y %k:%M:%S %z"
diff --git a/library/msgs/tr.msg b/library/msgs/tr.msg
index 7b2ecf9..12869ee 100644
--- a/library/msgs/tr.msg
+++ b/library/msgs/tr.msg
@@ -4,27 +4,27 @@ namespace eval ::tcl::clock {
"Paz"\
"Pzt"\
"Sal"\
- "\u00c7ar"\
+ "Çar"\
"Per"\
"Cum"\
"Cmt"]
::msgcat::mcset tr DAYS_OF_WEEK_FULL [list \
"Pazar"\
"Pazartesi"\
- "Sal\u0131"\
- "\u00c7ar\u015famba"\
- "Per\u015fembe"\
+ "Salı"\
+ "Çarşamba"\
+ "Perşembe"\
"Cuma"\
"Cumartesi"]
::msgcat::mcset tr MONTHS_ABBREV [list \
"Oca"\
- "\u015eub"\
+ "Şub"\
"Mar"\
"Nis"\
"May"\
"Haz"\
"Tem"\
- "A\u011fu"\
+ "Ağu"\
"Eyl"\
"Eki"\
"Kas"\
@@ -32,17 +32,17 @@ namespace eval ::tcl::clock {
""]
::msgcat::mcset tr MONTHS_FULL [list \
"Ocak"\
- "\u015eubat"\
+ "Şubat"\
"Mart"\
"Nisan"\
- "May\u0131s"\
+ "Mayıs"\
"Haziran"\
"Temmuz"\
- "A\u011fustos"\
- "Eyl\u00fcl"\
+ "Ağustos"\
+ "Eylül"\
"Ekim"\
- "Kas\u0131m"\
- "Aral\u0131k"\
+ "Kasım"\
+ "Aralık"\
""]
::msgcat::mcset tr DATE_FORMAT "%d.%m.%Y"
::msgcat::mcset tr TIME_FORMAT "%H:%M:%S"
diff --git a/library/msgs/uk.msg b/library/msgs/uk.msg
index 7d4c64a..42eb095 100644
--- a/library/msgs/uk.msg
+++ b/library/msgs/uk.msg
@@ -1,51 +1,51 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset uk DAYS_OF_WEEK_ABBREV [list \
- "\u043d\u0434"\
- "\u043f\u043d"\
- "\u0432\u0442"\
- "\u0441\u0440"\
- "\u0447\u0442"\
- "\u043f\u0442"\
- "\u0441\u0431"]
+ "нд"\
+ "пн"\
+ "вт"\
+ "ср"\
+ "чт"\
+ "пт"\
+ "сб"]
::msgcat::mcset uk DAYS_OF_WEEK_FULL [list \
- "\u043d\u0435\u0434\u0456\u043b\u044f"\
- "\u043f\u043e\u043d\u0435\u0434\u0456\u043b\u043e\u043a"\
- "\u0432\u0456\u0432\u0442\u043e\u0440\u043e\u043a"\
- "\u0441\u0435\u0440\u0435\u0434\u0430"\
- "\u0447\u0435\u0442\u0432\u0435\u0440"\
- "\u043f'\u044f\u0442\u043d\u0438\u0446\u044f"\
- "\u0441\u0443\u0431\u043e\u0442\u0430"]
+ "неділя"\
+ "понеділок"\
+ "вівторок"\
+ "середа"\
+ "четвер"\
+ "п'ятниця"\
+ "субота"]
::msgcat::mcset uk MONTHS_ABBREV [list \
- "\u0441\u0456\u0447"\
- "\u043b\u044e\u0442"\
- "\u0431\u0435\u0440"\
- "\u043a\u0432\u0456\u0442"\
- "\u0442\u0440\u0430\u0432"\
- "\u0447\u0435\u0440\u0432"\
- "\u043b\u0438\u043f"\
- "\u0441\u0435\u0440\u043f"\
- "\u0432\u0435\u0440"\
- "\u0436\u043e\u0432\u0442"\
- "\u043b\u0438\u0441\u0442"\
- "\u0433\u0440\u0443\u0434"\
+ "січ"\
+ "лют"\
+ "бер"\
+ "квіт"\
+ "трав"\
+ "черв"\
+ "лип"\
+ "серп"\
+ "вер"\
+ "жовт"\
+ "лист"\
+ "груд"\
""]
::msgcat::mcset uk MONTHS_FULL [list \
- "\u0441\u0456\u0447\u043d\u044f"\
- "\u043b\u044e\u0442\u043e\u0433\u043e"\
- "\u0431\u0435\u0440\u0435\u0437\u043d\u044f"\
- "\u043a\u0432\u0456\u0442\u043d\u044f"\
- "\u0442\u0440\u0430\u0432\u043d\u044f"\
- "\u0447\u0435\u0440\u0432\u043d\u044f"\
- "\u043b\u0438\u043f\u043d\u044f"\
- "\u0441\u0435\u0440\u043f\u043d\u044f"\
- "\u0432\u0435\u0440\u0435\u0441\u043d\u044f"\
- "\u0436\u043e\u0432\u0442\u043d\u044f"\
- "\u043b\u0438\u0441\u0442\u043e\u043f\u0430\u0434\u0430"\
- "\u0433\u0440\u0443\u0434\u043d\u044f"\
+ "січня"\
+ "лютого"\
+ "березня"\
+ "квітня"\
+ "травня"\
+ "червня"\
+ "липня"\
+ "серпня"\
+ "вересня"\
+ "жовтня"\
+ "листопада"\
+ "грудня"\
""]
- ::msgcat::mcset uk BCE "\u0434\u043e \u043d.\u0435."
- ::msgcat::mcset uk CE "\u043f\u0456\u0441\u043b\u044f \u043d.\u0435."
+ ::msgcat::mcset uk BCE "до н.е."
+ ::msgcat::mcset uk CE "після н.е."
::msgcat::mcset uk DATE_FORMAT "%e/%m/%Y"
::msgcat::mcset uk TIME_FORMAT "%k:%M:%S"
::msgcat::mcset uk DATE_TIME_FORMAT "%e/%m/%Y %k:%M:%S %z"
diff --git a/library/msgs/vi.msg b/library/msgs/vi.msg
index c98b2a6..3437ebf 100644
--- a/library/msgs/vi.msg
+++ b/library/msgs/vi.msg
@@ -9,13 +9,13 @@ namespace eval ::tcl::clock {
"Th 7"\
"CN"]
::msgcat::mcset vi DAYS_OF_WEEK_FULL [list \
- "Th\u01b0\u0301 hai"\
- "Th\u01b0\u0301 ba"\
- "Th\u01b0\u0301 t\u01b0"\
- "Th\u01b0\u0301 n\u0103m"\
- "Th\u01b0\u0301 s\u00e1u"\
- "Th\u01b0\u0301 ba\u0309y"\
- "Chu\u0309 nh\u00e2\u0323t"]
+ "Thứ hai"\
+ "Thứ ba"\
+ "Thứ tư"\
+ "Thứ năm"\
+ "Thứ sáu"\
+ "Thứ bảy"\
+ "Chủ nhật"]
::msgcat::mcset vi MONTHS_ABBREV [list \
"Thg 1"\
"Thg 2"\
@@ -31,18 +31,18 @@ namespace eval ::tcl::clock {
"Thg 12"\
""]
::msgcat::mcset vi MONTHS_FULL [list \
- "Th\u00e1ng m\u00f4\u0323t"\
- "Th\u00e1ng hai"\
- "Th\u00e1ng ba"\
- "Th\u00e1ng t\u01b0"\
- "Th\u00e1ng n\u0103m"\
- "Th\u00e1ng s\u00e1u"\
- "Th\u00e1ng ba\u0309y"\
- "Th\u00e1ng t\u00e1m"\
- "Th\u00e1ng ch\u00edn"\
- "Th\u00e1ng m\u01b0\u01a1\u0300i"\
- "Th\u00e1ng m\u01b0\u01a1\u0300i m\u00f4\u0323t"\
- "Th\u00e1ng m\u01b0\u01a1\u0300i hai"\
+ "Tháng một"\
+ "Tháng hai"\
+ "Tháng ba"\
+ "Tháng tư"\
+ "Tháng năm"\
+ "Tháng sáu"\
+ "Tháng bảy"\
+ "Tháng tám"\
+ "Tháng chín"\
+ "Tháng mười"\
+ "Tháng mười một"\
+ "Tháng mười hai"\
""]
::msgcat::mcset vi DATE_FORMAT "%d %b %Y"
::msgcat::mcset vi TIME_FORMAT "%H:%M:%S"
diff --git a/library/msgs/zh.msg b/library/msgs/zh.msg
index b799a32..9c1d08b 100644
--- a/library/msgs/zh.msg
+++ b/library/msgs/zh.msg
@@ -1,55 +1,55 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset zh DAYS_OF_WEEK_ABBREV [list \
- "\u661f\u671f\u65e5"\
- "\u661f\u671f\u4e00"\
- "\u661f\u671f\u4e8c"\
- "\u661f\u671f\u4e09"\
- "\u661f\u671f\u56db"\
- "\u661f\u671f\u4e94"\
- "\u661f\u671f\u516d"]
+ "星期日"\
+ "星期一"\
+ "星期二"\
+ "星期三"\
+ "星期四"\
+ "星期五"\
+ "星期六"]
::msgcat::mcset zh DAYS_OF_WEEK_FULL [list \
- "\u661f\u671f\u65e5"\
- "\u661f\u671f\u4e00"\
- "\u661f\u671f\u4e8c"\
- "\u661f\u671f\u4e09"\
- "\u661f\u671f\u56db"\
- "\u661f\u671f\u4e94"\
- "\u661f\u671f\u516d"]
+ "星期日"\
+ "星期一"\
+ "星期二"\
+ "星期三"\
+ "星期四"\
+ "星期五"\
+ "星期六"]
::msgcat::mcset zh MONTHS_ABBREV [list \
- "\u4e00\u6708"\
- "\u4e8c\u6708"\
- "\u4e09\u6708"\
- "\u56db\u6708"\
- "\u4e94\u6708"\
- "\u516d\u6708"\
- "\u4e03\u6708"\
- "\u516b\u6708"\
- "\u4e5d\u6708"\
- "\u5341\u6708"\
- "\u5341\u4e00\u6708"\
- "\u5341\u4e8c\u6708"\
+ "一月"\
+ "二月"\
+ "三月"\
+ "四月"\
+ "五月"\
+ "六月"\
+ "七月"\
+ "八月"\
+ "九月"\
+ "十月"\
+ "十一月"\
+ "十二月"\
""]
::msgcat::mcset zh MONTHS_FULL [list \
- "\u4e00\u6708"\
- "\u4e8c\u6708"\
- "\u4e09\u6708"\
- "\u56db\u6708"\
- "\u4e94\u6708"\
- "\u516d\u6708"\
- "\u4e03\u6708"\
- "\u516b\u6708"\
- "\u4e5d\u6708"\
- "\u5341\u6708"\
- "\u5341\u4e00\u6708"\
- "\u5341\u4e8c\u6708"\
+ "一月"\
+ "二月"\
+ "三月"\
+ "四月"\
+ "五月"\
+ "六月"\
+ "七月"\
+ "八月"\
+ "九月"\
+ "十月"\
+ "十一月"\
+ "十二月"\
""]
- ::msgcat::mcset zh BCE "\u516c\u5143\u524d"
- ::msgcat::mcset zh CE "\u516c\u5143"
- ::msgcat::mcset zh AM "\u4e0a\u5348"
- ::msgcat::mcset zh PM "\u4e0b\u5348"
- ::msgcat::mcset zh LOCALE_NUMERALS "\u3007 \u4e00 \u4e8c \u4e09 \u56db \u4e94 \u516d \u4e03 \u516b \u4e5d \u5341 \u5341\u4e00 \u5341\u4e8c \u5341\u4e09 \u5341\u56db \u5341\u4e94 \u5341\u516d \u5341\u4e03 \u5341\u516b \u5341\u4e5d \u4e8c\u5341 \u5eff\u4e00 \u5eff\u4e8c \u5eff\u4e09 \u5eff\u56db \u5eff\u4e94 \u5eff\u516d \u5eff\u4e03 \u5eff\u516b \u5eff\u4e5d \u4e09\u5341 \u5345\u4e00 \u5345\u4e8c \u5345\u4e09 \u5345\u56db \u5345\u4e94 \u5345\u516d \u5345\u4e03 \u5345\u516b \u5345\u4e5d \u56db\u5341 \u56db\u5341\u4e00 \u56db\u5341\u4e8c \u56db\u5341\u4e09 \u56db\u5341\u56db \u56db\u5341\u4e94 \u56db\u5341\u516d \u56db\u5341\u4e03 \u56db\u5341\u516b \u56db\u5341\u4e5d \u4e94\u5341 \u4e94\u5341\u4e00 \u4e94\u5341\u4e8c \u4e94\u5341\u4e09 \u4e94\u5341\u56db \u4e94\u5341\u4e94 \u4e94\u5341\u516d \u4e94\u5341\u4e03 \u4e94\u5341\u516b \u4e94\u5341\u4e5d \u516d\u5341 \u516d\u5341\u4e00 \u516d\u5341\u4e8c \u516d\u5341\u4e09 \u516d\u5341\u56db \u516d\u5341\u4e94 \u516d\u5341\u516d \u516d\u5341\u4e03 \u516d\u5341\u516b \u516d\u5341\u4e5d \u4e03\u5341 \u4e03\u5341\u4e00 \u4e03\u5341\u4e8c \u4e03\u5341\u4e09 \u4e03\u5341\u56db \u4e03\u5341\u4e94 \u4e03\u5341\u516d \u4e03\u5341\u4e03 \u4e03\u5341\u516b \u4e03\u5341\u4e5d \u516b\u5341 \u516b\u5341\u4e00 \u516b\u5341\u4e8c \u516b\u5341\u4e09 \u516b\u5341\u56db \u516b\u5341\u4e94 \u516b\u5341\u516d \u516b\u5341\u4e03 \u516b\u5341\u516b \u516b\u5341\u4e5d \u4e5d\u5341 \u4e5d\u5341\u4e00 \u4e5d\u5341\u4e8c \u4e5d\u5341\u4e09 \u4e5d\u5341\u56db \u4e5d\u5341\u4e94 \u4e5d\u5341\u516d \u4e5d\u5341\u4e03 \u4e5d\u5341\u516b \u4e5d\u5341\u4e5d"
- ::msgcat::mcset zh LOCALE_DATE_FORMAT "\u516c\u5143%Y\u5e74%B%Od\u65e5"
- ::msgcat::mcset zh LOCALE_TIME_FORMAT "%OH\u65f6%OM\u5206%OS\u79d2"
- ::msgcat::mcset zh LOCALE_DATE_TIME_FORMAT "%A %Y\u5e74%B%Od\u65e5%OH\u65f6%OM\u5206%OS\u79d2 %z"
+ ::msgcat::mcset zh BCE "公元前"
+ ::msgcat::mcset zh CE "公元"
+ ::msgcat::mcset zh AM "上午"
+ ::msgcat::mcset zh PM "下午"
+ ::msgcat::mcset zh LOCALE_NUMERALS "〇 一 二 三 四 五 六 七 八 九 十 十一 十二 十三 十四 十五 十六 十七 十八 十九 二十 廿一 廿二 廿三 廿四 廿五 廿六 廿七 廿八 廿九 三十 卅一 卅二 卅三 卅四 卅五 卅六 卅七 卅八 卅九 四十 四十一 四十二 四十三 四十四 四十五 四十六 四十七 四十八 四十九 五十 五十一 五十二 五十三 五十四 五十五 五十六 五十七 五十八 五十九 六十 六十一 六十二 六十三 六十四 六十五 六十六 六十七 六十八 六十九 七十 七十一 七十二 七十三 七十四 七十五 七十六 七十七 七十八 七十九 八十 八十一 八十二 八十三 八十四 八十五 八十六 八十七 八十八 八十九 九十 九十一 九十二 九十三 九十四 九十五 九十六 九十七 九十八 九十九"
+ ::msgcat::mcset zh LOCALE_DATE_FORMAT "公元%Y年%B%Od日"
+ ::msgcat::mcset zh LOCALE_TIME_FORMAT "%OH时%OM分%OS秒"
+ ::msgcat::mcset zh LOCALE_DATE_TIME_FORMAT "%A %Y年%B%Od日%OH时%OM分%OS秒 %z"
}
diff --git a/library/msgs/zh_cn.msg b/library/msgs/zh_cn.msg
index d62ce77..da2869a 100644
--- a/library/msgs/zh_cn.msg
+++ b/library/msgs/zh_cn.msg
@@ -2,6 +2,6 @@
namespace eval ::tcl::clock {
::msgcat::mcset zh_CN DATE_FORMAT "%Y-%m-%e"
::msgcat::mcset zh_CN TIME_FORMAT "%k:%M:%S"
- ::msgcat::mcset zh_CN TIME_FORMAT_12 "%P%I\u65f6%M\u5206%S\u79d2"
+ ::msgcat::mcset zh_CN TIME_FORMAT_12 "%P%I时%M分%S秒"
::msgcat::mcset zh_CN DATE_TIME_FORMAT "%Y-%m-%e %k:%M:%S %z"
}
diff --git a/library/msgs/zh_hk.msg b/library/msgs/zh_hk.msg
index badb1dd..7f1b181 100644
--- a/library/msgs/zh_hk.msg
+++ b/library/msgs/zh_hk.msg
@@ -1,28 +1,28 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
::msgcat::mcset zh_HK DAYS_OF_WEEK_ABBREV [list \
- "\u65e5"\
- "\u4e00"\
- "\u4e8c"\
- "\u4e09"\
- "\u56db"\
- "\u4e94"\
- "\u516d"]
+ "日"\
+ "一"\
+ "二"\
+ "三"\
+ "四"\
+ "五"\
+ "六"]
::msgcat::mcset zh_HK MONTHS_ABBREV [list \
- "1\u6708"\
- "2\u6708"\
- "3\u6708"\
- "4\u6708"\
- "5\u6708"\
- "6\u6708"\
- "7\u6708"\
- "8\u6708"\
- "9\u6708"\
- "10\u6708"\
- "11\u6708"\
- "12\u6708"\
+ "1月"\
+ "2月"\
+ "3月"\
+ "4月"\
+ "5月"\
+ "6月"\
+ "7月"\
+ "8月"\
+ "9月"\
+ "10月"\
+ "11月"\
+ "12月"\
""]
- ::msgcat::mcset zh_HK DATE_FORMAT "%Y\u5e74%m\u6708%e\u65e5"
+ ::msgcat::mcset zh_HK DATE_FORMAT "%Y年%m月%e日"
::msgcat::mcset zh_HK TIME_FORMAT_12 "%P%I:%M:%S"
- ::msgcat::mcset zh_HK DATE_TIME_FORMAT "%Y\u5e74%m\u6708%e\u65e5 %P%I:%M:%S %z"
+ ::msgcat::mcset zh_HK DATE_TIME_FORMAT "%Y年%m月%e日 %P%I:%M:%S %z"
}
diff --git a/library/msgs/zh_sg.msg b/library/msgs/zh_sg.msg
index a2f3e39..690edf7 100644
--- a/library/msgs/zh_sg.msg
+++ b/library/msgs/zh_sg.msg
@@ -1,7 +1,7 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
- ::msgcat::mcset zh_SG AM "\u4e0a\u5348"
- ::msgcat::mcset zh_SG PM "\u4e2d\u5348"
+ ::msgcat::mcset zh_SG AM "上午"
+ ::msgcat::mcset zh_SG PM "中午"
::msgcat::mcset zh_SG DATE_FORMAT "%d %B %Y"
::msgcat::mcset zh_SG TIME_FORMAT_12 "%P %I:%M:%S"
::msgcat::mcset zh_SG DATE_TIME_FORMAT "%d %B %Y %P %I:%M:%S %z"
diff --git a/library/msgs/zh_tw.msg b/library/msgs/zh_tw.msg
index e0796b1..17a6dd7 100644
--- a/library/msgs/zh_tw.msg
+++ b/library/msgs/zh_tw.msg
@@ -1,7 +1,7 @@
# created by tools/loadICU.tcl -- do not edit
namespace eval ::tcl::clock {
- ::msgcat::mcset zh_TW BCE "\u6c11\u570b\u524d"
- ::msgcat::mcset zh_TW CE "\u6c11\u570b"
+ ::msgcat::mcset zh_TW BCE "民國前"
+ ::msgcat::mcset zh_TW CE "民國"
::msgcat::mcset zh_TW DATE_FORMAT "%Y/%m/%e"
::msgcat::mcset zh_TW TIME_FORMAT_12 "%P %I:%M:%S"
::msgcat::mcset zh_TW DATE_TIME_FORMAT "%Y/%m/%e %P %I:%M:%S %z"
diff --git a/library/opt/optparse.tcl b/library/opt/optparse.tcl
index 1aec83b..3e21511 100644
--- a/library/opt/optparse.tcl
+++ b/library/opt/optparse.tcl
@@ -17,10 +17,10 @@ namespace eval ::tcl {
# Exported APIs
namespace export OptKeyRegister OptKeyDelete OptKeyError OptKeyParse \
- OptProc OptProcArgGiven OptParse \
- Lempty Lget \
- Lassign Lvarpop Lvarpop1 Lvarset Lvarincr \
- SetMax SetMin
+ OptProc OptProcArgGiven OptParse \
+ Lempty Lget \
+ Lassign Lvarpop Lvarpop1 Lvarset Lvarincr \
+ SetMax SetMin
################# Example of use / 'user documentation' ###################
@@ -38,28 +38,28 @@ namespace eval ::tcl {
# ::tcl::OptParseTest save -4 -pr 23 -libsok SybTcl\
# -nostatics false ch1
OptProc OptParseTest {
- {subcommand -choice {save print} "sub command"}
- {arg1 3 "some number"}
- {-aflag}
- {-intflag 7}
- {-weirdflag "help string"}
- {-noStatics "Not ok to load static packages"}
- {-nestedloading1 true "OK to load into nested children"}
- {-nestedloading2 -boolean true "OK to load into nested children"}
- {-libsOK -choice {Tk SybTcl}
- "List of packages that can be loaded"}
- {-precision -int 12 "Number of digits of precision"}
- {-intval 7 "An integer"}
- {-scale -float 1.0 "Scale factor"}
- {-zoom 1.0 "Zoom factor"}
- {-arbitrary foobar "Arbitrary string"}
- {-random -string 12 "Random string"}
- {-listval -list {} "List value"}
- {-blahflag -blah abc "Funny type"}
+ {subcommand -choice {save print} "sub command"}
+ {arg1 3 "some number"}
+ {-aflag}
+ {-intflag 7}
+ {-weirdflag "help string"}
+ {-noStatics "Not ok to load static packages"}
+ {-nestedloading1 true "OK to load into nested children"}
+ {-nestedloading2 -boolean true "OK to load into nested children"}
+ {-libsOK -choice {Tk SybTcl}
+ "List of packages that can be loaded"}
+ {-precision -int 12 "Number of digits of precision"}
+ {-intval 7 "An integer"}
+ {-scale -float 1.0 "Scale factor"}
+ {-zoom 1.0 "Zoom factor"}
+ {-arbitrary foobar "Arbitrary string"}
+ {-random -string 12 "Random string"}
+ {-listval -list {} "List value"}
+ {-blahflag -blah abc "Funny type"}
{arg2 -boolean "a boolean"}
{arg3 -choice "ch1 ch2"}
{?optarg? -list {} "optional argument"}
- } {
+ } {
foreach v [info locals] {
puts stderr [format "%14s : %s" $v [set $v]]
}
@@ -146,10 +146,10 @@ proc ::tcl::OptKeyRegister {desc {key ""}} {
variable OptDesc
variable OptDescN
if {[string equal $key ""]} {
- # in case a key given to us as a parameter was a number
- while {[info exists OptDesc($OptDescN)]} {incr OptDescN}
- set key $OptDescN
- incr OptDescN
+ # in case a key given to us as a parameter was a number
+ while {[info exists OptDesc($OptDescN)]} {incr OptDescN}
+ set key $OptDescN
+ incr OptDescN
}
# program counter
set program [list [list "P" 1]]
@@ -167,31 +167,31 @@ proc ::tcl::OptKeyRegister {desc {key ""}} {
# more items after 'args'...
return -code error "'args' special argument must be the last one"
}
- set res [OptNormalizeOne $item]
- set state [lindex $res 0]
- if {$inflags} {
- if {$state == "flags"} {
+ set res [OptNormalizeOne $item]
+ set state [lindex $res 0]
+ if {$inflags} {
+ if {$state == "flags"} {
# add to 'subprogram'
- lappend flagsprg $res
- } else {
- # put in the flags
- # structure for flag programs items is a list of
- # {subprgcounter {prg flag 1} {prg flag 2} {...}}
- lappend program $flagsprg
- # put the other regular stuff
- lappend program $res
+ lappend flagsprg $res
+ } else {
+ # put in the flags
+ # structure for flag programs items is a list of
+ # {subprgcounter {prg flag 1} {prg flag 2} {...}}
+ lappend program $flagsprg
+ # put the other regular stuff
+ lappend program $res
set inflags 0
set empty 0
- }
- } else {
- if {$state == "flags"} {
- set inflags 1
- # sub program counter + first sub program
- set flagsprg [list [list "P" 1] $res]
- } else {
- lappend program $res
- set empty 0
- }
+ }
+ } else {
+ if {$state == "flags"} {
+ set inflags 1
+ # sub program counter + first sub program
+ set flagsprg [list [list "P" 1] $res]
+ } else {
+ lappend program $res
+ set empty 0
+ }
}
}
if {$inflags} {
@@ -219,11 +219,11 @@ proc ::tcl::OptKeyDelete {key} {
# Get the parsed description stored under the given key.
proc OptKeyGetDesc {descKey} {
- variable OptDesc
- if {![info exists OptDesc($descKey)]} {
- return -code error "Unknown option description key \"$descKey\""
- }
- set OptDesc($descKey)
+ variable OptDesc
+ if {![info exists OptDesc($descKey)]} {
+ return -code error "Unknown option description key \"$descKey\""
+ }
+ set OptDesc($descKey)
}
# Parse entry point for people who don't want to register with a key,
@@ -248,11 +248,11 @@ proc ::tcl::OptParse {desc arglist} {
proc ::tcl::OptProc {name desc body} {
set namespace [uplevel 1 [list ::namespace current]]
if {[string match "::*" $name] || [string equal $namespace "::"]} {
- # absolute name or global namespace, name is the key
- set key $name
+ # absolute name or global namespace, name is the key
+ set key $name
} else {
- # we are relative to some non top level namespace:
- set key "${namespace}::${name}"
+ # we are relative to some non top level namespace:
+ set key "${namespace}::${name}"
}
OptKeyRegister $desc $key
uplevel 1 [list ::proc $name args "set Args \[::tcl::OptKeyParse $key \$args\]\n$body"]
@@ -300,21 +300,21 @@ proc ::tcl::OptProcArgGiven {argname} {
# Advance to next description
proc OptNextDesc {descName} {
- uplevel 1 [list Lvarincr $descName {0 1}]
+ uplevel 1 [list Lvarincr $descName {0 1}]
}
# Get the current description, eventually descend
proc OptCurDesc {descriptions} {
- lindex $descriptions [OptGetPrgCounter $descriptions]
+ lindex $descriptions [OptGetPrgCounter $descriptions]
}
# get the current description, eventually descend
# through sub programs as needed.
proc OptCurDescFinal {descriptions} {
- set item [OptCurDesc $descriptions]
+ set item [OptCurDesc $descriptions]
# Descend untill we get the actual item and not a sub program
- while {[OptIsPrg $item]} {
- set item [OptCurDesc $item]
- }
+ while {[OptIsPrg $item]} {
+ set item [OptCurDesc $item]
+ }
return $item
}
# Current final instruction adress
@@ -332,7 +332,7 @@ proc ::tcl::OptProcArgGiven {argname} {
proc OptCurSetValue {descriptionsName value} {
upvar $descriptionsName descriptions
# Get the current item full address.
- set adress [OptCurAddr $descriptions]
+ set adress [OptCurAddr $descriptions]
# Use the 3rd field of the item (see OptValue / OptNewInst).
lappend adress 2
Lvarset descriptions $adress [list 1 $value]
@@ -341,12 +341,12 @@ proc ::tcl::OptProcArgGiven {argname} {
# Empty state means done/paste the end of the program.
proc OptState {item} {
- lindex $item 0
+ lindex $item 0
}
# current state
proc OptCurState {descriptions} {
- OptState [OptCurDesc $descriptions]
+ OptState [OptCurDesc $descriptions]
}
#######
@@ -354,11 +354,11 @@ proc ::tcl::OptProcArgGiven {argname} {
# Returns the argument that has to be processed now.
proc OptCurrentArg {lst} {
- lindex $lst 0
+ lindex $lst 0
}
# Advance to next argument.
proc OptNextArg {argsName} {
- uplevel 1 [list Lvarpop1 $argsName]
+ uplevel 1 [list Lvarpop1 $argsName]
}
#######
@@ -377,7 +377,7 @@ proc ::tcl::OptProcArgGiven {argname} {
# when needed...
set state [OptCurState $descriptions]
# We'll exit the loop in "OptDoOne" or when state is empty.
- while 1 {
+ while 1 {
set curitem [OptCurDesc $descriptions]
# Do subprograms if needed, call ourselves on the sub branch
while {[OptIsPrg $curitem]} {
@@ -388,7 +388,7 @@ proc ::tcl::OptProcArgGiven {argname} {
$curitem
OptNextDesc descriptions
set curitem [OptCurDesc $descriptions]
- set state [OptCurState $descriptions]
+ set state [OptCurState $descriptions]
}
# puts "state = \"$state\" - arguments=($arguments)"
if {[Lempty $state]} {
@@ -398,20 +398,20 @@ proc ::tcl::OptProcArgGiven {argname} {
# The following statement can make us terminate/continue
# as it use return -code {break, continue, return and error}
# codes
- OptDoOne descriptions state arguments
+ OptDoOne descriptions state arguments
# If we are here, no special return code where issued,
# we'll step to next instruction :
# puts "new state = \"$state\""
OptNextDesc descriptions
set state [OptCurState $descriptions]
- }
+ }
}
# Process one step for the state machine,
# eventually consuming the current argument.
proc OptDoOne {descriptionsName stateName argumentsName} {
- upvar $argumentsName arguments
- upvar $descriptionsName descriptions
+ upvar $argumentsName arguments
+ upvar $descriptionsName descriptions
upvar $stateName state
# the special state/instruction "args" eats all
@@ -443,48 +443,48 @@ proc ::tcl::OptProcArgGiven {argname} {
set arg [OptCurrentArg $arguments]
}
- switch $state {
- flags {
- # A non-dash argument terminates the options, as does --
-
- # Still a flag ?
- if {![OptIsFlag $arg]} {
- # don't consume the argument, return to previous prg
- return -code return
- }
- # consume the flag
- OptNextArg arguments
- if {[string equal "--" $arg]} {
- # return from 'flags' state
- return -code return
- }
-
- set hits [OptHits descriptions $arg]
- if {$hits > 1} {
- return -code error [OptAmbigous $descriptions $arg]
- } elseif {$hits == 0} {
- return -code error [OptFlagUsage $descriptions $arg]
- }
+ switch $state {
+ flags {
+ # A non-dash argument terminates the options, as does --
+
+ # Still a flag ?
+ if {![OptIsFlag $arg]} {
+ # don't consume the argument, return to previous prg
+ return -code return
+ }
+ # consume the flag
+ OptNextArg arguments
+ if {[string equal "--" $arg]} {
+ # return from 'flags' state
+ return -code return
+ }
+
+ set hits [OptHits descriptions $arg]
+ if {$hits > 1} {
+ return -code error [OptAmbigous $descriptions $arg]
+ } elseif {$hits == 0} {
+ return -code error [OptFlagUsage $descriptions $arg]
+ }
set item [OptCurDesc $descriptions]
- if {[OptNeedValue $item]} {
+ if {[OptNeedValue $item]} {
# we need a value, next state is
set state flagValue
- } else {
- OptCurSetValue descriptions 1
- }
+ } else {
+ OptCurSetValue descriptions 1
+ }
# continue
return -code continue
- }
+ }
flagValue -
value {
set item [OptCurDesc $descriptions]
- # Test the values against their required type
+ # Test the values against their required type
if {[catch {OptCheckType $arg\
[OptType $item] [OptTypeArgs $item]} val]} {
return -code error [OptBadValue $item $arg $val]
}
- # consume the value
- OptNextArg arguments
+ # consume the value
+ OptNextArg arguments
# set the value
OptCurSetValue descriptions $val
# go to next state
@@ -498,7 +498,7 @@ proc ::tcl::OptProcArgGiven {argname} {
}
optValue {
set item [OptCurDesc $descriptions]
- # Test the values against their required type
+ # Test the values against their required type
if {![catch {OptCheckType $arg\
[OptType $item] [OptTypeArgs $item]} val]} {
# right type, so :
@@ -511,7 +511,7 @@ proc ::tcl::OptProcArgGiven {argname} {
set state next; # not used, for debug only
return ; # will go on next step
}
- }
+ }
# If we reach this point: an unknown
# state as been entered !
return -code error "Bug! unknown state in DoOne \"$state\"\
@@ -576,46 +576,46 @@ proc ::tcl::OptCheckType {arg type {typeArgs ""}} {
# only types "any", "choice", and numbers can have leading "-"
switch -exact -- $type {
- int {
- if {![string is integer -strict $arg]} {
- error "not an integer"
- }
+ int {
+ if {![string is integer -strict $arg]} {
+ error "not an integer"
+ }
return $arg
- }
- float {
- return [expr {double($arg)}]
- }
+ }
+ float {
+ return [expr {double($arg)}]
+ }
script -
- list {
+ list {
# if llength fail : malformed list
- if {[llength $arg]==0 && [OptIsFlag $arg]} {
+ if {[llength $arg]==0 && [OptIsFlag $arg]} {
error "no values with leading -"
}
return $arg
- }
- boolean {
+ }
+ boolean {
if {![string is boolean -strict $arg]} {
error "non canonic boolean"
- }
+ }
# convert true/false because expr/if is broken with "!,...
return [expr {$arg ? 1 : 0}]
- }
- choice {
- if {$arg ni $typeArgs} {
- error "invalid choice"
- }
+ }
+ choice {
+ if {$arg ni $typeArgs} {
+ error "invalid choice"
+ }
return $arg
- }
+ }
any {
return $arg
}
string -
default {
- if {[OptIsFlag $arg]} {
- error "no values with leading -"
- }
+ if {[OptIsFlag $arg]} {
+ error "no values with leading -"
+ }
return $arg
- }
+ }
}
return neverReached
}
@@ -625,17 +625,17 @@ proc ::tcl::OptCheckType {arg type {typeArgs ""}} {
# returns the number of flags matching the given arg
# sets the (local) prg counter to the list of matches
proc OptHits {descName arg} {
- upvar $descName desc
- set hits 0
- set hitems {}
+ upvar $descName desc
+ set hits 0
+ set hitems {}
set i 1
set larg [string tolower $arg]
set len [string length $larg]
set last [expr {$len-1}]
- foreach item [lrange $desc 1 end] {
- set flag [OptName $item]
+ foreach item [lrange $desc 1 end] {
+ set flag [OptName $item]
# lets try to match case insensitively
# (string length ought to be cheap)
set lflag [string tolower $flag]
@@ -648,19 +648,19 @@ proc ::tcl::OptCheckType {arg type {typeArgs ""}} {
} elseif {[string equal $larg [string range $lflag 0 $last]]} {
lappend hitems $i
incr hits
- }
+ }
incr i
- }
+ }
if {$hits} {
OptSetPrgCounter desc $hitems
}
- return $hits
+ return $hits
}
# Extract fields from the list structure:
proc OptName {item} {
- lindex $item 1
+ lindex $item 1
}
proc OptHasBeenSet {item} {
Lget $item {2 0}
@@ -670,37 +670,37 @@ proc ::tcl::OptCheckType {arg type {typeArgs ""}} {
}
proc OptIsFlag {name} {
- string match "-*" $name
+ string match "-*" $name
}
proc OptIsOpt {name} {
- string match {\?*} $name
+ string match {\?*} $name
}
proc OptVarName {item} {
- set name [OptName $item]
- if {[OptIsFlag $name]} {
- return [string range $name 1 end]
- } elseif {[OptIsOpt $name]} {
+ set name [OptName $item]
+ if {[OptIsFlag $name]} {
+ return [string range $name 1 end]
+ } elseif {[OptIsOpt $name]} {
return [string trim $name "?"]
} else {
- return $name
- }
+ return $name
+ }
}
proc OptType {item} {
- lindex $item 3
+ lindex $item 3
}
proc OptTypeArgs {item} {
- lindex $item 4
+ lindex $item 4
}
proc OptHelp {item} {
- lindex $item 5
+ lindex $item 5
}
proc OptNeedValue {item} {
- expr {![string equal [OptType $item] boolflag]}
+ expr {![string equal [OptType $item] boolflag]}
}
proc OptDefaultValue {item} {
- set val [OptTypeArgs $item]
- switch -exact -- [OptType $item] {
- choice {return [lindex $val 0]}
+ set val [OptTypeArgs $item]
+ switch -exact -- [OptType $item] {
+ choice {return [lindex $val 0]}
boolean -
boolflag {
# convert back false/true to 0/1 because expr !$bool
@@ -711,15 +711,15 @@ proc ::tcl::OptCheckType {arg type {typeArgs ""}} {
return 0
}
}
- }
- return $val
+ }
+ return $val
}
# Description format error helper
proc OptOptUsage {item {what ""}} {
- return -code error "invalid description format$what: $item\n\
- should be a list of {varname|-flagname ?-type? ?defaultvalue?\
- ?helpstring?}"
+ return -code error "invalid description format$what: $item\n\
+ should be a list of {varname|-flagname ?-type? ?defaultvalue?\
+ ?helpstring?}"
}
@@ -733,13 +733,13 @@ proc ::tcl::OptCheckType {arg type {typeArgs ""}} {
# Translate one item to canonical form
proc OptNormalizeOne {item} {
- set lg [Lassign $item varname arg1 arg2 arg3]
+ set lg [Lassign $item varname arg1 arg2 arg3]
# puts "called optnormalizeone '$item' v=($varname), lg=$lg"
- set isflag [OptIsFlag $varname]
+ set isflag [OptIsFlag $varname]
set isopt [OptIsOpt $varname]
- if {$isflag} {
- set state "flags"
- } elseif {$isopt} {
+ if {$isflag} {
+ set state "flags"
+ } elseif {$isopt} {
set state "optValue"
} elseif {![string equal $varname "args"]} {
set state "value"
@@ -751,20 +751,20 @@ proc ::tcl::OptCheckType {arg type {typeArgs ""}} {
# description writer's life easy, and our's difficult :
# let's guess the missing arguments :-)
- switch $lg {
- 1 {
- if {$isflag} {
- return [OptNewInst $state $varname boolflag false ""]
- } else {
- return [OptNewInst $state $varname any "" ""]
- }
- }
- 2 {
- # varname default
- # varname help
- set type [OptGuessType $arg1]
- if {[string equal $type "string"]} {
- if {$isflag} {
+ switch $lg {
+ 1 {
+ if {$isflag} {
+ return [OptNewInst $state $varname boolflag false ""]
+ } else {
+ return [OptNewInst $state $varname any "" ""]
+ }
+ }
+ 2 {
+ # varname default
+ # varname help
+ set type [OptGuessType $arg1]
+ if {[string equal $type "string"]} {
+ if {$isflag} {
set type boolflag
set def false
} else {
@@ -772,17 +772,17 @@ proc ::tcl::OptCheckType {arg type {typeArgs ""}} {
set def ""
}
set help $arg1
- } else {
- set help ""
- set def $arg1
- }
- return [OptNewInst $state $varname $type $def $help]
- }
- 3 {
- # varname type value
- # varname value comment
-
- if {[regexp {^-(.+)$} $arg1 x type]} {
+ } else {
+ set help ""
+ set def $arg1
+ }
+ return [OptNewInst $state $varname $type $def $help]
+ }
+ 3 {
+ # varname type value
+ # varname value comment
+
+ if {[regexp {^-(.+)$} $arg1 x type]} {
# flags/optValue as they are optional, need a "value",
# on the contrary, for a variable (non optional),
# default value is pointless, 'cept for choices :
@@ -791,48 +791,48 @@ proc ::tcl::OptCheckType {arg type {typeArgs ""}} {
} else {
return [OptNewInst $state $varname $type "" $arg2]
}
- } else {
- return [OptNewInst $state $varname\
+ } else {
+ return [OptNewInst $state $varname\
[OptGuessType $arg1] $arg1 $arg2]
- }
- }
- 4 {
- if {[regexp {^-(.+)$} $arg1 x type]} {
+ }
+ }
+ 4 {
+ if {[regexp {^-(.+)$} $arg1 x type]} {
return [OptNewInst $state $varname $type $arg2 $arg3]
- } else {
- return -code error [OptOptUsage $item]
- }
- }
- default {
- return -code error [OptOptUsage $item]
- }
- }
+ } else {
+ return -code error [OptOptUsage $item]
+ }
+ }
+ default {
+ return -code error [OptOptUsage $item]
+ }
+ }
}
# Auto magic lazy type determination
proc OptGuessType {arg} {
if { $arg == "true" || $arg == "false" } {
- return boolean
- }
- if {[string is integer -strict $arg]} {
- return int
- }
- if {[string is double -strict $arg]} {
- return float
- }
- return string
+ return boolean
+ }
+ if {[string is integer -strict $arg]} {
+ return int
+ }
+ if {[string is double -strict $arg]} {
+ return float
+ }
+ return string
}
# Error messages front ends
proc OptAmbigous {desc arg} {
- OptError "ambigous option \"$arg\", choose from:" [OptSelection $desc]
+ OptError "ambigous option \"$arg\", choose from:" [OptSelection $desc]
}
proc OptFlagUsage {desc arg} {
- OptError "bad flag \"$arg\", must be one of" $desc
+ OptError "bad flag \"$arg\", must be one of" $desc
}
proc OptTooManyArgs {desc arguments} {
- OptError "too many arguments (unexpected argument(s): $arguments),\
+ OptError "too many arguments (unexpected argument(s): $arguments),\
usage:"\
$desc 1
}
@@ -845,13 +845,13 @@ proc ::tcl::OptCheckType {arg type {typeArgs ""}} {
}
proc OptBadValue {item arg {err {}}} {
# puts "bad val err = \"$err\""
- OptError "bad value \"$arg\" for [OptParamType $item]"\
+ OptError "bad value \"$arg\" for [OptParamType $item]"\
[list $item]
}
proc OptMissingValue {descriptions} {
# set item [OptCurDescFinal $descriptions]
- set item [OptCurDesc $descriptions]
- OptError "no value given for [OptParamType $item] \"[OptName $item]\"\
+ set item [OptCurDesc $descriptions]
+ OptError "no value given for [OptParamType $item] \"[OptName $item]\"\
(use -help for full usage) :"\
[list $item]
}
@@ -943,7 +943,7 @@ proc ::tcl::Lempty {list} {
# Gets the value of one leaf of a lists tree
proc ::tcl::Lget {list indexLst} {
if {[llength $indexLst] <= 1} {
- return [lindex $list $indexLst]
+ return [lindex $list $indexLst]
}
Lget [lindex $list [lindex $indexLst 0]] [lrange $indexLst 1 end]
}
@@ -958,17 +958,17 @@ proc ::tcl::Lget {list indexLst} {
proc ::tcl::Lvarset {listName indexLst newValue} {
upvar $listName list
if {[llength $indexLst] <= 1} {
- Lvarset1nc list $indexLst $newValue
+ Lvarset1nc list $indexLst $newValue
} else {
- set idx [lindex $indexLst 0]
- set targetList [lindex $list $idx]
- # reduce refcount on targetList (not really usefull now,
+ set idx [lindex $indexLst 0]
+ set targetList [lindex $list $idx]
+ # reduce refcount on targetList (not really usefull now,
# could be with optimizing compiler)
# Lvarset1 list $idx {}
- # recursively replace in targetList
- Lvarset targetList [lrange $indexLst 1 end] $newValue
- # put updated sub list back in the tree
- Lvarset1nc list $idx $targetList
+ # recursively replace in targetList
+ Lvarset targetList [lrange $indexLst 1 end] $newValue
+ # put updated sub list back in the tree
+ Lvarset1nc list $idx $targetList
}
}
# Set one cell to a value, eventually create all the needed elements
@@ -979,13 +979,13 @@ proc ::tcl::Lvarset1 {listName index newValue} {
if {$index < 0} {return -code error "invalid negative index"}
set lg [llength $list]
if {$index >= $lg} {
- variable emptyList
- for {set i $lg} {$i<$index} {incr i} {
- lappend list $emptyList
- }
- lappend list $newValue
+ variable emptyList
+ for {set i $lg} {$i<$index} {incr i} {
+ lappend list $emptyList
+ }
+ lappend list $newValue
} else {
- set list [lreplace $list $index $index $newValue]
+ set list [lreplace $list $index $index $newValue]
}
}
# same as Lvarset1 but no bound checking / creation
@@ -998,16 +998,16 @@ proc ::tcl::Lvarset1nc {listName index newValue} {
proc ::tcl::Lvarincr {listName indexLst {howMuch 1}} {
upvar $listName list
if {[llength $indexLst] <= 1} {
- Lvarincr1 list $indexLst $howMuch
+ Lvarincr1 list $indexLst $howMuch
} else {
- set idx [lindex $indexLst 0]
- set targetList [lindex $list $idx]
- # reduce refcount on targetList
- Lvarset1nc list $idx {}
- # recursively replace in targetList
- Lvarincr targetList [lrange $indexLst 1 end] $howMuch
- # put updated sub list back in the tree
- Lvarset1nc list $idx $targetList
+ set idx [lindex $indexLst 0]
+ set targetList [lindex $list $idx]
+ # reduce refcount on targetList
+ Lvarset1nc list $idx {}
+ # recursively replace in targetList
+ Lvarincr targetList [lrange $indexLst 1 end] $howMuch
+ # put updated sub list back in the tree
+ Lvarset1nc list $idx $targetList
}
}
# Increments the value of one cell of a list
@@ -1037,9 +1037,9 @@ proc ::tcl::Lassign {list args} {
set i 0
set lg [llength $list]
foreach vname $args {
- if {$i>=$lg} break
- uplevel 1 [list ::set $vname [lindex $list $i]]
- incr i
+ if {$i>=$lg} break
+ uplevel 1 [list ::set $vname [lindex $list $i]]
+ incr i
}
return $lg
}
@@ -1051,7 +1051,7 @@ proc ::tcl::Lassign {list args} {
proc ::tcl::SetMax {varname value} {
upvar 1 $varname var
if {![info exists var] || $value > $var} {
- set var $value
+ set var $value
}
}
@@ -1060,7 +1060,7 @@ proc ::tcl::SetMax {varname value} {
proc ::tcl::SetMin {varname value} {
upvar 1 $varname var
if {![info exists var] || $value < $var} {
- set var $value
+ set var $value
}
}
diff --git a/library/package.tcl b/library/package.tcl
index 7b2b2e9..b44cc69 100644
--- a/library/package.tcl
+++ b/library/package.tcl
@@ -3,8 +3,8 @@
# 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.
+# Copyright © 1991-1993 The Regents of the University of California.
+# Copyright © 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.
@@ -409,6 +409,7 @@ proc pkg_mkIndex {args} {
}
set f [open [file join $dir pkgIndex.tcl] w]
+ fconfigure $f -encoding utf-8 -translation lf
puts $f $index
close $f
}
@@ -491,12 +492,16 @@ proc tclPkgUnknown {name args} {
set dir [file dirname $file]
if {![info exists procdDirs($dir)]} {
try {
- source -encoding utf-8 $file
+ ::tcl::Pkg::source $file
} trap {POSIX EACCES} {} {
# $file was not readable; silently ignore
continue
} on error msg {
- tclLog "error reading package index file $file: $msg"
+ if {[regexp {version conflict for package} $msg]} {
+ # In case of version conflict, silently ignore
+ continue
+ }
+ tclLog "error reading package index file $file: $msg"
} on ok {} {
set procdDirs($dir) 1
}
@@ -509,11 +514,15 @@ proc tclPkgUnknown {name args} {
# safe interps usually don't have "file exists",
if {([interp issafe] || [file exists $file])} {
try {
- source -encoding utf-8 $file
+ ::tcl::Pkg::source $file
} trap {POSIX EACCES} {} {
# $file was not readable; silently ignore
continue
} on error msg {
+ if {[regexp {version conflict for package} $msg]} {
+ # In case of version conflict, silently ignore
+ continue
+ }
tclLog "error reading package index file $file: $msg"
} on ok {} {
set procdDirs($dir) 1
@@ -594,11 +603,15 @@ proc tcl::MacOSXPkgUnknown {original name args} {
set dir [file dirname $file]
if {![info exists procdDirs($dir)]} {
try {
- source $file
+ ::tcl::Pkg::source $file
} trap {POSIX EACCES} {} {
# $file was not readable; silently ignore
continue
} on error msg {
+ if {[regexp {version conflict for package} $msg]} {
+ # In case of version conflict, silently ignore
+ continue
+ }
tclLog "error reading package index file $file: $msg"
} on ok {} {
set procdDirs($dir) 1
diff --git a/library/parray.tcl b/library/parray.tcl
index a9c2cb1..984bf47 100644
--- a/library/parray.tcl
+++ b/library/parray.tcl
@@ -1,8 +1,8 @@
# 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.
+# Copyright © 1991-1993 The Regents of the University of California.
+# Copyright © 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.
diff --git a/library/readfile.tcl b/library/readfile.tcl
new file mode 100644
index 0000000..c1d5b84
--- /dev/null
+++ b/library/readfile.tcl
@@ -0,0 +1,23 @@
+# readFile:
+# Read the contents of a file.
+#
+# Copyright © 2023 Donal K Fellows.
+#
+# See the file "license.terms" for information on usage and redistribution
+# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+#
+
+proc readFile {filename {mode text}} {
+ # Parse the arguments
+ set MODES {binary text}
+ set ERR [list -level 1 -errorcode [list TCL LOOKUP MODE $mode]]
+ set mode [tcl::prefix match -message "mode" -error $ERR $MODES $mode]
+
+ # Read the file
+ set f [open $filename [dict get {text r binary rb} $mode]]
+ try {
+ return [read $f]
+ } finally {
+ close $f
+ }
+}
diff --git a/library/reg/pkgIndex.tcl b/library/reg/pkgIndex.tcl
deleted file mode 100644
index 3b430b1..0000000
--- a/library/reg/pkgIndex.tcl
+++ /dev/null
@@ -1,9 +0,0 @@
-if {![package vsatisfies [package provide Tcl] 8.5]} return
-if {[info sharedlibextension] != ".dll"} return
-if {[::tcl::pkgconfig get debug]} {
- package ifneeded registry 1.3.5 \
- [list load [file join $dir tclreg13g.dll] Registry]
-} else {
- package ifneeded registry 1.3.5 \
- [list load [file join $dir tclreg13.dll] Registry]
-}
diff --git a/library/registry/pkgIndex.tcl b/library/registry/pkgIndex.tcl
new file mode 100644
index 0000000..edb4729
--- /dev/null
+++ b/library/registry/pkgIndex.tcl
@@ -0,0 +1,9 @@
+if {![package vsatisfies [package provide Tcl] 8.5-]} return
+if {[info sharedlibextension] != ".dll"} return
+if {[package vsatisfies [package provide Tcl] 9.0-]} {
+ package ifneeded registry 1.3.7 \
+ [list load [file join $dir tcl9registry13.dll] Registry]
+} else {
+ package ifneeded registry 1.3.7 \
+ [list load [file join $dir tclregistry13.dll] Registry]
+}
diff --git a/library/safe.tcl b/library/safe.tcl
index 7a8fcdb..6f06dc7 100644
--- a/library/safe.tcl
+++ b/library/safe.tcl
@@ -1,13 +1,13 @@
# safe.tcl --
#
# This file provide a safe loading/sourcing mechanism for safe interpreters.
-# It implements a virtual path mechanism to hide the real pathnames from the
+# It implements a virtual path mecanism to hide the real pathnames from the
# child. It runs in a parent interpreter and sets up data structure and
# aliases that will be invoked when used from a child interpreter.
#
# See the safe.n man page for details.
#
-# Copyright (c) 1996-1997 Sun Microsystems, Inc.
+# Copyright © 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.
@@ -20,7 +20,7 @@
#
# Needed utilities package
-package require opt 0.4.8
+package require opt 0.4.9
# Create the safe namespace
namespace eval ::safe {
@@ -78,20 +78,32 @@ proc ::safe::InterpNested {} {
# Interface/entry point function and front end for "Create"
proc ::safe::interpCreate {args} {
+ variable AutoPathSync
+ if {$AutoPathSync} {
+ set autoPath {}
+ }
set Args [::tcl::OptKeyParse ::safe::interpCreate $args]
- RejectExcessColons $slave
- InterpCreate $slave $accessPath \
- [InterpStatics] [InterpNested] $deleteHook
+ RejectExcessColons $child
+
+ set withAutoPath [::tcl::OptProcArgGiven -autoPath]
+ InterpCreate $child $accessPath \
+ [InterpStatics] [InterpNested] $deleteHook $autoPath $withAutoPath
}
proc ::safe::interpInit {args} {
+ variable AutoPathSync
+ if {$AutoPathSync} {
+ set autoPath {}
+ }
set Args [::tcl::OptKeyParse ::safe::interpIC $args]
- if {![::interp exists $slave]} {
- return -code error "\"$slave\" is not an interpreter"
+ if {![::interp exists $child]} {
+ return -code error "\"$child\" is not an interpreter"
}
- RejectExcessColons $slave
- InterpInit $slave $accessPath \
- [InterpStatics] [InterpNested] $deleteHook
+ RejectExcessColons $child
+
+ set withAutoPath [::tcl::OptProcArgGiven -autoPath]
+ InterpInit $child $accessPath \
+ [InterpStatics] [InterpNested] $deleteHook $autoPath $withAutoPath
}
# Check that the given child is "one of us"
@@ -117,6 +129,7 @@ proc ::safe::CheckInterp {child} {
# So this will be hopefully written and some integrated with opt1.0
# (hopefully for tcl8.1 ?)
proc ::safe::interpConfigure {args} {
+ variable AutoPathSync
switch [llength $args] {
1 {
# If we have exactly 1 argument the semantic is to return all
@@ -124,19 +137,24 @@ proc ::safe::interpConfigure {args} {
# we know that "child" is our given argument because it also
# checks for the "-help" option.
set Args [::tcl::OptKeyParse ::safe::interpIC $args]
- CheckInterp $slave
- namespace upvar ::safe [VarName $slave] state
+ CheckInterp $child
+ namespace upvar ::safe [VarName $child] state
- return [join [list \
+ set TMP [list \
[list -accessPath $state(access_path)] \
[list -statics $state(staticsok)] \
[list -nested $state(nestedok)] \
- [list -deleteHook $state(cleanupHook)]]]
+ [list -deleteHook $state(cleanupHook)] \
+ ]
+ if {!$AutoPathSync} {
+ lappend TMP [list -autoPath $state(auto_path)]
+ }
+ return [join $TMP]
}
2 {
# If we have exactly 2 arguments the semantic is a "configure
# get"
- lassign $args slave arg
+ lassign $args child arg
# get the flag sub program (we 'know' about Opt's internal
# representation of data)
@@ -147,8 +165,8 @@ proc ::safe::interpConfigure {args} {
} elseif {$hits == 0} {
return -code error [::tcl::OptFlagUsage $desc $arg]
}
- CheckInterp $slave
- namespace upvar ::safe [VarName $slave] state
+ CheckInterp $child
+ namespace upvar ::safe [VarName $child] state
set item [::tcl::OptCurDesc $desc]
set name [::tcl::OptName $item]
@@ -156,6 +174,13 @@ proc ::safe::interpConfigure {args} {
-accessPath {
return [list -accessPath $state(access_path)]
}
+ -autoPath {
+ if {$AutoPathSync} {
+ return -code error "unknown flag $name (bug)"
+ } else {
+ return [list -autoPath $state(auto_path)]
+ }
+ }
-statics {
return [list -statics $state(staticsok)]
}
@@ -188,8 +213,8 @@ proc ::safe::interpConfigure {args} {
# Otherwise we want to parse the arguments like init and
# create did
set Args [::tcl::OptKeyParse ::safe::interpIC $args]
- CheckInterp $slave
- namespace upvar ::safe [VarName $slave] state
+ CheckInterp $child
+ namespace upvar ::safe [VarName $child] state
# Get the current (and not the default) values of whatever has
# not been given:
@@ -199,6 +224,12 @@ proc ::safe::interpConfigure {args} {
} else {
set doreset 1
}
+ if {(!$AutoPathSync) && (![::tcl::OptProcArgGiven -autoPath])} {
+ set autoPath $state(auto_path)
+ } elseif {$AutoPathSync} {
+ set autoPath {}
+ } else {
+ }
if {
![::tcl::OptProcArgGiven -statics]
&& ![::tcl::OptProcArgGiven -noStatics]
@@ -218,21 +249,23 @@ proc ::safe::interpConfigure {args} {
if {![::tcl::OptProcArgGiven -deleteHook]} {
set deleteHook $state(cleanupHook)
}
- # we can now reconfigure :
- InterpSetConfig $slave $accessPath $statics $nested $deleteHook
- # auto_reset the child (to completely synch the new access_path)
+ # Now reconfigure
+ set withAutoPath [::tcl::OptProcArgGiven -autoPath]
+ InterpSetConfig $child $accessPath $statics $nested $deleteHook $autoPath $withAutoPath
+
+ # auto_reset the child (to completely sync the new access_path) tests safe-9.8 safe-9.9
if {$doreset} {
- if {[catch {::interp eval $slave {auto_reset}} msg]} {
- Log $slave "auto_reset failed: $msg"
+ if {[catch {::interp eval $child {auto_reset}} msg]} {
+ Log $child "auto_reset failed: $msg"
} else {
- Log $slave "successful auto_reset" NOTICE
+ Log $child "successful auto_reset" NOTICE
}
# Sync the paths used to search for Tcl modules.
- ::interp eval $slave {tcl::tm::path remove {*}[tcl::tm::list]}
- if {[llength $state(tm_path_slave)] > 0} {
- ::interp eval $slave [list \
- ::tcl::tm::add {*}[lreverse $state(tm_path_slave)]]
+ ::interp eval $child {tcl::tm::path remove {*}[tcl::tm::list]}
+ if {[llength $state(tm_path_child)] > 0} {
+ ::interp eval $child [list \
+ ::tcl::tm::add {*}[lreverse $state(tm_path_child)]]
}
# Remove stale "package ifneeded" data for non-loaded packages.
@@ -240,9 +273,9 @@ proc ::safe::interpConfigure {args} {
# data from "package provide" as well as "package ifneeded".
# - This is OK because the script cannot reload any version of
# the package unless it first does "package forget".
- foreach pkg [::interp eval $slave {package names}] {
- if {[::interp eval $slave [list package provide $pkg]] eq ""} {
- ::interp eval $slave [list package forget $pkg]
+ foreach pkg [::interp eval $child {package names}] {
+ if {[::interp eval $child [list package provide $pkg]] eq ""} {
+ ::interp eval $child [list package forget $pkg]
}
}
}
@@ -270,10 +303,11 @@ proc ::safe::interpConfigure {args} {
# Optional Arguments :
# + child name : if empty, generated name will be used
# + access_path: path list controlling where load/source can occur,
-# if empty: the parent auto_path will be used.
+# if empty: the parent auto_path and its subdirectories 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)
+# + 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
@@ -283,6 +317,8 @@ proc ::safe::InterpCreate {
staticsok
nestedok
deletehook
+ autoPath
+ withAutoPath
} {
# Create the child.
# If evaluated in ::safe, the interpreter command for foo is ::foo;
@@ -296,20 +332,25 @@ proc ::safe::InterpCreate {
Log $child "Created" NOTICE
# Initialize it. (returns child name)
- InterpInit $child $access_path $staticsok $nestedok $deletehook
+ InterpInit $child $access_path $staticsok $nestedok $deletehook $autoPath $withAutoPath
}
#
# InterpSetConfig (was setAccessPath) :
-# Sets up child virtual auto_path and corresponding structure within
+# Sets up child virtual access path and corresponding structure within
# the parent. Also sets the tcl_library in the child to be the first
# directory in the path.
# NB: If you change the path after the child has been initialized you
# probably need to call "auto_reset" in the child in order that it gets
# the right auto_index() array values.
+#
+# It is the caller's responsibility, if it supplies a non-empty value for
+# access_path, to make the first directory in the path suitable for use as
+# tcl_library, and (if ![setSyncMode]), to set the child's ::auto_path.
-proc ::safe::InterpSetConfig {child access_path staticsok nestedok deletehook} {
+proc ::safe::InterpSetConfig {child access_path staticsok nestedok deletehook autoPath withAutoPath} {
global auto_path
+ variable AutoPathSync
# determine and store the access path if empty
if {$access_path eq ""} {
@@ -322,24 +363,35 @@ proc ::safe::InterpSetConfig {child access_path staticsok nestedok deletehook} {
# not found, add it.
set access_path [linsert $access_path 0 [info library]]
Log $child "tcl_library was not in auto_path,\
- added it to slave's access_path" NOTICE
+ added it to child'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 $child "tcl_libray was not in first in auto_path,\
- moved it to front of slave's access_path" NOTICE
+ moved it to front of child's access_path" NOTICE
}
+ set raw_auto_path $access_path
+
# Add 1st level subdirs (will searched by auto loading from tcl
# code in the child using glob and thus fail, so we add them here
# so by default it works the same).
set access_path [AddSubDirs $access_path]
+ } else {
+ set raw_auto_path $autoPath
+ }
+
+ if {$withAutoPath} {
+ set raw_auto_path $autoPath
}
Log $child "Setting accessPath=($access_path) staticsok=$staticsok\
nestedok=$nestedok deletehook=($deletehook)" NOTICE
+ if {!$AutoPathSync} {
+ Log $child "Setting auto_path=($raw_auto_path)" NOTICE
+ }
namespace upvar ::safe [VarName $child] state
@@ -347,25 +399,38 @@ proc ::safe::InterpSetConfig {child access_path staticsok nestedok deletehook} {
# 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
- # child has to be deferred until the necessary commands are present for
+ # child has to be defered until the necessary commands are present for
# setup.
-
set norm_access_path {}
- set slave_access_path {}
+ set child_access_path {}
set map_access_path {}
set remap_access_path {}
- set slave_tm_path {}
+ set child_tm_path {}
set i 0
foreach dir $access_path {
set token [PathToken $i]
- lappend slave_access_path $token
+ lappend child_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 the child auto_path to a tokenized raw_auto_path.
+ # Silently ignore any directories that are not in the access path.
+ # If [setSyncMode], SyncAccessPath will overwrite this value with the
+ # full access path.
+ # If ![setSyncMode], Safe Base code will not change this value.
+ set tokens_auto_path {}
+ foreach dir $raw_auto_path {
+ if {[dict exists $remap_access_path $dir]} {
+ lappend tokens_auto_path [dict get $remap_access_path $dir]
+ }
+ }
+ ::interp eval $child [list set auto_path $tokens_auto_path]
+
+ # Add the tcl::tm directories to the access path.
set morepaths [::tcl::tm::list]
set firstpass 1
while {[llength $morepaths]} {
@@ -377,25 +442,25 @@ proc ::safe::InterpSetConfig {child access_path staticsok nestedok deletehook} {
# result if they are already known.
if {[dict exists $remap_access_path $dir]} {
if {$firstpass} {
- # $dir is in [::tcl::tm::list] and belongs in the slave_tm_path.
+ # $dir is in [::tcl::tm::list] and belongs in the child_tm_path.
# Later passes handle subdirectories, which belong in the
# access path but not in the module path.
- lappend slave_tm_path [dict get $remap_access_path $dir]
+ lappend child_tm_path [dict get $remap_access_path $dir]
}
continue
}
set token [PathToken $i]
lappend access_path $dir
- lappend slave_access_path $token
+ lappend child_access_path $token
lappend map_access_path $token $dir
lappend remap_access_path $dir $token
lappend norm_access_path [file normalize $dir]
if {$firstpass} {
- # $dir is in [::tcl::tm::list] and belongs in the slave_tm_path.
+ # $dir is in [::tcl::tm::list] and belongs in the child_tm_path.
# Later passes handle subdirectories, which belong in the
# access path but not in the module path.
- lappend slave_tm_path $token
+ lappend child_tm_path $token
}
incr i
@@ -414,21 +479,48 @@ proc ::safe::InterpSetConfig {child access_path staticsok nestedok deletehook} {
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(access_path,child) $child_access_path
+ set state(tm_path_child) $child_tm_path
set state(staticsok) $staticsok
set state(nestedok) $nestedok
set state(cleanupHook) $deletehook
+ if {!$AutoPathSync} {
+ set state(auto_path) $raw_auto_path
+ }
+
SyncAccessPath $child
return
}
+
+#
+# DetokPath:
+# Convert tokens to directories where possible.
+# Leave undefined tokens unconverted. They are
+# nonsense in both the child and the parent.
+#
+proc ::safe::DetokPath {child tokenPath} {
+ namespace upvar ::safe [VarName $child] state
+
+ set childPath {}
+ foreach token $tokenPath {
+ if {[dict exists $state(access_path,map) $token]} {
+ lappend childPath [dict get $state(access_path,map) $token]
+ } else {
+ lappend childPath $token
+ }
+ }
+ return $childPath
+}
+
#
#
-# FindInAccessPath:
+# interpFindInAccessPath:
# Search for a real directory and returns its virtual Id (including the
# "$")
+#
+# When debugging, use TranslatePath for the inverse operation.
proc ::safe::interpFindInAccessPath {child path} {
CheckInterp $child
namespace upvar ::safe [VarName $child] state
@@ -440,6 +532,7 @@ proc ::safe::interpFindInAccessPath {child path} {
return [dict get $state(access_path,remap) $path]
}
+
#
# addToAccessPath:
# add (if needed) a real directory to access path and return its
@@ -458,7 +551,7 @@ proc ::safe::interpAddToAccessPath {child path} {
set token [PathToken [llength $state(access_path)]]
lappend state(access_path) $path
- lappend state(access_path,slave) $token
+ lappend state(access_path,child) $token
lappend state(access_path,map) $token $path
lappend state(access_path,remap) $path $token
lappend state(access_path,norm) [file normalize $path]
@@ -476,9 +569,11 @@ proc ::safe::InterpInit {
staticsok
nestedok
deletehook
+ autoPath
+ withAutoPath
} {
# Configure will generate an access_path when access_path is empty.
- InterpSetConfig $child $access_path $staticsok $nestedok $deletehook
+ InterpSetConfig $child $access_path $staticsok $nestedok $deletehook $autoPath $withAutoPath
# NB we need to add [namespace current], aliases are always absolute
# paths.
@@ -494,37 +589,35 @@ proc ::safe::InterpInit {
foreach {command alias} {
source AliasSource
load AliasLoad
- encoding AliasEncoding
exit interpDelete
glob AliasGlob
} {
::interp alias $child $command {} [namespace current]::$alias $child
}
+ # UGLY POINT! These commands are safe (they're ensembles with unsafe
+ # subcommands), but is assumed to not be by existing policies so it is
+ # hidden by default. Hack it...
+ foreach command {encoding file} {
+ ::interp alias $child $command {} interp invokehidden $child $command
+ }
+
# This alias lets the child have access to a subset of the 'file'
# command functionality.
- ::interp expose $child file
foreach subcommand {dirname extension rootname tail} {
::interp alias $child ::tcl::file::$subcommand {} \
::safe::AliasFileSubcommand $child $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 $child ::tcl::file::$subcommand {} \
- ::safe::BadSubcommand $child file $subcommand
- }
+
+ # Subcommand of 'encoding' that has special handling; [encoding system] is
+ # OK provided it has no other arguments passed to it.
+ ::interp alias $child ::tcl::encoding::system {} \
+ ::safe::AliasEncodingSystem $child
# Subcommands of info
- foreach {subcommand alias} {
- nameofexecutable AliasExeName
- } {
- ::interp alias $child ::tcl::info::$subcommand \
- {} [namespace current]::$alias $child
- }
+ ::interp alias $child ::tcl::info::nameofexecutable {} \
+ ::safe::AliasExeName $child
# The allowed child variables already have been set by Tcl_MakeSafe(3)
@@ -535,22 +628,22 @@ proc ::safe::InterpInit {
source -encoding utf-8 [file join $tcl_library init.tcl]
}} msg opt]} {
Log $child "can't source init.tcl ($msg)"
- return -options $opt "can't source init.tcl into slave $child ($msg)"
+ return -options $opt "can't source init.tcl into child $child ($msg)"
}
if {[catch {::interp eval $child {
source -encoding utf-8 [file join $tcl_library tm.tcl]
}} msg opt]} {
Log $child "can't source tm.tcl ($msg)"
- return -options $opt "can't source tm.tcl into slave $child ($msg)"
+ return -options $opt "can't source tm.tcl into child $child ($msg)"
}
# Sync the paths used to search for Tcl modules. This can be done only
# now, after tm.tcl was loaded.
namespace upvar ::safe [VarName $child] state
- if {[llength $state(tm_path_slave)] > 0} {
+ if {[llength $state(tm_path_child)] > 0} {
::interp eval $child [list \
- ::tcl::tm::add {*}[lreverse $state(tm_path_slave)]]
+ ::tcl::tm::add {*}[lreverse $state(tm_path_child)]]
}
return $child
}
@@ -658,7 +751,7 @@ proc ::safe::setLogCmd {args} {
proc ::safe::Log {child msg {type ERROR}} {
variable Log
- {*}$Log "$type for slave $child : $msg"
+ {*}$Log "$type for child $child : $msg"
return
}
}
@@ -667,24 +760,28 @@ proc ::safe::setLogCmd {args} {
# ------------------- END OF PUBLIC METHODS ------------
#
-# Sets the child auto_path to the parent recorded value. Also sets
-# tcl_library to the first token of the virtual path.
+# Sets the child auto_path to its recorded access path. Also sets
+# tcl_library to the first token of the access path.
#
proc ::safe::SyncAccessPath {child} {
+ variable AutoPathSync
namespace upvar ::safe [VarName $child] state
- set slave_access_path $state(access_path,slave)
- ::interp eval $child [list set auto_path $slave_access_path]
+ set child_access_path $state(access_path,child)
+ if {$AutoPathSync} {
+ ::interp eval $child [list set auto_path $child_access_path]
- Log $child "auto_path in $child has been set to $slave_access_path"\
- NOTICE
+ Log $child "auto_path in $child has been set to $child_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
+ # list of access path's. See -> InterpSetConfig for the code which
# ensures this condition.
::interp eval $child [list \
- set tcl_library [lindex $slave_access_path 0]]
+ set tcl_library [lindex $child_access_path 0]]
+ return
}
# Returns the virtual token for directory number N.
@@ -744,6 +841,7 @@ proc ::safe::AliasFileSubcommand {child subcommand name} {
# AliasGlob is the target of the "glob" alias in safe interpreters.
proc ::safe::AliasGlob {child args} {
+ variable AutoPathSync
Log $child "GLOB ! $args" NOTICE
set cmd {}
set at 0
@@ -791,6 +889,7 @@ proc ::safe::AliasGlob {child args} {
-* {
Log $child "Safe base rejecting glob option '$opt'"
return -code error "Safe base rejecting glob option '$opt'"
+ # unsafe/unnecessary options rejected: -path
}
default {
break
@@ -825,7 +924,7 @@ proc ::safe::AliasGlob {child args} {
return -code error "permission denied"
}
- # Apply the -join semantics ourselves.
+ # Apply the -join semantics ourselves (hence -join not copied to $cmd)
if {$got(-join)} {
set args [lreplace $args $at end [join [lrange $args $at end] "/"]]
}
@@ -939,7 +1038,7 @@ proc ::safe::AliasSource {child args} {
}
} else {
set at 0
- set encoding {}
+ set encoding utf-8
}
if {$argc != 1} {
set msg "wrong # args: should be \"source ?-encoding E? fileName\""
@@ -982,10 +1081,7 @@ proc ::safe::AliasSource {child args} {
set replacementMsg "script error"
set code [catch {
set f [open $realfile]
- fconfigure $f -eofchar "\x1A {}"
- if {$encoding ne ""} {
- fconfigure $f -encoding $encoding
- }
+ fconfigure $f -encoding $encoding -eofchar "\x1A {}"
set contents [read $f]
close $f
::interp eval $child [list info script $file]
@@ -1018,8 +1114,8 @@ proc ::safe::AliasLoad {child file args} {
return -code error $msg
}
- # package name (can be empty if file is not).
- set package [lindex $args 0]
+ # prefix (can be empty if file is not).
+ set prefix [lindex $args 0]
namespace upvar ::safe [VarName $child] state
@@ -1031,23 +1127,23 @@ proc ::safe::AliasLoad {child file args} {
# authorize that.
if {!$state(nestedok)} {
Log $child "loading to a sub interp (nestedok)\
- disabled (trying to load $package to $target)"
+ disabled (trying to load $prefix 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"
+ # static loading
+ if {$prefix eq ""} {
+ set msg "load error: empty filename and no prefix"
Log $child $msg
return -code error $msg
}
if {!$state(staticsok)} {
- Log $child "static packages loading disabled\
- (trying to load $package to $target)"
- return -code error "permission denied (static package)"
+ Log $child "static loading disabled\
+ (trying to load $prefix to $target)"
+ return -code error "permission denied (static library)"
}
} else {
# file loading
@@ -1070,10 +1166,10 @@ proc ::safe::AliasLoad {child file args} {
}
try {
- return [::interp invokehidden $child load $file $package $target]
+ return [::interp invokehidden $child load $file $prefix $target]
} on error msg {
- # Some packages return no error message.
- set msg0 "load of binary library for package $package failed"
+ # Some libraries return no error message.
+ set msg0 "load of library for prefix $prefix failed"
if {$msg eq {}} {
set msg $msg0
} else {
@@ -1135,16 +1231,13 @@ proc ::safe::BadSubcommand {child command subcommand args} {
return -code error -errorcode {TCL SAFE SUBCOMMAND} $msg
}
-# AliasEncoding is the target of the "encoding" alias in safe interpreters.
-
-proc ::safe::AliasEncoding {child option args} {
- # Note that [encoding dirs] is not supported in safe children at all
- set subcommands {convertfrom convertto names system}
+# AliasEncodingSystem is the target of the "encoding system" alias in safe
+# interpreters.
+proc ::safe::AliasEncodingSystem {child args} {
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]} {
+ # Must not pass extra arguments; safe interpreters may not set the
+ # system encoding but they may read it.
+ if {[llength $args]} {
return -code error -errorcode {TCL WRONGARGS} \
"wrong # args: should be \"encoding system\""
}
@@ -1152,7 +1245,7 @@ proc ::safe::AliasEncoding {child option args} {
Log $child $msg
return -options $options $msg
}
- tailcall ::interp invokehidden $child encoding $option {*}$args
+ tailcall ::interp invokehidden $child tcl:encoding:system
}
# Various minor hiding of platform features. [Bug 2913625]
@@ -1219,29 +1312,34 @@ proc ::safe::Setup {} {
# Setup the arguments parsing
#
####
+ variable AutoPathSync
# Share the descriptions
- set temp [::tcl::OptKeyRegister {
- {-accessPath -list {} "access path for the slave"}
+ set OptList {
+ {-accessPath -list {} "access path for the child"}
{-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"}
- }]
+ }
+ if {!$AutoPathSync} {
+ lappend OptList {-autoPath -list {} "::auto_path for the child"}
+ }
+ set temp [::tcl::OptKeyRegister $OptList]
- # create case (slave is optional)
+ # create case (child is optional)
::tcl::OptKeyRegister {
- {?slave? -name {} "name of the slave (optional)"}
+ {?child? -name {} "name of the child (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)
+ # init and configure (child is needed)
::tcl::OptKeyRegister {
- {slave -name {} "name of the slave"}
+ {child -name {} "name of the child"}
} ::safe::interpIC
# adding the flags sub programs to the command program (relying on Opt's
@@ -1265,8 +1363,72 @@ proc ::safe::Setup {} {
return
}
+# Accessor method for ::safe::AutoPathSync
+# Usage: ::safe::setSyncMode ?newValue?
+# Respond to changes by calling Setup again, preserving any
+# caller-defined logging. This allows complete equivalence with
+# prior Safe Base behavior if AutoPathSync is true.
+#
+# >>> WARNING <<<
+#
+# DO NOT CHANGE AutoPathSync EXCEPT BY THIS COMMAND - IT IS VITAL THAT WHENEVER
+# THE VALUE CHANGES, THE EXISTING PARSE TOKENS ARE DELETED AND Setup IS CALLED
+# AGAIN.
+# (The initialization of AutoPathSync at the end of this file is acceptable
+# because Setup has not yet been called.)
+
+proc ::safe::setSyncMode {args} {
+ variable AutoPathSync
+
+ if {[llength $args] == 0} {
+ } elseif {[llength $args] == 1} {
+ set newValue [lindex $args 0]
+ if {![string is boolean -strict $newValue]} {
+ return -code error "new value must be a valid boolean"
+ }
+ set args [expr {$newValue && $newValue}]
+ if {([info vars ::safe::S*] ne {}) && ($args != $AutoPathSync)} {
+ return -code error \
+ "cannot set new value while Safe Base child interpreters exist"
+ }
+ if {($args != $AutoPathSync)} {
+ set AutoPathSync {*}$args
+ ::tcl::OptKeyDelete ::safe::interpCreate
+ ::tcl::OptKeyDelete ::safe::interpIC
+ set TmpLog [setLogCmd]
+ Setup
+ setLogCmd $TmpLog
+ }
+ } else {
+ set msg {wrong # args: should be "safe::setSyncMode ?newValue?"}
+ return -code error $msg
+ }
+
+ return $AutoPathSync
+}
+
namespace eval ::safe {
- # internal variables
+ # internal variables (must not begin with "S")
+
+ # AutoPathSync
+ #
+ # Set AutoPathSync to 0 to give a child's ::auto_path the same meaning as
+ # for an unsafe interpreter: the package command will search its directories
+ # and first-level subdirectories for pkgIndex.tcl files; the auto-loader
+ # will search its directories for tclIndex files. The access path and
+ # module path will be maintained as separate values, and ::auto_path will
+ # not be updated when the user calls ::safe::interpAddToAccessPath to add to
+ # the access path. If the user specifies an access path when calling
+ # interpCreate, interpInit or interpConfigure, it is the user's
+ # responsibility to define the child's auto_path. If these commands are
+ # called with no (or empty) access path, the child's auto_path will be set
+ # to a tokenized form of the parent's auto_path, and these directories and
+ # their first-level subdirectories will be added to the access path.
+ #
+ # Set to 1 for "traditional" behavior: a child's entire access path and
+ # module path are copied to its ::auto_path, which is updated whenever
+ # the user calls ::safe::interpAddToAccessPath to add to the access path.
+ variable AutoPathSync 1
# Log command, set via 'setLogCmd'. Logging is disabled when empty.
variable Log {}
@@ -1281,13 +1443,24 @@ namespace eval ::safe {
#
# access_path : List of paths accessible to the child.
# access_path,norm : Ditto, in normalized form.
- # access_path,slave : Ditto, as the path tokens as seen by the child.
+ # access_path,child : Ditto, as the path tokens as seen by the child.
# 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 child.
+ # auto_path : List of paths requested by the caller as child's ::auto_path.
+ # tm_path_child : List of TM root directories, as tokens seen by the child.
# staticsok : Value of option -statics
# nestedok : Value of option -nested
# cleanupHook : Value of option -deleteHook
+ #
+ # In principle, the child can change its value of ::auto_path -
+ # - a package might add a path (that is already in the access path) for
+ # access to tclIndex files;
+ # - the script might remove some elements of the auto_path.
+ # However, this is really the business of the parent, and the auto_path will
+ # be reset whenever the token mapping changes (i.e. when option -accessPath is
+ # used to change the access path).
+ # -autoPath is now stored in the array and is no longer obtained from
+ # the child.
}
::safe::Setup
diff --git a/library/tclIndex b/library/tclIndex
index 88a7bc0..871298f 100644
--- a/library/tclIndex
+++ b/library/tclIndex
@@ -6,80 +6,106 @@
# 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 -encoding utf-8 [file join $dir auto.tcl]]
-set auto_index(tcl_findLibrary) [list source -encoding utf-8 [file join $dir auto.tcl]]
-set auto_index(auto_mkindex) [list source -encoding utf-8 [file join $dir auto.tcl]]
-set auto_index(auto_mkindex_old) [list source -encoding utf-8 [file join $dir auto.tcl]]
-set auto_index(::auto_mkindex_parser::init) [list source -encoding utf-8 [file join $dir auto.tcl]]
-set auto_index(::auto_mkindex_parser::cleanup) [list source -encoding utf-8 [file join $dir auto.tcl]]
-set auto_index(::auto_mkindex_parser::mkindex) [list source -encoding utf-8 [file join $dir auto.tcl]]
-set auto_index(::auto_mkindex_parser::hook) [list source -encoding utf-8 [file join $dir auto.tcl]]
-set auto_index(::auto_mkindex_parser::slavehook) [list source -encoding utf-8 [file join $dir auto.tcl]]
-set auto_index(::auto_mkindex_parser::command) [list source -encoding utf-8 [file join $dir auto.tcl]]
-set auto_index(::auto_mkindex_parser::commandInit) [list source -encoding utf-8 [file join $dir auto.tcl]]
-set auto_index(::auto_mkindex_parser::fullname) [list source -encoding utf-8 [file join $dir auto.tcl]]
-set auto_index(::tcl::history) [list source -encoding utf-8 [file join $dir history.tcl]]
-set auto_index(history) [list source -encoding utf-8 [file join $dir history.tcl]]
-set auto_index(::tcl::HistAdd) [list source -encoding utf-8 [file join $dir history.tcl]]
-set auto_index(::tcl::HistKeep) [list source -encoding utf-8 [file join $dir history.tcl]]
-set auto_index(::tcl::HistClear) [list source -encoding utf-8 [file join $dir history.tcl]]
-set auto_index(::tcl::HistInfo) [list source -encoding utf-8 [file join $dir history.tcl]]
-set auto_index(::tcl::HistRedo) [list source -encoding utf-8 [file join $dir history.tcl]]
-set auto_index(::tcl::HistIndex) [list source -encoding utf-8 [file join $dir history.tcl]]
-set auto_index(::tcl::HistEvent) [list source -encoding utf-8 [file join $dir history.tcl]]
-set auto_index(::tcl::HistChange) [list source -encoding utf-8 [file join $dir history.tcl]]
-set auto_index(::tcl::HistNextID) [list source -encoding utf-8 [file join $dir history.tcl]]
-set auto_index(::tcl::Pkg::CompareExtension) [list source -encoding utf-8 [file join $dir package.tcl]]
-set auto_index(pkg_mkIndex) [list source -encoding utf-8 [file join $dir package.tcl]]
-set auto_index(tclPkgSetup) [list source -encoding utf-8 [file join $dir package.tcl]]
-set auto_index(tclPkgUnknown) [list source -encoding utf-8 [file join $dir package.tcl]]
-set auto_index(::tcl::MacOSXPkgUnknown) [list source -encoding utf-8 [file join $dir package.tcl]]
-set auto_index(::pkg::create) [list source -encoding utf-8 [file join $dir package.tcl]]
-set auto_index(parray) [list source -encoding utf-8 [file join $dir parray.tcl]]
-set auto_index(::safe::InterpStatics) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::InterpNested) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::interpCreate) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::interpInit) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::CheckInterp) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::interpConfigure) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::InterpCreate) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::InterpSetConfig) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::interpFindInAccessPath) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::interpAddToAccessPath) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::InterpInit) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::AddSubDirs) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::interpDelete) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::setLogCmd) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::SyncAccessPath) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::PathToken) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::TranslatePath) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::Log) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::CheckFileName) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::AliasFileSubcommand) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::AliasGlob) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::AliasSource) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::AliasLoad) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::FileInAccessPath) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::DirInAccessPath) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::BadSubcommand) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::AliasEncoding) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::AliasExeName) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::RejectExcessColons) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::VarName) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::safe::Setup) [list source -encoding utf-8 [file join $dir safe.tcl]]
-set auto_index(::tcl::tm::path) [list source -encoding utf-8 [file join $dir tm.tcl]]
-set auto_index(::tcl::tm::add) [list source -encoding utf-8 [file join $dir tm.tcl]]
-set auto_index(::tcl::tm::remove) [list source -encoding utf-8 [file join $dir tm.tcl]]
-set auto_index(::tcl::tm::list) [list source -encoding utf-8 [file join $dir tm.tcl]]
-set auto_index(::tcl::tm::UnknownHandler) [list source -encoding utf-8 [file join $dir tm.tcl]]
-set auto_index(::tcl::tm::Defaults) [list source -encoding utf-8 [file join $dir tm.tcl]]
-set auto_index(::tcl::tm::roots) [list source -encoding utf-8 [file join $dir tm.tcl]]
-set auto_index(::tcl::UpdateWordBreakREs) [list source -encoding utf-8 [file join $dir word.tcl]]
-set auto_index(tcl_wordBreakAfter) [list source -encoding utf-8 [file join $dir word.tcl]]
-set auto_index(tcl_wordBreakBefore) [list source -encoding utf-8 [file join $dir word.tcl]]
-set auto_index(tcl_endOfWord) [list source -encoding utf-8 [file join $dir word.tcl]]
-set auto_index(tcl_startOfNextWord) [list source -encoding utf-8 [file join $dir word.tcl]]
-set auto_index(tcl_startOfPreviousWord) [list source -encoding utf-8 [file join $dir word.tcl]]
-if {[namespace exists ::tcl::unsupported]} {
- set auto_index(timerate) {namespace import ::tcl::unsupported::timerate}
-}
+set auto_index(auto_reset) [list ::tcl::Pkg::source [file join $dir auto.tcl]]
+set auto_index(tcl_findLibrary) [list ::tcl::Pkg::source [file join $dir auto.tcl]]
+set auto_index(auto_mkindex) [list ::tcl::Pkg::source [file join $dir auto.tcl]]
+set auto_index(auto_mkindex_old) [list ::tcl::Pkg::source [file join $dir auto.tcl]]
+set auto_index(::auto_mkindex_parser::init) [list ::tcl::Pkg::source [file join $dir auto.tcl]]
+set auto_index(::auto_mkindex_parser::cleanup) [list ::tcl::Pkg::source [file join $dir auto.tcl]]
+set auto_index(::auto_mkindex_parser::mkindex) [list ::tcl::Pkg::source [file join $dir auto.tcl]]
+set auto_index(::auto_mkindex_parser::hook) [list ::tcl::Pkg::source [file join $dir auto.tcl]]
+set auto_index(::auto_mkindex_parser::childhook) [list ::tcl::Pkg::source [file join $dir auto.tcl]]
+set auto_index(::auto_mkindex_parser::command) [list ::tcl::Pkg::source [file join $dir auto.tcl]]
+set auto_index(::auto_mkindex_parser::commandInit) [list ::tcl::Pkg::source [file join $dir auto.tcl]]
+set auto_index(::auto_mkindex_parser::fullname) [list ::tcl::Pkg::source [file join $dir auto.tcl]]
+set auto_index(::auto_mkindex_parser::indexEntry) [list ::tcl::Pkg::source [file join $dir auto.tcl]]
+set auto_index(::tcl::clock::Initialize) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::mcget) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::mcMerge) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::GetSystemLocale) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::EnterLocale) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::_hasRegistry) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::LoadWindowsDateTimeFormats) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::LocalizeFormat) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::GetSystemTimeZone) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::SetupTimeZone) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::GuessWindowsTimeZone) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::LoadTimeZoneFile) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::LoadZoneinfoFile) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::ReadZoneinfoFile) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::ParsePosixTimeZone) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::ProcessPosixTimeZone) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::DeterminePosixDSTTime) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::GetJulianDayFromEraYearDay) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::GetJulianDayFromEraYearMonthWeekDay) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::IsGregorianLeapYear) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::WeekdayOnOrBefore) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::ChangeCurrentLocale) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(::tcl::clock::ClearCaches) [list ::tcl::Pkg::source [file join $dir clock.tcl]]
+set auto_index(foreachLine) [list ::tcl::Pkg::source [file join $dir foreachline.tcl]]
+set auto_index(::tcl::history) [list ::tcl::Pkg::source [file join $dir history.tcl]]
+set auto_index(history) [list ::tcl::Pkg::source [file join $dir history.tcl]]
+set auto_index(::tcl::HistAdd) [list ::tcl::Pkg::source [file join $dir history.tcl]]
+set auto_index(::tcl::HistKeep) [list ::tcl::Pkg::source [file join $dir history.tcl]]
+set auto_index(::tcl::HistClear) [list ::tcl::Pkg::source [file join $dir history.tcl]]
+set auto_index(::tcl::HistInfo) [list ::tcl::Pkg::source [file join $dir history.tcl]]
+set auto_index(::tcl::HistRedo) [list ::tcl::Pkg::source [file join $dir history.tcl]]
+set auto_index(::tcl::HistIndex) [list ::tcl::Pkg::source [file join $dir history.tcl]]
+set auto_index(::tcl::HistEvent) [list ::tcl::Pkg::source [file join $dir history.tcl]]
+set auto_index(::tcl::HistChange) [list ::tcl::Pkg::source [file join $dir history.tcl]]
+set auto_index(::tcl::HistNextID) [list ::tcl::Pkg::source [file join $dir history.tcl]]
+set auto_index(::tcl::Pkg::CompareExtension) [list ::tcl::Pkg::source [file join $dir package.tcl]]
+set auto_index(pkg_mkIndex) [list ::tcl::Pkg::source [file join $dir package.tcl]]
+set auto_index(tclPkgSetup) [list ::tcl::Pkg::source [file join $dir package.tcl]]
+set auto_index(tclPkgUnknown) [list ::tcl::Pkg::source [file join $dir package.tcl]]
+set auto_index(::tcl::MacOSXPkgUnknown) [list ::tcl::Pkg::source [file join $dir package.tcl]]
+set auto_index(::pkg::create) [list ::tcl::Pkg::source [file join $dir package.tcl]]
+set auto_index(parray) [list ::tcl::Pkg::source [file join $dir parray.tcl]]
+set auto_index(readFile) [list ::tcl::Pkg::source [file join $dir readfile.tcl]]
+set auto_index(::safe::InterpStatics) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::InterpNested) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::interpCreate) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::interpInit) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::CheckInterp) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::interpConfigure) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::InterpCreate) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::InterpSetConfig) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::DetokPath) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::interpFindInAccessPath) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::interpAddToAccessPath) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::InterpInit) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::AddSubDirs) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::interpDelete) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::setLogCmd) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::SyncAccessPath) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::PathToken) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::TranslatePath) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::Log) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::CheckFileName) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::AliasFileSubcommand) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::AliasGlob) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::AliasSource) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::AliasLoad) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::FileInAccessPath) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::DirInAccessPath) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::BadSubcommand) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::AliasEncodingSystem) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::AliasExeName) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::RejectExcessColons) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::VarName) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::Setup) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::safe::setSyncMode) [list ::tcl::Pkg::source [file join $dir safe.tcl]]
+set auto_index(::tcl::tm::path) [list ::tcl::Pkg::source [file join $dir tm.tcl]]
+set auto_index(::tcl::tm::add) [list ::tcl::Pkg::source [file join $dir tm.tcl]]
+set auto_index(::tcl::tm::remove) [list ::tcl::Pkg::source [file join $dir tm.tcl]]
+set auto_index(::tcl::tm::list) [list ::tcl::Pkg::source [file join $dir tm.tcl]]
+set auto_index(::tcl::tm::UnknownHandler) [list ::tcl::Pkg::source [file join $dir tm.tcl]]
+set auto_index(::tcl::tm::Defaults) [list ::tcl::Pkg::source [file join $dir tm.tcl]]
+set auto_index(::tcl::tm::roots) [list ::tcl::Pkg::source [file join $dir tm.tcl]]
+set auto_index(::tcl::UpdateWordBreakREs) [list ::tcl::Pkg::source [file join $dir word.tcl]]
+set auto_index(tcl_wordBreakAfter) [list ::tcl::Pkg::source [file join $dir word.tcl]]
+set auto_index(tcl_wordBreakBefore) [list ::tcl::Pkg::source [file join $dir word.tcl]]
+set auto_index(tcl_endOfWord) [list ::tcl::Pkg::source [file join $dir word.tcl]]
+set auto_index(tcl_startOfNextWord) [list ::tcl::Pkg::source [file join $dir word.tcl]]
+set auto_index(tcl_startOfPreviousWord) [list ::tcl::Pkg::source [file join $dir word.tcl]]
+set auto_index(writeFile) [list ::tcl::Pkg::source [file join $dir writefile.tcl]]
diff --git a/library/tcltest/tcltest.tcl b/library/tcltest/tcltest.tcl
index 168f521..4522081 100644
--- a/library/tcltest/tcltest.tcl
+++ b/library/tcltest/tcltest.tcl
@@ -1347,33 +1347,33 @@ proc tcltest::DefineConstraintInitializers {} {
ConstraintInitializer unixExecs {
set code 1
- if {$::tcl_platform(platform) eq "macintosh"} {
+ if {$::tcl_platform(platform) eq "macintosh"} {
set code 0
- }
- if {$::tcl_platform(platform) eq "windows"} {
+ }
+ if {$::tcl_platform(platform) eq "windows"} {
if {[catch {
- set file _tcl_test_remove_me.txt
- makeFile {hello} $file
+ set file _tcl_test_remove_me.txt
+ makeFile {hello} $file
}]} {
- set code 0
+ set code 0
} elseif {
- [catch {exec cat $file}] ||
- [catch {exec echo hello}] ||
- [catch {exec sh -c echo hello}] ||
- [catch {exec wc $file}] ||
- [catch {exec sleep 1}] ||
- [catch {exec echo abc > $file}] ||
- [catch {exec chmod 644 $file}] ||
- [catch {exec rm $file}] ||
- [llength [auto_execok mkdir]] == 0 ||
- [llength [auto_execok fgrep]] == 0 ||
- [llength [auto_execok grep]] == 0 ||
- [llength [auto_execok ps]] == 0
+ [catch {exec cat $file}] ||
+ [catch {exec echo hello}] ||
+ [catch {exec sh -c echo hello}] ||
+ [catch {exec wc $file}] ||
+ [catch {exec sleep 1}] ||
+ [catch {exec echo abc > $file}] ||
+ [catch {exec chmod 644 $file}] ||
+ [catch {exec rm $file}] ||
+ [llength [auto_execok mkdir]] == 0 ||
+ [llength [auto_execok fgrep]] == 0 ||
+ [llength [auto_execok grep]] == 0 ||
+ [llength [auto_execok ps]] == 0
} {
- set code 0
+ set code 0
}
removeFile $file
- }
+ }
set code
}
@@ -1548,8 +1548,8 @@ proc tcltest::ProcessFlags {flagArray} {
# Call the hook
catch {
- array set flag $flagArray
- processCmdLineArgsHook [array get flag]
+ array set flag $flagArray
+ processCmdLineArgsHook [array get flag]
}
return
}
@@ -1732,7 +1732,7 @@ proc tcltest::Eval {script {ignoreOutput 1}} {
proc tcltest::CompareStrings {actual expected mode} {
variable CustomMatch
if {![info exists CustomMatch($mode)]} {
- return -code error "No matching command registered for `-match $mode'"
+ return -code error "No matching command registered for `-match $mode'"
}
set match [namespace eval :: $CustomMatch($mode) [list $expected $actual]]
if {[catch {expr {$match && $match}} result]} {
@@ -1810,55 +1810,55 @@ proc tcltest::SubstArguments {argList} {
set token ""
while {[string length $argList]} {
- # Look for the next word containing a quote: " { }
- if {[regexp -indices {[^ \t\n]*[\"\{\}]+[^ \t\n]*} \
+ # Look for the next word containing a quote: " { }
+ if {[regexp -indices {[^ \t\n]*[\"\{\}]+[^ \t\n]*} \
$argList all]} {
- # Get the text leading up to this word, but not including
+ # Get the text leading up to this word, but not including
# this word, from the argList.
- set text [string range $argList 0 \
+ set text [string range $argList 0 \
[expr {[lindex $all 0] - 1}]]
- # Get the word with the quote
- set word [string range $argList \
- [lindex $all 0] [lindex $all 1]]
-
- # Remove all text up to and including the word from the
- # argList.
- set argList [string range $argList \
- [expr {[lindex $all 1] + 1}] end]
- } else {
- # Take everything up to the end of the argList.
- set text $argList
- set word {}
- set argList {}
- }
-
- if {$token ne {}} {
- # If we saw a word with quote before, then there is a
- # multi-word token starting with that word. In this case,
- # add the text and the current word to this token.
- append token $text $word
- } else {
- # Add the text to the result. There is no need to parse
- # the text because it couldn't be a part of any multi-word
- # token. Then start a new multi-word token with the word
- # because we need to pass this token to the Tcl parser to
- # check for balancing quotes
- append result $text
- set token $word
- }
-
- if { [catch {llength $token} length] == 0 && $length == 1} {
- # The token is a valid list so add it to the result.
- # lappend result [string trim $token]
- append result \{$token\}
- set token {}
- }
+ # Get the word with the quote
+ set word [string range $argList \
+ [lindex $all 0] [lindex $all 1]]
+
+ # Remove all text up to and including the word from the
+ # argList.
+ set argList [string range $argList \
+ [expr {[lindex $all 1] + 1}] end]
+ } else {
+ # Take everything up to the end of the argList.
+ set text $argList
+ set word {}
+ set argList {}
+ }
+
+ if {$token ne {}} {
+ # If we saw a word with quote before, then there is a
+ # multi-word token starting with that word. In this case,
+ # add the text and the current word to this token.
+ append token $text $word
+ } else {
+ # Add the text to the result. There is no need to parse
+ # the text because it couldn't be a part of any multi-word
+ # token. Then start a new multi-word token with the word
+ # because we need to pass this token to the Tcl parser to
+ # check for balancing quotes
+ append result $text
+ set token $word
+ }
+
+ if { [catch {llength $token} length] == 0 && $length == 1} {
+ # The token is a valid list so add it to the result.
+ # lappend result [string trim $token]
+ append result \{$token\}
+ set token {}
+ }
}
# If the last token has not been added to the list then there
# is a problem.
if { [string length $token] } {
- error "incomplete token \"$token\""
+ error "incomplete token \"$token\""
}
return $result
@@ -2009,10 +2009,10 @@ proc tcltest::test {name description args} {
foreach {strcode numcode} {ok 0 normal 0 error 1 return 2 break 3 continue 4} {
set returnCodes [string map -nocase [list $strcode $numcode] $returnCodes]
}
- # errorCode without returnCode 1 is meaningless
- if {$errorCode ne "*" && 1 ni $returnCodes} {
- set returnCodes 1
- }
+ # errorCode without returnCode 1 is meaningless
+ if {$errorCode ne "*" && 1 ni $returnCodes} {
+ set returnCodes 1
+ }
} else {
# This is parsing for the old test command format; it is here
# for backward compatibility.
@@ -2095,7 +2095,7 @@ proc tcltest::test {name description args} {
}
set errorCodeFailure 0
if {$processTest && !$setupFailure && !$codeFailure && $returnCode == 1 && \
- ![string match $errorCode $errorCodeRes(body)]} {
+ ![string match $errorCode $errorCodeRes(body)]} {
set errorCodeFailure 1
}
@@ -2414,7 +2414,7 @@ proc tcltest::Skipped {name constraints} {
# make sure that the constraints are satisfied.
set doTest 0
- set constraints [string trim $constraints]
+ set constraints [string trim $constraints]
if {[string match {*[$\[]*} $constraints] != 0} {
# full expression, e.g. {$foo > [info tclversion]}
catch {set doTest [uplevel #0 [list expr $constraints]]}
diff --git a/library/tm.tcl b/library/tm.tcl
index 796d09f..53295c8 100644
--- a/library/tm.tcl
+++ b/library/tm.tcl
@@ -267,7 +267,7 @@ proc ::tcl::tm::UnknownHandler {original name args} {
# 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]"
+ "[::list package provide $pkgname $pkgversion];[::list source $file]"
# We abort in this unknown handler only if we got a
# satisfying candidate for the requested package.
@@ -316,7 +316,7 @@ proc ::tcl::tm::UnknownHandler {original name args} {
proc ::tcl::tm::Defaults {} {
global env tcl_platform
- regexp {^(\d+)\.(\d+)} [package provide Tcl] - major minor
+ regexp {^(\d+)\.(\d+)} [package provide tcl] - major minor
set exe [file normalize [info nameofexecutable]]
# Note that we're using [::list], not [list] because [list] means
@@ -338,7 +338,10 @@ proc ::tcl::tm::Defaults {} {
] {
if {![info exists env($ev)]} continue
foreach p [split $env($ev) $sep] {
- path add $p
+ # Paths relative to unresolvable home dirs are ignored
+ if {![catch {file tildeexpand $p} expanded_path]} {
+ path add $expanded_path
+ }
}
}
}
@@ -359,7 +362,7 @@ proc ::tcl::tm::Defaults {} {
# Calls 'path add' to paths to the list of module search paths.
proc ::tcl::tm::roots {paths} {
- regexp {^(\d+)\.(\d+)} [package provide Tcl] - major minor
+ regexp {^(\d+)\.(\d+)} [package provide tcl] - major minor
foreach pa $paths {
set p [file join $pa tcl$major]
for {set n $minor} {$n >= 0} {incr n -1} {
diff --git a/library/word.tcl b/library/word.tcl
index a993918..ca15e12 100644
--- a/library/word.tcl
+++ b/library/word.tcl
@@ -4,31 +4,21 @@
# strings. This file is primarily needed so Tk text and entry widgets behave
# properly for different platforms.
#
-# Copyright (c) 1996 Sun Microsystems, Inc.
-# Copyright (c) 1998 Scriptics Corporation.
+# Copyright © 1996 Sun Microsystems, Inc.
+# Copyright © 1998 Scriptics 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.
+# interpreted as word characters. See bug [f1253530cdd8]. Will
+# probably be removed in Tcl 9.
-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}
- }
+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
@@ -75,6 +65,9 @@ namespace eval ::tcl {
proc tcl_wordBreakAfter {str start} {
variable ::tcl::WordBreakRE
set result {-1 -1}
+ if {$start < 0} {
+ set start 0;
+ }
regexp -indices -start $start -- $WordBreakRE(after) $str result
return [lindex $result 1]
}
@@ -93,7 +86,9 @@ proc tcl_wordBreakAfter {str start} {
proc tcl_wordBreakBefore {str start} {
variable ::tcl::WordBreakRE
set result {-1 -1}
- regexp -indices -- $WordBreakRE(before) [string range $str 0 $start] result
+ if {$start >= 0} {
+ regexp -indices -- $WordBreakRE(before) [string range $str 0 $start] result
+ }
return [lindex $result 1]
}
@@ -112,6 +107,9 @@ proc tcl_wordBreakBefore {str start} {
proc tcl_endOfWord {str start} {
variable ::tcl::WordBreakRE
set result {-1 -1}
+ if {$start < 0} {
+ set start 0
+ }
regexp -indices -start $start -- $WordBreakRE(end) $str result
return [lindex $result 1]
}
@@ -130,6 +128,9 @@ proc tcl_endOfWord {str start} {
proc tcl_startOfNextWord {str start} {
variable ::tcl::WordBreakRE
set result {-1 -1}
+ if {$start < 0} {
+ set start 0
+ }
regexp -indices -start $start -- $WordBreakRE(next) $str result
return [lindex $result 1]
}
diff --git a/library/writefile.tcl b/library/writefile.tcl
new file mode 100644
index 0000000..fbd9138
--- /dev/null
+++ b/library/writefile.tcl
@@ -0,0 +1,37 @@
+# writeFile:
+# Write the contents of a file.
+#
+# Copyright © 2023 Donal K Fellows.
+#
+# See the file "license.terms" for information on usage and redistribution
+# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+#
+
+proc writeFile {args} {
+ # Parse the arguments
+ switch [llength $args] {
+ 2 {
+ lassign $args filename data
+ set mode text
+ }
+ 3 {
+ lassign $args filename mode data
+ set MODES {binary text}
+ set ERR [list -level 1 -errorcode [list TCL LOOKUP MODE $mode]]
+ set mode [tcl::prefix match -message "mode" -error $ERR $MODES $mode]
+ }
+ default {
+ set COMMAND [lindex [info level 0] 0]
+ return -code error -errorcode {TCL WRONGARGS} \
+ "wrong # args: should be \"$COMMAND filename ?mode? data\""
+ }
+ }
+
+ # Write the file
+ set f [open $filename [dict get {text w binary wb} $mode]]
+ try {
+ puts -nonewline $f $data
+ } finally {
+ close $f
+ }
+}