diff --git a/lib/cmake/expat-2.7.1/expat-config-version.cmake b/lib/cmake/expat-2.7.1/expat-config-version.cmake new file mode 100644 index 0000000000000000000000000000000000000000..b04396bf5dd5bb757ad0fbea61d5ea1322e5bbc4 --- /dev/null +++ b/lib/cmake/expat-2.7.1/expat-config-version.cmake @@ -0,0 +1,65 @@ +# This is a basic version file for the Config-mode of find_package(). +# It is used by write_basic_package_version_file() as input file for configure_file() +# to create a version-file which can be installed along a config.cmake file. +# +# The created file sets PACKAGE_VERSION_EXACT if the current version string and +# the requested version string are exactly the same and it sets +# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version, +# but only if the requested major version is the same as the current one. +# The variable CVF_VERSION must be set before calling configure_file(). + + +set(PACKAGE_VERSION "2.7.1") + +if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else() + + if("2.7.1" MATCHES "^([0-9]+)\\.") + set(CVF_VERSION_MAJOR "${CMAKE_MATCH_1}") + if(NOT CVF_VERSION_MAJOR VERSION_EQUAL 0) + string(REGEX REPLACE "^0+" "" CVF_VERSION_MAJOR "${CVF_VERSION_MAJOR}") + endif() + else() + set(CVF_VERSION_MAJOR "2.7.1") + endif() + + if(PACKAGE_FIND_VERSION_RANGE) + # both endpoints of the range must have the expected major version + math (EXPR CVF_VERSION_MAJOR_NEXT "${CVF_VERSION_MAJOR} + 1") + if (NOT PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR + OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX_MAJOR STREQUAL CVF_VERSION_MAJOR) + OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX VERSION_LESS_EQUAL CVF_VERSION_MAJOR_NEXT))) + set(PACKAGE_VERSION_COMPATIBLE FALSE) + elseif(PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR + AND ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS_EQUAL PACKAGE_FIND_VERSION_MAX) + OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MAX))) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + else() + set(PACKAGE_VERSION_COMPATIBLE FALSE) + endif() + else() + if(PACKAGE_FIND_VERSION_MAJOR STREQUAL CVF_VERSION_MAJOR) + set(PACKAGE_VERSION_COMPATIBLE TRUE) + else() + set(PACKAGE_VERSION_COMPATIBLE FALSE) + endif() + + if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) + set(PACKAGE_VERSION_EXACT TRUE) + endif() + endif() +endif() + + +# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it: +if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "8" STREQUAL "") + return() +endif() + +# check that the installed version has the same 32/64bit-ness as the one which is currently searching: +if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8") + math(EXPR installedBits "8 * 8") + set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)") + set(PACKAGE_VERSION_UNSUITABLE TRUE) +endif() diff --git a/lib/cmake/expat-2.7.1/expat-config.cmake b/lib/cmake/expat-2.7.1/expat-config.cmake new file mode 100644 index 0000000000000000000000000000000000000000..36a013198b665a6b72e2016253093a1e36b33aca --- /dev/null +++ b/lib/cmake/expat-2.7.1/expat-config.cmake @@ -0,0 +1,99 @@ +# __ __ _ +# ___\ \/ /_ __ __ _| |_ +# / _ \\ /| '_ \ / _` | __| +# | __// \| |_) | (_| | |_ +# \___/_/\_\ .__/ \__,_|\__| +# |_| XML parser +# +# Copyright (c) 2019 Expat development team +# Licensed under the MIT license: +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the +# following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +# USE OR OTHER DEALINGS IN THE SOFTWARE. +# +if(NOT _expat_config_included) + # Protect against multiple inclusion + set(_expat_config_included TRUE) + + +include("${CMAKE_CURRENT_LIST_DIR}/expat.cmake") + + +####### Expanded from @PACKAGE_INIT@ by configure_package_config_file() ####### +####### Any changes to this file will be overwritten by the next CMake run #### +####### The input file was expat-config.cmake.in ######## + +get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE) + +macro(set_and_check _var _file) + set(${_var} "${_file}") + if(NOT EXISTS "${_file}") + message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !") + endif() +endmacro() + +macro(check_required_components _NAME) + foreach(comp ${${_NAME}_FIND_COMPONENTS}) + if(NOT ${_NAME}_${comp}_FOUND) + if(${_NAME}_FIND_REQUIRED_${comp}) + set(${_NAME}_FOUND FALSE) + endif() + endif() + endforeach() +endmacro() + +#################################################################################### + +# +# Supported components +# +macro(expat_register_component _NAME _AVAILABE) + set(expat_${_NAME}_FOUND ${_AVAILABE}) +endmacro() + +expat_register_component(attr_info OFF) +expat_register_component(dtd ON) +expat_register_component(large_size OFF) +expat_register_component(min_size OFF) +expat_register_component(ns ON) + +if(1024) + expat_register_component(context_bytes ON) +else() + expat_register_component(context_bytes OFF) +endif() + +if("char" STREQUAL "char") + expat_register_component(char ON) + expat_register_component(ushort OFF) + expat_register_component(wchar_t OFF) +elseif("char" STREQUAL "ushort") + expat_register_component(char OFF) + expat_register_component(ushort ON) + expat_register_component(wchar_t OFF) +elseif("char" STREQUAL "wchar_t") + expat_register_component(char OFF) + expat_register_component(ushort OFF) + expat_register_component(wchar_t ON) +endif() + +check_required_components(expat) + + +endif(NOT _expat_config_included) diff --git a/lib/cmake/expat-2.7.1/expat-noconfig.cmake b/lib/cmake/expat-2.7.1/expat-noconfig.cmake new file mode 100644 index 0000000000000000000000000000000000000000..bdb03959da4d65fe9ea745e0fc6d08b03c70dafc --- /dev/null +++ b/lib/cmake/expat-2.7.1/expat-noconfig.cmake @@ -0,0 +1,19 @@ +#---------------------------------------------------------------- +# Generated CMake target import file for configuration "NoConfig". +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Import target "expat::expat" for configuration "NoConfig" +set_property(TARGET expat::expat APPEND PROPERTY IMPORTED_CONFIGURATIONS NOCONFIG) +set_target_properties(expat::expat PROPERTIES + IMPORTED_LOCATION_NOCONFIG "${_IMPORT_PREFIX}/lib/libexpat.so.1.10.2" + IMPORTED_SONAME_NOCONFIG "libexpat.so.1" + ) + +list(APPEND _cmake_import_check_targets expat::expat ) +list(APPEND _cmake_import_check_files_for_expat::expat "${_IMPORT_PREFIX}/lib/libexpat.so.1.10.2" ) + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) diff --git a/lib/cmake/expat-2.7.1/expat.cmake b/lib/cmake/expat-2.7.1/expat.cmake new file mode 100644 index 0000000000000000000000000000000000000000..ecb2df12ada040f0197bf608daad810968046f65 --- /dev/null +++ b/lib/cmake/expat-2.7.1/expat.cmake @@ -0,0 +1,107 @@ +# Generated by CMake + +if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.8) + message(FATAL_ERROR "CMake >= 2.8.12 required") +endif() +if(CMAKE_VERSION VERSION_LESS "2.8.12") + message(FATAL_ERROR "CMake >= 2.8.12 required") +endif() +cmake_policy(PUSH) +cmake_policy(VERSION 2.8.12...3.29) +#---------------------------------------------------------------- +# Generated CMake target import file. +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Protect against multiple inclusion, which would fail when already imported targets are added once more. +set(_cmake_targets_defined "") +set(_cmake_targets_not_defined "") +set(_cmake_expected_targets "") +foreach(_cmake_expected_target IN ITEMS expat::expat) + list(APPEND _cmake_expected_targets "${_cmake_expected_target}") + if(TARGET "${_cmake_expected_target}") + list(APPEND _cmake_targets_defined "${_cmake_expected_target}") + else() + list(APPEND _cmake_targets_not_defined "${_cmake_expected_target}") + endif() +endforeach() +unset(_cmake_expected_target) +if(_cmake_targets_defined STREQUAL _cmake_expected_targets) + unset(_cmake_targets_defined) + unset(_cmake_targets_not_defined) + unset(_cmake_expected_targets) + unset(CMAKE_IMPORT_FILE_VERSION) + cmake_policy(POP) + return() +endif() +if(NOT _cmake_targets_defined STREQUAL "") + string(REPLACE ";" ", " _cmake_targets_defined_text "${_cmake_targets_defined}") + string(REPLACE ";" ", " _cmake_targets_not_defined_text "${_cmake_targets_not_defined}") + message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_cmake_targets_defined_text}\nTargets not yet defined: ${_cmake_targets_not_defined_text}\n") +endif() +unset(_cmake_targets_defined) +unset(_cmake_targets_not_defined) +unset(_cmake_expected_targets) + + +# Compute the installation prefix relative to this file. +get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +if(_IMPORT_PREFIX STREQUAL "/") + set(_IMPORT_PREFIX "") +endif() + +# Create imported target expat::expat +add_library(expat::expat SHARED IMPORTED) + +set_target_properties(expat::expat PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include" + INTERFACE_LINK_LIBRARIES "m" +) + +# Load information for each installed configuration. +file(GLOB _cmake_config_files "${CMAKE_CURRENT_LIST_DIR}/expat-*.cmake") +foreach(_cmake_config_file IN LISTS _cmake_config_files) + include("${_cmake_config_file}") +endforeach() +unset(_cmake_config_file) +unset(_cmake_config_files) + +# Cleanup temporary variables. +set(_IMPORT_PREFIX) + +# Loop over all imported files and verify that they actually exist +foreach(_cmake_target IN LISTS _cmake_import_check_targets) + if(CMAKE_VERSION VERSION_LESS "3.28" + OR NOT DEFINED _cmake_import_check_xcframework_for_${_cmake_target} + OR NOT IS_DIRECTORY "${_cmake_import_check_xcframework_for_${_cmake_target}}") + foreach(_cmake_file IN LISTS "_cmake_import_check_files_for_${_cmake_target}") + if(NOT EXISTS "${_cmake_file}") + message(FATAL_ERROR "The imported target \"${_cmake_target}\" references the file + \"${_cmake_file}\" +but this file does not exist. Possible reasons include: +* The file was deleted, renamed, or moved to another location. +* An install or uninstall procedure did not complete successfully. +* The installation package was faulty and contained + \"${CMAKE_CURRENT_LIST_FILE}\" +but not all the files it references. +") + endif() + endforeach() + endif() + unset(_cmake_file) + unset("_cmake_import_check_files_for_${_cmake_target}") +endforeach() +unset(_cmake_target) +unset(_cmake_import_check_targets) + +# This file does not depend on other imported targets which have +# been exported from the same project but in a separate export set. + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) +cmake_policy(POP) diff --git a/lib/cmake/libdeflate/libdeflate-config-version.cmake b/lib/cmake/libdeflate/libdeflate-config-version.cmake new file mode 100644 index 0000000000000000000000000000000000000000..fdf799bb0af9351c9bc90066372f4b4e692725cb --- /dev/null +++ b/lib/cmake/libdeflate/libdeflate-config-version.cmake @@ -0,0 +1,43 @@ +# This is a basic version file for the Config-mode of find_package(). +# It is used by write_basic_package_version_file() as input file for configure_file() +# to create a version-file which can be installed along a config.cmake file. +# +# The created file sets PACKAGE_VERSION_EXACT if the current version string and +# the requested version string are exactly the same and it sets +# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version. +# The variable CVF_VERSION must be set before calling configure_file(). + +set(PACKAGE_VERSION "1.22") + +if (PACKAGE_FIND_VERSION_RANGE) + # Package version must be in the requested version range + if ((PACKAGE_FIND_VERSION_RANGE_MIN STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MIN) + OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_GREATER PACKAGE_FIND_VERSION_MAX) + OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_GREATER_EQUAL PACKAGE_FIND_VERSION_MAX))) + set(PACKAGE_VERSION_COMPATIBLE FALSE) + else() + set(PACKAGE_VERSION_COMPATIBLE TRUE) + endif() +else() + if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) + set(PACKAGE_VERSION_COMPATIBLE FALSE) + else() + set(PACKAGE_VERSION_COMPATIBLE TRUE) + if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) + set(PACKAGE_VERSION_EXACT TRUE) + endif() + endif() +endif() + + +# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it: +if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "8" STREQUAL "") + return() +endif() + +# check that the installed version has the same 32/64bit-ness as the one which is currently searching: +if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8") + math(EXPR installedBits "8 * 8") + set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)") + set(PACKAGE_VERSION_UNSUITABLE TRUE) +endif() diff --git a/lib/cmake/libdeflate/libdeflate-config.cmake b/lib/cmake/libdeflate/libdeflate-config.cmake new file mode 100644 index 0000000000000000000000000000000000000000..e9bf76e96357d649e0fc14d95e2a3dda22b43992 --- /dev/null +++ b/lib/cmake/libdeflate/libdeflate-config.cmake @@ -0,0 +1,27 @@ + +####### Expanded from @PACKAGE_INIT@ by configure_package_config_file() ####### +####### Any changes to this file will be overwritten by the next CMake run #### +####### The input file was libdeflate-config.cmake.in ######## + +get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE) + +macro(set_and_check _var _file) + set(${_var} "${_file}") + if(NOT EXISTS "${_file}") + message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !") + endif() +endmacro() + +macro(check_required_components _NAME) + foreach(comp ${${_NAME}_FIND_COMPONENTS}) + if(NOT ${_NAME}_${comp}_FOUND) + if(${_NAME}_FIND_REQUIRED_${comp}) + set(${_NAME}_FOUND FALSE) + endif() + endif() + endforeach() +endmacro() + +#################################################################################### + +include("${CMAKE_CURRENT_LIST_DIR}/libdeflate-targets.cmake") diff --git a/lib/cmake/libdeflate/libdeflate-targets-release.cmake b/lib/cmake/libdeflate/libdeflate-targets-release.cmake new file mode 100644 index 0000000000000000000000000000000000000000..fc8fc629e7ef9d6757592fe1edcba4d45026f325 --- /dev/null +++ b/lib/cmake/libdeflate/libdeflate-targets-release.cmake @@ -0,0 +1,19 @@ +#---------------------------------------------------------------- +# Generated CMake target import file for configuration "Release". +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Import target "libdeflate::libdeflate_shared" for configuration "Release" +set_property(TARGET libdeflate::libdeflate_shared APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(libdeflate::libdeflate_shared PROPERTIES + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libdeflate.so.0" + IMPORTED_SONAME_RELEASE "libdeflate.so.0" + ) + +list(APPEND _cmake_import_check_targets libdeflate::libdeflate_shared ) +list(APPEND _cmake_import_check_files_for_libdeflate::libdeflate_shared "${_IMPORT_PREFIX}/lib/libdeflate.so.0" ) + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) diff --git a/lib/itcl4.2.4/itcl.tcl b/lib/itcl4.2.4/itcl.tcl new file mode 100644 index 0000000000000000000000000000000000000000..f68481d7f8717a87a94a358d58a5032775f6ba3c --- /dev/null +++ b/lib/itcl4.2.4/itcl.tcl @@ -0,0 +1,151 @@ +# +# itcl.tcl +# ---------------------------------------------------------------------- +# Invoked automatically upon startup to customize the interpreter +# for [incr Tcl]. +# ---------------------------------------------------------------------- +# AUTHOR: Michael J. McLennan +# Bell Labs Innovations for Lucent Technologies +# mmclennan@lucent.com +# http://www.tcltk.com/itcl +# ---------------------------------------------------------------------- +# Copyright (c) 1993-1998 Lucent Technologies, Inc. +# ====================================================================== +# See the file "license.terms" for information on usage and +# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES. + +proc ::itcl::delete_helper { name args } { + ::itcl::delete object $name +} + +# ---------------------------------------------------------------------- +# USAGE: local ? ...? +# +# Creates a new object called in class , passing +# the remaining 's to the constructor. Unlike the usual +# [incr Tcl] objects, however, an object created by this procedure +# will be automatically deleted when the local call frame is destroyed. +# This command is useful for creating objects that should only remain +# alive until a procedure exits. +# ---------------------------------------------------------------------- +proc ::itcl::local {class name args} { + set ptr [uplevel [list $class $name] $args] + uplevel [list set itcl-local-$ptr $ptr] + set cmd [uplevel namespace which -command $ptr] + uplevel [list trace add variable itcl-local-$ptr unset \ + "::itcl::delete_helper $cmd"] + return $ptr +} + +# ---------------------------------------------------------------------- +# auto_mkindex +# ---------------------------------------------------------------------- +# Define Itcl commands that will be recognized by the auto_mkindex +# parser in Tcl... +# + +# +# USAGE: itcl::class name body +# Adds an entry for the given class declaration. +# +foreach __cmd {itcl::class class itcl::type type ictl::widget widget itcl::widgetadaptor widgetadaptor itcl::extendedclass extendedclass} { + auto_mkindex_parser::command $__cmd {name body} { + variable index + variable scriptFile + append index "set [list auto_index([fullname $name])]" + append index " \[list source \[file join \$dir [list $scriptFile]\]\]\n" + + variable parser + variable contextStack + set contextStack [linsert $contextStack 0 $name] + $parser eval $body + set contextStack [lrange $contextStack 1 end] + } +} + +# +# USAGE: itcl::body name arglist body +# Adds an entry for the given method/proc body. +# +foreach __cmd {itcl::body body} { + auto_mkindex_parser::command $__cmd {name arglist body} { + variable index + variable scriptFile + append index "set [list auto_index([fullname $name])]" + append index " \[list source \[file join \$dir [list $scriptFile]\]\]\n" + } +} + +# +# USAGE: itcl::configbody name arglist body +# Adds an entry for the given method/proc body. +# +foreach __cmd {itcl::configbody configbody} { + auto_mkindex_parser::command $__cmd {name body} { + variable index + variable scriptFile + append index "set [list auto_index([fullname $name])]" + append index " \[list source \[file join \$dir [list $scriptFile]\]\]\n" + } +} + +# +# USAGE: ensemble name ?body? +# Adds an entry to the auto index list for the given ensemble name. +# +foreach __cmd {itcl::ensemble ensemble} { + auto_mkindex_parser::command $__cmd {name {body ""}} { + variable index + variable scriptFile + append index "set [list auto_index([fullname $name])]" + append index " \[list source \[file join \$dir [list $scriptFile]\]\]\n" + } +} + +# +# USAGE: public arg ?arg arg...? +# protected arg ?arg arg...? +# private arg ?arg arg...? +# +# Evaluates the arguments as commands, so we can recognize proc +# declarations within classes. +# +foreach __cmd {public protected private} { + auto_mkindex_parser::command $__cmd {args} { + variable parser + $parser eval $args + } +} + +# SF bug #246 unset variable __cmd to avoid problems in user programs!! +unset __cmd + +# ---------------------------------------------------------------------- +# auto_import +# ---------------------------------------------------------------------- +# This procedure overrides the usual "auto_import" function in the +# Tcl library. It is invoked during "namespace import" to make see +# if the imported commands reside in an autoloaded library. If so, +# stubs are created to represent the commands. Executing a stub +# later on causes the real implementation to be autoloaded. +# +# Arguments - +# pattern The pattern of commands being imported (like "foo::*") +# a canonical namespace as returned by [namespace current] + +proc auto_import {pattern} { + global auto_index + + set ns [uplevel namespace current] + set patternList [auto_qualify $pattern $ns] + + auto_load_index + + foreach pattern $patternList { + foreach name [array names auto_index $pattern] { + if {"" == [info commands $name]} { + ::itcl::import::stub create $name + } + } + } +} diff --git a/lib/itcl4.2.4/itclConfig.sh b/lib/itcl4.2.4/itclConfig.sh new file mode 100644 index 0000000000000000000000000000000000000000..1d4ae5d22f39fc924e6f82ec70c93af3b0cf46e6 --- /dev/null +++ b/lib/itcl4.2.4/itclConfig.sh @@ -0,0 +1,67 @@ +# itclConfig.sh -- +# +# This shell script (for sh) is generated automatically by Itcl's +# configure script. It will create shell variables for most of +# the configuration options discovered by the configure script. +# This script is intended to be included by the configure scripts +# for Itcl extensions so that they don't have to figure this all +# out for themselves. This file does not duplicate information +# already provided by tclConfig.sh, so you may need to use that +# file in addition to this one. +# +# The information in this file is specific to a single platform. + +# Itcl's version number. +itcl_VERSION='4.2.4' +ITCL_VERSION='4.2.4' + +# The name of the Itcl library (may be either a .a file or a shared library): +itcl_LIB_FILE=libitcl4.2.4.so +ITCL_LIB_FILE=libitcl4.2.4.so + +# String to pass to linker to pick up the Itcl library from its +# build directory. +itcl_BUILD_LIB_SPEC='-L/croot/tk_1748849386456/work/tcl8.6.14/unix/pkgs/itcl4.2.4 -litcl4.2.4' +ITCL_BUILD_LIB_SPEC='-L/croot/tk_1748849386456/work/tcl8.6.14/unix/pkgs/itcl4.2.4 -litcl4.2.4' + +# String to pass to linker to pick up the Itcl library from its +# installed directory. +itcl_LIB_SPEC='-L/home/aioz-nghiale/anaconda3/envs/testing_softzoo_pointe/lib/itcl4.2.4 -litcl4.2.4' +ITCL_LIB_SPEC='-L/home/aioz-nghiale/anaconda3/envs/testing_softzoo_pointe/lib/itcl4.2.4 -litcl4.2.4' + +# The name of the Itcl stub library (a .a file): +itcl_STUB_LIB_FILE=libitclstub4.2.4.a +ITCL_STUB_LIB_FILE=libitclstub4.2.4.a + +# String to pass to linker to pick up the Itcl stub library from its +# build directory. +itcl_BUILD_STUB_LIB_SPEC='-L/croot/tk_1748849386456/work/tcl8.6.14/unix/pkgs/itcl4.2.4 -litclstub4.2.4' +ITCL_BUILD_STUB_LIB_SPEC='-L/croot/tk_1748849386456/work/tcl8.6.14/unix/pkgs/itcl4.2.4 -litclstub4.2.4' + +# String to pass to linker to pick up the Itcl stub library from its +# installed directory. +itcl_STUB_LIB_SPEC='-L/home/aioz-nghiale/anaconda3/envs/testing_softzoo_pointe/lib/itcl4.2.4 -litclstub4.2.4' +ITCL_STUB_LIB_SPEC='-L/home/aioz-nghiale/anaconda3/envs/testing_softzoo_pointe/lib/itcl4.2.4 -litclstub4.2.4' + +# String to pass to linker to pick up the Itcl stub library from its +# build directory. +itcl_BUILD_STUB_LIB_PATH='/croot/tk_1748849386456/work/tcl8.6.14/unix/pkgs/itcl4.2.4/libitclstub4.2.4.a' +ITCL_BUILD_STUB_LIB_PATH='/croot/tk_1748849386456/work/tcl8.6.14/unix/pkgs/itcl4.2.4/libitclstub4.2.4.a' + +# String to pass to linker to pick up the Itcl stub library from its +# installed directory. +itcl_STUB_LIB_PATH='/home/aioz-nghiale/anaconda3/envs/testing_softzoo_pointe/lib/itcl4.2.4/libitclstub4.2.4.a' +ITCL_STUB_LIB_PATH='/home/aioz-nghiale/anaconda3/envs/testing_softzoo_pointe/lib/itcl4.2.4/libitclstub4.2.4.a' + +# Location of the top-level source directories from which [incr Tcl] +# was built. This is the directory that contains generic, unix, etc. +# If [incr Tcl] was compiled in a different place than the directory +# containing the source files, this points to the location of the sources, +# not the location where [incr Tcl] was compiled. +itcl_SRC_DIR='/croot/tk_1748849386456/work/tcl8.6.14/pkgs/itcl4.2.4' +ITCL_SRC_DIR='/croot/tk_1748849386456/work/tcl8.6.14/pkgs/itcl4.2.4' + +# String to pass to the compiler so that an extension can +# find installed Itcl headers. +itcl_INCLUDE_SPEC='' +ITCL_INCLUDE_SPEC='' diff --git a/lib/itcl4.2.4/itclHullCmds.tcl b/lib/itcl4.2.4/itclHullCmds.tcl new file mode 100644 index 0000000000000000000000000000000000000000..5cd07f606aebeac94452671fb02c4347c765ebf3 --- /dev/null +++ b/lib/itcl4.2.4/itclHullCmds.tcl @@ -0,0 +1,562 @@ +# +# itclHullCmds.tcl +# ---------------------------------------------------------------------- +# Invoked automatically upon startup to customize the interpreter +# for [incr Tcl] when one of setupcomponent or createhull is called. +# ---------------------------------------------------------------------- +# AUTHOR: Arnulf P. Wiedemann +# +# ---------------------------------------------------------------------- +# Copyright (c) 2008 Arnulf P. Wiedemann +# ====================================================================== +# See the file "license.terms" for information on usage and +# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES. + +package require Tk 8.6 9 + +namespace eval ::itcl::internal::commands { + +# ======================= widgetDeleted =========================== + +proc widgetDeleted {oldName newName op} { + # The widget is beeing deleted, so we have to delete the object + # which had the widget as itcl_hull too! + # We have to get the real name from for example + # ::itcl::internal::widgets::hull1.lw + # we need only .lw here + +#puts stderr "widgetDeleted!$oldName!$newName!$op!" + set cmdName [namespace tail $oldName] + set flds [split $cmdName {.}] + set cmdName .[join [lrange $flds 1 end] {.}] +#puts stderr "DELWIDGET![namespace current]!$cmdName![::info command $cmdName]!" + rename $cmdName {} +} + +} + +namespace eval ::itcl::builtin { + +# ======================= createhull =========================== +# the hull widget is a tk widget which is the (mega) widget handled behind the itcl +# extendedclass/itcl widget. +# It is created be renaming the itcl class object to a temporary name _ +# creating the widget with the +# appropriate options and the installing that as the "hull" widget (the container) +# All the options in args and the options delegated to component itcl_hull are used +# Then a unique name (hull_widget_name) in the itcl namespace is created for widget: +# ::itcl::internal::widgets::hull +# and widget is renamed to that name +# Finally the _ is renamed to the original again +# Component itcl_hull is created if not existent +# itcl_hull is set to the hull_widget_name and the +# is returned to the caller +# ============================================================== + +proc createhull {widget_type path args} { + variable hullCount + upvar this this + upvar win win + + +#puts stderr "il-1![::info level -1]!$this!" +#puts stderr "createhull!$widget_type!$path!$args!$this![::info command $this]!" +#puts stderr "ns1![uplevel 1 namespace current]!" +#puts stderr "ns2![uplevel 2 namespace current]!" +#puts stderr "ns3![uplevel 3 namespace current]!" +#puts stderr "level-1![::info level -1]!" +#puts stderr "level-2![::info level -2]!" +# set my_this [namespace tail $this] + set my_this $this + set tmp $my_this +#puts stderr "II![::info command $this]![::info command $tmp]!" +#puts stderr "rename1!rename $my_this ${tmp}_!" + rename ::$my_this ${tmp}_ + set options [list] + foreach {option_name value} $args { + switch -glob -- $option_name { + -class { + lappend options $option_name [namespace tail $value] + } + -* { + lappend options $option_name $value + } + default { + return -code error "bad option name\"$option_name\" options must start with a \"-\"" + } + } + } + set my_win [namespace tail $path] + set cmd [list $widget_type $my_win] +#puts stderr "my_win!$my_win!cmd!$cmd!$path!" + if {[llength $options] > 0} { + lappend cmd {*}$options + } + set widget [uplevel 1 $cmd] +#puts stderr "widget!$widget!" + trace add command $widget delete ::itcl::internal::commands::widgetDeleted + set opts [uplevel 1 info delegated options] + foreach entry $opts { + foreach {optName compName} $entry break + if {$compName eq "itcl_hull"} { + set optInfos [uplevel 1 info delegated option $optName] + set realOptName [lindex $optInfos 4] + # strip off the "-" at the beginning + set myOptName [string range $realOptName 1 end] + set my_opt_val [option get $my_win $myOptName *] + if {$my_opt_val ne ""} { + $my_win configure -$myOptName $my_opt_val + } + } + } + set idx 1 + while {1} { + set widgetName ::itcl::internal::widgets::hull${idx}$my_win +#puts stderr "widgetName!$widgetName!" + if {[string length [::info command $widgetName]] == 0} { + break + } + incr idx + } +#puts stderr "rename2!rename $widget $widgetName!" + set dorename 0 + rename $widget $widgetName +#puts stderr "rename3!rename ${tmp}_ $tmp![::info command ${tmp}_]!my_this!$my_this!" + rename ${tmp}_ ::$tmp + set exists [uplevel 1 ::info exists itcl_hull] + if {!$exists} { + # that does not yet work, beacause of problems with resolving + ::itcl::addcomponent $my_this itcl_hull + } + upvar itcl_hull itcl_hull + ::itcl::setcomponent $my_this itcl_hull $widgetName +#puts stderr "IC![::info command $my_win]!" + set exists [uplevel 1 ::info exists itcl_interior] + if {!$exists} { + # that does not yet work, beacause of problems with resolving + ::itcl::addcomponent $this itcl_interior + } + upvar itcl_interior itcl_interior + set itcl_interior $my_win +#puts stderr "hull end!win!$win!itcl_hull!$itcl_hull!itcl_interior!$itcl_interior!" + return $my_win +} + +# ======================= addToItclOptions =========================== + +proc addToItclOptions {my_class my_win myOptions argsDict} { + upvar win win + upvar itcl_hull itcl_hull + + set opt_lst [list configure] + foreach opt [lsort $myOptions] { +#puts stderr "IOPT!$opt!$my_class!$my_win![::itcl::is class $my_class]!" + set isClass [::itcl::is class $my_class] + set found 0 + if {$isClass} { + if {[catch { + set resource [namespace eval $my_class info option $opt -resource] + set class [namespace eval $my_class info option $opt -class] + set default_val [uplevel 2 info option $opt -default] + set found 1 + } msg]} { +# puts stderr "MSG!$opt!$my_class!$msg!" + } + } else { + set tmp_win [uplevel #0 $my_class .___xx] + + set my_info [$tmp_win configure $opt] + set resource [lindex $my_info 1] + set class [lindex $my_info 2] + set default_val [lindex $my_info 3] + uplevel #0 destroy $tmp_win + set found 1 + } + if {$found} { + if {[catch { + set val [uplevel #0 ::option get $win $resource $class] + } msg]} { + set val "" + } + if {[::dict exists $argsDict $opt]} { + # we have an explicitly set option + set val [::dict get $argsDict $opt] + } else { + if {[string length $val] == 0} { + set val $default_val + } + } + set ::itcl::internal::variables::${my_win}::itcl_options($opt) $val + set ::itcl::internal::variables::${my_win}::__itcl_option_infos($opt) [list $resource $class $default_val] +#puts stderr "OPT1!$opt!$val!" +# uplevel 1 [list set itcl_options($opt) [list $val]] + if {[catch {uplevel 1 $win configure $opt [list $val]} msg]} { +#puts stderr "addToItclOptions ERR!$msg!$my_class!$win!configure!$opt!$val!" + } + } + } +} + +# ======================= setupcomponent =========================== + +proc setupcomponent {comp using widget_type path args} { + upvar this this + upvar win win + upvar itcl_hull itcl_hull + +#puts stderr "setupcomponent!$comp!$widget_type!$path!$args!$this!$win!$itcl_hull!" +#puts stderr "CONT![uplevel 1 info context]!" +#puts stderr "ns1![uplevel 1 namespace current]!" +#puts stderr "ns2![uplevel 2 namespace current]!" +#puts stderr "ns3![uplevel 3 namespace current]!" + set my_comp_object [lindex [uplevel 1 info context] 1] + if {[::info exists ::itcl::internal::component_objects($my_comp_object)]} { + set my_comp_object [set ::itcl::internal::component_objects($my_comp_object)] + } else { + set ::itcl::internal::component_objects($path) $my_comp_object + } + set options [list] + foreach {option_name value} $args { + switch -glob -- $option_name { + -* { + lappend options $option_name $value + } + default { + return -code error "bad option name\"$option_name\" options must start with a \"-\"" + } + } + } + if {[llength $args]} { + set argsDict [dict create {*}$args] + } else { + set argsDict [dict create] + } + set cmd [list $widget_type $path] + if {[llength $options] > 0} { + lappend cmd {*}$options + } +#puts stderr "cmd0![::info command $widget_type]!$path![::info command $path]!" +#puts stderr "cmd1!$cmd!" +# set my_comp [uplevel 3 $cmd] + set my_comp [uplevel #0 $cmd] +#puts stderr 111![::info command $path]! + ::itcl::setcomponent $this $comp $my_comp + set opts [uplevel 1 info delegated options] + foreach entry $opts { + foreach {optName compName} $entry break + if {$compName eq $my_comp} { + set optInfos [uplevel 1 info delegated option $optName] + set realOptName [lindex $optInfos 4] + # strip off the "-" at the beginning + set myOptName [string range $realOptName 1 end] + set my_opt_val [option get $my_win $myOptName *] + if {$my_opt_val ne ""} { + $my_comp configure -$myOptName $my_opt_val + } + } + } + set my_class $widget_type + set my_parent_class [uplevel 1 namespace current] + if {[catch { + set myOptions [namespace eval $my_class {info classoptions}] + } msg]} { + set myOptions [list] + } + foreach entry [$path configure] { + foreach {opt dummy1 dummy2 dummy3} $entry break + lappend myOptions $opt + } +#puts stderr "OPTS!$myOptions!" + addToItclOptions $widget_type $my_comp_object $myOptions $argsDict +#puts stderr END!$path![::info command $path]! +} + +proc itcl_initoptions {args} { +puts stderr "ITCL_INITOPT!$args!" +} + +# ======================= initoptions =========================== + +proc initoptions {args} { + upvar win win + upvar itcl_hull itcl_hull + upvar itcl_option_components itcl_option_components + +#puts stderr "INITOPT!!$win!" + if {[llength $args]} { + set argsDict [dict create {*}$args] + } else { + set argsDict [dict create] + } + set my_class [uplevel 1 namespace current] + set myOptions [namespace eval $my_class {info classoptions}] + if {[dict exists $::itcl::internal::dicts::classComponents $my_class]} { + set class_info_dict [dict get $::itcl::internal::dicts::classComponents $my_class] +# set myOptions [lsort -unique [namespace eval $my_class {info options}]] + foreach comp [uplevel 1 info components] { + if {[dict exists $class_info_dict $comp -keptoptions]} { + foreach my_opt [dict get $class_info_dict $comp -keptoptions] { + if {[lsearch $myOptions $my_opt] < 0} { +#puts stderr "KEOPT!$my_opt!" + lappend myOptions $my_opt + } + } + } + } + } else { + set class_info_dict [list] + } +#puts stderr "OPTS!$win!$my_class![join [lsort $myOptions]] \n]!" + set opt_lst [list configure] + set my_win $win + foreach opt [lsort $myOptions] { + set found 0 + if {[catch { + set resource [uplevel 1 info option $opt -resource] + set class [uplevel 1 info option $opt -class] + set default_val [uplevel 1 info option $opt -default] + set found 1 + } msg]} { +# puts stderr "MSG!$opt!$msg!" + } +#puts stderr "OPT!$opt!$found!" + if {$found} { + if {[catch { + set val [uplevel #0 ::option get $my_win $resource $class] + } msg]} { + set val "" + } + if {[::dict exists $argsDict $opt]} { + # we have an explicitly set option + set val [::dict get $argsDict $opt] + } else { + if {[string length $val] == 0} { + set val $default_val + } + } + set ::itcl::internal::variables::${win}::itcl_options($opt) $val + set ::itcl::internal::variables::${win}::__itcl_option_infos($opt) [list $resource $class $default_val] +#puts stderr "OPT1!$opt!$val!" +# uplevel 1 [list set itcl_options($opt) [list $val]] + if {[catch {uplevel 1 $my_win configure $opt [list $val]} msg]} { +puts stderr "initoptions ERR!$msg!$my_class!$my_win!configure!$opt!$val!" + } + } + foreach comp [dict keys $class_info_dict] { +#puts stderr "OPT1!$opt!$comp![dict get $class_info_dict $comp]!" + if {[dict exists $class_info_dict $comp -keptoptions]} { + if {[lsearch [dict get $class_info_dict $comp -keptoptions] $opt] >= 0} { + if {$found == 0} { + # we use the option value of the first component for setting + # the option, as the components are traversed in the dict + # depending on the ordering of the component creation!! + set my_info [uplevel 1 \[set $comp\] configure $opt] + set resource [lindex $my_info 1] + set class [lindex $my_info 2] + set default_val [lindex $my_info 3] + set found 2 + set val [uplevel #0 ::option get $my_win $resource $class] + if {[::dict exists $argsDict $opt]} { + # we have an explicitly set option + set val [::dict get $argsDict $opt] + } else { + if {[string length $val] == 0} { + set val $default_val + } + } +#puts stderr "OPT2!$opt!$val!" + set ::itcl::internal::variables::${win}::itcl_options($opt) $val + set ::itcl::internal::variables::${win}::__itcl_option_infos($opt) [list $resource $class $default_val] +# uplevel 1 [list set itcl_options($opt) [list $val]] + } + if {[catch {uplevel 1 \[set $comp\] configure $opt [list $val]} msg]} { +puts stderr "initoptions ERR2!$msg!$my_class!$comp!configure!$opt!$val!" + } + if {![uplevel 1 info exists itcl_option_components($opt)]} { + set itcl_option_components($opt) [list] + } + if {[lsearch [set itcl_option_components($opt)] $comp] < 0} { + if {![catch { + set optval [uplevel 1 [list set itcl_options($opt)]] + } msg3]} { + uplevel 1 \[set $comp\] configure $opt $optval + } + lappend itcl_option_components($opt) $comp + } + } + } + } + } +# uplevel 1 $opt_lst +} + +# ======================= setoptions =========================== + +proc setoptions {args} { + +#puts stderr "setOPT!!$args!" + if {[llength $args]} { + set argsDict [dict create {*}$args] + } else { + set argsDict [dict create] + } + set my_class [uplevel 1 namespace current] + set myOptions [namespace eval $my_class {info options}] +#puts stderr "OPTS!$win!$my_class![join [lsort $myOptions]] \n]!" + set opt_lst [list configure] + foreach opt [lsort $myOptions] { + set found 0 + if {[catch { + set resource [uplevel 1 info option $opt -resource] + set class [uplevel 1 info option $opt -class] + set default_val [uplevel 1 info option $opt -default] + set found 1 + } msg]} { +# puts stderr "MSG!$opt!$msg!" + } +#puts stderr "OPT!$opt!$found!" + if {$found} { + set val "" + if {[::dict exists $argsDict $opt]} { + # we have an explicitly set option + set val [::dict get $argsDict $opt] + } else { + if {[string length $val] == 0} { + set val $default_val + } + } + set myObj [uplevel 1 set this] +#puts stderr "myObj!$myObj!" + set ::itcl::internal::variables::${myObj}::itcl_options($opt) $val + set ::itcl::internal::variables::${myObj}::__itcl_option_infos($opt) [list $resource $class $default_val] +#puts stderr "OPT1!$opt!$val!" + uplevel 1 [list set itcl_options($opt) [list $val]] +# if {[catch {uplevel 1 $myObj configure $opt [list $val]} msg]} { +#puts stderr "initoptions ERR!$msg!$my_class!$my_win!configure!$opt!$val!" +# } + } + } +# uplevel 1 $opt_lst +} + +# ========================= keepcomponentoption ====================== +# Invoked by Tcl during evaluating constructor whenever +# the "keepcomponentoption" command is invoked to list the options +# to be kept when an ::itcl::extendedclass component has been setup +# for an object. +# +# It checks, for all arguments, if the opt is an option of that class +# and of that component. If that is the case it adds the component name +# to the list of components for that option. +# The variable is the object variable: itcl_option_components($opt) +# +# Handles the following syntax: +# +# keepcomponentoption ? ...? +# +# ====================================================================== + + +proc keepcomponentoption {args} { + upvar win win + upvar itcl_hull itcl_hull + + set usage "wrong # args, should be: keepcomponentoption componentName optionName ?optionName ...?" + +#puts stderr "KEEP!$args![uplevel 1 namespace current]!" + if {[llength $args] < 2} { + puts stderr $usage + return -code error + } + set my_hull [uplevel 1 set itcl_hull] + set my_class [uplevel 1 namespace current] + set comp [lindex $args 0] + set args [lrange $args 1 end] + set class_info_dict [dict get $::itcl::internal::dicts::classComponents $my_class] + if {![dict exists $class_info_dict $comp]} { + puts stderr "keepcomponentoption cannot find component \"$comp\"" + return -code error + } + set class_comp_dict [dict get $class_info_dict $comp] + if {![dict exists $class_comp_dict -keptoptions]} { + dict set class_comp_dict -keptoptions [list] + } + foreach opt $args { +#puts stderr "KEEP!$opt!" + if {[string range $opt 0 0] ne "-"} { + puts stderr "keepcomponentoption: option must begin with a \"-\"!" + return -code error + } + if {[lsearch [dict get $class_comp_dict -keptoptions] $opt] < 0} { + dict lappend class_comp_dict -keptoptions $opt + } + } + if {![info exists ::itcl::internal::component_objects([lindex [uplevel 1 info context] 1])]} { + set comp_object $::itcl::internal::component_objects([lindex [uplevel 1 info context] 1]) + } else { + set comp_object "unknown_comp_obj_$comp!" + } + dict set class_info_dict $comp $class_comp_dict + dict set ::itcl::internal::dicts::classComponents $my_class $class_info_dict +puts stderr "CLDI!$class_comp_dict!" + addToItclOptions $my_class $comp_object $args [list] +} + +proc ignorecomponentoption {args} { +puts stderr "IGNORE_COMPONENT_OPTION!$args!" +} + +proc renamecomponentoption {args} { +puts stderr "rename_COMPONENT_OPTION!$args!" +} + +proc addoptioncomponent {args} { +puts stderr "ADD_OPTION_COMPONENT!$args!" +} + +proc ignoreoptioncomponent {args} { +puts stderr "IGNORE_OPTION_COMPONENT!$args!" +} + +proc renameoptioncomponent {args} { +puts stderr "RENAME_OPTION_COMPONENT!$args!" +} + +proc getEclassOptions {args} { + upvar win win + +#puts stderr "getEclassOptions!$args!$win![uplevel 1 namespace current]!" +#parray ::itcl::internal::variables::${win}::itcl_options + set result [list] + foreach opt [array names ::itcl::internal::variables::${win}::itcl_options] { + if {[catch { + foreach {res cls def} [set ::itcl::internal::variables::${win}::__itcl_option_infos($opt)] break + lappend result [list $opt $res $cls $def [set ::itcl::internal::variables::${win}::itcl_options($opt)]] + } msg]} { + } + } + return $result +} + +proc eclassConfigure {args} { + upvar win win + +#puts stderr "+++ eclassConfigure!$args!" + if {[llength $args] > 1} { + foreach {opt val} $args break + if {[::info exists ::itcl::internal::variables::${win}::itcl_options($opt)]} { + set ::itcl::internal::variables::${win}::itcl_options($opt) $val + return + } + } else { + foreach {opt} $args break + if {[::info exists ::itcl::internal::variables::${win}::itcl_options($opt)]} { +#puts stderr "OP![set ::itcl::internal::variables::${win}::itcl_options($opt)]!" + foreach {res cls def} [set ::itcl::internal::variables::${win}::__itcl_option_infos($opt)] break + return [list $opt $res $cls $def [set ::itcl::internal::variables::${win}::itcl_options($opt)]] + } + } + return -code error +} + +} diff --git a/lib/itcl4.2.4/itclWidget.tcl b/lib/itcl4.2.4/itclWidget.tcl new file mode 100644 index 0000000000000000000000000000000000000000..6f5f4ac0b5fa96a774f7e0e4c8793fcdda6a22b8 --- /dev/null +++ b/lib/itcl4.2.4/itclWidget.tcl @@ -0,0 +1,447 @@ +# +# itclWidget.tcl +# ---------------------------------------------------------------------- +# Invoked automatically upon startup to customize the interpreter +# for [incr Tcl] when one of ::itcl::widget or ::itcl::widgetadaptor is called. +# ---------------------------------------------------------------------- +# AUTHOR: Arnulf P. Wiedemann +# +# ---------------------------------------------------------------------- +# Copyright (c) 2008 Arnulf P. Wiedemann +# ====================================================================== +# See the file "license.terms" for information on usage and +# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES. + +package require Tk 8.6 9 +# package require itclwidget [set ::itcl::version] + +namespace eval ::itcl { + +proc widget {name args} { + set result [uplevel 1 ::itcl::internal::commands::genericclass widget $name $args] + # we handle create by owerselfs !! allow classunknown to handle that + oo::objdefine $result unexport create + return $result +} + +proc widgetadaptor {name args} { + set result [uplevel 1 ::itcl::internal::commands::genericclass widgetadaptor $name $args] + # we handle create by owerselfs !! allow classunknown to handle that + oo::objdefine $result unexport create + return $result +} + +} ; # end ::itcl + + +namespace eval ::itcl::internal::commands { + +proc initWidgetOptions {varNsName widgetName className} { + set myDict [set ::itcl::internal::dicts::classOptions] + if {$myDict eq ""} { + return + } + if {![dict exists $myDict $className]} { + return + } + set myDict [dict get $myDict $className] + foreach option [dict keys $myDict] { + set infos [dict get $myDict $option] + set resource [dict get $infos -resource] + set class [dict get $infos -class] + set value [::option get $widgetName $resource $class] + if {$value eq ""} { + if {[dict exists $infos -default]} { + set defaultValue [dict get $infos -default] + uplevel 1 set ${varNsName}::itcl_options($option) $defaultValue + } + } else { + uplevel 1 set ${varNsName}::itcl_options($option) $value + } + } +} + +proc initWidgetDelegatedOptions {varNsName widgetName className args} { + set myDict [set ::itcl::internal::dicts::classDelegatedOptions] + if {$myDict eq ""} { + return + } + if {![dict exists $myDict $className]} { + return + } + set myDict [dict get $myDict $className] + foreach option [dict keys $myDict] { + set infos [dict get $myDict $option] + if {![dict exists $infos -resource]} { + # this is the case when delegating "*" + continue + } + if {![dict exists $infos -component]} { + # nothing to do + continue + } + # check if not in the command line options + # these have higher priority + set myOption $option + if {[dict exists $infos -as]} { + set myOption [dict get $infos -as] + } + set noOptionSet 0 + foreach {optName optVal} $args { + if {$optName eq $myOption} { + set noOptionSet 1 + break + } + } + if {$noOptionSet} { + continue + } + set resource [dict get $infos -resource] + set class [dict get $infos -class] + set component [dict get $infos -component] + set value [::option get $widgetName $resource $class] + if {$component ne ""} { + if {$value ne ""} { + set compVar [namespace eval ${varNsName}${className} "set $component"] + if {$compVar ne ""} { + uplevel 1 $compVar configure $myOption $value + } + } + } + } +} + +proc widgetinitobjectoptions {varNsName widgetName className} { +#puts stderr "initWidgetObjectOptions!$varNsName!$widgetName!$className!" +} + +proc deletehull {newName oldName what} { + if {$what eq "delete"} { + set name [namespace tail $newName] + regsub {hull[0-9]+} $name {} name + rename $name {} + } + if {$what eq "rename"} { + set name [namespace tail $newName] + regsub {hull[0-9]+} $name {} name + rename $name {} + } +} + +proc hullandoptionsinstall {objectName className widgetClass hulltype args} { + if {$hulltype eq ""} { + set hulltype frame + } + set idx 0 + set found 0 + foreach {optName optValue} $args { + if {$optName eq "-class"} { + set found 1 + set widgetClass $optValue + break + } + incr idx + } + if {$found} { + set args [lreplace $args $idx [expr {$idx + 1}]] + } + if {$widgetClass eq ""} { + set widgetClass $className + set widgetClass [string totitle $widgetClass] + } + set cmd "set win $objectName; ::itcl::builtin::installhull using $hulltype -class $widgetClass $args" + uplevel 2 $cmd +} + +} ; # end ::itcl::internal::commands + +namespace eval ::itcl::builtin { + +proc installhull {args} { + set cmdPath ::itcl::internal::commands + set className [uplevel 1 info class] + + set replace 0 + switch -- [llength $args] { + 0 { + return -code error\ + "wrong # args: should be \"[lindex [info level 0] 0]\ + name|using ?arg ...?\"" + } + 1 { + set widgetName [lindex $args 0] + set varNsName $::itcl::internal::varNsName($widgetName) + } + default { + upvar win win + set widgetName $win + + set varNsName $::itcl::internal::varNsName($widgetName) + set widgetType [lindex $args 1] + incr replace + if {[llength $args] > 3 && [lindex $args 2] eq "-class"} { + set classNam [lindex $args 3] + incr replace 2 + } else { + set classNam [string totitle $widgetType] + } + uplevel 1 [lreplace $args 0 $replace $widgetType $widgetName -class $classNam] + uplevel 1 [list ${cmdPath}::initWidgetOptions $varNsName $widgetName $className] + } + } + + # initialize the itcl_hull variable + set i 0 + set nam ::itcl::internal::widgets::hull + while {1} { + incr i + set hullNam ${nam}${i}$widgetName + if {[::info command $hullNam] eq ""} { + break + } + } + uplevel 1 [list ${cmdPath}::sethullwindowname $widgetName] + uplevel 1 [list ::rename $widgetName $hullNam] + uplevel 1 [list ::trace add command $hullNam {delete rename} ::itcl::internal::commands::deletehull] + catch {${cmdPath}::checksetitclhull [list] 0} + namespace eval ${varNsName}${className} "set itcl_hull $hullNam" + catch {${cmdPath}::checksetitclhull [list] 2} + uplevel 1 [lreplace $args 0 $replace ${cmdPath}::initWidgetDelegatedOptions $varNsName $widgetName $className] +} + +proc installcomponent {args} { + upvar win win + + set className [uplevel 1 info class] + set myType [${className}::info types [namespace tail $className]] + set isType 0 + if {$myType ne ""} { + set isType 1 + } + set numArgs [llength $args] + set usage "usage: installcomponent using ?-option value ...?" + if {$numArgs < 4} { + error $usage + } + foreach {componentName using widgetType widgetPath} $args break + set opts [lrange $args 4 end] + if {$using ne "using"} { + error $usage + } + if {!$isType} { + set hullExists [uplevel 1 ::info exists itcl_hull] + if {!$hullExists} { + error "cannot install \"$componentName\" before \"itcl_hull\" exists" + } + set hullVal [uplevel 1 set itcl_hull] + if {$hullVal eq ""} { + error "cannot install \"$componentName\" before \"itcl_hull\" exists" + } + } + # check for delegated option and ask the option database for the values + # first check for number of delegated options + set numOpts 0 + set starOption 0 + set myDict [set ::itcl::internal::dicts::classDelegatedOptions] + if {[dict exists $myDict $className]} { + set myDict [dict get $myDict $className] + foreach option [dict keys $myDict] { + if {$option eq "*"} { + set starOption 1 + } + incr numOpts + } + } + set myOptionDict [set ::itcl::internal::dicts::classOptions] + if {[dict exists $myOptionDict $className]} { + set myOptionDict [dict get $myOptionDict $className] + } + set cmd [list $widgetPath configure] + set cmd1 "set $componentName \[$widgetType $widgetPath\]" + uplevel 1 $cmd1 + if {$starOption} { + upvar $componentName compName + set cmd1 [list $compName configure] + set configInfos [uplevel 1 $cmd1] + foreach entry $configInfos { + if {[llength $entry] > 2} { + foreach {optName resource class defaultValue} $entry break + set val "" + catch { + set val [::option get $win $resource $class] + } + if {$val ne ""} { + set addOpt 1 + if {[dict exists $myDict $$optName]} { + set addOpt 0 + } else { + set starDict [dict get $myDict "*"] + if {[dict exists $starDict -except]} { + set exceptions [dict get $starDict -except] + if {[lsearch $exceptions $optName] >= 0} { + set addOpt 0 + } + + } + if {[dict exists $myOptionDict $optName]} { + set addOpt 0 + } + } + if {$addOpt} { + lappend cmd $optName $val + } + + } + + } + } + } else { + foreach optName [dict keys $myDict] { + set optInfos [dict get $myDict $optName] + set resource [dict get $optInfos -resource] + set class [namespace tail $className] + set class [string totitle $class] + set val "" + catch { + set val [::option get $win $resource $class] + } + if {$val ne ""} { + if {[dict exists $optInfos -as] } { + set optName [dict get $optInfos -as] + } + lappend cmd $optName $val + } + } + } + lappend cmd {*}$opts + uplevel 1 $cmd +} + +} ; # end ::itcl::builtin + +set ::itcl::internal::dicts::hullTypes [list \ + frame \ + toplevel \ + labelframe \ + ttk:frame \ + ttk:toplevel \ + ttk:labelframe \ + ] + +namespace eval ::itcl::builtin::Info { + +proc hulltypes {args} { + namespace upvar ::itcl::internal::dicts hullTypes hullTypes + + set numArgs [llength $args] + if {$numArgs > 1} { + error "wrong # args should be: info hulltypes ??" + } + set pattern "" + if {$numArgs > 0} { + set pattern [lindex $args 0] + } + if {$pattern ne ""} { + return [lsearch -all -inline -glob $hullTypes $pattern] + } + return $hullTypes + +} + +proc widgetclasses {args} { + set numArgs [llength $args] + if {$numArgs > 1} { + error "wrong # args should be: info widgetclasses ??" + } + set pattern "" + if {$numArgs > 0} { + set pattern [lindex $args 0] + } + set myDict [set ::itcl::internal::dicts::classes] + if {![dict exists $myDict widget]} { + return [list] + } + set myDict [dict get $myDict widget] + set result [list] + if {$pattern ne ""} { + foreach key [dict keys $myDict] { + set myInfo [dict get $myDict $key] + set value [dict get $myInfo -widget] + if {[string match $pattern $value]} { + lappend result $value + } + } + } else { + foreach key [dict keys $myDict] { + set myInfo [dict get $myDict $key] + lappend result [dict get $myInfo -widget] + } + } + return $result +} + +proc widgets {args} { + set numArgs [llength $args] + if {$numArgs > 1} { + error "wrong # args should be: info widgets ??" + } + set pattern "" + if {$numArgs > 0} { + set pattern [lindex $args 0] + } + set myDict [set ::itcl::internal::dicts::classes] + if {![dict exists $myDict widget]} { + return [list] + } + set myDict [dict get $myDict widget] + set result [list] + if {$pattern ne ""} { + foreach key [dict keys $myDict] { + set myInfo [dict get $myDict $key] + set value [dict get $myInfo -name] + if {[string match $pattern $value]} { + lappend result $value + } + } + } else { + foreach key [dict keys $myDict] { + set myInfo [dict get $myDict $key] + lappend result [dict get $myInfo -name] + } + } + return $result +} + +proc widgetadaptors {args} { + set numArgs [llength $args] + if {$numArgs > 1} { + error "wrong # args should be: info widgetadaptors ??" + } + set pattern "" + if {$numArgs > 0} { + set pattern [lindex $args 0] + } + set myDict [set ::itcl::internal::dicts::classes] + if {![dict exists $myDict widgetadaptor]} { + return [list] + } + set myDict [dict get $myDict widgetadaptor] + set result [list] + if {$pattern ne ""} { + foreach key [dict keys $myDict] { + set myInfo [dict get $myDict $key] + set value [dict get $myInfo -name] + if {[string match $pattern $value]} { + lappend result $value + } + } + } else { + foreach key [dict keys $myDict] { + set myInfo [dict get $myDict $key] + lappend result [dict get $myInfo -name] + } + } + return $result +} + +} ; # end ::itcl::builtin::Info diff --git a/lib/itcl4.2.4/libitclstub4.2.4.a b/lib/itcl4.2.4/libitclstub4.2.4.a new file mode 100644 index 0000000000000000000000000000000000000000..95bfb45c8ba74d1bfa6427f00b8f34b05a50b585 Binary files /dev/null and b/lib/itcl4.2.4/libitclstub4.2.4.a differ diff --git a/lib/itcl4.2.4/pkgIndex.tcl b/lib/itcl4.2.4/pkgIndex.tcl new file mode 100644 index 0000000000000000000000000000000000000000..59e29ece29112a514d96b58fb5a4f5cc7c7758b9 --- /dev/null +++ b/lib/itcl4.2.4/pkgIndex.tcl @@ -0,0 +1,14 @@ +# -*- tcl -*- +# Tcl package index file, version 1.1 +# + +if {![package vsatisfies [package provide Tcl] 8.6-]} {return} + +if {[package vsatisfies [package provide Tcl] 9.0-]} { + package ifneeded itcl 4.2.4 \ + [list load [file join $dir libtcl9itcl4.2.4.so] Itcl] +} else { + package ifneeded itcl 4.2.4 \ + [list load [file join $dir libitcl4.2.4.so] Itcl] +} +package ifneeded Itcl 4.2.4 [list package require -exact itcl 4.2.4] diff --git a/lib/python3.10/py_compile.py b/lib/python3.10/py_compile.py new file mode 100644 index 0000000000000000000000000000000000000000..388614e51b1847cbd81400d9ce48dd734e050e14 --- /dev/null +++ b/lib/python3.10/py_compile.py @@ -0,0 +1,212 @@ +"""Routine to "compile" a .py file to a .pyc file. + +This module has intimate knowledge of the format of .pyc files. +""" + +import enum +import importlib._bootstrap_external +import importlib.machinery +import importlib.util +import os +import os.path +import sys +import traceback + +__all__ = ["compile", "main", "PyCompileError", "PycInvalidationMode"] + + +class PyCompileError(Exception): + """Exception raised when an error occurs while attempting to + compile the file. + + To raise this exception, use + + raise PyCompileError(exc_type,exc_value,file[,msg]) + + where + + exc_type: exception type to be used in error message + type name can be accesses as class variable + 'exc_type_name' + + exc_value: exception value to be used in error message + can be accesses as class variable 'exc_value' + + file: name of file being compiled to be used in error message + can be accesses as class variable 'file' + + msg: string message to be written as error message + If no value is given, a default exception message will be + given, consistent with 'standard' py_compile output. + message (or default) can be accesses as class variable + 'msg' + + """ + + def __init__(self, exc_type, exc_value, file, msg=''): + exc_type_name = exc_type.__name__ + if exc_type is SyntaxError: + tbtext = ''.join(traceback.format_exception_only( + exc_type, exc_value)) + errmsg = tbtext.replace('File ""', 'File "%s"' % file) + else: + errmsg = "Sorry: %s: %s" % (exc_type_name,exc_value) + + Exception.__init__(self,msg or errmsg,exc_type_name,exc_value,file) + + self.exc_type_name = exc_type_name + self.exc_value = exc_value + self.file = file + self.msg = msg or errmsg + + def __str__(self): + return self.msg + + +class PycInvalidationMode(enum.Enum): + TIMESTAMP = 1 + CHECKED_HASH = 2 + UNCHECKED_HASH = 3 + + +def _get_default_invalidation_mode(): + if os.environ.get('SOURCE_DATE_EPOCH'): + return PycInvalidationMode.CHECKED_HASH + else: + return PycInvalidationMode.TIMESTAMP + + +def compile(file, cfile=None, dfile=None, doraise=False, optimize=-1, + invalidation_mode=None, quiet=0): + """Byte-compile one Python source file to Python bytecode. + + :param file: The source file name. + :param cfile: The target byte compiled file name. When not given, this + defaults to the PEP 3147/PEP 488 location. + :param dfile: Purported file name, i.e. the file name that shows up in + error messages. Defaults to the source file name. + :param doraise: Flag indicating whether or not an exception should be + raised when a compile error is found. If an exception occurs and this + flag is set to False, a string indicating the nature of the exception + will be printed, and the function will return to the caller. If an + exception occurs and this flag is set to True, a PyCompileError + exception will be raised. + :param optimize: The optimization level for the compiler. Valid values + are -1, 0, 1 and 2. A value of -1 means to use the optimization + level of the current interpreter, as given by -O command line options. + :param invalidation_mode: + :param quiet: Return full output with False or 0, errors only with 1, + and no output with 2. + + :return: Path to the resulting byte compiled file. + + Note that it isn't necessary to byte-compile Python modules for + execution efficiency -- Python itself byte-compiles a module when + it is loaded, and if it can, writes out the bytecode to the + corresponding .pyc file. + + However, if a Python installation is shared between users, it is a + good idea to byte-compile all modules upon installation, since + other users may not be able to write in the source directories, + and thus they won't be able to write the .pyc file, and then + they would be byte-compiling every module each time it is loaded. + This can slow down program start-up considerably. + + See compileall.py for a script/module that uses this module to + byte-compile all installed files (or all files in selected + directories). + + Do note that FileExistsError is raised if cfile ends up pointing at a + non-regular file or symlink. Because the compilation uses a file renaming, + the resulting file would be regular and thus not the same type of file as + it was previously. + """ + if invalidation_mode is None: + invalidation_mode = _get_default_invalidation_mode() + if cfile is None: + if optimize >= 0: + optimization = optimize if optimize >= 1 else '' + cfile = importlib.util.cache_from_source(file, + optimization=optimization) + else: + cfile = importlib.util.cache_from_source(file) + if os.path.islink(cfile): + msg = ('{} is a symlink and will be changed into a regular file if ' + 'import writes a byte-compiled file to it') + raise FileExistsError(msg.format(cfile)) + elif os.path.exists(cfile) and not os.path.isfile(cfile): + msg = ('{} is a non-regular file and will be changed into a regular ' + 'one if import writes a byte-compiled file to it') + raise FileExistsError(msg.format(cfile)) + loader = importlib.machinery.SourceFileLoader('', file) + source_bytes = loader.get_data(file) + try: + code = loader.source_to_code(source_bytes, dfile or file, + _optimize=optimize) + except Exception as err: + py_exc = PyCompileError(err.__class__, err, dfile or file) + if quiet < 2: + if doraise: + raise py_exc + else: + sys.stderr.write(py_exc.msg + '\n') + return + try: + dirname = os.path.dirname(cfile) + if dirname: + os.makedirs(dirname) + except FileExistsError: + pass + if invalidation_mode == PycInvalidationMode.TIMESTAMP: + source_stats = loader.path_stats(file) + bytecode = importlib._bootstrap_external._code_to_timestamp_pyc( + code, source_stats['mtime'], source_stats['size']) + else: + source_hash = importlib.util.source_hash(source_bytes) + bytecode = importlib._bootstrap_external._code_to_hash_pyc( + code, + source_hash, + (invalidation_mode == PycInvalidationMode.CHECKED_HASH), + ) + mode = importlib._bootstrap_external._calc_mode(file) + importlib._bootstrap_external._write_atomic(cfile, bytecode, mode) + return cfile + + +def main(): + import argparse + + description = 'A simple command-line interface for py_compile module.' + parser = argparse.ArgumentParser(description=description) + parser.add_argument( + '-q', '--quiet', + action='store_true', + help='Suppress error output', + ) + parser.add_argument( + 'filenames', + nargs='+', + help='Files to compile', + ) + args = parser.parse_args() + if args.filenames == ['-']: + filenames = [filename.rstrip('\n') for filename in sys.stdin.readlines()] + else: + filenames = args.filenames + for filename in filenames: + try: + compile(filename, doraise=True) + except PyCompileError as error: + if args.quiet: + parser.exit(1) + else: + parser.exit(1, error.msg) + except OSError as error: + if args.quiet: + parser.exit(1) + else: + parser.exit(1, str(error)) + + +if __name__ == "__main__": + main() diff --git a/lib/python3.10/pyclbr.py b/lib/python3.10/pyclbr.py new file mode 100644 index 0000000000000000000000000000000000000000..37f86995d6ce00013643f1b4a8aa387cebaeae72 --- /dev/null +++ b/lib/python3.10/pyclbr.py @@ -0,0 +1,314 @@ +"""Parse a Python module and describe its classes and functions. + +Parse enough of a Python file to recognize imports and class and +function definitions, and to find out the superclasses of a class. + +The interface consists of a single function: + readmodule_ex(module, path=None) +where module is the name of a Python module, and path is an optional +list of directories where the module is to be searched. If present, +path is prepended to the system search path sys.path. The return value +is a dictionary. The keys of the dictionary are the names of the +classes and functions defined in the module (including classes that are +defined via the from XXX import YYY construct). The values are +instances of classes Class and Function. One special key/value pair is +present for packages: the key '__path__' has a list as its value which +contains the package search path. + +Classes and Functions have a common superclass: _Object. Every instance +has the following attributes: + module -- name of the module; + name -- name of the object; + file -- file in which the object is defined; + lineno -- line in the file where the object's definition starts; + end_lineno -- line in the file where the object's definition ends; + parent -- parent of this object, if any; + children -- nested objects contained in this object. +The 'children' attribute is a dictionary mapping names to objects. + +Instances of Function describe functions with the attributes from _Object, +plus the following: + is_async -- if a function is defined with an 'async' prefix + +Instances of Class describe classes with the attributes from _Object, +plus the following: + super -- list of super classes (Class instances if possible); + methods -- mapping of method names to beginning line numbers. +If the name of a super class is not recognized, the corresponding +entry in the list of super classes is not a class instance but a +string giving the name of the super class. Since import statements +are recognized and imported modules are scanned as well, this +shouldn't happen often. +""" + +import ast +import sys +import importlib.util + +__all__ = ["readmodule", "readmodule_ex", "Class", "Function"] + +_modules = {} # Initialize cache of modules we've seen. + + +class _Object: + "Information about Python class or function." + def __init__(self, module, name, file, lineno, end_lineno, parent): + self.module = module + self.name = name + self.file = file + self.lineno = lineno + self.end_lineno = end_lineno + self.parent = parent + self.children = {} + if parent is not None: + parent.children[name] = self + + +# Odd Function and Class signatures are for back-compatibility. +class Function(_Object): + "Information about a Python function, including methods." + def __init__(self, module, name, file, lineno, + parent=None, is_async=False, *, end_lineno=None): + super().__init__(module, name, file, lineno, end_lineno, parent) + self.is_async = is_async + if isinstance(parent, Class): + parent.methods[name] = lineno + + +class Class(_Object): + "Information about a Python class." + def __init__(self, module, name, super_, file, lineno, + parent=None, *, end_lineno=None): + super().__init__(module, name, file, lineno, end_lineno, parent) + self.super = super_ or [] + self.methods = {} + + +# These 2 functions are used in these tests +# Lib/test/test_pyclbr, Lib/idlelib/idle_test/test_browser.py +def _nest_function(ob, func_name, lineno, end_lineno, is_async=False): + "Return a Function after nesting within ob." + return Function(ob.module, func_name, ob.file, lineno, + parent=ob, is_async=is_async, end_lineno=end_lineno) + +def _nest_class(ob, class_name, lineno, end_lineno, super=None): + "Return a Class after nesting within ob." + return Class(ob.module, class_name, super, ob.file, lineno, + parent=ob, end_lineno=end_lineno) + + +def readmodule(module, path=None): + """Return Class objects for the top-level classes in module. + + This is the original interface, before Functions were added. + """ + + res = {} + for key, value in _readmodule(module, path or []).items(): + if isinstance(value, Class): + res[key] = value + return res + +def readmodule_ex(module, path=None): + """Return a dictionary with all functions and classes in module. + + Search for module in PATH + sys.path. + If possible, include imported superclasses. + Do this by reading source, without importing (and executing) it. + """ + return _readmodule(module, path or []) + + +def _readmodule(module, path, inpackage=None): + """Do the hard work for readmodule[_ex]. + + If inpackage is given, it must be the dotted name of the package in + which we are searching for a submodule, and then PATH must be the + package search path; otherwise, we are searching for a top-level + module, and path is combined with sys.path. + """ + # Compute the full module name (prepending inpackage if set). + if inpackage is not None: + fullmodule = "%s.%s" % (inpackage, module) + else: + fullmodule = module + + # Check in the cache. + if fullmodule in _modules: + return _modules[fullmodule] + + # Initialize the dict for this module's contents. + tree = {} + + # Check if it is a built-in module; we don't do much for these. + if module in sys.builtin_module_names and inpackage is None: + _modules[module] = tree + return tree + + # Check for a dotted module name. + i = module.rfind('.') + if i >= 0: + package = module[:i] + submodule = module[i+1:] + parent = _readmodule(package, path, inpackage) + if inpackage is not None: + package = "%s.%s" % (inpackage, package) + if not '__path__' in parent: + raise ImportError('No package named {}'.format(package)) + return _readmodule(submodule, parent['__path__'], package) + + # Search the path for the module. + f = None + if inpackage is not None: + search_path = path + else: + search_path = path + sys.path + spec = importlib.util._find_spec_from_path(fullmodule, search_path) + if spec is None: + raise ModuleNotFoundError(f"no module named {fullmodule!r}", name=fullmodule) + _modules[fullmodule] = tree + # Is module a package? + if spec.submodule_search_locations is not None: + tree['__path__'] = spec.submodule_search_locations + try: + source = spec.loader.get_source(fullmodule) + except (AttributeError, ImportError): + # If module is not Python source, we cannot do anything. + return tree + else: + if source is None: + return tree + + fname = spec.loader.get_filename(fullmodule) + return _create_tree(fullmodule, path, fname, source, tree, inpackage) + + +class _ModuleBrowser(ast.NodeVisitor): + def __init__(self, module, path, file, tree, inpackage): + self.path = path + self.tree = tree + self.file = file + self.module = module + self.inpackage = inpackage + self.stack = [] + + def visit_ClassDef(self, node): + bases = [] + for base in node.bases: + name = ast.unparse(base) + if name in self.tree: + # We know this super class. + bases.append(self.tree[name]) + elif len(names := name.split(".")) > 1: + # Super class form is module.class: + # look in module for class. + *_, module, class_ = names + if module in _modules: + bases.append(_modules[module].get(class_, name)) + else: + bases.append(name) + + parent = self.stack[-1] if self.stack else None + class_ = Class(self.module, node.name, bases, self.file, node.lineno, + parent=parent, end_lineno=node.end_lineno) + if parent is None: + self.tree[node.name] = class_ + self.stack.append(class_) + self.generic_visit(node) + self.stack.pop() + + def visit_FunctionDef(self, node, *, is_async=False): + parent = self.stack[-1] if self.stack else None + function = Function(self.module, node.name, self.file, node.lineno, + parent, is_async, end_lineno=node.end_lineno) + if parent is None: + self.tree[node.name] = function + self.stack.append(function) + self.generic_visit(node) + self.stack.pop() + + def visit_AsyncFunctionDef(self, node): + self.visit_FunctionDef(node, is_async=True) + + def visit_Import(self, node): + if node.col_offset != 0: + return + + for module in node.names: + try: + try: + _readmodule(module.name, self.path, self.inpackage) + except ImportError: + _readmodule(module.name, []) + except (ImportError, SyntaxError): + # If we can't find or parse the imported module, + # too bad -- don't die here. + continue + + def visit_ImportFrom(self, node): + if node.col_offset != 0: + return + try: + module = "." * node.level + if node.module: + module += node.module + module = _readmodule(module, self.path, self.inpackage) + except (ImportError, SyntaxError): + return + + for name in node.names: + if name.name in module: + self.tree[name.asname or name.name] = module[name.name] + elif name.name == "*": + for import_name, import_value in module.items(): + if import_name.startswith("_"): + continue + self.tree[import_name] = import_value + + +def _create_tree(fullmodule, path, fname, source, tree, inpackage): + mbrowser = _ModuleBrowser(fullmodule, path, fname, tree, inpackage) + mbrowser.visit(ast.parse(source)) + return mbrowser.tree + + +def _main(): + "Print module output (default this file) for quick visual check." + import os + try: + mod = sys.argv[1] + except: + mod = __file__ + if os.path.exists(mod): + path = [os.path.dirname(mod)] + mod = os.path.basename(mod) + if mod.lower().endswith(".py"): + mod = mod[:-3] + else: + path = [] + tree = readmodule_ex(mod, path) + lineno_key = lambda a: getattr(a, 'lineno', 0) + objs = sorted(tree.values(), key=lineno_key, reverse=True) + indent_level = 2 + while objs: + obj = objs.pop() + if isinstance(obj, list): + # Value is a __path__ key. + continue + if not hasattr(obj, 'indent'): + obj.indent = 0 + + if isinstance(obj, _Object): + new_objs = sorted(obj.children.values(), + key=lineno_key, reverse=True) + for ob in new_objs: + ob.indent = obj.indent + indent_level + objs.extend(new_objs) + if isinstance(obj, Class): + print("{}class {} {} {}" + .format(' ' * obj.indent, obj.name, obj.super, obj.lineno)) + elif isinstance(obj, Function): + print("{}def {} {}".format(' ' * obj.indent, obj.name, obj.lineno)) + +if __name__ == "__main__": + _main() diff --git a/lib/python3.10/pydoc.py b/lib/python3.10/pydoc.py new file mode 100644 index 0000000000000000000000000000000000000000..e00ba4191c41005fb56a75147a2bac90c8d9b2fe --- /dev/null +++ b/lib/python3.10/pydoc.py @@ -0,0 +1,2842 @@ +#!/usr/bin/env python3 +"""Generate Python documentation in HTML or text for interactive use. + +At the Python interactive prompt, calling help(thing) on a Python object +documents the object, and calling help() starts up an interactive +help session. + +Or, at the shell command line outside of Python: + +Run "pydoc " to show documentation on something. may be +the name of a function, module, package, or a dotted reference to a +class or function within a module or module in a package. If the +argument contains a path segment delimiter (e.g. slash on Unix, +backslash on Windows) it is treated as the path to a Python source file. + +Run "pydoc -k " to search for a keyword in the synopsis lines +of all available modules. + +Run "pydoc -n " to start an HTTP server with the given +hostname (default: localhost) on the local machine. + +Run "pydoc -p " to start an HTTP server on the given port on the +local machine. Port number 0 can be used to get an arbitrary unused port. + +Run "pydoc -b" to start an HTTP server on an arbitrary unused port and +open a web browser to interactively browse documentation. Combine with +the -n and -p options to control the hostname and port used. + +Run "pydoc -w " to write out the HTML documentation for a module +to a file named ".html". + +Module docs for core modules are assumed to be in + + https://docs.python.org/X.Y/library/ + +This can be overridden by setting the PYTHONDOCS environment variable +to a different URL or to a local directory containing the Library +Reference Manual pages. +""" +__all__ = ['help'] +__author__ = "Ka-Ping Yee " +__date__ = "26 February 2001" + +__credits__ = """Guido van Rossum, for an excellent programming language. +Tommy Burnette, the original creator of manpy. +Paul Prescod, for all his work on onlinehelp. +Richard Chamberlain, for the first implementation of textdoc. +""" + +# Known bugs that can't be fixed here: +# - synopsis() cannot be prevented from clobbering existing +# loaded modules. +# - If the __file__ attribute on a module is a relative path and +# the current directory is changed with os.chdir(), an incorrect +# path will be displayed. + +import builtins +import importlib._bootstrap +import importlib._bootstrap_external +import importlib.machinery +import importlib.util +import inspect +import io +import os +import pkgutil +import platform +import re +import sys +import sysconfig +import time +import tokenize +import types +import urllib.parse +import warnings +from collections import deque +from reprlib import Repr +from traceback import format_exception_only + + +# --------------------------------------------------------- common routines + +def pathdirs(): + """Convert sys.path into a list of absolute, existing, unique paths.""" + dirs = [] + normdirs = [] + for dir in sys.path: + dir = os.path.abspath(dir or '.') + normdir = os.path.normcase(dir) + if normdir not in normdirs and os.path.isdir(dir): + dirs.append(dir) + normdirs.append(normdir) + return dirs + +def _isclass(object): + return inspect.isclass(object) and not isinstance(object, types.GenericAlias) + +def _findclass(func): + cls = sys.modules.get(func.__module__) + if cls is None: + return None + for name in func.__qualname__.split('.')[:-1]: + cls = getattr(cls, name) + if not _isclass(cls): + return None + return cls + +def _finddoc(obj): + if inspect.ismethod(obj): + name = obj.__func__.__name__ + self = obj.__self__ + if (_isclass(self) and + getattr(getattr(self, name, None), '__func__') is obj.__func__): + # classmethod + cls = self + else: + cls = self.__class__ + elif inspect.isfunction(obj): + name = obj.__name__ + cls = _findclass(obj) + if cls is None or getattr(cls, name) is not obj: + return None + elif inspect.isbuiltin(obj): + name = obj.__name__ + self = obj.__self__ + if (_isclass(self) and + self.__qualname__ + '.' + name == obj.__qualname__): + # classmethod + cls = self + else: + cls = self.__class__ + # Should be tested before isdatadescriptor(). + elif isinstance(obj, property): + func = obj.fget + name = func.__name__ + cls = _findclass(func) + if cls is None or getattr(cls, name) is not obj: + return None + elif inspect.ismethoddescriptor(obj) or inspect.isdatadescriptor(obj): + name = obj.__name__ + cls = obj.__objclass__ + if getattr(cls, name) is not obj: + return None + if inspect.ismemberdescriptor(obj): + slots = getattr(cls, '__slots__', None) + if isinstance(slots, dict) and name in slots: + return slots[name] + else: + return None + for base in cls.__mro__: + try: + doc = _getowndoc(getattr(base, name)) + except AttributeError: + continue + if doc is not None: + return doc + return None + +def _getowndoc(obj): + """Get the documentation string for an object if it is not + inherited from its class.""" + try: + doc = object.__getattribute__(obj, '__doc__') + if doc is None: + return None + if obj is not type: + typedoc = type(obj).__doc__ + if isinstance(typedoc, str) and typedoc == doc: + return None + return doc + except AttributeError: + return None + +def _getdoc(object): + """Get the documentation string for an object. + + All tabs are expanded to spaces. To clean up docstrings that are + indented to line up with blocks of code, any whitespace than can be + uniformly removed from the second line onwards is removed.""" + doc = _getowndoc(object) + if doc is None: + try: + doc = _finddoc(object) + except (AttributeError, TypeError): + return None + if not isinstance(doc, str): + return None + return inspect.cleandoc(doc) + +def getdoc(object): + """Get the doc string or comments for an object.""" + result = _getdoc(object) or inspect.getcomments(object) + return result and re.sub('^ *\n', '', result.rstrip()) or '' + +def splitdoc(doc): + """Split a doc string into a synopsis line (if any) and the rest.""" + lines = doc.strip().split('\n') + if len(lines) == 1: + return lines[0], '' + elif len(lines) >= 2 and not lines[1].rstrip(): + return lines[0], '\n'.join(lines[2:]) + return '', '\n'.join(lines) + +def classname(object, modname): + """Get a class name and qualify it with a module name if necessary.""" + name = object.__name__ + if object.__module__ != modname: + name = object.__module__ + '.' + name + return name + +def isdata(object): + """Check if an object is of a type that probably means it's data.""" + return not (inspect.ismodule(object) or _isclass(object) or + inspect.isroutine(object) or inspect.isframe(object) or + inspect.istraceback(object) or inspect.iscode(object)) + +def replace(text, *pairs): + """Do a series of global replacements on a string.""" + while pairs: + text = pairs[1].join(text.split(pairs[0])) + pairs = pairs[2:] + return text + +def cram(text, maxlen): + """Omit part of a string if needed to make it fit in a maximum length.""" + if len(text) > maxlen: + pre = max(0, (maxlen-3)//2) + post = max(0, maxlen-3-pre) + return text[:pre] + '...' + text[len(text)-post:] + return text + +_re_stripid = re.compile(r' at 0x[0-9a-f]{6,16}(>+)$', re.IGNORECASE) +def stripid(text): + """Remove the hexadecimal id from a Python object representation.""" + # The behaviour of %p is implementation-dependent in terms of case. + return _re_stripid.sub(r'\1', text) + +def _is_bound_method(fn): + """ + Returns True if fn is a bound method, regardless of whether + fn was implemented in Python or in C. + """ + if inspect.ismethod(fn): + return True + if inspect.isbuiltin(fn): + self = getattr(fn, '__self__', None) + return not (inspect.ismodule(self) or (self is None)) + return False + + +def allmethods(cl): + methods = {} + for key, value in inspect.getmembers(cl, inspect.isroutine): + methods[key] = 1 + for base in cl.__bases__: + methods.update(allmethods(base)) # all your base are belong to us + for key in methods.keys(): + methods[key] = getattr(cl, key) + return methods + +def _split_list(s, predicate): + """Split sequence s via predicate, and return pair ([true], [false]). + + The return value is a 2-tuple of lists, + ([x for x in s if predicate(x)], + [x for x in s if not predicate(x)]) + """ + + yes = [] + no = [] + for x in s: + if predicate(x): + yes.append(x) + else: + no.append(x) + return yes, no + +def visiblename(name, all=None, obj=None): + """Decide whether to show documentation on a variable.""" + # Certain special names are redundant or internal. + # XXX Remove __initializing__? + if name in {'__author__', '__builtins__', '__cached__', '__credits__', + '__date__', '__doc__', '__file__', '__spec__', + '__loader__', '__module__', '__name__', '__package__', + '__path__', '__qualname__', '__slots__', '__version__'}: + return 0 + # Private names are hidden, but special names are displayed. + if name.startswith('__') and name.endswith('__'): return 1 + # Namedtuples have public fields and methods with a single leading underscore + if name.startswith('_') and hasattr(obj, '_fields'): + return True + if all is not None: + # only document that which the programmer exported in __all__ + return name in all + else: + return not name.startswith('_') + +def classify_class_attrs(object): + """Wrap inspect.classify_class_attrs, with fixup for data descriptors.""" + results = [] + for (name, kind, cls, value) in inspect.classify_class_attrs(object): + if inspect.isdatadescriptor(value): + kind = 'data descriptor' + if isinstance(value, property) and value.fset is None: + kind = 'readonly property' + results.append((name, kind, cls, value)) + return results + +def sort_attributes(attrs, object): + 'Sort the attrs list in-place by _fields and then alphabetically by name' + # This allows data descriptors to be ordered according + # to a _fields attribute if present. + fields = getattr(object, '_fields', []) + try: + field_order = {name : i-len(fields) for (i, name) in enumerate(fields)} + except TypeError: + field_order = {} + keyfunc = lambda attr: (field_order.get(attr[0], 0), attr[0]) + attrs.sort(key=keyfunc) + +# ----------------------------------------------------- module manipulation + +def ispackage(path): + """Guess whether a path refers to a package directory.""" + if os.path.isdir(path): + for ext in ('.py', '.pyc'): + if os.path.isfile(os.path.join(path, '__init__' + ext)): + return True + return False + +def source_synopsis(file): + line = file.readline() + while line[:1] == '#' or not line.strip(): + line = file.readline() + if not line: break + line = line.strip() + if line[:4] == 'r"""': line = line[1:] + if line[:3] == '"""': + line = line[3:] + if line[-1:] == '\\': line = line[:-1] + while not line.strip(): + line = file.readline() + if not line: break + result = line.split('"""')[0].strip() + else: result = None + return result + +def synopsis(filename, cache={}): + """Get the one-line summary out of a module file.""" + mtime = os.stat(filename).st_mtime + lastupdate, result = cache.get(filename, (None, None)) + if lastupdate is None or lastupdate < mtime: + # Look for binary suffixes first, falling back to source. + if filename.endswith(tuple(importlib.machinery.BYTECODE_SUFFIXES)): + loader_cls = importlib.machinery.SourcelessFileLoader + elif filename.endswith(tuple(importlib.machinery.EXTENSION_SUFFIXES)): + loader_cls = importlib.machinery.ExtensionFileLoader + else: + loader_cls = None + # Now handle the choice. + if loader_cls is None: + # Must be a source file. + try: + file = tokenize.open(filename) + except OSError: + # module can't be opened, so skip it + return None + # text modules can be directly examined + with file: + result = source_synopsis(file) + else: + # Must be a binary module, which has to be imported. + loader = loader_cls('__temp__', filename) + # XXX We probably don't need to pass in the loader here. + spec = importlib.util.spec_from_file_location('__temp__', filename, + loader=loader) + try: + module = importlib._bootstrap._load(spec) + except: + return None + del sys.modules['__temp__'] + result = module.__doc__.splitlines()[0] if module.__doc__ else None + # Cache the result. + cache[filename] = (mtime, result) + return result + +class ErrorDuringImport(Exception): + """Errors that occurred while trying to import something to document it.""" + def __init__(self, filename, exc_info): + self.filename = filename + self.exc, self.value, self.tb = exc_info + + def __str__(self): + exc = self.exc.__name__ + return 'problem in %s - %s: %s' % (self.filename, exc, self.value) + +def importfile(path): + """Import a Python source file or compiled file given its path.""" + magic = importlib.util.MAGIC_NUMBER + with open(path, 'rb') as file: + is_bytecode = magic == file.read(len(magic)) + filename = os.path.basename(path) + name, ext = os.path.splitext(filename) + if is_bytecode: + loader = importlib._bootstrap_external.SourcelessFileLoader(name, path) + else: + loader = importlib._bootstrap_external.SourceFileLoader(name, path) + # XXX We probably don't need to pass in the loader here. + spec = importlib.util.spec_from_file_location(name, path, loader=loader) + try: + return importlib._bootstrap._load(spec) + except: + raise ErrorDuringImport(path, sys.exc_info()) + +def safeimport(path, forceload=0, cache={}): + """Import a module; handle errors; return None if the module isn't found. + + If the module *is* found but an exception occurs, it's wrapped in an + ErrorDuringImport exception and reraised. Unlike __import__, if a + package path is specified, the module at the end of the path is returned, + not the package at the beginning. If the optional 'forceload' argument + is 1, we reload the module from disk (unless it's a dynamic extension).""" + try: + # If forceload is 1 and the module has been previously loaded from + # disk, we always have to reload the module. Checking the file's + # mtime isn't good enough (e.g. the module could contain a class + # that inherits from another module that has changed). + if forceload and path in sys.modules: + if path not in sys.builtin_module_names: + # Remove the module from sys.modules and re-import to try + # and avoid problems with partially loaded modules. + # Also remove any submodules because they won't appear + # in the newly loaded module's namespace if they're already + # in sys.modules. + subs = [m for m in sys.modules if m.startswith(path + '.')] + for key in [path] + subs: + # Prevent garbage collection. + cache[key] = sys.modules[key] + del sys.modules[key] + module = __import__(path) + except: + # Did the error occur before or after the module was found? + (exc, value, tb) = info = sys.exc_info() + if path in sys.modules: + # An error occurred while executing the imported module. + raise ErrorDuringImport(sys.modules[path].__file__, info) + elif exc is SyntaxError: + # A SyntaxError occurred before we could execute the module. + raise ErrorDuringImport(value.filename, info) + elif issubclass(exc, ImportError) and value.name == path: + # No such module in the path. + return None + else: + # Some other error occurred during the importing process. + raise ErrorDuringImport(path, sys.exc_info()) + for part in path.split('.')[1:]: + try: module = getattr(module, part) + except AttributeError: return None + return module + +# ---------------------------------------------------- formatter base class + +class Doc: + + PYTHONDOCS = os.environ.get("PYTHONDOCS", + "https://docs.python.org/%d.%d/library" + % sys.version_info[:2]) + + def document(self, object, name=None, *args): + """Generate documentation for an object.""" + args = (object, name) + args + # 'try' clause is to attempt to handle the possibility that inspect + # identifies something in a way that pydoc itself has issues handling; + # think 'super' and how it is a descriptor (which raises the exception + # by lacking a __name__ attribute) and an instance. + try: + if inspect.ismodule(object): return self.docmodule(*args) + if _isclass(object): return self.docclass(*args) + if inspect.isroutine(object): return self.docroutine(*args) + except AttributeError: + pass + if inspect.isdatadescriptor(object): return self.docdata(*args) + return self.docother(*args) + + def fail(self, object, name=None, *args): + """Raise an exception for unimplemented types.""" + message = "don't know how to document object%s of type %s" % ( + name and ' ' + repr(name), type(object).__name__) + raise TypeError(message) + + docmodule = docclass = docroutine = docother = docproperty = docdata = fail + + def getdocloc(self, object, basedir=sysconfig.get_path('stdlib')): + """Return the location of module docs or None""" + + try: + file = inspect.getabsfile(object) + except TypeError: + file = '(built-in)' + + docloc = os.environ.get("PYTHONDOCS", self.PYTHONDOCS) + + basedir = os.path.normcase(basedir) + if (isinstance(object, type(os)) and + (object.__name__ in ('errno', 'exceptions', 'gc', 'imp', + 'marshal', 'posix', 'signal', 'sys', + '_thread', 'zipimport') or + (file.startswith(basedir) and + not file.startswith(os.path.join(basedir, 'site-packages')))) and + object.__name__ not in ('xml.etree', 'test.pydoc_mod')): + if docloc.startswith(("http://", "https://")): + docloc = "{}/{}.html".format(docloc.rstrip("/"), object.__name__.lower()) + else: + docloc = os.path.join(docloc, object.__name__.lower() + ".html") + else: + docloc = None + return docloc + +# -------------------------------------------- HTML documentation generator + +class HTMLRepr(Repr): + """Class for safely making an HTML representation of a Python object.""" + def __init__(self): + Repr.__init__(self) + self.maxlist = self.maxtuple = 20 + self.maxdict = 10 + self.maxstring = self.maxother = 100 + + def escape(self, text): + return replace(text, '&', '&', '<', '<', '>', '>') + + def repr(self, object): + return Repr.repr(self, object) + + def repr1(self, x, level): + if hasattr(type(x), '__name__'): + methodname = 'repr_' + '_'.join(type(x).__name__.split()) + if hasattr(self, methodname): + return getattr(self, methodname)(x, level) + return self.escape(cram(stripid(repr(x)), self.maxother)) + + def repr_string(self, x, level): + test = cram(x, self.maxstring) + testrepr = repr(test) + if '\\' in test and '\\' not in replace(testrepr, r'\\', ''): + # Backslashes are only literal in the string and are never + # needed to make any special characters, so show a raw string. + return 'r' + testrepr[0] + self.escape(test) + testrepr[0] + return re.sub(r'((\\[\\abfnrtv\'"]|\\[0-9]..|\\x..|\\u....)+)', + r'\1', + self.escape(testrepr)) + + repr_str = repr_string + + def repr_instance(self, x, level): + try: + return self.escape(cram(stripid(repr(x)), self.maxstring)) + except: + return self.escape('<%s instance>' % x.__class__.__name__) + + repr_unicode = repr_string + +class HTMLDoc(Doc): + """Formatter class for HTML documentation.""" + + # ------------------------------------------- HTML formatting utilities + + _repr_instance = HTMLRepr() + repr = _repr_instance.repr + escape = _repr_instance.escape + + def page(self, title, contents): + """Format an HTML page.""" + return '''\ + +Python: %s + + +%s +''' % (title, contents) + + def heading(self, title, fgcol, bgcol, extras=''): + """Format a page heading.""" + return ''' + + +
 
+ 
%s
%s
+ ''' % (bgcol, fgcol, title, fgcol, extras or ' ') + + def section(self, title, fgcol, bgcol, contents, width=6, + prelude='', marginalia=None, gap=' '): + """Format a section with a heading.""" + if marginalia is None: + marginalia = '' + ' ' * width + '' + result = '''

+ + + + ''' % (bgcol, fgcol, title) + if prelude: + result = result + ''' + + +''' % (bgcol, marginalia, prelude, gap) + else: + result = result + ''' +''' % (bgcol, marginalia, gap) + + return result + '\n
 
+%s
%s%s
%s
%s%s%s
' % contents + + def bigsection(self, title, *args): + """Format a section with a big heading.""" + title = '%s' % title + return self.section(title, *args) + + def preformat(self, text): + """Format literal preformatted text.""" + text = self.escape(text.expandtabs()) + return replace(text, '\n\n', '\n \n', '\n\n', '\n \n', + ' ', ' ', '\n', '
\n') + + def multicolumn(self, list, format, cols=4): + """Format a list of items into a multi-column list.""" + result = '' + rows = (len(list)+cols-1)//cols + for col in range(cols): + result = result + '' % (100//cols) + for i in range(rows*col, rows*col+rows): + if i < len(list): + result = result + format(list[i]) + '
\n' + result = result + '' + return '%s
' % result + + def grey(self, text): return '%s' % text + + def namelink(self, name, *dicts): + """Make a link for an identifier, given name-to-URL mappings.""" + for dict in dicts: + if name in dict: + return '%s' % (dict[name], name) + return name + + def classlink(self, object, modname): + """Make a link for a class.""" + name, module = object.__name__, sys.modules.get(object.__module__) + if hasattr(module, name) and getattr(module, name) is object: + return '%s' % ( + module.__name__, name, classname(object, modname)) + return classname(object, modname) + + def modulelink(self, object): + """Make a link for a module.""" + return '%s' % (object.__name__, object.__name__) + + def modpkglink(self, modpkginfo): + """Make a link for a module or package to display in an index.""" + name, path, ispackage, shadowed = modpkginfo + if shadowed: + return self.grey(name) + if path: + url = '%s.%s.html' % (path, name) + else: + url = '%s.html' % name + if ispackage: + text = '%s (package)' % name + else: + text = name + return '%s' % (url, text) + + def filelink(self, url, path): + """Make a link to source file.""" + return '%s' % (url, path) + + def markup(self, text, escape=None, funcs={}, classes={}, methods={}): + """Mark up some plain text, given a context of symbols to look for. + Each context dictionary maps object names to anchor names.""" + escape = escape or self.escape + results = [] + here = 0 + pattern = re.compile(r'\b((http|https|ftp)://\S+[\w/]|' + r'RFC[- ]?(\d+)|' + r'PEP[- ]?(\d+)|' + r'(self\.)?(\w+))') + while True: + match = pattern.search(text, here) + if not match: break + start, end = match.span() + results.append(escape(text[here:start])) + + all, scheme, rfc, pep, selfdot, name = match.groups() + if scheme: + url = escape(all).replace('"', '"') + results.append('%s' % (url, url)) + elif rfc: + url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc) + results.append('%s' % (url, escape(all))) + elif pep: + url = 'https://www.python.org/dev/peps/pep-%04d/' % int(pep) + results.append('%s' % (url, escape(all))) + elif selfdot: + # Create a link for methods like 'self.method(...)' + # and use for attributes like 'self.attr' + if text[end:end+1] == '(': + results.append('self.' + self.namelink(name, methods)) + else: + results.append('self.%s' % name) + elif text[end:end+1] == '(': + results.append(self.namelink(name, methods, funcs, classes)) + else: + results.append(self.namelink(name, classes)) + here = end + results.append(escape(text[here:])) + return ''.join(results) + + # ---------------------------------------------- type-specific routines + + def formattree(self, tree, modname, parent=None): + """Produce HTML for a class tree as given by inspect.getclasstree().""" + result = '' + for entry in tree: + if type(entry) is type(()): + c, bases = entry + result = result + '

' + result = result + self.classlink(c, modname) + if bases and bases != (parent,): + parents = [] + for base in bases: + parents.append(self.classlink(base, modname)) + result = result + '(' + ', '.join(parents) + ')' + result = result + '\n
' + elif type(entry) is type([]): + result = result + '
\n%s
\n' % self.formattree( + entry, modname, c) + return '
\n%s
\n' % result + + def docmodule(self, object, name=None, mod=None, *ignored): + """Produce HTML documentation for a module object.""" + name = object.__name__ # ignore the passed-in name + try: + all = object.__all__ + except AttributeError: + all = None + parts = name.split('.') + links = [] + for i in range(len(parts)-1): + links.append( + '%s' % + ('.'.join(parts[:i+1]), parts[i])) + linkedname = '.'.join(links + parts[-1:]) + head = '%s' % linkedname + try: + path = inspect.getabsfile(object) + url = urllib.parse.quote(path) + filelink = self.filelink(url, path) + except TypeError: + filelink = '(built-in)' + info = [] + if hasattr(object, '__version__'): + version = str(object.__version__) + if version[:11] == '$' + 'Revision: ' and version[-1:] == '$': + version = version[11:-1].strip() + info.append('version %s' % self.escape(version)) + if hasattr(object, '__date__'): + info.append(self.escape(str(object.__date__))) + if info: + head = head + ' (%s)' % ', '.join(info) + docloc = self.getdocloc(object) + if docloc is not None: + docloc = '
Module Reference' % locals() + else: + docloc = '' + result = self.heading( + head, '#ffffff', '#7799ee', + 'index
' + filelink + docloc) + + modules = inspect.getmembers(object, inspect.ismodule) + + classes, cdict = [], {} + for key, value in inspect.getmembers(object, _isclass): + # if __all__ exists, believe it. Otherwise use old heuristic. + if (all is not None or + (inspect.getmodule(value) or object) is object): + if visiblename(key, all, object): + classes.append((key, value)) + cdict[key] = cdict[value] = '#' + key + for key, value in classes: + for base in value.__bases__: + key, modname = base.__name__, base.__module__ + module = sys.modules.get(modname) + if modname != name and module and hasattr(module, key): + if getattr(module, key) is base: + if not key in cdict: + cdict[key] = cdict[base] = modname + '.html#' + key + funcs, fdict = [], {} + for key, value in inspect.getmembers(object, inspect.isroutine): + # if __all__ exists, believe it. Otherwise use old heuristic. + if (all is not None or + inspect.isbuiltin(value) or inspect.getmodule(value) is object): + if visiblename(key, all, object): + funcs.append((key, value)) + fdict[key] = '#-' + key + if inspect.isfunction(value): fdict[value] = fdict[key] + data = [] + for key, value in inspect.getmembers(object, isdata): + if visiblename(key, all, object): + data.append((key, value)) + + doc = self.markup(getdoc(object), self.preformat, fdict, cdict) + doc = doc and '%s' % doc + result = result + '

%s

\n' % doc + + if hasattr(object, '__path__'): + modpkgs = [] + for importer, modname, ispkg in pkgutil.iter_modules(object.__path__): + modpkgs.append((modname, name, ispkg, 0)) + modpkgs.sort() + contents = self.multicolumn(modpkgs, self.modpkglink) + result = result + self.bigsection( + 'Package Contents', '#ffffff', '#aa55cc', contents) + elif modules: + contents = self.multicolumn( + modules, lambda t: self.modulelink(t[1])) + result = result + self.bigsection( + 'Modules', '#ffffff', '#aa55cc', contents) + + if classes: + classlist = [value for (key, value) in classes] + contents = [ + self.formattree(inspect.getclasstree(classlist, 1), name)] + for key, value in classes: + contents.append(self.document(value, key, name, fdict, cdict)) + result = result + self.bigsection( + 'Classes', '#ffffff', '#ee77aa', ' '.join(contents)) + if funcs: + contents = [] + for key, value in funcs: + contents.append(self.document(value, key, name, fdict, cdict)) + result = result + self.bigsection( + 'Functions', '#ffffff', '#eeaa77', ' '.join(contents)) + if data: + contents = [] + for key, value in data: + contents.append(self.document(value, key)) + result = result + self.bigsection( + 'Data', '#ffffff', '#55aa55', '
\n'.join(contents)) + if hasattr(object, '__author__'): + contents = self.markup(str(object.__author__), self.preformat) + result = result + self.bigsection( + 'Author', '#ffffff', '#7799ee', contents) + if hasattr(object, '__credits__'): + contents = self.markup(str(object.__credits__), self.preformat) + result = result + self.bigsection( + 'Credits', '#ffffff', '#7799ee', contents) + + return result + + def docclass(self, object, name=None, mod=None, funcs={}, classes={}, + *ignored): + """Produce HTML documentation for a class object.""" + realname = object.__name__ + name = name or realname + bases = object.__bases__ + + contents = [] + push = contents.append + + # Cute little class to pump out a horizontal rule between sections. + class HorizontalRule: + def __init__(self): + self.needone = 0 + def maybe(self): + if self.needone: + push('
\n') + self.needone = 1 + hr = HorizontalRule() + + # List the mro, if non-trivial. + mro = deque(inspect.getmro(object)) + if len(mro) > 2: + hr.maybe() + push('
Method resolution order:
\n') + for base in mro: + push('
%s
\n' % self.classlink(base, + object.__module__)) + push('
\n') + + def spill(msg, attrs, predicate): + ok, attrs = _split_list(attrs, predicate) + if ok: + hr.maybe() + push(msg) + for name, kind, homecls, value in ok: + try: + value = getattr(object, name) + except Exception: + # Some descriptors may meet a failure in their __get__. + # (bug #1785) + push(self.docdata(value, name, mod)) + else: + push(self.document(value, name, mod, + funcs, classes, mdict, object)) + push('\n') + return attrs + + def spilldescriptors(msg, attrs, predicate): + ok, attrs = _split_list(attrs, predicate) + if ok: + hr.maybe() + push(msg) + for name, kind, homecls, value in ok: + push(self.docdata(value, name, mod)) + return attrs + + def spilldata(msg, attrs, predicate): + ok, attrs = _split_list(attrs, predicate) + if ok: + hr.maybe() + push(msg) + for name, kind, homecls, value in ok: + base = self.docother(getattr(object, name), name, mod) + doc = getdoc(value) + if not doc: + push('
%s
\n' % base) + else: + doc = self.markup(getdoc(value), self.preformat, + funcs, classes, mdict) + doc = '
%s' % doc + push('
%s%s
\n' % (base, doc)) + push('\n') + return attrs + + attrs = [(name, kind, cls, value) + for name, kind, cls, value in classify_class_attrs(object) + if visiblename(name, obj=object)] + + mdict = {} + for key, kind, homecls, value in attrs: + mdict[key] = anchor = '#' + name + '-' + key + try: + value = getattr(object, name) + except Exception: + # Some descriptors may meet a failure in their __get__. + # (bug #1785) + pass + try: + # The value may not be hashable (e.g., a data attr with + # a dict or list value). + mdict[value] = anchor + except TypeError: + pass + + while attrs: + if mro: + thisclass = mro.popleft() + else: + thisclass = attrs[0][2] + attrs, inherited = _split_list(attrs, lambda t: t[2] is thisclass) + + if object is not builtins.object and thisclass is builtins.object: + attrs = inherited + continue + elif thisclass is object: + tag = 'defined here' + else: + tag = 'inherited from %s' % self.classlink(thisclass, + object.__module__) + tag += ':
\n' + + sort_attributes(attrs, object) + + # Pump out the attrs, segregated by kind. + attrs = spill('Methods %s' % tag, attrs, + lambda t: t[1] == 'method') + attrs = spill('Class methods %s' % tag, attrs, + lambda t: t[1] == 'class method') + attrs = spill('Static methods %s' % tag, attrs, + lambda t: t[1] == 'static method') + attrs = spilldescriptors("Readonly properties %s" % tag, attrs, + lambda t: t[1] == 'readonly property') + attrs = spilldescriptors('Data descriptors %s' % tag, attrs, + lambda t: t[1] == 'data descriptor') + attrs = spilldata('Data and other attributes %s' % tag, attrs, + lambda t: t[1] == 'data') + assert attrs == [] + attrs = inherited + + contents = ''.join(contents) + + if name == realname: + title = 'class %s' % ( + name, realname) + else: + title = '%s = class %s' % ( + name, name, realname) + if bases: + parents = [] + for base in bases: + parents.append(self.classlink(base, object.__module__)) + title = title + '(%s)' % ', '.join(parents) + + decl = '' + try: + signature = inspect.signature(object) + except (ValueError, TypeError): + signature = None + if signature: + argspec = str(signature) + if argspec and argspec != '()': + decl = name + self.escape(argspec) + '\n\n' + + doc = getdoc(object) + if decl: + doc = decl + (doc or '') + doc = self.markup(doc, self.preformat, funcs, classes, mdict) + doc = doc and '%s
 
' % doc + + return self.section(title, '#000000', '#ffc8d8', contents, 3, doc) + + def formatvalue(self, object): + """Format an argument default value as text.""" + return self.grey('=' + self.repr(object)) + + def docroutine(self, object, name=None, mod=None, + funcs={}, classes={}, methods={}, cl=None): + """Produce HTML documentation for a function or method object.""" + realname = object.__name__ + name = name or realname + anchor = (cl and cl.__name__ or '') + '-' + name + note = '' + skipdocs = 0 + if _is_bound_method(object): + imclass = object.__self__.__class__ + if cl: + if imclass is not cl: + note = ' from ' + self.classlink(imclass, mod) + else: + if object.__self__ is not None: + note = ' method of %s instance' % self.classlink( + object.__self__.__class__, mod) + else: + note = ' unbound %s method' % self.classlink(imclass,mod) + + if (inspect.iscoroutinefunction(object) or + inspect.isasyncgenfunction(object)): + asyncqualifier = 'async ' + else: + asyncqualifier = '' + + if name == realname: + title = '%s' % (anchor, realname) + else: + if cl and inspect.getattr_static(cl, realname, []) is object: + reallink = '%s' % ( + cl.__name__ + '-' + realname, realname) + skipdocs = 1 + else: + reallink = realname + title = '%s = %s' % ( + anchor, name, reallink) + argspec = None + if inspect.isroutine(object): + try: + signature = inspect.signature(object) + except (ValueError, TypeError): + signature = None + if signature: + argspec = str(signature) + if realname == '': + title = '%s lambda ' % name + # XXX lambda's won't usually have func_annotations['return'] + # since the syntax doesn't support but it is possible. + # So removing parentheses isn't truly safe. + argspec = argspec[1:-1] # remove parentheses + if not argspec: + argspec = '(...)' + + decl = asyncqualifier + title + self.escape(argspec) + (note and + self.grey('%s' % note)) + + if skipdocs: + return '
%s
\n' % decl + else: + doc = self.markup( + getdoc(object), self.preformat, funcs, classes, methods) + doc = doc and '
%s
' % doc + return '
%s
%s
\n' % (decl, doc) + + def docdata(self, object, name=None, mod=None, cl=None): + """Produce html documentation for a data descriptor.""" + results = [] + push = results.append + + if name: + push('
%s
\n' % name) + doc = self.markup(getdoc(object), self.preformat) + if doc: + push('
%s
\n' % doc) + push('
\n') + + return ''.join(results) + + docproperty = docdata + + def docother(self, object, name=None, mod=None, *ignored): + """Produce HTML documentation for a data object.""" + lhs = name and '%s = ' % name or '' + return lhs + self.repr(object) + + def index(self, dir, shadowed=None): + """Generate an HTML index for a directory of modules.""" + modpkgs = [] + if shadowed is None: shadowed = {} + for importer, name, ispkg in pkgutil.iter_modules([dir]): + if any((0xD800 <= ord(ch) <= 0xDFFF) for ch in name): + # ignore a module if its name contains a surrogate character + continue + modpkgs.append((name, '', ispkg, name in shadowed)) + shadowed[name] = 1 + + modpkgs.sort() + contents = self.multicolumn(modpkgs, self.modpkglink) + return self.bigsection(dir, '#ffffff', '#ee77aa', contents) + +# -------------------------------------------- text documentation generator + +class TextRepr(Repr): + """Class for safely making a text representation of a Python object.""" + def __init__(self): + Repr.__init__(self) + self.maxlist = self.maxtuple = 20 + self.maxdict = 10 + self.maxstring = self.maxother = 100 + + def repr1(self, x, level): + if hasattr(type(x), '__name__'): + methodname = 'repr_' + '_'.join(type(x).__name__.split()) + if hasattr(self, methodname): + return getattr(self, methodname)(x, level) + return cram(stripid(repr(x)), self.maxother) + + def repr_string(self, x, level): + test = cram(x, self.maxstring) + testrepr = repr(test) + if '\\' in test and '\\' not in replace(testrepr, r'\\', ''): + # Backslashes are only literal in the string and are never + # needed to make any special characters, so show a raw string. + return 'r' + testrepr[0] + test + testrepr[0] + return testrepr + + repr_str = repr_string + + def repr_instance(self, x, level): + try: + return cram(stripid(repr(x)), self.maxstring) + except: + return '<%s instance>' % x.__class__.__name__ + +class TextDoc(Doc): + """Formatter class for text documentation.""" + + # ------------------------------------------- text formatting utilities + + _repr_instance = TextRepr() + repr = _repr_instance.repr + + def bold(self, text): + """Format a string in bold by overstriking.""" + return ''.join(ch + '\b' + ch for ch in text) + + def indent(self, text, prefix=' '): + """Indent text by prepending a given prefix to each line.""" + if not text: return '' + lines = [prefix + line for line in text.split('\n')] + if lines: lines[-1] = lines[-1].rstrip() + return '\n'.join(lines) + + def section(self, title, contents): + """Format a section with a given heading.""" + clean_contents = self.indent(contents).rstrip() + return self.bold(title) + '\n' + clean_contents + '\n\n' + + # ---------------------------------------------- type-specific routines + + def formattree(self, tree, modname, parent=None, prefix=''): + """Render in text a class tree as returned by inspect.getclasstree().""" + result = '' + for entry in tree: + if type(entry) is type(()): + c, bases = entry + result = result + prefix + classname(c, modname) + if bases and bases != (parent,): + parents = (classname(c, modname) for c in bases) + result = result + '(%s)' % ', '.join(parents) + result = result + '\n' + elif type(entry) is type([]): + result = result + self.formattree( + entry, modname, c, prefix + ' ') + return result + + def docmodule(self, object, name=None, mod=None): + """Produce text documentation for a given module object.""" + name = object.__name__ # ignore the passed-in name + synop, desc = splitdoc(getdoc(object)) + result = self.section('NAME', name + (synop and ' - ' + synop)) + all = getattr(object, '__all__', None) + docloc = self.getdocloc(object) + if docloc is not None: + result = result + self.section('MODULE REFERENCE', docloc + """ + +The following documentation is automatically generated from the Python +source files. It may be incomplete, incorrect or include features that +are considered implementation detail and may vary between Python +implementations. When in doubt, consult the module reference at the +location listed above. +""") + + if desc: + result = result + self.section('DESCRIPTION', desc) + + classes = [] + for key, value in inspect.getmembers(object, _isclass): + # if __all__ exists, believe it. Otherwise use old heuristic. + if (all is not None + or (inspect.getmodule(value) or object) is object): + if visiblename(key, all, object): + classes.append((key, value)) + funcs = [] + for key, value in inspect.getmembers(object, inspect.isroutine): + # if __all__ exists, believe it. Otherwise use old heuristic. + if (all is not None or + inspect.isbuiltin(value) or inspect.getmodule(value) is object): + if visiblename(key, all, object): + funcs.append((key, value)) + data = [] + for key, value in inspect.getmembers(object, isdata): + if visiblename(key, all, object): + data.append((key, value)) + + modpkgs = [] + modpkgs_names = set() + if hasattr(object, '__path__'): + for importer, modname, ispkg in pkgutil.iter_modules(object.__path__): + modpkgs_names.add(modname) + if ispkg: + modpkgs.append(modname + ' (package)') + else: + modpkgs.append(modname) + + modpkgs.sort() + result = result + self.section( + 'PACKAGE CONTENTS', '\n'.join(modpkgs)) + + # Detect submodules as sometimes created by C extensions + submodules = [] + for key, value in inspect.getmembers(object, inspect.ismodule): + if value.__name__.startswith(name + '.') and key not in modpkgs_names: + submodules.append(key) + if submodules: + submodules.sort() + result = result + self.section( + 'SUBMODULES', '\n'.join(submodules)) + + if classes: + classlist = [value for key, value in classes] + contents = [self.formattree( + inspect.getclasstree(classlist, 1), name)] + for key, value in classes: + contents.append(self.document(value, key, name)) + result = result + self.section('CLASSES', '\n'.join(contents)) + + if funcs: + contents = [] + for key, value in funcs: + contents.append(self.document(value, key, name)) + result = result + self.section('FUNCTIONS', '\n'.join(contents)) + + if data: + contents = [] + for key, value in data: + contents.append(self.docother(value, key, name, maxlen=70)) + result = result + self.section('DATA', '\n'.join(contents)) + + if hasattr(object, '__version__'): + version = str(object.__version__) + if version[:11] == '$' + 'Revision: ' and version[-1:] == '$': + version = version[11:-1].strip() + result = result + self.section('VERSION', version) + if hasattr(object, '__date__'): + result = result + self.section('DATE', str(object.__date__)) + if hasattr(object, '__author__'): + result = result + self.section('AUTHOR', str(object.__author__)) + if hasattr(object, '__credits__'): + result = result + self.section('CREDITS', str(object.__credits__)) + try: + file = inspect.getabsfile(object) + except TypeError: + file = '(built-in)' + result = result + self.section('FILE', file) + return result + + def docclass(self, object, name=None, mod=None, *ignored): + """Produce text documentation for a given class object.""" + realname = object.__name__ + name = name or realname + bases = object.__bases__ + + def makename(c, m=object.__module__): + return classname(c, m) + + if name == realname: + title = 'class ' + self.bold(realname) + else: + title = self.bold(name) + ' = class ' + realname + if bases: + parents = map(makename, bases) + title = title + '(%s)' % ', '.join(parents) + + contents = [] + push = contents.append + + try: + signature = inspect.signature(object) + except (ValueError, TypeError): + signature = None + if signature: + argspec = str(signature) + if argspec and argspec != '()': + push(name + argspec + '\n') + + doc = getdoc(object) + if doc: + push(doc + '\n') + + # List the mro, if non-trivial. + mro = deque(inspect.getmro(object)) + if len(mro) > 2: + push("Method resolution order:") + for base in mro: + push(' ' + makename(base)) + push('') + + # List the built-in subclasses, if any: + subclasses = sorted( + (str(cls.__name__) for cls in type.__subclasses__(object) + if not cls.__name__.startswith("_") and cls.__module__ == "builtins"), + key=str.lower + ) + no_of_subclasses = len(subclasses) + MAX_SUBCLASSES_TO_DISPLAY = 4 + if subclasses: + push("Built-in subclasses:") + for subclassname in subclasses[:MAX_SUBCLASSES_TO_DISPLAY]: + push(' ' + subclassname) + if no_of_subclasses > MAX_SUBCLASSES_TO_DISPLAY: + push(' ... and ' + + str(no_of_subclasses - MAX_SUBCLASSES_TO_DISPLAY) + + ' other subclasses') + push('') + + # Cute little class to pump out a horizontal rule between sections. + class HorizontalRule: + def __init__(self): + self.needone = 0 + def maybe(self): + if self.needone: + push('-' * 70) + self.needone = 1 + hr = HorizontalRule() + + def spill(msg, attrs, predicate): + ok, attrs = _split_list(attrs, predicate) + if ok: + hr.maybe() + push(msg) + for name, kind, homecls, value in ok: + try: + value = getattr(object, name) + except Exception: + # Some descriptors may meet a failure in their __get__. + # (bug #1785) + push(self.docdata(value, name, mod)) + else: + push(self.document(value, + name, mod, object)) + return attrs + + def spilldescriptors(msg, attrs, predicate): + ok, attrs = _split_list(attrs, predicate) + if ok: + hr.maybe() + push(msg) + for name, kind, homecls, value in ok: + push(self.docdata(value, name, mod)) + return attrs + + def spilldata(msg, attrs, predicate): + ok, attrs = _split_list(attrs, predicate) + if ok: + hr.maybe() + push(msg) + for name, kind, homecls, value in ok: + doc = getdoc(value) + try: + obj = getattr(object, name) + except AttributeError: + obj = homecls.__dict__[name] + push(self.docother(obj, name, mod, maxlen=70, doc=doc) + + '\n') + return attrs + + attrs = [(name, kind, cls, value) + for name, kind, cls, value in classify_class_attrs(object) + if visiblename(name, obj=object)] + + while attrs: + if mro: + thisclass = mro.popleft() + else: + thisclass = attrs[0][2] + attrs, inherited = _split_list(attrs, lambda t: t[2] is thisclass) + + if object is not builtins.object and thisclass is builtins.object: + attrs = inherited + continue + elif thisclass is object: + tag = "defined here" + else: + tag = "inherited from %s" % classname(thisclass, + object.__module__) + + sort_attributes(attrs, object) + + # Pump out the attrs, segregated by kind. + attrs = spill("Methods %s:\n" % tag, attrs, + lambda t: t[1] == 'method') + attrs = spill("Class methods %s:\n" % tag, attrs, + lambda t: t[1] == 'class method') + attrs = spill("Static methods %s:\n" % tag, attrs, + lambda t: t[1] == 'static method') + attrs = spilldescriptors("Readonly properties %s:\n" % tag, attrs, + lambda t: t[1] == 'readonly property') + attrs = spilldescriptors("Data descriptors %s:\n" % tag, attrs, + lambda t: t[1] == 'data descriptor') + attrs = spilldata("Data and other attributes %s:\n" % tag, attrs, + lambda t: t[1] == 'data') + + assert attrs == [] + attrs = inherited + + contents = '\n'.join(contents) + if not contents: + return title + '\n' + return title + '\n' + self.indent(contents.rstrip(), ' | ') + '\n' + + def formatvalue(self, object): + """Format an argument default value as text.""" + return '=' + self.repr(object) + + def docroutine(self, object, name=None, mod=None, cl=None): + """Produce text documentation for a function or method object.""" + realname = object.__name__ + name = name or realname + note = '' + skipdocs = 0 + if _is_bound_method(object): + imclass = object.__self__.__class__ + if cl: + if imclass is not cl: + note = ' from ' + classname(imclass, mod) + else: + if object.__self__ is not None: + note = ' method of %s instance' % classname( + object.__self__.__class__, mod) + else: + note = ' unbound %s method' % classname(imclass,mod) + + if (inspect.iscoroutinefunction(object) or + inspect.isasyncgenfunction(object)): + asyncqualifier = 'async ' + else: + asyncqualifier = '' + + if name == realname: + title = self.bold(realname) + else: + if cl and inspect.getattr_static(cl, realname, []) is object: + skipdocs = 1 + title = self.bold(name) + ' = ' + realname + argspec = None + + if inspect.isroutine(object): + try: + signature = inspect.signature(object) + except (ValueError, TypeError): + signature = None + if signature: + argspec = str(signature) + if realname == '': + title = self.bold(name) + ' lambda ' + # XXX lambda's won't usually have func_annotations['return'] + # since the syntax doesn't support but it is possible. + # So removing parentheses isn't truly safe. + argspec = argspec[1:-1] # remove parentheses + if not argspec: + argspec = '(...)' + decl = asyncqualifier + title + argspec + note + + if skipdocs: + return decl + '\n' + else: + doc = getdoc(object) or '' + return decl + '\n' + (doc and self.indent(doc).rstrip() + '\n') + + def docdata(self, object, name=None, mod=None, cl=None): + """Produce text documentation for a data descriptor.""" + results = [] + push = results.append + + if name: + push(self.bold(name)) + push('\n') + doc = getdoc(object) or '' + if doc: + push(self.indent(doc)) + push('\n') + return ''.join(results) + + docproperty = docdata + + def docother(self, object, name=None, mod=None, parent=None, maxlen=None, doc=None): + """Produce text documentation for a data object.""" + repr = self.repr(object) + if maxlen: + line = (name and name + ' = ' or '') + repr + chop = maxlen - len(line) + if chop < 0: repr = repr[:chop] + '...' + line = (name and self.bold(name) + ' = ' or '') + repr + if not doc: + doc = getdoc(object) + if doc: + line += '\n' + self.indent(str(doc)) + '\n' + return line + +class _PlainTextDoc(TextDoc): + """Subclass of TextDoc which overrides string styling""" + def bold(self, text): + return text + +# --------------------------------------------------------- user interfaces + +def pager(text): + """The first time this is called, determine what kind of pager to use.""" + global pager + pager = getpager() + pager(text) + +def getpager(): + """Decide what method to use for paging through text.""" + if not hasattr(sys.stdin, "isatty"): + return plainpager + if not hasattr(sys.stdout, "isatty"): + return plainpager + if not sys.stdin.isatty() or not sys.stdout.isatty(): + return plainpager + use_pager = os.environ.get('MANPAGER') or os.environ.get('PAGER') + if use_pager: + if sys.platform == 'win32': # pipes completely broken in Windows + return lambda text: tempfilepager(plain(text), use_pager) + elif os.environ.get('TERM') in ('dumb', 'emacs'): + return lambda text: pipepager(plain(text), use_pager) + else: + return lambda text: pipepager(text, use_pager) + if os.environ.get('TERM') in ('dumb', 'emacs'): + return plainpager + if sys.platform == 'win32': + return lambda text: tempfilepager(plain(text), 'more <') + if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0: + return lambda text: pipepager(text, 'less') + + import tempfile + (fd, filename) = tempfile.mkstemp() + os.close(fd) + try: + if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0: + return lambda text: pipepager(text, 'more') + else: + return ttypager + finally: + os.unlink(filename) + +def plain(text): + """Remove boldface formatting from text.""" + return re.sub('.\b', '', text) + +def pipepager(text, cmd): + """Page through text by feeding it to another program.""" + import subprocess + proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, + errors='backslashreplace') + try: + with proc.stdin as pipe: + try: + pipe.write(text) + except KeyboardInterrupt: + # We've hereby abandoned whatever text hasn't been written, + # but the pager is still in control of the terminal. + pass + except OSError: + pass # Ignore broken pipes caused by quitting the pager program. + while True: + try: + proc.wait() + break + except KeyboardInterrupt: + # Ignore ctl-c like the pager itself does. Otherwise the pager is + # left running and the terminal is in raw mode and unusable. + pass + +def tempfilepager(text, cmd): + """Page through text by invoking a program on a temporary file.""" + import tempfile + with tempfile.TemporaryDirectory() as tempdir: + filename = os.path.join(tempdir, 'pydoc.out') + with open(filename, 'w', errors='backslashreplace', + encoding=os.device_encoding(0) if + sys.platform == 'win32' else None + ) as file: + file.write(text) + os.system(cmd + ' "' + filename + '"') + +def _escape_stdout(text): + # Escape non-encodable characters to avoid encoding errors later + encoding = getattr(sys.stdout, 'encoding', None) or 'utf-8' + return text.encode(encoding, 'backslashreplace').decode(encoding) + +def ttypager(text): + """Page through text on a text terminal.""" + lines = plain(_escape_stdout(text)).split('\n') + try: + import tty + fd = sys.stdin.fileno() + old = tty.tcgetattr(fd) + tty.setcbreak(fd) + getchar = lambda: sys.stdin.read(1) + except (ImportError, AttributeError, io.UnsupportedOperation): + tty = None + getchar = lambda: sys.stdin.readline()[:-1][:1] + + try: + try: + h = int(os.environ.get('LINES', 0)) + except ValueError: + h = 0 + if h <= 1: + h = 25 + r = inc = h - 1 + sys.stdout.write('\n'.join(lines[:inc]) + '\n') + while lines[r:]: + sys.stdout.write('-- more --') + sys.stdout.flush() + c = getchar() + + if c in ('q', 'Q'): + sys.stdout.write('\r \r') + break + elif c in ('\r', '\n'): + sys.stdout.write('\r \r' + lines[r] + '\n') + r = r + 1 + continue + if c in ('b', 'B', '\x1b'): + r = r - inc - inc + if r < 0: r = 0 + sys.stdout.write('\n' + '\n'.join(lines[r:r+inc]) + '\n') + r = r + inc + + finally: + if tty: + tty.tcsetattr(fd, tty.TCSAFLUSH, old) + +def plainpager(text): + """Simply print unformatted text. This is the ultimate fallback.""" + sys.stdout.write(plain(_escape_stdout(text))) + +def describe(thing): + """Produce a short description of the given thing.""" + if inspect.ismodule(thing): + if thing.__name__ in sys.builtin_module_names: + return 'built-in module ' + thing.__name__ + if hasattr(thing, '__path__'): + return 'package ' + thing.__name__ + else: + return 'module ' + thing.__name__ + if inspect.isbuiltin(thing): + return 'built-in function ' + thing.__name__ + if inspect.isgetsetdescriptor(thing): + return 'getset descriptor %s.%s.%s' % ( + thing.__objclass__.__module__, thing.__objclass__.__name__, + thing.__name__) + if inspect.ismemberdescriptor(thing): + return 'member descriptor %s.%s.%s' % ( + thing.__objclass__.__module__, thing.__objclass__.__name__, + thing.__name__) + if _isclass(thing): + return 'class ' + thing.__name__ + if inspect.isfunction(thing): + return 'function ' + thing.__name__ + if inspect.ismethod(thing): + return 'method ' + thing.__name__ + return type(thing).__name__ + +def locate(path, forceload=0): + """Locate an object by name or dotted path, importing as necessary.""" + parts = [part for part in path.split('.') if part] + module, n = None, 0 + while n < len(parts): + nextmodule = safeimport('.'.join(parts[:n+1]), forceload) + if nextmodule: module, n = nextmodule, n + 1 + else: break + if module: + object = module + else: + object = builtins + for part in parts[n:]: + try: + object = getattr(object, part) + except AttributeError: + return None + return object + +# --------------------------------------- interactive interpreter interface + +text = TextDoc() +plaintext = _PlainTextDoc() +html = HTMLDoc() + +def resolve(thing, forceload=0): + """Given an object or a path to an object, get the object and its name.""" + if isinstance(thing, str): + object = locate(thing, forceload) + if object is None: + raise ImportError('''\ +No Python documentation found for %r. +Use help() to get the interactive help utility. +Use help(str) for help on the str class.''' % thing) + return object, thing + else: + name = getattr(thing, '__name__', None) + return thing, name if isinstance(name, str) else None + +def render_doc(thing, title='Python Library Documentation: %s', forceload=0, + renderer=None): + """Render text documentation, given an object or a path to an object.""" + if renderer is None: + renderer = text + object, name = resolve(thing, forceload) + desc = describe(object) + module = inspect.getmodule(object) + if name and '.' in name: + desc += ' in ' + name[:name.rfind('.')] + elif module and module is not object: + desc += ' in module ' + module.__name__ + + if not (inspect.ismodule(object) or + _isclass(object) or + inspect.isroutine(object) or + inspect.isdatadescriptor(object) or + _getdoc(object)): + # If the passed object is a piece of data or an instance, + # document its available methods instead of its value. + if hasattr(object, '__origin__'): + object = object.__origin__ + else: + object = type(object) + desc += ' object' + return title % desc + '\n\n' + renderer.document(object, name) + +def doc(thing, title='Python Library Documentation: %s', forceload=0, + output=None): + """Display text documentation, given an object or a path to an object.""" + try: + if output is None: + pager(render_doc(thing, title, forceload)) + else: + output.write(render_doc(thing, title, forceload, plaintext)) + except (ImportError, ErrorDuringImport) as value: + print(value) + +def writedoc(thing, forceload=0): + """Write HTML documentation to a file in the current directory.""" + try: + object, name = resolve(thing, forceload) + page = html.page(describe(object), html.document(object, name)) + with open(name + '.html', 'w', encoding='utf-8') as file: + file.write(page) + print('wrote', name + '.html') + except (ImportError, ErrorDuringImport) as value: + print(value) + +def writedocs(dir, pkgpath='', done=None): + """Write out HTML documentation for all modules in a directory tree.""" + if done is None: done = {} + for importer, modname, ispkg in pkgutil.walk_packages([dir], pkgpath): + writedoc(modname) + return + +class Helper: + + # These dictionaries map a topic name to either an alias, or a tuple + # (label, seealso-items). The "label" is the label of the corresponding + # section in the .rst file under Doc/ and an index into the dictionary + # in pydoc_data/topics.py. + # + # CAUTION: if you change one of these dictionaries, be sure to adapt the + # list of needed labels in Doc/tools/extensions/pyspecific.py and + # regenerate the pydoc_data/topics.py file by running + # make pydoc-topics + # in Doc/ and copying the output file into the Lib/ directory. + + keywords = { + 'False': '', + 'None': '', + 'True': '', + 'and': 'BOOLEAN', + 'as': 'with', + 'assert': ('assert', ''), + 'async': ('async', ''), + 'await': ('await', ''), + 'break': ('break', 'while for'), + 'class': ('class', 'CLASSES SPECIALMETHODS'), + 'continue': ('continue', 'while for'), + 'def': ('function', ''), + 'del': ('del', 'BASICMETHODS'), + 'elif': 'if', + 'else': ('else', 'while for'), + 'except': 'try', + 'finally': 'try', + 'for': ('for', 'break continue while'), + 'from': 'import', + 'global': ('global', 'nonlocal NAMESPACES'), + 'if': ('if', 'TRUTHVALUE'), + 'import': ('import', 'MODULES'), + 'in': ('in', 'SEQUENCEMETHODS'), + 'is': 'COMPARISON', + 'lambda': ('lambda', 'FUNCTIONS'), + 'nonlocal': ('nonlocal', 'global NAMESPACES'), + 'not': 'BOOLEAN', + 'or': 'BOOLEAN', + 'pass': ('pass', ''), + 'raise': ('raise', 'EXCEPTIONS'), + 'return': ('return', 'FUNCTIONS'), + 'try': ('try', 'EXCEPTIONS'), + 'while': ('while', 'break continue if TRUTHVALUE'), + 'with': ('with', 'CONTEXTMANAGERS EXCEPTIONS yield'), + 'yield': ('yield', ''), + } + # Either add symbols to this dictionary or to the symbols dictionary + # directly: Whichever is easier. They are merged later. + _strprefixes = [p + q for p in ('b', 'f', 'r', 'u') for q in ("'", '"')] + _symbols_inverse = { + 'STRINGS' : ("'", "'''", '"', '"""', *_strprefixes), + 'OPERATORS' : ('+', '-', '*', '**', '/', '//', '%', '<<', '>>', '&', + '|', '^', '~', '<', '>', '<=', '>=', '==', '!=', '<>'), + 'COMPARISON' : ('<', '>', '<=', '>=', '==', '!=', '<>'), + 'UNARY' : ('-', '~'), + 'AUGMENTEDASSIGNMENT' : ('+=', '-=', '*=', '/=', '%=', '&=', '|=', + '^=', '<<=', '>>=', '**=', '//='), + 'BITWISE' : ('<<', '>>', '&', '|', '^', '~'), + 'COMPLEX' : ('j', 'J') + } + symbols = { + '%': 'OPERATORS FORMATTING', + '**': 'POWER', + ',': 'TUPLES LISTS FUNCTIONS', + '.': 'ATTRIBUTES FLOAT MODULES OBJECTS', + '...': 'ELLIPSIS', + ':': 'SLICINGS DICTIONARYLITERALS', + '@': 'def class', + '\\': 'STRINGS', + '_': 'PRIVATENAMES', + '__': 'PRIVATENAMES SPECIALMETHODS', + '`': 'BACKQUOTES', + '(': 'TUPLES FUNCTIONS CALLS', + ')': 'TUPLES FUNCTIONS CALLS', + '[': 'LISTS SUBSCRIPTS SLICINGS', + ']': 'LISTS SUBSCRIPTS SLICINGS' + } + for topic, symbols_ in _symbols_inverse.items(): + for symbol in symbols_: + topics = symbols.get(symbol, topic) + if topic not in topics: + topics = topics + ' ' + topic + symbols[symbol] = topics + + topics = { + 'TYPES': ('types', 'STRINGS UNICODE NUMBERS SEQUENCES MAPPINGS ' + 'FUNCTIONS CLASSES MODULES FILES inspect'), + 'STRINGS': ('strings', 'str UNICODE SEQUENCES STRINGMETHODS ' + 'FORMATTING TYPES'), + 'STRINGMETHODS': ('string-methods', 'STRINGS FORMATTING'), + 'FORMATTING': ('formatstrings', 'OPERATORS'), + 'UNICODE': ('strings', 'encodings unicode SEQUENCES STRINGMETHODS ' + 'FORMATTING TYPES'), + 'NUMBERS': ('numbers', 'INTEGER FLOAT COMPLEX TYPES'), + 'INTEGER': ('integers', 'int range'), + 'FLOAT': ('floating', 'float math'), + 'COMPLEX': ('imaginary', 'complex cmath'), + 'SEQUENCES': ('typesseq', 'STRINGMETHODS FORMATTING range LISTS'), + 'MAPPINGS': 'DICTIONARIES', + 'FUNCTIONS': ('typesfunctions', 'def TYPES'), + 'METHODS': ('typesmethods', 'class def CLASSES TYPES'), + 'CODEOBJECTS': ('bltin-code-objects', 'compile FUNCTIONS TYPES'), + 'TYPEOBJECTS': ('bltin-type-objects', 'types TYPES'), + 'FRAMEOBJECTS': 'TYPES', + 'TRACEBACKS': 'TYPES', + 'NONE': ('bltin-null-object', ''), + 'ELLIPSIS': ('bltin-ellipsis-object', 'SLICINGS'), + 'SPECIALATTRIBUTES': ('specialattrs', ''), + 'CLASSES': ('types', 'class SPECIALMETHODS PRIVATENAMES'), + 'MODULES': ('typesmodules', 'import'), + 'PACKAGES': 'import', + 'EXPRESSIONS': ('operator-summary', 'lambda or and not in is BOOLEAN ' + 'COMPARISON BITWISE SHIFTING BINARY FORMATTING POWER ' + 'UNARY ATTRIBUTES SUBSCRIPTS SLICINGS CALLS TUPLES ' + 'LISTS DICTIONARIES'), + 'OPERATORS': 'EXPRESSIONS', + 'PRECEDENCE': 'EXPRESSIONS', + 'OBJECTS': ('objects', 'TYPES'), + 'SPECIALMETHODS': ('specialnames', 'BASICMETHODS ATTRIBUTEMETHODS ' + 'CALLABLEMETHODS SEQUENCEMETHODS MAPPINGMETHODS ' + 'NUMBERMETHODS CLASSES'), + 'BASICMETHODS': ('customization', 'hash repr str SPECIALMETHODS'), + 'ATTRIBUTEMETHODS': ('attribute-access', 'ATTRIBUTES SPECIALMETHODS'), + 'CALLABLEMETHODS': ('callable-types', 'CALLS SPECIALMETHODS'), + 'SEQUENCEMETHODS': ('sequence-types', 'SEQUENCES SEQUENCEMETHODS ' + 'SPECIALMETHODS'), + 'MAPPINGMETHODS': ('sequence-types', 'MAPPINGS SPECIALMETHODS'), + 'NUMBERMETHODS': ('numeric-types', 'NUMBERS AUGMENTEDASSIGNMENT ' + 'SPECIALMETHODS'), + 'EXECUTION': ('execmodel', 'NAMESPACES DYNAMICFEATURES EXCEPTIONS'), + 'NAMESPACES': ('naming', 'global nonlocal ASSIGNMENT DELETION DYNAMICFEATURES'), + 'DYNAMICFEATURES': ('dynamic-features', ''), + 'SCOPING': 'NAMESPACES', + 'FRAMES': 'NAMESPACES', + 'EXCEPTIONS': ('exceptions', 'try except finally raise'), + 'CONVERSIONS': ('conversions', ''), + 'IDENTIFIERS': ('identifiers', 'keywords SPECIALIDENTIFIERS'), + 'SPECIALIDENTIFIERS': ('id-classes', ''), + 'PRIVATENAMES': ('atom-identifiers', ''), + 'LITERALS': ('atom-literals', 'STRINGS NUMBERS TUPLELITERALS ' + 'LISTLITERALS DICTIONARYLITERALS'), + 'TUPLES': 'SEQUENCES', + 'TUPLELITERALS': ('exprlists', 'TUPLES LITERALS'), + 'LISTS': ('typesseq-mutable', 'LISTLITERALS'), + 'LISTLITERALS': ('lists', 'LISTS LITERALS'), + 'DICTIONARIES': ('typesmapping', 'DICTIONARYLITERALS'), + 'DICTIONARYLITERALS': ('dict', 'DICTIONARIES LITERALS'), + 'ATTRIBUTES': ('attribute-references', 'getattr hasattr setattr ATTRIBUTEMETHODS'), + 'SUBSCRIPTS': ('subscriptions', 'SEQUENCEMETHODS'), + 'SLICINGS': ('slicings', 'SEQUENCEMETHODS'), + 'CALLS': ('calls', 'EXPRESSIONS'), + 'POWER': ('power', 'EXPRESSIONS'), + 'UNARY': ('unary', 'EXPRESSIONS'), + 'BINARY': ('binary', 'EXPRESSIONS'), + 'SHIFTING': ('shifting', 'EXPRESSIONS'), + 'BITWISE': ('bitwise', 'EXPRESSIONS'), + 'COMPARISON': ('comparisons', 'EXPRESSIONS BASICMETHODS'), + 'BOOLEAN': ('booleans', 'EXPRESSIONS TRUTHVALUE'), + 'ASSERTION': 'assert', + 'ASSIGNMENT': ('assignment', 'AUGMENTEDASSIGNMENT'), + 'AUGMENTEDASSIGNMENT': ('augassign', 'NUMBERMETHODS'), + 'DELETION': 'del', + 'RETURNING': 'return', + 'IMPORTING': 'import', + 'CONDITIONAL': 'if', + 'LOOPING': ('compound', 'for while break continue'), + 'TRUTHVALUE': ('truth', 'if while and or not BASICMETHODS'), + 'DEBUGGING': ('debugger', 'pdb'), + 'CONTEXTMANAGERS': ('context-managers', 'with'), + } + + def __init__(self, input=None, output=None): + self._input = input + self._output = output + + @property + def input(self): + return self._input or sys.stdin + + @property + def output(self): + return self._output or sys.stdout + + def __repr__(self): + if inspect.stack()[1][3] == '?': + self() + return '' + return '<%s.%s instance>' % (self.__class__.__module__, + self.__class__.__qualname__) + + _GoInteractive = object() + def __call__(self, request=_GoInteractive): + if request is not self._GoInteractive: + self.help(request) + else: + self.intro() + self.interact() + self.output.write(''' +You are now leaving help and returning to the Python interpreter. +If you want to ask for help on a particular object directly from the +interpreter, you can type "help(object)". Executing "help('string')" +has the same effect as typing a particular string at the help> prompt. +''') + + def interact(self): + self.output.write('\n') + while True: + try: + request = self.getline('help> ') + if not request: break + except (KeyboardInterrupt, EOFError): + break + request = request.strip() + + # Make sure significant trailing quoting marks of literals don't + # get deleted while cleaning input + if (len(request) > 2 and request[0] == request[-1] in ("'", '"') + and request[0] not in request[1:-1]): + request = request[1:-1] + if request.lower() in ('q', 'quit'): break + if request == 'help': + self.intro() + else: + self.help(request) + + def getline(self, prompt): + """Read one line, using input() when appropriate.""" + if self.input is sys.stdin: + return input(prompt) + else: + self.output.write(prompt) + self.output.flush() + return self.input.readline() + + def help(self, request): + if type(request) is type(''): + request = request.strip() + if request == 'keywords': self.listkeywords() + elif request == 'symbols': self.listsymbols() + elif request == 'topics': self.listtopics() + elif request == 'modules': self.listmodules() + elif request[:8] == 'modules ': + self.listmodules(request.split()[1]) + elif request in self.symbols: self.showsymbol(request) + elif request in ['True', 'False', 'None']: + # special case these keywords since they are objects too + doc(eval(request), 'Help on %s:') + elif request in self.keywords: self.showtopic(request) + elif request in self.topics: self.showtopic(request) + elif request: doc(request, 'Help on %s:', output=self._output) + else: doc(str, 'Help on %s:', output=self._output) + elif isinstance(request, Helper): self() + else: doc(request, 'Help on %s:', output=self._output) + self.output.write('\n') + + def intro(self): + self.output.write(''' +Welcome to Python {0}'s help utility! + +If this is your first time using Python, you should definitely check out +the tutorial on the internet at https://docs.python.org/{0}/tutorial/. + +Enter the name of any module, keyword, or topic to get help on writing +Python programs and using Python modules. To quit this help utility and +return to the interpreter, just type "quit". + +To get a list of available modules, keywords, symbols, or topics, type +"modules", "keywords", "symbols", or "topics". Each module also comes +with a one-line summary of what it does; to list the modules whose name +or summary contain a given string such as "spam", type "modules spam". +'''.format('%d.%d' % sys.version_info[:2])) + + def list(self, items, columns=4, width=80): + items = list(sorted(items)) + colw = width // columns + rows = (len(items) + columns - 1) // columns + for row in range(rows): + for col in range(columns): + i = col * rows + row + if i < len(items): + self.output.write(items[i]) + if col < columns - 1: + self.output.write(' ' + ' ' * (colw - 1 - len(items[i]))) + self.output.write('\n') + + def listkeywords(self): + self.output.write(''' +Here is a list of the Python keywords. Enter any keyword to get more help. + +''') + self.list(self.keywords.keys()) + + def listsymbols(self): + self.output.write(''' +Here is a list of the punctuation symbols which Python assigns special meaning +to. Enter any symbol to get more help. + +''') + self.list(self.symbols.keys()) + + def listtopics(self): + self.output.write(''' +Here is a list of available topics. Enter any topic name to get more help. + +''') + self.list(self.topics.keys()) + + def showtopic(self, topic, more_xrefs=''): + try: + import pydoc_data.topics + except ImportError: + self.output.write(''' +Sorry, topic and keyword documentation is not available because the +module "pydoc_data.topics" could not be found. +''') + return + target = self.topics.get(topic, self.keywords.get(topic)) + if not target: + self.output.write('no documentation found for %s\n' % repr(topic)) + return + if type(target) is type(''): + return self.showtopic(target, more_xrefs) + + label, xrefs = target + try: + doc = pydoc_data.topics.topics[label] + except KeyError: + self.output.write('no documentation found for %s\n' % repr(topic)) + return + doc = doc.strip() + '\n' + if more_xrefs: + xrefs = (xrefs or '') + ' ' + more_xrefs + if xrefs: + import textwrap + text = 'Related help topics: ' + ', '.join(xrefs.split()) + '\n' + wrapped_text = textwrap.wrap(text, 72) + doc += '\n%s\n' % '\n'.join(wrapped_text) + pager(doc) + + def _gettopic(self, topic, more_xrefs=''): + """Return unbuffered tuple of (topic, xrefs). + + If an error occurs here, the exception is caught and displayed by + the url handler. + + This function duplicates the showtopic method but returns its + result directly so it can be formatted for display in an html page. + """ + try: + import pydoc_data.topics + except ImportError: + return(''' +Sorry, topic and keyword documentation is not available because the +module "pydoc_data.topics" could not be found. +''' , '') + target = self.topics.get(topic, self.keywords.get(topic)) + if not target: + raise ValueError('could not find topic') + if isinstance(target, str): + return self._gettopic(target, more_xrefs) + label, xrefs = target + doc = pydoc_data.topics.topics[label] + if more_xrefs: + xrefs = (xrefs or '') + ' ' + more_xrefs + return doc, xrefs + + def showsymbol(self, symbol): + target = self.symbols[symbol] + topic, _, xrefs = target.partition(' ') + self.showtopic(topic, xrefs) + + def listmodules(self, key=''): + if key: + self.output.write(''' +Here is a list of modules whose name or summary contains '{}'. +If there are any, enter a module name to get more help. + +'''.format(key)) + apropos(key) + else: + self.output.write(''' +Please wait a moment while I gather a list of all available modules... + +''') + modules = {} + def callback(path, modname, desc, modules=modules): + if modname and modname[-9:] == '.__init__': + modname = modname[:-9] + ' (package)' + if modname.find('.') < 0: + modules[modname] = 1 + def onerror(modname): + callback(None, modname, None) + ModuleScanner().run(callback, onerror=onerror) + self.list(modules.keys()) + self.output.write(''' +Enter any module name to get more help. Or, type "modules spam" to search +for modules whose name or summary contain the string "spam". +''') + +help = Helper() + +class ModuleScanner: + """An interruptible scanner that searches module synopses.""" + + def run(self, callback, key=None, completer=None, onerror=None): + if key: key = key.lower() + self.quit = False + seen = {} + + for modname in sys.builtin_module_names: + if modname != '__main__': + seen[modname] = 1 + if key is None: + callback(None, modname, '') + else: + name = __import__(modname).__doc__ or '' + desc = name.split('\n')[0] + name = modname + ' - ' + desc + if name.lower().find(key) >= 0: + callback(None, modname, desc) + + for importer, modname, ispkg in pkgutil.walk_packages(onerror=onerror): + if self.quit: + break + + if key is None: + callback(None, modname, '') + else: + try: + spec = pkgutil._get_spec(importer, modname) + except SyntaxError: + # raised by tests for bad coding cookies or BOM + continue + loader = spec.loader + if hasattr(loader, 'get_source'): + try: + source = loader.get_source(modname) + except Exception: + if onerror: + onerror(modname) + continue + desc = source_synopsis(io.StringIO(source)) or '' + if hasattr(loader, 'get_filename'): + path = loader.get_filename(modname) + else: + path = None + else: + try: + module = importlib._bootstrap._load(spec) + except ImportError: + if onerror: + onerror(modname) + continue + desc = module.__doc__.splitlines()[0] if module.__doc__ else '' + path = getattr(module,'__file__',None) + name = modname + ' - ' + desc + if name.lower().find(key) >= 0: + callback(path, modname, desc) + + if completer: + completer() + +def apropos(key): + """Print all the one-line module summaries that contain a substring.""" + def callback(path, modname, desc): + if modname[-9:] == '.__init__': + modname = modname[:-9] + ' (package)' + print(modname, desc and '- ' + desc) + def onerror(modname): + pass + with warnings.catch_warnings(): + warnings.filterwarnings('ignore') # ignore problems during import + ModuleScanner().run(callback, key, onerror=onerror) + +# --------------------------------------- enhanced web browser interface + +def _start_server(urlhandler, hostname, port): + """Start an HTTP server thread on a specific port. + + Start an HTML/text server thread, so HTML or text documents can be + browsed dynamically and interactively with a web browser. Example use: + + >>> import time + >>> import pydoc + + Define a URL handler. To determine what the client is asking + for, check the URL and content_type. + + Then get or generate some text or HTML code and return it. + + >>> def my_url_handler(url, content_type): + ... text = 'the URL sent was: (%s, %s)' % (url, content_type) + ... return text + + Start server thread on port 0. + If you use port 0, the server will pick a random port number. + You can then use serverthread.port to get the port number. + + >>> port = 0 + >>> serverthread = pydoc._start_server(my_url_handler, port) + + Check that the server is really started. If it is, open browser + and get first page. Use serverthread.url as the starting page. + + >>> if serverthread.serving: + ... import webbrowser + + The next two lines are commented out so a browser doesn't open if + doctest is run on this module. + + #... webbrowser.open(serverthread.url) + #True + + Let the server do its thing. We just need to monitor its status. + Use time.sleep so the loop doesn't hog the CPU. + + >>> starttime = time.monotonic() + >>> timeout = 1 #seconds + + This is a short timeout for testing purposes. + + >>> while serverthread.serving: + ... time.sleep(.01) + ... if serverthread.serving and time.monotonic() - starttime > timeout: + ... serverthread.stop() + ... break + + Print any errors that may have occurred. + + >>> print(serverthread.error) + None + """ + import http.server + import email.message + import select + import threading + + class DocHandler(http.server.BaseHTTPRequestHandler): + + def do_GET(self): + """Process a request from an HTML browser. + + The URL received is in self.path. + Get an HTML page from self.urlhandler and send it. + """ + if self.path.endswith('.css'): + content_type = 'text/css' + else: + content_type = 'text/html' + self.send_response(200) + self.send_header('Content-Type', '%s; charset=UTF-8' % content_type) + self.end_headers() + self.wfile.write(self.urlhandler( + self.path, content_type).encode('utf-8')) + + def log_message(self, *args): + # Don't log messages. + pass + + class DocServer(http.server.HTTPServer): + + def __init__(self, host, port, callback): + self.host = host + self.address = (self.host, port) + self.callback = callback + self.base.__init__(self, self.address, self.handler) + self.quit = False + + def serve_until_quit(self): + while not self.quit: + rd, wr, ex = select.select([self.socket.fileno()], [], [], 1) + if rd: + self.handle_request() + self.server_close() + + def server_activate(self): + self.base.server_activate(self) + if self.callback: + self.callback(self) + + class ServerThread(threading.Thread): + + def __init__(self, urlhandler, host, port): + self.urlhandler = urlhandler + self.host = host + self.port = int(port) + threading.Thread.__init__(self) + self.serving = False + self.error = None + + def run(self): + """Start the server.""" + try: + DocServer.base = http.server.HTTPServer + DocServer.handler = DocHandler + DocHandler.MessageClass = email.message.Message + DocHandler.urlhandler = staticmethod(self.urlhandler) + docsvr = DocServer(self.host, self.port, self.ready) + self.docserver = docsvr + docsvr.serve_until_quit() + except Exception as e: + self.error = e + + def ready(self, server): + self.serving = True + self.host = server.host + self.port = server.server_port + self.url = 'http://%s:%d/' % (self.host, self.port) + + def stop(self): + """Stop the server and this thread nicely""" + self.docserver.quit = True + self.join() + # explicitly break a reference cycle: DocServer.callback + # has indirectly a reference to ServerThread. + self.docserver = None + self.serving = False + self.url = None + + thread = ServerThread(urlhandler, hostname, port) + thread.start() + # Wait until thread.serving is True to make sure we are + # really up before returning. + while not thread.error and not thread.serving: + time.sleep(.01) + return thread + + +def _url_handler(url, content_type="text/html"): + """The pydoc url handler for use with the pydoc server. + + If the content_type is 'text/css', the _pydoc.css style + sheet is read and returned if it exits. + + If the content_type is 'text/html', then the result of + get_html_page(url) is returned. + """ + class _HTMLDoc(HTMLDoc): + + def page(self, title, contents): + """Format an HTML page.""" + css_path = "pydoc_data/_pydoc.css" + css_link = ( + '' % + css_path) + return '''\ + +Pydoc: %s + +%s%s
%s
+''' % (title, css_link, html_navbar(), contents) + + + html = _HTMLDoc() + + def html_navbar(): + version = html.escape("%s [%s, %s]" % (platform.python_version(), + platform.python_build()[0], + platform.python_compiler())) + return """ +
+ Python %s
%s +
+
+ +
+
+ + +
  +
+ + +
+
+
+ """ % (version, html.escape(platform.platform(terse=True))) + + def html_index(): + """Module Index page.""" + + def bltinlink(name): + return '%s' % (name, name) + + heading = html.heading( + 'Index of Modules', + '#ffffff', '#7799ee') + names = [name for name in sys.builtin_module_names + if name != '__main__'] + contents = html.multicolumn(names, bltinlink) + contents = [heading, '

' + html.bigsection( + 'Built-in Modules', '#ffffff', '#ee77aa', contents)] + + seen = {} + for dir in sys.path: + contents.append(html.index(dir, seen)) + + contents.append( + '

pydoc by Ka-Ping Yee' + '<ping@lfw.org>') + return 'Index of Modules', ''.join(contents) + + def html_search(key): + """Search results page.""" + # scan for modules + search_result = [] + + def callback(path, modname, desc): + if modname[-9:] == '.__init__': + modname = modname[:-9] + ' (package)' + search_result.append((modname, desc and '- ' + desc)) + + with warnings.catch_warnings(): + warnings.filterwarnings('ignore') # ignore problems during import + def onerror(modname): + pass + ModuleScanner().run(callback, key, onerror=onerror) + + # format page + def bltinlink(name): + return '%s' % (name, name) + + results = [] + heading = html.heading( + 'Search Results', + '#ffffff', '#7799ee') + for name, desc in search_result: + results.append(bltinlink(name) + desc) + contents = heading + html.bigsection( + 'key = %s' % key, '#ffffff', '#ee77aa', '
'.join(results)) + return 'Search Results', contents + + def html_topics(): + """Index of topic texts available.""" + + def bltinlink(name): + return '%s' % (name, name) + + heading = html.heading( + 'INDEX', + '#ffffff', '#7799ee') + names = sorted(Helper.topics.keys()) + + contents = html.multicolumn(names, bltinlink) + contents = heading + html.bigsection( + 'Topics', '#ffffff', '#ee77aa', contents) + return 'Topics', contents + + def html_keywords(): + """Index of keywords.""" + heading = html.heading( + 'INDEX', + '#ffffff', '#7799ee') + names = sorted(Helper.keywords.keys()) + + def bltinlink(name): + return '%s' % (name, name) + + contents = html.multicolumn(names, bltinlink) + contents = heading + html.bigsection( + 'Keywords', '#ffffff', '#ee77aa', contents) + return 'Keywords', contents + + def html_topicpage(topic): + """Topic or keyword help page.""" + buf = io.StringIO() + htmlhelp = Helper(buf, buf) + contents, xrefs = htmlhelp._gettopic(topic) + if topic in htmlhelp.keywords: + title = 'KEYWORD' + else: + title = 'TOPIC' + heading = html.heading( + '%s' % title, + '#ffffff', '#7799ee') + contents = '

%s
' % html.markup(contents) + contents = html.bigsection(topic , '#ffffff','#ee77aa', contents) + if xrefs: + xrefs = sorted(xrefs.split()) + + def bltinlink(name): + return '%s' % (name, name) + + xrefs = html.multicolumn(xrefs, bltinlink) + xrefs = html.section('Related help topics: ', + '#ffffff', '#ee77aa', xrefs) + return ('%s %s' % (title, topic), + ''.join((heading, contents, xrefs))) + + def html_getobj(url): + obj = locate(url, forceload=1) + if obj is None and url != 'None': + raise ValueError('could not find object') + title = describe(obj) + content = html.document(obj, url) + return title, content + + def html_error(url, exc): + heading = html.heading( + 'Error', + '#ffffff', '#7799ee') + contents = '
'.join(html.escape(line) for line in + format_exception_only(type(exc), exc)) + contents = heading + html.bigsection(url, '#ffffff', '#bb0000', + contents) + return "Error - %s" % url, contents + + def get_html_page(url): + """Generate an HTML page for url.""" + complete_url = url + if url.endswith('.html'): + url = url[:-5] + try: + if url in ("", "index"): + title, content = html_index() + elif url == "topics": + title, content = html_topics() + elif url == "keywords": + title, content = html_keywords() + elif '=' in url: + op, _, url = url.partition('=') + if op == "search?key": + title, content = html_search(url) + elif op == "topic?key": + # try topics first, then objects. + try: + title, content = html_topicpage(url) + except ValueError: + title, content = html_getobj(url) + elif op == "get?key": + # try objects first, then topics. + if url in ("", "index"): + title, content = html_index() + else: + try: + title, content = html_getobj(url) + except ValueError: + title, content = html_topicpage(url) + else: + raise ValueError('bad pydoc url') + else: + title, content = html_getobj(url) + except Exception as exc: + # Catch any errors and display them in an error page. + title, content = html_error(complete_url, exc) + return html.page(title, content) + + if url.startswith('/'): + url = url[1:] + if content_type == 'text/css': + path_here = os.path.dirname(os.path.realpath(__file__)) + css_path = os.path.join(path_here, url) + with open(css_path) as fp: + return ''.join(fp.readlines()) + elif content_type == 'text/html': + return get_html_page(url) + # Errors outside the url handler are caught by the server. + raise TypeError('unknown content type %r for url %s' % (content_type, url)) + + +def browse(port=0, *, open_browser=True, hostname='localhost'): + """Start the enhanced pydoc web server and open a web browser. + + Use port '0' to start the server on an arbitrary port. + Set open_browser to False to suppress opening a browser. + """ + import webbrowser + serverthread = _start_server(_url_handler, hostname, port) + if serverthread.error: + print(serverthread.error) + return + if serverthread.serving: + server_help_msg = 'Server commands: [b]rowser, [q]uit' + if open_browser: + webbrowser.open(serverthread.url) + try: + print('Server ready at', serverthread.url) + print(server_help_msg) + while serverthread.serving: + cmd = input('server> ') + cmd = cmd.lower() + if cmd == 'q': + break + elif cmd == 'b': + webbrowser.open(serverthread.url) + else: + print(server_help_msg) + except (KeyboardInterrupt, EOFError): + print() + finally: + if serverthread.serving: + serverthread.stop() + print('Server stopped') + + +# -------------------------------------------------- command-line interface + +def ispath(x): + return isinstance(x, str) and x.find(os.sep) >= 0 + +def _get_revised_path(given_path, argv0): + """Ensures current directory is on returned path, and argv0 directory is not + + Exception: argv0 dir is left alone if it's also pydoc's directory. + + Returns a new path entry list, or None if no adjustment is needed. + """ + # Scripts may get the current directory in their path by default if they're + # run with the -m switch, or directly from the current directory. + # The interactive prompt also allows imports from the current directory. + + # Accordingly, if the current directory is already present, don't make + # any changes to the given_path + if '' in given_path or os.curdir in given_path or os.getcwd() in given_path: + return None + + # Otherwise, add the current directory to the given path, and remove the + # script directory (as long as the latter isn't also pydoc's directory. + stdlib_dir = os.path.dirname(__file__) + script_dir = os.path.dirname(argv0) + revised_path = given_path.copy() + if script_dir in given_path and not os.path.samefile(script_dir, stdlib_dir): + revised_path.remove(script_dir) + revised_path.insert(0, os.getcwd()) + return revised_path + + +# Note: the tests only cover _get_revised_path, not _adjust_cli_path itself +def _adjust_cli_sys_path(): + """Ensures current directory is on sys.path, and __main__ directory is not. + + Exception: __main__ dir is left alone if it's also pydoc's directory. + """ + revised_path = _get_revised_path(sys.path, sys.argv[0]) + if revised_path is not None: + sys.path[:] = revised_path + + +def cli(): + """Command-line interface (looks at sys.argv to decide what to do).""" + import getopt + class BadUsage(Exception): pass + + _adjust_cli_sys_path() + + try: + opts, args = getopt.getopt(sys.argv[1:], 'bk:n:p:w') + writing = False + start_server = False + open_browser = False + port = 0 + hostname = 'localhost' + for opt, val in opts: + if opt == '-b': + start_server = True + open_browser = True + if opt == '-k': + apropos(val) + return + if opt == '-p': + start_server = True + port = val + if opt == '-w': + writing = True + if opt == '-n': + start_server = True + hostname = val + + if start_server: + browse(port, hostname=hostname, open_browser=open_browser) + return + + if not args: raise BadUsage + for arg in args: + if ispath(arg) and not os.path.exists(arg): + print('file %r does not exist' % arg) + break + try: + if ispath(arg) and os.path.isfile(arg): + arg = importfile(arg) + if writing: + if ispath(arg) and os.path.isdir(arg): + writedocs(arg) + else: + writedoc(arg) + else: + help.help(arg) + except ErrorDuringImport as value: + print(value) + + except (getopt.error, BadUsage): + cmd = os.path.splitext(os.path.basename(sys.argv[0]))[0] + print("""pydoc - the Python documentation tool + +{cmd} ... + Show text documentation on something. may be the name of a + Python keyword, topic, function, module, or package, or a dotted + reference to a class or function within a module or module in a + package. If contains a '{sep}', it is used as the path to a + Python source file to document. If name is 'keywords', 'topics', + or 'modules', a listing of these things is displayed. + +{cmd} -k + Search for a keyword in the synopsis lines of all available modules. + +{cmd} -n + Start an HTTP server with the given hostname (default: localhost). + +{cmd} -p + Start an HTTP server on the given port on the local machine. Port + number 0 can be used to get an arbitrary unused port. + +{cmd} -b + Start an HTTP server on an arbitrary unused port and open a web browser + to interactively browse documentation. This option can be used in + combination with -n and/or -p. + +{cmd} -w ... + Write out the HTML documentation for a module to a file in the current + directory. If contains a '{sep}', it is treated as a filename; if + it names a directory, documentation is written for all the contents. +""".format(cmd=cmd, sep=os.sep)) + +if __name__ == '__main__': + cli() diff --git a/lib/python3.10/queue.py b/lib/python3.10/queue.py new file mode 100644 index 0000000000000000000000000000000000000000..55f50088460f9e5450c86d8c68d99b259a4c6d5c --- /dev/null +++ b/lib/python3.10/queue.py @@ -0,0 +1,326 @@ +'''A multi-producer, multi-consumer queue.''' + +import threading +import types +from collections import deque +from heapq import heappush, heappop +from time import monotonic as time +try: + from _queue import SimpleQueue +except ImportError: + SimpleQueue = None + +__all__ = ['Empty', 'Full', 'Queue', 'PriorityQueue', 'LifoQueue', 'SimpleQueue'] + + +try: + from _queue import Empty +except ImportError: + class Empty(Exception): + 'Exception raised by Queue.get(block=0)/get_nowait().' + pass + +class Full(Exception): + 'Exception raised by Queue.put(block=0)/put_nowait().' + pass + + +class Queue: + '''Create a queue object with a given maximum size. + + If maxsize is <= 0, the queue size is infinite. + ''' + + def __init__(self, maxsize=0): + self.maxsize = maxsize + self._init(maxsize) + + # mutex must be held whenever the queue is mutating. All methods + # that acquire mutex must release it before returning. mutex + # is shared between the three conditions, so acquiring and + # releasing the conditions also acquires and releases mutex. + self.mutex = threading.Lock() + + # Notify not_empty whenever an item is added to the queue; a + # thread waiting to get is notified then. + self.not_empty = threading.Condition(self.mutex) + + # Notify not_full whenever an item is removed from the queue; + # a thread waiting to put is notified then. + self.not_full = threading.Condition(self.mutex) + + # Notify all_tasks_done whenever the number of unfinished tasks + # drops to zero; thread waiting to join() is notified to resume + self.all_tasks_done = threading.Condition(self.mutex) + self.unfinished_tasks = 0 + + def task_done(self): + '''Indicate that a formerly enqueued task is complete. + + Used by Queue consumer threads. For each get() used to fetch a task, + a subsequent call to task_done() tells the queue that the processing + on the task is complete. + + If a join() is currently blocking, it will resume when all items + have been processed (meaning that a task_done() call was received + for every item that had been put() into the queue). + + Raises a ValueError if called more times than there were items + placed in the queue. + ''' + with self.all_tasks_done: + unfinished = self.unfinished_tasks - 1 + if unfinished <= 0: + if unfinished < 0: + raise ValueError('task_done() called too many times') + self.all_tasks_done.notify_all() + self.unfinished_tasks = unfinished + + def join(self): + '''Blocks until all items in the Queue have been gotten and processed. + + The count of unfinished tasks goes up whenever an item is added to the + queue. The count goes down whenever a consumer thread calls task_done() + to indicate the item was retrieved and all work on it is complete. + + When the count of unfinished tasks drops to zero, join() unblocks. + ''' + with self.all_tasks_done: + while self.unfinished_tasks: + self.all_tasks_done.wait() + + def qsize(self): + '''Return the approximate size of the queue (not reliable!).''' + with self.mutex: + return self._qsize() + + def empty(self): + '''Return True if the queue is empty, False otherwise (not reliable!). + + This method is likely to be removed at some point. Use qsize() == 0 + as a direct substitute, but be aware that either approach risks a race + condition where a queue can grow before the result of empty() or + qsize() can be used. + + To create code that needs to wait for all queued tasks to be + completed, the preferred technique is to use the join() method. + ''' + with self.mutex: + return not self._qsize() + + def full(self): + '''Return True if the queue is full, False otherwise (not reliable!). + + This method is likely to be removed at some point. Use qsize() >= n + as a direct substitute, but be aware that either approach risks a race + condition where a queue can shrink before the result of full() or + qsize() can be used. + ''' + with self.mutex: + return 0 < self.maxsize <= self._qsize() + + def put(self, item, block=True, timeout=None): + '''Put an item into the queue. + + If optional args 'block' is true and 'timeout' is None (the default), + block if necessary until a free slot is available. If 'timeout' is + a non-negative number, it blocks at most 'timeout' seconds and raises + the Full exception if no free slot was available within that time. + Otherwise ('block' is false), put an item on the queue if a free slot + is immediately available, else raise the Full exception ('timeout' + is ignored in that case). + ''' + with self.not_full: + if self.maxsize > 0: + if not block: + if self._qsize() >= self.maxsize: + raise Full + elif timeout is None: + while self._qsize() >= self.maxsize: + self.not_full.wait() + elif timeout < 0: + raise ValueError("'timeout' must be a non-negative number") + else: + endtime = time() + timeout + while self._qsize() >= self.maxsize: + remaining = endtime - time() + if remaining <= 0.0: + raise Full + self.not_full.wait(remaining) + self._put(item) + self.unfinished_tasks += 1 + self.not_empty.notify() + + def get(self, block=True, timeout=None): + '''Remove and return an item from the queue. + + If optional args 'block' is true and 'timeout' is None (the default), + block if necessary until an item is available. If 'timeout' is + a non-negative number, it blocks at most 'timeout' seconds and raises + the Empty exception if no item was available within that time. + Otherwise ('block' is false), return an item if one is immediately + available, else raise the Empty exception ('timeout' is ignored + in that case). + ''' + with self.not_empty: + if not block: + if not self._qsize(): + raise Empty + elif timeout is None: + while not self._qsize(): + self.not_empty.wait() + elif timeout < 0: + raise ValueError("'timeout' must be a non-negative number") + else: + endtime = time() + timeout + while not self._qsize(): + remaining = endtime - time() + if remaining <= 0.0: + raise Empty + self.not_empty.wait(remaining) + item = self._get() + self.not_full.notify() + return item + + def put_nowait(self, item): + '''Put an item into the queue without blocking. + + Only enqueue the item if a free slot is immediately available. + Otherwise raise the Full exception. + ''' + return self.put(item, block=False) + + def get_nowait(self): + '''Remove and return an item from the queue without blocking. + + Only get an item if one is immediately available. Otherwise + raise the Empty exception. + ''' + return self.get(block=False) + + # Override these methods to implement other queue organizations + # (e.g. stack or priority queue). + # These will only be called with appropriate locks held + + # Initialize the queue representation + def _init(self, maxsize): + self.queue = deque() + + def _qsize(self): + return len(self.queue) + + # Put a new item in the queue + def _put(self, item): + self.queue.append(item) + + # Get an item from the queue + def _get(self): + return self.queue.popleft() + + __class_getitem__ = classmethod(types.GenericAlias) + + +class PriorityQueue(Queue): + '''Variant of Queue that retrieves open entries in priority order (lowest first). + + Entries are typically tuples of the form: (priority number, data). + ''' + + def _init(self, maxsize): + self.queue = [] + + def _qsize(self): + return len(self.queue) + + def _put(self, item): + heappush(self.queue, item) + + def _get(self): + return heappop(self.queue) + + +class LifoQueue(Queue): + '''Variant of Queue that retrieves most recently added entries first.''' + + def _init(self, maxsize): + self.queue = [] + + def _qsize(self): + return len(self.queue) + + def _put(self, item): + self.queue.append(item) + + def _get(self): + return self.queue.pop() + + +class _PySimpleQueue: + '''Simple, unbounded FIFO queue. + + This pure Python implementation is not reentrant. + ''' + # Note: while this pure Python version provides fairness + # (by using a threading.Semaphore which is itself fair, being based + # on threading.Condition), fairness is not part of the API contract. + # This allows the C version to use a different implementation. + + def __init__(self): + self._queue = deque() + self._count = threading.Semaphore(0) + + def put(self, item, block=True, timeout=None): + '''Put the item on the queue. + + The optional 'block' and 'timeout' arguments are ignored, as this method + never blocks. They are provided for compatibility with the Queue class. + ''' + self._queue.append(item) + self._count.release() + + def get(self, block=True, timeout=None): + '''Remove and return an item from the queue. + + If optional args 'block' is true and 'timeout' is None (the default), + block if necessary until an item is available. If 'timeout' is + a non-negative number, it blocks at most 'timeout' seconds and raises + the Empty exception if no item was available within that time. + Otherwise ('block' is false), return an item if one is immediately + available, else raise the Empty exception ('timeout' is ignored + in that case). + ''' + if timeout is not None and timeout < 0: + raise ValueError("'timeout' must be a non-negative number") + if not self._count.acquire(block, timeout): + raise Empty + return self._queue.popleft() + + def put_nowait(self, item): + '''Put an item into the queue without blocking. + + This is exactly equivalent to `put(item, block=False)` and is only provided + for compatibility with the Queue class. + ''' + return self.put(item, block=False) + + def get_nowait(self): + '''Remove and return an item from the queue without blocking. + + Only get an item if one is immediately available. Otherwise + raise the Empty exception. + ''' + return self.get(block=False) + + def empty(self): + '''Return True if the queue is empty, False otherwise (not reliable!).''' + return len(self._queue) == 0 + + def qsize(self): + '''Return the approximate size of the queue (not reliable!).''' + return len(self._queue) + + __class_getitem__ = classmethod(types.GenericAlias) + + +if SimpleQueue is None: + SimpleQueue = _PySimpleQueue diff --git a/lib/python3.10/quopri.py b/lib/python3.10/quopri.py new file mode 100644 index 0000000000000000000000000000000000000000..08899c5cb73a300d340ba2f84e25e82b249d3f55 --- /dev/null +++ b/lib/python3.10/quopri.py @@ -0,0 +1,242 @@ +#! /usr/bin/env python3 + +"""Conversions to/from quoted-printable transport encoding as per RFC 1521.""" + +# (Dec 1991 version). + +__all__ = ["encode", "decode", "encodestring", "decodestring"] + +ESCAPE = b'=' +MAXLINESIZE = 76 +HEX = b'0123456789ABCDEF' +EMPTYSTRING = b'' + +try: + from binascii import a2b_qp, b2a_qp +except ImportError: + a2b_qp = None + b2a_qp = None + + +def needsquoting(c, quotetabs, header): + """Decide whether a particular byte ordinal needs to be quoted. + + The 'quotetabs' flag indicates whether embedded tabs and spaces should be + quoted. Note that line-ending tabs and spaces are always encoded, as per + RFC 1521. + """ + assert isinstance(c, bytes) + if c in b' \t': + return quotetabs + # if header, we have to escape _ because _ is used to escape space + if c == b'_': + return header + return c == ESCAPE or not (b' ' <= c <= b'~') + +def quote(c): + """Quote a single character.""" + assert isinstance(c, bytes) and len(c)==1 + c = ord(c) + return ESCAPE + bytes((HEX[c//16], HEX[c%16])) + + + +def encode(input, output, quotetabs, header=False): + """Read 'input', apply quoted-printable encoding, and write to 'output'. + + 'input' and 'output' are binary file objects. The 'quotetabs' flag + indicates whether embedded tabs and spaces should be quoted. Note that + line-ending tabs and spaces are always encoded, as per RFC 1521. + The 'header' flag indicates whether we are encoding spaces as _ as per RFC + 1522.""" + + if b2a_qp is not None: + data = input.read() + odata = b2a_qp(data, quotetabs=quotetabs, header=header) + output.write(odata) + return + + def write(s, output=output, lineEnd=b'\n'): + # RFC 1521 requires that the line ending in a space or tab must have + # that trailing character encoded. + if s and s[-1:] in b' \t': + output.write(s[:-1] + quote(s[-1:]) + lineEnd) + elif s == b'.': + output.write(quote(s) + lineEnd) + else: + output.write(s + lineEnd) + + prevline = None + while 1: + line = input.readline() + if not line: + break + outline = [] + # Strip off any readline induced trailing newline + stripped = b'' + if line[-1:] == b'\n': + line = line[:-1] + stripped = b'\n' + # Calculate the un-length-limited encoded line + for c in line: + c = bytes((c,)) + if needsquoting(c, quotetabs, header): + c = quote(c) + if header and c == b' ': + outline.append(b'_') + else: + outline.append(c) + # First, write out the previous line + if prevline is not None: + write(prevline) + # Now see if we need any soft line breaks because of RFC-imposed + # length limitations. Then do the thisline->prevline dance. + thisline = EMPTYSTRING.join(outline) + while len(thisline) > MAXLINESIZE: + # Don't forget to include the soft line break `=' sign in the + # length calculation! + write(thisline[:MAXLINESIZE-1], lineEnd=b'=\n') + thisline = thisline[MAXLINESIZE-1:] + # Write out the current line + prevline = thisline + # Write out the last line, without a trailing newline + if prevline is not None: + write(prevline, lineEnd=stripped) + +def encodestring(s, quotetabs=False, header=False): + if b2a_qp is not None: + return b2a_qp(s, quotetabs=quotetabs, header=header) + from io import BytesIO + infp = BytesIO(s) + outfp = BytesIO() + encode(infp, outfp, quotetabs, header) + return outfp.getvalue() + + + +def decode(input, output, header=False): + """Read 'input', apply quoted-printable decoding, and write to 'output'. + 'input' and 'output' are binary file objects. + If 'header' is true, decode underscore as space (per RFC 1522).""" + + if a2b_qp is not None: + data = input.read() + odata = a2b_qp(data, header=header) + output.write(odata) + return + + new = b'' + while 1: + line = input.readline() + if not line: break + i, n = 0, len(line) + if n > 0 and line[n-1:n] == b'\n': + partial = 0; n = n-1 + # Strip trailing whitespace + while n > 0 and line[n-1:n] in b" \t\r": + n = n-1 + else: + partial = 1 + while i < n: + c = line[i:i+1] + if c == b'_' and header: + new = new + b' '; i = i+1 + elif c != ESCAPE: + new = new + c; i = i+1 + elif i+1 == n and not partial: + partial = 1; break + elif i+1 < n and line[i+1:i+2] == ESCAPE: + new = new + ESCAPE; i = i+2 + elif i+2 < n and ishex(line[i+1:i+2]) and ishex(line[i+2:i+3]): + new = new + bytes((unhex(line[i+1:i+3]),)); i = i+3 + else: # Bad escape sequence -- leave it in + new = new + c; i = i+1 + if not partial: + output.write(new + b'\n') + new = b'' + if new: + output.write(new) + +def decodestring(s, header=False): + if a2b_qp is not None: + return a2b_qp(s, header=header) + from io import BytesIO + infp = BytesIO(s) + outfp = BytesIO() + decode(infp, outfp, header=header) + return outfp.getvalue() + + + +# Other helper functions +def ishex(c): + """Return true if the byte ordinal 'c' is a hexadecimal digit in ASCII.""" + assert isinstance(c, bytes) + return b'0' <= c <= b'9' or b'a' <= c <= b'f' or b'A' <= c <= b'F' + +def unhex(s): + """Get the integer value of a hexadecimal number.""" + bits = 0 + for c in s: + c = bytes((c,)) + if b'0' <= c <= b'9': + i = ord('0') + elif b'a' <= c <= b'f': + i = ord('a')-10 + elif b'A' <= c <= b'F': + i = ord(b'A')-10 + else: + assert False, "non-hex digit "+repr(c) + bits = bits*16 + (ord(c) - i) + return bits + + + +def main(): + import sys + import getopt + try: + opts, args = getopt.getopt(sys.argv[1:], 'td') + except getopt.error as msg: + sys.stdout = sys.stderr + print(msg) + print("usage: quopri [-t | -d] [file] ...") + print("-t: quote tabs") + print("-d: decode; default encode") + sys.exit(2) + deco = False + tabs = False + for o, a in opts: + if o == '-t': tabs = True + if o == '-d': deco = True + if tabs and deco: + sys.stdout = sys.stderr + print("-t and -d are mutually exclusive") + sys.exit(2) + if not args: args = ['-'] + sts = 0 + for file in args: + if file == '-': + fp = sys.stdin.buffer + else: + try: + fp = open(file, "rb") + except OSError as msg: + sys.stderr.write("%s: can't open (%s)\n" % (file, msg)) + sts = 1 + continue + try: + if deco: + decode(fp, sys.stdout.buffer) + else: + encode(fp, sys.stdout.buffer, tabs) + finally: + if file != '-': + fp.close() + if sts: + sys.exit(sts) + + + +if __name__ == '__main__': + main() diff --git a/lib/python3.10/random.py b/lib/python3.10/random.py new file mode 100644 index 0000000000000000000000000000000000000000..1310a2d9d0e07104ee8b67a0efc2e004bfe62277 --- /dev/null +++ b/lib/python3.10/random.py @@ -0,0 +1,930 @@ +"""Random variable generators. + + bytes + ----- + uniform bytes (values between 0 and 255) + + integers + -------- + uniform within range + + sequences + --------- + pick random element + pick random sample + pick weighted random sample + generate random permutation + + distributions on the real line: + ------------------------------ + uniform + triangular + normal (Gaussian) + lognormal + negative exponential + gamma + beta + pareto + Weibull + + distributions on the circle (angles 0 to 2pi) + --------------------------------------------- + circular uniform + von Mises + +General notes on the underlying Mersenne Twister core generator: + +* The period is 2**19937-1. +* It is one of the most extensively tested generators in existence. +* The random() method is implemented in C, executes in a single Python step, + and is, therefore, threadsafe. + +""" + +# Translated by Guido van Rossum from C source provided by +# Adrian Baddeley. Adapted by Raymond Hettinger for use with +# the Mersenne Twister and os.urandom() core generators. + +from warnings import warn as _warn +from math import log as _log, exp as _exp, pi as _pi, e as _e, ceil as _ceil +from math import sqrt as _sqrt, acos as _acos, cos as _cos, sin as _sin +from math import tau as TWOPI, floor as _floor, isfinite as _isfinite +from os import urandom as _urandom +from _collections_abc import Set as _Set, Sequence as _Sequence +from operator import index as _index +from itertools import accumulate as _accumulate, repeat as _repeat +from bisect import bisect as _bisect +import os as _os +import _random + +try: + # hashlib is pretty heavy to load, try lean internal module first + from _sha512 import sha512 as _sha512 +except ImportError: + # fallback to official implementation + from hashlib import sha512 as _sha512 + +__all__ = [ + "Random", + "SystemRandom", + "betavariate", + "choice", + "choices", + "expovariate", + "gammavariate", + "gauss", + "getrandbits", + "getstate", + "lognormvariate", + "normalvariate", + "paretovariate", + "randbytes", + "randint", + "random", + "randrange", + "sample", + "seed", + "setstate", + "shuffle", + "triangular", + "uniform", + "vonmisesvariate", + "weibullvariate", +] + +NV_MAGICCONST = 4 * _exp(-0.5) / _sqrt(2.0) +LOG4 = _log(4.0) +SG_MAGICCONST = 1.0 + _log(4.5) +BPF = 53 # Number of bits in a float +RECIP_BPF = 2 ** -BPF +_ONE = 1 + + +class Random(_random.Random): + """Random number generator base class used by bound module functions. + + Used to instantiate instances of Random to get generators that don't + share state. + + Class Random can also be subclassed if you want to use a different basic + generator of your own devising: in that case, override the following + methods: random(), seed(), getstate(), and setstate(). + Optionally, implement a getrandbits() method so that randrange() + can cover arbitrarily large ranges. + + """ + + VERSION = 3 # used by getstate/setstate + + def __init__(self, x=None): + """Initialize an instance. + + Optional argument x controls seeding, as for Random.seed(). + """ + + self.seed(x) + self.gauss_next = None + + def seed(self, a=None, version=2): + """Initialize internal state from a seed. + + The only supported seed types are None, int, float, + str, bytes, and bytearray. + + None or no argument seeds from current time or from an operating + system specific randomness source if available. + + If *a* is an int, all bits are used. + + For version 2 (the default), all of the bits are used if *a* is a str, + bytes, or bytearray. For version 1 (provided for reproducing random + sequences from older versions of Python), the algorithm for str and + bytes generates a narrower range of seeds. + + """ + + if version == 1 and isinstance(a, (str, bytes)): + a = a.decode('latin-1') if isinstance(a, bytes) else a + x = ord(a[0]) << 7 if a else 0 + for c in map(ord, a): + x = ((1000003 * x) ^ c) & 0xFFFFFFFFFFFFFFFF + x ^= len(a) + a = -2 if x == -1 else x + + elif version == 2 and isinstance(a, (str, bytes, bytearray)): + if isinstance(a, str): + a = a.encode() + a = int.from_bytes(a + _sha512(a).digest(), 'big') + + elif not isinstance(a, (type(None), int, float, str, bytes, bytearray)): + _warn('Seeding based on hashing is deprecated\n' + 'since Python 3.9 and will be removed in a subsequent ' + 'version. The only \n' + 'supported seed types are: None, ' + 'int, float, str, bytes, and bytearray.', + DeprecationWarning, 2) + + super().seed(a) + self.gauss_next = None + + def getstate(self): + """Return internal state; can be passed to setstate() later.""" + return self.VERSION, super().getstate(), self.gauss_next + + def setstate(self, state): + """Restore internal state from object returned by getstate().""" + version = state[0] + if version == 3: + version, internalstate, self.gauss_next = state + super().setstate(internalstate) + elif version == 2: + version, internalstate, self.gauss_next = state + # In version 2, the state was saved as signed ints, which causes + # inconsistencies between 32/64-bit systems. The state is + # really unsigned 32-bit ints, so we convert negative ints from + # version 2 to positive longs for version 3. + try: + internalstate = tuple(x % (2 ** 32) for x in internalstate) + except ValueError as e: + raise TypeError from e + super().setstate(internalstate) + else: + raise ValueError("state with version %s passed to " + "Random.setstate() of version %s" % + (version, self.VERSION)) + + + ## ------------------------------------------------------- + ## ---- Methods below this point do not need to be overridden or extended + ## ---- when subclassing for the purpose of using a different core generator. + + + ## -------------------- pickle support ------------------- + + # Issue 17489: Since __reduce__ was defined to fix #759889 this is no + # longer called; we leave it here because it has been here since random was + # rewritten back in 2001 and why risk breaking something. + def __getstate__(self): # for pickle + return self.getstate() + + def __setstate__(self, state): # for pickle + self.setstate(state) + + def __reduce__(self): + return self.__class__, (), self.getstate() + + + ## ---- internal support method for evenly distributed integers ---- + + def __init_subclass__(cls, /, **kwargs): + """Control how subclasses generate random integers. + + The algorithm a subclass can use depends on the random() and/or + getrandbits() implementation available to it and determines + whether it can generate random integers from arbitrarily large + ranges. + """ + + for c in cls.__mro__: + if '_randbelow' in c.__dict__: + # just inherit it + break + if 'getrandbits' in c.__dict__: + cls._randbelow = cls._randbelow_with_getrandbits + break + if 'random' in c.__dict__: + cls._randbelow = cls._randbelow_without_getrandbits + break + + def _randbelow_with_getrandbits(self, n): + "Return a random int in the range [0,n). Returns 0 if n==0." + + if not n: + return 0 + getrandbits = self.getrandbits + k = n.bit_length() # don't use (n-1) here because n can be 1 + r = getrandbits(k) # 0 <= r < 2**k + while r >= n: + r = getrandbits(k) + return r + + def _randbelow_without_getrandbits(self, n, maxsize=1<= maxsize: + _warn("Underlying random() generator does not supply \n" + "enough bits to choose from a population range this large.\n" + "To remove the range limitation, add a getrandbits() method.") + return _floor(random() * n) + if n == 0: + return 0 + rem = maxsize % n + limit = (maxsize - rem) / maxsize # int(limit * maxsize) % n == 0 + r = random() + while r >= limit: + r = random() + return _floor(r * maxsize) % n + + _randbelow = _randbelow_with_getrandbits + + + ## -------------------------------------------------------- + ## ---- Methods below this point generate custom distributions + ## ---- based on the methods defined above. They do not + ## ---- directly touch the underlying generator and only + ## ---- access randomness through the methods: random(), + ## ---- getrandbits(), or _randbelow(). + + + ## -------------------- bytes methods --------------------- + + def randbytes(self, n): + """Generate n random bytes.""" + return self.getrandbits(n * 8).to_bytes(n, 'little') + + + ## -------------------- integer methods ------------------- + + def randrange(self, start, stop=None, step=_ONE): + """Choose a random item from range(start, stop[, step]). + + This fixes the problem with randint() which includes the + endpoint; in Python this is usually not what you want. + + """ + + # This code is a bit messy to make it fast for the + # common case while still doing adequate error checking. + try: + istart = _index(start) + except TypeError: + istart = int(start) + if istart != start: + _warn('randrange() will raise TypeError in the future', + DeprecationWarning, 2) + raise ValueError("non-integer arg 1 for randrange()") + _warn('non-integer arguments to randrange() have been deprecated ' + 'since Python 3.10 and will be removed in a subsequent ' + 'version', + DeprecationWarning, 2) + if stop is None: + # We don't check for "step != 1" because it hasn't been + # type checked and converted to an integer yet. + if step is not _ONE: + raise TypeError('Missing a non-None stop argument') + if istart > 0: + return self._randbelow(istart) + raise ValueError("empty range for randrange()") + + # stop argument supplied. + try: + istop = _index(stop) + except TypeError: + istop = int(stop) + if istop != stop: + _warn('randrange() will raise TypeError in the future', + DeprecationWarning, 2) + raise ValueError("non-integer stop for randrange()") + _warn('non-integer arguments to randrange() have been deprecated ' + 'since Python 3.10 and will be removed in a subsequent ' + 'version', + DeprecationWarning, 2) + width = istop - istart + try: + istep = _index(step) + except TypeError: + istep = int(step) + if istep != step: + _warn('randrange() will raise TypeError in the future', + DeprecationWarning, 2) + raise ValueError("non-integer step for randrange()") + _warn('non-integer arguments to randrange() have been deprecated ' + 'since Python 3.10 and will be removed in a subsequent ' + 'version', + DeprecationWarning, 2) + # Fast path. + if istep == 1: + if width > 0: + return istart + self._randbelow(width) + raise ValueError("empty range for randrange() (%d, %d, %d)" % (istart, istop, width)) + + # Non-unit step argument supplied. + if istep > 0: + n = (width + istep - 1) // istep + elif istep < 0: + n = (width + istep + 1) // istep + else: + raise ValueError("zero step for randrange()") + if n <= 0: + raise ValueError("empty range for randrange()") + return istart + istep * self._randbelow(n) + + def randint(self, a, b): + """Return random integer in range [a, b], including both end points. + """ + + return self.randrange(a, b+1) + + + ## -------------------- sequence methods ------------------- + + def choice(self, seq): + """Choose a random element from a non-empty sequence.""" + # raises IndexError if seq is empty + return seq[self._randbelow(len(seq))] + + def shuffle(self, x, random=None): + """Shuffle list x in place, and return None. + + Optional argument random is a 0-argument function returning a + random float in [0.0, 1.0); if it is the default None, the + standard random.random will be used. + + """ + + if random is None: + randbelow = self._randbelow + for i in reversed(range(1, len(x))): + # pick an element in x[:i+1] with which to exchange x[i] + j = randbelow(i + 1) + x[i], x[j] = x[j], x[i] + else: + _warn('The *random* parameter to shuffle() has been deprecated\n' + 'since Python 3.9 and will be removed in a subsequent ' + 'version.', + DeprecationWarning, 2) + floor = _floor + for i in reversed(range(1, len(x))): + # pick an element in x[:i+1] with which to exchange x[i] + j = floor(random() * (i + 1)) + x[i], x[j] = x[j], x[i] + + def sample(self, population, k, *, counts=None): + """Chooses k unique random elements from a population sequence or set. + + Returns a new list containing elements from the population while + leaving the original population unchanged. The resulting list is + in selection order so that all sub-slices will also be valid random + samples. This allows raffle winners (the sample) to be partitioned + into grand prize and second place winners (the subslices). + + Members of the population need not be hashable or unique. If the + population contains repeats, then each occurrence is a possible + selection in the sample. + + Repeated elements can be specified one at a time or with the optional + counts parameter. For example: + + sample(['red', 'blue'], counts=[4, 2], k=5) + + is equivalent to: + + sample(['red', 'red', 'red', 'red', 'blue', 'blue'], k=5) + + To choose a sample from a range of integers, use range() for the + population argument. This is especially fast and space efficient + for sampling from a large population: + + sample(range(10000000), 60) + + """ + + # Sampling without replacement entails tracking either potential + # selections (the pool) in a list or previous selections in a set. + + # When the number of selections is small compared to the + # population, then tracking selections is efficient, requiring + # only a small set and an occasional reselection. For + # a larger number of selections, the pool tracking method is + # preferred since the list takes less space than the + # set and it doesn't suffer from frequent reselections. + + # The number of calls to _randbelow() is kept at or near k, the + # theoretical minimum. This is important because running time + # is dominated by _randbelow() and because it extracts the + # least entropy from the underlying random number generators. + + # Memory requirements are kept to the smaller of a k-length + # set or an n-length list. + + # There are other sampling algorithms that do not require + # auxiliary memory, but they were rejected because they made + # too many calls to _randbelow(), making them slower and + # causing them to eat more entropy than necessary. + + if not isinstance(population, _Sequence): + if isinstance(population, _Set): + _warn('Sampling from a set deprecated\n' + 'since Python 3.9 and will be removed in a subsequent version.', + DeprecationWarning, 2) + population = tuple(population) + else: + raise TypeError("Population must be a sequence. For dicts or sets, use sorted(d).") + n = len(population) + if counts is not None: + cum_counts = list(_accumulate(counts)) + if len(cum_counts) != n: + raise ValueError('The number of counts does not match the population') + total = cum_counts.pop() + if not isinstance(total, int): + raise TypeError('Counts must be integers') + if total <= 0: + raise ValueError('Total of counts must be greater than zero') + selections = self.sample(range(total), k=k) + bisect = _bisect + return [population[bisect(cum_counts, s)] for s in selections] + randbelow = self._randbelow + if not 0 <= k <= n: + raise ValueError("Sample larger than population or is negative") + result = [None] * k + setsize = 21 # size of a small set minus size of an empty list + if k > 5: + setsize += 4 ** _ceil(_log(k * 3, 4)) # table size for big sets + if n <= setsize: + # An n-length list is smaller than a k-length set. + # Invariant: non-selected at pool[0 : n-i] + pool = list(population) + for i in range(k): + j = randbelow(n - i) + result[i] = pool[j] + pool[j] = pool[n - i - 1] # move non-selected item into vacancy + else: + selected = set() + selected_add = selected.add + for i in range(k): + j = randbelow(n) + while j in selected: + j = randbelow(n) + selected_add(j) + result[i] = population[j] + return result + + def choices(self, population, weights=None, *, cum_weights=None, k=1): + """Return a k sized list of population elements chosen with replacement. + + If the relative weights or cumulative weights are not specified, + the selections are made with equal probability. + + """ + random = self.random + n = len(population) + if cum_weights is None: + if weights is None: + floor = _floor + n += 0.0 # convert to float for a small speed improvement + return [population[floor(random() * n)] for i in _repeat(None, k)] + try: + cum_weights = list(_accumulate(weights)) + except TypeError: + if not isinstance(weights, int): + raise + k = weights + raise TypeError( + f'The number of choices must be a keyword argument: {k=}' + ) from None + elif weights is not None: + raise TypeError('Cannot specify both weights and cumulative weights') + if len(cum_weights) != n: + raise ValueError('The number of weights does not match the population') + total = cum_weights[-1] + 0.0 # convert to float + if total <= 0.0: + raise ValueError('Total of weights must be greater than zero') + if not _isfinite(total): + raise ValueError('Total of weights must be finite') + bisect = _bisect + hi = n - 1 + return [population[bisect(cum_weights, random() * total, 0, hi)] + for i in _repeat(None, k)] + + + ## -------------------- real-valued distributions ------------------- + + def uniform(self, a, b): + "Get a random number in the range [a, b) or [a, b] depending on rounding." + return a + (b - a) * self.random() + + def triangular(self, low=0.0, high=1.0, mode=None): + """Triangular distribution. + + Continuous distribution bounded by given lower and upper limits, + and having a given mode value in-between. + + http://en.wikipedia.org/wiki/Triangular_distribution + + """ + u = self.random() + try: + c = 0.5 if mode is None else (mode - low) / (high - low) + except ZeroDivisionError: + return low + if u > c: + u = 1.0 - u + c = 1.0 - c + low, high = high, low + return low + (high - low) * _sqrt(u * c) + + def normalvariate(self, mu, sigma): + """Normal distribution. + + mu is the mean, and sigma is the standard deviation. + + """ + # Uses Kinderman and Monahan method. Reference: Kinderman, + # A.J. and Monahan, J.F., "Computer generation of random + # variables using the ratio of uniform deviates", ACM Trans + # Math Software, 3, (1977), pp257-260. + + random = self.random + while True: + u1 = random() + u2 = 1.0 - random() + z = NV_MAGICCONST * (u1 - 0.5) / u2 + zz = z * z / 4.0 + if zz <= -_log(u2): + break + return mu + z * sigma + + def gauss(self, mu, sigma): + """Gaussian distribution. + + mu is the mean, and sigma is the standard deviation. This is + slightly faster than the normalvariate() function. + + Not thread-safe without a lock around calls. + + """ + # When x and y are two variables from [0, 1), uniformly + # distributed, then + # + # cos(2*pi*x)*sqrt(-2*log(1-y)) + # sin(2*pi*x)*sqrt(-2*log(1-y)) + # + # are two *independent* variables with normal distribution + # (mu = 0, sigma = 1). + # (Lambert Meertens) + # (corrected version; bug discovered by Mike Miller, fixed by LM) + + # Multithreading note: When two threads call this function + # simultaneously, it is possible that they will receive the + # same return value. The window is very small though. To + # avoid this, you have to use a lock around all calls. (I + # didn't want to slow this down in the serial case by using a + # lock here.) + + random = self.random + z = self.gauss_next + self.gauss_next = None + if z is None: + x2pi = random() * TWOPI + g2rad = _sqrt(-2.0 * _log(1.0 - random())) + z = _cos(x2pi) * g2rad + self.gauss_next = _sin(x2pi) * g2rad + + return mu + z * sigma + + def lognormvariate(self, mu, sigma): + """Log normal distribution. + + If you take the natural logarithm of this distribution, you'll get a + normal distribution with mean mu and standard deviation sigma. + mu can have any value, and sigma must be greater than zero. + + """ + return _exp(self.normalvariate(mu, sigma)) + + def expovariate(self, lambd): + """Exponential distribution. + + lambd is 1.0 divided by the desired mean. It should be + nonzero. (The parameter would be called "lambda", but that is + a reserved word in Python.) Returned values range from 0 to + positive infinity if lambd is positive, and from negative + infinity to 0 if lambd is negative. + + """ + # lambd: rate lambd = 1/mean + # ('lambda' is a Python reserved word) + + # we use 1-random() instead of random() to preclude the + # possibility of taking the log of zero. + return -_log(1.0 - self.random()) / lambd + + def vonmisesvariate(self, mu, kappa): + """Circular data distribution. + + mu is the mean angle, expressed in radians between 0 and 2*pi, and + kappa is the concentration parameter, which must be greater than or + equal to zero. If kappa is equal to zero, this distribution reduces + to a uniform random angle over the range 0 to 2*pi. + + """ + # Based upon an algorithm published in: Fisher, N.I., + # "Statistical Analysis of Circular Data", Cambridge + # University Press, 1993. + + # Thanks to Magnus Kessler for a correction to the + # implementation of step 4. + + random = self.random + if kappa <= 1e-6: + return TWOPI * random() + + s = 0.5 / kappa + r = s + _sqrt(1.0 + s * s) + + while True: + u1 = random() + z = _cos(_pi * u1) + + d = z / (r + z) + u2 = random() + if u2 < 1.0 - d * d or u2 <= (1.0 - d) * _exp(d): + break + + q = 1.0 / r + f = (q + z) / (1.0 + q * z) + u3 = random() + if u3 > 0.5: + theta = (mu + _acos(f)) % TWOPI + else: + theta = (mu - _acos(f)) % TWOPI + + return theta + + def gammavariate(self, alpha, beta): + """Gamma distribution. Not the gamma function! + + Conditions on the parameters are alpha > 0 and beta > 0. + + The probability distribution function is: + + x ** (alpha - 1) * math.exp(-x / beta) + pdf(x) = -------------------------------------- + math.gamma(alpha) * beta ** alpha + + """ + # alpha > 0, beta > 0, mean is alpha*beta, variance is alpha*beta**2 + + # Warning: a few older sources define the gamma distribution in terms + # of alpha > -1.0 + if alpha <= 0.0 or beta <= 0.0: + raise ValueError('gammavariate: alpha and beta must be > 0.0') + + random = self.random + if alpha > 1.0: + + # Uses R.C.H. Cheng, "The generation of Gamma + # variables with non-integral shape parameters", + # Applied Statistics, (1977), 26, No. 1, p71-74 + + ainv = _sqrt(2.0 * alpha - 1.0) + bbb = alpha - LOG4 + ccc = alpha + ainv + + while True: + u1 = random() + if not 1e-7 < u1 < 0.9999999: + continue + u2 = 1.0 - random() + v = _log(u1 / (1.0 - u1)) / ainv + x = alpha * _exp(v) + z = u1 * u1 * u2 + r = bbb + ccc * v - x + if r + SG_MAGICCONST - 4.5 * z >= 0.0 or r >= _log(z): + return x * beta + + elif alpha == 1.0: + # expovariate(1/beta) + return -_log(1.0 - random()) * beta + + else: + # alpha is between 0 and 1 (exclusive) + # Uses ALGORITHM GS of Statistical Computing - Kennedy & Gentle + while True: + u = random() + b = (_e + alpha) / _e + p = b * u + if p <= 1.0: + x = p ** (1.0 / alpha) + else: + x = -_log((b - p) / alpha) + u1 = random() + if p > 1.0: + if u1 <= x ** (alpha - 1.0): + break + elif u1 <= _exp(-x): + break + return x * beta + + def betavariate(self, alpha, beta): + """Beta distribution. + + Conditions on the parameters are alpha > 0 and beta > 0. + Returned values range between 0 and 1. + + """ + ## See + ## http://mail.python.org/pipermail/python-bugs-list/2001-January/003752.html + ## for Ivan Frohne's insightful analysis of why the original implementation: + ## + ## def betavariate(self, alpha, beta): + ## # Discrete Event Simulation in C, pp 87-88. + ## + ## y = self.expovariate(alpha) + ## z = self.expovariate(1.0/beta) + ## return z/(y+z) + ## + ## was dead wrong, and how it probably got that way. + + # This version due to Janne Sinkkonen, and matches all the std + # texts (e.g., Knuth Vol 2 Ed 3 pg 134 "the beta distribution"). + y = self.gammavariate(alpha, 1.0) + if y: + return y / (y + self.gammavariate(beta, 1.0)) + return 0.0 + + def paretovariate(self, alpha): + """Pareto distribution. alpha is the shape parameter.""" + # Jain, pg. 495 + + u = 1.0 - self.random() + return u ** (-1.0 / alpha) + + def weibullvariate(self, alpha, beta): + """Weibull distribution. + + alpha is the scale parameter and beta is the shape parameter. + + """ + # Jain, pg. 499; bug fix courtesy Bill Arms + + u = 1.0 - self.random() + return alpha * (-_log(u)) ** (1.0 / beta) + + +## ------------------------------------------------------------------ +## --------------- Operating System Random Source ------------------ + + +class SystemRandom(Random): + """Alternate random number generator using sources provided + by the operating system (such as /dev/urandom on Unix or + CryptGenRandom on Windows). + + Not available on all systems (see os.urandom() for details). + + """ + + def random(self): + """Get the next random number in the range [0.0, 1.0).""" + return (int.from_bytes(_urandom(7), 'big') >> 3) * RECIP_BPF + + def getrandbits(self, k): + """getrandbits(k) -> x. Generates an int with k random bits.""" + if k < 0: + raise ValueError('number of bits must be non-negative') + numbytes = (k + 7) // 8 # bits / 8 and rounded up + x = int.from_bytes(_urandom(numbytes), 'big') + return x >> (numbytes * 8 - k) # trim excess bits + + def randbytes(self, n): + """Generate n random bytes.""" + # os.urandom(n) fails with ValueError for n < 0 + # and returns an empty bytes string for n == 0. + return _urandom(n) + + def seed(self, *args, **kwds): + "Stub method. Not used for a system random number generator." + return None + + def _notimplemented(self, *args, **kwds): + "Method should not be called for a system random number generator." + raise NotImplementedError('System entropy source does not have state.') + getstate = setstate = _notimplemented + + +# ---------------------------------------------------------------------- +# Create one instance, seeded from current time, and export its methods +# as module-level functions. The functions share state across all uses +# (both in the user's code and in the Python libraries), but that's fine +# for most programs and is easier for the casual user than making them +# instantiate their own Random() instance. + +_inst = Random() +seed = _inst.seed +random = _inst.random +uniform = _inst.uniform +triangular = _inst.triangular +randint = _inst.randint +choice = _inst.choice +randrange = _inst.randrange +sample = _inst.sample +shuffle = _inst.shuffle +choices = _inst.choices +normalvariate = _inst.normalvariate +lognormvariate = _inst.lognormvariate +expovariate = _inst.expovariate +vonmisesvariate = _inst.vonmisesvariate +gammavariate = _inst.gammavariate +gauss = _inst.gauss +betavariate = _inst.betavariate +paretovariate = _inst.paretovariate +weibullvariate = _inst.weibullvariate +getstate = _inst.getstate +setstate = _inst.setstate +getrandbits = _inst.getrandbits +randbytes = _inst.randbytes + + +## ------------------------------------------------------ +## ----------------- test program ----------------------- + +def _test_generator(n, func, args): + from statistics import stdev, fmean as mean + from time import perf_counter + + t0 = perf_counter() + data = [func(*args) for i in _repeat(None, n)] + t1 = perf_counter() + + xbar = mean(data) + sigma = stdev(data, xbar) + low = min(data) + high = max(data) + + print(f'{t1 - t0:.3f} sec, {n} times {func.__name__}') + print('avg %g, stddev %g, min %g, max %g\n' % (xbar, sigma, low, high)) + + +def _test(N=2000): + _test_generator(N, random, ()) + _test_generator(N, normalvariate, (0.0, 1.0)) + _test_generator(N, lognormvariate, (0.0, 1.0)) + _test_generator(N, vonmisesvariate, (0.0, 1.0)) + _test_generator(N, gammavariate, (0.01, 1.0)) + _test_generator(N, gammavariate, (0.1, 1.0)) + _test_generator(N, gammavariate, (0.1, 2.0)) + _test_generator(N, gammavariate, (0.5, 1.0)) + _test_generator(N, gammavariate, (0.9, 1.0)) + _test_generator(N, gammavariate, (1.0, 1.0)) + _test_generator(N, gammavariate, (2.0, 1.0)) + _test_generator(N, gammavariate, (20.0, 1.0)) + _test_generator(N, gammavariate, (200.0, 1.0)) + _test_generator(N, gauss, (0.0, 1.0)) + _test_generator(N, betavariate, (3.0, 3.0)) + _test_generator(N, triangular, (0.0, 1.0, 1.0 / 3.0)) + + +## ------------------------------------------------------ +## ------------------ fork support --------------------- + +if hasattr(_os, "fork"): + _os.register_at_fork(after_in_child=_inst.seed) + + +if __name__ == '__main__': + _test() diff --git a/lib/python3.10/re.py b/lib/python3.10/re.py new file mode 100644 index 0000000000000000000000000000000000000000..1d82b5006396cde400807e17eaf09fa342b01750 --- /dev/null +++ b/lib/python3.10/re.py @@ -0,0 +1,383 @@ +# +# Secret Labs' Regular Expression Engine +# +# re-compatible interface for the sre matching engine +# +# Copyright (c) 1998-2001 by Secret Labs AB. All rights reserved. +# +# This version of the SRE library can be redistributed under CNRI's +# Python 1.6 license. For any other use, please contact Secret Labs +# AB (info@pythonware.com). +# +# Portions of this engine have been developed in cooperation with +# CNRI. Hewlett-Packard provided funding for 1.6 integration and +# other compatibility work. +# + +r"""Support for regular expressions (RE). + +This module provides regular expression matching operations similar to +those found in Perl. It supports both 8-bit and Unicode strings; both +the pattern and the strings being processed can contain null bytes and +characters outside the US ASCII range. + +Regular expressions can contain both special and ordinary characters. +Most ordinary characters, like "A", "a", or "0", are the simplest +regular expressions; they simply match themselves. You can +concatenate ordinary characters, so last matches the string 'last'. + +The special characters are: + "." Matches any character except a newline. + "^" Matches the start of the string. + "$" Matches the end of the string or just before the newline at + the end of the string. + "*" Matches 0 or more (greedy) repetitions of the preceding RE. + Greedy means that it will match as many repetitions as possible. + "+" Matches 1 or more (greedy) repetitions of the preceding RE. + "?" Matches 0 or 1 (greedy) of the preceding RE. + *?,+?,?? Non-greedy versions of the previous three special characters. + {m,n} Matches from m to n repetitions of the preceding RE. + {m,n}? Non-greedy version of the above. + "\\" Either escapes special characters or signals a special sequence. + [] Indicates a set of characters. + A "^" as the first character indicates a complementing set. + "|" A|B, creates an RE that will match either A or B. + (...) Matches the RE inside the parentheses. + The contents can be retrieved or matched later in the string. + (?aiLmsux) The letters set the corresponding flags defined below. + (?:...) Non-grouping version of regular parentheses. + (?P...) The substring matched by the group is accessible by name. + (?P=name) Matches the text matched earlier by the group named name. + (?#...) A comment; ignored. + (?=...) Matches if ... matches next, but doesn't consume the string. + (?!...) Matches if ... doesn't match next. + (?<=...) Matches if preceded by ... (must be fixed length). + (? 1: + res = f'~({res})' + else: + res = f'~{res}' + return res + __str__ = object.__str__ +globals().update(RegexFlag.__members__) + +# sre exception +error = sre_compile.error + +# -------------------------------------------------------------------- +# public interface + +def match(pattern, string, flags=0): + """Try to apply the pattern at the start of the string, returning + a Match object, or None if no match was found.""" + return _compile(pattern, flags).match(string) + +def fullmatch(pattern, string, flags=0): + """Try to apply the pattern to all of the string, returning + a Match object, or None if no match was found.""" + return _compile(pattern, flags).fullmatch(string) + +def search(pattern, string, flags=0): + """Scan through string looking for a match to the pattern, returning + a Match object, or None if no match was found.""" + return _compile(pattern, flags).search(string) + +def sub(pattern, repl, string, count=0, flags=0): + """Return the string obtained by replacing the leftmost + non-overlapping occurrences of the pattern in string by the + replacement repl. repl can be either a string or a callable; + if a string, backslash escapes in it are processed. If it is + a callable, it's passed the Match object and must return + a replacement string to be used.""" + return _compile(pattern, flags).sub(repl, string, count) + +def subn(pattern, repl, string, count=0, flags=0): + """Return a 2-tuple containing (new_string, number). + new_string is the string obtained by replacing the leftmost + non-overlapping occurrences of the pattern in the source + string by the replacement repl. number is the number of + substitutions that were made. repl can be either a string or a + callable; if a string, backslash escapes in it are processed. + If it is a callable, it's passed the Match object and must + return a replacement string to be used.""" + return _compile(pattern, flags).subn(repl, string, count) + +def split(pattern, string, maxsplit=0, flags=0): + """Split the source string by the occurrences of the pattern, + returning a list containing the resulting substrings. If + capturing parentheses are used in pattern, then the text of all + groups in the pattern are also returned as part of the resulting + list. If maxsplit is nonzero, at most maxsplit splits occur, + and the remainder of the string is returned as the final element + of the list.""" + return _compile(pattern, flags).split(string, maxsplit) + +def findall(pattern, string, flags=0): + """Return a list of all non-overlapping matches in the string. + + If one or more capturing groups are present in the pattern, return + a list of groups; this will be a list of tuples if the pattern + has more than one group. + + Empty matches are included in the result.""" + return _compile(pattern, flags).findall(string) + +def finditer(pattern, string, flags=0): + """Return an iterator over all non-overlapping matches in the + string. For each match, the iterator returns a Match object. + + Empty matches are included in the result.""" + return _compile(pattern, flags).finditer(string) + +def compile(pattern, flags=0): + "Compile a regular expression pattern, returning a Pattern object." + return _compile(pattern, flags) + +def purge(): + "Clear the regular expression caches" + _cache.clear() + _compile_repl.cache_clear() + +def template(pattern, flags=0): + "Compile a template pattern, returning a Pattern object" + return _compile(pattern, flags|T) + +# SPECIAL_CHARS +# closing ')', '}' and ']' +# '-' (a range in character set) +# '&', '~', (extended character set operations) +# '#' (comment) and WHITESPACE (ignored) in verbose mode +_special_chars_map = {i: '\\' + chr(i) for i in b'()[]{}?*+-|^$\\.&~# \t\n\r\v\f'} + +def escape(pattern): + """ + Escape special characters in a string. + """ + if isinstance(pattern, str): + return pattern.translate(_special_chars_map) + else: + pattern = str(pattern, 'latin1') + return pattern.translate(_special_chars_map).encode('latin1') + +Pattern = type(sre_compile.compile('', 0)) +Match = type(sre_compile.compile('', 0).match('')) + +# -------------------------------------------------------------------- +# internals + +_cache = {} # ordered! + +_MAXCACHE = 512 +def _compile(pattern, flags): + # internal: compile pattern + if isinstance(flags, RegexFlag): + flags = flags.value + try: + return _cache[type(pattern), pattern, flags] + except KeyError: + pass + if isinstance(pattern, Pattern): + if flags: + raise ValueError( + "cannot process flags argument with a compiled pattern") + return pattern + if not sre_compile.isstring(pattern): + raise TypeError("first argument must be string or compiled pattern") + p = sre_compile.compile(pattern, flags) + if not (flags & DEBUG): + if len(_cache) >= _MAXCACHE: + # Drop the oldest item + try: + del _cache[next(iter(_cache))] + except (StopIteration, RuntimeError, KeyError): + pass + _cache[type(pattern), pattern, flags] = p + return p + +@functools.lru_cache(_MAXCACHE) +def _compile_repl(repl, pattern): + # internal: compile replacement pattern + return sre_parse.parse_template(repl, pattern) + +def _expand(pattern, match, template): + # internal: Match.expand implementation hook + template = sre_parse.parse_template(template, pattern) + return sre_parse.expand_template(template, match) + +def _subx(pattern, template): + # internal: Pattern.sub/subn implementation helper + template = _compile_repl(template, pattern) + if not template[0] and len(template[1]) == 1: + # literal replacement + return template[1][0] + def filter(match, template=template): + return sre_parse.expand_template(template, match) + return filter + +# register myself for pickling + +import copyreg + +def _pickle(p): + return _compile, (p.pattern, p.flags) + +copyreg.pickle(Pattern, _pickle, _compile) + +# -------------------------------------------------------------------- +# experimental stuff (see python-dev discussions for details) + +class Scanner: + def __init__(self, lexicon, flags=0): + from sre_constants import BRANCH, SUBPATTERN + if isinstance(flags, RegexFlag): + flags = flags.value + self.lexicon = lexicon + # combine phrases into a compound pattern + p = [] + s = sre_parse.State() + s.flags = flags + for phrase, action in lexicon: + gid = s.opengroup() + p.append(sre_parse.SubPattern(s, [ + (SUBPATTERN, (gid, 0, 0, sre_parse.parse(phrase, flags))), + ])) + s.closegroup(gid, p[-1]) + p = sre_parse.SubPattern(s, [(BRANCH, (None, p))]) + self.scanner = sre_compile.compile(p) + def scan(self, string): + result = [] + append = result.append + match = self.scanner.scanner(string).match + i = 0 + while True: + m = match() + if not m: + break + j = m.end() + if i == j: + break + action = self.lexicon[m.lastindex-1][1] + if callable(action): + self.match = m + action = action(self, m.group()) + if action is not None: + append(action) + i = j + return result, string[i:] diff --git a/lib/python3.10/reprlib.py b/lib/python3.10/reprlib.py new file mode 100644 index 0000000000000000000000000000000000000000..616b3439b5de30a9a8cef6b1beb100625e825ee4 --- /dev/null +++ b/lib/python3.10/reprlib.py @@ -0,0 +1,161 @@ +"""Redo the builtin repr() (representation) but with limits on most sizes.""" + +__all__ = ["Repr", "repr", "recursive_repr"] + +import builtins +from itertools import islice +from _thread import get_ident + +def recursive_repr(fillvalue='...'): + 'Decorator to make a repr function return fillvalue for a recursive call' + + def decorating_function(user_function): + repr_running = set() + + def wrapper(self): + key = id(self), get_ident() + if key in repr_running: + return fillvalue + repr_running.add(key) + try: + result = user_function(self) + finally: + repr_running.discard(key) + return result + + # Can't use functools.wraps() here because of bootstrap issues + wrapper.__module__ = getattr(user_function, '__module__') + wrapper.__doc__ = getattr(user_function, '__doc__') + wrapper.__name__ = getattr(user_function, '__name__') + wrapper.__qualname__ = getattr(user_function, '__qualname__') + wrapper.__annotations__ = getattr(user_function, '__annotations__', {}) + return wrapper + + return decorating_function + +class Repr: + + def __init__(self): + self.maxlevel = 6 + self.maxtuple = 6 + self.maxlist = 6 + self.maxarray = 5 + self.maxdict = 4 + self.maxset = 6 + self.maxfrozenset = 6 + self.maxdeque = 6 + self.maxstring = 30 + self.maxlong = 40 + self.maxother = 30 + + def repr(self, x): + return self.repr1(x, self.maxlevel) + + def repr1(self, x, level): + typename = type(x).__name__ + if ' ' in typename: + parts = typename.split() + typename = '_'.join(parts) + if hasattr(self, 'repr_' + typename): + return getattr(self, 'repr_' + typename)(x, level) + else: + return self.repr_instance(x, level) + + def _repr_iterable(self, x, level, left, right, maxiter, trail=''): + n = len(x) + if level <= 0 and n: + s = '...' + else: + newlevel = level - 1 + repr1 = self.repr1 + pieces = [repr1(elem, newlevel) for elem in islice(x, maxiter)] + if n > maxiter: pieces.append('...') + s = ', '.join(pieces) + if n == 1 and trail: right = trail + right + return '%s%s%s' % (left, s, right) + + def repr_tuple(self, x, level): + return self._repr_iterable(x, level, '(', ')', self.maxtuple, ',') + + def repr_list(self, x, level): + return self._repr_iterable(x, level, '[', ']', self.maxlist) + + def repr_array(self, x, level): + if not x: + return "array('%s')" % x.typecode + header = "array('%s', [" % x.typecode + return self._repr_iterable(x, level, header, '])', self.maxarray) + + def repr_set(self, x, level): + if not x: + return 'set()' + x = _possibly_sorted(x) + return self._repr_iterable(x, level, '{', '}', self.maxset) + + def repr_frozenset(self, x, level): + if not x: + return 'frozenset()' + x = _possibly_sorted(x) + return self._repr_iterable(x, level, 'frozenset({', '})', + self.maxfrozenset) + + def repr_deque(self, x, level): + return self._repr_iterable(x, level, 'deque([', '])', self.maxdeque) + + def repr_dict(self, x, level): + n = len(x) + if n == 0: return '{}' + if level <= 0: return '{...}' + newlevel = level - 1 + repr1 = self.repr1 + pieces = [] + for key in islice(_possibly_sorted(x), self.maxdict): + keyrepr = repr1(key, newlevel) + valrepr = repr1(x[key], newlevel) + pieces.append('%s: %s' % (keyrepr, valrepr)) + if n > self.maxdict: pieces.append('...') + s = ', '.join(pieces) + return '{%s}' % (s,) + + def repr_str(self, x, level): + s = builtins.repr(x[:self.maxstring]) + if len(s) > self.maxstring: + i = max(0, (self.maxstring-3)//2) + j = max(0, self.maxstring-3-i) + s = builtins.repr(x[:i] + x[len(x)-j:]) + s = s[:i] + '...' + s[len(s)-j:] + return s + + def repr_int(self, x, level): + s = builtins.repr(x) # XXX Hope this isn't too slow... + if len(s) > self.maxlong: + i = max(0, (self.maxlong-3)//2) + j = max(0, self.maxlong-3-i) + s = s[:i] + '...' + s[len(s)-j:] + return s + + def repr_instance(self, x, level): + try: + s = builtins.repr(x) + # Bugs in x.__repr__() can cause arbitrary + # exceptions -- then make up something + except Exception: + return '<%s instance at %#x>' % (x.__class__.__name__, id(x)) + if len(s) > self.maxother: + i = max(0, (self.maxother-3)//2) + j = max(0, self.maxother-3-i) + s = s[:i] + '...' + s[len(s)-j:] + return s + + +def _possibly_sorted(x): + # Since not all sequences of items can be sorted and comparison + # functions may raise arbitrary exceptions, return an unsorted + # sequence in that case. + try: + return sorted(x) + except Exception: + return list(x) + +aRepr = Repr() +repr = aRepr.repr diff --git a/lib/python3.10/rlcompleter.py b/lib/python3.10/rlcompleter.py new file mode 100644 index 0000000000000000000000000000000000000000..98b7930b32fab32e387500dee01c7a2a68eb2e3a --- /dev/null +++ b/lib/python3.10/rlcompleter.py @@ -0,0 +1,219 @@ +"""Word completion for GNU readline. + +The completer completes keywords, built-ins and globals in a selectable +namespace (which defaults to __main__); when completing NAME.NAME..., it +evaluates (!) the expression up to the last dot and completes its attributes. + +It's very cool to do "import sys" type "sys.", hit the completion key (twice), +and see the list of names defined by the sys module! + +Tip: to use the tab key as the completion key, call + + readline.parse_and_bind("tab: complete") + +Notes: + +- Exceptions raised by the completer function are *ignored* (and generally cause + the completion to fail). This is a feature -- since readline sets the tty + device in raw (or cbreak) mode, printing a traceback wouldn't work well + without some complicated hoopla to save, reset and restore the tty state. + +- The evaluation of the NAME.NAME... form may cause arbitrary application + defined code to be executed if an object with a __getattr__ hook is found. + Since it is the responsibility of the application (or the user) to enable this + feature, I consider this an acceptable risk. More complicated expressions + (e.g. function calls or indexing operations) are *not* evaluated. + +- When the original stdin is not a tty device, GNU readline is never + used, and this module (and the readline module) are silently inactive. + +""" + +import atexit +import builtins +import inspect +import __main__ + +__all__ = ["Completer"] + +class Completer: + def __init__(self, namespace = None): + """Create a new completer for the command line. + + Completer([namespace]) -> completer instance. + + If unspecified, the default namespace where completions are performed + is __main__ (technically, __main__.__dict__). Namespaces should be + given as dictionaries. + + Completer instances should be used as the completion mechanism of + readline via the set_completer() call: + + readline.set_completer(Completer(my_namespace).complete) + """ + + if namespace and not isinstance(namespace, dict): + raise TypeError('namespace must be a dictionary') + + # Don't bind to namespace quite yet, but flag whether the user wants a + # specific namespace or to use __main__.__dict__. This will allow us + # to bind to __main__.__dict__ at completion time, not now. + if namespace is None: + self.use_main_ns = 1 + else: + self.use_main_ns = 0 + self.namespace = namespace + + def complete(self, text, state): + """Return the next possible completion for 'text'. + + This is called successively with state == 0, 1, 2, ... until it + returns None. The completion should begin with 'text'. + + """ + if self.use_main_ns: + self.namespace = __main__.__dict__ + + if not text.strip(): + if state == 0: + if _readline_available: + readline.insert_text('\t') + readline.redisplay() + return '' + else: + return '\t' + else: + return None + + if state == 0: + if "." in text: + self.matches = self.attr_matches(text) + else: + self.matches = self.global_matches(text) + try: + return self.matches[state] + except IndexError: + return None + + def _callable_postfix(self, val, word): + if callable(val): + word += "(" + try: + if not inspect.signature(val).parameters: + word += ")" + except ValueError: + pass + + return word + + def global_matches(self, text): + """Compute matches when text is a simple name. + + Return a list of all keywords, built-in functions and names currently + defined in self.namespace that match. + + """ + import keyword + matches = [] + seen = {"__builtins__"} + n = len(text) + for word in keyword.kwlist: + if word[:n] == text: + seen.add(word) + if word in {'finally', 'try'}: + word = word + ':' + elif word not in {'False', 'None', 'True', + 'break', 'continue', 'pass', + 'else'}: + word = word + ' ' + matches.append(word) + for nspace in [self.namespace, builtins.__dict__]: + for word, val in nspace.items(): + if word[:n] == text and word not in seen: + seen.add(word) + matches.append(self._callable_postfix(val, word)) + return matches + + def attr_matches(self, text): + """Compute matches when text contains a dot. + + Assuming the text is of the form NAME.NAME....[NAME], and is + evaluable in self.namespace, it will be evaluated and its attributes + (as revealed by dir()) are used as possible completions. (For class + instances, class members are also considered.) + + WARNING: this can still invoke arbitrary C code, if an object + with a __getattr__ hook is evaluated. + + """ + import re + m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text) + if not m: + return [] + expr, attr = m.group(1, 3) + try: + thisobject = eval(expr, self.namespace) + except Exception: + return [] + + # get the content of the object, except __builtins__ + words = set(dir(thisobject)) + words.discard("__builtins__") + + if hasattr(thisobject, '__class__'): + words.add('__class__') + words.update(get_class_members(thisobject.__class__)) + matches = [] + n = len(attr) + if attr == '': + noprefix = '_' + elif attr == '_': + noprefix = '__' + else: + noprefix = None + while True: + for word in words: + if (word[:n] == attr and + not (noprefix and word[:n+1] == noprefix)): + match = "%s.%s" % (expr, word) + if isinstance(getattr(type(thisobject), word, None), + property): + # bpo-44752: thisobject.word is a method decorated by + # `@property`. What follows applies a postfix if + # thisobject.word is callable, but know we know that + # this is not callable (because it is a property). + # Also, getattr(thisobject, word) will evaluate the + # property method, which is not desirable. + matches.append(match) + continue + if (value := getattr(thisobject, word, None)) is not None: + matches.append(self._callable_postfix(value, match)) + else: + matches.append(match) + if matches or not noprefix: + break + if noprefix == '_': + noprefix = '__' + else: + noprefix = None + matches.sort() + return matches + +def get_class_members(klass): + ret = dir(klass) + if hasattr(klass,'__bases__'): + for base in klass.__bases__: + ret = ret + get_class_members(base) + return ret + +try: + import readline +except ImportError: + _readline_available = False +else: + readline.set_completer(Completer().complete) + # Release references early at shutdown (the readline module's + # contents are quasi-immortal, and the completer function holds a + # reference to globals). + atexit.register(lambda: readline.set_completer(None)) + _readline_available = True diff --git a/lib/python3.10/runpy.py b/lib/python3.10/runpy.py new file mode 100644 index 0000000000000000000000000000000000000000..c7d3d8caad1611ed52f1be8d517ad2ac906f04db --- /dev/null +++ b/lib/python3.10/runpy.py @@ -0,0 +1,321 @@ +"""runpy.py - locating and running Python code using the module namespace + +Provides support for locating and running Python scripts using the Python +module namespace instead of the native filesystem. + +This allows Python code to play nicely with non-filesystem based PEP 302 +importers when locating support scripts as well as when importing modules. +""" +# Written by Nick Coghlan +# to implement PEP 338 (Executing Modules as Scripts) + + +import sys +import importlib.machinery # importlib first so we can test #15386 via -m +import importlib.util +import io +import types +import os + +__all__ = [ + "run_module", "run_path", +] + +class _TempModule(object): + """Temporarily replace a module in sys.modules with an empty namespace""" + def __init__(self, mod_name): + self.mod_name = mod_name + self.module = types.ModuleType(mod_name) + self._saved_module = [] + + def __enter__(self): + mod_name = self.mod_name + try: + self._saved_module.append(sys.modules[mod_name]) + except KeyError: + pass + sys.modules[mod_name] = self.module + return self + + def __exit__(self, *args): + if self._saved_module: + sys.modules[self.mod_name] = self._saved_module[0] + else: + del sys.modules[self.mod_name] + self._saved_module = [] + +class _ModifiedArgv0(object): + def __init__(self, value): + self.value = value + self._saved_value = self._sentinel = object() + + def __enter__(self): + if self._saved_value is not self._sentinel: + raise RuntimeError("Already preserving saved value") + self._saved_value = sys.argv[0] + sys.argv[0] = self.value + + def __exit__(self, *args): + self.value = self._sentinel + sys.argv[0] = self._saved_value + +# TODO: Replace these helpers with importlib._bootstrap_external functions. +def _run_code(code, run_globals, init_globals=None, + mod_name=None, mod_spec=None, + pkg_name=None, script_name=None): + """Helper to run code in nominated namespace""" + if init_globals is not None: + run_globals.update(init_globals) + if mod_spec is None: + loader = None + fname = script_name + cached = None + else: + loader = mod_spec.loader + fname = mod_spec.origin + cached = mod_spec.cached + if pkg_name is None: + pkg_name = mod_spec.parent + run_globals.update(__name__ = mod_name, + __file__ = fname, + __cached__ = cached, + __doc__ = None, + __loader__ = loader, + __package__ = pkg_name, + __spec__ = mod_spec) + exec(code, run_globals) + return run_globals + +def _run_module_code(code, init_globals=None, + mod_name=None, mod_spec=None, + pkg_name=None, script_name=None): + """Helper to run code in new namespace with sys modified""" + fname = script_name if mod_spec is None else mod_spec.origin + with _TempModule(mod_name) as temp_module, _ModifiedArgv0(fname): + mod_globals = temp_module.module.__dict__ + _run_code(code, mod_globals, init_globals, + mod_name, mod_spec, pkg_name, script_name) + # Copy the globals of the temporary module, as they + # may be cleared when the temporary module goes away + return mod_globals.copy() + +# Helper to get the full name, spec and code for a module +def _get_module_details(mod_name, error=ImportError): + if mod_name.startswith("."): + raise error("Relative module names not supported") + pkg_name, _, _ = mod_name.rpartition(".") + if pkg_name: + # Try importing the parent to avoid catching initialization errors + try: + __import__(pkg_name) + except ImportError as e: + # If the parent or higher ancestor package is missing, let the + # error be raised by find_spec() below and then be caught. But do + # not allow other errors to be caught. + if e.name is None or (e.name != pkg_name and + not pkg_name.startswith(e.name + ".")): + raise + # Warn if the module has already been imported under its normal name + existing = sys.modules.get(mod_name) + if existing is not None and not hasattr(existing, "__path__"): + from warnings import warn + msg = "{mod_name!r} found in sys.modules after import of " \ + "package {pkg_name!r}, but prior to execution of " \ + "{mod_name!r}; this may result in unpredictable " \ + "behaviour".format(mod_name=mod_name, pkg_name=pkg_name) + warn(RuntimeWarning(msg)) + + try: + spec = importlib.util.find_spec(mod_name) + except (ImportError, AttributeError, TypeError, ValueError) as ex: + # This hack fixes an impedance mismatch between pkgutil and + # importlib, where the latter raises other errors for cases where + # pkgutil previously raised ImportError + msg = "Error while finding module specification for {!r} ({}: {})" + if mod_name.endswith(".py"): + msg += (f". Try using '{mod_name[:-3]}' instead of " + f"'{mod_name}' as the module name.") + raise error(msg.format(mod_name, type(ex).__name__, ex)) from ex + if spec is None: + raise error("No module named %s" % mod_name) + if spec.submodule_search_locations is not None: + if mod_name == "__main__" or mod_name.endswith(".__main__"): + raise error("Cannot use package as __main__ module") + try: + pkg_main_name = mod_name + ".__main__" + return _get_module_details(pkg_main_name, error) + except error as e: + if mod_name not in sys.modules: + raise # No module loaded; being a package is irrelevant + raise error(("%s; %r is a package and cannot " + + "be directly executed") %(e, mod_name)) + loader = spec.loader + if loader is None: + raise error("%r is a namespace package and cannot be executed" + % mod_name) + try: + code = loader.get_code(mod_name) + except ImportError as e: + raise error(format(e)) from e + if code is None: + raise error("No code object available for %s" % mod_name) + return mod_name, spec, code + +class _Error(Exception): + """Error that _run_module_as_main() should report without a traceback""" + +# XXX ncoghlan: Should this be documented and made public? +# (Current thoughts: don't repeat the mistake that lead to its +# creation when run_module() no longer met the needs of +# mainmodule.c, but couldn't be changed because it was public) +def _run_module_as_main(mod_name, alter_argv=True): + """Runs the designated module in the __main__ namespace + + Note that the executed module will have full access to the + __main__ namespace. If this is not desirable, the run_module() + function should be used to run the module code in a fresh namespace. + + At the very least, these variables in __main__ will be overwritten: + __name__ + __file__ + __cached__ + __loader__ + __package__ + """ + try: + if alter_argv or mod_name != "__main__": # i.e. -m switch + mod_name, mod_spec, code = _get_module_details(mod_name, _Error) + else: # i.e. directory or zipfile execution + mod_name, mod_spec, code = _get_main_module_details(_Error) + except _Error as exc: + msg = "%s: %s" % (sys.executable, exc) + sys.exit(msg) + main_globals = sys.modules["__main__"].__dict__ + if alter_argv: + sys.argv[0] = mod_spec.origin + return _run_code(code, main_globals, None, + "__main__", mod_spec) + +def run_module(mod_name, init_globals=None, + run_name=None, alter_sys=False): + """Execute a module's code without importing it. + + mod_name -- an absolute module name or package name. + + Optional arguments: + init_globals -- dictionary used to pre-populate the module’s + globals dictionary before the code is executed. + + run_name -- if not None, this will be used for setting __name__; + otherwise, __name__ will be set to mod_name + '__main__' if the + named module is a package and to just mod_name otherwise. + + alter_sys -- if True, sys.argv[0] is updated with the value of + __file__ and sys.modules[__name__] is updated with a temporary + module object for the module being executed. Both are + restored to their original values before the function returns. + + Returns the resulting module globals dictionary. + """ + mod_name, mod_spec, code = _get_module_details(mod_name) + if run_name is None: + run_name = mod_name + if alter_sys: + return _run_module_code(code, init_globals, run_name, mod_spec) + else: + # Leave the sys module alone + return _run_code(code, {}, init_globals, run_name, mod_spec) + +def _get_main_module_details(error=ImportError): + # Helper that gives a nicer error message when attempting to + # execute a zipfile or directory by invoking __main__.py + # Also moves the standard __main__ out of the way so that the + # preexisting __loader__ entry doesn't cause issues + main_name = "__main__" + saved_main = sys.modules[main_name] + del sys.modules[main_name] + try: + return _get_module_details(main_name) + except ImportError as exc: + if main_name in str(exc): + raise error("can't find %r module in %r" % + (main_name, sys.path[0])) from exc + raise + finally: + sys.modules[main_name] = saved_main + + +def _get_code_from_file(run_name, fname): + # Check for a compiled file first + from pkgutil import read_code + decoded_path = os.path.abspath(os.fsdecode(fname)) + with io.open_code(decoded_path) as f: + code = read_code(f) + if code is None: + # That didn't work, so try it as normal source code + with io.open_code(decoded_path) as f: + code = compile(f.read(), fname, 'exec') + return code, fname + +def run_path(path_name, init_globals=None, run_name=None): + """Execute code located at the specified filesystem location. + + path_name -- filesystem location of a Python script, zipfile, + or directory containing a top level __main__.py script. + + Optional arguments: + init_globals -- dictionary used to pre-populate the module’s + globals dictionary before the code is executed. + + run_name -- if not None, this will be used to set __name__; + otherwise, '' will be used for __name__. + + Returns the resulting module globals dictionary. + """ + if run_name is None: + run_name = "" + pkg_name = run_name.rpartition(".")[0] + from pkgutil import get_importer + importer = get_importer(path_name) + # Trying to avoid importing imp so as to not consume the deprecation warning. + is_NullImporter = False + if type(importer).__module__ == 'imp': + if type(importer).__name__ == 'NullImporter': + is_NullImporter = True + if isinstance(importer, type(None)) or is_NullImporter: + # Not a valid sys.path entry, so run the code directly + # execfile() doesn't help as we want to allow compiled files + code, fname = _get_code_from_file(run_name, path_name) + return _run_module_code(code, init_globals, run_name, + pkg_name=pkg_name, script_name=fname) + else: + # Finder is defined for path, so add it to + # the start of sys.path + sys.path.insert(0, path_name) + try: + # Here's where things are a little different from the run_module + # case. There, we only had to replace the module in sys while the + # code was running and doing so was somewhat optional. Here, we + # have no choice and we have to remove it even while we read the + # code. If we don't do this, a __loader__ attribute in the + # existing __main__ module may prevent location of the new module. + mod_name, mod_spec, code = _get_main_module_details() + with _TempModule(run_name) as temp_module, \ + _ModifiedArgv0(path_name): + mod_globals = temp_module.module.__dict__ + return _run_code(code, mod_globals, init_globals, + run_name, mod_spec, pkg_name).copy() + finally: + try: + sys.path.remove(path_name) + except ValueError: + pass + + +if __name__ == "__main__": + # Run the module specified as the next command line argument + if len(sys.argv) < 2: + print("No module specified for execution", file=sys.stderr) + else: + del sys.argv[0] # Make the requested module sys.argv[0] + _run_module_as_main(sys.argv[0]) diff --git a/lib/python3.10/sched.py b/lib/python3.10/sched.py new file mode 100644 index 0000000000000000000000000000000000000000..14613cf29874da539490accd7d83abce51499964 --- /dev/null +++ b/lib/python3.10/sched.py @@ -0,0 +1,167 @@ +"""A generally useful event scheduler class. + +Each instance of this class manages its own queue. +No multi-threading is implied; you are supposed to hack that +yourself, or use a single instance per application. + +Each instance is parametrized with two functions, one that is +supposed to return the current time, one that is supposed to +implement a delay. You can implement real-time scheduling by +substituting time and sleep from built-in module time, or you can +implement simulated time by writing your own functions. This can +also be used to integrate scheduling with STDWIN events; the delay +function is allowed to modify the queue. Time can be expressed as +integers or floating point numbers, as long as it is consistent. + +Events are specified by tuples (time, priority, action, argument, kwargs). +As in UNIX, lower priority numbers mean higher priority; in this +way the queue can be maintained as a priority queue. Execution of the +event means calling the action function, passing it the argument +sequence in "argument" (remember that in Python, multiple function +arguments are be packed in a sequence) and keyword parameters in "kwargs". +The action function may be an instance method so it +has another way to reference private data (besides global variables). +""" + +import time +import heapq +from collections import namedtuple +from itertools import count +import threading +from time import monotonic as _time + +__all__ = ["scheduler"] + +Event = namedtuple('Event', 'time, priority, sequence, action, argument, kwargs') +Event.time.__doc__ = ('''Numeric type compatible with the return value of the +timefunc function passed to the constructor.''') +Event.priority.__doc__ = ('''Events scheduled for the same time will be executed +in the order of their priority.''') +Event.sequence.__doc__ = ('''A continually increasing sequence number that + separates events if time and priority are equal.''') +Event.action.__doc__ = ('''Executing the event means executing +action(*argument, **kwargs)''') +Event.argument.__doc__ = ('''argument is a sequence holding the positional +arguments for the action.''') +Event.kwargs.__doc__ = ('''kwargs is a dictionary holding the keyword +arguments for the action.''') + +_sentinel = object() + +class scheduler: + + def __init__(self, timefunc=_time, delayfunc=time.sleep): + """Initialize a new instance, passing the time and delay + functions""" + self._queue = [] + self._lock = threading.RLock() + self.timefunc = timefunc + self.delayfunc = delayfunc + self._sequence_generator = count() + + def enterabs(self, time, priority, action, argument=(), kwargs=_sentinel): + """Enter a new event in the queue at an absolute time. + + Returns an ID for the event which can be used to remove it, + if necessary. + + """ + if kwargs is _sentinel: + kwargs = {} + + with self._lock: + event = Event(time, priority, next(self._sequence_generator), + action, argument, kwargs) + heapq.heappush(self._queue, event) + return event # The ID + + def enter(self, delay, priority, action, argument=(), kwargs=_sentinel): + """A variant that specifies the time as a relative time. + + This is actually the more commonly used interface. + + """ + time = self.timefunc() + delay + return self.enterabs(time, priority, action, argument, kwargs) + + def cancel(self, event): + """Remove an event from the queue. + + This must be presented the ID as returned by enter(). + If the event is not in the queue, this raises ValueError. + + """ + with self._lock: + self._queue.remove(event) + heapq.heapify(self._queue) + + def empty(self): + """Check whether the queue is empty.""" + with self._lock: + return not self._queue + + def run(self, blocking=True): + """Execute events until the queue is empty. + If blocking is False executes the scheduled events due to + expire soonest (if any) and then return the deadline of the + next scheduled call in the scheduler. + + When there is a positive delay until the first event, the + delay function is called and the event is left in the queue; + otherwise, the event is removed from the queue and executed + (its action function is called, passing it the argument). If + the delay function returns prematurely, it is simply + restarted. + + It is legal for both the delay function and the action + function to modify the queue or to raise an exception; + exceptions are not caught but the scheduler's state remains + well-defined so run() may be called again. + + A questionable hack is added to allow other threads to run: + just after an event is executed, a delay of 0 is executed, to + avoid monopolizing the CPU when other threads are also + runnable. + + """ + # localize variable access to minimize overhead + # and to improve thread safety + lock = self._lock + q = self._queue + delayfunc = self.delayfunc + timefunc = self.timefunc + pop = heapq.heappop + while True: + with lock: + if not q: + break + (time, priority, sequence, action, + argument, kwargs) = q[0] + now = timefunc() + if time > now: + delay = True + else: + delay = False + pop(q) + if delay: + if not blocking: + return time - now + delayfunc(time - now) + else: + action(*argument, **kwargs) + delayfunc(0) # Let other threads run + + @property + def queue(self): + """An ordered list of upcoming events. + + Events are named tuples with fields for: + time, priority, action, arguments, kwargs + + """ + # Use heapq to sort the queue rather than using 'sorted(self._queue)'. + # With heapq, two events scheduled at the same time will show in + # the actual order they would be retrieved. + with self._lock: + events = self._queue[:] + return list(map(heapq.heappop, [events]*len(events))) diff --git a/lib/python3.10/secrets.py b/lib/python3.10/secrets.py new file mode 100644 index 0000000000000000000000000000000000000000..a546efbdd4204c81d0a8d8917dc539380fa77070 --- /dev/null +++ b/lib/python3.10/secrets.py @@ -0,0 +1,72 @@ +"""Generate cryptographically strong pseudo-random numbers suitable for +managing secrets such as account authentication, tokens, and similar. + +See PEP 506 for more information. +https://www.python.org/dev/peps/pep-0506/ + +""" + +__all__ = ['choice', 'randbelow', 'randbits', 'SystemRandom', + 'token_bytes', 'token_hex', 'token_urlsafe', + 'compare_digest', + ] + + +import base64 +import binascii + +from hmac import compare_digest +from random import SystemRandom + +_sysrand = SystemRandom() + +randbits = _sysrand.getrandbits +choice = _sysrand.choice + +def randbelow(exclusive_upper_bound): + """Return a random int in the range [0, n).""" + if exclusive_upper_bound <= 0: + raise ValueError("Upper bound must be positive.") + return _sysrand._randbelow(exclusive_upper_bound) + +DEFAULT_ENTROPY = 32 # number of bytes to return by default + +def token_bytes(nbytes=None): + """Return a random byte string containing *nbytes* bytes. + + If *nbytes* is ``None`` or not supplied, a reasonable + default is used. + + >>> token_bytes(16) #doctest:+SKIP + b'\\xebr\\x17D*t\\xae\\xd4\\xe3S\\xb6\\xe2\\xebP1\\x8b' + + """ + if nbytes is None: + nbytes = DEFAULT_ENTROPY + return _sysrand.randbytes(nbytes) + +def token_hex(nbytes=None): + """Return a random text string, in hexadecimal. + + The string has *nbytes* random bytes, each byte converted to two + hex digits. If *nbytes* is ``None`` or not supplied, a reasonable + default is used. + + >>> token_hex(16) #doctest:+SKIP + 'f9bf78b9a18ce6d46a0cd2b0b86df9da' + + """ + return binascii.hexlify(token_bytes(nbytes)).decode('ascii') + +def token_urlsafe(nbytes=None): + """Return a random URL-safe text string, in Base64 encoding. + + The string has *nbytes* random bytes. If *nbytes* is ``None`` + or not supplied, a reasonable default is used. + + >>> token_urlsafe(16) #doctest:+SKIP + 'Drmhze6EPcv0fN_81Bj-nA' + + """ + tok = token_bytes(nbytes) + return base64.urlsafe_b64encode(tok).rstrip(b'=').decode('ascii') diff --git a/lib/python3.10/selectors.py b/lib/python3.10/selectors.py new file mode 100644 index 0000000000000000000000000000000000000000..bb15a1cb1bada1ae70ad4fb2a0863017215754a0 --- /dev/null +++ b/lib/python3.10/selectors.py @@ -0,0 +1,619 @@ +"""Selectors module. + +This module allows high-level and efficient I/O multiplexing, built upon the +`select` module primitives. +""" + + +from abc import ABCMeta, abstractmethod +from collections import namedtuple +from collections.abc import Mapping +import math +import select +import sys + + +# generic events, that must be mapped to implementation-specific ones +EVENT_READ = (1 << 0) +EVENT_WRITE = (1 << 1) + + +def _fileobj_to_fd(fileobj): + """Return a file descriptor from a file object. + + Parameters: + fileobj -- file object or file descriptor + + Returns: + corresponding file descriptor + + Raises: + ValueError if the object is invalid + """ + if isinstance(fileobj, int): + fd = fileobj + else: + try: + fd = int(fileobj.fileno()) + except (AttributeError, TypeError, ValueError): + raise ValueError("Invalid file object: " + "{!r}".format(fileobj)) from None + if fd < 0: + raise ValueError("Invalid file descriptor: {}".format(fd)) + return fd + + +SelectorKey = namedtuple('SelectorKey', ['fileobj', 'fd', 'events', 'data']) + +SelectorKey.__doc__ = """SelectorKey(fileobj, fd, events, data) + + Object used to associate a file object to its backing + file descriptor, selected event mask, and attached data. +""" +if sys.version_info >= (3, 5): + SelectorKey.fileobj.__doc__ = 'File object registered.' + SelectorKey.fd.__doc__ = 'Underlying file descriptor.' + SelectorKey.events.__doc__ = 'Events that must be waited for on this file object.' + SelectorKey.data.__doc__ = ('''Optional opaque data associated to this file object. + For example, this could be used to store a per-client session ID.''') + + +class _SelectorMapping(Mapping): + """Mapping of file objects to selector keys.""" + + def __init__(self, selector): + self._selector = selector + + def __len__(self): + return len(self._selector._fd_to_key) + + def __getitem__(self, fileobj): + try: + fd = self._selector._fileobj_lookup(fileobj) + return self._selector._fd_to_key[fd] + except KeyError: + raise KeyError("{!r} is not registered".format(fileobj)) from None + + def __iter__(self): + return iter(self._selector._fd_to_key) + + +class BaseSelector(metaclass=ABCMeta): + """Selector abstract base class. + + A selector supports registering file objects to be monitored for specific + I/O events. + + A file object is a file descriptor or any object with a `fileno()` method. + An arbitrary object can be attached to the file object, which can be used + for example to store context information, a callback, etc. + + A selector can use various implementations (select(), poll(), epoll()...) + depending on the platform. The default `Selector` class uses the most + efficient implementation on the current platform. + """ + + @abstractmethod + def register(self, fileobj, events, data=None): + """Register a file object. + + Parameters: + fileobj -- file object or file descriptor + events -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE) + data -- attached data + + Returns: + SelectorKey instance + + Raises: + ValueError if events is invalid + KeyError if fileobj is already registered + OSError if fileobj is closed or otherwise is unacceptable to + the underlying system call (if a system call is made) + + Note: + OSError may or may not be raised + """ + raise NotImplementedError + + @abstractmethod + def unregister(self, fileobj): + """Unregister a file object. + + Parameters: + fileobj -- file object or file descriptor + + Returns: + SelectorKey instance + + Raises: + KeyError if fileobj is not registered + + Note: + If fileobj is registered but has since been closed this does + *not* raise OSError (even if the wrapped syscall does) + """ + raise NotImplementedError + + def modify(self, fileobj, events, data=None): + """Change a registered file object monitored events or attached data. + + Parameters: + fileobj -- file object or file descriptor + events -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE) + data -- attached data + + Returns: + SelectorKey instance + + Raises: + Anything that unregister() or register() raises + """ + self.unregister(fileobj) + return self.register(fileobj, events, data) + + @abstractmethod + def select(self, timeout=None): + """Perform the actual selection, until some monitored file objects are + ready or a timeout expires. + + Parameters: + timeout -- if timeout > 0, this specifies the maximum wait time, in + seconds + if timeout <= 0, the select() call won't block, and will + report the currently ready file objects + if timeout is None, select() will block until a monitored + file object becomes ready + + Returns: + list of (key, events) for ready file objects + `events` is a bitwise mask of EVENT_READ|EVENT_WRITE + """ + raise NotImplementedError + + def close(self): + """Close the selector. + + This must be called to make sure that any underlying resource is freed. + """ + pass + + def get_key(self, fileobj): + """Return the key associated to a registered file object. + + Returns: + SelectorKey for this file object + """ + mapping = self.get_map() + if mapping is None: + raise RuntimeError('Selector is closed') + try: + return mapping[fileobj] + except KeyError: + raise KeyError("{!r} is not registered".format(fileobj)) from None + + @abstractmethod + def get_map(self): + """Return a mapping of file objects to selector keys.""" + raise NotImplementedError + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + +class _BaseSelectorImpl(BaseSelector): + """Base selector implementation.""" + + def __init__(self): + # this maps file descriptors to keys + self._fd_to_key = {} + # read-only mapping returned by get_map() + self._map = _SelectorMapping(self) + + def _fileobj_lookup(self, fileobj): + """Return a file descriptor from a file object. + + This wraps _fileobj_to_fd() to do an exhaustive search in case + the object is invalid but we still have it in our map. This + is used by unregister() so we can unregister an object that + was previously registered even if it is closed. It is also + used by _SelectorMapping. + """ + try: + return _fileobj_to_fd(fileobj) + except ValueError: + # Do an exhaustive search. + for key in self._fd_to_key.values(): + if key.fileobj is fileobj: + return key.fd + # Raise ValueError after all. + raise + + def register(self, fileobj, events, data=None): + if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)): + raise ValueError("Invalid events: {!r}".format(events)) + + key = SelectorKey(fileobj, self._fileobj_lookup(fileobj), events, data) + + if key.fd in self._fd_to_key: + raise KeyError("{!r} (FD {}) is already registered" + .format(fileobj, key.fd)) + + self._fd_to_key[key.fd] = key + return key + + def unregister(self, fileobj): + try: + key = self._fd_to_key.pop(self._fileobj_lookup(fileobj)) + except KeyError: + raise KeyError("{!r} is not registered".format(fileobj)) from None + return key + + def modify(self, fileobj, events, data=None): + try: + key = self._fd_to_key[self._fileobj_lookup(fileobj)] + except KeyError: + raise KeyError("{!r} is not registered".format(fileobj)) from None + if events != key.events: + self.unregister(fileobj) + key = self.register(fileobj, events, data) + elif data != key.data: + # Use a shortcut to update the data. + key = key._replace(data=data) + self._fd_to_key[key.fd] = key + return key + + def close(self): + self._fd_to_key.clear() + self._map = None + + def get_map(self): + return self._map + + def _key_from_fd(self, fd): + """Return the key associated to a given file descriptor. + + Parameters: + fd -- file descriptor + + Returns: + corresponding key, or None if not found + """ + try: + return self._fd_to_key[fd] + except KeyError: + return None + + +class SelectSelector(_BaseSelectorImpl): + """Select-based selector.""" + + def __init__(self): + super().__init__() + self._readers = set() + self._writers = set() + + def register(self, fileobj, events, data=None): + key = super().register(fileobj, events, data) + if events & EVENT_READ: + self._readers.add(key.fd) + if events & EVENT_WRITE: + self._writers.add(key.fd) + return key + + def unregister(self, fileobj): + key = super().unregister(fileobj) + self._readers.discard(key.fd) + self._writers.discard(key.fd) + return key + + if sys.platform == 'win32': + def _select(self, r, w, _, timeout=None): + r, w, x = select.select(r, w, w, timeout) + return r, w + x, [] + else: + _select = select.select + + def select(self, timeout=None): + timeout = None if timeout is None else max(timeout, 0) + ready = [] + try: + r, w, _ = self._select(self._readers, self._writers, [], timeout) + except InterruptedError: + return ready + r = set(r) + w = set(w) + for fd in r | w: + events = 0 + if fd in r: + events |= EVENT_READ + if fd in w: + events |= EVENT_WRITE + + key = self._key_from_fd(fd) + if key: + ready.append((key, events & key.events)) + return ready + + +class _PollLikeSelector(_BaseSelectorImpl): + """Base class shared between poll, epoll and devpoll selectors.""" + _selector_cls = None + _EVENT_READ = None + _EVENT_WRITE = None + + def __init__(self): + super().__init__() + self._selector = self._selector_cls() + + def register(self, fileobj, events, data=None): + key = super().register(fileobj, events, data) + poller_events = 0 + if events & EVENT_READ: + poller_events |= self._EVENT_READ + if events & EVENT_WRITE: + poller_events |= self._EVENT_WRITE + try: + self._selector.register(key.fd, poller_events) + except: + super().unregister(fileobj) + raise + return key + + def unregister(self, fileobj): + key = super().unregister(fileobj) + try: + self._selector.unregister(key.fd) + except OSError: + # This can happen if the FD was closed since it + # was registered. + pass + return key + + def modify(self, fileobj, events, data=None): + try: + key = self._fd_to_key[self._fileobj_lookup(fileobj)] + except KeyError: + raise KeyError(f"{fileobj!r} is not registered") from None + + changed = False + if events != key.events: + selector_events = 0 + if events & EVENT_READ: + selector_events |= self._EVENT_READ + if events & EVENT_WRITE: + selector_events |= self._EVENT_WRITE + try: + self._selector.modify(key.fd, selector_events) + except: + super().unregister(fileobj) + raise + changed = True + if data != key.data: + changed = True + + if changed: + key = key._replace(events=events, data=data) + self._fd_to_key[key.fd] = key + return key + + def select(self, timeout=None): + # This is shared between poll() and epoll(). + # epoll() has a different signature and handling of timeout parameter. + if timeout is None: + timeout = None + elif timeout <= 0: + timeout = 0 + else: + # poll() has a resolution of 1 millisecond, round away from + # zero to wait *at least* timeout seconds. + timeout = math.ceil(timeout * 1e3) + ready = [] + try: + fd_event_list = self._selector.poll(timeout) + except InterruptedError: + return ready + for fd, event in fd_event_list: + events = 0 + if event & ~self._EVENT_READ: + events |= EVENT_WRITE + if event & ~self._EVENT_WRITE: + events |= EVENT_READ + + key = self._key_from_fd(fd) + if key: + ready.append((key, events & key.events)) + return ready + + +if hasattr(select, 'poll'): + + class PollSelector(_PollLikeSelector): + """Poll-based selector.""" + _selector_cls = select.poll + _EVENT_READ = select.POLLIN + _EVENT_WRITE = select.POLLOUT + + +if hasattr(select, 'epoll'): + + class EpollSelector(_PollLikeSelector): + """Epoll-based selector.""" + _selector_cls = select.epoll + _EVENT_READ = select.EPOLLIN + _EVENT_WRITE = select.EPOLLOUT + + def fileno(self): + return self._selector.fileno() + + def select(self, timeout=None): + if timeout is None: + timeout = -1 + elif timeout <= 0: + timeout = 0 + else: + # epoll_wait() has a resolution of 1 millisecond, round away + # from zero to wait *at least* timeout seconds. + timeout = math.ceil(timeout * 1e3) * 1e-3 + + # epoll_wait() expects `maxevents` to be greater than zero; + # we want to make sure that `select()` can be called when no + # FD is registered. + max_ev = max(len(self._fd_to_key), 1) + + ready = [] + try: + fd_event_list = self._selector.poll(timeout, max_ev) + except InterruptedError: + return ready + for fd, event in fd_event_list: + events = 0 + if event & ~select.EPOLLIN: + events |= EVENT_WRITE + if event & ~select.EPOLLOUT: + events |= EVENT_READ + + key = self._key_from_fd(fd) + if key: + ready.append((key, events & key.events)) + return ready + + def close(self): + self._selector.close() + super().close() + + +if hasattr(select, 'devpoll'): + + class DevpollSelector(_PollLikeSelector): + """Solaris /dev/poll selector.""" + _selector_cls = select.devpoll + _EVENT_READ = select.POLLIN + _EVENT_WRITE = select.POLLOUT + + def fileno(self): + return self._selector.fileno() + + def close(self): + self._selector.close() + super().close() + + +if hasattr(select, 'kqueue'): + + class KqueueSelector(_BaseSelectorImpl): + """Kqueue-based selector.""" + + def __init__(self): + super().__init__() + self._selector = select.kqueue() + + def fileno(self): + return self._selector.fileno() + + def register(self, fileobj, events, data=None): + key = super().register(fileobj, events, data) + try: + if events & EVENT_READ: + kev = select.kevent(key.fd, select.KQ_FILTER_READ, + select.KQ_EV_ADD) + self._selector.control([kev], 0, 0) + if events & EVENT_WRITE: + kev = select.kevent(key.fd, select.KQ_FILTER_WRITE, + select.KQ_EV_ADD) + self._selector.control([kev], 0, 0) + except: + super().unregister(fileobj) + raise + return key + + def unregister(self, fileobj): + key = super().unregister(fileobj) + if key.events & EVENT_READ: + kev = select.kevent(key.fd, select.KQ_FILTER_READ, + select.KQ_EV_DELETE) + try: + self._selector.control([kev], 0, 0) + except OSError: + # This can happen if the FD was closed since it + # was registered. + pass + if key.events & EVENT_WRITE: + kev = select.kevent(key.fd, select.KQ_FILTER_WRITE, + select.KQ_EV_DELETE) + try: + self._selector.control([kev], 0, 0) + except OSError: + # See comment above. + pass + return key + + def select(self, timeout=None): + timeout = None if timeout is None else max(timeout, 0) + # If max_ev is 0, kqueue will ignore the timeout. For consistent + # behavior with the other selector classes, we prevent that here + # (using max). See https://bugs.python.org/issue29255 + max_ev = max(len(self._fd_to_key), 1) + ready = [] + try: + kev_list = self._selector.control(None, max_ev, timeout) + except InterruptedError: + return ready + for kev in kev_list: + fd = kev.ident + flag = kev.filter + events = 0 + if flag == select.KQ_FILTER_READ: + events |= EVENT_READ + if flag == select.KQ_FILTER_WRITE: + events |= EVENT_WRITE + + key = self._key_from_fd(fd) + if key: + ready.append((key, events & key.events)) + return ready + + def close(self): + self._selector.close() + super().close() + + +def _can_use(method): + """Check if we can use the selector depending upon the + operating system. """ + # Implementation based upon https://github.com/sethmlarson/selectors2/blob/master/selectors2.py + selector = getattr(select, method, None) + if selector is None: + # select module does not implement method + return False + # check if the OS and Kernel actually support the method. Call may fail with + # OSError: [Errno 38] Function not implemented + try: + selector_obj = selector() + if method == 'poll': + # check that poll actually works + selector_obj.poll(0) + else: + # close epoll, kqueue, and devpoll fd + selector_obj.close() + return True + except OSError: + return False + + +# Choose the best implementation, roughly: +# epoll|kqueue|devpoll > poll > select. +# select() also can't accept a FD > FD_SETSIZE (usually around 1024) +if _can_use('kqueue'): + DefaultSelector = KqueueSelector +elif _can_use('epoll'): + DefaultSelector = EpollSelector +elif _can_use('devpoll'): + DefaultSelector = DevpollSelector +elif _can_use('poll'): + DefaultSelector = PollSelector +else: + DefaultSelector = SelectSelector diff --git a/lib/python3.10/shelve.py b/lib/python3.10/shelve.py new file mode 100644 index 0000000000000000000000000000000000000000..e053c397345a07e69dfa8f72a3d5ebbede86a883 --- /dev/null +++ b/lib/python3.10/shelve.py @@ -0,0 +1,243 @@ +"""Manage shelves of pickled objects. + +A "shelf" is a persistent, dictionary-like object. The difference +with dbm databases is that the values (not the keys!) in a shelf can +be essentially arbitrary Python objects -- anything that the "pickle" +module can handle. This includes most class instances, recursive data +types, and objects containing lots of shared sub-objects. The keys +are ordinary strings. + +To summarize the interface (key is a string, data is an arbitrary +object): + + import shelve + d = shelve.open(filename) # open, with (g)dbm filename -- no suffix + + d[key] = data # store data at key (overwrites old data if + # using an existing key) + data = d[key] # retrieve a COPY of the data at key (raise + # KeyError if no such key) -- NOTE that this + # access returns a *copy* of the entry! + del d[key] # delete data stored at key (raises KeyError + # if no such key) + flag = key in d # true if the key exists + list = d.keys() # a list of all existing keys (slow!) + + d.close() # close it + +Dependent on the implementation, closing a persistent dictionary may +or may not be necessary to flush changes to disk. + +Normally, d[key] returns a COPY of the entry. This needs care when +mutable entries are mutated: for example, if d[key] is a list, + d[key].append(anitem) +does NOT modify the entry d[key] itself, as stored in the persistent +mapping -- it only modifies the copy, which is then immediately +discarded, so that the append has NO effect whatsoever. To append an +item to d[key] in a way that will affect the persistent mapping, use: + data = d[key] + data.append(anitem) + d[key] = data + +To avoid the problem with mutable entries, you may pass the keyword +argument writeback=True in the call to shelve.open. When you use: + d = shelve.open(filename, writeback=True) +then d keeps a cache of all entries you access, and writes them all back +to the persistent mapping when you call d.close(). This ensures that +such usage as d[key].append(anitem) works as intended. + +However, using keyword argument writeback=True may consume vast amount +of memory for the cache, and it may make d.close() very slow, if you +access many of d's entries after opening it in this way: d has no way to +check which of the entries you access are mutable and/or which ones you +actually mutate, so it must cache, and write back at close, all of the +entries that you access. You can call d.sync() to write back all the +entries in the cache, and empty the cache (d.sync() also synchronizes +the persistent dictionary on disk, if feasible). +""" + +from pickle import DEFAULT_PROTOCOL, Pickler, Unpickler +from io import BytesIO + +import collections.abc + +__all__ = ["Shelf", "BsdDbShelf", "DbfilenameShelf", "open"] + +class _ClosedDict(collections.abc.MutableMapping): + 'Marker for a closed dict. Access attempts raise a ValueError.' + + def closed(self, *args): + raise ValueError('invalid operation on closed shelf') + __iter__ = __len__ = __getitem__ = __setitem__ = __delitem__ = keys = closed + + def __repr__(self): + return '' + + +class Shelf(collections.abc.MutableMapping): + """Base class for shelf implementations. + + This is initialized with a dictionary-like object. + See the module's __doc__ string for an overview of the interface. + """ + + def __init__(self, dict, protocol=None, writeback=False, + keyencoding="utf-8"): + self.dict = dict + if protocol is None: + protocol = DEFAULT_PROTOCOL + self._protocol = protocol + self.writeback = writeback + self.cache = {} + self.keyencoding = keyencoding + + def __iter__(self): + for k in self.dict.keys(): + yield k.decode(self.keyencoding) + + def __len__(self): + return len(self.dict) + + def __contains__(self, key): + return key.encode(self.keyencoding) in self.dict + + def get(self, key, default=None): + if key.encode(self.keyencoding) in self.dict: + return self[key] + return default + + def __getitem__(self, key): + try: + value = self.cache[key] + except KeyError: + f = BytesIO(self.dict[key.encode(self.keyencoding)]) + value = Unpickler(f).load() + if self.writeback: + self.cache[key] = value + return value + + def __setitem__(self, key, value): + if self.writeback: + self.cache[key] = value + f = BytesIO() + p = Pickler(f, self._protocol) + p.dump(value) + self.dict[key.encode(self.keyencoding)] = f.getvalue() + + def __delitem__(self, key): + del self.dict[key.encode(self.keyencoding)] + try: + del self.cache[key] + except KeyError: + pass + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.close() + + def close(self): + if self.dict is None: + return + try: + self.sync() + try: + self.dict.close() + except AttributeError: + pass + finally: + # Catch errors that may happen when close is called from __del__ + # because CPython is in interpreter shutdown. + try: + self.dict = _ClosedDict() + except: + self.dict = None + + def __del__(self): + if not hasattr(self, 'writeback'): + # __init__ didn't succeed, so don't bother closing + # see http://bugs.python.org/issue1339007 for details + return + self.close() + + def sync(self): + if self.writeback and self.cache: + self.writeback = False + for key, entry in self.cache.items(): + self[key] = entry + self.writeback = True + self.cache = {} + if hasattr(self.dict, 'sync'): + self.dict.sync() + + +class BsdDbShelf(Shelf): + """Shelf implementation using the "BSD" db interface. + + This adds methods first(), next(), previous(), last() and + set_location() that have no counterpart in [g]dbm databases. + + The actual database must be opened using one of the "bsddb" + modules "open" routines (i.e. bsddb.hashopen, bsddb.btopen or + bsddb.rnopen) and passed to the constructor. + + See the module's __doc__ string for an overview of the interface. + """ + + def __init__(self, dict, protocol=None, writeback=False, + keyencoding="utf-8"): + Shelf.__init__(self, dict, protocol, writeback, keyencoding) + + def set_location(self, key): + (key, value) = self.dict.set_location(key) + f = BytesIO(value) + return (key.decode(self.keyencoding), Unpickler(f).load()) + + def next(self): + (key, value) = next(self.dict) + f = BytesIO(value) + return (key.decode(self.keyencoding), Unpickler(f).load()) + + def previous(self): + (key, value) = self.dict.previous() + f = BytesIO(value) + return (key.decode(self.keyencoding), Unpickler(f).load()) + + def first(self): + (key, value) = self.dict.first() + f = BytesIO(value) + return (key.decode(self.keyencoding), Unpickler(f).load()) + + def last(self): + (key, value) = self.dict.last() + f = BytesIO(value) + return (key.decode(self.keyencoding), Unpickler(f).load()) + + +class DbfilenameShelf(Shelf): + """Shelf implementation using the "dbm" generic dbm interface. + + This is initialized with the filename for the dbm database. + See the module's __doc__ string for an overview of the interface. + """ + + def __init__(self, filename, flag='c', protocol=None, writeback=False): + import dbm + Shelf.__init__(self, dbm.open(filename, flag), protocol, writeback) + + +def open(filename, flag='c', protocol=None, writeback=False): + """Open a persistent dictionary for reading and writing. + + The filename parameter is the base filename for the underlying + database. As a side-effect, an extension may be added to the + filename and more than one file may be created. The optional flag + parameter has the same interpretation as the flag parameter of + dbm.open(). The optional protocol parameter specifies the + version of the pickle protocol. + + See the module's __doc__ string for an overview of the interface. + """ + + return DbfilenameShelf(filename, flag, protocol, writeback) diff --git a/lib/python3.10/shlex.py b/lib/python3.10/shlex.py new file mode 100644 index 0000000000000000000000000000000000000000..4801a6c1d47bd9e0a8ada16089221c8237b777d5 --- /dev/null +++ b/lib/python3.10/shlex.py @@ -0,0 +1,350 @@ +"""A lexical analyzer class for simple shell-like syntaxes.""" + +# Module and documentation by Eric S. Raymond, 21 Dec 1998 +# Input stacking and error message cleanup added by ESR, March 2000 +# push_source() and pop_source() made explicit by ESR, January 2001. +# Posix compliance, split(), string arguments, and +# iterator interface by Gustavo Niemeyer, April 2003. +# changes to tokenize more like Posix shells by Vinay Sajip, July 2016. + +import os +import re +import sys +from collections import deque + +from io import StringIO + +__all__ = ["shlex", "split", "quote", "join"] + +class shlex: + "A lexical analyzer class for simple shell-like syntaxes." + def __init__(self, instream=None, infile=None, posix=False, + punctuation_chars=False): + if isinstance(instream, str): + instream = StringIO(instream) + if instream is not None: + self.instream = instream + self.infile = infile + else: + self.instream = sys.stdin + self.infile = None + self.posix = posix + if posix: + self.eof = None + else: + self.eof = '' + self.commenters = '#' + self.wordchars = ('abcdfeghijklmnopqrstuvwxyz' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_') + if self.posix: + self.wordchars += ('ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ' + 'ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖØÙÚÛÜÃÞ') + self.whitespace = ' \t\r\n' + self.whitespace_split = False + self.quotes = '\'"' + self.escape = '\\' + self.escapedquotes = '"' + self.state = ' ' + self.pushback = deque() + self.lineno = 1 + self.debug = 0 + self.token = '' + self.filestack = deque() + self.source = None + if not punctuation_chars: + punctuation_chars = '' + elif punctuation_chars is True: + punctuation_chars = '();<>|&' + self._punctuation_chars = punctuation_chars + if punctuation_chars: + # _pushback_chars is a push back queue used by lookahead logic + self._pushback_chars = deque() + # these chars added because allowed in file names, args, wildcards + self.wordchars += '~-./*?=' + #remove any punctuation chars from wordchars + t = self.wordchars.maketrans(dict.fromkeys(punctuation_chars)) + self.wordchars = self.wordchars.translate(t) + + @property + def punctuation_chars(self): + return self._punctuation_chars + + def push_token(self, tok): + "Push a token onto the stack popped by the get_token method" + if self.debug >= 1: + print("shlex: pushing token " + repr(tok)) + self.pushback.appendleft(tok) + + def push_source(self, newstream, newfile=None): + "Push an input source onto the lexer's input source stack." + if isinstance(newstream, str): + newstream = StringIO(newstream) + self.filestack.appendleft((self.infile, self.instream, self.lineno)) + self.infile = newfile + self.instream = newstream + self.lineno = 1 + if self.debug: + if newfile is not None: + print('shlex: pushing to file %s' % (self.infile,)) + else: + print('shlex: pushing to stream %s' % (self.instream,)) + + def pop_source(self): + "Pop the input source stack." + self.instream.close() + (self.infile, self.instream, self.lineno) = self.filestack.popleft() + if self.debug: + print('shlex: popping to %s, line %d' \ + % (self.instream, self.lineno)) + self.state = ' ' + + def get_token(self): + "Get a token from the input stream (or from stack if it's nonempty)" + if self.pushback: + tok = self.pushback.popleft() + if self.debug >= 1: + print("shlex: popping token " + repr(tok)) + return tok + # No pushback. Get a token. + raw = self.read_token() + # Handle inclusions + if self.source is not None: + while raw == self.source: + spec = self.sourcehook(self.read_token()) + if spec: + (newfile, newstream) = spec + self.push_source(newstream, newfile) + raw = self.get_token() + # Maybe we got EOF instead? + while raw == self.eof: + if not self.filestack: + return self.eof + else: + self.pop_source() + raw = self.get_token() + # Neither inclusion nor EOF + if self.debug >= 1: + if raw != self.eof: + print("shlex: token=" + repr(raw)) + else: + print("shlex: token=EOF") + return raw + + def read_token(self): + quoted = False + escapedstate = ' ' + while True: + if self.punctuation_chars and self._pushback_chars: + nextchar = self._pushback_chars.pop() + else: + nextchar = self.instream.read(1) + if nextchar == '\n': + self.lineno += 1 + if self.debug >= 3: + print("shlex: in state %r I see character: %r" % (self.state, + nextchar)) + if self.state is None: + self.token = '' # past end of file + break + elif self.state == ' ': + if not nextchar: + self.state = None # end of file + break + elif nextchar in self.whitespace: + if self.debug >= 2: + print("shlex: I see whitespace in whitespace state") + if self.token or (self.posix and quoted): + break # emit current token + else: + continue + elif nextchar in self.commenters: + self.instream.readline() + self.lineno += 1 + elif self.posix and nextchar in self.escape: + escapedstate = 'a' + self.state = nextchar + elif nextchar in self.wordchars: + self.token = nextchar + self.state = 'a' + elif nextchar in self.punctuation_chars: + self.token = nextchar + self.state = 'c' + elif nextchar in self.quotes: + if not self.posix: + self.token = nextchar + self.state = nextchar + elif self.whitespace_split: + self.token = nextchar + self.state = 'a' + else: + self.token = nextchar + if self.token or (self.posix and quoted): + break # emit current token + else: + continue + elif self.state in self.quotes: + quoted = True + if not nextchar: # end of file + if self.debug >= 2: + print("shlex: I see EOF in quotes state") + # XXX what error should be raised here? + raise ValueError("No closing quotation") + if nextchar == self.state: + if not self.posix: + self.token += nextchar + self.state = ' ' + break + else: + self.state = 'a' + elif (self.posix and nextchar in self.escape and self.state + in self.escapedquotes): + escapedstate = self.state + self.state = nextchar + else: + self.token += nextchar + elif self.state in self.escape: + if not nextchar: # end of file + if self.debug >= 2: + print("shlex: I see EOF in escape state") + # XXX what error should be raised here? + raise ValueError("No escaped character") + # In posix shells, only the quote itself or the escape + # character may be escaped within quotes. + if (escapedstate in self.quotes and + nextchar != self.state and nextchar != escapedstate): + self.token += self.state + self.token += nextchar + self.state = escapedstate + elif self.state in ('a', 'c'): + if not nextchar: + self.state = None # end of file + break + elif nextchar in self.whitespace: + if self.debug >= 2: + print("shlex: I see whitespace in word state") + self.state = ' ' + if self.token or (self.posix and quoted): + break # emit current token + else: + continue + elif nextchar in self.commenters: + self.instream.readline() + self.lineno += 1 + if self.posix: + self.state = ' ' + if self.token or (self.posix and quoted): + break # emit current token + else: + continue + elif self.state == 'c': + if nextchar in self.punctuation_chars: + self.token += nextchar + else: + if nextchar not in self.whitespace: + self._pushback_chars.append(nextchar) + self.state = ' ' + break + elif self.posix and nextchar in self.quotes: + self.state = nextchar + elif self.posix and nextchar in self.escape: + escapedstate = 'a' + self.state = nextchar + elif (nextchar in self.wordchars or nextchar in self.quotes + or (self.whitespace_split and + nextchar not in self.punctuation_chars)): + self.token += nextchar + else: + if self.punctuation_chars: + self._pushback_chars.append(nextchar) + else: + self.pushback.appendleft(nextchar) + if self.debug >= 2: + print("shlex: I see punctuation in word state") + self.state = ' ' + if self.token or (self.posix and quoted): + break # emit current token + else: + continue + result = self.token + self.token = '' + if self.posix and not quoted and result == '': + result = None + if self.debug > 1: + if result: + print("shlex: raw token=" + repr(result)) + else: + print("shlex: raw token=EOF") + return result + + def sourcehook(self, newfile): + "Hook called on a filename to be sourced." + if newfile[0] == '"': + newfile = newfile[1:-1] + # This implements cpp-like semantics for relative-path inclusion. + if isinstance(self.infile, str) and not os.path.isabs(newfile): + newfile = os.path.join(os.path.dirname(self.infile), newfile) + return (newfile, open(newfile, "r")) + + def error_leader(self, infile=None, lineno=None): + "Emit a C-compiler-like, Emacs-friendly error-message leader." + if infile is None: + infile = self.infile + if lineno is None: + lineno = self.lineno + return "\"%s\", line %d: " % (infile, lineno) + + def __iter__(self): + return self + + def __next__(self): + token = self.get_token() + if token == self.eof: + raise StopIteration + return token + +def split(s, comments=False, posix=True): + """Split the string *s* using shell-like syntax.""" + if s is None: + import warnings + warnings.warn("Passing None for 's' to shlex.split() is deprecated.", + DeprecationWarning, stacklevel=2) + lex = shlex(s, posix=posix) + lex.whitespace_split = True + if not comments: + lex.commenters = '' + return list(lex) + + +def join(split_command): + """Return a shell-escaped string from *split_command*.""" + return ' '.join(quote(arg) for arg in split_command) + + +_find_unsafe = re.compile(r'[^\w@%+=:,./-]', re.ASCII).search + +def quote(s): + """Return a shell-escaped version of the string *s*.""" + if not s: + return "''" + if _find_unsafe(s) is None: + return s + + # use single quotes, and put single quotes into double quotes + # the string $'b is then quoted as '$'"'"'b' + return "'" + s.replace("'", "'\"'\"'") + "'" + + +def _print_tokens(lexer): + while 1: + tt = lexer.get_token() + if not tt: + break + print("Token: " + repr(tt)) + +if __name__ == '__main__': + if len(sys.argv) == 1: + _print_tokens(shlex()) + else: + fn = sys.argv[1] + with open(fn) as f: + _print_tokens(shlex(f, fn)) diff --git a/lib/python3.10/shutil.py b/lib/python3.10/shutil.py new file mode 100644 index 0000000000000000000000000000000000000000..482ce95a7b23caff1404dcf4b78a494895cb1f03 --- /dev/null +++ b/lib/python3.10/shutil.py @@ -0,0 +1,1517 @@ +"""Utility functions for copying and archiving files and directory trees. + +XXX The functions here don't copy the resource fork or other metadata on Mac. + +""" + +import os +import sys +import stat +import fnmatch +import collections +import errno + +try: + import zlib + del zlib + _ZLIB_SUPPORTED = True +except ImportError: + _ZLIB_SUPPORTED = False + +try: + import bz2 + del bz2 + _BZ2_SUPPORTED = True +except ImportError: + _BZ2_SUPPORTED = False + +try: + import lzma + del lzma + _LZMA_SUPPORTED = True +except ImportError: + _LZMA_SUPPORTED = False + +_WINDOWS = os.name == 'nt' +posix = nt = None +if os.name == 'posix': + import posix +elif _WINDOWS: + import nt + +COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 64 * 1024 +_USE_CP_SENDFILE = hasattr(os, "sendfile") and sys.platform.startswith("linux") +_HAS_FCOPYFILE = posix and hasattr(posix, "_fcopyfile") # macOS + +# CMD defaults in Windows 10 +_WIN_DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC" + +__all__ = ["copyfileobj", "copyfile", "copymode", "copystat", "copy", "copy2", + "copytree", "move", "rmtree", "Error", "SpecialFileError", + "ExecError", "make_archive", "get_archive_formats", + "register_archive_format", "unregister_archive_format", + "get_unpack_formats", "register_unpack_format", + "unregister_unpack_format", "unpack_archive", + "ignore_patterns", "chown", "which", "get_terminal_size", + "SameFileError"] + # disk_usage is added later, if available on the platform + +class Error(OSError): + pass + +class SameFileError(Error): + """Raised when source and destination are the same file.""" + +class SpecialFileError(OSError): + """Raised when trying to do a kind of operation (e.g. copying) which is + not supported on a special file (e.g. a named pipe)""" + +class ExecError(OSError): + """Raised when a command could not be executed""" + +class ReadError(OSError): + """Raised when an archive cannot be read""" + +class RegistryError(Exception): + """Raised when a registry operation with the archiving + and unpacking registries fails""" + +class _GiveupOnFastCopy(Exception): + """Raised as a signal to fallback on using raw read()/write() + file copy when fast-copy functions fail to do so. + """ + +def _fastcopy_fcopyfile(fsrc, fdst, flags): + """Copy a regular file content or metadata by using high-performance + fcopyfile(3) syscall (macOS). + """ + try: + infd = fsrc.fileno() + outfd = fdst.fileno() + except Exception as err: + raise _GiveupOnFastCopy(err) # not a regular file + + try: + posix._fcopyfile(infd, outfd, flags) + except OSError as err: + err.filename = fsrc.name + err.filename2 = fdst.name + if err.errno in {errno.EINVAL, errno.ENOTSUP}: + raise _GiveupOnFastCopy(err) + else: + raise err from None + +def _fastcopy_sendfile(fsrc, fdst): + """Copy data from one regular mmap-like fd to another by using + high-performance sendfile(2) syscall. + This should work on Linux >= 2.6.33 only. + """ + # Note: copyfileobj() is left alone in order to not introduce any + # unexpected breakage. Possible risks by using zero-copy calls + # in copyfileobj() are: + # - fdst cannot be open in "a"(ppend) mode + # - fsrc and fdst may be open in "t"(ext) mode + # - fsrc may be a BufferedReader (which hides unread data in a buffer), + # GzipFile (which decompresses data), HTTPResponse (which decodes + # chunks). + # - possibly others (e.g. encrypted fs/partition?) + global _USE_CP_SENDFILE + try: + infd = fsrc.fileno() + outfd = fdst.fileno() + except Exception as err: + raise _GiveupOnFastCopy(err) # not a regular file + + # Hopefully the whole file will be copied in a single call. + # sendfile() is called in a loop 'till EOF is reached (0 return) + # so a bufsize smaller or bigger than the actual file size + # should not make any difference, also in case the file content + # changes while being copied. + try: + blocksize = max(os.fstat(infd).st_size, 2 ** 23) # min 8MiB + except OSError: + blocksize = 2 ** 27 # 128MiB + # On 32-bit architectures truncate to 1GiB to avoid OverflowError, + # see bpo-38319. + if sys.maxsize < 2 ** 32: + blocksize = min(blocksize, 2 ** 30) + + offset = 0 + while True: + try: + sent = os.sendfile(outfd, infd, offset, blocksize) + except OSError as err: + # ...in oder to have a more informative exception. + err.filename = fsrc.name + err.filename2 = fdst.name + + if err.errno == errno.ENOTSOCK: + # sendfile() on this platform (probably Linux < 2.6.33) + # does not support copies between regular files (only + # sockets). + _USE_CP_SENDFILE = False + raise _GiveupOnFastCopy(err) + + if err.errno == errno.ENOSPC: # filesystem is full + raise err from None + + # Give up on first call and if no data was copied. + if offset == 0 and os.lseek(outfd, 0, os.SEEK_CUR) == 0: + raise _GiveupOnFastCopy(err) + + raise err + else: + if sent == 0: + break # EOF + offset += sent + +def _copyfileobj_readinto(fsrc, fdst, length=COPY_BUFSIZE): + """readinto()/memoryview() based variant of copyfileobj(). + *fsrc* must support readinto() method and both files must be + open in binary mode. + """ + # Localize variable access to minimize overhead. + fsrc_readinto = fsrc.readinto + fdst_write = fdst.write + with memoryview(bytearray(length)) as mv: + while True: + n = fsrc_readinto(mv) + if not n: + break + elif n < length: + with mv[:n] as smv: + fdst.write(smv) + else: + fdst_write(mv) + +def copyfileobj(fsrc, fdst, length=0): + """copy data from file-like object fsrc to file-like object fdst""" + # Localize variable access to minimize overhead. + if not length: + length = COPY_BUFSIZE + fsrc_read = fsrc.read + fdst_write = fdst.write + while True: + buf = fsrc_read(length) + if not buf: + break + fdst_write(buf) + +def _samefile(src, dst): + # Macintosh, Unix. + if isinstance(src, os.DirEntry) and hasattr(os.path, 'samestat'): + try: + return os.path.samestat(src.stat(), os.stat(dst)) + except OSError: + return False + + if hasattr(os.path, 'samefile'): + try: + return os.path.samefile(src, dst) + except OSError: + return False + + # All other platforms: check for same pathname. + return (os.path.normcase(os.path.abspath(src)) == + os.path.normcase(os.path.abspath(dst))) + +def _stat(fn): + return fn.stat() if isinstance(fn, os.DirEntry) else os.stat(fn) + +def _islink(fn): + return fn.is_symlink() if isinstance(fn, os.DirEntry) else os.path.islink(fn) + +def copyfile(src, dst, *, follow_symlinks=True): + """Copy data from src to dst in the most efficient way possible. + + If follow_symlinks is not set and src is a symbolic link, a new + symlink will be created instead of copying the file it points to. + + """ + sys.audit("shutil.copyfile", src, dst) + + if _samefile(src, dst): + raise SameFileError("{!r} and {!r} are the same file".format(src, dst)) + + file_size = 0 + for i, fn in enumerate([src, dst]): + try: + st = _stat(fn) + except OSError: + # File most likely does not exist + pass + else: + # XXX What about other special files? (sockets, devices...) + if stat.S_ISFIFO(st.st_mode): + fn = fn.path if isinstance(fn, os.DirEntry) else fn + raise SpecialFileError("`%s` is a named pipe" % fn) + if _WINDOWS and i == 0: + file_size = st.st_size + + if not follow_symlinks and _islink(src): + os.symlink(os.readlink(src), dst) + else: + with open(src, 'rb') as fsrc: + try: + with open(dst, 'wb') as fdst: + # macOS + if _HAS_FCOPYFILE: + try: + _fastcopy_fcopyfile(fsrc, fdst, posix._COPYFILE_DATA) + return dst + except _GiveupOnFastCopy: + pass + # Linux + elif _USE_CP_SENDFILE: + try: + _fastcopy_sendfile(fsrc, fdst) + return dst + except _GiveupOnFastCopy: + pass + # Windows, see: + # https://github.com/python/cpython/pull/7160#discussion_r195405230 + elif _WINDOWS and file_size > 0: + _copyfileobj_readinto(fsrc, fdst, min(file_size, COPY_BUFSIZE)) + return dst + + copyfileobj(fsrc, fdst) + + # Issue 43219, raise a less confusing exception + except IsADirectoryError as e: + if not os.path.exists(dst): + raise FileNotFoundError(f'Directory does not exist: {dst}') from e + else: + raise + + return dst + +def copymode(src, dst, *, follow_symlinks=True): + """Copy mode bits from src to dst. + + If follow_symlinks is not set, symlinks aren't followed if and only + if both `src` and `dst` are symlinks. If `lchmod` isn't available + (e.g. Linux) this method does nothing. + + """ + sys.audit("shutil.copymode", src, dst) + + if not follow_symlinks and _islink(src) and os.path.islink(dst): + if hasattr(os, 'lchmod'): + stat_func, chmod_func = os.lstat, os.lchmod + else: + return + else: + stat_func, chmod_func = _stat, os.chmod + + st = stat_func(src) + chmod_func(dst, stat.S_IMODE(st.st_mode)) + +if hasattr(os, 'listxattr'): + def _copyxattr(src, dst, *, follow_symlinks=True): + """Copy extended filesystem attributes from `src` to `dst`. + + Overwrite existing attributes. + + If `follow_symlinks` is false, symlinks won't be followed. + + """ + + try: + names = os.listxattr(src, follow_symlinks=follow_symlinks) + except OSError as e: + if e.errno not in (errno.ENOTSUP, errno.ENODATA, errno.EINVAL): + raise + return + for name in names: + try: + value = os.getxattr(src, name, follow_symlinks=follow_symlinks) + os.setxattr(dst, name, value, follow_symlinks=follow_symlinks) + except OSError as e: + if e.errno not in (errno.EPERM, errno.ENOTSUP, errno.ENODATA, + errno.EINVAL): + raise +else: + def _copyxattr(*args, **kwargs): + pass + +def copystat(src, dst, *, follow_symlinks=True): + """Copy file metadata + + Copy the permission bits, last access time, last modification time, and + flags from `src` to `dst`. On Linux, copystat() also copies the "extended + attributes" where possible. The file contents, owner, and group are + unaffected. `src` and `dst` are path-like objects or path names given as + strings. + + If the optional flag `follow_symlinks` is not set, symlinks aren't + followed if and only if both `src` and `dst` are symlinks. + """ + sys.audit("shutil.copystat", src, dst) + + def _nop(*args, ns=None, follow_symlinks=None): + pass + + # follow symlinks (aka don't not follow symlinks) + follow = follow_symlinks or not (_islink(src) and os.path.islink(dst)) + if follow: + # use the real function if it exists + def lookup(name): + return getattr(os, name, _nop) + else: + # use the real function only if it exists + # *and* it supports follow_symlinks + def lookup(name): + fn = getattr(os, name, _nop) + if fn in os.supports_follow_symlinks: + return fn + return _nop + + if isinstance(src, os.DirEntry): + st = src.stat(follow_symlinks=follow) + else: + st = lookup("stat")(src, follow_symlinks=follow) + mode = stat.S_IMODE(st.st_mode) + lookup("utime")(dst, ns=(st.st_atime_ns, st.st_mtime_ns), + follow_symlinks=follow) + # We must copy extended attributes before the file is (potentially) + # chmod()'ed read-only, otherwise setxattr() will error with -EACCES. + _copyxattr(src, dst, follow_symlinks=follow) + try: + lookup("chmod")(dst, mode, follow_symlinks=follow) + except NotImplementedError: + # if we got a NotImplementedError, it's because + # * follow_symlinks=False, + # * lchown() is unavailable, and + # * either + # * fchownat() is unavailable or + # * fchownat() doesn't implement AT_SYMLINK_NOFOLLOW. + # (it returned ENOSUP.) + # therefore we're out of options--we simply cannot chown the + # symlink. give up, suppress the error. + # (which is what shutil always did in this circumstance.) + pass + if hasattr(st, 'st_flags'): + try: + lookup("chflags")(dst, st.st_flags, follow_symlinks=follow) + except OSError as why: + for err in 'EOPNOTSUPP', 'ENOTSUP': + if hasattr(errno, err) and why.errno == getattr(errno, err): + break + else: + raise + +def copy(src, dst, *, follow_symlinks=True): + """Copy data and mode bits ("cp src dst"). Return the file's destination. + + The destination may be a directory. + + If follow_symlinks is false, symlinks won't be followed. This + resembles GNU's "cp -P src dst". + + If source and destination are the same file, a SameFileError will be + raised. + + """ + if os.path.isdir(dst): + dst = os.path.join(dst, os.path.basename(src)) + copyfile(src, dst, follow_symlinks=follow_symlinks) + copymode(src, dst, follow_symlinks=follow_symlinks) + return dst + +def copy2(src, dst, *, follow_symlinks=True): + """Copy data and metadata. Return the file's destination. + + Metadata is copied with copystat(). Please see the copystat function + for more information. + + The destination may be a directory. + + If follow_symlinks is false, symlinks won't be followed. This + resembles GNU's "cp -P src dst". + """ + if os.path.isdir(dst): + dst = os.path.join(dst, os.path.basename(src)) + copyfile(src, dst, follow_symlinks=follow_symlinks) + copystat(src, dst, follow_symlinks=follow_symlinks) + return dst + +def ignore_patterns(*patterns): + """Function that can be used as copytree() ignore parameter. + + Patterns is a sequence of glob-style patterns + that are used to exclude files""" + def _ignore_patterns(path, names): + ignored_names = [] + for pattern in patterns: + ignored_names.extend(fnmatch.filter(names, pattern)) + return set(ignored_names) + return _ignore_patterns + +def _copytree(entries, src, dst, symlinks, ignore, copy_function, + ignore_dangling_symlinks, dirs_exist_ok=False): + if ignore is not None: + ignored_names = ignore(os.fspath(src), [x.name for x in entries]) + else: + ignored_names = set() + + os.makedirs(dst, exist_ok=dirs_exist_ok) + errors = [] + use_srcentry = copy_function is copy2 or copy_function is copy + + for srcentry in entries: + if srcentry.name in ignored_names: + continue + srcname = os.path.join(src, srcentry.name) + dstname = os.path.join(dst, srcentry.name) + srcobj = srcentry if use_srcentry else srcname + try: + is_symlink = srcentry.is_symlink() + if is_symlink and os.name == 'nt': + # Special check for directory junctions, which appear as + # symlinks but we want to recurse. + lstat = srcentry.stat(follow_symlinks=False) + if lstat.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT: + is_symlink = False + if is_symlink: + linkto = os.readlink(srcname) + if symlinks: + # We can't just leave it to `copy_function` because legacy + # code with a custom `copy_function` may rely on copytree + # doing the right thing. + os.symlink(linkto, dstname) + copystat(srcobj, dstname, follow_symlinks=not symlinks) + else: + # ignore dangling symlink if the flag is on + if not os.path.exists(linkto) and ignore_dangling_symlinks: + continue + # otherwise let the copy occur. copy2 will raise an error + if srcentry.is_dir(): + copytree(srcobj, dstname, symlinks, ignore, + copy_function, ignore_dangling_symlinks, + dirs_exist_ok) + else: + copy_function(srcobj, dstname) + elif srcentry.is_dir(): + copytree(srcobj, dstname, symlinks, ignore, copy_function, + ignore_dangling_symlinks, dirs_exist_ok) + else: + # Will raise a SpecialFileError for unsupported file types + copy_function(srcobj, dstname) + # catch the Error from the recursive copytree so that we can + # continue with other files + except Error as err: + errors.extend(err.args[0]) + except OSError as why: + errors.append((srcname, dstname, str(why))) + try: + copystat(src, dst) + except OSError as why: + # Copying file access times may fail on Windows + if getattr(why, 'winerror', None) is None: + errors.append((src, dst, str(why))) + if errors: + raise Error(errors) + return dst + +def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, + ignore_dangling_symlinks=False, dirs_exist_ok=False): + """Recursively copy a directory tree and return the destination directory. + + If exception(s) occur, an Error is raised with a list of reasons. + + If the optional symlinks flag is true, symbolic links in the + source tree result in symbolic links in the destination tree; if + it is false, the contents of the files pointed to by symbolic + links are copied. If the file pointed by the symlink doesn't + exist, an exception will be added in the list of errors raised in + an Error exception at the end of the copy process. + + You can set the optional ignore_dangling_symlinks flag to true if you + want to silence this exception. Notice that this has no effect on + platforms that don't support os.symlink. + + The optional ignore argument is a callable. If given, it + is called with the `src` parameter, which is the directory + being visited by copytree(), and `names` which is the list of + `src` contents, as returned by os.listdir(): + + callable(src, names) -> ignored_names + + Since copytree() is called recursively, the callable will be + called once for each directory that is copied. It returns a + list of names relative to the `src` directory that should + not be copied. + + The optional copy_function argument is a callable that will be used + to copy each file. It will be called with the source path and the + destination path as arguments. By default, copy2() is used, but any + function that supports the same signature (like copy()) can be used. + + If dirs_exist_ok is false (the default) and `dst` already exists, a + `FileExistsError` is raised. If `dirs_exist_ok` is true, the copying + operation will continue if it encounters existing directories, and files + within the `dst` tree will be overwritten by corresponding files from the + `src` tree. + """ + sys.audit("shutil.copytree", src, dst) + with os.scandir(src) as itr: + entries = list(itr) + return _copytree(entries=entries, src=src, dst=dst, symlinks=symlinks, + ignore=ignore, copy_function=copy_function, + ignore_dangling_symlinks=ignore_dangling_symlinks, + dirs_exist_ok=dirs_exist_ok) + +if hasattr(os.stat_result, 'st_file_attributes'): + # Special handling for directory junctions to make them behave like + # symlinks for shutil.rmtree, since in general they do not appear as + # regular links. + def _rmtree_isdir(entry): + try: + st = entry.stat(follow_symlinks=False) + return (stat.S_ISDIR(st.st_mode) and not + (st.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT + and st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT)) + except OSError: + return False + + def _rmtree_islink(path): + try: + st = os.lstat(path) + return (stat.S_ISLNK(st.st_mode) or + (st.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT + and st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT)) + except OSError: + return False +else: + def _rmtree_isdir(entry): + try: + return entry.is_dir(follow_symlinks=False) + except OSError: + return False + + def _rmtree_islink(path): + return os.path.islink(path) + +# version vulnerable to race conditions +def _rmtree_unsafe(path, onerror): + try: + with os.scandir(path) as scandir_it: + entries = list(scandir_it) + except OSError: + onerror(os.scandir, path, sys.exc_info()) + entries = [] + for entry in entries: + fullname = entry.path + if _rmtree_isdir(entry): + try: + if entry.is_symlink(): + # This can only happen if someone replaces + # a directory with a symlink after the call to + # os.scandir or entry.is_dir above. + raise OSError("Cannot call rmtree on a symbolic link") + except OSError: + onerror(os.path.islink, fullname, sys.exc_info()) + continue + _rmtree_unsafe(fullname, onerror) + else: + try: + os.unlink(fullname) + except OSError: + onerror(os.unlink, fullname, sys.exc_info()) + try: + os.rmdir(path) + except OSError: + onerror(os.rmdir, path, sys.exc_info()) + +# Version using fd-based APIs to protect against races +def _rmtree_safe_fd(topfd, path, onerror): + try: + with os.scandir(topfd) as scandir_it: + entries = list(scandir_it) + except OSError as err: + err.filename = path + onerror(os.scandir, path, sys.exc_info()) + return + for entry in entries: + fullname = os.path.join(path, entry.name) + try: + is_dir = entry.is_dir(follow_symlinks=False) + except OSError: + is_dir = False + else: + if is_dir: + try: + orig_st = entry.stat(follow_symlinks=False) + is_dir = stat.S_ISDIR(orig_st.st_mode) + except OSError: + onerror(os.lstat, fullname, sys.exc_info()) + continue + if is_dir: + try: + dirfd = os.open(entry.name, os.O_RDONLY, dir_fd=topfd) + dirfd_closed = False + except OSError: + onerror(os.open, fullname, sys.exc_info()) + else: + try: + if os.path.samestat(orig_st, os.fstat(dirfd)): + _rmtree_safe_fd(dirfd, fullname, onerror) + try: + os.close(dirfd) + dirfd_closed = True + os.rmdir(entry.name, dir_fd=topfd) + except OSError: + onerror(os.rmdir, fullname, sys.exc_info()) + else: + try: + # This can only happen if someone replaces + # a directory with a symlink after the call to + # os.scandir or stat.S_ISDIR above. + raise OSError("Cannot call rmtree on a symbolic " + "link") + except OSError: + onerror(os.path.islink, fullname, sys.exc_info()) + finally: + if not dirfd_closed: + os.close(dirfd) + else: + try: + os.unlink(entry.name, dir_fd=topfd) + except OSError: + onerror(os.unlink, fullname, sys.exc_info()) + +_use_fd_functions = ({os.open, os.stat, os.unlink, os.rmdir} <= + os.supports_dir_fd and + os.scandir in os.supports_fd and + os.stat in os.supports_follow_symlinks) + +def rmtree(path, ignore_errors=False, onerror=None): + """Recursively delete a directory tree. + + If ignore_errors is set, errors are ignored; otherwise, if onerror + is set, it is called to handle the error with arguments (func, + path, exc_info) where func is platform and implementation dependent; + path is the argument to that function that caused it to fail; and + exc_info is a tuple returned by sys.exc_info(). If ignore_errors + is false and onerror is None, an exception is raised. + + """ + sys.audit("shutil.rmtree", path) + if ignore_errors: + def onerror(*args): + pass + elif onerror is None: + def onerror(*args): + raise + if _use_fd_functions: + # While the unsafe rmtree works fine on bytes, the fd based does not. + if isinstance(path, bytes): + path = os.fsdecode(path) + # Note: To guard against symlink races, we use the standard + # lstat()/open()/fstat() trick. + try: + orig_st = os.lstat(path) + except Exception: + onerror(os.lstat, path, sys.exc_info()) + return + try: + fd = os.open(path, os.O_RDONLY) + fd_closed = False + except Exception: + onerror(os.open, path, sys.exc_info()) + return + try: + if os.path.samestat(orig_st, os.fstat(fd)): + _rmtree_safe_fd(fd, path, onerror) + try: + os.close(fd) + fd_closed = True + os.rmdir(path) + except OSError: + onerror(os.rmdir, path, sys.exc_info()) + else: + try: + # symlinks to directories are forbidden, see bug #1669 + raise OSError("Cannot call rmtree on a symbolic link") + except OSError: + onerror(os.path.islink, path, sys.exc_info()) + finally: + if not fd_closed: + os.close(fd) + else: + try: + if _rmtree_islink(path): + # symlinks to directories are forbidden, see bug #1669 + raise OSError("Cannot call rmtree on a symbolic link") + except OSError: + onerror(os.path.islink, path, sys.exc_info()) + # can't continue even if onerror hook returns + return + return _rmtree_unsafe(path, onerror) + +# Allow introspection of whether or not the hardening against symlink +# attacks is supported on the current platform +rmtree.avoids_symlink_attacks = _use_fd_functions + +def _basename(path): + """A basename() variant which first strips the trailing slash, if present. + Thus we always get the last component of the path, even for directories. + + path: Union[PathLike, str] + + e.g. + >>> os.path.basename('/bar/foo') + 'foo' + >>> os.path.basename('/bar/foo/') + '' + >>> _basename('/bar/foo/') + 'foo' + """ + path = os.fspath(path) + sep = os.path.sep + (os.path.altsep or '') + return os.path.basename(path.rstrip(sep)) + +def move(src, dst, copy_function=copy2): + """Recursively move a file or directory to another location. This is + similar to the Unix "mv" command. Return the file or directory's + destination. + + If the destination is a directory or a symlink to a directory, the source + is moved inside the directory. The destination path must not already + exist. + + If the destination already exists but is not a directory, it may be + overwritten depending on os.rename() semantics. + + If the destination is on our current filesystem, then rename() is used. + Otherwise, src is copied to the destination and then removed. Symlinks are + recreated under the new name if os.rename() fails because of cross + filesystem renames. + + The optional `copy_function` argument is a callable that will be used + to copy the source or it will be delegated to `copytree`. + By default, copy2() is used, but any function that supports the same + signature (like copy()) can be used. + + A lot more could be done here... A look at a mv.c shows a lot of + the issues this implementation glosses over. + + """ + sys.audit("shutil.move", src, dst) + real_dst = dst + if os.path.isdir(dst): + if _samefile(src, dst): + # We might be on a case insensitive filesystem, + # perform the rename anyway. + os.rename(src, dst) + return + + # Using _basename instead of os.path.basename is important, as we must + # ignore any trailing slash to avoid the basename returning '' + real_dst = os.path.join(dst, _basename(src)) + + if os.path.exists(real_dst): + raise Error("Destination path '%s' already exists" % real_dst) + try: + os.rename(src, real_dst) + except OSError: + if os.path.islink(src): + linkto = os.readlink(src) + os.symlink(linkto, real_dst) + os.unlink(src) + elif os.path.isdir(src): + if _destinsrc(src, dst): + raise Error("Cannot move a directory '%s' into itself" + " '%s'." % (src, dst)) + if (_is_immutable(src) + or (not os.access(src, os.W_OK) and os.listdir(src) + and sys.platform == 'darwin')): + raise PermissionError("Cannot move the non-empty directory " + "'%s': Lacking write permission to '%s'." + % (src, src)) + copytree(src, real_dst, copy_function=copy_function, + symlinks=True) + rmtree(src) + else: + copy_function(src, real_dst) + os.unlink(src) + return real_dst + +def _destinsrc(src, dst): + src = os.path.abspath(src) + dst = os.path.abspath(dst) + if not src.endswith(os.path.sep): + src += os.path.sep + if not dst.endswith(os.path.sep): + dst += os.path.sep + return dst.startswith(src) + +def _is_immutable(src): + st = _stat(src) + immutable_states = [stat.UF_IMMUTABLE, stat.SF_IMMUTABLE] + return hasattr(st, 'st_flags') and st.st_flags in immutable_states + +def _get_gid(name): + """Returns a gid, given a group name.""" + if name is None: + return None + + try: + from grp import getgrnam + except ImportError: + return None + + try: + result = getgrnam(name) + except KeyError: + result = None + if result is not None: + return result[2] + return None + +def _get_uid(name): + """Returns an uid, given a user name.""" + if name is None: + return None + + try: + from pwd import getpwnam + except ImportError: + return None + + try: + result = getpwnam(name) + except KeyError: + result = None + if result is not None: + return result[2] + return None + +def _make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0, + owner=None, group=None, logger=None, root_dir=None): + """Create a (possibly compressed) tar file from all the files under + 'base_dir'. + + 'compress' must be "gzip" (the default), "bzip2", "xz", or None. + + 'owner' and 'group' can be used to define an owner and a group for the + archive that is being built. If not provided, the current owner and group + will be used. + + The output tar file will be named 'base_name' + ".tar", possibly plus + the appropriate compression extension (".gz", ".bz2", or ".xz"). + + Returns the output filename. + """ + if compress is None: + tar_compression = '' + elif _ZLIB_SUPPORTED and compress == 'gzip': + tar_compression = 'gz' + elif _BZ2_SUPPORTED and compress == 'bzip2': + tar_compression = 'bz2' + elif _LZMA_SUPPORTED and compress == 'xz': + tar_compression = 'xz' + else: + raise ValueError("bad value for 'compress', or compression format not " + "supported : {0}".format(compress)) + + import tarfile # late import for breaking circular dependency + + compress_ext = '.' + tar_compression if compress else '' + archive_name = base_name + '.tar' + compress_ext + archive_dir = os.path.dirname(archive_name) + + if archive_dir and not os.path.exists(archive_dir): + if logger is not None: + logger.info("creating %s", archive_dir) + if not dry_run: + os.makedirs(archive_dir) + + # creating the tarball + if logger is not None: + logger.info('Creating tar archive') + + uid = _get_uid(owner) + gid = _get_gid(group) + + def _set_uid_gid(tarinfo): + if gid is not None: + tarinfo.gid = gid + tarinfo.gname = group + if uid is not None: + tarinfo.uid = uid + tarinfo.uname = owner + return tarinfo + + if not dry_run: + tar = tarfile.open(archive_name, 'w|%s' % tar_compression) + arcname = base_dir + if root_dir is not None: + base_dir = os.path.join(root_dir, base_dir) + try: + tar.add(base_dir, arcname, filter=_set_uid_gid) + finally: + tar.close() + + if root_dir is not None: + archive_name = os.path.abspath(archive_name) + return archive_name + +def _make_zipfile(base_name, base_dir, verbose=0, dry_run=0, + logger=None, owner=None, group=None, root_dir=None): + """Create a zip file from all the files under 'base_dir'. + + The output zip file will be named 'base_name' + ".zip". Returns the + name of the output zip file. + """ + import zipfile # late import for breaking circular dependency + + zip_filename = base_name + ".zip" + archive_dir = os.path.dirname(base_name) + + if archive_dir and not os.path.exists(archive_dir): + if logger is not None: + logger.info("creating %s", archive_dir) + if not dry_run: + os.makedirs(archive_dir) + + if logger is not None: + logger.info("creating '%s' and adding '%s' to it", + zip_filename, base_dir) + + if not dry_run: + with zipfile.ZipFile(zip_filename, "w", + compression=zipfile.ZIP_DEFLATED) as zf: + arcname = os.path.normpath(base_dir) + if root_dir is not None: + base_dir = os.path.join(root_dir, base_dir) + base_dir = os.path.normpath(base_dir) + if arcname != os.curdir: + zf.write(base_dir, arcname) + if logger is not None: + logger.info("adding '%s'", base_dir) + for dirpath, dirnames, filenames in os.walk(base_dir): + arcdirpath = dirpath + if root_dir is not None: + arcdirpath = os.path.relpath(arcdirpath, root_dir) + arcdirpath = os.path.normpath(arcdirpath) + for name in sorted(dirnames): + path = os.path.join(dirpath, name) + arcname = os.path.join(arcdirpath, name) + zf.write(path, arcname) + if logger is not None: + logger.info("adding '%s'", path) + for name in filenames: + path = os.path.join(dirpath, name) + path = os.path.normpath(path) + if os.path.isfile(path): + arcname = os.path.join(arcdirpath, name) + zf.write(path, arcname) + if logger is not None: + logger.info("adding '%s'", path) + + if root_dir is not None: + zip_filename = os.path.abspath(zip_filename) + return zip_filename + +# Maps the name of the archive format to a tuple containing: +# * the archiving function +# * extra keyword arguments +# * description +# * does it support the root_dir argument? +_ARCHIVE_FORMATS = { + 'tar': (_make_tarball, [('compress', None)], + "uncompressed tar file", True), +} + +if _ZLIB_SUPPORTED: + _ARCHIVE_FORMATS['gztar'] = (_make_tarball, [('compress', 'gzip')], + "gzip'ed tar-file", True) + _ARCHIVE_FORMATS['zip'] = (_make_zipfile, [], "ZIP file", True) + +if _BZ2_SUPPORTED: + _ARCHIVE_FORMATS['bztar'] = (_make_tarball, [('compress', 'bzip2')], + "bzip2'ed tar-file", True) + +if _LZMA_SUPPORTED: + _ARCHIVE_FORMATS['xztar'] = (_make_tarball, [('compress', 'xz')], + "xz'ed tar-file", True) + +def get_archive_formats(): + """Returns a list of supported formats for archiving and unarchiving. + + Each element of the returned sequence is a tuple (name, description) + """ + formats = [(name, registry[2]) for name, registry in + _ARCHIVE_FORMATS.items()] + formats.sort() + return formats + +def register_archive_format(name, function, extra_args=None, description=''): + """Registers an archive format. + + name is the name of the format. function is the callable that will be + used to create archives. If provided, extra_args is a sequence of + (name, value) tuples that will be passed as arguments to the callable. + description can be provided to describe the format, and will be returned + by the get_archive_formats() function. + """ + if extra_args is None: + extra_args = [] + if not callable(function): + raise TypeError('The %s object is not callable' % function) + if not isinstance(extra_args, (tuple, list)): + raise TypeError('extra_args needs to be a sequence') + for element in extra_args: + if not isinstance(element, (tuple, list)) or len(element) !=2: + raise TypeError('extra_args elements are : (arg_name, value)') + + _ARCHIVE_FORMATS[name] = (function, extra_args, description, False) + +def unregister_archive_format(name): + del _ARCHIVE_FORMATS[name] + +def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0, + dry_run=0, owner=None, group=None, logger=None): + """Create an archive file (eg. zip or tar). + + 'base_name' is the name of the file to create, minus any format-specific + extension; 'format' is the archive format: one of "zip", "tar", "gztar", + "bztar", or "xztar". Or any other registered format. + + 'root_dir' is a directory that will be the root directory of the + archive; ie. we typically chdir into 'root_dir' before creating the + archive. 'base_dir' is the directory where we start archiving from; + ie. 'base_dir' will be the common prefix of all files and + directories in the archive. 'root_dir' and 'base_dir' both default + to the current directory. Returns the name of the archive file. + + 'owner' and 'group' are used when creating a tar archive. By default, + uses the current owner and group. + """ + sys.audit("shutil.make_archive", base_name, format, root_dir, base_dir) + try: + format_info = _ARCHIVE_FORMATS[format] + except KeyError: + raise ValueError("unknown archive format '%s'" % format) from None + + kwargs = {'dry_run': dry_run, 'logger': logger, + 'owner': owner, 'group': group} + + func = format_info[0] + for arg, val in format_info[1]: + kwargs[arg] = val + + if base_dir is None: + base_dir = os.curdir + + support_root_dir = format_info[3] + save_cwd = None + if root_dir is not None: + if support_root_dir: + # Support path-like base_name here for backwards-compatibility. + base_name = os.fspath(base_name) + kwargs['root_dir'] = root_dir + else: + save_cwd = os.getcwd() + if logger is not None: + logger.debug("changing into '%s'", root_dir) + base_name = os.path.abspath(base_name) + if not dry_run: + os.chdir(root_dir) + + try: + filename = func(base_name, base_dir, **kwargs) + finally: + if save_cwd is not None: + if logger is not None: + logger.debug("changing back to '%s'", save_cwd) + os.chdir(save_cwd) + + return filename + + +def get_unpack_formats(): + """Returns a list of supported formats for unpacking. + + Each element of the returned sequence is a tuple + (name, extensions, description) + """ + formats = [(name, info[0], info[3]) for name, info in + _UNPACK_FORMATS.items()] + formats.sort() + return formats + +def _check_unpack_options(extensions, function, extra_args): + """Checks what gets registered as an unpacker.""" + # first make sure no other unpacker is registered for this extension + existing_extensions = {} + for name, info in _UNPACK_FORMATS.items(): + for ext in info[0]: + existing_extensions[ext] = name + + for extension in extensions: + if extension in existing_extensions: + msg = '%s is already registered for "%s"' + raise RegistryError(msg % (extension, + existing_extensions[extension])) + + if not callable(function): + raise TypeError('The registered function must be a callable') + + +def register_unpack_format(name, extensions, function, extra_args=None, + description=''): + """Registers an unpack format. + + `name` is the name of the format. `extensions` is a list of extensions + corresponding to the format. + + `function` is the callable that will be + used to unpack archives. The callable will receive archives to unpack. + If it's unable to handle an archive, it needs to raise a ReadError + exception. + + If provided, `extra_args` is a sequence of + (name, value) tuples that will be passed as arguments to the callable. + description can be provided to describe the format, and will be returned + by the get_unpack_formats() function. + """ + if extra_args is None: + extra_args = [] + _check_unpack_options(extensions, function, extra_args) + _UNPACK_FORMATS[name] = extensions, function, extra_args, description + +def unregister_unpack_format(name): + """Removes the pack format from the registry.""" + del _UNPACK_FORMATS[name] + +def _ensure_directory(path): + """Ensure that the parent directory of `path` exists""" + dirname = os.path.dirname(path) + if not os.path.isdir(dirname): + os.makedirs(dirname) + +def _unpack_zipfile(filename, extract_dir): + """Unpack zip `filename` to `extract_dir` + """ + import zipfile # late import for breaking circular dependency + + if not zipfile.is_zipfile(filename): + raise ReadError("%s is not a zip file" % filename) + + zip = zipfile.ZipFile(filename) + try: + for info in zip.infolist(): + name = info.filename + + # don't extract absolute paths or ones with .. in them + if name.startswith('/') or '..' in name: + continue + + targetpath = os.path.join(extract_dir, *name.split('/')) + if not targetpath: + continue + + _ensure_directory(targetpath) + if not name.endswith('/'): + # file + with zip.open(name, 'r') as source, \ + open(targetpath, 'wb') as target: + copyfileobj(source, target) + finally: + zip.close() + +def _unpack_tarfile(filename, extract_dir, *, filter=None): + """Unpack tar/tar.gz/tar.bz2/tar.xz `filename` to `extract_dir` + """ + import tarfile # late import for breaking circular dependency + try: + tarobj = tarfile.open(filename) + except tarfile.TarError: + raise ReadError( + "%s is not a compressed or uncompressed tar file" % filename) + try: + tarobj.extractall(extract_dir, filter=filter) + finally: + tarobj.close() + +# Maps the name of the unpack format to a tuple containing: +# * extensions +# * the unpacking function +# * extra keyword arguments +# * description +_UNPACK_FORMATS = { + 'tar': (['.tar'], _unpack_tarfile, [], "uncompressed tar file"), + 'zip': (['.zip'], _unpack_zipfile, [], "ZIP file"), +} + +if _ZLIB_SUPPORTED: + _UNPACK_FORMATS['gztar'] = (['.tar.gz', '.tgz'], _unpack_tarfile, [], + "gzip'ed tar-file") + +if _BZ2_SUPPORTED: + _UNPACK_FORMATS['bztar'] = (['.tar.bz2', '.tbz2'], _unpack_tarfile, [], + "bzip2'ed tar-file") + +if _LZMA_SUPPORTED: + _UNPACK_FORMATS['xztar'] = (['.tar.xz', '.txz'], _unpack_tarfile, [], + "xz'ed tar-file") + +def _find_unpack_format(filename): + for name, info in _UNPACK_FORMATS.items(): + for extension in info[0]: + if filename.endswith(extension): + return name + return None + +def unpack_archive(filename, extract_dir=None, format=None, *, filter=None): + """Unpack an archive. + + `filename` is the name of the archive. + + `extract_dir` is the name of the target directory, where the archive + is unpacked. If not provided, the current working directory is used. + + `format` is the archive format: one of "zip", "tar", "gztar", "bztar", + or "xztar". Or any other registered format. If not provided, + unpack_archive will use the filename extension and see if an unpacker + was registered for that extension. + + In case none is found, a ValueError is raised. + + If `filter` is given, it is passed to the underlying + extraction function. + """ + sys.audit("shutil.unpack_archive", filename, extract_dir, format) + + if extract_dir is None: + extract_dir = os.getcwd() + + extract_dir = os.fspath(extract_dir) + filename = os.fspath(filename) + + if filter is None: + filter_kwargs = {} + else: + filter_kwargs = {'filter': filter} + if format is not None: + try: + format_info = _UNPACK_FORMATS[format] + except KeyError: + raise ValueError("Unknown unpack format '{0}'".format(format)) from None + + func = format_info[1] + func(filename, extract_dir, **dict(format_info[2]), **filter_kwargs) + else: + # we need to look at the registered unpackers supported extensions + format = _find_unpack_format(filename) + if format is None: + raise ReadError("Unknown archive format '{0}'".format(filename)) + + func = _UNPACK_FORMATS[format][1] + kwargs = dict(_UNPACK_FORMATS[format][2]) | filter_kwargs + func(filename, extract_dir, **kwargs) + + +if hasattr(os, 'statvfs'): + + __all__.append('disk_usage') + _ntuple_diskusage = collections.namedtuple('usage', 'total used free') + _ntuple_diskusage.total.__doc__ = 'Total space in bytes' + _ntuple_diskusage.used.__doc__ = 'Used space in bytes' + _ntuple_diskusage.free.__doc__ = 'Free space in bytes' + + def disk_usage(path): + """Return disk usage statistics about the given path. + + Returned value is a named tuple with attributes 'total', 'used' and + 'free', which are the amount of total, used and free space, in bytes. + """ + st = os.statvfs(path) + free = st.f_bavail * st.f_frsize + total = st.f_blocks * st.f_frsize + used = (st.f_blocks - st.f_bfree) * st.f_frsize + return _ntuple_diskusage(total, used, free) + +elif _WINDOWS: + + __all__.append('disk_usage') + _ntuple_diskusage = collections.namedtuple('usage', 'total used free') + + def disk_usage(path): + """Return disk usage statistics about the given path. + + Returned values is a named tuple with attributes 'total', 'used' and + 'free', which are the amount of total, used and free space, in bytes. + """ + total, free = nt._getdiskusage(path) + used = total - free + return _ntuple_diskusage(total, used, free) + + +def chown(path, user=None, group=None): + """Change owner user and group of the given path. + + user and group can be the uid/gid or the user/group names, and in that case, + they are converted to their respective uid/gid. + """ + sys.audit('shutil.chown', path, user, group) + + if user is None and group is None: + raise ValueError("user and/or group must be set") + + _user = user + _group = group + + # -1 means don't change it + if user is None: + _user = -1 + # user can either be an int (the uid) or a string (the system username) + elif isinstance(user, str): + _user = _get_uid(user) + if _user is None: + raise LookupError("no such user: {!r}".format(user)) + + if group is None: + _group = -1 + elif not isinstance(group, int): + _group = _get_gid(group) + if _group is None: + raise LookupError("no such group: {!r}".format(group)) + + os.chown(path, _user, _group) + +def get_terminal_size(fallback=(80, 24)): + """Get the size of the terminal window. + + For each of the two dimensions, the environment variable, COLUMNS + and LINES respectively, is checked. If the variable is defined and + the value is a positive integer, it is used. + + When COLUMNS or LINES is not defined, which is the common case, + the terminal connected to sys.__stdout__ is queried + by invoking os.get_terminal_size. + + If the terminal size cannot be successfully queried, either because + the system doesn't support querying, or because we are not + connected to a terminal, the value given in fallback parameter + is used. Fallback defaults to (80, 24) which is the default + size used by many terminal emulators. + + The value returned is a named tuple of type os.terminal_size. + """ + # columns, lines are the working values + try: + columns = int(os.environ['COLUMNS']) + except (KeyError, ValueError): + columns = 0 + + try: + lines = int(os.environ['LINES']) + except (KeyError, ValueError): + lines = 0 + + # only query if necessary + if columns <= 0 or lines <= 0: + try: + size = os.get_terminal_size(sys.__stdout__.fileno()) + except (AttributeError, ValueError, OSError): + # stdout is None, closed, detached, or not a terminal, or + # os.get_terminal_size() is unsupported + size = os.terminal_size(fallback) + if columns <= 0: + columns = size.columns + if lines <= 0: + lines = size.lines + + return os.terminal_size((columns, lines)) + + +# Check that a given file can be accessed with the correct mode. +# Additionally check that `file` is not a directory, as on Windows +# directories pass the os.access check. +def _access_check(fn, mode): + return (os.path.exists(fn) and os.access(fn, mode) + and not os.path.isdir(fn)) + + +def which(cmd, mode=os.F_OK | os.X_OK, path=None): + """Given a command, mode, and a PATH string, return the path which + conforms to the given mode on the PATH, or None if there is no such + file. + + `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result + of os.environ.get("PATH"), or can be overridden with a custom search + path. + + """ + # If we're given a path with a directory part, look it up directly rather + # than referring to PATH directories. This includes checking relative to the + # current directory, e.g. ./script + if os.path.dirname(cmd): + if _access_check(cmd, mode): + return cmd + return None + + use_bytes = isinstance(cmd, bytes) + + if path is None: + path = os.environ.get("PATH", None) + if path is None: + try: + path = os.confstr("CS_PATH") + except (AttributeError, ValueError): + # os.confstr() or CS_PATH is not available + path = os.defpath + # bpo-35755: Don't use os.defpath if the PATH environment variable is + # set to an empty string + + # PATH='' doesn't match, whereas PATH=':' looks in the current directory + if not path: + return None + + if use_bytes: + path = os.fsencode(path) + path = path.split(os.fsencode(os.pathsep)) + else: + path = os.fsdecode(path) + path = path.split(os.pathsep) + + if sys.platform == "win32": + # The current directory takes precedence on Windows. + curdir = os.curdir + if use_bytes: + curdir = os.fsencode(curdir) + if curdir not in path: + path.insert(0, curdir) + + # PATHEXT is necessary to check on Windows. + pathext_source = os.getenv("PATHEXT") or _WIN_DEFAULT_PATHEXT + pathext = [ext for ext in pathext_source.split(os.pathsep) if ext] + + if use_bytes: + pathext = [os.fsencode(ext) for ext in pathext] + # See if the given file matches any of the expected path extensions. + # This will allow us to short circuit when given "python.exe". + # If it does match, only test that one, otherwise we have to try + # others. + if any(cmd.lower().endswith(ext.lower()) for ext in pathext): + files = [cmd] + else: + files = [cmd + ext for ext in pathext] + else: + # On other platforms you don't have things like PATHEXT to tell you + # what file suffixes are executable, so just pass on cmd as-is. + files = [cmd] + + seen = set() + for dir in path: + normdir = os.path.normcase(dir) + if not normdir in seen: + seen.add(normdir) + for thefile in files: + name = os.path.join(dir, thefile) + if _access_check(name, mode): + return name + return None diff --git a/lib/python3.10/signal.py b/lib/python3.10/signal.py new file mode 100644 index 0000000000000000000000000000000000000000..50b215b29d2fadf6ccc38e860242d04e5d946fb5 --- /dev/null +++ b/lib/python3.10/signal.py @@ -0,0 +1,92 @@ +import _signal +from _signal import * +from enum import IntEnum as _IntEnum + +_globals = globals() + +_IntEnum._convert_( + 'Signals', __name__, + lambda name: + name.isupper() + and (name.startswith('SIG') and not name.startswith('SIG_')) + or name.startswith('CTRL_')) + +_IntEnum._convert_( + 'Handlers', __name__, + lambda name: name in ('SIG_DFL', 'SIG_IGN')) + +if 'pthread_sigmask' in _globals: + _IntEnum._convert_( + 'Sigmasks', __name__, + lambda name: name in ('SIG_BLOCK', 'SIG_UNBLOCK', 'SIG_SETMASK')) + + +def _int_to_enum(value, enum_klass): + """Convert a numeric value to an IntEnum member. + If it's not a known member, return the numeric value itself. + """ + try: + return enum_klass(value) + except ValueError: + return value + + +def _enum_to_int(value): + """Convert an IntEnum member to a numeric value. + If it's not an IntEnum member return the value itself. + """ + try: + return int(value) + except (ValueError, TypeError): + return value + + +# Similar to functools.wraps(), but only assign __doc__. +# __module__ should be preserved, +# __name__ and __qualname__ are already fine, +# __annotations__ is not set. +def _wraps(wrapped): + def decorator(wrapper): + wrapper.__doc__ = wrapped.__doc__ + return wrapper + return decorator + +@_wraps(_signal.signal) +def signal(signalnum, handler): + handler = _signal.signal(_enum_to_int(signalnum), _enum_to_int(handler)) + return _int_to_enum(handler, Handlers) + + +@_wraps(_signal.getsignal) +def getsignal(signalnum): + handler = _signal.getsignal(signalnum) + return _int_to_enum(handler, Handlers) + + +if 'pthread_sigmask' in _globals: + @_wraps(_signal.pthread_sigmask) + def pthread_sigmask(how, mask): + sigs_set = _signal.pthread_sigmask(how, mask) + return set(_int_to_enum(x, Signals) for x in sigs_set) + + +if 'sigpending' in _globals: + @_wraps(_signal.sigpending) + def sigpending(): + return {_int_to_enum(x, Signals) for x in _signal.sigpending()} + + +if 'sigwait' in _globals: + @_wraps(_signal.sigwait) + def sigwait(sigset): + retsig = _signal.sigwait(sigset) + return _int_to_enum(retsig, Signals) + + +if 'valid_signals' in _globals: + @_wraps(_signal.valid_signals) + def valid_signals(): + return {_int_to_enum(x, Signals) for x in _signal.valid_signals()} + + +del _globals, _wraps diff --git a/lib/python3.10/uu.py b/lib/python3.10/uu.py new file mode 100644 index 0000000000000000000000000000000000000000..9fe252a639eace1d288c31a4993508a2e40b0427 --- /dev/null +++ b/lib/python3.10/uu.py @@ -0,0 +1,213 @@ +#! /usr/bin/env python3 + +# Copyright 1994 by Lance Ellinghouse +# Cathedral City, California Republic, United States of America. +# All Rights Reserved +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose and without fee is hereby granted, +# provided that the above copyright notice appear in all copies and that +# both that copyright notice and this permission notice appear in +# supporting documentation, and that the name of Lance Ellinghouse +# not be used in advertising or publicity pertaining to distribution +# of the software without specific, written prior permission. +# LANCE ELLINGHOUSE DISCLAIMS ALL WARRANTIES WITH REGARD TO +# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS, IN NO EVENT SHALL LANCE ELLINGHOUSE CENTRUM BE LIABLE +# FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +# Modified by Jack Jansen, CWI, July 1995: +# - Use binascii module to do the actual line-by-line conversion +# between ascii and binary. This results in a 1000-fold speedup. The C +# version is still 5 times faster, though. +# - Arguments more compliant with python standard + +"""Implementation of the UUencode and UUdecode functions. + +encode(in_file, out_file [,name, mode], *, backtick=False) +decode(in_file [, out_file, mode, quiet]) +""" + +import binascii +import os +import sys + +__all__ = ["Error", "encode", "decode"] + +class Error(Exception): + pass + +def encode(in_file, out_file, name=None, mode=None, *, backtick=False): + """Uuencode file""" + # + # If in_file is a pathname open it and change defaults + # + opened_files = [] + try: + if in_file == '-': + in_file = sys.stdin.buffer + elif isinstance(in_file, str): + if name is None: + name = os.path.basename(in_file) + if mode is None: + try: + mode = os.stat(in_file).st_mode + except AttributeError: + pass + in_file = open(in_file, 'rb') + opened_files.append(in_file) + # + # Open out_file if it is a pathname + # + if out_file == '-': + out_file = sys.stdout.buffer + elif isinstance(out_file, str): + out_file = open(out_file, 'wb') + opened_files.append(out_file) + # + # Set defaults for name and mode + # + if name is None: + name = '-' + if mode is None: + mode = 0o666 + + # + # Remove newline chars from name + # + name = name.replace('\n','\\n') + name = name.replace('\r','\\r') + + # + # Write the data + # + out_file.write(('begin %o %s\n' % ((mode & 0o777), name)).encode("ascii")) + data = in_file.read(45) + while len(data) > 0: + out_file.write(binascii.b2a_uu(data, backtick=backtick)) + data = in_file.read(45) + if backtick: + out_file.write(b'`\nend\n') + else: + out_file.write(b' \nend\n') + finally: + for f in opened_files: + f.close() + + +def decode(in_file, out_file=None, mode=None, quiet=False): + """Decode uuencoded file""" + # + # Open the input file, if needed. + # + opened_files = [] + if in_file == '-': + in_file = sys.stdin.buffer + elif isinstance(in_file, str): + in_file = open(in_file, 'rb') + opened_files.append(in_file) + + try: + # + # Read until a begin is encountered or we've exhausted the file + # + while True: + hdr = in_file.readline() + if not hdr: + raise Error('No valid begin line found in input file') + if not hdr.startswith(b'begin'): + continue + hdrfields = hdr.split(b' ', 2) + if len(hdrfields) == 3 and hdrfields[0] == b'begin': + try: + int(hdrfields[1], 8) + break + except ValueError: + pass + if out_file is None: + # If the filename isn't ASCII, what's up with that?!? + out_file = hdrfields[2].rstrip(b' \t\r\n\f').decode("ascii") + if os.path.exists(out_file): + raise Error(f'Cannot overwrite existing file: {out_file}') + if (out_file.startswith(os.sep) or + f'..{os.sep}' in out_file or ( + os.altsep and + (out_file.startswith(os.altsep) or + f'..{os.altsep}' in out_file)) + ): + raise Error(f'Refusing to write to {out_file} due to directory traversal') + if mode is None: + mode = int(hdrfields[1], 8) + # + # Open the output file + # + if out_file == '-': + out_file = sys.stdout.buffer + elif isinstance(out_file, str): + fp = open(out_file, 'wb') + os.chmod(out_file, mode) + out_file = fp + opened_files.append(out_file) + # + # Main decoding loop + # + s = in_file.readline() + while s and s.strip(b' \t\r\n\f') != b'end': + try: + data = binascii.a2b_uu(s) + except binascii.Error as v: + # Workaround for broken uuencoders by /Fredrik Lundh + nbytes = (((s[0]-32) & 63) * 4 + 5) // 3 + data = binascii.a2b_uu(s[:nbytes]) + if not quiet: + sys.stderr.write("Warning: %s\n" % v) + out_file.write(data) + s = in_file.readline() + if not s: + raise Error('Truncated input file') + finally: + for f in opened_files: + f.close() + +def test(): + """uuencode/uudecode main program""" + + import optparse + parser = optparse.OptionParser(usage='usage: %prog [-d] [-t] [input [output]]') + parser.add_option('-d', '--decode', dest='decode', help='Decode (instead of encode)?', default=False, action='store_true') + parser.add_option('-t', '--text', dest='text', help='data is text, encoded format unix-compatible text?', default=False, action='store_true') + + (options, args) = parser.parse_args() + if len(args) > 2: + parser.error('incorrect number of arguments') + sys.exit(1) + + # Use the binary streams underlying stdin/stdout + input = sys.stdin.buffer + output = sys.stdout.buffer + if len(args) > 0: + input = args[0] + if len(args) > 1: + output = args[1] + + if options.decode: + if options.text: + if isinstance(output, str): + output = open(output, 'wb') + else: + print(sys.argv[0], ': cannot do -t to stdout') + sys.exit(1) + decode(input, output) + else: + if options.text: + if isinstance(input, str): + input = open(input, 'rb') + else: + print(sys.argv[0], ': cannot do -t from stdin') + sys.exit(1) + encode(input, output) + +if __name__ == '__main__': + test() diff --git a/lib/python3.10/uuid.py b/lib/python3.10/uuid.py new file mode 100644 index 0000000000000000000000000000000000000000..fe9f87b79457fac74f7dee5643a01870dd3fd8e9 --- /dev/null +++ b/lib/python3.10/uuid.py @@ -0,0 +1,733 @@ +r"""UUID objects (universally unique identifiers) according to RFC 4122. + +This module provides immutable UUID objects (class UUID) and the functions +uuid1(), uuid3(), uuid4(), uuid5() for generating version 1, 3, 4, and 5 +UUIDs as specified in RFC 4122. + +If all you want is a unique ID, you should probably call uuid1() or uuid4(). +Note that uuid1() may compromise privacy since it creates a UUID containing +the computer's network address. uuid4() creates a random UUID. + +Typical usage: + + >>> import uuid + + # make a UUID based on the host ID and current time + >>> uuid.uuid1() # doctest: +SKIP + UUID('a8098c1a-f86e-11da-bd1a-00112444be1e') + + # make a UUID using an MD5 hash of a namespace UUID and a name + >>> uuid.uuid3(uuid.NAMESPACE_DNS, 'python.org') + UUID('6fa459ea-ee8a-3ca4-894e-db77e160355e') + + # make a random UUID + >>> uuid.uuid4() # doctest: +SKIP + UUID('16fd2706-8baf-433b-82eb-8c7fada847da') + + # make a UUID using a SHA-1 hash of a namespace UUID and a name + >>> uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org') + UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d') + + # make a UUID from a string of hex digits (braces and hyphens ignored) + >>> x = uuid.UUID('{00010203-0405-0607-0809-0a0b0c0d0e0f}') + + # convert a UUID to a string of hex digits in standard form + >>> str(x) + '00010203-0405-0607-0809-0a0b0c0d0e0f' + + # get the raw 16 bytes of the UUID + >>> x.bytes + b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f' + + # make a UUID from a 16-byte string + >>> uuid.UUID(bytes=x.bytes) + UUID('00010203-0405-0607-0809-0a0b0c0d0e0f') +""" + +import os +import sys + +from enum import Enum + + +__author__ = 'Ka-Ping Yee ' + +# The recognized platforms - known behaviors +if sys.platform in ('win32', 'darwin'): + _AIX = _LINUX = False +else: + import platform + _platform_system = platform.system() + _AIX = _platform_system == 'AIX' + _LINUX = _platform_system == 'Linux' + +_MAC_DELIM = b':' +_MAC_OMITS_LEADING_ZEROES = False +if _AIX: + _MAC_DELIM = b'.' + _MAC_OMITS_LEADING_ZEROES = True + +RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE = [ + 'reserved for NCS compatibility', 'specified in RFC 4122', + 'reserved for Microsoft compatibility', 'reserved for future definition'] + +int_ = int # The built-in int type +bytes_ = bytes # The built-in bytes type + + +class SafeUUID(Enum): + safe = 0 + unsafe = -1 + unknown = None + + +class UUID: + """Instances of the UUID class represent UUIDs as specified in RFC 4122. + UUID objects are immutable, hashable, and usable as dictionary keys. + Converting a UUID to a string with str() yields something in the form + '12345678-1234-1234-1234-123456789abc'. The UUID constructor accepts + five possible forms: a similar string of hexadecimal digits, or a tuple + of six integer fields (with 32-bit, 16-bit, 16-bit, 8-bit, 8-bit, and + 48-bit values respectively) as an argument named 'fields', or a string + of 16 bytes (with all the integer fields in big-endian order) as an + argument named 'bytes', or a string of 16 bytes (with the first three + fields in little-endian order) as an argument named 'bytes_le', or a + single 128-bit integer as an argument named 'int'. + + UUIDs have these read-only attributes: + + bytes the UUID as a 16-byte string (containing the six + integer fields in big-endian byte order) + + bytes_le the UUID as a 16-byte string (with time_low, time_mid, + and time_hi_version in little-endian byte order) + + fields a tuple of the six integer fields of the UUID, + which are also available as six individual attributes + and two derived attributes: + + time_low the first 32 bits of the UUID + time_mid the next 16 bits of the UUID + time_hi_version the next 16 bits of the UUID + clock_seq_hi_variant the next 8 bits of the UUID + clock_seq_low the next 8 bits of the UUID + node the last 48 bits of the UUID + + time the 60-bit timestamp + clock_seq the 14-bit sequence number + + hex the UUID as a 32-character hexadecimal string + + int the UUID as a 128-bit integer + + urn the UUID as a URN as specified in RFC 4122 + + variant the UUID variant (one of the constants RESERVED_NCS, + RFC_4122, RESERVED_MICROSOFT, or RESERVED_FUTURE) + + version the UUID version number (1 through 5, meaningful only + when the variant is RFC_4122) + + is_safe An enum indicating whether the UUID has been generated in + a way that is safe for multiprocessing applications, via + uuid_generate_time_safe(3). + """ + + __slots__ = ('int', 'is_safe', '__weakref__') + + def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None, + int=None, version=None, + *, is_safe=SafeUUID.unknown): + r"""Create a UUID from either a string of 32 hexadecimal digits, + a string of 16 bytes as the 'bytes' argument, a string of 16 bytes + in little-endian order as the 'bytes_le' argument, a tuple of six + integers (32-bit time_low, 16-bit time_mid, 16-bit time_hi_version, + 8-bit clock_seq_hi_variant, 8-bit clock_seq_low, 48-bit node) as + the 'fields' argument, or a single 128-bit integer as the 'int' + argument. When a string of hex digits is given, curly braces, + hyphens, and a URN prefix are all optional. For example, these + expressions all yield the same UUID: + + UUID('{12345678-1234-5678-1234-567812345678}') + UUID('12345678123456781234567812345678') + UUID('urn:uuid:12345678-1234-5678-1234-567812345678') + UUID(bytes='\x12\x34\x56\x78'*4) + UUID(bytes_le='\x78\x56\x34\x12\x34\x12\x78\x56' + + '\x12\x34\x56\x78\x12\x34\x56\x78') + UUID(fields=(0x12345678, 0x1234, 0x5678, 0x12, 0x34, 0x567812345678)) + UUID(int=0x12345678123456781234567812345678) + + Exactly one of 'hex', 'bytes', 'bytes_le', 'fields', or 'int' must + be given. The 'version' argument is optional; if given, the resulting + UUID will have its variant and version set according to RFC 4122, + overriding the given 'hex', 'bytes', 'bytes_le', 'fields', or 'int'. + + is_safe is an enum exposed as an attribute on the instance. It + indicates whether the UUID has been generated in a way that is safe + for multiprocessing applications, via uuid_generate_time_safe(3). + """ + + if [hex, bytes, bytes_le, fields, int].count(None) != 4: + raise TypeError('one of the hex, bytes, bytes_le, fields, ' + 'or int arguments must be given') + if hex is not None: + hex = hex.replace('urn:', '').replace('uuid:', '') + hex = hex.strip('{}').replace('-', '') + if len(hex) != 32: + raise ValueError('badly formed hexadecimal UUID string') + int = int_(hex, 16) + if bytes_le is not None: + if len(bytes_le) != 16: + raise ValueError('bytes_le is not a 16-char string') + bytes = (bytes_le[4-1::-1] + bytes_le[6-1:4-1:-1] + + bytes_le[8-1:6-1:-1] + bytes_le[8:]) + if bytes is not None: + if len(bytes) != 16: + raise ValueError('bytes is not a 16-char string') + assert isinstance(bytes, bytes_), repr(bytes) + int = int_.from_bytes(bytes, byteorder='big') + if fields is not None: + if len(fields) != 6: + raise ValueError('fields is not a 6-tuple') + (time_low, time_mid, time_hi_version, + clock_seq_hi_variant, clock_seq_low, node) = fields + if not 0 <= time_low < 1<<32: + raise ValueError('field 1 out of range (need a 32-bit value)') + if not 0 <= time_mid < 1<<16: + raise ValueError('field 2 out of range (need a 16-bit value)') + if not 0 <= time_hi_version < 1<<16: + raise ValueError('field 3 out of range (need a 16-bit value)') + if not 0 <= clock_seq_hi_variant < 1<<8: + raise ValueError('field 4 out of range (need an 8-bit value)') + if not 0 <= clock_seq_low < 1<<8: + raise ValueError('field 5 out of range (need an 8-bit value)') + if not 0 <= node < 1<<48: + raise ValueError('field 6 out of range (need a 48-bit value)') + clock_seq = (clock_seq_hi_variant << 8) | clock_seq_low + int = ((time_low << 96) | (time_mid << 80) | + (time_hi_version << 64) | (clock_seq << 48) | node) + if int is not None: + if not 0 <= int < 1<<128: + raise ValueError('int is out of range (need a 128-bit value)') + if version is not None: + if not 1 <= version <= 5: + raise ValueError('illegal version number') + # Set the variant to RFC 4122. + int &= ~(0xc000 << 48) + int |= 0x8000 << 48 + # Set the version number. + int &= ~(0xf000 << 64) + int |= version << 76 + object.__setattr__(self, 'int', int) + object.__setattr__(self, 'is_safe', is_safe) + + def __getstate__(self): + d = {'int': self.int} + if self.is_safe != SafeUUID.unknown: + # is_safe is a SafeUUID instance. Return just its value, so that + # it can be un-pickled in older Python versions without SafeUUID. + d['is_safe'] = self.is_safe.value + return d + + def __setstate__(self, state): + object.__setattr__(self, 'int', state['int']) + # is_safe was added in 3.7; it is also omitted when it is "unknown" + object.__setattr__(self, 'is_safe', + SafeUUID(state['is_safe']) + if 'is_safe' in state else SafeUUID.unknown) + + def __eq__(self, other): + if isinstance(other, UUID): + return self.int == other.int + return NotImplemented + + # Q. What's the value of being able to sort UUIDs? + # A. Use them as keys in a B-Tree or similar mapping. + + def __lt__(self, other): + if isinstance(other, UUID): + return self.int < other.int + return NotImplemented + + def __gt__(self, other): + if isinstance(other, UUID): + return self.int > other.int + return NotImplemented + + def __le__(self, other): + if isinstance(other, UUID): + return self.int <= other.int + return NotImplemented + + def __ge__(self, other): + if isinstance(other, UUID): + return self.int >= other.int + return NotImplemented + + def __hash__(self): + return hash(self.int) + + def __int__(self): + return self.int + + def __repr__(self): + return '%s(%r)' % (self.__class__.__name__, str(self)) + + def __setattr__(self, name, value): + raise TypeError('UUID objects are immutable') + + def __str__(self): + hex = '%032x' % self.int + return '%s-%s-%s-%s-%s' % ( + hex[:8], hex[8:12], hex[12:16], hex[16:20], hex[20:]) + + @property + def bytes(self): + return self.int.to_bytes(16, 'big') + + @property + def bytes_le(self): + bytes = self.bytes + return (bytes[4-1::-1] + bytes[6-1:4-1:-1] + bytes[8-1:6-1:-1] + + bytes[8:]) + + @property + def fields(self): + return (self.time_low, self.time_mid, self.time_hi_version, + self.clock_seq_hi_variant, self.clock_seq_low, self.node) + + @property + def time_low(self): + return self.int >> 96 + + @property + def time_mid(self): + return (self.int >> 80) & 0xffff + + @property + def time_hi_version(self): + return (self.int >> 64) & 0xffff + + @property + def clock_seq_hi_variant(self): + return (self.int >> 56) & 0xff + + @property + def clock_seq_low(self): + return (self.int >> 48) & 0xff + + @property + def time(self): + return (((self.time_hi_version & 0x0fff) << 48) | + (self.time_mid << 32) | self.time_low) + + @property + def clock_seq(self): + return (((self.clock_seq_hi_variant & 0x3f) << 8) | + self.clock_seq_low) + + @property + def node(self): + return self.int & 0xffffffffffff + + @property + def hex(self): + return '%032x' % self.int + + @property + def urn(self): + return 'urn:uuid:' + str(self) + + @property + def variant(self): + if not self.int & (0x8000 << 48): + return RESERVED_NCS + elif not self.int & (0x4000 << 48): + return RFC_4122 + elif not self.int & (0x2000 << 48): + return RESERVED_MICROSOFT + else: + return RESERVED_FUTURE + + @property + def version(self): + # The version bits are only meaningful for RFC 4122 UUIDs. + if self.variant == RFC_4122: + return int((self.int >> 76) & 0xf) + + +def _get_command_stdout(command, *args): + import io, os, shutil, subprocess + + try: + path_dirs = os.environ.get('PATH', os.defpath).split(os.pathsep) + path_dirs.extend(['/sbin', '/usr/sbin']) + executable = shutil.which(command, path=os.pathsep.join(path_dirs)) + if executable is None: + return None + # LC_ALL=C to ensure English output, stderr=DEVNULL to prevent output + # on stderr (Note: we don't have an example where the words we search + # for are actually localized, but in theory some system could do so.) + env = dict(os.environ) + env['LC_ALL'] = 'C' + # Empty strings will be quoted by popen so we should just ommit it + if args != ('',): + command = (executable, *args) + else: + command = (executable,) + proc = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + env=env) + if not proc: + return None + stdout, stderr = proc.communicate() + return io.BytesIO(stdout) + except (OSError, subprocess.SubprocessError): + return None + + +# For MAC (a.k.a. IEEE 802, or EUI-48) addresses, the second least significant +# bit of the first octet signifies whether the MAC address is universally (0) +# or locally (1) administered. Network cards from hardware manufacturers will +# always be universally administered to guarantee global uniqueness of the MAC +# address, but any particular machine may have other interfaces which are +# locally administered. An example of the latter is the bridge interface to +# the Touch Bar on MacBook Pros. +# +# This bit works out to be the 42nd bit counting from 1 being the least +# significant, or 1<<41. We'll prefer universally administered MAC addresses +# over locally administered ones since the former are globally unique, but +# we'll return the first of the latter found if that's all the machine has. +# +# See https://en.wikipedia.org/wiki/MAC_address#Universal_vs._local + +def _is_universal(mac): + return not (mac & (1 << 41)) + + +def _find_mac_near_keyword(command, args, keywords, get_word_index): + """Searches a command's output for a MAC address near a keyword. + + Each line of words in the output is case-insensitively searched for + any of the given keywords. Upon a match, get_word_index is invoked + to pick a word from the line, given the index of the match. For + example, lambda i: 0 would get the first word on the line, while + lambda i: i - 1 would get the word preceding the keyword. + """ + stdout = _get_command_stdout(command, args) + if stdout is None: + return None + + first_local_mac = None + for line in stdout: + words = line.lower().rstrip().split() + for i in range(len(words)): + if words[i] in keywords: + try: + word = words[get_word_index(i)] + mac = int(word.replace(_MAC_DELIM, b''), 16) + except (ValueError, IndexError): + # Virtual interfaces, such as those provided by + # VPNs, do not have a colon-delimited MAC address + # as expected, but a 16-byte HWAddr separated by + # dashes. These should be ignored in favor of a + # real MAC address + pass + else: + if _is_universal(mac): + return mac + first_local_mac = first_local_mac or mac + return first_local_mac or None + + +def _parse_mac(word): + # Accept 'HH:HH:HH:HH:HH:HH' MAC address (ex: '52:54:00:9d:0e:67'), + # but reject IPv6 address (ex: 'fe80::5054:ff:fe9' or '123:2:3:4:5:6:7:8'). + # + # Virtual interfaces, such as those provided by VPNs, do not have a + # colon-delimited MAC address as expected, but a 16-byte HWAddr separated + # by dashes. These should be ignored in favor of a real MAC address + parts = word.split(_MAC_DELIM) + if len(parts) != 6: + return + if _MAC_OMITS_LEADING_ZEROES: + # (Only) on AIX the macaddr value given is not prefixed by 0, e.g. + # en0 1500 link#2 fa.bc.de.f7.62.4 110854824 0 160133733 0 0 + # not + # en0 1500 link#2 fa.bc.de.f7.62.04 110854824 0 160133733 0 0 + if not all(1 <= len(part) <= 2 for part in parts): + return + hexstr = b''.join(part.rjust(2, b'0') for part in parts) + else: + if not all(len(part) == 2 for part in parts): + return + hexstr = b''.join(parts) + try: + return int(hexstr, 16) + except ValueError: + return + + +def _find_mac_under_heading(command, args, heading): + """Looks for a MAC address under a heading in a command's output. + + The first line of words in the output is searched for the given + heading. Words at the same word index as the heading in subsequent + lines are then examined to see if they look like MAC addresses. + """ + stdout = _get_command_stdout(command, args) + if stdout is None: + return None + + keywords = stdout.readline().rstrip().split() + try: + column_index = keywords.index(heading) + except ValueError: + return None + + first_local_mac = None + for line in stdout: + words = line.rstrip().split() + try: + word = words[column_index] + except IndexError: + continue + + mac = _parse_mac(word) + if mac is None: + continue + if _is_universal(mac): + return mac + if first_local_mac is None: + first_local_mac = mac + + return first_local_mac + + +# The following functions call external programs to 'get' a macaddr value to +# be used as basis for an uuid +def _ifconfig_getnode(): + """Get the hardware address on Unix by running ifconfig.""" + # This works on Linux ('' or '-a'), Tru64 ('-av'), but not all Unixes. + keywords = (b'hwaddr', b'ether', b'address:', b'lladdr') + for args in ('', '-a', '-av'): + mac = _find_mac_near_keyword('ifconfig', args, keywords, lambda i: i+1) + if mac: + return mac + return None + +def _ip_getnode(): + """Get the hardware address on Unix by running ip.""" + # This works on Linux with iproute2. + mac = _find_mac_near_keyword('ip', 'link', [b'link/ether'], lambda i: i+1) + if mac: + return mac + return None + +def _arp_getnode(): + """Get the hardware address on Unix by running arp.""" + import os, socket + try: + ip_addr = socket.gethostbyname(socket.gethostname()) + except OSError: + return None + + # Try getting the MAC addr from arp based on our IP address (Solaris). + mac = _find_mac_near_keyword('arp', '-an', [os.fsencode(ip_addr)], lambda i: -1) + if mac: + return mac + + # This works on OpenBSD + mac = _find_mac_near_keyword('arp', '-an', [os.fsencode(ip_addr)], lambda i: i+1) + if mac: + return mac + + # This works on Linux, FreeBSD and NetBSD + mac = _find_mac_near_keyword('arp', '-an', [os.fsencode('(%s)' % ip_addr)], + lambda i: i+2) + # Return None instead of 0. + if mac: + return mac + return None + +def _lanscan_getnode(): + """Get the hardware address on Unix by running lanscan.""" + # This might work on HP-UX. + return _find_mac_near_keyword('lanscan', '-ai', [b'lan0'], lambda i: 0) + +def _netstat_getnode(): + """Get the hardware address on Unix by running netstat.""" + # This works on AIX and might work on Tru64 UNIX. + return _find_mac_under_heading('netstat', '-ian', b'Address') + +def _ipconfig_getnode(): + """[DEPRECATED] Get the hardware address on Windows.""" + # bpo-40501: UuidCreateSequential() is now the only supported approach + return _windll_getnode() + +def _netbios_getnode(): + """[DEPRECATED] Get the hardware address on Windows.""" + # bpo-40501: UuidCreateSequential() is now the only supported approach + return _windll_getnode() + + +# Import optional C extension at toplevel, to help disabling it when testing +try: + import _uuid + _generate_time_safe = getattr(_uuid, "generate_time_safe", None) + _UuidCreate = getattr(_uuid, "UuidCreate", None) + _has_uuid_generate_time_safe = _uuid.has_uuid_generate_time_safe +except ImportError: + _uuid = None + _generate_time_safe = None + _UuidCreate = None + _has_uuid_generate_time_safe = None + + +def _load_system_functions(): + """[DEPRECATED] Platform-specific functions loaded at import time""" + + +def _unix_getnode(): + """Get the hardware address on Unix using the _uuid extension module.""" + if _generate_time_safe: + uuid_time, _ = _generate_time_safe() + return UUID(bytes=uuid_time).node + +def _windll_getnode(): + """Get the hardware address on Windows using the _uuid extension module.""" + if _UuidCreate: + uuid_bytes = _UuidCreate() + return UUID(bytes_le=uuid_bytes).node + +def _random_getnode(): + """Get a random node ID.""" + # RFC 4122, $4.1.6 says "For systems with no IEEE address, a randomly or + # pseudo-randomly generated value may be used; see Section 4.5. The + # multicast bit must be set in such addresses, in order that they will + # never conflict with addresses obtained from network cards." + # + # The "multicast bit" of a MAC address is defined to be "the least + # significant bit of the first octet". This works out to be the 41st bit + # counting from 1 being the least significant bit, or 1<<40. + # + # See https://en.wikipedia.org/wiki/MAC_address#Unicast_vs._multicast + import random + return random.getrandbits(48) | (1 << 40) + + +# _OS_GETTERS, when known, are targeted for a specific OS or platform. +# The order is by 'common practice' on the specified platform. +# Note: 'posix' and 'windows' _OS_GETTERS are prefixed by a dll/dlload() method +# which, when successful, means none of these "external" methods are called. +# _GETTERS is (also) used by test_uuid.py to SkipUnless(), e.g., +# @unittest.skipUnless(_uuid._ifconfig_getnode in _uuid._GETTERS, ...) +if _LINUX: + _OS_GETTERS = [_ip_getnode, _ifconfig_getnode] +elif sys.platform == 'darwin': + _OS_GETTERS = [_ifconfig_getnode, _arp_getnode, _netstat_getnode] +elif sys.platform == 'win32': + # bpo-40201: _windll_getnode will always succeed, so these are not needed + _OS_GETTERS = [] +elif _AIX: + _OS_GETTERS = [_netstat_getnode] +else: + _OS_GETTERS = [_ifconfig_getnode, _ip_getnode, _arp_getnode, + _netstat_getnode, _lanscan_getnode] +if os.name == 'posix': + _GETTERS = [_unix_getnode] + _OS_GETTERS +elif os.name == 'nt': + _GETTERS = [_windll_getnode] + _OS_GETTERS +else: + _GETTERS = _OS_GETTERS + +_node = None + +def getnode(): + """Get the hardware address as a 48-bit positive integer. + + The first time this runs, it may launch a separate program, which could + be quite slow. If all attempts to obtain the hardware address fail, we + choose a random 48-bit number with its eighth bit set to 1 as recommended + in RFC 4122. + """ + global _node + if _node is not None: + return _node + + for getter in _GETTERS + [_random_getnode]: + try: + _node = getter() + except: + continue + if (_node is not None) and (0 <= _node < (1 << 48)): + return _node + assert False, '_random_getnode() returned invalid value: {}'.format(_node) + + +_last_timestamp = None + +def uuid1(node=None, clock_seq=None): + """Generate a UUID from a host ID, sequence number, and the current time. + If 'node' is not given, getnode() is used to obtain the hardware + address. If 'clock_seq' is given, it is used as the sequence number; + otherwise a random 14-bit sequence number is chosen.""" + + # When the system provides a version-1 UUID generator, use it (but don't + # use UuidCreate here because its UUIDs don't conform to RFC 4122). + if _generate_time_safe is not None and node is clock_seq is None: + uuid_time, safely_generated = _generate_time_safe() + try: + is_safe = SafeUUID(safely_generated) + except ValueError: + is_safe = SafeUUID.unknown + return UUID(bytes=uuid_time, is_safe=is_safe) + + global _last_timestamp + import time + nanoseconds = time.time_ns() + # 0x01b21dd213814000 is the number of 100-ns intervals between the + # UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00. + timestamp = nanoseconds // 100 + 0x01b21dd213814000 + if _last_timestamp is not None and timestamp <= _last_timestamp: + timestamp = _last_timestamp + 1 + _last_timestamp = timestamp + if clock_seq is None: + import random + clock_seq = random.getrandbits(14) # instead of stable storage + time_low = timestamp & 0xffffffff + time_mid = (timestamp >> 32) & 0xffff + time_hi_version = (timestamp >> 48) & 0x0fff + clock_seq_low = clock_seq & 0xff + clock_seq_hi_variant = (clock_seq >> 8) & 0x3f + if node is None: + node = getnode() + return UUID(fields=(time_low, time_mid, time_hi_version, + clock_seq_hi_variant, clock_seq_low, node), version=1) + +def uuid3(namespace, name): + """Generate a UUID from the MD5 hash of a namespace UUID and a name.""" + from hashlib import md5 + digest = md5( + namespace.bytes + bytes(name, "utf-8"), + usedforsecurity=False + ).digest() + return UUID(bytes=digest[:16], version=3) + +def uuid4(): + """Generate a random UUID.""" + return UUID(bytes=os.urandom(16), version=4) + +def uuid5(namespace, name): + """Generate a UUID from the SHA-1 hash of a namespace UUID and a name.""" + from hashlib import sha1 + hash = sha1(namespace.bytes + bytes(name, "utf-8")).digest() + return UUID(bytes=hash[:16], version=5) + +# The following standard UUIDs are for use with uuid3() or uuid5(). + +NAMESPACE_DNS = UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8') +NAMESPACE_URL = UUID('6ba7b811-9dad-11d1-80b4-00c04fd430c8') +NAMESPACE_OID = UUID('6ba7b812-9dad-11d1-80b4-00c04fd430c8') +NAMESPACE_X500 = UUID('6ba7b814-9dad-11d1-80b4-00c04fd430c8') diff --git a/lib/python3.10/warnings.py b/lib/python3.10/warnings.py new file mode 100644 index 0000000000000000000000000000000000000000..691ccddfa450ad242e45458e693dbdead2d56bf4 --- /dev/null +++ b/lib/python3.10/warnings.py @@ -0,0 +1,549 @@ +"""Python part of the warnings subsystem.""" + +import sys + + +__all__ = ["warn", "warn_explicit", "showwarning", + "formatwarning", "filterwarnings", "simplefilter", + "resetwarnings", "catch_warnings"] + +def showwarning(message, category, filename, lineno, file=None, line=None): + """Hook to write a warning to a file; replace if you like.""" + msg = WarningMessage(message, category, filename, lineno, file, line) + _showwarnmsg_impl(msg) + +def formatwarning(message, category, filename, lineno, line=None): + """Function to format a warning the standard way.""" + msg = WarningMessage(message, category, filename, lineno, None, line) + return _formatwarnmsg_impl(msg) + +def _showwarnmsg_impl(msg): + file = msg.file + if file is None: + file = sys.stderr + if file is None: + # sys.stderr is None when run with pythonw.exe: + # warnings get lost + return + text = _formatwarnmsg(msg) + try: + file.write(text) + except OSError: + # the file (probably stderr) is invalid - this warning gets lost. + pass + +def _formatwarnmsg_impl(msg): + category = msg.category.__name__ + s = f"{msg.filename}:{msg.lineno}: {category}: {msg.message}\n" + + if msg.line is None: + try: + import linecache + line = linecache.getline(msg.filename, msg.lineno) + except Exception: + # When a warning is logged during Python shutdown, linecache + # and the import machinery don't work anymore + line = None + linecache = None + else: + line = msg.line + if line: + line = line.strip() + s += " %s\n" % line + + if msg.source is not None: + try: + import tracemalloc + # Logging a warning should not raise a new exception: + # catch Exception, not only ImportError and RecursionError. + except Exception: + # don't suggest to enable tracemalloc if it's not available + tracing = True + tb = None + else: + tracing = tracemalloc.is_tracing() + try: + tb = tracemalloc.get_object_traceback(msg.source) + except Exception: + # When a warning is logged during Python shutdown, tracemalloc + # and the import machinery don't work anymore + tb = None + + if tb is not None: + s += 'Object allocated at (most recent call last):\n' + for frame in tb: + s += (' File "%s", lineno %s\n' + % (frame.filename, frame.lineno)) + + try: + if linecache is not None: + line = linecache.getline(frame.filename, frame.lineno) + else: + line = None + except Exception: + line = None + if line: + line = line.strip() + s += ' %s\n' % line + elif not tracing: + s += (f'{category}: Enable tracemalloc to get the object ' + f'allocation traceback\n') + return s + +# Keep a reference to check if the function was replaced +_showwarning_orig = showwarning + +def _showwarnmsg(msg): + """Hook to write a warning to a file; replace if you like.""" + try: + sw = showwarning + except NameError: + pass + else: + if sw is not _showwarning_orig: + # warnings.showwarning() was replaced + if not callable(sw): + raise TypeError("warnings.showwarning() must be set to a " + "function or method") + + sw(msg.message, msg.category, msg.filename, msg.lineno, + msg.file, msg.line) + return + _showwarnmsg_impl(msg) + +# Keep a reference to check if the function was replaced +_formatwarning_orig = formatwarning + +def _formatwarnmsg(msg): + """Function to format a warning the standard way.""" + try: + fw = formatwarning + except NameError: + pass + else: + if fw is not _formatwarning_orig: + # warnings.formatwarning() was replaced + return fw(msg.message, msg.category, + msg.filename, msg.lineno, msg.line) + return _formatwarnmsg_impl(msg) + +def filterwarnings(action, message="", category=Warning, module="", lineno=0, + append=False): + """Insert an entry into the list of warnings filters (at the front). + + 'action' -- one of "error", "ignore", "always", "default", "module", + or "once" + 'message' -- a regex that the warning message must match + 'category' -- a class that the warning must be a subclass of + 'module' -- a regex that the module name must match + 'lineno' -- an integer line number, 0 matches all warnings + 'append' -- if true, append to the list of filters + """ + assert action in ("error", "ignore", "always", "default", "module", + "once"), "invalid action: %r" % (action,) + assert isinstance(message, str), "message must be a string" + assert isinstance(category, type), "category must be a class" + assert issubclass(category, Warning), "category must be a Warning subclass" + assert isinstance(module, str), "module must be a string" + assert isinstance(lineno, int) and lineno >= 0, \ + "lineno must be an int >= 0" + + if message or module: + import re + + if message: + message = re.compile(message, re.I) + else: + message = None + if module: + module = re.compile(module) + else: + module = None + + _add_filter(action, message, category, module, lineno, append=append) + +def simplefilter(action, category=Warning, lineno=0, append=False): + """Insert a simple entry into the list of warnings filters (at the front). + + A simple filter matches all modules and messages. + 'action' -- one of "error", "ignore", "always", "default", "module", + or "once" + 'category' -- a class that the warning must be a subclass of + 'lineno' -- an integer line number, 0 matches all warnings + 'append' -- if true, append to the list of filters + """ + assert action in ("error", "ignore", "always", "default", "module", + "once"), "invalid action: %r" % (action,) + assert isinstance(lineno, int) and lineno >= 0, \ + "lineno must be an int >= 0" + _add_filter(action, None, category, None, lineno, append=append) + +def _add_filter(*item, append): + # Remove possible duplicate filters, so new one will be placed + # in correct place. If append=True and duplicate exists, do nothing. + if not append: + try: + filters.remove(item) + except ValueError: + pass + filters.insert(0, item) + else: + if item not in filters: + filters.append(item) + _filters_mutated() + +def resetwarnings(): + """Clear the list of warning filters, so that no filters are active.""" + filters[:] = [] + _filters_mutated() + +class _OptionError(Exception): + """Exception used by option processing helpers.""" + pass + +# Helper to process -W options passed via sys.warnoptions +def _processoptions(args): + for arg in args: + try: + _setoption(arg) + except _OptionError as msg: + print("Invalid -W option ignored:", msg, file=sys.stderr) + +# Helper for _processoptions() +def _setoption(arg): + parts = arg.split(':') + if len(parts) > 5: + raise _OptionError("too many fields (max 5): %r" % (arg,)) + while len(parts) < 5: + parts.append('') + action, message, category, module, lineno = [s.strip() + for s in parts] + action = _getaction(action) + category = _getcategory(category) + if message or module: + import re + if message: + message = re.escape(message) + if module: + module = re.escape(module) + r'\Z' + if lineno: + try: + lineno = int(lineno) + if lineno < 0: + raise ValueError + except (ValueError, OverflowError): + raise _OptionError("invalid lineno %r" % (lineno,)) from None + else: + lineno = 0 + filterwarnings(action, message, category, module, lineno) + +# Helper for _setoption() +def _getaction(action): + if not action: + return "default" + if action == "all": return "always" # Alias + for a in ('default', 'always', 'ignore', 'module', 'once', 'error'): + if a.startswith(action): + return a + raise _OptionError("invalid action: %r" % (action,)) + +# Helper for _setoption() +def _getcategory(category): + if not category: + return Warning + if '.' not in category: + import builtins as m + klass = category + else: + module, _, klass = category.rpartition('.') + try: + m = __import__(module, None, None, [klass]) + except ImportError: + raise _OptionError("invalid module name: %r" % (module,)) from None + try: + cat = getattr(m, klass) + except AttributeError: + raise _OptionError("unknown warning category: %r" % (category,)) from None + if not issubclass(cat, Warning): + raise _OptionError("invalid warning category: %r" % (category,)) + return cat + + +def _is_internal_frame(frame): + """Signal whether the frame is an internal CPython implementation detail.""" + filename = frame.f_code.co_filename + return 'importlib' in filename and '_bootstrap' in filename + + +def _next_external_frame(frame): + """Find the next frame that doesn't involve CPython internals.""" + frame = frame.f_back + while frame is not None and _is_internal_frame(frame): + frame = frame.f_back + return frame + + +# Code typically replaced by _warnings +def warn(message, category=None, stacklevel=1, source=None): + """Issue a warning, or maybe ignore it or raise an exception.""" + # Check if message is already a Warning object + if isinstance(message, Warning): + category = message.__class__ + # Check category argument + if category is None: + category = UserWarning + if not (isinstance(category, type) and issubclass(category, Warning)): + raise TypeError("category must be a Warning subclass, " + "not '{:s}'".format(type(category).__name__)) + # Get context information + try: + if stacklevel <= 1 or _is_internal_frame(sys._getframe(1)): + # If frame is too small to care or if the warning originated in + # internal code, then do not try to hide any frames. + frame = sys._getframe(stacklevel) + else: + frame = sys._getframe(1) + # Look for one frame less since the above line starts us off. + for x in range(stacklevel-1): + frame = _next_external_frame(frame) + if frame is None: + raise ValueError + except ValueError: + globals = sys.__dict__ + filename = "sys" + lineno = 1 + else: + globals = frame.f_globals + filename = frame.f_code.co_filename + lineno = frame.f_lineno + if '__name__' in globals: + module = globals['__name__'] + else: + module = "" + registry = globals.setdefault("__warningregistry__", {}) + warn_explicit(message, category, filename, lineno, module, registry, + globals, source) + +def warn_explicit(message, category, filename, lineno, + module=None, registry=None, module_globals=None, + source=None): + lineno = int(lineno) + if module is None: + module = filename or "" + if module[-3:].lower() == ".py": + module = module[:-3] # XXX What about leading pathname? + if registry is None: + registry = {} + if registry.get('version', 0) != _filters_version: + registry.clear() + registry['version'] = _filters_version + if isinstance(message, Warning): + text = str(message) + category = message.__class__ + else: + text = message + message = category(message) + key = (text, category, lineno) + # Quick test for common case + if registry.get(key): + return + # Search the filters + for item in filters: + action, msg, cat, mod, ln = item + if ((msg is None or msg.match(text)) and + issubclass(category, cat) and + (mod is None or mod.match(module)) and + (ln == 0 or lineno == ln)): + break + else: + action = defaultaction + # Early exit actions + if action == "ignore": + return + + # Prime the linecache for formatting, in case the + # "file" is actually in a zipfile or something. + import linecache + linecache.getlines(filename, module_globals) + + if action == "error": + raise message + # Other actions + if action == "once": + registry[key] = 1 + oncekey = (text, category) + if onceregistry.get(oncekey): + return + onceregistry[oncekey] = 1 + elif action == "always": + pass + elif action == "module": + registry[key] = 1 + altkey = (text, category, 0) + if registry.get(altkey): + return + registry[altkey] = 1 + elif action == "default": + registry[key] = 1 + else: + # Unrecognized actions are errors + raise RuntimeError( + "Unrecognized action (%r) in warnings.filters:\n %s" % + (action, item)) + # Print message and context + msg = WarningMessage(message, category, filename, lineno, source) + _showwarnmsg(msg) + + +class WarningMessage(object): + + _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file", + "line", "source") + + def __init__(self, message, category, filename, lineno, file=None, + line=None, source=None): + self.message = message + self.category = category + self.filename = filename + self.lineno = lineno + self.file = file + self.line = line + self.source = source + self._category_name = category.__name__ if category else None + + def __str__(self): + return ("{message : %r, category : %r, filename : %r, lineno : %s, " + "line : %r}" % (self.message, self._category_name, + self.filename, self.lineno, self.line)) + + +class catch_warnings(object): + + """A context manager that copies and restores the warnings filter upon + exiting the context. + + The 'record' argument specifies whether warnings should be captured by a + custom implementation of warnings.showwarning() and be appended to a list + returned by the context manager. Otherwise None is returned by the context + manager. The objects appended to the list are arguments whose attributes + mirror the arguments to showwarning(). + + The 'module' argument is to specify an alternative module to the module + named 'warnings' and imported under that name. This argument is only useful + when testing the warnings module itself. + + """ + + def __init__(self, *, record=False, module=None): + """Specify whether to record warnings and if an alternative module + should be used other than sys.modules['warnings']. + + For compatibility with Python 3.0, please consider all arguments to be + keyword-only. + + """ + self._record = record + self._module = sys.modules['warnings'] if module is None else module + self._entered = False + + def __repr__(self): + args = [] + if self._record: + args.append("record=True") + if self._module is not sys.modules['warnings']: + args.append("module=%r" % self._module) + name = type(self).__name__ + return "%s(%s)" % (name, ", ".join(args)) + + def __enter__(self): + if self._entered: + raise RuntimeError("Cannot enter %r twice" % self) + self._entered = True + self._filters = self._module.filters + self._module.filters = self._filters[:] + self._module._filters_mutated() + self._showwarning = self._module.showwarning + self._showwarnmsg_impl = self._module._showwarnmsg_impl + if self._record: + log = [] + self._module._showwarnmsg_impl = log.append + # Reset showwarning() to the default implementation to make sure + # that _showwarnmsg() calls _showwarnmsg_impl() + self._module.showwarning = self._module._showwarning_orig + return log + else: + return None + + def __exit__(self, *exc_info): + if not self._entered: + raise RuntimeError("Cannot exit %r without entering first" % self) + self._module.filters = self._filters + self._module._filters_mutated() + self._module.showwarning = self._showwarning + self._module._showwarnmsg_impl = self._showwarnmsg_impl + + +# Private utility function called by _PyErr_WarnUnawaitedCoroutine +def _warn_unawaited_coroutine(coro): + msg_lines = [ + f"coroutine '{coro.__qualname__}' was never awaited\n" + ] + if coro.cr_origin is not None: + import linecache, traceback + def extract(): + for filename, lineno, funcname in reversed(coro.cr_origin): + line = linecache.getline(filename, lineno) + yield (filename, lineno, funcname, line) + msg_lines.append("Coroutine created at (most recent call last)\n") + msg_lines += traceback.format_list(list(extract())) + msg = "".join(msg_lines).rstrip("\n") + # Passing source= here means that if the user happens to have tracemalloc + # enabled and tracking where the coroutine was created, the warning will + # contain that traceback. This does mean that if they have *both* + # coroutine origin tracking *and* tracemalloc enabled, they'll get two + # partially-redundant tracebacks. If we wanted to be clever we could + # probably detect this case and avoid it, but for now we don't bother. + warn(msg, category=RuntimeWarning, stacklevel=2, source=coro) + + +# filters contains a sequence of filter 5-tuples +# The components of the 5-tuple are: +# - an action: error, ignore, always, default, module, or once +# - a compiled regex that must match the warning message +# - a class representing the warning category +# - a compiled regex that must match the module that is being warned +# - a line number for the line being warning, or 0 to mean any line +# If either if the compiled regexs are None, match anything. +try: + from _warnings import (filters, _defaultaction, _onceregistry, + warn, warn_explicit, _filters_mutated) + defaultaction = _defaultaction + onceregistry = _onceregistry + _warnings_defaults = True +except ImportError: + filters = [] + defaultaction = "default" + onceregistry = {} + + _filters_version = 1 + + def _filters_mutated(): + global _filters_version + _filters_version += 1 + + _warnings_defaults = False + + +# Module initialization +_processoptions(sys.warnoptions) +if not _warnings_defaults: + # Several warning categories are ignored by default in regular builds + if not hasattr(sys, 'gettotalrefcount'): + filterwarnings("default", category=DeprecationWarning, + module="__main__", append=1) + simplefilter("ignore", category=DeprecationWarning, append=1) + simplefilter("ignore", category=PendingDeprecationWarning, append=1) + simplefilter("ignore", category=ImportWarning, append=1) + simplefilter("ignore", category=ResourceWarning, append=1) + +del _warnings_defaults diff --git a/lib/python3.10/wave.py b/lib/python3.10/wave.py new file mode 100644 index 0000000000000000000000000000000000000000..b7071198e6b8413cea89839a221f613779506714 --- /dev/null +++ b/lib/python3.10/wave.py @@ -0,0 +1,513 @@ +"""Stuff to parse WAVE files. + +Usage. + +Reading WAVE files: + f = wave.open(file, 'r') +where file is either the name of a file or an open file pointer. +The open file pointer must have methods read(), seek(), and close(). +When the setpos() and rewind() methods are not used, the seek() +method is not necessary. + +This returns an instance of a class with the following public methods: + getnchannels() -- returns number of audio channels (1 for + mono, 2 for stereo) + getsampwidth() -- returns sample width in bytes + getframerate() -- returns sampling frequency + getnframes() -- returns number of audio frames + getcomptype() -- returns compression type ('NONE' for linear samples) + getcompname() -- returns human-readable version of + compression type ('not compressed' linear samples) + getparams() -- returns a namedtuple consisting of all of the + above in the above order + getmarkers() -- returns None (for compatibility with the + aifc module) + getmark(id) -- raises an error since the mark does not + exist (for compatibility with the aifc module) + readframes(n) -- returns at most n frames of audio + rewind() -- rewind to the beginning of the audio stream + setpos(pos) -- seek to the specified position + tell() -- return the current position + close() -- close the instance (make it unusable) +The position returned by tell() and the position given to setpos() +are compatible and have nothing to do with the actual position in the +file. +The close() method is called automatically when the class instance +is destroyed. + +Writing WAVE files: + f = wave.open(file, 'w') +where file is either the name of a file or an open file pointer. +The open file pointer must have methods write(), tell(), seek(), and +close(). + +This returns an instance of a class with the following public methods: + setnchannels(n) -- set the number of channels + setsampwidth(n) -- set the sample width + setframerate(n) -- set the frame rate + setnframes(n) -- set the number of frames + setcomptype(type, name) + -- set the compression type and the + human-readable compression type + setparams(tuple) + -- set all parameters at once + tell() -- return current position in output file + writeframesraw(data) + -- write audio frames without patching up the + file header + writeframes(data) + -- write audio frames and patch up the file header + close() -- patch up the file header and close the + output file +You should set the parameters before the first writeframesraw or +writeframes. The total number of frames does not need to be set, +but when it is set to the correct value, the header does not have to +be patched up. +It is best to first set all parameters, perhaps possibly the +compression type, and then write audio frames using writeframesraw. +When all frames have been written, either call writeframes(b'') or +close() to patch up the sizes in the header. +The close() method is called automatically when the class instance +is destroyed. +""" + +from chunk import Chunk +from collections import namedtuple +import audioop +import builtins +import struct +import sys + + +__all__ = ["open", "Error", "Wave_read", "Wave_write"] + +class Error(Exception): + pass + +WAVE_FORMAT_PCM = 0x0001 + +_array_fmts = None, 'b', 'h', None, 'i' + +_wave_params = namedtuple('_wave_params', + 'nchannels sampwidth framerate nframes comptype compname') + +class Wave_read: + """Variables used in this class: + + These variables are available to the user though appropriate + methods of this class: + _file -- the open file with methods read(), close(), and seek() + set through the __init__() method + _nchannels -- the number of audio channels + available through the getnchannels() method + _nframes -- the number of audio frames + available through the getnframes() method + _sampwidth -- the number of bytes per audio sample + available through the getsampwidth() method + _framerate -- the sampling frequency + available through the getframerate() method + _comptype -- the AIFF-C compression type ('NONE' if AIFF) + available through the getcomptype() method + _compname -- the human-readable AIFF-C compression type + available through the getcomptype() method + _soundpos -- the position in the audio stream + available through the tell() method, set through the + setpos() method + + These variables are used internally only: + _fmt_chunk_read -- 1 iff the FMT chunk has been read + _data_seek_needed -- 1 iff positioned correctly in audio + file for readframes() + _data_chunk -- instantiation of a chunk class for the DATA chunk + _framesize -- size of one frame in the file + """ + + def initfp(self, file): + self._convert = None + self._soundpos = 0 + self._file = Chunk(file, bigendian = 0) + if self._file.getname() != b'RIFF': + raise Error('file does not start with RIFF id') + if self._file.read(4) != b'WAVE': + raise Error('not a WAVE file') + self._fmt_chunk_read = 0 + self._data_chunk = None + while 1: + self._data_seek_needed = 1 + try: + chunk = Chunk(self._file, bigendian = 0) + except EOFError: + break + chunkname = chunk.getname() + if chunkname == b'fmt ': + self._read_fmt_chunk(chunk) + self._fmt_chunk_read = 1 + elif chunkname == b'data': + if not self._fmt_chunk_read: + raise Error('data chunk before fmt chunk') + self._data_chunk = chunk + self._nframes = chunk.chunksize // self._framesize + self._data_seek_needed = 0 + break + chunk.skip() + if not self._fmt_chunk_read or not self._data_chunk: + raise Error('fmt chunk and/or data chunk missing') + + def __init__(self, f): + self._i_opened_the_file = None + if isinstance(f, str): + f = builtins.open(f, 'rb') + self._i_opened_the_file = f + # else, assume it is an open file object already + try: + self.initfp(f) + except: + if self._i_opened_the_file: + f.close() + raise + + def __del__(self): + self.close() + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + # + # User visible methods. + # + def getfp(self): + return self._file + + def rewind(self): + self._data_seek_needed = 1 + self._soundpos = 0 + + def close(self): + self._file = None + file = self._i_opened_the_file + if file: + self._i_opened_the_file = None + file.close() + + def tell(self): + return self._soundpos + + def getnchannels(self): + return self._nchannels + + def getnframes(self): + return self._nframes + + def getsampwidth(self): + return self._sampwidth + + def getframerate(self): + return self._framerate + + def getcomptype(self): + return self._comptype + + def getcompname(self): + return self._compname + + def getparams(self): + return _wave_params(self.getnchannels(), self.getsampwidth(), + self.getframerate(), self.getnframes(), + self.getcomptype(), self.getcompname()) + + def getmarkers(self): + return None + + def getmark(self, id): + raise Error('no marks') + + def setpos(self, pos): + if pos < 0 or pos > self._nframes: + raise Error('position not in range') + self._soundpos = pos + self._data_seek_needed = 1 + + def readframes(self, nframes): + if self._data_seek_needed: + self._data_chunk.seek(0, 0) + pos = self._soundpos * self._framesize + if pos: + self._data_chunk.seek(pos, 0) + self._data_seek_needed = 0 + if nframes == 0: + return b'' + data = self._data_chunk.read(nframes * self._framesize) + if self._sampwidth != 1 and sys.byteorder == 'big': + data = audioop.byteswap(data, self._sampwidth) + if self._convert and data: + data = self._convert(data) + self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth) + return data + + # + # Internal methods. + # + + def _read_fmt_chunk(self, chunk): + try: + wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack_from(' 4: + raise Error('bad sample width') + self._sampwidth = sampwidth + + def getsampwidth(self): + if not self._sampwidth: + raise Error('sample width not set') + return self._sampwidth + + def setframerate(self, framerate): + if self._datawritten: + raise Error('cannot change parameters after starting to write') + if framerate <= 0: + raise Error('bad frame rate') + self._framerate = int(round(framerate)) + + def getframerate(self): + if not self._framerate: + raise Error('frame rate not set') + return self._framerate + + def setnframes(self, nframes): + if self._datawritten: + raise Error('cannot change parameters after starting to write') + self._nframes = nframes + + def getnframes(self): + return self._nframeswritten + + def setcomptype(self, comptype, compname): + if self._datawritten: + raise Error('cannot change parameters after starting to write') + if comptype not in ('NONE',): + raise Error('unsupported compression type') + self._comptype = comptype + self._compname = compname + + def getcomptype(self): + return self._comptype + + def getcompname(self): + return self._compname + + def setparams(self, params): + nchannels, sampwidth, framerate, nframes, comptype, compname = params + if self._datawritten: + raise Error('cannot change parameters after starting to write') + self.setnchannels(nchannels) + self.setsampwidth(sampwidth) + self.setframerate(framerate) + self.setnframes(nframes) + self.setcomptype(comptype, compname) + + def getparams(self): + if not self._nchannels or not self._sampwidth or not self._framerate: + raise Error('not all parameters set') + return _wave_params(self._nchannels, self._sampwidth, self._framerate, + self._nframes, self._comptype, self._compname) + + def setmark(self, id, pos, name): + raise Error('setmark() not supported') + + def getmark(self, id): + raise Error('no marks') + + def getmarkers(self): + return None + + def tell(self): + return self._nframeswritten + + def writeframesraw(self, data): + if not isinstance(data, (bytes, bytearray)): + data = memoryview(data).cast('B') + self._ensure_header_written(len(data)) + nframes = len(data) // (self._sampwidth * self._nchannels) + if self._convert: + data = self._convert(data) + if self._sampwidth != 1 and sys.byteorder == 'big': + data = audioop.byteswap(data, self._sampwidth) + self._file.write(data) + self._datawritten += len(data) + self._nframeswritten = self._nframeswritten + nframes + + def writeframes(self, data): + self.writeframesraw(data) + if self._datalength != self._datawritten: + self._patchheader() + + def close(self): + try: + if self._file: + self._ensure_header_written(0) + if self._datalength != self._datawritten: + self._patchheader() + self._file.flush() + finally: + self._file = None + file = self._i_opened_the_file + if file: + self._i_opened_the_file = None + file.close() + + # + # Internal methods. + # + + def _ensure_header_written(self, datasize): + if not self._headerwritten: + if not self._nchannels: + raise Error('# channels not specified') + if not self._sampwidth: + raise Error('sample width not specified') + if not self._framerate: + raise Error('sampling rate not specified') + self._write_header(datasize) + + def _write_header(self, initlength): + assert not self._headerwritten + self._file.write(b'RIFF') + if not self._nframes: + self._nframes = initlength // (self._nchannels * self._sampwidth) + self._datalength = self._nframes * self._nchannels * self._sampwidth + try: + self._form_length_pos = self._file.tell() + except (AttributeError, OSError): + self._form_length_pos = None + self._file.write(struct.pack('" % (self.__class__.__name__, id(self)) + + def __setitem__(self, key, value): + if self._pending_removals: + self._commit_removals() + self.data[key] = KeyedRef(value, self._remove, key) + + def copy(self): + if self._pending_removals: + self._commit_removals() + new = WeakValueDictionary() + with _IterationGuard(self): + for key, wr in self.data.items(): + o = wr() + if o is not None: + new[key] = o + return new + + __copy__ = copy + + def __deepcopy__(self, memo): + from copy import deepcopy + if self._pending_removals: + self._commit_removals() + new = self.__class__() + with _IterationGuard(self): + for key, wr in self.data.items(): + o = wr() + if o is not None: + new[deepcopy(key, memo)] = o + return new + + def get(self, key, default=None): + if self._pending_removals: + self._commit_removals() + try: + wr = self.data[key] + except KeyError: + return default + else: + o = wr() + if o is None: + # This should only happen + return default + else: + return o + + def items(self): + if self._pending_removals: + self._commit_removals() + with _IterationGuard(self): + for k, wr in self.data.items(): + v = wr() + if v is not None: + yield k, v + + def keys(self): + if self._pending_removals: + self._commit_removals() + with _IterationGuard(self): + for k, wr in self.data.items(): + if wr() is not None: + yield k + + __iter__ = keys + + def itervaluerefs(self): + """Return an iterator that yields the weak references to the values. + + The references are not guaranteed to be 'live' at the time + they are used, so the result of calling the references needs + to be checked before being used. This can be used to avoid + creating references that will cause the garbage collector to + keep the values around longer than needed. + + """ + if self._pending_removals: + self._commit_removals() + with _IterationGuard(self): + yield from self.data.values() + + def values(self): + if self._pending_removals: + self._commit_removals() + with _IterationGuard(self): + for wr in self.data.values(): + obj = wr() + if obj is not None: + yield obj + + def popitem(self): + if self._pending_removals: + self._commit_removals() + while True: + key, wr = self.data.popitem() + o = wr() + if o is not None: + return key, o + + def pop(self, key, *args): + if self._pending_removals: + self._commit_removals() + try: + o = self.data.pop(key)() + except KeyError: + o = None + if o is None: + if args: + return args[0] + else: + raise KeyError(key) + else: + return o + + def setdefault(self, key, default=None): + try: + o = self.data[key]() + except KeyError: + o = None + if o is None: + if self._pending_removals: + self._commit_removals() + self.data[key] = KeyedRef(default, self._remove, key) + return default + else: + return o + + def update(self, other=None, /, **kwargs): + if self._pending_removals: + self._commit_removals() + d = self.data + if other is not None: + if not hasattr(other, "items"): + other = dict(other) + for key, o in other.items(): + d[key] = KeyedRef(o, self._remove, key) + for key, o in kwargs.items(): + d[key] = KeyedRef(o, self._remove, key) + + def valuerefs(self): + """Return a list of weak references to the values. + + The references are not guaranteed to be 'live' at the time + they are used, so the result of calling the references needs + to be checked before being used. This can be used to avoid + creating references that will cause the garbage collector to + keep the values around longer than needed. + + """ + if self._pending_removals: + self._commit_removals() + return list(self.data.values()) + + def __ior__(self, other): + self.update(other) + return self + + def __or__(self, other): + if isinstance(other, _collections_abc.Mapping): + c = self.copy() + c.update(other) + return c + return NotImplemented + + def __ror__(self, other): + if isinstance(other, _collections_abc.Mapping): + c = self.__class__() + c.update(other) + c.update(self) + return c + return NotImplemented + + +class KeyedRef(ref): + """Specialized reference that includes a key corresponding to the value. + + This is used in the WeakValueDictionary to avoid having to create + a function object for each key stored in the mapping. A shared + callback object can use the 'key' attribute of a KeyedRef instead + of getting a reference to the key from an enclosing scope. + + """ + + __slots__ = "key", + + def __new__(type, ob, callback, key): + self = ref.__new__(type, ob, callback) + self.key = key + return self + + def __init__(self, ob, callback, key): + super().__init__(ob, callback) + + +class WeakKeyDictionary(_collections_abc.MutableMapping): + """ Mapping class that references keys weakly. + + Entries in the dictionary will be discarded when there is no + longer a strong reference to the key. This can be used to + associate additional data with an object owned by other parts of + an application without adding attributes to those objects. This + can be especially useful with objects that override attribute + accesses. + """ + + def __init__(self, dict=None): + self.data = {} + def remove(k, selfref=ref(self)): + self = selfref() + if self is not None: + if self._iterating: + self._pending_removals.append(k) + else: + try: + del self.data[k] + except KeyError: + pass + self._remove = remove + # A list of dead weakrefs (keys to be removed) + self._pending_removals = [] + self._iterating = set() + self._dirty_len = False + if dict is not None: + self.update(dict) + + def _commit_removals(self): + # NOTE: We don't need to call this method before mutating the dict, + # because a dead weakref never compares equal to a live weakref, + # even if they happened to refer to equal objects. + # However, it means keys may already have been removed. + pop = self._pending_removals.pop + d = self.data + while True: + try: + key = pop() + except IndexError: + return + + try: + del d[key] + except KeyError: + pass + + def _scrub_removals(self): + d = self.data + self._pending_removals = [k for k in self._pending_removals if k in d] + self._dirty_len = False + + def __delitem__(self, key): + self._dirty_len = True + del self.data[ref(key)] + + def __getitem__(self, key): + return self.data[ref(key)] + + def __len__(self): + if self._dirty_len and self._pending_removals: + # self._pending_removals may still contain keys which were + # explicitly removed, we have to scrub them (see issue #21173). + self._scrub_removals() + return len(self.data) - len(self._pending_removals) + + def __repr__(self): + return "<%s at %#x>" % (self.__class__.__name__, id(self)) + + def __setitem__(self, key, value): + self.data[ref(key, self._remove)] = value + + def copy(self): + new = WeakKeyDictionary() + with _IterationGuard(self): + for key, value in self.data.items(): + o = key() + if o is not None: + new[o] = value + return new + + __copy__ = copy + + def __deepcopy__(self, memo): + from copy import deepcopy + new = self.__class__() + with _IterationGuard(self): + for key, value in self.data.items(): + o = key() + if o is not None: + new[o] = deepcopy(value, memo) + return new + + def get(self, key, default=None): + return self.data.get(ref(key),default) + + def __contains__(self, key): + try: + wr = ref(key) + except TypeError: + return False + return wr in self.data + + def items(self): + with _IterationGuard(self): + for wr, value in self.data.items(): + key = wr() + if key is not None: + yield key, value + + def keys(self): + with _IterationGuard(self): + for wr in self.data: + obj = wr() + if obj is not None: + yield obj + + __iter__ = keys + + def values(self): + with _IterationGuard(self): + for wr, value in self.data.items(): + if wr() is not None: + yield value + + def keyrefs(self): + """Return a list of weak references to the keys. + + The references are not guaranteed to be 'live' at the time + they are used, so the result of calling the references needs + to be checked before being used. This can be used to avoid + creating references that will cause the garbage collector to + keep the keys around longer than needed. + + """ + return list(self.data) + + def popitem(self): + self._dirty_len = True + while True: + key, value = self.data.popitem() + o = key() + if o is not None: + return o, value + + def pop(self, key, *args): + self._dirty_len = True + return self.data.pop(ref(key), *args) + + def setdefault(self, key, default=None): + return self.data.setdefault(ref(key, self._remove),default) + + def update(self, dict=None, /, **kwargs): + d = self.data + if dict is not None: + if not hasattr(dict, "items"): + dict = type({})(dict) + for key, value in dict.items(): + d[ref(key, self._remove)] = value + if len(kwargs): + self.update(kwargs) + + def __ior__(self, other): + self.update(other) + return self + + def __or__(self, other): + if isinstance(other, _collections_abc.Mapping): + c = self.copy() + c.update(other) + return c + return NotImplemented + + def __ror__(self, other): + if isinstance(other, _collections_abc.Mapping): + c = self.__class__() + c.update(other) + c.update(self) + return c + return NotImplemented + + +class finalize: + """Class for finalization of weakrefable objects + + finalize(obj, func, *args, **kwargs) returns a callable finalizer + object which will be called when obj is garbage collected. The + first time the finalizer is called it evaluates func(*arg, **kwargs) + and returns the result. After this the finalizer is dead, and + calling it just returns None. + + When the program exits any remaining finalizers for which the + atexit attribute is true will be run in reverse order of creation. + By default atexit is true. + """ + + # Finalizer objects don't have any state of their own. They are + # just used as keys to lookup _Info objects in the registry. This + # ensures that they cannot be part of a ref-cycle. + + __slots__ = () + _registry = {} + _shutdown = False + _index_iter = itertools.count() + _dirty = False + _registered_with_atexit = False + + class _Info: + __slots__ = ("weakref", "func", "args", "kwargs", "atexit", "index") + + def __init__(self, obj, func, /, *args, **kwargs): + if not self._registered_with_atexit: + # We may register the exit function more than once because + # of a thread race, but that is harmless + import atexit + atexit.register(self._exitfunc) + finalize._registered_with_atexit = True + info = self._Info() + info.weakref = ref(obj, self) + info.func = func + info.args = args + info.kwargs = kwargs or None + info.atexit = True + info.index = next(self._index_iter) + self._registry[self] = info + finalize._dirty = True + + def __call__(self, _=None): + """If alive then mark as dead and return func(*args, **kwargs); + otherwise return None""" + info = self._registry.pop(self, None) + if info and not self._shutdown: + return info.func(*info.args, **(info.kwargs or {})) + + def detach(self): + """If alive then mark as dead and return (obj, func, args, kwargs); + otherwise return None""" + info = self._registry.get(self) + obj = info and info.weakref() + if obj is not None and self._registry.pop(self, None): + return (obj, info.func, info.args, info.kwargs or {}) + + def peek(self): + """If alive then return (obj, func, args, kwargs); + otherwise return None""" + info = self._registry.get(self) + obj = info and info.weakref() + if obj is not None: + return (obj, info.func, info.args, info.kwargs or {}) + + @property + def alive(self): + """Whether finalizer is alive""" + return self in self._registry + + @property + def atexit(self): + """Whether finalizer should be called at exit""" + info = self._registry.get(self) + return bool(info) and info.atexit + + @atexit.setter + def atexit(self, value): + info = self._registry.get(self) + if info: + info.atexit = bool(value) + + def __repr__(self): + info = self._registry.get(self) + obj = info and info.weakref() + if obj is None: + return '<%s object at %#x; dead>' % (type(self).__name__, id(self)) + else: + return '<%s object at %#x; for %r at %#x>' % \ + (type(self).__name__, id(self), type(obj).__name__, id(obj)) + + @classmethod + def _select_for_exit(cls): + # Return live finalizers marked for exit, oldest first + L = [(f,i) for (f,i) in cls._registry.items() if i.atexit] + L.sort(key=lambda item:item[1].index) + return [f for (f,i) in L] + + @classmethod + def _exitfunc(cls): + # At shutdown invoke finalizers for which atexit is true. + # This is called once all other non-daemonic threads have been + # joined. + reenable_gc = False + try: + if cls._registry: + import gc + if gc.isenabled(): + reenable_gc = True + gc.disable() + pending = None + while True: + if pending is None or finalize._dirty: + pending = cls._select_for_exit() + finalize._dirty = False + if not pending: + break + f = pending.pop() + try: + # gc is disabled, so (assuming no daemonic + # threads) the following is the only line in + # this function which might trigger creation + # of a new finalizer + f() + except Exception: + sys.excepthook(*sys.exc_info()) + assert f not in cls._registry + finally: + # prevent any more finalizers from executing during shutdown + finalize._shutdown = True + if reenable_gc: + gc.enable() diff --git a/lib/sqlite3.44.2/pkgIndex.tcl b/lib/sqlite3.44.2/pkgIndex.tcl new file mode 100644 index 0000000000000000000000000000000000000000..3a4d4a1148e309acc3c3bf9ca7a8a3ba354e2c31 --- /dev/null +++ b/lib/sqlite3.44.2/pkgIndex.tcl @@ -0,0 +1,12 @@ +# -*- tcl -*- +# Tcl package index file, version 1.1 +# +# Note sqlite*3* init specifically +# +if {[package vsatisfies [package provide Tcl] 9.0-]} { + package ifneeded sqlite3 3.44.2 \ + [list load [file join $dir libtcl9sqlite3.44.2.so] Sqlite3] +} else { + package ifneeded sqlite3 3.44.2 \ + [list load [file join $dir libsqlite3.44.2.so] Sqlite3] +} diff --git a/lib/tcl8.6/auto.tcl b/lib/tcl8.6/auto.tcl new file mode 100644 index 0000000000000000000000000000000000000000..f293a38f0433f426e30a168392075f52140dc678 --- /dev/null +++ b/lib/tcl8.6/auto.tcl @@ -0,0 +1,648 @@ +# auto.tcl -- +# +# utility procs formerly in init.tcl dealing with auto execution of commands +# and can be auto loaded themselves. +# +# Copyright (c) 1991-1993 The Regents of the University of California. +# Copyright (c) 1994-1998 Sun Microsystems, Inc. +# +# See the file "license.terms" for information on usage and redistribution of +# this file, and for a DISCLAIMER OF ALL WARRANTIES. +# + +# auto_reset -- +# +# Destroy all cached information for auto-loading and auto-execution, so that +# the information gets recomputed the next time it's needed. Also delete any +# commands that are listed in the auto-load index. +# +# Arguments: +# None. + +proc auto_reset {} { + global auto_execs auto_index auto_path + if {[array exists auto_index]} { + foreach cmdName [array names auto_index] { + set fqcn [namespace which $cmdName] + if {$fqcn eq ""} { + continue + } + rename $fqcn {} + } + } + unset -nocomplain auto_execs auto_index ::tcl::auto_oldpath + if {[catch {llength $auto_path}]} { + set auto_path [list [info library]] + } elseif {[info library] ni $auto_path} { + lappend auto_path [info library] + } +} + +# tcl_findLibrary -- +# +# This is a utility for extensions that searches for a library directory +# using a canonical searching algorithm. A side effect is to source the +# initialization script and set a global library variable. +# +# Arguments: +# basename Prefix of the directory name, (e.g., "tk") +# version Version number of the package, (e.g., "8.0") +# patch Patchlevel of the package, (e.g., "8.0.3") +# initScript Initialization script to source (e.g., tk.tcl) +# enVarName environment variable to honor (e.g., TK_LIBRARY) +# varName Global variable to set when done (e.g., tk_library) + +proc tcl_findLibrary {basename version patch initScript enVarName varName} { + upvar #0 $varName the_library + global auto_path env tcl_platform + + set dirs {} + set errors {} + + # The C application may have hardwired a path, which we honor + + if {[info exists the_library] && $the_library ne ""} { + lappend dirs $the_library + } else { + # Do the canonical search + + # 1. From an environment variable, if it exists. Placing this first + # gives the end-user ultimate control to work-around any bugs, or + # to customize. + + if {[info exists env($enVarName)]} { + lappend dirs $env($enVarName) + } + + # 2. In the package script directory registered within the + # configuration of the package itself. + + catch { + lappend dirs [::${basename}::pkgconfig get scriptdir,runtime] + } + + # 3. Relative to auto_path directories. This checks relative to the + # Tcl library as well as allowing loading of libraries added to the + # auto_path that is not relative to the core library or binary paths. + foreach d $auto_path { + lappend dirs [file join $d $basename$version] + if {$tcl_platform(platform) eq "unix" + && $tcl_platform(os) eq "Darwin"} { + # 4. On MacOSX, check the Resources/Scripts subdir too + lappend dirs [file join $d $basename$version Resources Scripts] + } + } + + # 3. Various locations relative to the executable + # ../lib/foo1.0 (From bin directory in install hierarchy) + # ../../lib/foo1.0 (From bin/arch directory in install hierarchy) + # ../library (From unix directory in build hierarchy) + # + # Remaining locations are out of date (when relevant, they ought to be + # covered by the $::auto_path seach above) and disabled. + # + # ../../library (From unix/arch directory in build hierarchy) + # ../../foo1.0.1/library + # (From unix directory in parallel build hierarchy) + # ../../../foo1.0.1/library + # (From unix/arch directory in parallel build hierarchy) + + set parentDir [file dirname [file dirname [info nameofexecutable]]] + set grandParentDir [file dirname $parentDir] + lappend dirs [file join $parentDir lib $basename$version] + lappend dirs [file join $grandParentDir lib $basename$version] + lappend dirs [file join $parentDir library] + if {0} { + lappend dirs [file join $grandParentDir library] + lappend dirs [file join $grandParentDir $basename$patch library] + lappend dirs [file join [file dirname $grandParentDir] \ + $basename$patch library] + } + } + # make $dirs unique, preserving order + array set seen {} + foreach i $dirs { + # Make sure $i is unique under normalization. Avoid repeated [source]. + if {[interp issafe]} { + # Safe interps have no [file normalize]. + set norm $i + } else { + set norm [file normalize $i] + } + if {[info exists seen($norm)]} { + continue + } + set seen($norm) {} + + set the_library $i + set file [file join $i $initScript] + + # source everything when in a safe interpreter because we have a + # source command, but no file exists command + + if {[interp issafe] || [file exists $file]} { + if {![catch {uplevel #0 [list source $file]} msg opts]} { + return + } + append errors "$file: $msg\n" + append errors [dict get $opts -errorinfo]\n + } + } + unset -nocomplain the_library + set msg "Can't find a usable $initScript in the following directories: \n" + append msg " $dirs\n\n" + append msg "$errors\n\n" + append msg "This probably means that $basename wasn't installed properly.\n" + error $msg +} + + +# ---------------------------------------------------------------------- +# auto_mkindex +# ---------------------------------------------------------------------- +# The following procedures are used to generate the tclIndex file from Tcl +# source files. They use a special safe interpreter to parse Tcl source +# files, writing out index entries as "proc" commands are encountered. This +# implementation won't work in a safe interpreter, since a safe interpreter +# can't create the special parser and mess with its commands. + +if {[interp issafe]} { + return ;# Stop sourcing the file here +} + +# auto_mkindex -- +# Regenerate a tclIndex file from Tcl source files. Takes as argument the +# name of the directory in which the tclIndex file is to be placed, followed +# by any number of glob patterns to use in that directory to locate all of the +# relevant files. +# +# Arguments: +# dir - Name of the directory in which to create an index. + +# args - Any number of additional arguments giving the names of files +# within dir. If no additional are given auto_mkindex will look +# for *.tcl. + +proc auto_mkindex {dir args} { + if {[interp issafe]} { + error "can't generate index within safe interpreter" + } + + set oldDir [pwd] + cd $dir + + append index "# Tcl autoload index file, version 2.0\n" + append index "# This file is generated by the \"auto_mkindex\" command\n" + append index "# and sourced to set up indexing information for one or\n" + append index "# more commands. Typically each line is a command that\n" + append index "# sets an element in the auto_index array, where the\n" + append index "# element name is the name of a command and the value is\n" + append index "# a script that loads the command.\n\n" + if {![llength $args]} { + set args *.tcl + } + + auto_mkindex_parser::init + foreach file [lsort [glob -- {*}$args]] { + try { + append index [auto_mkindex_parser::mkindex $file] + } on error {msg opts} { + cd $oldDir + return -options $opts $msg + } + } + auto_mkindex_parser::cleanup + + set fid [open "tclIndex" w] + puts -nonewline $fid $index + close $fid + cd $oldDir +} + +# Original version of auto_mkindex that just searches the source code for +# "proc" at the beginning of the line. + +proc auto_mkindex_old {dir args} { + set oldDir [pwd] + cd $dir + set dir [pwd] + append index "# Tcl autoload index file, version 2.0\n" + append index "# This file is generated by the \"auto_mkindex\" command\n" + append index "# and sourced to set up indexing information for one or\n" + append index "# more commands. Typically each line is a command that\n" + append index "# sets an element in the auto_index array, where the\n" + append index "# element name is the name of a command and the value is\n" + append index "# a script that loads the command.\n\n" + if {![llength $args]} { + set args *.tcl + } + foreach file [lsort [glob -- {*}$args]] { + set f "" + set error [catch { + set f [open $file] + fconfigure $f -eofchar "\x1A {}" + while {[gets $f line] >= 0} { + if {[regexp {^proc[ ]+([^ ]*)} $line match procName]} { + set procName [lindex [auto_qualify $procName "::"] 0] + append index "set [list auto_index($procName)]" + append index " \[list source \[file join \$dir [list $file]\]\]\n" + } + } + close $f + } msg opts] + if {$error} { + catch {close $f} + cd $oldDir + return -options $opts $msg + } + } + set f "" + set error [catch { + set f [open tclIndex w] + puts -nonewline $f $index + close $f + cd $oldDir + } msg opts] + if {$error} { + catch {close $f} + cd $oldDir + error $msg $info $code + return -options $opts $msg + } +} + +# Create a safe interpreter that can be used to parse Tcl source files +# generate a tclIndex file for autoloading. This interp contains commands for +# things that need index entries. Each time a command is executed, it writes +# an entry out to the index file. + +namespace eval auto_mkindex_parser { + variable parser "" ;# parser used to build index + variable index "" ;# maintains index as it is built + variable scriptFile "" ;# name of file being processed + variable contextStack "" ;# stack of namespace scopes + variable imports "" ;# keeps track of all imported cmds + variable initCommands ;# list of commands that create aliases + if {![info exists initCommands]} { + set initCommands [list] + } + + proc init {} { + variable parser + variable initCommands + + if {![interp issafe]} { + set parser [interp create -safe] + $parser hide info + $parser hide rename + $parser hide proc + $parser hide namespace + $parser hide eval + $parser hide puts + foreach ns [$parser invokehidden namespace children ::] { + # MUST NOT DELETE "::tcl" OR BAD THINGS HAPPEN! + if {$ns eq "::tcl"} continue + $parser invokehidden namespace delete $ns + } + foreach cmd [$parser invokehidden info commands ::*] { + $parser invokehidden rename $cmd {} + } + $parser invokehidden proc unknown {args} {} + + # We'll need access to the "namespace" command within the + # interp. Put it back, but move it out of the way. + + $parser expose namespace + $parser invokehidden rename namespace _%@namespace + $parser expose eval + $parser invokehidden rename eval _%@eval + + # Install all the registered pseudo-command implementations + + foreach cmd $initCommands { + eval $cmd + } + } + } + proc cleanup {} { + variable parser + interp delete $parser + unset parser + } +} + +# auto_mkindex_parser::mkindex -- +# +# Used by the "auto_mkindex" command to create a "tclIndex" file for the given +# Tcl source file. Executes the commands in the file, and handles things like +# the "proc" command by adding an entry for the index file. Returns a string +# that represents the index file. +# +# Arguments: +# file Name of Tcl source file to be indexed. + +proc auto_mkindex_parser::mkindex {file} { + variable parser + variable index + variable scriptFile + variable contextStack + variable imports + + set scriptFile $file + + set fid [open $file] + fconfigure $fid -eofchar "\x1A {}" + set contents [read $fid] + close $fid + + # There is one problem with sourcing files into the safe interpreter: + # references like "$x" will fail since code is not really being executed + # and variables do not really exist. To avoid this, we replace all $ with + # \0 (literally, the null char) later, when getting proc names we will + # have to reverse this replacement, in case there were any $ in the proc + # name. This will cause a problem if somebody actually tries to have a \0 + # in their proc name. Too bad for them. + set contents [string map [list \$ \0] $contents] + + set index "" + set contextStack "" + set imports "" + + $parser eval $contents + + foreach name $imports { + catch {$parser eval [list _%@namespace forget $name]} + } + return $index +} + +# auto_mkindex_parser::hook command +# +# Registers a Tcl command to evaluate when initializing the child interpreter +# used by the mkindex parser. The command is evaluated in the parent +# interpreter, and can use the variable auto_mkindex_parser::parser to get to +# the child + +proc auto_mkindex_parser::hook {cmd} { + variable initCommands + + lappend initCommands $cmd +} + +# auto_mkindex_parser::slavehook command +# +# Registers a Tcl command to evaluate when initializing the child interpreter +# used by the mkindex parser. The command is evaluated in the child +# interpreter. + +proc auto_mkindex_parser::slavehook {cmd} { + variable initCommands + + # The $parser variable is defined to be the name of the child interpreter + # when this command is used later. + + lappend initCommands "\$parser eval [list $cmd]" +} + +# auto_mkindex_parser::command -- +# +# Registers a new command with the "auto_mkindex_parser" interpreter that +# parses Tcl files. These commands are fake versions of things like the +# "proc" command. When you execute them, they simply write out an entry to a +# "tclIndex" file for auto-loading. +# +# This procedure allows extensions to register their own commands with the +# auto_mkindex facility. For example, a package like [incr Tcl] might +# register a "class" command so that class definitions could be added to a +# "tclIndex" file for auto-loading. +# +# Arguments: +# name Name of command recognized in Tcl files. +# arglist Argument list for command. +# body Implementation of command to handle indexing. + +proc auto_mkindex_parser::command {name arglist body} { + hook [list auto_mkindex_parser::commandInit $name $arglist $body] +} + +# auto_mkindex_parser::commandInit -- +# +# This does the actual work set up by auto_mkindex_parser::command. This is +# called when the interpreter used by the parser is created. +# +# Arguments: +# name Name of command recognized in Tcl files. +# arglist Argument list for command. +# body Implementation of command to handle indexing. + +proc auto_mkindex_parser::commandInit {name arglist body} { + variable parser + + set ns [namespace qualifiers $name] + set tail [namespace tail $name] + if {$ns eq ""} { + set fakeName [namespace current]::_%@fake_$tail + } else { + set fakeName [namespace current]::[string map {:: _} _%@fake_$name] + } + proc $fakeName $arglist $body + + # YUK! Tcl won't let us alias fully qualified command names, so we can't + # handle names like "::itcl::class". Instead, we have to build procs with + # the fully qualified names, and have the procs point to the aliases. + + if {[string match *::* $name]} { + set exportCmd [list _%@namespace export [namespace tail $name]] + $parser eval [list _%@namespace eval $ns $exportCmd] + + # The following proc definition does not work if you want to tolerate + # space or something else diabolical in the procedure name, (i.e., + # space in $alias). The following does not work: + # "_%@eval {$alias} \$args" + # because $alias gets concat'ed to $args. The following does not work + # because $cmd is somehow undefined + # "set cmd {$alias} \; _%@eval {\$cmd} \$args" + # A gold star to someone that can make test autoMkindex-3.3 work + # properly + + set alias [namespace tail $fakeName] + $parser invokehidden proc $name {args} "_%@eval {$alias} \$args" + $parser alias $alias $fakeName + } else { + $parser alias $name $fakeName + } + return +} + +# auto_mkindex_parser::fullname -- +# +# Used by commands like "proc" within the auto_mkindex parser. Returns the +# qualified namespace name for the "name" argument. If the "name" does not +# start with "::", elements are added from the current namespace stack to +# produce a qualified name. Then, the name is examined to see whether or not +# it should really be qualified. If the name has more than the leading "::", +# it is returned as a fully qualified name. Otherwise, it is returned as a +# simple name. That way, the Tcl autoloader will recognize it properly. +# +# Arguments: +# name - Name that is being added to index. + +proc auto_mkindex_parser::fullname {name} { + variable contextStack + + if {![string match ::* $name]} { + foreach ns $contextStack { + set name "${ns}::$name" + if {[string match ::* $name]} { + break + } + } + } + + if {[namespace qualifiers $name] eq ""} { + set name [namespace tail $name] + } elseif {![string match ::* $name]} { + set name "::$name" + } + + # Earlier, mkindex replaced all $'s with \0. Now, we have to reverse that + # replacement. + return [string map [list \0 \$] $name] +} + +# auto_mkindex_parser::indexEntry -- +# +# Used by commands like "proc" within the auto_mkindex parser to add a +# correctly-quoted entry to the index. This is shared code so it is done +# *right*, in one place. +# +# Arguments: +# name - Name that is being added to index. + +proc auto_mkindex_parser::indexEntry {name} { + variable index + variable scriptFile + + # We convert all metacharacters to their backslashed form, and pre-split + # the file name that we know about (which will be a proper list, and so + # correctly quoted). + + set name [string range [list \}[fullname $name]] 2 end] + set filenameParts [file split $scriptFile] + + append index [format \ + {set auto_index(%s) [list source [file join $dir %s]]%s} \ + $name $filenameParts \n] + return +} + +if {[llength $::auto_mkindex_parser::initCommands]} { + return +} + +# Register all of the procedures for the auto_mkindex parser that will build +# the "tclIndex" file. + +# AUTO MKINDEX: proc name arglist body +# Adds an entry to the auto index list for the given procedure name. + +auto_mkindex_parser::command proc {name args} { + indexEntry $name +} + +# Conditionally add support for Tcl byte code files. There are some tricky +# details here. First, we need to get the tbcload library initialized in the +# current interpreter. We cannot load tbcload into the child until we have +# done so because it needs access to the tcl_patchLevel variable. Second, +# because the package index file may defer loading the library until we invoke +# a command, we need to explicitly invoke auto_load to force it to be loaded. +# This should be a noop if the package has already been loaded + +auto_mkindex_parser::hook { + try { + package require tbcload + } on error {} { + # OK, don't have it so do nothing + } on ok {} { + if {[namespace which -command tbcload::bcproc] eq ""} { + auto_load tbcload::bcproc + } + load {} tbcload $auto_mkindex_parser::parser + + # AUTO MKINDEX: tbcload::bcproc name arglist body + # Adds an entry to the auto index list for the given precompiled + # procedure name. + + auto_mkindex_parser::commandInit tbcload::bcproc {name args} { + indexEntry $name + } + } +} + +# AUTO MKINDEX: namespace eval name command ?arg arg...? +# Adds the namespace name onto the context stack and evaluates the associated +# body of commands. +# +# AUTO MKINDEX: namespace import ?-force? pattern ?pattern...? +# Performs the "import" action in the parser interpreter. This is important +# for any commands contained in a namespace that affect the index. For +# example, a script may say "itcl::class ...", or it may import "itcl::*" and +# then say "class ...". This procedure does the import operation, but keeps +# track of imported patterns so we can remove the imports later. + +auto_mkindex_parser::command namespace {op args} { + switch -- $op { + eval { + variable parser + variable contextStack + + set name [lindex $args 0] + set args [lrange $args 1 end] + + set contextStack [linsert $contextStack 0 $name] + $parser eval [list _%@namespace eval $name] $args + set contextStack [lrange $contextStack 1 end] + } + import { + variable parser + variable imports + foreach pattern $args { + if {$pattern ne "-force"} { + lappend imports $pattern + } + } + catch {$parser eval "_%@namespace import $args"} + } + ensemble { + variable parser + variable contextStack + if {[lindex $args 0] eq "create"} { + set name ::[join [lreverse $contextStack] ::] + catch { + set name [dict get [lrange $args 1 end] -command] + if {![string match ::* $name]} { + set name ::[join [lreverse $contextStack] ::]$name + } + regsub -all ::+ $name :: name + } + # create artificial proc to force an entry in the tclIndex + $parser eval [list ::proc $name {} {}] + } + } + } +} + +# AUTO MKINDEX: oo::class create name ?definition? +# Adds an entry to the auto index list for the given class name. +auto_mkindex_parser::command oo::class {op name {body ""}} { + if {$op eq "create"} { + indexEntry $name + } +} +auto_mkindex_parser::command class {op name {body ""}} { + if {$op eq "create"} { + indexEntry $name + } +} + +return diff --git a/lib/tcl8.6/clock.tcl b/lib/tcl8.6/clock.tcl new file mode 100644 index 0000000000000000000000000000000000000000..b9bbc2c47b9119126313da2eadf0735db2941c4c --- /dev/null +++ b/lib/tcl8.6/clock.tcl @@ -0,0 +1,4551 @@ +#---------------------------------------------------------------------- +# +# clock.tcl -- +# +# This file implements the portions of the [clock] ensemble that are +# coded in Tcl. Refer to the users' manual to see the description of +# the [clock] command and its subcommands. +# +# +#---------------------------------------------------------------------- +# +# Copyright (c) 2004-2007 Kevin B. Kenny +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +#---------------------------------------------------------------------- + +# We must have message catalogs that support the root locale, and we need +# access to the Registry on Windows systems. + +uplevel \#0 { + package require msgcat 1.6 + if { $::tcl_platform(platform) eq {windows} } { + if { [catch { package require registry 1.1 }] } { + namespace eval ::tcl::clock [list variable NoRegistry {}] + } + } +} + +# Put the library directory into the namespace for the ensemble so that the +# library code can find message catalogs and time zone definition files. + +namespace eval ::tcl::clock \ + [list variable LibDir [file dirname [info script]]] + +#---------------------------------------------------------------------- +# +# clock -- +# +# Manipulate times. +# +# The 'clock' command manipulates time. Refer to the user documentation for +# the available subcommands and what they do. +# +#---------------------------------------------------------------------- + +namespace eval ::tcl::clock { + + # Export the subcommands + + namespace export format + namespace export clicks + namespace export microseconds + namespace export milliseconds + namespace export scan + namespace export seconds + namespace export add + + # Import the message catalog commands that we use. + + namespace import ::msgcat::mcload + namespace import ::msgcat::mclocale + namespace import ::msgcat::mc + namespace import ::msgcat::mcpackagelocale + +} + +#---------------------------------------------------------------------- +# +# ::tcl::clock::Initialize -- +# +# Finish initializing the 'clock' subsystem +# +# Results: +# None. +# +# Side effects: +# Namespace variable in the 'clock' subsystem are initialized. +# +# The '::tcl::clock::Initialize' procedure initializes the namespace variables +# and root locale message catalog for the 'clock' subsystem. It is broken +# into a procedure rather than simply evaluated as a script so that it will be +# able to use local variables, avoiding the dangers of 'creative writing' as +# in Bug 1185933. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::Initialize {} { + + rename ::tcl::clock::Initialize {} + + variable LibDir + + # Define the Greenwich time zone + + proc InitTZData {} { + variable TZData + array unset TZData + set TZData(:Etc/GMT) { + {-9223372036854775808 0 0 GMT} + } + set TZData(:GMT) $TZData(:Etc/GMT) + set TZData(:Etc/UTC) { + {-9223372036854775808 0 0 UTC} + } + set TZData(:UTC) $TZData(:Etc/UTC) + set TZData(:localtime) {} + } + InitTZData + + mcpackagelocale set {} + ::msgcat::mcpackageconfig set mcfolder [file join $LibDir msgs] + ::msgcat::mcpackageconfig set unknowncmd "" + ::msgcat::mcpackageconfig set changecmd ChangeCurrentLocale + + # Define the message catalog for the root locale. + + ::msgcat::mcmset {} { + AM {am} + BCE {B.C.E.} + CE {C.E.} + DATE_FORMAT {%m/%d/%Y} + DATE_TIME_FORMAT {%a %b %e %H:%M:%S %Y} + DAYS_OF_WEEK_ABBREV { + Sun Mon Tue Wed Thu Fri Sat + } + DAYS_OF_WEEK_FULL { + Sunday Monday Tuesday Wednesday Thursday Friday Saturday + } + GREGORIAN_CHANGE_DATE 2299161 + LOCALE_DATE_FORMAT {%m/%d/%Y} + LOCALE_DATE_TIME_FORMAT {%a %b %e %H:%M:%S %Y} + LOCALE_ERAS {} + LOCALE_NUMERALS { + 00 01 02 03 04 05 06 07 08 09 + 10 11 12 13 14 15 16 17 18 19 + 20 21 22 23 24 25 26 27 28 29 + 30 31 32 33 34 35 36 37 38 39 + 40 41 42 43 44 45 46 47 48 49 + 50 51 52 53 54 55 56 57 58 59 + 60 61 62 63 64 65 66 67 68 69 + 70 71 72 73 74 75 76 77 78 79 + 80 81 82 83 84 85 86 87 88 89 + 90 91 92 93 94 95 96 97 98 99 + } + LOCALE_TIME_FORMAT {%H:%M:%S} + LOCALE_YEAR_FORMAT {%EC%Ey} + MONTHS_ABBREV { + Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec + } + MONTHS_FULL { + January February March + April May June + July August September + October November December + } + PM {pm} + TIME_FORMAT {%H:%M:%S} + TIME_FORMAT_12 {%I:%M:%S %P} + TIME_FORMAT_24 {%H:%M} + TIME_FORMAT_24_SECS {%H:%M:%S} + } + + # Define a few Gregorian change dates for other locales. In most cases + # the change date follows a language, because a nation's colonies changed + # at the same time as the nation itself. In many cases, different + # national boundaries existed; the dominating rule is to follow the + # nation's capital. + + # Italy, Spain, Portugal, Poland + + ::msgcat::mcset it GREGORIAN_CHANGE_DATE 2299161 + ::msgcat::mcset es GREGORIAN_CHANGE_DATE 2299161 + ::msgcat::mcset pt GREGORIAN_CHANGE_DATE 2299161 + ::msgcat::mcset pl GREGORIAN_CHANGE_DATE 2299161 + + # France, Austria + + ::msgcat::mcset fr GREGORIAN_CHANGE_DATE 2299227 + + # For Belgium, we follow Southern Netherlands; Liege Diocese changed + # several weeks later. + + ::msgcat::mcset fr_BE GREGORIAN_CHANGE_DATE 2299238 + ::msgcat::mcset nl_BE GREGORIAN_CHANGE_DATE 2299238 + + # Austria + + ::msgcat::mcset de_AT GREGORIAN_CHANGE_DATE 2299527 + + # Hungary + + ::msgcat::mcset hu GREGORIAN_CHANGE_DATE 2301004 + + # Germany, Norway, Denmark (Catholic Germany changed earlier) + + ::msgcat::mcset de_DE GREGORIAN_CHANGE_DATE 2342032 + ::msgcat::mcset nb GREGORIAN_CHANGE_DATE 2342032 + ::msgcat::mcset nn GREGORIAN_CHANGE_DATE 2342032 + ::msgcat::mcset no GREGORIAN_CHANGE_DATE 2342032 + ::msgcat::mcset da GREGORIAN_CHANGE_DATE 2342032 + + # Holland (Brabant, Gelderland, Flanders, Friesland, etc. changed at + # various times) + + ::msgcat::mcset nl GREGORIAN_CHANGE_DATE 2342165 + + # Protestant Switzerland (Catholic cantons changed earlier) + + ::msgcat::mcset fr_CH GREGORIAN_CHANGE_DATE 2361342 + ::msgcat::mcset it_CH GREGORIAN_CHANGE_DATE 2361342 + ::msgcat::mcset de_CH GREGORIAN_CHANGE_DATE 2361342 + + # English speaking countries + + ::msgcat::mcset en GREGORIAN_CHANGE_DATE 2361222 + + # Sweden (had several changes onto and off of the Gregorian calendar) + + ::msgcat::mcset sv GREGORIAN_CHANGE_DATE 2361390 + + # Russia + + ::msgcat::mcset ru GREGORIAN_CHANGE_DATE 2421639 + + # Romania (Transylvania changed earlier - perhaps de_RO should show the + # earlier date?) + + ::msgcat::mcset ro GREGORIAN_CHANGE_DATE 2422063 + + # Greece + + ::msgcat::mcset el GREGORIAN_CHANGE_DATE 2423480 + + #------------------------------------------------------------------ + # + # CONSTANTS + # + #------------------------------------------------------------------ + + # Paths at which binary time zone data for the Olson libraries are known + # to reside on various operating systems + + variable ZoneinfoPaths {} + foreach path { + /usr/share/zoneinfo + /usr/share/lib/zoneinfo + /usr/lib/zoneinfo + /usr/local/etc/zoneinfo + } { + if { [file isdirectory $path] } { + lappend ZoneinfoPaths $path + } + } + + # Define the directories for time zone data and message catalogs. + + variable DataDir [file join $LibDir tzdata] + + # Number of days in the months, in common years and leap years. + + variable DaysInRomanMonthInCommonYear \ + { 31 28 31 30 31 30 31 31 30 31 30 31 } + variable DaysInRomanMonthInLeapYear \ + { 31 29 31 30 31 30 31 31 30 31 30 31 } + variable DaysInPriorMonthsInCommonYear [list 0] + variable DaysInPriorMonthsInLeapYear [list 0] + set i 0 + foreach j $DaysInRomanMonthInCommonYear { + lappend DaysInPriorMonthsInCommonYear [incr i $j] + } + set i 0 + foreach j $DaysInRomanMonthInLeapYear { + lappend DaysInPriorMonthsInLeapYear [incr i $j] + } + + # Another epoch (Hi, Jeff!) + + variable Roddenberry 1946 + + # Integer ranges + + variable MINWIDE -9223372036854775808 + variable MAXWIDE 9223372036854775807 + + # Day before Leap Day + + variable FEB_28 58 + + # Translation table to map Windows TZI onto cities, so that the Olson + # rules can apply. In some cases the mapping is ambiguous, so it's wise + # to specify $::env(TCL_TZ) rather than simply depending on the system + # time zone. + + # The keys are long lists of values obtained from the time zone + # information in the Registry. In order, the list elements are: + # Bias StandardBias DaylightBias + # StandardDate.wYear StandardDate.wMonth StandardDate.wDayOfWeek + # StandardDate.wDay StandardDate.wHour StandardDate.wMinute + # StandardDate.wSecond StandardDate.wMilliseconds + # DaylightDate.wYear DaylightDate.wMonth DaylightDate.wDayOfWeek + # DaylightDate.wDay DaylightDate.wHour DaylightDate.wMinute + # DaylightDate.wSecond DaylightDate.wMilliseconds + # The values are the names of time zones where those rules apply. There + # is considerable ambiguity in certain zones; an attempt has been made to + # make a reasonable guess, but this table needs to be taken with a grain + # of salt. + + variable WinZoneInfo [dict create {*}{ + {-43200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Kwajalein + {-39600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Midway + {-36000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Honolulu + {-32400 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Anchorage + {-28800 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Los_Angeles + {-28800 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Tijuana + {-25200 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Denver + {-25200 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Chihuahua + {-25200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Phoenix + {-21600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Regina + {-21600 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Chicago + {-21600 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Mexico_City + {-18000 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/New_York + {-18000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Indianapolis + {-14400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Caracas + {-14400 0 3600 0 3 6 2 23 59 59 999 0 10 6 2 23 59 59 999} + :America/Santiago + {-14400 0 3600 0 2 0 5 2 0 0 0 0 11 0 1 2 0 0 0} :America/Manaus + {-14400 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Halifax + {-12600 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/St_Johns + {-10800 0 3600 0 2 0 2 2 0 0 0 0 10 0 3 2 0 0 0} :America/Sao_Paulo + {-10800 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Godthab + {-10800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Buenos_Aires + {-10800 0 3600 0 2 0 5 2 0 0 0 0 11 0 1 2 0 0 0} :America/Bahia + {-10800 0 3600 0 3 0 2 2 0 0 0 0 10 0 1 2 0 0 0} :America/Montevideo + {-7200 0 3600 0 9 0 5 2 0 0 0 0 3 0 5 2 0 0 0} :America/Noronha + {-3600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Atlantic/Azores + {-3600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Atlantic/Cape_Verde + {0 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :UTC + {0 0 3600 0 10 0 5 2 0 0 0 0 3 0 5 1 0 0 0} :Europe/London + {3600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Africa/Kinshasa + {3600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :CET + {7200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Africa/Harare + {7200 0 3600 0 9 4 5 23 59 59 0 0 4 4 5 23 59 59 0} + :Africa/Cairo + {7200 0 3600 0 10 0 5 4 0 0 0 0 3 0 5 3 0 0 0} :Europe/Helsinki + {7200 0 3600 0 9 0 3 2 0 0 0 0 3 5 5 2 0 0 0} :Asia/Jerusalem + {7200 0 3600 0 9 0 5 1 0 0 0 0 3 0 5 0 0 0 0} :Europe/Bucharest + {7200 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Europe/Athens + {7200 0 3600 0 9 5 5 1 0 0 0 0 3 4 5 0 0 0 0} :Asia/Amman + {7200 0 3600 0 10 6 5 23 59 59 999 0 3 0 5 0 0 0 0} + :Asia/Beirut + {7200 0 -3600 0 4 0 1 2 0 0 0 0 9 0 1 2 0 0 0} :Africa/Windhoek + {10800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Riyadh + {10800 0 3600 0 10 0 1 4 0 0 0 0 4 0 1 3 0 0 0} :Asia/Baghdad + {10800 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Europe/Moscow + {12600 0 3600 0 9 2 4 2 0 0 0 0 3 0 1 2 0 0 0} :Asia/Tehran + {14400 0 3600 0 10 0 5 5 0 0 0 0 3 0 5 4 0 0 0} :Asia/Baku + {14400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Muscat + {14400 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Tbilisi + {16200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Kabul + {18000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Karachi + {18000 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Yekaterinburg + {19800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Calcutta + {20700 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Katmandu + {21600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Dhaka + {21600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Novosibirsk + {23400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Rangoon + {25200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Bangkok + {25200 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Krasnoyarsk + {28800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Chongqing + {28800 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Irkutsk + {32400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Tokyo + {32400 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Yakutsk + {34200 0 3600 0 3 0 5 3 0 0 0 0 10 0 5 2 0 0 0} :Australia/Adelaide + {34200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Australia/Darwin + {36000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Australia/Brisbane + {36000 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Vladivostok + {36000 0 3600 0 3 0 5 3 0 0 0 0 10 0 1 2 0 0 0} :Australia/Hobart + {36000 0 3600 0 3 0 5 3 0 0 0 0 10 0 5 2 0 0 0} :Australia/Sydney + {39600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Noumea + {43200 0 3600 0 3 0 3 3 0 0 0 0 10 0 1 2 0 0 0} :Pacific/Auckland + {43200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Fiji + {46800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Tongatapu + }] + + # Groups of fields that specify the date, priorities, and code bursts that + # determine Julian Day Number given those groups. The code in [clock + # scan] will choose the highest priority (lowest numbered) set of fields + # that determines the date. + + variable DateParseActions { + + { seconds } 0 {} + + { julianDay } 1 {} + + { era century yearOfCentury month dayOfMonth } 2 { + dict set date year [expr { 100 * [dict get $date century] + + [dict get $date yearOfCentury] }] + set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ + $changeover] + } + { era century yearOfCentury dayOfYear } 2 { + dict set date year [expr { 100 * [dict get $date century] + + [dict get $date yearOfCentury] }] + set date [GetJulianDayFromEraYearDay $date[set date {}] \ + $changeover] + } + + { century yearOfCentury month dayOfMonth } 3 { + dict set date era CE + dict set date year [expr { 100 * [dict get $date century] + + [dict get $date yearOfCentury] }] + set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ + $changeover] + } + { century yearOfCentury dayOfYear } 3 { + dict set date era CE + dict set date year [expr { 100 * [dict get $date century] + + [dict get $date yearOfCentury] }] + set date [GetJulianDayFromEraYearDay $date[set date {}] \ + $changeover] + } + { iso8601Century iso8601YearOfCentury iso8601Week dayOfWeek } 3 { + dict set date era CE + dict set date iso8601Year \ + [expr { 100 * [dict get $date iso8601Century] + + [dict get $date iso8601YearOfCentury] }] + set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \ + $changeover] + } + + { yearOfCentury month dayOfMonth } 4 { + set date [InterpretTwoDigitYear $date[set date {}] $baseTime] + dict set date era CE + set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ + $changeover] + } + { yearOfCentury dayOfYear } 4 { + set date [InterpretTwoDigitYear $date[set date {}] $baseTime] + dict set date era CE + set date [GetJulianDayFromEraYearDay $date[set date {}] \ + $changeover] + } + { iso8601YearOfCentury iso8601Week dayOfWeek } 4 { + set date [InterpretTwoDigitYear \ + $date[set date {}] $baseTime \ + iso8601YearOfCentury iso8601Year] + dict set date era CE + set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \ + $changeover] + } + + { month dayOfMonth } 5 { + set date [AssignBaseYear $date[set date {}] \ + $baseTime $timeZone $changeover] + set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ + $changeover] + } + { dayOfYear } 5 { + set date [AssignBaseYear $date[set date {}] \ + $baseTime $timeZone $changeover] + set date [GetJulianDayFromEraYearDay $date[set date {}] \ + $changeover] + } + { iso8601Week dayOfWeek } 5 { + set date [AssignBaseIso8601Year $date[set date {}] \ + $baseTime $timeZone $changeover] + set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \ + $changeover] + } + + { dayOfMonth } 6 { + set date [AssignBaseMonth $date[set date {}] \ + $baseTime $timeZone $changeover] + set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ + $changeover] + } + + { dayOfWeek } 7 { + set date [AssignBaseWeek $date[set date {}] \ + $baseTime $timeZone $changeover] + set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \ + $changeover] + } + + {} 8 { + set date [AssignBaseJulianDay $date[set date {}] \ + $baseTime $timeZone $changeover] + } + } + + # Groups of fields that specify time of day, priorities, and code that + # processes them + + variable TimeParseActions { + + seconds 1 {} + + { hourAMPM minute second amPmIndicator } 2 { + dict set date secondOfDay [InterpretHMSP $date] + } + { hour minute second } 2 { + dict set date secondOfDay [InterpretHMS $date] + } + + { hourAMPM minute amPmIndicator } 3 { + dict set date second 0 + dict set date secondOfDay [InterpretHMSP $date] + } + { hour minute } 3 { + dict set date second 0 + dict set date secondOfDay [InterpretHMS $date] + } + + { hourAMPM amPmIndicator } 4 { + dict set date minute 0 + dict set date second 0 + dict set date secondOfDay [InterpretHMSP $date] + } + { hour } 4 { + dict set date minute 0 + dict set date second 0 + dict set date secondOfDay [InterpretHMS $date] + } + + { } 5 { + dict set date secondOfDay 0 + } + } + + # Legacy time zones, used primarily for parsing RFC822 dates. + + variable LegacyTimeZone [dict create \ + gmt +0000 \ + ut +0000 \ + utc +0000 \ + bst +0100 \ + wet +0000 \ + wat -0100 \ + at -0200 \ + nft -0330 \ + nst -0330 \ + ndt -0230 \ + ast -0400 \ + adt -0300 \ + est -0500 \ + edt -0400 \ + cst -0600 \ + cdt -0500 \ + mst -0700 \ + mdt -0600 \ + pst -0800 \ + pdt -0700 \ + yst -0900 \ + ydt -0800 \ + hst -1000 \ + hdt -0900 \ + cat -1000 \ + ahst -1000 \ + nt -1100 \ + idlw -1200 \ + cet +0100 \ + cest +0200 \ + met +0100 \ + mewt +0100 \ + mest +0200 \ + swt +0100 \ + sst +0200 \ + fwt +0100 \ + fst +0200 \ + eet +0200 \ + eest +0300 \ + bt +0300 \ + it +0330 \ + zp4 +0400 \ + zp5 +0500 \ + ist +0530 \ + zp6 +0600 \ + wast +0700 \ + wadt +0800 \ + jt +0730 \ + cct +0800 \ + jst +0900 \ + kst +0900 \ + cast +0930 \ + jdt +1000 \ + kdt +1000 \ + cadt +1030 \ + east +1000 \ + eadt +1030 \ + gst +1000 \ + nzt +1200 \ + nzst +1200 \ + nzdt +1300 \ + idle +1200 \ + a +0100 \ + b +0200 \ + c +0300 \ + d +0400 \ + e +0500 \ + f +0600 \ + g +0700 \ + h +0800 \ + i +0900 \ + k +1000 \ + l +1100 \ + m +1200 \ + n -0100 \ + o -0200 \ + p -0300 \ + q -0400 \ + r -0500 \ + s -0600 \ + t -0700 \ + u -0800 \ + v -0900 \ + w -1000 \ + x -1100 \ + y -1200 \ + z +0000 \ + ] + + # Caches + + variable LocaleNumeralCache {}; # Dictionary whose keys are locale + # names and whose values are pairs + # comprising regexes matching numerals + # in the given locales and dictionaries + # mapping the numerals to their numeric + # values. + # variable CachedSystemTimeZone; # If 'CachedSystemTimeZone' exists, + # it contains the value of the + # system time zone, as determined from + # the environment. + variable TimeZoneBad {}; # Dictionary whose keys are time zone + # names and whose values are 1 if + # the time zone is unknown and 0 + # if it is known. + variable TZData; # Array whose keys are time zone names + # and whose values are lists of quads + # comprising start time, UTC offset, + # Daylight Saving Time indicator, and + # time zone abbreviation. + variable FormatProc; # Array mapping format group + # and locale to the name of a procedure + # that renders the given format +} +::tcl::clock::Initialize + +#---------------------------------------------------------------------- +# +# clock format -- +# +# Formats a count of seconds since the Posix Epoch as a time of day. +# +# The 'clock format' command formats times of day for output. Refer to the +# user documentation to see what it does. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::format { args } { + + variable FormatProc + variable TZData + + lassign [ParseFormatArgs {*}$args] format locale timezone + set locale [string tolower $locale] + set clockval [lindex $args 0] + + # Get the data for time changes in the given zone + + if {$timezone eq ""} { + set timezone [GetSystemTimeZone] + } + if {![info exists TZData($timezone)]} { + if {[catch {SetupTimeZone $timezone} retval opts]} { + dict unset opts -errorinfo + return -options $opts $retval + } + } + + # Build a procedure to format the result. Cache the built procedure's name + # in the 'FormatProc' array to avoid losing its internal representation, + # which contains the name resolution. + + set procName formatproc'$format'$locale + set procName [namespace current]::[string map {: {\:} \\ {\\}} $procName] + if {[info exists FormatProc($procName)]} { + set procName $FormatProc($procName) + } else { + set FormatProc($procName) \ + [ParseClockFormatFormat $procName $format $locale] + } + + return [$procName $clockval $timezone] + +} + +#---------------------------------------------------------------------- +# +# ParseClockFormatFormat -- +# +# Builds and caches a procedure that formats a time value. +# +# Parameters: +# format -- Format string to use +# locale -- Locale in which the format string is to be interpreted +# +# Results: +# Returns the name of the newly-built procedure. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::ParseClockFormatFormat {procName format locale} { + + if {[namespace which $procName] ne {}} { + return $procName + } + + # Map away the locale-dependent composite format groups + + EnterLocale $locale + + # Change locale if a fresh locale has been given on the command line. + + try { + return [ParseClockFormatFormat2 $format $locale $procName] + } trap CLOCK {result opts} { + dict unset opts -errorinfo + return -options $opts $result + } +} + +proc ::tcl::clock::ParseClockFormatFormat2 {format locale procName} { + set didLocaleEra 0 + set didLocaleNumerals 0 + set preFormatCode \ + [string map [list @GREGORIAN_CHANGE_DATE@ \ + [mc GREGORIAN_CHANGE_DATE]] \ + { + variable TZData + set date [GetDateFields $clockval \ + $TZData($timezone) \ + @GREGORIAN_CHANGE_DATE@] + }] + set formatString {} + set substituents {} + set state {} + + set format [LocalizeFormat $locale $format] + + foreach char [split $format {}] { + switch -exact -- $state { + {} { + if { [string equal % $char] } { + set state percent + } else { + append formatString $char + } + } + percent { # Character following a '%' character + set state {} + switch -exact -- $char { + % { # A literal character, '%' + append formatString %% + } + a { # Day of week, abbreviated + append formatString %s + append substituents \ + [string map \ + [list @DAYS_OF_WEEK_ABBREV@ \ + [list [mc DAYS_OF_WEEK_ABBREV]]] \ + { [lindex @DAYS_OF_WEEK_ABBREV@ \ + [expr {[dict get $date dayOfWeek] \ + % 7}]]}] + } + A { # Day of week, spelt out. + append formatString %s + append substituents \ + [string map \ + [list @DAYS_OF_WEEK_FULL@ \ + [list [mc DAYS_OF_WEEK_FULL]]] \ + { [lindex @DAYS_OF_WEEK_FULL@ \ + [expr {[dict get $date dayOfWeek] \ + % 7}]]}] + } + b - h { # Name of month, abbreviated. + append formatString %s + append substituents \ + [string map \ + [list @MONTHS_ABBREV@ \ + [list [mc MONTHS_ABBREV]]] \ + { [lindex @MONTHS_ABBREV@ \ + [expr {[dict get $date month]-1}]]}] + } + B { # Name of month, spelt out + append formatString %s + append substituents \ + [string map \ + [list @MONTHS_FULL@ \ + [list [mc MONTHS_FULL]]] \ + { [lindex @MONTHS_FULL@ \ + [expr {[dict get $date month]-1}]]}] + } + C { # Century number + append formatString %02d + append substituents \ + { [expr {[dict get $date year] / 100}]} + } + d { # Day of month, with leading zero + append formatString %02d + append substituents { [dict get $date dayOfMonth]} + } + e { # Day of month, without leading zero + append formatString %2d + append substituents { [dict get $date dayOfMonth]} + } + E { # Format group in a locale-dependent + # alternative era + set state percentE + if {!$didLocaleEra} { + append preFormatCode \ + [string map \ + [list @LOCALE_ERAS@ \ + [list [mc LOCALE_ERAS]]] \ + { + set date [GetLocaleEra \ + $date[set date {}] \ + @LOCALE_ERAS@]}] \n + set didLocaleEra 1 + } + if {!$didLocaleNumerals} { + append preFormatCode \ + [list set localeNumerals \ + [mc LOCALE_NUMERALS]] \n + set didLocaleNumerals 1 + } + } + g { # Two-digit year relative to ISO8601 + # week number + append formatString %02d + append substituents \ + { [expr { [dict get $date iso8601Year] % 100 }]} + } + G { # Four-digit year relative to ISO8601 + # week number + append formatString %02d + append substituents { [dict get $date iso8601Year]} + } + H { # Hour in the 24-hour day, leading zero + append formatString %02d + append substituents \ + { [expr { [dict get $date localSeconds] \ + / 3600 % 24}]} + } + I { # Hour AM/PM, with leading zero + append formatString %02d + append substituents \ + { [expr { ( ( ( [dict get $date localSeconds] \ + % 86400 ) \ + + 86400 \ + - 3600 ) \ + / 3600 ) \ + % 12 + 1 }] } + } + j { # Day of year (001-366) + append formatString %03d + append substituents { [dict get $date dayOfYear]} + } + J { # Julian Day Number + append formatString %07ld + append substituents { [dict get $date julianDay]} + } + k { # Hour (0-23), no leading zero + append formatString %2d + append substituents \ + { [expr { [dict get $date localSeconds] + / 3600 + % 24 }]} + } + l { # Hour (12-11), no leading zero + append formatString %2d + append substituents \ + { [expr { ( ( ( [dict get $date localSeconds] + % 86400 ) + + 86400 + - 3600 ) + / 3600 ) + % 12 + 1 }]} + } + m { # Month number, leading zero + append formatString %02d + append substituents { [dict get $date month]} + } + M { # Minute of the hour, leading zero + append formatString %02d + append substituents \ + { [expr { [dict get $date localSeconds] + / 60 + % 60 }]} + } + n { # A literal newline + append formatString \n + } + N { # Month number, no leading zero + append formatString %2d + append substituents { [dict get $date month]} + } + O { # A format group in the locale's + # alternative numerals + set state percentO + if {!$didLocaleNumerals} { + append preFormatCode \ + [list set localeNumerals \ + [mc LOCALE_NUMERALS]] \n + set didLocaleNumerals 1 + } + } + p { # Localized 'AM' or 'PM' indicator + # converted to uppercase + append formatString %s + append preFormatCode \ + [list set AM [string toupper [mc AM]]] \n \ + [list set PM [string toupper [mc PM]]] \n + append substituents \ + { [expr {(([dict get $date localSeconds] + % 86400) < 43200) ? + $AM : $PM}]} + } + P { # Localized 'AM' or 'PM' indicator + append formatString %s + append preFormatCode \ + [list set am [mc AM]] \n \ + [list set pm [mc PM]] \n + append substituents \ + { [expr {(([dict get $date localSeconds] + % 86400) < 43200) ? + $am : $pm}]} + + } + Q { # Hi, Jeff! + append formatString %s + append substituents { [FormatStarDate $date]} + } + s { # Seconds from the Posix Epoch + append formatString %s + append substituents { [dict get $date seconds]} + } + S { # Second of the minute, with + # leading zero + append formatString %02d + append substituents \ + { [expr { [dict get $date localSeconds] + % 60 }]} + } + t { # A literal tab character + append formatString \t + } + u { # Day of the week (1-Monday, 7-Sunday) + append formatString %1d + append substituents { [dict get $date dayOfWeek]} + } + U { # Week of the year (00-53). The + # first Sunday of the year is the + # first day of week 01 + append formatString %02d + append preFormatCode { + set dow [dict get $date dayOfWeek] + if { $dow == 7 } { + set dow 0 + } + incr dow + set UweekNumber \ + [expr { ( [dict get $date dayOfYear] + - $dow + 7 ) + / 7 }] + } + append substituents { $UweekNumber} + } + V { # The ISO8601 week number + append formatString %02d + append substituents { [dict get $date iso8601Week]} + } + w { # Day of the week (0-Sunday, + # 6-Saturday) + append formatString %1d + append substituents \ + { [expr { [dict get $date dayOfWeek] % 7 }]} + } + W { # Week of the year (00-53). The first + # Monday of the year is the first day + # of week 01. + append preFormatCode { + set WweekNumber \ + [expr { ( [dict get $date dayOfYear] + - [dict get $date dayOfWeek] + + 7 ) + / 7 }] + } + append formatString %02d + append substituents { $WweekNumber} + } + y { # The two-digit year of the century + append formatString %02d + append substituents \ + { [expr { [dict get $date year] % 100 }]} + } + Y { # The four-digit year + append formatString %04d + append substituents { [dict get $date year]} + } + z { # The time zone as hours and minutes + # east (+) or west (-) of Greenwich + append formatString %s + append substituents { [FormatNumericTimeZone \ + [dict get $date tzOffset]]} + } + Z { # The name of the time zone + append formatString %s + append substituents { [dict get $date tzName]} + } + % { # A literal percent character + append formatString %% + } + default { # An unknown escape sequence + append formatString %% $char + } + } + } + percentE { # Character following %E + set state {} + switch -exact -- $char { + E { + append formatString %s + append substituents { } \ + [string map \ + [list @BCE@ [list [mc BCE]] \ + @CE@ [list [mc CE]]] \ + {[dict get {BCE @BCE@ CE @CE@} \ + [dict get $date era]]}] + } + C { # Locale-dependent era + append formatString %s + append substituents { [dict get $date localeEra]} + } + y { # Locale-dependent year of the era + append preFormatCode { + set y [dict get $date localeYear] + if { $y >= 0 && $y < 100 } { + set Eyear [lindex $localeNumerals $y] + } else { + set Eyear $y + } + } + append formatString %s + append substituents { $Eyear} + } + default { # Unknown %E format group + append formatString %%E $char + } + } + } + percentO { # Character following %O + set state {} + switch -exact -- $char { + d - e { # Day of the month in alternative + # numerals + append formatString %s + append substituents \ + { [lindex $localeNumerals \ + [dict get $date dayOfMonth]]} + } + H - k { # Hour of the day in alternative + # numerals + append formatString %s + append substituents \ + { [lindex $localeNumerals \ + [expr { [dict get $date localSeconds] + / 3600 + % 24 }]]} + } + I - l { # Hour (12-11) AM/PM in alternative + # numerals + append formatString %s + append substituents \ + { [lindex $localeNumerals \ + [expr { ( ( ( [dict get $date localSeconds] + % 86400 ) + + 86400 + - 3600 ) + / 3600 ) + % 12 + 1 }]]} + } + m { # Month number in alternative numerals + append formatString %s + append substituents \ + { [lindex $localeNumerals [dict get $date month]]} + } + M { # Minute of the hour in alternative + # numerals + append formatString %s + append substituents \ + { [lindex $localeNumerals \ + [expr { [dict get $date localSeconds] + / 60 + % 60 }]]} + } + S { # Second of the minute in alternative + # numerals + append formatString %s + append substituents \ + { [lindex $localeNumerals \ + [expr { [dict get $date localSeconds] + % 60 }]]} + } + u { # Day of the week (Monday=1,Sunday=7) + # in alternative numerals + append formatString %s + append substituents \ + { [lindex $localeNumerals \ + [dict get $date dayOfWeek]]} + } + w { # Day of the week (Sunday=0,Saturday=6) + # in alternative numerals + append formatString %s + append substituents \ + { [lindex $localeNumerals \ + [expr { [dict get $date dayOfWeek] % 7 }]]} + } + y { # Year of the century in alternative + # numerals + append formatString %s + append substituents \ + { [lindex $localeNumerals \ + [expr { [dict get $date year] % 100 }]]} + } + default { # Unknown format group + append formatString %%O $char + } + } + } + } + } + + # Clean up any improperly terminated groups + + switch -exact -- $state { + percent { + append formatString %% + } + percentE { + append retval %%E + } + percentO { + append retval %%O + } + } + + proc $procName {clockval timezone} " + $preFormatCode + return \[::format [list $formatString] $substituents\] + " + + # puts [list $procName [info args $procName] [info body $procName]] + + return $procName +} + +#---------------------------------------------------------------------- +# +# clock scan -- +# +# Inputs a count of seconds since the Posix Epoch as a time of day. +# +# The 'clock scan' command scans times of day on input. Refer to the user +# documentation to see what it does. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::scan { args } { + + set format {} + + # Check the count of args + + if { [llength $args] < 1 || [llength $args] % 2 != 1 } { + set cmdName "clock scan" + return -code error \ + -errorcode [list CLOCK wrongNumArgs] \ + "wrong \# args: should be\ + \"$cmdName string\ + ?-base seconds?\ + ?-format string? ?-gmt boolean?\ + ?-locale LOCALE? ?-timezone ZONE?\"" + } + + # Set defaults + + set base [clock seconds] + set string [lindex $args 0] + set format {} + set gmt 0 + set locale c + set timezone [GetSystemTimeZone] + + # Pick up command line options. + + foreach { flag value } [lreplace $args 0 0] { + 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 { ![info exists saw(-format)] } { + # Perhaps someday we'll localize the legacy code. Right now, it's not + # localized. + if { [info exists saw(-locale)] } { + return -code error \ + -errorcode [list CLOCK flagWithLegacyFormat] \ + "legacy \[clock scan\] does not support -locale" + + } + return [FreeScan $string $base $timezone $locale] + } + + # Change locale if a fresh locale has been given on the command line. + + EnterLocale $locale + + try { + # Map away the locale-dependent composite format groups + + set scanner [ParseClockScanFormat $format $locale] + return [$scanner $string $base $timezone] + } trap CLOCK {result opts} { + # Conceal location of generation of expected errors + dict unset opts -errorinfo + return -options $opts $result + } +} + +#---------------------------------------------------------------------- +# +# FreeScan -- +# +# Scans a time in free format +# +# Parameters: +# string - String containing the time to scan +# base - Base time, expressed in seconds from the Epoch +# timezone - Default time zone in which the time will be expressed +# locale - (Unused) Name of the locale where the time will be scanned. +# +# Results: +# Returns the date and time extracted from the string in seconds from +# the epoch +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::FreeScan { string base timezone locale } { + + variable TZData + + # Get the data for time changes in the given zone + + try { + SetupTimeZone $timezone + } on error {retval opts} { + dict unset opts -errorinfo + return -options $opts $retval + } + + # Extract year, month and day from the base time for the parser to use as + # defaults + + set date [GetDateFields $base $TZData($timezone) 2361222] + dict set date secondOfDay [expr { + [dict get $date localSeconds] % 86400 + }] + + # Parse the date. The parser will return a list comprising date, time, + # time zone, relative month/day/seconds, relative weekday, ordinal month. + + try { + set scanned [Oldscan $string \ + [dict get $date year] \ + [dict get $date month] \ + [dict get $date dayOfMonth]] + lassign $scanned \ + parseDate parseTime parseZone parseRel \ + parseWeekday parseOrdinalMonth + } on error message { + return -code error \ + "unable to convert date-time string \"$string\": $message" + } + + # If the caller supplied a date in the string, update the 'date' dict with + # the value. If the caller didn't specify a time with the date, default to + # midnight. + + if { [llength $parseDate] > 0 } { + lassign $parseDate y m d + if { $y < 100 } { + if { $y >= 39 } { + incr y 1900 + } else { + incr y 2000 + } + } + dict set date era CE + dict set date year $y + dict set date month $m + dict set date dayOfMonth $d + if { $parseTime eq {} } { + set parseTime 0 + } + } + + # If the caller supplied a time zone in the string, it comes back as a + # two-element list; the first element is the number of minutes east of + # Greenwich, and the second is a Daylight Saving Time indicator (1 == yes, + # 0 == no, -1 == unknown). We make it into a time zone indicator of + # +-hhmm. + + if { [llength $parseZone] > 0 } { + lassign $parseZone minEast dstFlag + set timezone [FormatNumericTimeZone \ + [expr { 60 * $minEast + 3600 * $dstFlag }]] + SetupTimeZone $timezone + } + dict set date tzName $timezone + + # Assemble date, time, zone into seconds-from-epoch + + set date [GetJulianDayFromEraYearMonthDay $date[set date {}] 2361222] + if { $parseTime ne {} } { + dict set date secondOfDay $parseTime + } elseif { [llength $parseWeekday] != 0 + || [llength $parseOrdinalMonth] != 0 + || ( [llength $parseRel] != 0 + && ( [lindex $parseRel 0] != 0 + || [lindex $parseRel 1] != 0 ) ) } { + dict set date secondOfDay 0 + } + + dict set date localSeconds [expr { + -210866803200 + + ( 86400 * wide([dict get $date julianDay]) ) + + [dict get $date secondOfDay] + }] + dict set date tzName $timezone + set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) 2361222] + set seconds [dict get $date seconds] + + # Do relative times + + if { [llength $parseRel] > 0 } { + lassign $parseRel relMonth relDay relSecond + set seconds [add $seconds \ + $relMonth months $relDay days $relSecond seconds \ + -timezone $timezone -locale $locale] + } + + # Do relative weekday + + if { [llength $parseWeekday] > 0 } { + lassign $parseWeekday dayOrdinal dayOfWeek + set date2 [GetDateFields $seconds $TZData($timezone) 2361222] + dict set date2 era CE + set jdwkday [WeekdayOnOrBefore $dayOfWeek [expr { + [dict get $date2 julianDay] + 6 + }]] + incr jdwkday [expr { 7 * $dayOrdinal }] + if { $dayOrdinal > 0 } { + incr jdwkday -7 + } + dict set date2 secondOfDay \ + [expr { [dict get $date2 localSeconds] % 86400 }] + dict set date2 julianDay $jdwkday + dict set date2 localSeconds [expr { + -210866803200 + + ( 86400 * wide([dict get $date2 julianDay]) ) + + [dict get $date secondOfDay] + }] + dict set date2 tzName $timezone + set date2 [ConvertLocalToUTC $date2[set date2 {}] $TZData($timezone) \ + 2361222] + set seconds [dict get $date2 seconds] + + } + + # Do relative month + + if { [llength $parseOrdinalMonth] > 0 } { + lassign $parseOrdinalMonth monthOrdinal monthNumber + if { $monthOrdinal > 0 } { + set monthDiff [expr { $monthNumber - [dict get $date month] }] + if { $monthDiff <= 0 } { + incr monthDiff 12 + } + incr monthOrdinal -1 + } else { + set monthDiff [expr { [dict get $date month] - $monthNumber }] + if { $monthDiff >= 0 } { + incr monthDiff -12 + } + incr monthOrdinal + } + set seconds [add $seconds $monthOrdinal years $monthDiff months \ + -timezone $timezone -locale $locale] + } + + return $seconds +} + + +#---------------------------------------------------------------------- +# +# ParseClockScanFormat -- +# +# Parses a format string given to [clock scan -format] +# +# Parameters: +# formatString - The format being parsed +# locale - The current locale +# +# Results: +# Constructs and returns a procedure that accepts the string being +# scanned, the base time, and the time zone. The procedure will either +# return the scanned time or else throw an error that should be rethrown +# to the caller of [clock scan] +# +# Side effects: +# The given procedure is defined in the ::tcl::clock namespace. Scan +# procedures are not deleted once installed. +# +# Why do we parse dates by defining a procedure to parse them? The reason is +# that by doing so, we have one convenient place to cache all the information: +# the regular expressions that match the patterns (which will be compiled), +# the code that assembles the date information, everything lands in one place. +# In this way, when a given format is reused at run time, all the information +# of how to apply it is available in a single place. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::ParseClockScanFormat {formatString locale} { + # Check whether the format has been parsed previously, and return the + # existing recognizer if it has. + + set procName scanproc'$formatString'$locale + set procName [namespace current]::[string map {: {\:} \\ {\\}} $procName] + if { [namespace which $procName] != {} } { + return $procName + } + + variable DateParseActions + variable TimeParseActions + + # Localize the %x, %X, etc. groups + + set formatString [LocalizeFormat $locale $formatString] + + # Condense whitespace + + regsub -all {[[:space:]]+} $formatString { } formatString + + # Walk through the groups of the format string. In this loop, we + # accumulate: + # - a regular expression that matches the string, + # - the count of capturing brackets in the regexp + # - a set of code that post-processes the fields captured by the regexp, + # - a dictionary whose keys are the names of fields that are present + # in the format string. + + set re {^[[:space:]]*} + set captureCount 0 + set postcode {} + set fieldSet [dict create] + set fieldCount 0 + set postSep {} + set state {} + + foreach c [split $formatString {}] { + switch -exact -- $state { + {} { + if { $c eq "%" } { + set state % + } elseif { $c eq " " } { + append re {[[:space:]]+} + } else { + if { ! [string is alnum $c] } { + append re "\\" + } + append re $c + } + } + % { + set state {} + switch -exact -- $c { + % { + append re % + } + { } { + append re "\[\[:space:\]\]*" + } + a - A { # Day of week, in words + set l {} + foreach \ + i {7 1 2 3 4 5 6} \ + abr [mc DAYS_OF_WEEK_ABBREV] \ + full [mc DAYS_OF_WEEK_FULL] { + dict set l [string tolower $abr] $i + dict set l [string tolower $full] $i + incr i + } + lassign [UniquePrefixRegexp $l] regex lookup + append re ( $regex ) + dict set fieldSet dayOfWeek [incr fieldCount] + append postcode "dict set date dayOfWeek \[" \ + "dict get " [list $lookup] " " \ + \[ {string tolower $field} [incr captureCount] \] \ + "\]\n" + } + b - B - h { # Name of month + set i 0 + set l {} + foreach \ + abr [mc MONTHS_ABBREV] \ + full [mc MONTHS_FULL] { + incr i + dict set l [string tolower $abr] $i + dict set l [string tolower $full] $i + } + lassign [UniquePrefixRegexp $l] regex lookup + append re ( $regex ) + dict set fieldSet month [incr fieldCount] + append postcode "dict set date month \[" \ + "dict get " [list $lookup] \ + " " \[ {string tolower $field} \ + [incr captureCount] \] \ + "\]\n" + } + C { # Gregorian century + append re \\s*(\\d\\d?) + dict set fieldSet century [incr fieldCount] + append postcode "dict set date century \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + d - e { # Day of month + append re \\s*(\\d\\d?) + dict set fieldSet dayOfMonth [incr fieldCount] + append postcode "dict set date dayOfMonth \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + E { # Prefix for locale-specific codes + set state %E + } + g { # ISO8601 2-digit year + append re \\s*(\\d\\d) + dict set fieldSet iso8601YearOfCentury \ + [incr fieldCount] + append postcode \ + "dict set date iso8601YearOfCentury \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + G { # ISO8601 4-digit year + append re \\s*(\\d\\d)(\\d\\d) + dict set fieldSet iso8601Century [incr fieldCount] + dict set fieldSet iso8601YearOfCentury \ + [incr fieldCount] + append postcode \ + "dict set date iso8601Century \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" \ + "dict set date iso8601YearOfCentury \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + H - k { # Hour of day + append re \\s*(\\d\\d?) + dict set fieldSet hour [incr fieldCount] + append postcode "dict set date hour \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + I - l { # Hour, AM/PM + append re \\s*(\\d\\d?) + dict set fieldSet hourAMPM [incr fieldCount] + append postcode "dict set date hourAMPM \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + j { # Day of year + append re \\s*(\\d\\d?\\d?) + dict set fieldSet dayOfYear [incr fieldCount] + append postcode "dict set date dayOfYear \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + J { # Julian Day Number + append re \\s*(\\d+) + dict set fieldSet julianDay [incr fieldCount] + append postcode "dict set date julianDay \[" \ + "::scan \$field" [incr captureCount] " %ld" \ + "\]\n" + } + m - N { # Month number + append re \\s*(\\d\\d?) + dict set fieldSet month [incr fieldCount] + append postcode "dict set date month \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + M { # Minute + append re \\s*(\\d\\d?) + dict set fieldSet minute [incr fieldCount] + append postcode "dict set date minute \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + n { # Literal newline + append re \\n + } + O { # Prefix for locale numerics + set state %O + } + p - P { # AM/PM indicator + set l [list [string tolower [mc AM]] 0 \ + [string tolower [mc PM]] 1] + lassign [UniquePrefixRegexp $l] regex lookup + append re ( $regex ) + dict set fieldSet amPmIndicator [incr fieldCount] + append postcode "dict set date amPmIndicator \[" \ + "dict get " [list $lookup] " \[string tolower " \ + "\$field" \ + [incr captureCount] \ + "\]\]\n" + } + Q { # Hi, Jeff! + append re {Stardate\s+([-+]?\d+)(\d\d\d)[.](\d)} + incr captureCount + dict set fieldSet seconds [incr fieldCount] + append postcode {dict set date seconds } \[ \ + {ParseStarDate $field} [incr captureCount] \ + { $field} [incr captureCount] \ + { $field} [incr captureCount] \ + \] \n + } + s { # Seconds from Posix Epoch + # This next case is insanely difficult, because it's + # problematic to determine whether the field is + # actually within the range of a wide integer. + append re {\s*([-+]?\d+)} + dict set fieldSet seconds [incr fieldCount] + append postcode {dict set date seconds } \[ \ + {ScanWide $field} [incr captureCount] \] \n + } + S { # Second + append re \\s*(\\d\\d?) + dict set fieldSet second [incr fieldCount] + append postcode "dict set date second \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + t { # Literal tab character + append re \\t + } + u - w { # Day number within week, 0 or 7 == Sun + # 1=Mon, 6=Sat + append re \\s*(\\d) + dict set fieldSet dayOfWeek [incr fieldCount] + append postcode {::scan $field} [incr captureCount] \ + { %d dow} \n \ + { + if { $dow == 0 } { + set dow 7 + } elseif { $dow > 7 } { + return -code error \ + -errorcode [list CLOCK badDayOfWeek] \ + "day of week is greater than 7" + } + dict set date dayOfWeek $dow + } + } + U { # Week of year. The first Sunday of + # the year is the first day of week + # 01. No scan rule uses this group. + append re \\s*\\d\\d? + } + V { # Week of ISO8601 year + + append re \\s*(\\d\\d?) + dict set fieldSet iso8601Week [incr fieldCount] + append postcode "dict set date iso8601Week \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + W { # Week of the year (00-53). The first + # Monday of the year is the first day + # of week 01. No scan rule uses this + # group. + append re \\s*\\d\\d? + } + y { # Two-digit Gregorian year + append re \\s*(\\d\\d?) + dict set fieldSet yearOfCentury [incr fieldCount] + append postcode "dict set date yearOfCentury \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + Y { # 4-digit Gregorian year + append re \\s*(\\d\\d)(\\d\\d) + dict set fieldSet century [incr fieldCount] + dict set fieldSet yearOfCentury [incr fieldCount] + append postcode \ + "dict set date century \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" \ + "dict set date yearOfCentury \[" \ + "::scan \$field" [incr captureCount] " %d" \ + "\]\n" + } + z - Z { # Time zone name + append re {(?:([-+]\d\d(?::?\d\d(?::?\d\d)?)?)|([[:alnum:]]{1,4}))} + dict set fieldSet tzName [incr fieldCount] + append postcode \ + {if } \{ { $field} [incr captureCount] \ + { ne "" } \} { } \{ \n \ + {dict set date tzName $field} \ + $captureCount \n \ + \} { else } \{ \n \ + {dict set date tzName } \[ \ + {ConvertLegacyTimeZone $field} \ + [incr captureCount] \] \n \ + \} \n \ + } + % { # Literal percent character + append re % + } + default { + append re % + if { ! [string is alnum $c] } { + append re \\ + } + append re $c + } + } + } + %E { + switch -exact -- $c { + C { # Locale-dependent era + set d {} + foreach triple [mc LOCALE_ERAS] { + lassign $triple t symbol year + dict set d [string tolower $symbol] $year + } + lassign [UniquePrefixRegexp $d] regex lookup + append re (?: $regex ) + } + E { + set l {} + dict set l [string tolower [mc BCE]] BCE + dict set l [string tolower [mc CE]] CE + dict set l b.c.e. BCE + dict set l c.e. CE + dict set l b.c. BCE + dict set l a.d. CE + lassign [UniquePrefixRegexp $l] regex lookup + append re ( $regex ) + dict set fieldSet era [incr fieldCount] + append postcode "dict set date era \["\ + "dict get " [list $lookup] \ + { } \[ {string tolower $field} \ + [incr captureCount] \] \ + "\]\n" + } + y { # Locale-dependent year of the era + lassign [LocaleNumeralMatcher $locale] regex lookup + append re $regex + incr captureCount + } + default { + append re %E + if { ! [string is alnum $c] } { + append re \\ + } + append re $c + } + } + set state {} + } + %O { + switch -exact -- $c { + d - e { + lassign [LocaleNumeralMatcher $locale] regex lookup + append re $regex + dict set fieldSet dayOfMonth [incr fieldCount] + append postcode "dict set date dayOfMonth \[" \ + "dict get " [list $lookup] " \$field" \ + [incr captureCount] \ + "\]\n" + } + H - k { + lassign [LocaleNumeralMatcher $locale] regex lookup + append re $regex + dict set fieldSet hour [incr fieldCount] + append postcode "dict set date hour \[" \ + "dict get " [list $lookup] " \$field" \ + [incr captureCount] \ + "\]\n" + } + I - l { + lassign [LocaleNumeralMatcher $locale] regex lookup + append re $regex + dict set fieldSet hourAMPM [incr fieldCount] + append postcode "dict set date hourAMPM \[" \ + "dict get " [list $lookup] " \$field" \ + [incr captureCount] \ + "\]\n" + } + m { + lassign [LocaleNumeralMatcher $locale] regex lookup + append re $regex + dict set fieldSet month [incr fieldCount] + append postcode "dict set date month \[" \ + "dict get " [list $lookup] " \$field" \ + [incr captureCount] \ + "\]\n" + } + M { + lassign [LocaleNumeralMatcher $locale] regex lookup + append re $regex + dict set fieldSet minute [incr fieldCount] + append postcode "dict set date minute \[" \ + "dict get " [list $lookup] " \$field" \ + [incr captureCount] \ + "\]\n" + } + S { + lassign [LocaleNumeralMatcher $locale] regex lookup + append re $regex + dict set fieldSet second [incr fieldCount] + append postcode "dict set date second \[" \ + "dict get " [list $lookup] " \$field" \ + [incr captureCount] \ + "\]\n" + } + u - w { + lassign [LocaleNumeralMatcher $locale] regex lookup + append re $regex + dict set fieldSet dayOfWeek [incr fieldCount] + append postcode "set dow \[dict get " [list $lookup] \ + { $field} [incr captureCount] \] \n \ + { + if { $dow == 0 } { + set dow 7 + } elseif { $dow > 7 } { + return -code error \ + -errorcode [list CLOCK badDayOfWeek] \ + "day of week is greater than 7" + } + dict set date dayOfWeek $dow + } + } + y { + lassign [LocaleNumeralMatcher $locale] regex lookup + append re $regex + dict set fieldSet yearOfCentury [incr fieldCount] + append postcode {dict set date yearOfCentury } \[ \ + {dict get } [list $lookup] { $field} \ + [incr captureCount] \] \n + } + default { + append re %O + if { ! [string is alnum $c] } { + append re \\ + } + append re $c + } + } + set state {} + } + } + } + + # Clean up any unfinished format groups + + append re $state \\s*\$ + + # Build the procedure + + set procBody {} + append procBody "variable ::tcl::clock::TZData" \n + append procBody "if \{ !\[ regexp -nocase [list $re] \$string ->" + for { set i 1 } { $i <= $captureCount } { incr i } { + append procBody " " field $i + } + append procBody "\] \} \{" \n + append procBody { + return -code error -errorcode [list CLOCK badInputString] \ + {input string does not match supplied format} + } + append procBody \}\n + append procBody "set date \[dict create\]" \n + append procBody {dict set date tzName $timeZone} \n + append procBody $postcode + append procBody [list set changeover [mc GREGORIAN_CHANGE_DATE]] \n + + # Set up the time zone before doing anything with a default base date + # that might need a timezone to interpret it. + + if { ![dict exists $fieldSet seconds] + && ![dict exists $fieldSet starDate] } { + if { [dict exists $fieldSet tzName] } { + append procBody { + set timeZone [dict get $date tzName] + } + } + append procBody { + ::tcl::clock::SetupTimeZone $timeZone + } + } + + # Add code that gets Julian Day Number from the fields. + + append procBody [MakeParseCodeFromFields $fieldSet $DateParseActions] + + # Get time of day + + append procBody [MakeParseCodeFromFields $fieldSet $TimeParseActions] + + # Assemble seconds from the Julian day and second of the day. + # Convert to local time unless epoch seconds or stardate are + # being processed - they're always absolute + + if { ![dict exists $fieldSet seconds] + && ![dict exists $fieldSet starDate] } { + append procBody { + if { [dict get $date julianDay] > 5373484 } { + return -code error -errorcode [list CLOCK dateTooLarge] \ + "requested date too large to represent" + } + dict set date localSeconds [expr { + -210866803200 + + ( 86400 * wide([dict get $date julianDay]) ) + + [dict get $date secondOfDay] + }] + } + + # Finally, convert the date to local time + + append procBody { + set date [::tcl::clock::ConvertLocalToUTC $date[set date {}] \ + $TZData($timeZone) $changeover] + } + } + + # Return result + + append procBody {return [dict get $date seconds]} \n + + proc $procName { string baseTime timeZone } $procBody + + # puts [list proc $procName [list string baseTime timeZone] $procBody] + + return $procName +} + +#---------------------------------------------------------------------- +# +# LocaleNumeralMatcher -- +# +# Composes a regexp that captures the numerals in the given locale, and +# a dictionary to map them to conventional numerals. +# +# Parameters: +# locale - Name of the current locale +# +# Results: +# Returns a two-element list comprising the regexp and the dictionary. +# +# Side effects: +# Caches the result. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::LocaleNumeralMatcher {l} { + variable LocaleNumeralCache + + if { ![dict exists $LocaleNumeralCache $l] } { + set d {} + set i 0 + set sep \( + foreach n [mc LOCALE_NUMERALS] { + dict set d $n $i + regsub -all {[^[:alnum:]]} $n \\\\& subex + append re $sep $subex + set sep | + incr i + } + append re \) + dict set LocaleNumeralCache $l [list $re $d] + } + return [dict get $LocaleNumeralCache $l] +} + + + +#---------------------------------------------------------------------- +# +# UniquePrefixRegexp -- +# +# Composes a regexp that performs unique-prefix matching. The RE +# matches one of a supplied set of strings, or any unique prefix +# thereof. +# +# Parameters: +# data - List of alternating match-strings and values. +# Match-strings with distinct values are considered +# distinct. +# +# Results: +# Returns a two-element list. The first is a regexp that matches any +# unique prefix of any of the strings. The second is a dictionary whose +# keys are match values from the regexp and whose values are the +# corresponding values from 'data'. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::UniquePrefixRegexp { data } { + # The 'successors' dictionary will contain, for each string that is a + # prefix of any key, all characters that may follow that prefix. The + # 'prefixMapping' dictionary will have keys that are prefixes of keys and + # values that correspond to the keys. + + set prefixMapping [dict create] + set successors [dict create {} {}] + + # Walk the key-value pairs + + foreach { key value } $data { + # Construct all prefixes of the key; + + set prefix {} + foreach char [split $key {}] { + set oldPrefix $prefix + dict set successors $oldPrefix $char {} + append prefix $char + + # Put the prefixes in the 'prefixMapping' and 'successors' + # dictionaries + + dict lappend prefixMapping $prefix $value + if { ![dict exists $successors $prefix] } { + dict set successors $prefix {} + } + } + } + + # Identify those prefixes that designate unique values, and those that are + # the full keys + + set uniquePrefixMapping {} + dict for { key valueList } $prefixMapping { + if { [llength $valueList] == 1 } { + dict set uniquePrefixMapping $key [lindex $valueList 0] + } + } + foreach { key value } $data { + dict set uniquePrefixMapping $key $value + } + + # Construct the re. + + return [list \ + [MakeUniquePrefixRegexp $successors $uniquePrefixMapping {}] \ + $uniquePrefixMapping] +} + +#---------------------------------------------------------------------- +# +# MakeUniquePrefixRegexp -- +# +# Service procedure for 'UniquePrefixRegexp' that constructs a regular +# expresison that matches the unique prefixes. +# +# Parameters: +# successors - Dictionary whose keys are all prefixes +# of keys passed to 'UniquePrefixRegexp' and whose +# values are dictionaries whose keys are the characters +# that may follow those prefixes. +# uniquePrefixMapping - Dictionary whose keys are the unique +# prefixes and whose values are not examined. +# prefixString - Current prefix being processed. +# +# Results: +# Returns a constructed regular expression that matches the set of +# unique prefixes beginning with the 'prefixString'. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::MakeUniquePrefixRegexp { successors + uniquePrefixMapping + prefixString } { + + # Get the characters that may follow the current prefix string + + set schars [lsort -ascii [dict keys [dict get $successors $prefixString]]] + if { [llength $schars] == 0 } { + return {} + } + + # If there is more than one successor character, or if the current prefix + # is a unique prefix, surround the generated re with non-capturing + # parentheses. + + set re {} + if { + [dict exists $uniquePrefixMapping $prefixString] + || [llength $schars] > 1 + } then { + append re "(?:" + } + + # Generate a regexp that matches the successors. + + set sep "" + foreach { c } $schars { + set nextPrefix $prefixString$c + regsub -all {[^[:alnum:]]} $c \\\\& rechar + append re $sep $rechar \ + [MakeUniquePrefixRegexp \ + $successors $uniquePrefixMapping $nextPrefix] + set sep | + } + + # If the current prefix is a unique prefix, make all following text + # optional. Otherwise, if there is more than one successor character, + # close the non-capturing parentheses. + + if { [dict exists $uniquePrefixMapping $prefixString] } { + append re ")?" + } elseif { [llength $schars] > 1 } { + append re ")" + } + + return $re +} + +#---------------------------------------------------------------------- +# +# MakeParseCodeFromFields -- +# +# Composes Tcl code to extract the Julian Day Number from a dictionary +# containing date fields. +# +# Parameters: +# dateFields -- Dictionary whose keys are fields of the date, +# and whose values are the rightmost positions +# at which those fields appear. +# parseActions -- List of triples: field set, priority, and +# code to emit. Smaller priorities are better, and +# the list must be in ascending order by priority +# +# Results: +# Returns a burst of code that extracts the day number from the given +# date. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::MakeParseCodeFromFields { dateFields parseActions } { + + set currPrio 999 + set currFieldPos [list] + set currCodeBurst { + error "in ::tcl::clock::MakeParseCodeFromFields: can't happen" + } + + foreach { fieldSet prio parseAction } $parseActions { + # If we've found an answer that's better than any that follow, quit + # now. + + if { $prio > $currPrio } { + break + } + + # Accumulate the field positions that are used in the current field + # grouping. + + set fieldPos [list] + set ok true + foreach field $fieldSet { + if { ! [dict exists $dateFields $field] } { + set ok 0 + break + } + lappend fieldPos [dict get $dateFields $field] + } + + # Quit if we don't have a complete set of fields + if { !$ok } { + continue + } + + # Determine whether the current answer is better than the last. + + set fPos [lsort -integer -decreasing $fieldPos] + + if { $prio == $currPrio } { + foreach currPos $currFieldPos newPos $fPos { + if { + ![string is integer $newPos] + || ![string is integer $currPos] + || $newPos > $currPos + } then { + break + } + if { $newPos < $currPos } { + set ok 0 + break + } + } + } + if { !$ok } { + continue + } + + # Remember the best possibility for extracting date information + + set currPrio $prio + set currFieldPos $fPos + set currCodeBurst $parseAction + } + + return $currCodeBurst +} + +#---------------------------------------------------------------------- +# +# EnterLocale -- +# +# Switch [mclocale] to a given locale if necessary +# +# Parameters: +# locale -- Desired locale +# +# Results: +# Returns the locale that was previously current. +# +# Side effects: +# Does [mclocale]. If necessary, loads the designated locale's files. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::EnterLocale { locale } { + if { $locale eq {system} } { + if { $::tcl_platform(platform) ne {windows} } { + # On a non-windows platform, the 'system' locale is the same as + # the 'current' locale + + set locale current + } else { + # On a windows platform, the 'system' locale is adapted from the + # 'current' locale by applying the date and time formats from the + # Control Panel. First, load the 'current' locale if it's not yet + # loaded + + mcpackagelocale set [mclocale] + + # Make a new locale string for the system locale, and get the + # Control Panel information + + set locale [mclocale]_windows + if { ! [mcpackagelocale present $locale] } { + LoadWindowsDateTimeFormats $locale + } + } + } + if { $locale eq {current}} { + set locale [mclocale] + } + # Eventually load the locale + mcpackagelocale set $locale +} + +#---------------------------------------------------------------------- +# +# LoadWindowsDateTimeFormats -- +# +# Load the date/time formats from the Control Panel in Windows and +# convert them so that they're usable by Tcl. +# +# Parameters: +# locale - Name of the locale in whose message catalog +# the converted formats are to be stored. +# +# Results: +# None. +# +# Side effects: +# Updates the given message catalog with the locale strings. +# +# Presumes that on entry, [mclocale] is set to the current locale, so that +# default strings can be obtained if the Registry query fails. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::LoadWindowsDateTimeFormats { locale } { + # Bail out if we can't find the Registry + + variable NoRegistry + if { [info exists NoRegistry] } return + + if { ![catch { + registry get "HKEY_CURRENT_USER\\Control Panel\\International" \ + sShortDate + } string] } { + set quote {} + set datefmt {} + foreach { unquoted quoted } [split $string '] { + append datefmt $quote [string map { + dddd %A + ddd %a + dd %d + d %e + MMMM %B + MMM %b + MM %m + M %N + yyyy %Y + yy %y + y %y + gg {} + } $unquoted] + if { $quoted eq {} } { + set quote ' + } else { + set quote $quoted + } + } + ::msgcat::mcset $locale DATE_FORMAT $datefmt + } + + if { ![catch { + registry get "HKEY_CURRENT_USER\\Control Panel\\International" \ + sLongDate + } string] } { + set quote {} + set ldatefmt {} + foreach { unquoted quoted } [split $string '] { + append ldatefmt $quote [string map { + dddd %A + ddd %a + dd %d + d %e + MMMM %B + MMM %b + MM %m + M %N + yyyy %Y + yy %y + y %y + gg {} + } $unquoted] + if { $quoted eq {} } { + set quote ' + } else { + set quote $quoted + } + } + ::msgcat::mcset $locale LOCALE_DATE_FORMAT $ldatefmt + } + + if { ![catch { + registry get "HKEY_CURRENT_USER\\Control Panel\\International" \ + sTimeFormat + } string] } { + set quote {} + set timefmt {} + foreach { unquoted quoted } [split $string '] { + append timefmt $quote [string map { + HH %H + H %k + hh %I + h %l + mm %M + m %M + ss %S + s %S + tt %p + t %p + } $unquoted] + if { $quoted eq {} } { + set quote ' + } else { + set quote $quoted + } + } + ::msgcat::mcset $locale TIME_FORMAT $timefmt + } + + catch { + ::msgcat::mcset $locale DATE_TIME_FORMAT "$datefmt $timefmt" + } + catch { + ::msgcat::mcset $locale LOCALE_DATE_TIME_FORMAT "$ldatefmt $timefmt" + } + + return + +} + +#---------------------------------------------------------------------- +# +# LocalizeFormat -- +# +# Map away locale-dependent format groups in a clock format. +# +# Parameters: +# locale -- Current [mclocale] locale, supplied to avoid +# an extra call +# format -- Format supplied to [clock scan] or [clock format] +# +# Results: +# Returns the string with locale-dependent composite format groups +# substituted out. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::LocalizeFormat { locale format } { + + # message catalog key to cache this format + set key FORMAT_$format + + if { [::msgcat::mcexists -exactlocale -exactnamespace $key] } { + return [mc $key] + } + # Handle locale-dependent format groups by mapping them out of the format + # string. Note that the order of the [string map] operations is + # significant because later formats can refer to later ones; for example + # %c can refer to %X, which in turn can refer to %T. + + set list { + %% %% + %D %m/%d/%Y + %+ {%a %b %e %H:%M:%S %Z %Y} + } + lappend list %EY [string map $list [mc LOCALE_YEAR_FORMAT]] + lappend list %T [string map $list [mc TIME_FORMAT_24_SECS]] + lappend list %R [string map $list [mc TIME_FORMAT_24]] + lappend list %r [string map $list [mc TIME_FORMAT_12]] + lappend list %X [string map $list [mc TIME_FORMAT]] + lappend list %EX [string map $list [mc LOCALE_TIME_FORMAT]] + lappend list %x [string map $list [mc DATE_FORMAT]] + lappend list %Ex [string map $list [mc LOCALE_DATE_FORMAT]] + lappend list %c [string map $list [mc DATE_TIME_FORMAT]] + lappend list %Ec [string map $list [mc LOCALE_DATE_TIME_FORMAT]] + set format [string map $list $format] + + ::msgcat::mcset $locale $key $format + return $format +} + +#---------------------------------------------------------------------- +# +# FormatNumericTimeZone -- +# +# Formats a time zone as +hhmmss +# +# Parameters: +# z - Time zone in seconds east of Greenwich +# +# Results: +# Returns the time zone formatted in a numeric form +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::FormatNumericTimeZone { z } { + if { $z < 0 } { + set z [expr { - $z }] + set retval - + } else { + set retval + + } + append retval [::format %02d [expr { $z / 3600 }]] + set z [expr { $z % 3600 }] + append retval [::format %02d [expr { $z / 60 }]] + set z [expr { $z % 60 }] + if { $z != 0 } { + append retval [::format %02d $z] + } + return $retval +} + +#---------------------------------------------------------------------- +# +# FormatStarDate -- +# +# Formats a date as a StarDate. +# +# Parameters: +# date - Dictionary containing 'year', 'dayOfYear', and +# 'localSeconds' fields. +# +# Results: +# Returns the given date formatted as a StarDate. +# +# Side effects: +# None. +# +# Jeff Hobbs put this in to support an atrocious pun about Tcl being +# "Enterprise ready." Now we're stuck with it. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::FormatStarDate { date } { + variable Roddenberry + + # Get day of year, zero based + + set doy [expr { [dict get $date dayOfYear] - 1 }] + + # Determine whether the year is a leap year + + set lp [IsGregorianLeapYear $date] + + # Convert day of year to a fractional year + + if { $lp } { + set fractYear [expr { 1000 * $doy / 366 }] + } else { + set fractYear [expr { 1000 * $doy / 365 }] + } + + # Put together the StarDate + + return [::format "Stardate %02d%03d.%1d" \ + [expr { [dict get $date year] - $Roddenberry }] \ + $fractYear \ + [expr { [dict get $date localSeconds] % 86400 + / ( 86400 / 10 ) }]] +} + +#---------------------------------------------------------------------- +# +# ParseStarDate -- +# +# Parses a StarDate +# +# Parameters: +# year - Year from the Roddenberry epoch +# fractYear - Fraction of a year 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] + }] +} + +#---------------------------------------------------------------------- +# +# GetSystemTimeZone -- +# +# Determines the system time zone, which is the default for the +# 'clock' command if no other zone is supplied. +# +# Parameters: +# None. +# +# Results: +# Returns the system time zone. +# +# Side effects: +# Stores the system time zone in the 'CachedSystemTimeZone' +# variable, since determining it may be an expensive process. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::GetSystemTimeZone {} { + variable CachedSystemTimeZone + variable TimeZoneBad + + if {[set result [getenv TCL_TZ]] ne {}} { + set timezone $result + } elseif {[set result [getenv TZ]] ne {}} { + set timezone $result + } 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} } { + set timezone [GuessWindowsTimeZone] + } elseif { [file exists /etc/localtime] + && ![catch {ReadZoneinfoFile \ + Tcl/Localtime /etc/localtime}] } { + set timezone :Tcl/Localtime + } else { + set timezone :localtime + } + set CachedSystemTimeZone $timezone + } + if { ![dict exists $TimeZoneBad $timezone] } { + dict set TimeZoneBad $timezone [catch {SetupTimeZone $timezone}] + } + if { [dict get $TimeZoneBad $timezone] } { + return :localtime + } else { + return $timezone + } +} + +#---------------------------------------------------------------------- +# +# ConvertLegacyTimeZone -- +# +# Given an alphanumeric time zone identifier and the system time zone, +# convert the alphanumeric identifier to an unambiguous time zone. +# +# Parameters: +# tzname - Name of the time zone to convert +# +# Results: +# Returns a time zone name corresponding to tzname, but in an +# unambiguous form, generally +hhmm. +# +# This procedure is implemented primarily to allow the parsing of RFC822 +# date/time strings. Processing a time zone name on input is not recommended +# practice, because there is considerable room for ambiguity; for instance, is +# BST Brazilian Standard Time, or British Summer Time? +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::ConvertLegacyTimeZone { tzname } { + variable LegacyTimeZone + + set tzname [string tolower $tzname] + if { ![dict exists $LegacyTimeZone $tzname] } { + return -code error -errorcode [list CLOCK badTZName $tzname] \ + "time zone \"$tzname\" not found" + } + return [dict get $LegacyTimeZone $tzname] +} + +#---------------------------------------------------------------------- +# +# SetupTimeZone -- +# +# Given the name or specification of a time zone, sets up its in-memory +# data. +# +# Parameters: +# tzname - Name of a time zone +# +# Results: +# Unless the time zone is ':localtime', sets the TZData array to contain +# the lookup table for local<->UTC conversion. Returns an error if the +# time zone cannot be parsed. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::SetupTimeZone { timezone } { + variable TZData + + if {! [info exists TZData($timezone)] } { + variable MINWIDE + if { $timezone eq {:localtime} } { + # Nothing to do, we'll convert using the localtime function + + } elseif { + [regexp {^([-+])(\d\d)(?::?(\d\d)(?::?(\d\d))?)?} $timezone \ + -> s hh mm ss] + } then { + # Make a fixed offset + + ::scan $hh %d hh + if { $mm eq {} } { + set mm 0 + } else { + ::scan $mm %d mm + } + if { $ss eq {} } { + set ss 0 + } else { + ::scan $ss %d ss + } + set offset [expr { ( $hh * 60 + $mm ) * 60 + $ss }] + if { $s eq {-} } { + set offset [expr { - $offset }] + } + set TZData($timezone) [list [list $MINWIDE $offset -1 $timezone]] + + } elseif { [string index $timezone 0] eq {:} } { + # Convert using a time zone file + + if { + [catch { + LoadTimeZoneFile [string range $timezone 1 end] + }] && [catch { + LoadZoneinfoFile [string range $timezone 1 end] + }] + } then { + return -code error \ + -errorcode [list CLOCK badTimeZone $timezone] \ + "time zone \"$timezone\" not found" + } + } elseif { ![catch {ParsePosixTimeZone $timezone} tzfields] } { + # This looks like a POSIX time zone - try to process it + + if { [catch {ProcessPosixTimeZone $tzfields} data opts] } { + if { [lindex [dict get $opts -errorcode] 0] eq {CLOCK} } { + dict unset opts -errorinfo + } + return -options $opts $data + } else { + set TZData($timezone) $data + } + + } else { + # We couldn't parse this as a POSIX time zone. Try again with a + # time zone file - this time without a colon + + if { [catch { LoadTimeZoneFile $timezone }] + && [catch { LoadZoneinfoFile $timezone } - opts] } { + dict unset opts -errorinfo + return -options $opts "time zone $timezone not found" + } + set TZData($timezone) $TZData(:$timezone) + } + } + + return +} + +#---------------------------------------------------------------------- +# +# GuessWindowsTimeZone -- +# +# Determines the system time zone on windows. +# +# Parameters: +# None. +# +# Results: +# Returns a time zone specifier that corresponds to the system time zone +# information found in the Registry. +# +# Bugs: +# Fixed dates for DST change are unimplemented at present, because no +# time zone information supplied with Windows actually uses them! +# +# On a Windows system where neither $env(TCL_TZ) nor $env(TZ) is specified, +# GuessWindowsTimeZone looks in the Registry for the system time zone +# information. It then attempts to find an entry in WinZoneInfo for a time +# zone that uses the same rules. If it finds one, it returns it; otherwise, +# it constructs a Posix-style time zone string and returns that. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::GuessWindowsTimeZone {} { + variable WinZoneInfo + variable NoRegistry + variable TimeZoneBad + + if { [info exists NoRegistry] } { + return :localtime + } + + # Dredge time zone information out of the registry + + if { [catch { + set rpath HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\TimeZoneInformation + set data [list \ + [expr { -60 + * [registry get $rpath Bias] }] \ + [expr { -60 + * [registry get $rpath StandardBias] }] \ + [expr { -60 \ + * [registry get $rpath DaylightBias] }]] + set stdtzi [registry get $rpath StandardStart] + foreach ind {0 2 14 4 6 8 10 12} { + binary scan $stdtzi @${ind}s val + lappend data $val + } + set daytzi [registry get $rpath DaylightStart] + foreach ind {0 2 14 4 6 8 10 12} { + binary scan $daytzi @${ind}s val + lappend data $val + } + }] } { + # Missing values in the Registry - bail out + + return :localtime + } + + # Make up a Posix time zone specifier if we can't find one. Check here + # that the tzdata file exists, in case we're running in an environment + # (e.g. starpack) where tzdata is incomplete. (Bug 1237907) + + if { [dict exists $WinZoneInfo $data] } { + set tzname [dict get $WinZoneInfo $data] + if { ! [dict exists $TimeZoneBad $tzname] } { + dict set TimeZoneBad $tzname [catch {SetupTimeZone $tzname}] + } + } else { + set tzname {} + } + if { $tzname eq {} || [dict get $TimeZoneBad $tzname] } { + lassign $data \ + bias stdBias dstBias \ + stdYear stdMonth stdDayOfWeek stdDayOfMonth \ + stdHour stdMinute stdSecond stdMillisec \ + dstYear dstMonth dstDayOfWeek dstDayOfMonth \ + dstHour dstMinute dstSecond dstMillisec + set stdDelta [expr { $bias + $stdBias }] + set dstDelta [expr { $bias + $dstBias }] + if { $stdDelta <= 0 } { + set stdSignum + + set stdDelta [expr { - $stdDelta }] + set dispStdSignum - + } else { + set stdSignum - + set dispStdSignum + + } + set hh [::format %02d [expr { $stdDelta / 3600 }]] + set mm [::format %02d [expr { ($stdDelta / 60 ) % 60 }]] + set ss [::format %02d [expr { $stdDelta % 60 }]] + set tzname {} + append tzname < $dispStdSignum $hh $mm > $stdSignum $hh : $mm : $ss + if { $stdMonth >= 0 } { + if { $dstDelta <= 0 } { + set dstSignum + + set dstDelta [expr { - $dstDelta }] + set dispDstSignum - + } else { + set dstSignum - + set dispDstSignum + + } + set hh [::format %02d [expr { $dstDelta / 3600 }]] + set mm [::format %02d [expr { ($dstDelta / 60 ) % 60 }]] + set ss [::format %02d [expr { $dstDelta % 60 }]] + append tzname < $dispDstSignum $hh $mm > $dstSignum $hh : $mm : $ss + if { $dstYear == 0 } { + append tzname ,M $dstMonth . $dstDayOfMonth . $dstDayOfWeek + } else { + # I have not been able to find any locale on which Windows + # converts time zone on a fixed day of the year, hence don't + # know how to interpret the fields. If someone can inform me, + # I'd be glad to code it up. For right now, we bail out in + # such a case. + return :localtime + } + append tzname / [::format %02d $dstHour] \ + : [::format %02d $dstMinute] \ + : [::format %02d $dstSecond] + if { $stdYear == 0 } { + append tzname ,M $stdMonth . $stdDayOfMonth . $stdDayOfWeek + } else { + # I have not been able to find any locale on which Windows + # converts time zone on a fixed day of the year, hence don't + # know how to interpret the fields. If someone can inform me, + # I'd be glad to code it up. For right now, we bail out in + # such a case. + return :localtime + } + append tzname / [::format %02d $stdHour] \ + : [::format %02d $stdMinute] \ + : [::format %02d $stdSecond] + } + dict set WinZoneInfo $data $tzname + } + + return [dict get $WinZoneInfo $data] +} + +#---------------------------------------------------------------------- +# +# LoadTimeZoneFile -- +# +# Load the data file that specifies the conversion between a +# given time zone and Greenwich. +# +# Parameters: +# fileName -- Name of the file to load +# +# Results: +# None. +# +# Side effects: +# TZData(:fileName) contains the time zone data +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::LoadTimeZoneFile { fileName } { + variable DataDir + variable TZData + + if { [info exists TZData($fileName)] } { + return + } + + # Since an unsafe interp uses the [clock] command in the parent, this code + # is security sensitive. Make sure that the path name cannot escape the + # given directory. + + if { ![regexp {^[[.-.][:alpha:]_]+(?:/[[.-.][:alpha:]_]+)*$} $fileName] } { + return -code error \ + -errorcode [list CLOCK badTimeZone $:fileName] \ + "time zone \":$fileName\" not valid" + } + try { + source -encoding utf-8 [file join $DataDir $fileName] + } on error {} { + return -code error \ + -errorcode [list CLOCK badTimeZone :$fileName] \ + "time zone \":$fileName\" not found" + } + return +} + +#---------------------------------------------------------------------- +# +# LoadZoneinfoFile -- +# +# Loads a binary time zone information file in Olson format. +# +# Parameters: +# fileName - Relative path name of the file to load. +# +# Results: +# Returns an empty result normally; returns an error if no Olson file +# was found or the file was malformed in some way. +# +# Side effects: +# TZData(:fileName) contains the time zone data +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::LoadZoneinfoFile { fileName } { + variable ZoneinfoPaths + + # Since an unsafe interp uses the [clock] command in the parent, this code + # is security sensitive. Make sure that the path name cannot escape the + # given directory. + + if { ![regexp {^[[.-.][:alpha:]_]+(?:/[[.-.][:alpha:]_]+)*$} $fileName] } { + return -code error \ + -errorcode [list CLOCK badTimeZone $:fileName] \ + "time zone \":$fileName\" not valid" + } + foreach d $ZoneinfoPaths { + set fname [file join $d $fileName] + if { [file readable $fname] && [file isfile $fname] } { + break + } + unset fname + } + ReadZoneinfoFile $fileName $fname +} + +#---------------------------------------------------------------------- +# +# ReadZoneinfoFile -- +# +# Loads a binary time zone information file in Olson format. +# +# Parameters: +# fileName - Name of the time zone (relative path name of the +# file). +# fname - Absolute path name of the file. +# +# Results: +# Returns an empty result normally; returns an error if no Olson file +# was found or the file was malformed in some way. +# +# Side effects: +# TZData(:fileName) contains the time zone data +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::ReadZoneinfoFile {fileName fname} { + variable MINWIDE + variable TZData + if { ![file exists $fname] } { + return -code error "$fileName not found" + } + + if { [file size $fname] > 262144 } { + return -code error "$fileName too big" + } + + # Suck in all the data from the file + + set f [open $fname r] + fconfigure $f -translation binary + set d [read $f] + close $f + + # The file begins with a magic number, sixteen reserved bytes, and then + # six 4-byte integers giving counts of fields in the file. + + binary scan $d a4a1x15IIIIII \ + magic version nIsGMT nIsStd nLeap nTime nType nChar + set seek 44 + set ilen 4 + set iformat I + if { $magic != {TZif} } { + return -code error "$fileName not a time zone information file" + } + if { $nType > 255 } { + return -code error "$fileName contains too many time types" + } + # Accept only Posix-style zoneinfo. Sorry, 'leaps' bigots. + if { $nLeap != 0 } { + return -code error "$fileName contains leap seconds" + } + + # In a version 2 file, we use the second part of the file, which contains + # 64-bit transition times. + + if {$version eq "2"} { + set seek [expr { + 44 + + 5 * $nTime + + 6 * $nType + + 4 * $nLeap + + $nIsStd + + $nIsGMT + + $nChar + }] + binary scan $d @${seek}a4a1x15IIIIII \ + magic version nIsGMT nIsStd nLeap nTime nType nChar + if {$magic ne {TZif}} { + return -code error "seek address $seek miscomputed, magic = $magic" + } + set iformat W + set ilen 8 + incr seek 44 + } + + # Next come ${nTime} transition times, followed by ${nTime} time type + # codes. The type codes are unsigned 1-byte quantities. We insert an + # arbitrary start time in front of the transitions. + + binary scan $d @${seek}${iformat}${nTime}c${nTime} times tempCodes + incr seek [expr { ($ilen + 1) * $nTime }] + set times [linsert $times 0 $MINWIDE] + set codes {} + foreach c $tempCodes { + lappend codes [expr { $c & 0xFF }] + } + set codes [linsert $codes 0 0] + + # Next come ${nType} time type descriptions, each of which has an offset + # (seconds east of GMT), a DST indicator, and an index into the + # abbreviation text. + + for { set i 0 } { $i < $nType } { incr i } { + binary scan $d @${seek}Icc gmtOff isDst abbrInd + lappend types [list $gmtOff $isDst $abbrInd] + incr seek 6 + } + + # Next come $nChar characters of time zone name abbreviations, which are + # null-terminated. + # We build them up into a dictionary indexed by character index, because + # that's what's in the indices above. + + binary scan $d @${seek}a${nChar} abbrs + incr seek ${nChar} + set abbrList [split $abbrs \0] + set i 0 + set abbrevs {} + foreach a $abbrList { + for {set j 0} {$j <= [string length $a]} {incr j} { + dict set abbrevs $i [string range $a $j end] + incr i + } + } + + # Package up a list of tuples, each of which contains transition time, + # seconds east of Greenwich, DST flag and time zone abbreviation. + + set r {} + set lastTime $MINWIDE + foreach t $times c $codes { + if { $t < $lastTime } { + return -code error "$fileName has times out of order" + } + set lastTime $t + lassign [lindex $types $c] gmtoff isDst abbrInd + set abbrev [dict get $abbrevs $abbrInd] + lappend r [list $t $gmtoff $isDst $abbrev] + } + + # In a version 2 file, there is also a POSIX-style time zone description + # at the very end of the file. To get to it, skip over nLeap leap second + # values (8 bytes each), + # nIsStd standard/DST indicators and nIsGMT UTC/local indicators. + + if {$version eq {2}} { + set seek [expr {$seek + 8 * $nLeap + $nIsStd + $nIsGMT + 1}] + set last [string first \n $d $seek] + set posix [string range $d $seek [expr {$last-1}]] + if {[llength $posix] > 0} { + set posixFields [ParsePosixTimeZone $posix] + foreach tuple [ProcessPosixTimeZone $posixFields] { + lassign $tuple t gmtoff isDst abbrev + if {$t > $lastTime} { + lappend r $tuple + } + } + } + } + + set TZData(:$fileName) $r + + return +} + +#---------------------------------------------------------------------- +# +# ParsePosixTimeZone -- +# +# Parses the TZ environment variable in Posix form +# +# Parameters: +# tz Time zone specifier to be interpreted +# +# Results: +# Returns a dictionary whose values contain the various pieces of the +# time zone specification. +# +# Side effects: +# None. +# +# Errors: +# Throws an error if the syntax of the time zone is incorrect. +# +# The following keys are present in the dictionary: +# stdName - Name of the time zone when Daylight Saving Time +# is not in effect. +# stdSignum - Sign (+, -, or empty) of the offset from Greenwich +# to the given (non-DST) time zone. + and the empty +# string denote zones west of Greenwich, - denotes east +# of Greenwich; this is contrary to the ISO convention +# but follows Posix. +# stdHours - Hours part of the offset from Greenwich to the given +# (non-DST) time zone. +# stdMinutes - Minutes part of the offset from Greenwich to the +# given (non-DST) time zone. Empty denotes zero. +# stdSeconds - Seconds part of the offset from Greenwich to the +# given (non-DST) time zone. Empty denotes zero. +# dstName - Name of the time zone when DST is in effect, or the +# empty string if the time zone does not observe Daylight +# Saving Time. +# dstSignum, dstHours, dstMinutes, dstSeconds - +# Fields corresponding to stdSignum, stdHours, stdMinutes, +# stdSeconds for the Daylight Saving Time version of the +# time zone. If dstHours is empty, it is presumed to be 1. +# startDayOfYear - The ordinal number of the day of the year on which +# Daylight Saving Time begins. If this field is +# empty, then DST begins on a given month-week-day, +# as below. +# startJ - The letter J, or an empty string. If a J is present in +# this field, then startDayOfYear does not count February 29 +# even in leap years. +# startMonth - The number of the month in which Daylight Saving Time +# begins, supplied if startDayOfYear is empty. If both +# startDayOfYear and startMonth are empty, then US rules +# are presumed. +# startWeekOfMonth - The number of the week in the month in which +# Daylight Saving Time begins, in the range 1-5. +# 5 denotes the last week of the month even in a +# 4-week month. +# startDayOfWeek - The number of the day of the week (Sunday=0, +# Saturday=6) on which Daylight Saving Time begins. +# startHours - The hours part of the time of day at which Daylight +# Saving Time begins. An empty string is presumed to be 2. +# startMinutes - The minutes part of the time of day at which DST begins. +# An empty string is presumed zero. +# startSeconds - The seconds part of the time of day at which DST begins. +# An empty string is presumed zero. +# endDayOfYear, endJ, endMonth, endWeekOfMonth, endDayOfWeek, +# endHours, endMinutes, endSeconds - +# Specify the end of DST in the same way that the start* fields +# specify the beginning of DST. +# +# This procedure serves only to break the time specifier into fields. No +# attempt is made to canonicalize the fields or supply default values. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::ParsePosixTimeZone { tz } { + if {[regexp -expanded -nocase -- { + ^ + # 1 - Standard time zone name + ([[:alpha:]]+ | <[-+[:alnum:]]+>) + # 2 - Standard time zone offset, signum + ([-+]?) + # 3 - Standard time zone offset, hours + ([[:digit:]]{1,2}) + (?: + # 4 - Standard time zone offset, minutes + : ([[:digit:]]{1,2}) + (?: + # 5 - Standard time zone offset, seconds + : ([[:digit:]]{1,2} ) + )? + )? + (?: + # 6 - DST time zone name + ([[:alpha:]]+ | <[-+[:alnum:]]+>) + (?: + (?: + # 7 - DST time zone offset, signum + ([-+]?) + # 8 - DST time zone offset, hours + ([[:digit:]]{1,2}) + (?: + # 9 - DST time zone offset, minutes + : ([[:digit:]]{1,2}) + (?: + # 10 - DST time zone offset, seconds + : ([[:digit:]]{1,2}) + )? + )? + )? + (?: + , + (?: + # 11 - Optional J in n and Jn form 12 - Day of year + ( J ? ) ( [[:digit:]]+ ) + | M + # 13 - Month number 14 - Week of month 15 - Day of week + ( [[:digit:]] + ) + [.] ( [[:digit:]] + ) + [.] ( [[:digit:]] + ) + ) + (?: + # 16 - Start time of DST - hours + / ( [[:digit:]]{1,2} ) + (?: + # 17 - Start time of DST - minutes + : ( [[:digit:]]{1,2} ) + (?: + # 18 - Start time of DST - seconds + : ( [[:digit:]]{1,2} ) + )? + )? + )? + , + (?: + # 19 - Optional J in n and Jn form 20 - Day of year + ( J ? ) ( [[:digit:]]+ ) + | M + # 21 - Month number 22 - Week of month 23 - Day of week + ( [[:digit:]] + ) + [.] ( [[:digit:]] + ) + [.] ( [[:digit:]] + ) + ) + (?: + # 24 - End time of DST - hours + / ( [[:digit:]]{1,2} ) + (?: + # 25 - End time of DST - minutes + : ( [[:digit:]]{1,2} ) + (?: + # 26 - End time of DST - seconds + : ( [[:digit:]]{1,2} ) + )? + )? + )? + )? + )? + )? + $ + } $tz -> x(stdName) x(stdSignum) x(stdHours) x(stdMinutes) x(stdSeconds) \ + x(dstName) x(dstSignum) x(dstHours) x(dstMinutes) x(dstSeconds) \ + x(startJ) x(startDayOfYear) \ + x(startMonth) x(startWeekOfMonth) x(startDayOfWeek) \ + x(startHours) x(startMinutes) x(startSeconds) \ + x(endJ) x(endDayOfYear) \ + x(endMonth) x(endWeekOfMonth) x(endDayOfWeek) \ + x(endHours) x(endMinutes) x(endSeconds)] } { + # it's a good timezone + + return [array get x] + } + + return -code error\ + -errorcode [list CLOCK badTimeZone $tz] \ + "unable to parse time zone specification \"$tz\"" +} + +#---------------------------------------------------------------------- +# +# ProcessPosixTimeZone -- +# +# Handle a Posix time zone after it's been broken out into fields. +# +# Parameters: +# z - Dictionary returned from 'ParsePosixTimeZone' +# +# Results: +# Returns time zone information for the 'TZData' array. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::ProcessPosixTimeZone { z } { + variable MINWIDE + variable TZData + + # Determine the standard time zone name and seconds east of Greenwich + + set stdName [dict get $z stdName] + if { [string index $stdName 0] eq {<} } { + set stdName [string range $stdName 1 end-1] + } + if { [dict get $z stdSignum] eq {-} } { + set stdSignum +1 + } else { + set stdSignum -1 + } + set stdHours [lindex [::scan [dict get $z stdHours] %d] 0] + if { [dict get $z stdMinutes] ne {} } { + set stdMinutes [lindex [::scan [dict get $z stdMinutes] %d] 0] + } else { + set stdMinutes 0 + } + if { [dict get $z stdSeconds] ne {} } { + set stdSeconds [lindex [::scan [dict get $z stdSeconds] %d] 0] + } else { + set stdSeconds 0 + } + set stdOffset [expr { + (($stdHours * 60 + $stdMinutes) * 60 + $stdSeconds) * $stdSignum + }] + set data [list [list $MINWIDE $stdOffset 0 $stdName]] + + # If there's no daylight zone, we're done + + set dstName [dict get $z dstName] + if { $dstName eq {} } { + return $data + } + if { [string index $dstName 0] eq {<} } { + set dstName [string range $dstName 1 end-1] + } + + # Determine the daylight name + + if { [dict get $z dstSignum] eq {-} } { + set dstSignum +1 + } else { + set dstSignum -1 + } + if { [dict get $z dstHours] eq {} } { + set dstOffset [expr { 3600 + $stdOffset }] + } else { + set dstHours [lindex [::scan [dict get $z dstHours] %d] 0] + if { [dict get $z dstMinutes] ne {} } { + set dstMinutes [lindex [::scan [dict get $z dstMinutes] %d] 0] + } else { + set dstMinutes 0 + } + if { [dict get $z dstSeconds] ne {} } { + set dstSeconds [lindex [::scan [dict get $z dstSeconds] %d] 0] + } else { + set dstSeconds 0 + } + set dstOffset [expr { + (($dstHours*60 + $dstMinutes) * 60 + $dstSeconds) * $dstSignum + }] + } + + # Fill in defaults for European or US DST rules + # US start time is the second Sunday in March + # EU start time is the last Sunday in March + # US end time is the first Sunday in November. + # EU end time is the last Sunday in October + + if { + [dict get $z startDayOfYear] eq {} + && [dict get $z startMonth] eq {} + } then { + if {($stdSignum * $stdHours>=0) && ($stdSignum * $stdHours<=12)} { + # EU + dict set z startWeekOfMonth 5 + if {$stdHours>2} { + dict set z startHours 2 + } else { + dict set z startHours [expr {$stdHours+1}] + } + } else { + # US + dict set z startWeekOfMonth 2 + dict set z startHours 2 + } + dict set z startMonth 3 + dict set z startDayOfWeek 0 + dict set z startMinutes 0 + dict set z startSeconds 0 + } + if { + [dict get $z endDayOfYear] eq {} + && [dict get $z endMonth] eq {} + } then { + if {($stdSignum * $stdHours>=0) && ($stdSignum * $stdHours<=12)} { + # EU + dict set z endMonth 10 + dict set z endWeekOfMonth 5 + if {$stdHours>2} { + dict set z endHours 3 + } else { + dict set z endHours [expr {$stdHours+2}] + } + } else { + # US + dict set z endMonth 11 + dict set z endWeekOfMonth 1 + dict set z endHours 2 + } + dict set z endDayOfWeek 0 + dict set z endMinutes 0 + dict set z endSeconds 0 + } + + # Put DST in effect in all years from 1916 to 2099. + + for { set y 1916 } { $y < 2100 } { incr y } { + set startTime [DeterminePosixDSTTime $z start $y] + incr startTime [expr { - wide($stdOffset) }] + set endTime [DeterminePosixDSTTime $z end $y] + incr endTime [expr { - wide($dstOffset) }] + if { $startTime < $endTime } { + lappend data \ + [list $startTime $dstOffset 1 $dstName] \ + [list $endTime $stdOffset 0 $stdName] + } else { + lappend data \ + [list $endTime $stdOffset 0 $stdName] \ + [list $startTime $dstOffset 1 $dstName] + } + } + + return $data +} + +#---------------------------------------------------------------------- +# +# DeterminePosixDSTTime -- +# +# Determines the time that Daylight Saving Time starts or ends from a +# Posix time zone specification. +# +# Parameters: +# z - Time zone data returned from ParsePosixTimeZone. +# Missing fields are expected to be filled in with +# default values. +# bound - The word 'start' or 'end' +# y - The year for which the transition time is to be determined. +# +# Results: +# Returns the transition time as a count of seconds from the epoch. The +# time is relative to the wall clock, not UTC. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::DeterminePosixDSTTime { z bound y } { + + variable FEB_28 + + # Determine the start or end day of DST + + set date [dict create era CE year $y] + set doy [dict get $z ${bound}DayOfYear] + if { $doy ne {} } { + + # Time was specified as a day of the year + + if { [dict get $z ${bound}J] ne {} + && [IsGregorianLeapYear $y] + && ( $doy > $FEB_28 ) } { + incr doy + } + dict set date dayOfYear $doy + set date [GetJulianDayFromEraYearDay $date[set date {}] 2361222] + } else { + # Time was specified as a day of the week within a month + + dict set date month [dict get $z ${bound}Month] + dict set date dayOfWeek [dict get $z ${bound}DayOfWeek] + set dowim [dict get $z ${bound}WeekOfMonth] + if { $dowim >= 5 } { + set dowim -1 + } + dict set date dayOfWeekInMonth $dowim + set date [GetJulianDayFromEraYearMonthWeekDay $date[set date {}] 2361222] + + } + + set jd [dict get $date julianDay] + set seconds [expr { + wide($jd) * wide(86400) - wide(210866803200) + }] + + set h [dict get $z ${bound}Hours] + if { $h eq {} } { + set h 2 + } else { + set h [lindex [::scan $h %d] 0] + } + set m [dict get $z ${bound}Minutes] + if { $m eq {} } { + set m 0 + } else { + set m [lindex [::scan $m %d] 0] + } + set s [dict get $z ${bound}Seconds] + if { $s eq {} } { + set s 0 + } else { + set s [lindex [::scan $s %d] 0] + } + set tod [expr { ( $h * 60 + $m ) * 60 + $s }] + return [expr { $seconds + $tod }] +} + +#---------------------------------------------------------------------- +# +# GetLocaleEra -- +# +# Given local time expressed in seconds from the Posix epoch, +# determine localized era and year within the era. +# +# Parameters: +# date - Dictionary that must contain the keys, 'localSeconds', +# whose value is expressed as the appropriate local time; +# and 'year', whose value is the Gregorian year. +# etable - Value of the LOCALE_ERAS key in the message catalogue +# for the target locale. +# +# Results: +# Returns the dictionary, augmented with the keys, 'localeEra' and +# 'localeYear'. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::GetLocaleEra { date etable } { + set index [BSearch $etable [dict get $date localSeconds]] + if { $index < 0} { + dict set date localeEra \ + [::format %02d [expr { [dict get $date year] / 100 }]] + dict set date localeYear [expr { + [dict get $date year] % 100 + }] + } else { + dict set date localeEra [lindex $etable $index 1] + dict set date localeYear [expr { + [dict get $date year] - [lindex $etable $index 2] + }] + } + return $date +} + +#---------------------------------------------------------------------- +# +# GetJulianDayFromEraYearDay -- +# +# Given a year, month and day on the Gregorian calendar, determines +# the Julian Day Number beginning at noon on that date. +# +# Parameters: +# date -- A dictionary in which the 'era', 'year', and +# 'dayOfYear' slots are populated. The calendar in use +# is determined by the date itself relative to: +# changeover -- Julian day on which the Gregorian calendar was +# adopted in the current locale. +# +# Results: +# Returns the given dictionary augmented with a 'julianDay' key whose +# value is the desired Julian Day Number, and a 'gregorian' key that +# specifies whether the calendar is Gregorian (1) or Julian (0). +# +# Side effects: +# None. +# +# Bugs: +# This code needs to be moved to the C layer. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::GetJulianDayFromEraYearDay {date changeover} { + # Get absolute year number from the civil year + + switch -exact -- [dict get $date era] { + BCE { + set year [expr { 1 - [dict get $date year] }] + } + CE { + set year [dict get $date year] + } + } + set ym1 [expr { $year - 1 }] + + # Try the Gregorian calendar first. + + dict set date gregorian 1 + set jd [expr { + 1721425 + + [dict get $date dayOfYear] + + ( 365 * $ym1 ) + + ( $ym1 / 4 ) + - ( $ym1 / 100 ) + + ( $ym1 / 400 ) + }] + + # If the date is before the Gregorian change, use the Julian calendar. + + if { $jd < $changeover } { + dict set date gregorian 0 + set jd [expr { + 1721423 + + [dict get $date dayOfYear] + + ( 365 * $ym1 ) + + ( $ym1 / 4 ) + }] + } + + dict set date julianDay $jd + return $date +} + +#---------------------------------------------------------------------- +# +# GetJulianDayFromEraYearMonthWeekDay -- +# +# Determines the Julian Day number corresponding to the nth given +# day-of-the-week in a given month. +# +# Parameters: +# date - Dictionary containing the keys, 'era', 'year', 'month' +# 'weekOfMonth', 'dayOfWeek', and 'dayOfWeekInMonth'. +# changeover - Julian Day of adoption of the Gregorian calendar +# +# Results: +# Returns the given dictionary, augmented with a 'julianDay' key. +# +# Side effects: +# None. +# +# Bugs: +# This code needs to be moved to the C layer. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::GetJulianDayFromEraYearMonthWeekDay {date changeover} { + # Come up with a reference day; either the zeroeth day of the given month + # (dayOfWeekInMonth >= 0) or the seventh day of the following month + # (dayOfWeekInMonth < 0) + + set date2 $date + set week [dict get $date dayOfWeekInMonth] + if { $week >= 0 } { + dict set date2 dayOfMonth 0 + } else { + dict incr date2 month + dict set date2 dayOfMonth 7 + } + set date2 [GetJulianDayFromEraYearMonthDay $date2[set date2 {}] \ + $changeover] + set wd0 [WeekdayOnOrBefore [dict get $date dayOfWeek] \ + [dict get $date2 julianDay]] + dict set date julianDay [expr { $wd0 + 7 * $week }] + return $date +} + +#---------------------------------------------------------------------- +# +# IsGregorianLeapYear -- +# +# Determines whether a given date represents a leap year in the +# Gregorian calendar. +# +# Parameters: +# date -- The date to test. The fields, 'era', 'year' and 'gregorian' +# must be set. +# +# Results: +# Returns 1 if the year is a leap year, 0 otherwise. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::IsGregorianLeapYear { date } { + switch -exact -- [dict get $date era] { + BCE { + set year [expr { 1 - [dict get $date year]}] + } + CE { + set year [dict get $date year] + } + } + if { $year % 4 != 0 } { + return 0 + } elseif { ![dict get $date gregorian] } { + return 1 + } elseif { $year % 400 == 0 } { + return 1 + } elseif { $year % 100 == 0 } { + return 0 + } else { + return 1 + } +} + +#---------------------------------------------------------------------- +# +# WeekdayOnOrBefore -- +# +# Determine the nearest day of week (given by the 'weekday' parameter, +# Sunday==0) on or before a given Julian Day. +# +# Parameters: +# weekday -- Day of the week +# j -- Julian Day number +# +# Results: +# Returns the Julian Day Number of the desired date. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::WeekdayOnOrBefore { weekday j } { + set k [expr { ( $weekday + 6 ) % 7 }] + return [expr { $j - ( $j - $k ) % 7 }] +} + +#---------------------------------------------------------------------- +# +# BSearch -- +# +# Service procedure that does binary search in several places inside the +# 'clock' command. +# +# Parameters: +# list - List of lists, sorted in ascending order by the +# first elements +# key - Value to search for +# +# Results: +# Returns the index of the greatest element in $list that is less than +# or equal to $key. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::BSearch { list key } { + if {[llength $list] == 0} { + return -1 + } + if { $key < [lindex $list 0 0] } { + return -1 + } + + set l 0 + set u [expr { [llength $list] - 1 }] + + while { $l < $u } { + # At this point, we know that + # $k >= [lindex $list $l 0] + # Either $u == [llength $list] or else $k < [lindex $list $u+1 0] + # We find the midpoint of the interval {l,u} rounded UP, compare + # against it, and set l or u to maintain the invariant. Note that the + # interval shrinks at each step, guaranteeing convergence. + + set m [expr { ( $l + $u + 1 ) / 2 }] + if { $key >= [lindex $list $m 0] } { + set l $m + } else { + set u [expr { $m - 1 }] + } + } + + return $l +} + +#---------------------------------------------------------------------- +# +# clock add -- +# +# Adds an offset to a given time. +# +# Syntax: +# clock add clockval ?count unit?... ?-option value? +# +# Parameters: +# clockval -- Starting time value +# count -- Amount of a unit of time to add +# unit -- Unit of time to add, must be one of: +# years year months month weeks week +# days day hours hour minutes minute +# seconds second +# +# Options: +# -gmt BOOLEAN +# (Deprecated) Flag synonymous with '-timezone :GMT' +# -timezone ZONE +# Name of the time zone in which calculations are to be done. +# -locale NAME +# Name of the locale in which calculations are to be done. +# Used to determine the Gregorian change date. +# +# Results: +# Returns the given time adjusted by the given offset(s) in +# order. +# +# Notes: +# It is possible that adding a number of months or years will adjust the +# day of the month as well. For instance, the time at one month after +# 31 January is either 28 or 29 February, because February has fewer +# than 31 days. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::add { clockval args } { + if { [llength $args] % 2 != 0 } { + set cmdName "clock add" + return -code error \ + -errorcode [list CLOCK wrongNumArgs] \ + "wrong \# args: should be\ + \"$cmdName clockval ?number units?...\ + ?-gmt boolean? ?-locale LOCALE? ?-timezone ZONE?\"" + } + if { [catch { expr {wide($clockval)} } result] } { + return -code error $result + } + + set offsets {} + set gmt 0 + set locale c + set timezone [GetSystemTimeZone] + + foreach { a b } $args { + if { [string is integer -strict $a] } { + lappend offsets $a $b + } else { + switch -exact -- $a { + -g - -gm - -gmt { + set 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. +# Clears the buffered parse functions of the current locale. +# +# Parameters: +# loclist (ignored) +# +# Results: +# None. +# +# Side effects: +# Buffered parse functions are cleared. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::ChangeCurrentLocale {args} { + variable FormatProc + variable LocaleNumeralCache + variable CachedSystemTimeZone + variable TimeZoneBad + + foreach p [info procs [namespace current]::scanproc'*'current] { + rename $p {} + } + foreach p [info procs [namespace current]::formatproc'*'current] { + rename $p {} + } + + catch {array unset FormatProc *'current} + set LocaleNumeralCache {} +} + +#---------------------------------------------------------------------- +# +# ClearCaches -- +# +# Clears all caches to reclaim the memory used in [clock] +# +# Parameters: +# None. +# +# Results: +# None. +# +# Side effects: +# Caches are cleared. +# +#---------------------------------------------------------------------- + +proc ::tcl::clock::ClearCaches {} { + variable FormatProc + variable LocaleNumeralCache + variable CachedSystemTimeZone + variable TimeZoneBad + + foreach p [info procs [namespace current]::scanproc'*] { + rename $p {} + } + foreach p [info procs [namespace current]::formatproc'*] { + rename $p {} + } + + catch {unset FormatProc} + set LocaleNumeralCache {} + catch {unset CachedSystemTimeZone} + set TimeZoneBad {} + InitTZData +} diff --git a/lib/tcl8.6/history.tcl b/lib/tcl8.6/history.tcl new file mode 100644 index 0000000000000000000000000000000000000000..f06ffc94138f33c536a1aefa0eecd2413c6e7ee6 --- /dev/null +++ b/lib/tcl8.6/history.tcl @@ -0,0 +1,335 @@ +# history.tcl -- +# +# Implementation of the history command. +# +# Copyright (c) 1997 Sun Microsystems, Inc. +# +# See the file "license.terms" for information on usage and redistribution of +# this file, and for a DISCLAIMER OF ALL WARRANTIES. +# + +# The tcl::history array holds the history list and some additional +# bookkeeping variables. +# +# nextid the index used for the next history list item. +# keep the max size of the history list +# oldest the index of the oldest item in the history. + +namespace eval ::tcl { + variable history + if {![info exists history]} { + array set history { + nextid 0 + keep 20 + oldest -20 + } + } + + namespace ensemble create -command ::tcl::history -map { + add ::tcl::HistAdd + change ::tcl::HistChange + clear ::tcl::HistClear + event ::tcl::HistEvent + info ::tcl::HistInfo + keep ::tcl::HistKeep + nextid ::tcl::HistNextID + redo ::tcl::HistRedo + } +} + +# history -- +# +# This is the main history command. See the man page for its interface. +# This does some argument checking and calls the helper ensemble in the +# tcl namespace. + +proc ::history {args} { + # If no command given, we're doing 'history info'. Can't be done with an + # ensemble unknown handler, as those don't fire when no subcommand is + # given at all. + + if {![llength $args]} { + set args info + } + + # Tricky stuff needed to make stack and errors come out right! + tailcall apply {arglist {tailcall ::tcl::history {*}$arglist} ::tcl} $args +} + +# (unnamed) -- +# +# Callback when [::history] is destroyed. Destroys the implementation. +# +# Parameters: +# oldName what the command was called. +# newName what the command is now called (an empty string). +# op the operation (= delete). +# +# Results: +# none +# +# Side Effects: +# The implementation of the [::history] command ceases to exist. + +trace add command ::history delete [list apply {{oldName newName op} { + variable history + unset -nocomplain history + foreach c [info procs ::tcl::Hist*] { + rename $c {} + } + rename ::tcl::history {} +} ::tcl}] + +# tcl::HistAdd -- +# +# Add an item to the history, and optionally eval it at the global scope +# +# Parameters: +# event the command to add +# exec (optional) a substring of "exec" causes the command to +# be evaled. +# Results: +# If executing, then the results of the command are returned +# +# Side Effects: +# Adds to the history list + +proc ::tcl::HistAdd {event {exec {}}} { + variable history + + if { + [prefix longest {exec {}} $exec] eq "" + && [llength [info level 0]] == 3 + } then { + return -code error "bad argument \"$exec\": should be \"exec\"" + } + + # Do not add empty commands to the history + if {[string trim $event] eq ""} { + return "" + } + + # Maintain the history + set history([incr history(nextid)]) $event + unset -nocomplain history([incr history(oldest)]) + + # Only execute if 'exec' (or non-empty prefix of it) given + if {$exec eq ""} { + return "" + } + tailcall eval $event +} + +# tcl::HistKeep -- +# +# Set or query the limit on the length of the history list +# +# Parameters: +# limit (optional) the length of the history list +# +# Results: +# If no limit is specified, the current limit is returned +# +# Side Effects: +# Updates history(keep) if a limit is specified + +proc ::tcl::HistKeep {{count {}}} { + variable history + if {[llength [info level 0]] == 1} { + return $history(keep) + } + if {![string is integer -strict $count] || ($count < 0)} { + return -code error "illegal keep count \"$count\"" + } + set oldold $history(oldest) + set history(oldest) [expr {$history(nextid) - $count}] + for {} {$oldold <= $history(oldest)} {incr oldold} { + unset -nocomplain history($oldold) + } + set history(keep) $count +} + +# tcl::HistClear -- +# +# Erase the history list +# +# Parameters: +# none +# +# Results: +# none +# +# Side Effects: +# Resets the history array, except for the keep limit + +proc ::tcl::HistClear {} { + variable history + set keep $history(keep) + unset history + array set history [list \ + nextid 0 \ + keep $keep \ + oldest -$keep \ + ] +} + +# tcl::HistInfo -- +# +# Return a pretty-printed version of the history list +# +# Parameters: +# num (optional) the length of the history list to return +# +# Results: +# A formatted history list + +proc ::tcl::HistInfo {{count {}}} { + variable history + if {[llength [info level 0]] == 1} { + set count [expr {$history(keep) + 1}] + } elseif {![string is integer -strict $count]} { + return -code error "bad integer \"$count\"" + } + set result {} + set newline "" + for {set i [expr {$history(nextid) - $count + 1}]} \ + {$i <= $history(nextid)} {incr i} { + if {![info exists history($i)]} { + continue + } + set cmd [string map [list \n \n\t] [string trimright $history($i) \ \n]] + append result $newline[format "%6d %s" $i $cmd] + set newline \n + } + return $result +} + +# tcl::HistRedo -- +# +# Fetch the previous or specified event, execute it, and then replace +# the current history item with that event. +# +# Parameters: +# event (optional) index of history item to redo. Defaults to -1, +# which means the previous event. +# +# Results: +# Those of the command being redone. +# +# Side Effects: +# Replaces the current history list item with the one being redone. + +proc ::tcl::HistRedo {{event -1}} { + variable history + + set i [HistIndex $event] + if {$i == $history(nextid)} { + return -code error "cannot redo the current event" + } + set cmd $history($i) + HistChange $cmd 0 + tailcall eval $cmd +} + +# tcl::HistIndex -- +# +# Map from an event specifier to an index in the history list. +# +# Parameters: +# event index of history item to redo. +# If this is a positive number, it is used directly. +# If it is a negative number, then it counts back to a previous +# event, where -1 is the most recent event. +# A string can be matched, either by being the prefix of a +# command or by matching a command with string match. +# +# Results: +# The index into history, or an error if the index didn't match. + +proc ::tcl::HistIndex {event} { + variable history + if {![string is integer -strict $event]} { + for {set i [expr {$history(nextid)-1}]} {[info exists history($i)]} \ + {incr i -1} { + if {[string match $event* $history($i)]} { + return $i + } + if {[string match $event $history($i)]} { + return $i + } + } + return -code error "no event matches \"$event\"" + } elseif {$event <= 0} { + set i [expr {$history(nextid) + $event}] + } else { + set i $event + } + if {$i <= $history(oldest)} { + return -code error "event \"$event\" is too far in the past" + } + if {$i > $history(nextid)} { + return -code error "event \"$event\" hasn't occurred yet" + } + return $i +} + +# tcl::HistEvent -- +# +# Map from an event specifier to the value in the history list. +# +# Parameters: +# event index of history item to redo. See index for a description of +# possible event patterns. +# +# Results: +# The value from the history list. + +proc ::tcl::HistEvent {{event -1}} { + variable history + set i [HistIndex $event] + if {![info exists history($i)]} { + return "" + } + return [string trimright $history($i) \ \n] +} + +# tcl::HistChange -- +# +# Replace a value in the history list. +# +# Parameters: +# newValue The new value to put into the history list. +# event (optional) index of history item to redo. See index for a +# description of possible event patterns. This defaults to 0, +# which specifies the current event. +# +# Side Effects: +# Changes the history list. + +proc ::tcl::HistChange {newValue {event 0}} { + variable history + set i [HistIndex $event] + set history($i) $newValue +} + +# tcl::HistNextID -- +# +# Returns the number of the next history event. +# +# Parameters: +# None. +# +# Side Effects: +# None. + +proc ::tcl::HistNextID {} { + variable history + return [expr {$history(nextid) + 1}] +} + +return + +# Local Variables: +# mode: tcl +# fill-column: 78 +# End: diff --git a/lib/tcl8.6/init.tcl b/lib/tcl8.6/init.tcl new file mode 100644 index 0000000000000000000000000000000000000000..1a512940197987010b2517780971b59956a5ad28 --- /dev/null +++ b/lib/tcl8.6/init.tcl @@ -0,0 +1,827 @@ +# init.tcl -- +# +# Default system startup file for Tcl-based applications. Defines +# "unknown" procedure and auto-load facilities. +# +# Copyright (c) 1991-1993 The Regents of the University of California. +# Copyright (c) 1994-1996 Sun Microsystems, Inc. +# Copyright (c) 1998-1999 Scriptics Corporation. +# Copyright (c) 2004 Kevin B. Kenny. All rights reserved. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# + +# This test intentionally written in pre-7.5 Tcl +if {[info commands package] == ""} { + error "version mismatch: library\nscripts expect Tcl version 7.5b1 or later but the loaded version is\nonly [info patchlevel]" +} +package require -exact Tcl 8.6.14 + +# Compute the auto path to use in this interpreter. +# The values on the path come from several locations: +# +# The environment variable TCLLIBPATH +# +# tcl_library, which is the directory containing this init.tcl script. +# [tclInit] (Tcl_Init()) searches around for the directory containing this +# init.tcl and defines tcl_library to that location before sourcing it. +# +# The parent directory of tcl_library. Adding the parent +# means that packages in peer directories will be found automatically. +# +# Also add the directory ../lib relative to the directory where the +# executable is located. This is meant to find binary packages for the +# same architecture as the current executable. +# +# tcl_pkgPath, which is set by the platform-specific initialization routines +# On UNIX it is compiled in +# On Windows, it is not used +# +# (Ticket 41c9857bdd) In a safe interpreter, this file does not set +# ::auto_path (other than to {} if it is undefined). The caller, typically +# a Safe Base command, is responsible for setting ::auto_path. + +if {![info exists auto_path]} { + if {[info exists env(TCLLIBPATH)] && (![interp issafe])} { + set auto_path $env(TCLLIBPATH) + } else { + set auto_path "" + } +} +namespace eval tcl { + if {![interp issafe]} { + variable Dir + foreach Dir [list $::tcl_library [file dirname $::tcl_library]] { + if {$Dir ni $::auto_path} { + lappend ::auto_path $Dir + } + } + set Dir [file join [file dirname [file dirname \ + [info nameofexecutable]]] lib] + if {$Dir ni $::auto_path} { + lappend ::auto_path $Dir + } + if {[info exists ::tcl_pkgPath]} { catch { + foreach Dir $::tcl_pkgPath { + if {$Dir ni $::auto_path} { + lappend ::auto_path $Dir + } + } + }} + + variable Path [encoding dirs] + set Dir [file join $::tcl_library encoding] + if {$Dir ni $Path} { + lappend Path $Dir + encoding dirs $Path + } + 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 + } +} + +# Setup the unknown package handler + + +if {[interp issafe]} { + package unknown {::tcl::tm::UnknownHandler ::tclPkgUnknown} +} else { + # Set up search for Tcl Modules (TIP #189). + # and setup platform specific unknown package handlers + if {$tcl_platform(os) eq "Darwin" + && $tcl_platform(platform) eq "unix"} { + package unknown {::tcl::tm::UnknownHandler \ + {::tcl::MacOSXPkgUnknown ::tclPkgUnknown}} + } else { + package unknown {::tcl::tm::UnknownHandler ::tclPkgUnknown} + } + + # Set up the 'clock' ensemble + + namespace eval ::tcl::clock [list variable TclLibDir $::tcl_library] + + proc ::tcl::initClock {} { + # Auto-loading stubs for 'clock.tcl' + + foreach cmd {add format scan} { + proc ::tcl::clock::$cmd args { + variable TclLibDir + source -encoding utf-8 [file join $TclLibDir clock.tcl] + return [uplevel 1 [info level 0]] + } + } + + rename ::tcl::initClock {} + } + ::tcl::initClock +} + +# Conditionalize for presence of exec. + +if {[namespace which -command exec] eq ""} { + + # Some machines do not have exec. Also, on all + # platforms, safe interpreters do not have exec. + + set auto_noexec 1 +} + +# Define a log command (which can be overwritten to log errors +# differently, specially when stderr is not available) + +if {[namespace which -command tclLog] eq ""} { + proc tclLog {string} { + catch {puts stderr $string} + } +} + +# unknown -- +# This procedure is called when a Tcl command is invoked that doesn't +# exist in the interpreter. It takes the following steps to make the +# command available: +# +# 1. See if the autoload facility can locate the command in a +# Tcl script file. If so, load it and execute it. +# 2. If the command was invoked interactively at top-level: +# (a) see if the command exists as an executable UNIX program. +# If so, "exec" the command. +# (b) see if the command requests csh-like history substitution +# in one of the common forms !!, !, or ^old^new. If +# so, emulate csh's history substitution. +# (c) see if the command is a unique abbreviation for another +# command. If so, invoke the command. +# +# Arguments: +# args - A list whose elements are the words of the original +# command, including the command name. + +proc unknown args { + variable ::tcl::UnknownPending + global auto_noexec auto_noload env tcl_interactive errorInfo errorCode + + if {[info exists errorInfo]} { + set savedErrorInfo $errorInfo + } + if {[info exists errorCode]} { + set savedErrorCode $errorCode + } + + set name [lindex $args 0] + if {![info exists auto_noload]} { + # + # Make sure we're not trying to load the same proc twice. + # + if {[info exists UnknownPending($name)]} { + return -code error "self-referential recursion\ + in \"unknown\" for command \"$name\"" + } + set UnknownPending($name) pending + set ret [catch { + auto_load $name [uplevel 1 {::namespace current}] + } msg opts] + unset UnknownPending($name) + if {$ret != 0} { + dict append opts -errorinfo "\n (autoloading \"$name\")" + return -options $opts $msg + } + if {![array size UnknownPending]} { + unset UnknownPending + } + if {$msg} { + if {[info exists savedErrorCode]} { + set ::errorCode $savedErrorCode + } else { + unset -nocomplain ::errorCode + } + if {[info exists savedErrorInfo]} { + set errorInfo $savedErrorInfo + } else { + unset -nocomplain errorInfo + } + set code [catch {uplevel 1 $args} msg opts] + if {$code == 1} { + # + # Compute stack trace contribution from the [uplevel]. + # Note the dependence on how Tcl_AddErrorInfo, etc. + # construct the stack trace. + # + set errInfo [dict get $opts -errorinfo] + set errCode [dict get $opts -errorcode] + set cinfo $args + if {[string bytelength $cinfo] > 150} { + set cinfo [string range $cinfo 0 150] + while {[string bytelength $cinfo] > 150} { + set cinfo [string range $cinfo 0 end-1] + } + append cinfo ... + } + set tail "\n (\"uplevel\" body line 1)\n invoked\ + from within\n\"uplevel 1 \$args\"" + set expect "$msg\n while executing\n\"$cinfo\"$tail" + if {$errInfo eq $expect} { + # + # The stack has only the eval from the expanded command + # Do not generate any stack trace here. + # + dict unset opts -errorinfo + dict incr opts -level + return -options $opts $msg + } + # + # Stack trace is nested, trim off just the contribution + # from the extra "eval" of $args due to the "catch" above. + # + set last [string last $tail $errInfo] + if {$last + [string length $tail] != [string length $errInfo]} { + # Very likely cannot happen + return -options $opts $msg + } + set errInfo [string range $errInfo 0 $last-1] + set tail "\"$cinfo\"" + set last [string last $tail $errInfo] + if {$last < 0 || $last + [string length $tail] != [string length $errInfo]} { + return -code error -errorcode $errCode \ + -errorinfo $errInfo $msg + } + set errInfo [string range $errInfo 0 $last-1] + set tail "\n invoked from within\n" + set last [string last $tail $errInfo] + if {$last + [string length $tail] == [string length $errInfo]} { + return -code error -errorcode $errCode \ + -errorinfo [string range $errInfo 0 $last-1] $msg + } + set tail "\n while executing\n" + set last [string last $tail $errInfo] + if {$last + [string length $tail] == [string length $errInfo]} { + return -code error -errorcode $errCode \ + -errorinfo [string range $errInfo 0 $last-1] $msg + } + return -options $opts $msg + } else { + dict incr opts -level + return -options $opts $msg + } + } + } + + if {([info level] == 1) && ([info script] eq "") + && [info exists tcl_interactive] && $tcl_interactive} { + if {![info exists auto_noexec]} { + set new [auto_execok $name] + if {$new ne ""} { + set redir "" + if {[namespace which -command console] eq ""} { + set redir ">&@stdout <@stdin" + } + uplevel 1 [list ::catch \ + [concat exec $redir $new [lrange $args 1 end]] \ + ::tcl::UnknownResult ::tcl::UnknownOptions] + dict incr ::tcl::UnknownOptions -level + return -options $::tcl::UnknownOptions $::tcl::UnknownResult + } + } + if {$name eq "!!"} { + set newcmd [history event] + } elseif {[regexp {^!(.+)$} $name -> event]} { + set newcmd [history event $event] + } elseif {[regexp {^\^([^^]*)\^([^^]*)\^?$} $name -> old new]} { + set newcmd [history event -1] + catch {regsub -all -- $old $newcmd $new newcmd} + } + if {[info exists newcmd]} { + tclLog $newcmd + history change $newcmd 0 + uplevel 1 [list ::catch $newcmd \ + ::tcl::UnknownResult ::tcl::UnknownOptions] + dict incr ::tcl::UnknownOptions -level + return -options $::tcl::UnknownOptions $::tcl::UnknownResult + } + + set ret [catch [list uplevel 1 [list info commands $name*]] candidates] + if {$name eq "::"} { + set name "" + } + if {$ret != 0} { + dict append opts -errorinfo \ + "\n (expanding command prefix \"$name\" in unknown)" + return -options $opts $candidates + } + # Filter out bogus matches when $name contained + # a glob-special char [Bug 946952] + if {$name eq ""} { + # Handle empty $name separately due to strangeness + # in [string first] (See RFE 1243354) + set cmds $candidates + } else { + set cmds [list] + foreach x $candidates { + if {[string first $name $x] == 0} { + lappend cmds $x + } + } + } + if {[llength $cmds] == 1} { + uplevel 1 [list ::catch [lreplace $args 0 0 [lindex $cmds 0]] \ + ::tcl::UnknownResult ::tcl::UnknownOptions] + dict incr ::tcl::UnknownOptions -level + return -options $::tcl::UnknownOptions $::tcl::UnknownResult + } + if {[llength $cmds]} { + return -code error "ambiguous command name \"$name\": [lsort $cmds]" + } + } + return -code error -errorcode [list TCL LOOKUP COMMAND $name] \ + "invalid command name \"$name\"" +} + +# auto_load -- +# Checks a collection of library directories to see if a procedure +# is defined in one of them. If so, it sources the appropriate +# library file to create the procedure. Returns 1 if it successfully +# loaded the procedure, 0 otherwise. +# +# Arguments: +# cmd - Name of the command to find and load. +# namespace (optional) The namespace where the command is being used - must be +# a canonical namespace as returned [namespace current] +# for instance. If not given, namespace current is used. + +proc auto_load {cmd {namespace {}}} { + global auto_index auto_path + + if {$namespace eq ""} { + set namespace [uplevel 1 [list ::namespace current]] + } + set nameList [auto_qualify $cmd $namespace] + # workaround non canonical auto_index entries that might be around + # from older auto_mkindex versions + lappend nameList $cmd + foreach name $nameList { + if {[info exists auto_index($name)]} { + namespace eval :: $auto_index($name) + # There's a couple of ways to look for a command of a given + # name. One is to use + # info commands $name + # Unfortunately, if the name has glob-magic chars in it like * + # or [], it may not match. For our purposes here, a better + # route is to use + # namespace which -command $name + if {[namespace which -command $name] ne ""} { + return 1 + } + } + } + if {![info exists auto_path]} { + return 0 + } + + if {![auto_load_index]} { + return 0 + } + foreach name $nameList { + if {[info exists auto_index($name)]} { + namespace eval :: $auto_index($name) + if {[namespace which -command $name] ne ""} { + return 1 + } + } + } + return 0 +} + +# auto_load_index -- +# Loads the contents of tclIndex files on the auto_path directory +# list. This is usually invoked within auto_load to load the index +# of available commands. Returns 1 if the index is loaded, and 0 if +# the index is already loaded and up to date. +# +# Arguments: +# None. + +proc auto_load_index {} { + variable ::tcl::auto_oldpath + global auto_index auto_path + + if {[info exists auto_oldpath] && ($auto_oldpath eq $auto_path)} { + return 0 + } + set auto_oldpath $auto_path + + # Check if we are a safe interpreter. In that case, we support only + # newer format tclIndex files. + + set issafe [interp issafe] + for {set i [expr {[llength $auto_path] - 1}]} {$i >= 0} {incr i -1} { + set dir [lindex $auto_path $i] + set f "" + if {$issafe} { + catch {source [file join $dir tclIndex]} + } elseif {[catch {set f [open [file join $dir tclIndex]]}]} { + continue + } else { + set error [catch { + fconfigure $f -eofchar "\x1A {}" + set id [gets $f] + if {$id eq "# Tcl autoload index file, version 2.0"} { + eval [read $f] + } elseif {$id eq "# Tcl autoload index file: each line identifies a Tcl"} { + while {[gets $f line] >= 0} { + if {([string index $line 0] eq "#") \ + || ([llength $line] != 2)} { + continue + } + set name [lindex $line 0] + set auto_index($name) \ + "source [file join $dir [lindex $line 1]]" + } + } else { + error "[file join $dir tclIndex] isn't a proper Tcl index file" + } + } msg opts] + if {$f ne ""} { + close $f + } + if {$error} { + return -options $opts $msg + } + } + } + return 1 +} + +# auto_qualify -- +# +# Compute a fully qualified names list for use in the auto_index array. +# For historical reasons, commands in the global namespace do not have leading +# :: in the index key. The list has two elements when the command name is +# relative (no leading ::) and the namespace is not the global one. Otherwise +# only one name is returned (and searched in the auto_index). +# +# Arguments - +# cmd The command name. Can be any name accepted for command +# invocations (Like "foo::::bar"). +# namespace The namespace where the command is being used - must be +# a canonical namespace as returned by [namespace current] +# for instance. + +proc auto_qualify {cmd namespace} { + + # count separators and clean them up + # (making sure that foo:::::bar will be treated as foo::bar) + set n [regsub -all {::+} $cmd :: cmd] + + # Ignore namespace if the name starts with :: + # Handle special case of only leading :: + + # Before each return case we give an example of which category it is + # with the following form : + # (inputCmd, inputNameSpace) -> output + + if {[string match ::* $cmd]} { + if {$n > 1} { + # (::foo::bar , *) -> ::foo::bar + return [list $cmd] + } else { + # (::global , *) -> global + return [list [string range $cmd 2 end]] + } + } + + # Potentially returning 2 elements to try : + # (if the current namespace is not the global one) + + if {$n == 0} { + if {$namespace eq "::"} { + # (nocolons , ::) -> nocolons + return [list $cmd] + } else { + # (nocolons , ::sub) -> ::sub::nocolons nocolons + return [list ${namespace}::$cmd $cmd] + } + } elseif {$namespace eq "::"} { + # (foo::bar , ::) -> ::foo::bar + return [list ::$cmd] + } else { + # (foo::bar , ::sub) -> ::sub::foo::bar ::foo::bar + return [list ${namespace}::$cmd ::$cmd] + } +} + +# auto_import -- +# +# Invoked during "namespace import" to make see if the imported commands +# reside in an autoloaded library. If so, the commands are loaded so +# that they will be available for the import links. If not, then this +# procedure does nothing. +# +# Arguments - +# pattern The pattern of commands being imported (like "foo::*") +# a canonical namespace as returned by [namespace current] + +proc auto_import {pattern} { + global auto_index + + # If no namespace is specified, this will be an error case + + if {![string match *::* $pattern]} { + return + } + + set ns [uplevel 1 [list ::namespace current]] + set patternList [auto_qualify $pattern $ns] + + auto_load_index + + foreach pattern $patternList { + foreach name [array names auto_index $pattern] { + if {([namespace which -command $name] eq "") + && ([namespace qualifiers $pattern] eq [namespace qualifiers $name])} { + namespace eval :: $auto_index($name) + } + } + } +} + +# auto_execok -- +# +# Returns string that indicates name of program to execute if +# name corresponds to a shell builtin or an executable in the +# Windows search path, or "" otherwise. Builds an associative +# array auto_execs that caches information about previous checks, +# for speed. +# +# Arguments: +# name - Name of a command. + +if {$tcl_platform(platform) eq "windows"} { +# Windows version. +# +# Note that file executable doesn't work under Windows, so we have to +# look for files with .exe, .com, or .bat extensions. Also, the path +# may be in the Path or PATH environment variables, and path +# components are separated with semicolons, not colons as under Unix. +# +proc auto_execok name { + global auto_execs env tcl_platform + + if {[info exists auto_execs($name)]} { + return $auto_execs($name) + } + set auto_execs($name) "" + + set shellBuiltins [list assoc cls copy date del dir echo erase exit ftype \ + md mkdir mklink move rd ren rename rmdir start time type ver vol] + if {[info exists env(PATHEXT)]} { + # Add an initial ; to have the {} extension check first. + set execExtensions [split ";$env(PATHEXT)" ";"] + } else { + set execExtensions [list {} .com .exe .bat .cmd] + } + + if {[string tolower $name] in $shellBuiltins} { + # When this is command.com for some reason on Win2K, Tcl won't + # exec it unless the case is right, which this corrects. COMSPEC + # may not point to a real file, so do the check. + set cmd $env(COMSPEC) + if {[file exists $cmd]} { + set cmd [file attributes $cmd -shortname] + } + return [set auto_execs($name) [list $cmd /c $name]] + } + + if {[llength [file split $name]] != 1} { + foreach ext $execExtensions { + set file ${name}${ext} + if {[file exists $file] && ![file isdirectory $file]} { + return [set auto_execs($name) [list $file]] + } + } + return "" + } + + set path "[file dirname [info nameof]];.;" + if {[info exists env(SystemRoot)]} { + set windir $env(SystemRoot) + } elseif {[info exists env(WINDIR)]} { + set windir $env(WINDIR) + } + if {[info exists windir]} { + if {$tcl_platform(os) eq "Windows NT"} { + append path "$windir/system32;" + } + append path "$windir/system;$windir;" + } + + foreach var {PATH Path path} { + if {[info exists env($var)]} { + append path ";$env($var)" + } + } + + foreach ext $execExtensions { + unset -nocomplain checked + foreach dir [split $path {;}] { + # Skip already checked directories + if {[info exists checked($dir)] || ($dir eq "")} { + continue + } + set checked($dir) {} + set file [file join $dir ${name}${ext}] + if {[file exists $file] && ![file isdirectory $file]} { + return [set auto_execs($name) [list $file]] + } + } + } + return "" +} + +} else { +# Unix version. +# +proc auto_execok name { + global auto_execs env + + if {[info exists auto_execs($name)]} { + return $auto_execs($name) + } + set auto_execs($name) "" + if {[llength [file split $name]] != 1} { + if {[file executable $name] && ![file isdirectory $name]} { + set auto_execs($name) [list $name] + } + return $auto_execs($name) + } + foreach dir [split $env(PATH) :] { + if {$dir eq ""} { + set dir . + } + set file [file join $dir $name] + if {[file executable $file] && ![file isdirectory $file]} { + set auto_execs($name) [list $file] + return $auto_execs($name) + } + } + return "" +} + +} + +# ::tcl::CopyDirectory -- +# +# This procedure is called by Tcl's core when attempts to call the +# filesystem's copydirectory function fail. The semantics of the call +# are that 'dest' does not yet exist, i.e. dest should become the exact +# image of src. If dest does exist, we throw an error. +# +# Note that making changes to this procedure can change the results +# of running Tcl's tests. +# +# Arguments: +# action - "renaming" or "copying" +# src - source directory +# dest - destination directory +proc tcl::CopyDirectory {action src dest} { + set nsrc [file normalize $src] + set ndest [file normalize $dest] + + if {$action eq "renaming"} { + # Can't rename volumes. We could give a more precise + # error message here, but that would break the test suite. + if {$nsrc in [file volumes]} { + return -code error "error $action \"$src\" to\ + \"$dest\": trying to rename a volume or move a directory\ + into itself" + } + } + if {[file exists $dest]} { + if {$nsrc eq $ndest} { + return -code error "error $action \"$src\" to\ + \"$dest\": trying to rename a volume or move a directory\ + into itself" + } + if {$action eq "copying"} { + # We used to throw an error here, but, looking more closely + # at the core copy code in tclFCmd.c, if the destination + # exists, then we should only call this function if -force + # is true, which means we just want to over-write. So, + # the following code is now commented out. + # + # return -code error "error $action \"$src\" to\ + # \"$dest\": file already exists" + } else { + # Depending on the platform, and on the current + # working directory, the directories '.', '..' + # can be returned in various combinations. Anyway, + # if any other file is returned, we must signal an error. + set existing [glob -nocomplain -directory $dest * .*] + lappend existing {*}[glob -nocomplain -directory $dest \ + -type hidden * .*] + foreach s $existing { + if {[file tail $s] ni {. ..}} { + return -code error "error $action \"$src\" to\ + \"$dest\": file already exists" + } + } + } + } else { + if {[string first $nsrc $ndest] >= 0} { + set srclen [expr {[llength [file split $nsrc]] - 1}] + set ndest [lindex [file split $ndest] $srclen] + if {$ndest eq [file tail $nsrc]} { + return -code error "error $action \"$src\" to\ + \"$dest\": trying to rename a volume or move a directory\ + into itself" + } + } + file mkdir $dest + } + # Have to be careful to capture both visible and hidden files. + # We will also be more generous to the file system and not + # assume the hidden and non-hidden lists are non-overlapping. + # + # On Unix 'hidden' files begin with '.'. On other platforms + # or filesystems hidden files may have other interpretations. + set filelist [concat [glob -nocomplain -directory $src *] \ + [glob -nocomplain -directory $src -types hidden *]] + + foreach s [lsort -unique $filelist] { + if {[file tail $s] ni {. ..}} { + file copy -force -- $s [file join $dest [file tail $s]] + } + } + return +} diff --git a/lib/tcl8.6/package.tcl b/lib/tcl8.6/package.tcl new file mode 100644 index 0000000000000000000000000000000000000000..4ccc20a50432c227ffe8e7c4b2e4a07f9218474f --- /dev/null +++ b/lib/tcl8.6/package.tcl @@ -0,0 +1,751 @@ +# package.tcl -- +# +# utility procs formerly in init.tcl which can be loaded on demand +# for package management. +# +# Copyright (c) 1991-1993 The Regents of the University of California. +# Copyright (c) 1994-1998 Sun Microsystems, Inc. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# + +namespace eval tcl::Pkg {} + +# ::tcl::Pkg::CompareExtension -- +# +# Used internally by pkg_mkIndex to compare the extension of a file to a given +# extension. On Windows, it uses a case-insensitive comparison because the +# file system can be file insensitive. +# +# Arguments: +# fileName name of a file whose extension is compared +# ext (optional) The extension to compare against; you must +# provide the starting dot. +# Defaults to [info sharedlibextension] +# +# Results: +# Returns 1 if the extension matches, 0 otherwise + +proc tcl::Pkg::CompareExtension {fileName {ext {}}} { + global tcl_platform + if {$ext eq ""} {set ext [info sharedlibextension]} + if {$tcl_platform(platform) eq "windows"} { + return [string equal -nocase [file extension $fileName] $ext] + } else { + # Some unices add trailing numbers after the .so, so + # we could have something like '.so.1.2'. + set root $fileName + while {1} { + set currExt [file extension $root] + if {$currExt eq $ext} { + return 1 + } + + # The current extension does not match; if it is not a numeric + # value, quit, as we are only looking to ignore version number + # extensions. Otherwise we might return 1 in this case: + # tcl::Pkg::CompareExtension foo.so.bar .so + # which should not match. + + if {![string is integer -strict [string range $currExt 1 end]]} { + return 0 + } + set root [file rootname $root] + } + } +} + +# pkg_mkIndex -- +# This procedure creates a package index in a given directory. The package +# index consists of a "pkgIndex.tcl" file whose contents are a Tcl script that +# sets up package information with "package require" commands. The commands +# describe all of the packages defined by the files given as arguments. +# +# Arguments: +# -direct (optional) If this flag is present, the generated +# code in pkgMkIndex.tcl will cause the package to be +# loaded when "package require" is executed, rather +# than lazily when the first reference to an exported +# procedure in the package is made. +# -verbose (optional) Verbose output; the name of each file that +# was successfully processed is printed out. Additionally, +# if processing of a file failed a message is printed. +# -load pat (optional) Preload any packages whose names match +# the pattern. Used to handle DLLs that depend on +# other packages during their Init procedure. +# dir - Name of the directory in which to create the index. +# args - Any number of additional arguments, each giving +# a glob pattern that matches the names of one or +# more shared libraries or Tcl script files in +# dir. + +proc pkg_mkIndex {args} { + set usage {"pkg_mkIndex ?-direct? ?-lazy? ?-load pattern? ?-verbose? ?--? dir ?pattern ...?"} + + set argCount [llength $args] + if {$argCount < 1} { + return -code error "wrong # args: should be\n$usage" + } + + set more "" + set direct 1 + set doVerbose 0 + set loadPat "" + for {set idx 0} {$idx < $argCount} {incr idx} { + set flag [lindex $args $idx] + switch -glob -- $flag { + -- { + # done with the flags + incr idx + break + } + -verbose { + set doVerbose 1 + } + -lazy { + set direct 0 + append more " -lazy" + } + -direct { + append more " -direct" + } + -load { + incr idx + set loadPat [lindex $args $idx] + append more " -load $loadPat" + } + -* { + return -code error "unknown flag $flag: should be\n$usage" + } + default { + # done with the flags + break + } + } + } + + set dir [lindex $args $idx] + set patternList [lrange $args [expr {$idx + 1}] end] + if {![llength $patternList]} { + set patternList [list "*.tcl" "*[info sharedlibextension]"] + } + + try { + set fileList [glob -directory $dir -tails -types {r f} -- \ + {*}$patternList] + } on error {msg opt} { + return -options $opt $msg + } + foreach file $fileList { + # For each file, figure out what commands and packages it provides. + # To do this, create a child interpreter, load the file into the + # interpreter, and get a list of the new commands and packages that + # are defined. + + if {$file eq "pkgIndex.tcl"} { + continue + } + + set c [interp create] + + # Load into the child any packages currently loaded in the parent + # interpreter that match the -load pattern. + + if {$loadPat ne ""} { + if {$doVerbose} { + tclLog "currently loaded packages: '[info loaded]'" + tclLog "trying to load all packages matching $loadPat" + } + if {![llength [info loaded]]} { + tclLog "warning: no packages are currently loaded, nothing" + tclLog "can possibly match '$loadPat'" + } + } + foreach pkg [info loaded] { + if {![string match -nocase $loadPat [lindex $pkg 1]]} { + continue + } + if {$doVerbose} { + tclLog "package [lindex $pkg 1] matches '$loadPat'" + } + try { + load [lindex $pkg 0] [lindex $pkg 1] $c + } on error err { + if {$doVerbose} { + tclLog "warning: load [lindex $pkg 0]\ + [lindex $pkg 1]\nfailed with: $err" + } + } on ok {} { + if {$doVerbose} { + tclLog "loaded [lindex $pkg 0] [lindex $pkg 1]" + } + } + if {[lindex $pkg 1] eq "Tk"} { + # Withdraw . if Tk was loaded, to avoid showing a window. + $c eval [list wm withdraw .] + } + } + + $c eval { + # Stub out the package command so packages can require other + # packages. + + rename package __package_orig + proc package {what args} { + switch -- $what { + require { + return; # Ignore transitive requires + } + default { + __package_orig $what {*}$args + } + } + } + proc tclPkgUnknown args {} + package unknown tclPkgUnknown + + # Stub out the unknown command so package can call into each other + # during their initialization. + + proc unknown {args} {} + + # Stub out the auto_import mechanism + + proc auto_import {args} {} + + # reserve the ::tcl namespace for support procs and temporary + # variables. This might make it awkward to generate a + # pkgIndex.tcl file for the ::tcl namespace. + + namespace eval ::tcl { + variable dir ;# Current directory being processed + variable file ;# Current file being processed + variable direct ;# -direct flag value + variable x ;# Loop variable + variable debug ;# For debugging + variable type ;# "load" or "source", for -direct + variable namespaces ;# Existing namespaces (e.g., ::tcl) + variable packages ;# Existing packages (e.g., Tcl) + variable origCmds ;# Existing commands + variable newCmds ;# Newly created commands + variable newPkgs {} ;# Newly created packages + } + } + + $c eval [list set ::tcl::dir $dir] + $c eval [list set ::tcl::file $file] + $c eval [list set ::tcl::direct $direct] + + # Download needed procedures into the child because we've just deleted + # the unknown procedure. This doesn't handle procedures with default + # arguments. + + foreach p {::tcl::Pkg::CompareExtension} { + $c eval [list namespace eval [namespace qualifiers $p] {}] + $c eval [list proc $p [info args $p] [info body $p]] + } + + try { + $c eval { + set ::tcl::debug "loading or sourcing" + + # we need to track command defined by each package even in the + # -direct case, because they are needed internally by the + # "partial pkgIndex.tcl" step above. + + proc ::tcl::GetAllNamespaces {{root ::}} { + set list $root + foreach ns [namespace children $root] { + lappend list {*}[::tcl::GetAllNamespaces $ns] + } + return $list + } + + # init the list of existing namespaces, packages, commands + + foreach ::tcl::x [::tcl::GetAllNamespaces] { + set ::tcl::namespaces($::tcl::x) 1 + } + foreach ::tcl::x [package names] { + if {[package provide $::tcl::x] ne ""} { + set ::tcl::packages($::tcl::x) 1 + } + } + set ::tcl::origCmds [info commands] + + # Try to load the file if it has the shared library extension, + # otherwise source it. It's important not to try to load + # files that aren't shared libraries, because on some systems + # (like SunOS) the loader will abort the whole application + # when it gets an error. + + if {[::tcl::Pkg::CompareExtension $::tcl::file [info sharedlibextension]]} { + # The "file join ." command below is necessary. Without + # it, if the file name has no \'s and we're on UNIX, the + # load command will invoke the LD_LIBRARY_PATH search + # mechanism, which could cause the wrong file to be used. + + set ::tcl::debug loading + load [file join $::tcl::dir $::tcl::file] + set ::tcl::type load + } else { + set ::tcl::debug sourcing + source [file join $::tcl::dir $::tcl::file] + set ::tcl::type source + } + + # As a performance optimization, if we are creating direct + # load packages, don't bother figuring out the set of commands + # created by the new packages. We only need that list for + # setting up the autoloading used in the non-direct case. + if {!$::tcl::direct} { + # See what new namespaces appeared, and import commands + # from them. Only exported commands go into the index. + + foreach ::tcl::x [::tcl::GetAllNamespaces] { + if {![info exists ::tcl::namespaces($::tcl::x)]} { + namespace import -force ${::tcl::x}::* + } + + # Figure out what commands appeared + + foreach ::tcl::x [info commands] { + set ::tcl::newCmds($::tcl::x) 1 + } + foreach ::tcl::x $::tcl::origCmds { + unset -nocomplain ::tcl::newCmds($::tcl::x) + } + foreach ::tcl::x [array names ::tcl::newCmds] { + # determine which namespace a command comes from + + set ::tcl::abs [namespace origin $::tcl::x] + + # special case so that global names have no + # leading ::, this is required by the unknown + # command + + set ::tcl::abs \ + [lindex [auto_qualify $::tcl::abs ::] 0] + + if {$::tcl::x ne $::tcl::abs} { + # Name changed during qualification + + set ::tcl::newCmds($::tcl::abs) 1 + unset ::tcl::newCmds($::tcl::x) + } + } + } + } + + # Look through the packages that appeared, and if there is a + # version provided, then record it + + foreach ::tcl::x [package names] { + if {[package provide $::tcl::x] ne "" + && ![info exists ::tcl::packages($::tcl::x)]} { + lappend ::tcl::newPkgs \ + [list $::tcl::x [package provide $::tcl::x]] + } + } + } + } on error msg { + set what [$c eval set ::tcl::debug] + if {$doVerbose} { + tclLog "warning: error while $what $file: $msg" + } + } on ok {} { + set what [$c eval set ::tcl::debug] + if {$doVerbose} { + tclLog "successful $what of $file" + } + set type [$c eval set ::tcl::type] + set cmds [lsort [$c eval array names ::tcl::newCmds]] + set pkgs [$c eval set ::tcl::newPkgs] + if {$doVerbose} { + if {!$direct} { + tclLog "commands provided were $cmds" + } + tclLog "packages provided were $pkgs" + } + if {[llength $pkgs] > 1} { + tclLog "warning: \"$file\" provides more than one package ($pkgs)" + } + foreach pkg $pkgs { + # cmds is empty/not used in the direct case + lappend files($pkg) [list $file $type $cmds] + } + + if {$doVerbose} { + tclLog "processed $file" + } + } + interp delete $c + } + + append index "# Tcl package index file, version 1.1\n" + append index "# This file is generated by the \"pkg_mkIndex$more\" command\n" + append index "# and sourced either when an application starts up or\n" + append index "# by a \"package unknown\" script. It invokes the\n" + append index "# \"package ifneeded\" command to set up package-related\n" + append index "# information so that packages will be loaded automatically\n" + append index "# in response to \"package require\" commands. When this\n" + append index "# script is sourced, the variable \$dir must contain the\n" + append index "# full path name of this file's directory.\n" + + foreach pkg [lsort [array names files]] { + set cmd {} + lassign $pkg name version + lappend cmd ::tcl::Pkg::Create -name $name -version $version + foreach spec [lsort -index 0 $files($pkg)] { + foreach {file type procs} $spec { + if {$direct} { + set procs {} + } + lappend cmd "-$type" [list $file $procs] + } + } + append index "\n[eval $cmd]" + } + + set f [open [file join $dir pkgIndex.tcl] w] + puts $f $index + close $f +} + +# tclPkgSetup -- +# This is a utility procedure use by pkgIndex.tcl files. It is invoked as +# part of a "package ifneeded" script. It calls "package provide" to indicate +# that a package is available, then sets entries in the auto_index array so +# that the package's files will be auto-loaded when the commands are used. +# +# Arguments: +# dir - Directory containing all the files for this package. +# pkg - Name of the package (no version number). +# version - Version number for the package, such as 2.1.3. +# files - List of files that constitute the package. Each +# element is a sub-list with three elements. The first +# is the name of a file relative to $dir, the second is +# "load" or "source", indicating whether the file is a +# loadable binary or a script to source, and the third +# is a list of commands defined by this file. + +proc tclPkgSetup {dir pkg version files} { + global auto_index + + package provide $pkg $version + foreach fileInfo $files { + set f [lindex $fileInfo 0] + set type [lindex $fileInfo 1] + foreach cmd [lindex $fileInfo 2] { + if {$type eq "load"} { + set auto_index($cmd) [list load [file join $dir $f] $pkg] + } else { + set auto_index($cmd) [list source [file join $dir $f]] + } + } + } +} + +# tclPkgUnknown -- +# This procedure provides the default for the "package unknown" function. It +# is invoked when a package that's needed can't be found. It scans the +# auto_path directories and their immediate children looking for pkgIndex.tcl +# files and sources any such files that are found to setup the package +# database. As it searches, it will recognize changes to the auto_path and +# scan any new directories. +# +# Arguments: +# name - Name of desired package. Not used. +# version - Version of desired package. Not used. +# exact - Either "-exact" or omitted. Not used. + +proc tclPkgUnknown {name args} { + global auto_path env + + if {![info exists auto_path]} { + return + } + # Cache the auto_path, because it may change while we run through the + # first set of pkgIndex.tcl files + set old_path [set use_path $auto_path] + while {[llength $use_path]} { + set dir [lindex $use_path end] + + # Make sure we only scan each directory one time. + if {[info exists tclSeenPath($dir)]} { + set use_path [lrange $use_path 0 end-1] + continue + } + set tclSeenPath($dir) 1 + + # Get the pkgIndex.tcl files in subdirectories of auto_path directories. + # - Safe Base interpreters have a restricted "glob" command that + # works in this case. + # - The "catch" was essential when there was no safe glob and every + # call in a safe interp failed; it is retained only for corner + # cases in which the eventual call to glob returns an error. + catch { + foreach file [glob -directory $dir -join -nocomplain \ + * pkgIndex.tcl] { + set dir [file dirname $file] + if {![info exists procdDirs($dir)]} { + try { + source $file + } trap {POSIX EACCES} {} { + # $file was not readable; silently ignore + continue + } on error msg { + tclLog "error reading package index file $file: $msg" + } on ok {} { + set procdDirs($dir) 1 + } + } + } + } + set dir [lindex $use_path end] + if {![info exists procdDirs($dir)]} { + set file [file join $dir pkgIndex.tcl] + # safe interps usually don't have "file exists", + if {([interp issafe] || [file exists $file])} { + try { + source $file + } trap {POSIX EACCES} {} { + # $file was not readable; silently ignore + continue + } on error msg { + tclLog "error reading package index file $file: $msg" + } on ok {} { + set procdDirs($dir) 1 + } + } + } + + set use_path [lrange $use_path 0 end-1] + + # Check whether any of the index scripts we [source]d above set a new + # value for $::auto_path. If so, then find any new directories on the + # $::auto_path, and lappend them to the $use_path we are working from. + # This gives index scripts the (arguably unwise) power to expand the + # index script search path while the search is in progress. + set index 0 + if {[llength $old_path] == [llength $auto_path]} { + foreach dir $auto_path old $old_path { + if {$dir ne $old} { + # This entry in $::auto_path has changed. + break + } + incr index + } + } + + # $index now points to the first element of $auto_path that has + # changed, or the beginning if $auto_path has changed length Scan the + # new elements of $auto_path for directories to add to $use_path. + # Don't add directories we've already seen, or ones already on the + # $use_path. + foreach dir [lrange $auto_path $index end] { + if {![info exists tclSeenPath($dir)] && ($dir ni $use_path)} { + lappend use_path $dir + } + } + set old_path $auto_path + } +} + +# tcl::MacOSXPkgUnknown -- +# This procedure extends the "package unknown" function for MacOSX. It scans +# the Resources/Scripts directories of the immediate children of the auto_path +# directories for pkgIndex files. +# +# Arguments: +# original - original [package unknown] procedure +# name - Name of desired package. Not used. +# version - Version of desired package. Not used. +# exact - Either "-exact" or omitted. Not used. + +proc tcl::MacOSXPkgUnknown {original name args} { + # First do the cross-platform default search + uplevel 1 $original [linsert $args 0 $name] + + # Now do MacOSX specific searching + global auto_path + + if {![info exists auto_path]} { + return + } + # Cache the auto_path, because it may change while we run through the + # first set of pkgIndex.tcl files + set old_path [set use_path $auto_path] + while {[llength $use_path]} { + set dir [lindex $use_path end] + + # Make sure we only scan each directory one time. + if {[info exists tclSeenPath($dir)]} { + set use_path [lrange $use_path 0 end-1] + continue + } + set tclSeenPath($dir) 1 + + # get the pkgIndex files out of the subdirectories + # Safe interpreters do not use tcl::MacOSXPkgUnknown - see init.tcl. + foreach file [glob -directory $dir -join -nocomplain \ + * Resources Scripts pkgIndex.tcl] { + set dir [file dirname $file] + if {![info exists procdDirs($dir)]} { + try { + source $file + } trap {POSIX EACCES} {} { + # $file was not readable; silently ignore + continue + } on error msg { + tclLog "error reading package index file $file: $msg" + } on ok {} { + set procdDirs($dir) 1 + } + } + } + set use_path [lrange $use_path 0 end-1] + + # Check whether any of the index scripts we [source]d above set a new + # value for $::auto_path. If so, then find any new directories on the + # $::auto_path, and lappend them to the $use_path we are working from. + # This gives index scripts the (arguably unwise) power to expand the + # index script search path while the search is in progress. + set index 0 + if {[llength $old_path] == [llength $auto_path]} { + foreach dir $auto_path old $old_path { + if {$dir ne $old} { + # This entry in $::auto_path has changed. + break + } + incr index + } + } + + # $index now points to the first element of $auto_path that has + # changed, or the beginning if $auto_path has changed length Scan the + # new elements of $auto_path for directories to add to $use_path. + # Don't add directories we've already seen, or ones already on the + # $use_path. + foreach dir [lrange $auto_path $index end] { + if {![info exists tclSeenPath($dir)] && ($dir ni $use_path)} { + lappend use_path $dir + } + } + set old_path $auto_path + } +} + +# ::tcl::Pkg::Create -- +# +# Given a package specification generate a "package ifneeded" statement +# for the package, suitable for inclusion in a pkgIndex.tcl file. +# +# Arguments: +# args arguments used by the Create function: +# -name packageName +# -version packageVersion +# -load {filename ?{procs}?} +# ... +# -source {filename ?{procs}?} +# ... +# +# Any number of -load and -source parameters may be +# specified, so long as there is at least one -load or +# -source parameter. If the procs component of a module +# specifier is left off, that module will be set up for +# direct loading; otherwise, it will be set up for lazy +# loading. If both -source and -load are specified, the +# -load'ed files will be loaded first, followed by the +# -source'd files. +# +# Results: +# An appropriate "package ifneeded" statement for the package. + +proc ::tcl::Pkg::Create {args} { + append err(usage) "[lindex [info level 0] 0] " + append err(usage) "-name packageName -version packageVersion" + append err(usage) "?-load {filename ?{procs}?}? ... " + append err(usage) "?-source {filename ?{procs}?}? ..." + + set err(wrongNumArgs) "wrong # args: should be \"$err(usage)\"" + set err(valueMissing) "value for \"%s\" missing: should be \"$err(usage)\"" + set err(unknownOpt) "unknown option \"%s\": should be \"$err(usage)\"" + set err(noLoadOrSource) "at least one of -load and -source must be given" + + # process arguments + set len [llength $args] + if {$len < 6} { + error $err(wrongNumArgs) + } + + # Initialize parameters + array set opts {-name {} -version {} -source {} -load {}} + + # process parameters + for {set i 0} {$i < $len} {incr i} { + set flag [lindex $args $i] + incr i + switch -glob -- $flag { + "-name" - + "-version" { + if {$i >= $len} { + error [format $err(valueMissing) $flag] + } + set opts($flag) [lindex $args $i] + } + "-source" - + "-load" { + if {$i >= $len} { + error [format $err(valueMissing) $flag] + } + lappend opts($flag) [lindex $args $i] + } + default { + error [format $err(unknownOpt) [lindex $args $i]] + } + } + } + + # Validate the parameters + if {![llength $opts(-name)]} { + error [format $err(valueMissing) "-name"] + } + if {![llength $opts(-version)]} { + error [format $err(valueMissing) "-version"] + } + + if {!([llength $opts(-source)] || [llength $opts(-load)])} { + error $err(noLoadOrSource) + } + + # OK, now everything is good. Generate the package ifneeded statement. + set cmdline "package ifneeded $opts(-name) $opts(-version) " + + set cmdList {} + set lazyFileList {} + + # Handle -load and -source specs + foreach key {load source} { + foreach filespec $opts(-$key) { + lassign $filespec filename proclist + + if { [llength $proclist] == 0 } { + set cmd "\[list $key \[file join \$dir [list $filename]\]\]" + lappend cmdList $cmd + } else { + lappend lazyFileList [list $filename $key $proclist] + } + } + } + + if {[llength $lazyFileList]} { + lappend cmdList "\[list tclPkgSetup \$dir $opts(-name)\ + $opts(-version) [list $lazyFileList]\]" + } + append cmdline [join $cmdList "\\n"] + return $cmdline +} + +interp alias {} ::pkg::create {} ::tcl::Pkg::Create diff --git a/lib/tcl8.6/parray.tcl b/lib/tcl8.6/parray.tcl new file mode 100644 index 0000000000000000000000000000000000000000..a9c2cb15a9bd81339f2d91535b3758bb4785023e --- /dev/null +++ b/lib/tcl8.6/parray.tcl @@ -0,0 +1,28 @@ +# parray: +# Print the contents of a global array on stdout. +# +# Copyright (c) 1991-1993 The Regents of the University of California. +# Copyright (c) 1994 Sun Microsystems, Inc. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# + +proc parray {a {pattern *}} { + upvar 1 $a array + if {![array exists array]} { + return -code error "\"$a\" isn't an array" + } + set maxl 0 + set names [lsort [array names array $pattern]] + foreach name $names { + if {[string length $name] > $maxl} { + set maxl [string length $name] + } + } + set maxl [expr {$maxl + [string length $a] + 2}] + foreach name $names { + set nameString [format %s(%s) $a $name] + puts stdout [format "%-*s = %s" $maxl $nameString $array($name)] + } +} diff --git a/lib/tcl8.6/safe.tcl b/lib/tcl8.6/safe.tcl new file mode 100644 index 0000000000000000000000000000000000000000..8c79abdccbc23e372f1bf81d407fe1c0a64033f1 --- /dev/null +++ b/lib/tcl8.6/safe.tcl @@ -0,0 +1,1289 @@ +# 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 +# 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. +# +# See the file "license.terms" for information on usage and redistribution of +# this file, and for a DISCLAIMER OF ALL WARRANTIES. + +# +# The implementation is based on namespaces. These naming conventions are +# followed: +# Private procs starts with uppercase. +# Public procs are exported and starts with lowercase +# + +# Needed utilities package +package require opt 0.4.8 + +# Create the safe namespace +namespace eval ::safe { + # Exported API: + namespace export interpCreate interpInit interpConfigure interpDelete \ + interpAddToAccessPath interpFindInAccessPath setLogCmd +} + +# Helper function to resolve the dual way of specifying staticsok (either +# by -noStatics or -statics 0) +proc ::safe::InterpStatics {} { + foreach v {Args statics noStatics} { + upvar $v $v + } + set flag [::tcl::OptProcArgGiven -noStatics] + if {$flag && (!$noStatics == !$statics) + && ([::tcl::OptProcArgGiven -statics])} { + return -code error\ + "conflicting values given for -statics and -noStatics" + } + if {$flag} { + return [expr {!$noStatics}] + } else { + return $statics + } +} + +# Helper function to resolve the dual way of specifying nested loading +# (either by -nestedLoadOk or -nested 1) +proc ::safe::InterpNested {} { + foreach v {Args nested nestedLoadOk} { + upvar $v $v + } + set flag [::tcl::OptProcArgGiven -nestedLoadOk] + # note that the test here is the opposite of the "InterpStatics" one + # (it is not -noNested... because of the wanted default value) + if {$flag && (!$nestedLoadOk != !$nested) + && ([::tcl::OptProcArgGiven -nested])} { + return -code error\ + "conflicting values given for -nested and -nestedLoadOk" + } + if {$flag} { + # another difference with "InterpStatics" + return $nestedLoadOk + } else { + return $nested + } +} + +#### +# +# API entry points that needs argument parsing : +# +#### + +# Interface/entry point function and front end for "Create" +proc ::safe::interpCreate {args} { + set Args [::tcl::OptKeyParse ::safe::interpCreate $args] + RejectExcessColons $slave + InterpCreate $slave $accessPath \ + [InterpStatics] [InterpNested] $deleteHook +} + +proc ::safe::interpInit {args} { + set Args [::tcl::OptKeyParse ::safe::interpIC $args] + if {![::interp exists $slave]} { + return -code error "\"$slave\" is not an interpreter" + } + RejectExcessColons $slave + InterpInit $slave $accessPath \ + [InterpStatics] [InterpNested] $deleteHook +} + +# Check that the given child is "one of us" +proc ::safe::CheckInterp {child} { + namespace upvar ::safe [VarName $child] state + if {![info exists state] || ![::interp exists $child]} { + return -code error \ + "\"$child\" is not an interpreter managed by ::safe::" + } +} + +# Interface/entry point function and front end for "Configure". This code +# is awfully pedestrian because it would need more coupling and support +# between the way we store the configuration values in safe::interp's and +# the Opt package. Obviously we would like an OptConfigure to avoid +# duplicating all this code everywhere. +# -> TODO (the app should share or access easily the program/value stored +# by opt) + +# This is even more complicated by the boolean flags with no values that +# we had the bad idea to support for the sake of user simplicity in +# create/init but which makes life hard in configure... +# So this will be hopefully written and some integrated with opt1.0 +# (hopefully for tcl8.1 ?) +proc ::safe::interpConfigure {args} { + switch [llength $args] { + 1 { + # If we have exactly 1 argument the semantic is to return all + # the current configuration. We still call OptKeyParse though + # we know that "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 + + return [join [list \ + [list -accessPath $state(access_path)] \ + [list -statics $state(staticsok)] \ + [list -nested $state(nestedok)] \ + [list -deleteHook $state(cleanupHook)]]] + } + 2 { + # If we have exactly 2 arguments the semantic is a "configure + # get" + lassign $args slave arg + + # get the flag sub program (we 'know' about Opt's internal + # representation of data) + set desc [lindex [::tcl::OptKeyGetDesc ::safe::interpIC] 2] + set hits [::tcl::OptHits desc $arg] + if {$hits > 1} { + return -code error [::tcl::OptAmbigous $desc $arg] + } elseif {$hits == 0} { + return -code error [::tcl::OptFlagUsage $desc $arg] + } + CheckInterp $slave + namespace upvar ::safe [VarName $slave] state + + set item [::tcl::OptCurDesc $desc] + set name [::tcl::OptName $item] + switch -exact -- $name { + -accessPath { + return [list -accessPath $state(access_path)] + } + -statics { + return [list -statics $state(staticsok)] + } + -nested { + return [list -nested $state(nestedok)] + } + -deleteHook { + return [list -deleteHook $state(cleanupHook)] + } + -noStatics { + # it is most probably a set in fact but we would need + # then to jump to the set part and it is not *sure* + # that it is a set action that the user want, so force + # it to use the unambiguous -statics ?value? instead: + return -code error\ + "ambigous query (get or set -noStatics ?)\ + use -statics instead" + } + -nestedLoadOk { + return -code error\ + "ambigous query (get or set -nestedLoadOk ?)\ + use -nested instead" + } + default { + return -code error "unknown flag $name (bug)" + } + } + } + default { + # Otherwise we want to parse the arguments like init and + # create did + set Args [::tcl::OptKeyParse ::safe::interpIC $args] + CheckInterp $slave + namespace upvar ::safe [VarName $slave] state + + # Get the current (and not the default) values of whatever has + # not been given: + if {![::tcl::OptProcArgGiven -accessPath]} { + set doreset 0 + set accessPath $state(access_path) + } else { + set doreset 1 + } + if { + ![::tcl::OptProcArgGiven -statics] + && ![::tcl::OptProcArgGiven -noStatics] + } then { + set statics $state(staticsok) + } else { + set statics [InterpStatics] + } + if { + [::tcl::OptProcArgGiven -nested] || + [::tcl::OptProcArgGiven -nestedLoadOk] + } then { + set nested [InterpNested] + } else { + set nested $state(nestedok) + } + if {![::tcl::OptProcArgGiven -deleteHook]} { + set deleteHook $state(cleanupHook) + } + # we can now reconfigure : + InterpSetConfig $slave $accessPath $statics $nested $deleteHook + # auto_reset the child (to completely synch the new access_path) + if {$doreset} { + if {[catch {::interp eval $slave {auto_reset}} msg]} { + Log $slave "auto_reset failed: $msg" + } else { + Log $slave "successful auto_reset" NOTICE + } + + # 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)]] + } + + # Remove stale "package ifneeded" data for non-loaded packages. + # - Not for loaded packages, because "package forget" erases + # 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] + } + } + } + return + } + } +} + +#### +# +# Functions that actually implements the exported APIs +# +#### + +# +# safe::InterpCreate : doing the real job +# +# This procedure creates a safe interpreter and initializes it with the safe +# base aliases. +# NB: child name must be simple alphanumeric string, no spaces, no (), no +# {},... {because the state array is stored as part of the name} +# +# Returns the child name. +# +# 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. +# + staticsok : flag, if 0 :no static package can be loaded (load {} Xxx) +# if 1 :static packages are ok. +# + nestedok: flag, if 0 :no loading to sub-sub interps (load xx xx sub) +# if 1 : multiple levels are ok. + +# use the full name and no indent so auto_mkIndex can find us +proc ::safe::InterpCreate { + child + access_path + staticsok + nestedok + deletehook + } { + # Create the child. + # If evaluated in ::safe, the interpreter command for foo is ::foo; + # but for foo::bar is safe::foo::bar. So evaluate in :: instead. + if {$child ne ""} { + namespace eval :: [list ::interp create -safe $child] + } else { + # empty argument: generate child name + set child [::interp create -safe] + } + Log $child "Created" NOTICE + + # Initialize it. (returns child name) + InterpInit $child $access_path $staticsok $nestedok $deletehook +} + +# +# InterpSetConfig (was setAccessPath) : +# Sets up child virtual auto_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. + +proc ::safe::InterpSetConfig {child access_path staticsok nestedok deletehook} { + global auto_path + + # determine and store the access path if empty + if {$access_path eq ""} { + set access_path $auto_path + + # Make sure that tcl_library is in auto_path and at the first + # position (needed by setAccessPath) + set where [lsearch -exact $access_path [info library]] + if {$where < 0} { + # 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 + } 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 + } + + # 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] + } + + Log $child "Setting accessPath=($access_path) staticsok=$staticsok\ + nestedok=$nestedok deletehook=($deletehook)" NOTICE + + namespace upvar ::safe [VarName $child] state + + # clear old autopath if it existed + # build new one + # Extend the access list with the paths used to look for Tcl Modules. + # We save the virtual form separately as well, as syncing it with the + # child has to be deferred until the necessary commands are present for + # setup. + + set norm_access_path {} + set slave_access_path {} + set map_access_path {} + set remap_access_path {} + set slave_tm_path {} + + set i 0 + foreach dir $access_path { + set token [PathToken $i] + lappend slave_access_path $token + lappend map_access_path $token $dir + lappend remap_access_path $dir $token + lappend norm_access_path [file normalize $dir] + incr i + } + + set morepaths [::tcl::tm::list] + set firstpass 1 + while {[llength $morepaths]} { + set addpaths $morepaths + set morepaths {} + + foreach dir $addpaths { + # Prevent the addition of dirs on the tm list to the + # result if they are already known. + if {[dict exists $remap_access_path $dir]} { + if {$firstpass} { + # $dir is in [::tcl::tm::list] and belongs in the slave_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] + } + continue + } + + set token [PathToken $i] + lappend access_path $dir + lappend slave_access_path $token + lappend map_access_path $token $dir + lappend remap_access_path $dir $token + lappend norm_access_path [file normalize $dir] + if {$firstpass} { + # $dir is in [::tcl::tm::list] and belongs in the slave_tm_path. + # Later passes handle subdirectories, which belong in the + # access path but not in the module path. + lappend slave_tm_path $token + } + incr i + + # [Bug 2854929] + # Recursively find deeper paths which may contain + # modules. Required to handle modules with names like + # 'platform::shell', which translate into + # 'platform/shell-X.tm', i.e arbitrarily deep + # subdirectories. + lappend morepaths {*}[glob -nocomplain -directory $dir -type d *] + } + set firstpass 0 + } + + set state(access_path) $access_path + set state(access_path,map) $map_access_path + set state(access_path,remap) $remap_access_path + set state(access_path,norm) $norm_access_path + set state(access_path,slave) $slave_access_path + set state(tm_path_slave) $slave_tm_path + set state(staticsok) $staticsok + set state(nestedok) $nestedok + set state(cleanupHook) $deletehook + + SyncAccessPath $child + return +} + +# +# +# FindInAccessPath: +# Search for a real directory and returns its virtual Id (including the +# "$") +proc ::safe::interpFindInAccessPath {child path} { + CheckInterp $child + namespace upvar ::safe [VarName $child] state + + if {![dict exists $state(access_path,remap) $path]} { + return -code error "$path not found in access path" + } + + return [dict get $state(access_path,remap) $path] +} + +# +# addToAccessPath: +# add (if needed) a real directory to access path and return its +# virtual token (including the "$"). +proc ::safe::interpAddToAccessPath {child path} { + # first check if the directory is already in there + # (inlined interpFindInAccessPath). + CheckInterp $child + namespace upvar ::safe [VarName $child] state + + if {[dict exists $state(access_path,remap) $path]} { + return [dict get $state(access_path,remap) $path] + } + + # new one, add it: + set token [PathToken [llength $state(access_path)]] + + lappend state(access_path) $path + lappend state(access_path,slave) $token + lappend state(access_path,map) $token $path + lappend state(access_path,remap) $path $token + lappend state(access_path,norm) [file normalize $path] + + SyncAccessPath $child + return $token +} + +# This procedure applies the initializations to an already existing +# interpreter. It is useful when you want to install the safe base aliases +# into a preexisting safe interpreter. +proc ::safe::InterpInit { + child + access_path + staticsok + nestedok + deletehook + } { + # Configure will generate an access_path when access_path is empty. + InterpSetConfig $child $access_path $staticsok $nestedok $deletehook + + # NB we need to add [namespace current], aliases are always absolute + # paths. + + # These aliases let the child load files to define new commands + # This alias lets the child use the encoding names, convertfrom, + # convertto, and system, but not "encoding system " to set the + # system encoding. + # Handling Tcl Modules, we need a restricted form of Glob. + # This alias interposes on the 'exit' command and cleanly terminates + # the child. + + foreach {command alias} { + source AliasSource + load AliasLoad + encoding AliasEncoding + exit interpDelete + glob AliasGlob + } { + ::interp alias $child $command {} [namespace current]::$alias $child + } + + # 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 + } + + # Subcommands of info + foreach {subcommand alias} { + nameofexecutable AliasExeName + } { + ::interp alias $child ::tcl::info::$subcommand \ + {} [namespace current]::$alias $child + } + + # The allowed child variables already have been set by Tcl_MakeSafe(3) + + # Source init.tcl and tm.tcl into the child, to get auto_load and + # other procedures defined: + + if {[catch {::interp eval $child { + source [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)" + } + + if {[catch {::interp eval $child { + source [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)" + } + + # 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} { + ::interp eval $child [list \ + ::tcl::tm::add {*}[lreverse $state(tm_path_slave)]] + } + return $child +} + +# Add (only if needed, avoid duplicates) 1 level of sub directories to an +# existing path list. Also removes non directories from the returned +# list. +proc ::safe::AddSubDirs {pathList} { + set res {} + foreach dir $pathList { + if {[file isdirectory $dir]} { + # check that we don't have it yet as a children of a previous + # dir + if {$dir ni $res} { + lappend res $dir + } + foreach sub [glob -directory $dir -nocomplain *] { + if {[file isdirectory $sub] && ($sub ni $res)} { + # new sub dir, add it ! + lappend res $sub + } + } + } + } + return $res +} + +# This procedure deletes a safe interpreter managed by Safe Tcl and cleans up +# associated state. +# - The command will also delete non-Safe-Base interpreters. +# - This is regrettable, but to avoid breaking existing code this should be +# amended at the next major revision by uncommenting "CheckInterp". + +proc ::safe::interpDelete {child} { + Log $child "About to delete" NOTICE + + # CheckInterp $child + namespace upvar ::safe [VarName $child] state + + # When an interpreter is deleted with [interp delete], any sub-interpreters + # are deleted automatically, but this leaves behind their data in the Safe + # Base. To clean up properly, we call safe::interpDelete recursively on each + # Safe Base sub-interpreter, so each one is deleted cleanly and not by + # the automatic mechanism built into [interp delete]. + foreach sub [interp children $child] { + if {[info exists ::safe::[VarName [list $child $sub]]]} { + ::safe::interpDelete [list $child $sub] + } + } + + # If the child has a cleanup hook registered, call it. Check the + # existence because we might be called to delete an interp which has + # not been registered with us at all + + if {[info exists state(cleanupHook)]} { + set hook $state(cleanupHook) + if {[llength $hook]} { + # remove the hook now, otherwise if the hook calls us somehow, + # we'll loop + unset state(cleanupHook) + try { + {*}$hook $child + } on error err { + Log $child "Delete hook error ($err)" + } + } + } + + # Discard the global array of state associated with the child, and + # delete the interpreter. + + if {[info exists state]} { + unset state + } + + # if we have been called twice, the interp might have been deleted + # already + if {[::interp exists $child]} { + ::interp delete $child + Log $child "Deleted" NOTICE + } + + return +} + +# Set (or get) the logging mechanism + +proc ::safe::setLogCmd {args} { + variable Log + set la [llength $args] + if {$la == 0} { + return $Log + } elseif {$la == 1} { + set Log [lindex $args 0] + } else { + set Log $args + } + + if {$Log eq ""} { + # Disable logging completely. Calls to it will be compiled out + # of all users. + proc ::safe::Log {args} {} + } else { + # Activate logging, define proper command. + + proc ::safe::Log {child msg {type ERROR}} { + variable Log + {*}$Log "$type for slave $child : $msg" + return + } + } +} + +# ------------------- 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. +# +proc ::safe::SyncAccessPath {child} { + namespace upvar ::safe [VarName $child] state + + set slave_access_path $state(access_path,slave) + ::interp eval $child [list set auto_path $slave_access_path] + + Log $child "auto_path in $child has been set to $slave_access_path"\ + NOTICE + + # This code assumes that info library is the first element in the + # list of auto_path's. See -> InterpSetConfig for the code which + # ensures this condition. + + ::interp eval $child [list \ + set tcl_library [lindex $slave_access_path 0]] +} + +# Returns the virtual token for directory number N. +proc ::safe::PathToken {n} { + # We need to have a ":" in the token string so [file join] on the + # mac won't turn it into a relative path. + return "\$p(:$n:)" ;# Form tested by case 7.2 +} + +# +# translate virtual path into real path +# +proc ::safe::TranslatePath {child path} { + namespace upvar ::safe [VarName $child] state + + # somehow strip the namespaces 'functionality' out (the danger is that + # we would strip valid macintosh "../" queries... : + if {[string match "*::*" $path] || [string match "*..*" $path]} { + return -code error "invalid characters in path $path" + } + + # Use a cached map instead of computed local vars and subst. + + return [string map $state(access_path,map) $path] +} + +# file name control (limit access to files/resources that should be a +# valid tcl source file) +proc ::safe::CheckFileName {child file} { + # This used to limit what can be sourced to ".tcl" and forbid files + # with more than 1 dot and longer than 14 chars, but I changed that + # for 8.4 as a safe interp has enough internal protection already to + # allow sourcing anything. - hobbs + + if {![file exists $file]} { + # don't tell the file path + return -code error "no such file or directory" + } + + if {![file readable $file]} { + # don't tell the file path + return -code error "not readable" + } +} + +# AliasFileSubcommand handles selected subcommands of [file] in safe +# interpreters that are *almost* safe. In particular, it just acts to +# prevent discovery of what home directories exist. + +proc ::safe::AliasFileSubcommand {child subcommand name} { + if {[string match ~* $name]} { + set name ./$name + } + tailcall ::interp invokehidden $child tcl:file:$subcommand $name +} + +# AliasGlob is the target of the "glob" alias in safe interpreters. + +proc ::safe::AliasGlob {child args} { + Log $child "GLOB ! $args" NOTICE + set cmd {} + set at 0 + array set got { + -directory 0 + -nocomplain 0 + -join 0 + -tails 0 + -- 0 + } + + if {$::tcl_platform(platform) eq "windows"} { + set dirPartRE {^(.*)[\\/]([^\\/]*)$} + } else { + set dirPartRE {^(.*)/([^/]*)$} + } + + set dir {} + set virtualdir {} + + while {$at < [llength $args]} { + switch -glob -- [set opt [lindex $args $at]] { + -nocomplain - -- - -tails { + lappend cmd $opt + set got($opt) 1 + incr at + } + -join { + set got($opt) 1 + incr at + } + -types - -type { + lappend cmd -types [lindex $args [incr at]] + incr at + } + -directory { + if {$got($opt)} { + return -code error \ + {"-directory" cannot be used with "-path"} + } + set got($opt) 1 + set virtualdir [lindex $args [incr at]] + incr at + } + -* { + Log $child "Safe base rejecting glob option '$opt'" + return -code error "Safe base rejecting glob option '$opt'" + } + default { + break + } + } + if {$got(--)} break + } + + # Get the real path from the virtual one and check that the path is in the + # access path of that child. Done after basic argument processing so that + # we know if -nocomplain is set. + if {$got(-directory)} { + try { + set dir [TranslatePath $child $virtualdir] + DirInAccessPath $child $dir + } on error msg { + Log $child $msg + if {$got(-nocomplain)} return + return -code error "permission denied" + } + if {$got(--)} { + set cmd [linsert $cmd end-1 -directory $dir] + } else { + lappend cmd -directory $dir + } + } else { + # The code after this "if ... else" block would conspire to return with + # no results in this case, if it were allowed to proceed. Instead, + # return now and reduce the number of cases to be considered later. + Log $child {option -directory must be supplied} + if {$got(-nocomplain)} return + return -code error "permission denied" + } + + # Apply the -join semantics ourselves. + if {$got(-join)} { + set args [lreplace $args $at end [join [lrange $args $at end] "/"]] + } + + # Process the pattern arguments. If we've done a join there is only one + # pattern argument. + + set firstPattern [llength $cmd] + foreach opt [lrange $args $at end] { + if {![regexp $dirPartRE $opt -> thedir thefile]} { + set thedir . + # The *.tm search comes here. + } + # "Special" treatment for (joined) argument {*/pkgIndex.tcl}. + # Do the expansion of "*" here, and filter out any directories that are + # not in the access path. The outcome is to lappend to cmd a path of + # the form $virtualdir/subdir/pkgIndex.tcl for each subdirectory subdir, + # after removing any subdir that are not in the access path. + if {($thedir eq "*") && ($thefile eq "pkgIndex.tcl")} { + set mapped 0 + foreach d [glob -directory [TranslatePath $child $virtualdir] \ + -types d -tails *] { + catch { + DirInAccessPath $child \ + [TranslatePath $child [file join $virtualdir $d]] + lappend cmd [file join $d $thefile] + set mapped 1 + } + } + if {$mapped} continue + # Don't [continue] if */pkgIndex.tcl has no matches in the access + # path. The pattern will now receive the same treatment as a + # "non-special" pattern (and will fail because it includes a "*" in + # the directory name). + } + # Any directory pattern that is not an exact (i.e. non-glob) match to a + # directory in the access path will be rejected here. + # - Rejections include any directory pattern that has glob matching + # patterns "*", "?", backslashes, braces or square brackets, (UNLESS + # it corresponds to a genuine directory name AND that directory is in + # the access path). + # - The only "special matching characters" that remain in patterns for + # processing by glob are in the filename tail. + # - [file join $anything ~${foo}] is ~${foo}, which is not an exact + # match to any directory in the access path. Hence directory patterns + # that begin with "~" are rejected here. Tests safe-16.[5-8] check + # that "file join" remains as required and does not expand ~${foo}. + # - Bug [3529949] relates to unwanted expansion of ~${foo} and this is + # how the present code avoids the bug. All tests safe-16.* relate. + try { + DirInAccessPath $child [TranslatePath $child \ + [file join $virtualdir $thedir]] + } on error msg { + Log $child $msg + if {$got(-nocomplain)} continue + return -code error "permission denied" + } + lappend cmd $opt + } + + Log $child "GLOB = $cmd" NOTICE + + if {$got(-nocomplain) && [llength $cmd] eq $firstPattern} { + return + } + try { + # >>>>>>>>>> HERE'S THE CALL TO SAFE INTERP GLOB <<<<<<<<<< + # - Pattern arguments added to cmd have NOT been translated from tokens. + # Only the virtualdir is translated (to dir). + # - In the pkgIndex.tcl case, there is no "*" in the pattern arguments, + # which are a list of names each with tail pkgIndex.tcl. The purpose + # of the call to glob is to remove the names for which the file does + # not exist. + set entries [::interp invokehidden $child glob {*}$cmd] + } on error msg { + # This is the only place that a call with -nocomplain and no invalid + # "dash-options" can return an error. + Log $child $msg + return -code error "script error" + } + + Log $child "GLOB < $entries" NOTICE + + # Translate path back to what the child should see. + set res {} + set l [string length $dir] + foreach p $entries { + if {[string equal -length $l $dir $p]} { + set p [string replace $p 0 [expr {$l-1}] $virtualdir] + } + lappend res $p + } + + Log $child "GLOB > $res" NOTICE + return $res +} + +# AliasSource is the target of the "source" alias in safe interpreters. + +proc ::safe::AliasSource {child args} { + set argc [llength $args] + # Extended for handling of Tcl Modules to allow not only "source + # filename", but "source -encoding E filename" as well. + if {[lindex $args 0] eq "-encoding"} { + incr argc -2 + set encoding [lindex $args 1] + set at 2 + if {$encoding eq "identity"} { + Log $child "attempt to use the identity encoding" + return -code error "permission denied" + } + } else { + set at 0 + set encoding {} + } + if {$argc != 1} { + set msg "wrong # args: should be \"source ?-encoding E? fileName\"" + Log $child "$msg ($args)" + return -code error $msg + } + set file [lindex $args $at] + + # get the real path from the virtual one. + if {[catch { + set realfile [TranslatePath $child $file] + } msg]} { + Log $child $msg + return -code error "permission denied" + } + + # check that the path is in the access path of that child + if {[catch { + FileInAccessPath $child $realfile + } msg]} { + Log $child $msg + return -code error "permission denied" + } + + # Check that the filename exists and is readable. If it is not, deliver + # this -errorcode so that caller in tclPkgUnknown does not write a message + # to tclLog. Has no effect on other callers of ::source, which are in + # "package ifneeded" scripts. + if {[catch { + CheckFileName $child $realfile + } msg]} { + Log $child "$realfile:$msg" + return -code error -errorcode {POSIX EACCES} $msg + } + + # Passed all the tests, lets source it. Note that we do this all manually + # because we want to control [info script] in the child so information + # doesn't leak so much. [Bug 2913625] + set old [::interp eval $child {info script}] + set replacementMsg "script error" + set code [catch { + set f [open $realfile] + fconfigure $f -eofchar "\x1A {}" + if {$encoding ne ""} { + fconfigure $f -encoding $encoding + } + set contents [read $f] + close $f + ::interp eval $child [list info script $file] + } msg opt] + if {$code == 0} { + set code [catch {::interp eval $child $contents} msg opt] + set replacementMsg $msg + } + catch {interp eval $child [list info script $old]} + # Note that all non-errors are fine result codes from [source], so we must + # take a little care to do it properly. [Bug 2923613] + if {$code == 1} { + Log $child $msg + return -code error $replacementMsg + } + return -code $code -options $opt $msg +} + +# AliasLoad is the target of the "load" alias in safe interpreters. + +proc ::safe::AliasLoad {child file args} { + set argc [llength $args] + if {$argc > 2} { + set msg "load error: too many arguments" + Log $child "$msg ($argc) {$file $args}" + return -code error $msg + } + + # package name (can be empty if file is not). + set package [lindex $args 0] + + namespace upvar ::safe [VarName $child] state + + # Determine where to load. load use a relative interp path and {} + # means self, so we can directly and safely use passed arg. + set target [lindex $args 1] + if {$target ne ""} { + # we will try to load into a sub sub interp; check that we want to + # authorize that. + if {!$state(nestedok)} { + Log $child "loading to a sub interp (nestedok)\ + disabled (trying to load $package to $target)" + return -code error "permission denied (nested load)" + } + } + + # Determine what kind of load is requested + if {$file eq ""} { + # static package loading + if {$package eq ""} { + set msg "load error: empty filename and no package name" + Log $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)" + } + } else { + # file loading + + # get the real path from the virtual one. + try { + set file [TranslatePath $child $file] + } on error msg { + Log $child $msg + return -code error "permission denied" + } + + # check the translated path + try { + FileInAccessPath $child $file + } on error msg { + Log $child $msg + return -code error "permission denied (path)" + } + } + + try { + return [::interp invokehidden $child load $file $package $target] + } on error msg { + # Some packages return no error message. + set msg0 "load of binary library for package $package failed" + if {$msg eq {}} { + set msg $msg0 + } else { + set msg "$msg0: $msg" + } + Log $child $msg + return -code error $msg + } +} + +# FileInAccessPath raises an error if the file is not found in the list of +# directories contained in the (parent side recorded) child's access path. + +# the security here relies on "file dirname" answering the proper +# result... needs checking ? +proc ::safe::FileInAccessPath {child file} { + namespace upvar ::safe [VarName $child] state + set access_path $state(access_path) + + if {[file isdirectory $file]} { + return -code error "\"$file\": is a directory" + } + set parent [file dirname $file] + + # Normalize paths for comparison since lsearch knows nothing of + # potential pathname anomalies. + set norm_parent [file normalize $parent] + + namespace upvar ::safe [VarName $child] state + if {$norm_parent ni $state(access_path,norm)} { + return -code error "\"$file\": not in access_path" + } +} + +proc ::safe::DirInAccessPath {child dir} { + namespace upvar ::safe [VarName $child] state + set access_path $state(access_path) + + if {[file isfile $dir]} { + return -code error "\"$dir\": is a file" + } + + # Normalize paths for comparison since lsearch knows nothing of + # potential pathname anomalies. + set norm_dir [file normalize $dir] + + namespace upvar ::safe [VarName $child] state + if {$norm_dir ni $state(access_path,norm)} { + return -code error "\"$dir\": not in access_path" + } +} + +# This procedure is used to report an attempt to use an unsafe member of an +# ensemble command. + +proc ::safe::BadSubcommand {child command subcommand args} { + set msg "not allowed to invoke subcommand $subcommand of $command" + Log $child $msg + 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} + try { + set option [tcl::prefix match -error [list -level 1 -errorcode \ + [list TCL LOOKUP INDEX option $option]] $subcommands $option] + # Special case: [encoding system] ok, but [encoding system foo] not + if {$option eq "system" && [llength $args]} { + return -code error -errorcode {TCL WRONGARGS} \ + "wrong # args: should be \"encoding system\"" + } + } on error {msg options} { + Log $child $msg + return -options $options $msg + } + tailcall ::interp invokehidden $child encoding $option {*}$args +} + +# Various minor hiding of platform features. [Bug 2913625] + +proc ::safe::AliasExeName {child} { + return "" +} + +# ------------------------------------------------------------------------------ +# Using Interpreter Names with Namespace Qualifiers +# ------------------------------------------------------------------------------ +# (1) We wish to preserve compatibility with existing code, in which Safe Base +# interpreter names have no namespace qualifiers. +# (2) safe::interpCreate and the rest of the Safe Base previously could not +# accept namespace qualifiers in an interpreter name. +# (3) The interp command will accept namespace qualifiers in an interpreter +# name, but accepts distinct interpreters that will have the same command +# name (e.g. foo, ::foo, and :::foo) (bug 66c2e8c974). +# (4) To satisfy these constraints, Safe Base interpreter names will be fully +# qualified namespace names with no excess colons and with the leading "::" +# omitted. +# (5) Trailing "::" implies a namespace tail {}, which interp reads as {{}}. +# Reject such names. +# (6) We could: +# (a) EITHER reject usable but non-compliant names (e.g. excess colons) in +# interpCreate, interpInit; +# (b) OR accept such names and then translate to a compliant name in every +# command. +# The problem with (b) is that the user will expect to use the name with the +# interp command and will find that it is not recognised. +# E.g "interpCreate ::foo" creates interpreter "foo", and the user's name +# "::foo" works with all the Safe Base commands, but "interp eval ::foo" +# fails. +# So we choose (a). +# (7) The command +# namespace upvar ::safe S$child state +# becomes +# namespace upvar ::safe [VarName $child] state +# ------------------------------------------------------------------------------ + +proc ::safe::RejectExcessColons {child} { + set stripped [regsub -all -- {:::*} $child ::] + if {[string range $stripped end-1 end] eq {::}} { + return -code error {interpreter name must not end in "::"} + } + if {$stripped ne $child} { + set msg {interpreter name has excess colons in namespace separators} + return -code error $msg + } + if {[string range $stripped 0 1] eq {::}} { + return -code error {interpreter name must not begin "::"} + } + return +} + +proc ::safe::VarName {child} { + # return S$child + return S[string map {:: @N @ @A} $child] +} + +proc ::safe::Setup {} { + #### + # + # Setup the arguments parsing + # + #### + + # Share the descriptions + set temp [::tcl::OptKeyRegister { + {-accessPath -list {} "access path for the slave"} + {-noStatics "prevent loading of statically linked pkgs"} + {-statics true "loading of statically linked pkgs"} + {-nestedLoadOk "allow nested loading"} + {-nested false "nested loading"} + {-deleteHook -script {} "delete hook"} + }] + + # create case (slave is optional) + ::tcl::OptKeyRegister { + {?slave? -name {} "name of the slave (optional)"} + } ::safe::interpCreate + + # adding the flags sub programs to the command program (relying on Opt's + # internal implementation details) + lappend ::tcl::OptDesc(::safe::interpCreate) $::tcl::OptDesc($temp) + + # init and configure (slave is needed) + ::tcl::OptKeyRegister { + {slave -name {} "name of the slave"} + } ::safe::interpIC + + # adding the flags sub programs to the command program (relying on Opt's + # internal implementation details) + lappend ::tcl::OptDesc(::safe::interpIC) $::tcl::OptDesc($temp) + + # temp not needed anymore + ::tcl::OptKeyDelete $temp + + #### + # + # Default: No logging. + # + #### + + setLogCmd {} + + # Log eventually. + # To enable error logging, set Log to {puts stderr} for instance, + # via setLogCmd. + return +} + +namespace eval ::safe { + # internal variables + + # Log command, set via 'setLogCmd'. Logging is disabled when empty. + variable Log {} + + # The package maintains a state array per child interp under its + # control. The name of this array is S. This array is + # brought into scope where needed, using 'namespace upvar'. The S + # prefix is used to avoid that a child interp called "Log" smashes + # the "Log" variable. + # + # The array's elements are: + # + # 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,map : dict ( token -> path ) + # access_path,remap : dict ( path -> token ) + # tm_path_slave : 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 +} + +::safe::Setup diff --git a/lib/tcl8.6/tclAppInit.c b/lib/tcl8.6/tclAppInit.c new file mode 100644 index 0000000000000000000000000000000000000000..552f9e4b2426327929f79700720e085794a2a0f8 --- /dev/null +++ b/lib/tcl8.6/tclAppInit.c @@ -0,0 +1,176 @@ +/* + * tclAppInit.c -- + * + * Provides a default version of the main program and Tcl_AppInit + * procedure for tclsh and other Tcl-based applications (without Tk). + * + * Copyright (c) 1993 The Regents of the University of California. + * Copyright (c) 1994-1997 Sun Microsystems, Inc. + * Copyright (c) 1998-1999 Scriptics Corporation. + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#undef BUILD_tcl +#undef STATIC_BUILD +#include "tcl.h" +#if TCL_MAJOR_VERSION < 9 && TCL_MINOR_VERSION < 7 +# define Tcl_LibraryInitProc Tcl_PackageInitProc +# define Tcl_StaticLibrary Tcl_StaticPackage +#endif + +#ifdef TCL_TEST +extern Tcl_LibraryInitProc Tcltest_Init; +extern Tcl_LibraryInitProc Tcltest_SafeInit; +#endif /* TCL_TEST */ + +#ifdef TCL_XT_TEST +extern void XtToolkitInitialize(void); +extern Tcl_LibraryInitProc Tclxttest_Init; +#endif /* TCL_XT_TEST */ + +/* + * The following #if block allows you to change the AppInit function by using + * a #define of TCL_LOCAL_APPINIT instead of rewriting this entire file. The + * #if checks for that #define and uses Tcl_AppInit if it does not exist. + */ + +#ifndef TCL_LOCAL_APPINIT +#define TCL_LOCAL_APPINIT Tcl_AppInit +#endif +#ifndef MODULE_SCOPE +# define MODULE_SCOPE extern +#endif +MODULE_SCOPE int TCL_LOCAL_APPINIT(Tcl_Interp *); +MODULE_SCOPE int main(int, char **); + +/* + * The following #if block allows you to change how Tcl finds the startup + * script, prime the library or encoding paths, fiddle with the argv, etc., + * without needing to rewrite Tcl_Main() + */ + +#ifdef TCL_LOCAL_MAIN_HOOK +MODULE_SCOPE int TCL_LOCAL_MAIN_HOOK(int *argc, char ***argv); +#endif + +/* + *---------------------------------------------------------------------- + * + * main -- + * + * This is the main program for the application. + * + * Results: + * None: Tcl_Main never returns here, so this procedure never returns + * either. + * + * Side effects: + * Just about anything, since from here we call arbitrary Tcl code. + * + *---------------------------------------------------------------------- + */ + +int +main( + int argc, /* Number of command-line arguments. */ + char *argv[]) /* Values of command-line arguments. */ +{ +#ifdef TCL_XT_TEST + XtToolkitInitialize(); +#endif + +#ifdef TCL_LOCAL_MAIN_HOOK + TCL_LOCAL_MAIN_HOOK(&argc, &argv); +#elif (TCL_MAJOR_VERSION > 8 || TCL_MINOR_VERSION > 6) && (!defined(_WIN32) || defined(UNICODE)) + /* New in Tcl 8.7. This doesn't work on Windows without UNICODE */ + TclZipfs_AppHook(&argc, &argv); +#endif + + Tcl_Main(argc, argv, TCL_LOCAL_APPINIT); + return 0; /* Needed only to prevent compiler warning. */ +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_AppInit -- + * + * This procedure performs application-specific initialization. Most + * applications, especially those that incorporate additional packages, + * will have their own version of this procedure. + * + * Results: + * Returns a standard Tcl completion code, and leaves an error message in + * the interp's result if an error occurs. + * + * Side effects: + * Depends on the startup script. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_AppInit( + Tcl_Interp *interp) /* Interpreter for application. */ +{ + if ((Tcl_Init)(interp) == TCL_ERROR) { + return TCL_ERROR; + } + +#ifdef TCL_XT_TEST + if (Tclxttest_Init(interp) == TCL_ERROR) { + return TCL_ERROR; + } +#endif + +#ifdef TCL_TEST + if (Tcltest_Init(interp) == TCL_ERROR) { + return TCL_ERROR; + } + Tcl_StaticLibrary(interp, "Tcltest", Tcltest_Init, Tcltest_SafeInit); +#endif /* TCL_TEST */ + + /* + * Call the init procedures for included packages. Each call should look + * like this: + * + * if (Mod_Init(interp) == TCL_ERROR) { + * return TCL_ERROR; + * } + * + * where "Mod" is the name of the module. (Dynamically-loadable packages + * should have the same entry-point name.) + */ + + /* + * Call Tcl_CreateCommand for application-specific commands, if they + * weren't already created by the init procedures called above. + */ + + /* + * Specify a user-specific startup file to invoke if the application is + * run interactively. Typically the startup file is "~/.apprc" where "app" + * is the name of the application. If this line is deleted then no + * user-specific startup file will be run under any conditions. + */ + +#ifdef DJGPP + (Tcl_ObjSetVar2)(interp, Tcl_NewStringObj("tcl_rcFileName", -1), NULL, + Tcl_NewStringObj("~/tclsh.rc", -1), TCL_GLOBAL_ONLY); +#else + (Tcl_ObjSetVar2)(interp, Tcl_NewStringObj("tcl_rcFileName", -1), NULL, + Tcl_NewStringObj("~/.tclshrc", -1), TCL_GLOBAL_ONLY); +#endif + + return TCL_OK; +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ diff --git a/lib/tcl8.6/tclIndex b/lib/tcl8.6/tclIndex new file mode 100644 index 0000000000000000000000000000000000000000..a186a7d258d56bad2090c618bdf2bc3645dfd882 --- /dev/null +++ b/lib/tcl8.6/tclIndex @@ -0,0 +1,79 @@ +# Tcl autoload index file, version 2.0 +# -*- tcl -*- +# This file is generated by the "auto_mkindex" command +# and sourced to set up indexing information for one or +# more commands. Typically each line is a command that +# sets an element in the auto_index array, where the +# element name is the name of a command and the value is +# a script that loads the command. + +set auto_index(auto_reset) [list source [file join $dir auto.tcl]] +set auto_index(tcl_findLibrary) [list source [file join $dir auto.tcl]] +set auto_index(auto_mkindex) [list source [file join $dir auto.tcl]] +set auto_index(auto_mkindex_old) [list source [file join $dir auto.tcl]] +set auto_index(::auto_mkindex_parser::init) [list source [file join $dir auto.tcl]] +set auto_index(::auto_mkindex_parser::cleanup) [list source [file join $dir auto.tcl]] +set auto_index(::auto_mkindex_parser::mkindex) [list source [file join $dir auto.tcl]] +set auto_index(::auto_mkindex_parser::hook) [list source [file join $dir auto.tcl]] +set auto_index(::auto_mkindex_parser::slavehook) [list source [file join $dir auto.tcl]] +set auto_index(::auto_mkindex_parser::command) [list source [file join $dir auto.tcl]] +set auto_index(::auto_mkindex_parser::commandInit) [list source [file join $dir auto.tcl]] +set auto_index(::auto_mkindex_parser::fullname) [list source [file join $dir auto.tcl]] +set auto_index(history) [list source [file join $dir history.tcl]] +set auto_index(::tcl::history) [list source [file join $dir history.tcl]] +set auto_index(::tcl::HistAdd) [list source [file join $dir history.tcl]] +set auto_index(::tcl::HistKeep) [list source [file join $dir history.tcl]] +set auto_index(::tcl::HistClear) [list source [file join $dir history.tcl]] +set auto_index(::tcl::HistInfo) [list source [file join $dir history.tcl]] +set auto_index(::tcl::HistRedo) [list source [file join $dir history.tcl]] +set auto_index(::tcl::HistIndex) [list source [file join $dir history.tcl]] +set auto_index(::tcl::HistEvent) [list source [file join $dir history.tcl]] +set auto_index(::tcl::HistChange) [list source [file join $dir history.tcl]] +set auto_index(pkg_mkIndex) [list source [file join $dir package.tcl]] +set auto_index(tclPkgSetup) [list source [file join $dir package.tcl]] +set auto_index(tclPkgUnknown) [list source [file join $dir package.tcl]] +set auto_index(::tcl::MacOSXPkgUnknown) [list source [file join $dir package.tcl]] +set auto_index(::pkg::create) [list source [file join $dir package.tcl]] +set auto_index(parray) [list source [file join $dir parray.tcl]] +set auto_index(::safe::InterpStatics) [list source [file join $dir safe.tcl]] +set auto_index(::safe::InterpNested) [list source [file join $dir safe.tcl]] +set auto_index(::safe::interpCreate) [list source [file join $dir safe.tcl]] +set auto_index(::safe::interpInit) [list source [file join $dir safe.tcl]] +set auto_index(::safe::CheckInterp) [list source [file join $dir safe.tcl]] +set auto_index(::safe::interpConfigure) [list source [file join $dir safe.tcl]] +set auto_index(::safe::InterpCreate) [list source [file join $dir safe.tcl]] +set auto_index(::safe::InterpSetConfig) [list source [file join $dir safe.tcl]] +set auto_index(::safe::interpFindInAccessPath) [list source [file join $dir safe.tcl]] +set auto_index(::safe::interpAddToAccessPath) [list source [file join $dir safe.tcl]] +set auto_index(::safe::InterpInit) [list source [file join $dir safe.tcl]] +set auto_index(::safe::AddSubDirs) [list source [file join $dir safe.tcl]] +set auto_index(::safe::interpDelete) [list source [file join $dir safe.tcl]] +set auto_index(::safe::setLogCmd) [list source [file join $dir safe.tcl]] +set auto_index(::safe::SyncAccessPath) [list source [file join $dir safe.tcl]] +set auto_index(::safe::PathToken) [list source [file join $dir safe.tcl]] +set auto_index(::safe::TranslatePath) [list source [file join $dir safe.tcl]] +set auto_index(::safe::Log) [list source [file join $dir safe.tcl]] +set auto_index(::safe::CheckFileName) [list source [file join $dir safe.tcl]] +set auto_index(::safe::AliasGlob) [list source [file join $dir safe.tcl]] +set auto_index(::safe::AliasSource) [list source [file join $dir safe.tcl]] +set auto_index(::safe::AliasLoad) [list source [file join $dir safe.tcl]] +set auto_index(::safe::FileInAccessPath) [list source [file join $dir safe.tcl]] +set auto_index(::safe::DirInAccessPath) [list source [file join $dir safe.tcl]] +set auto_index(::safe::Subset) [list source [file join $dir safe.tcl]] +set auto_index(::safe::AliasSubset) [list source [file join $dir safe.tcl]] +set auto_index(::safe::AliasEncoding) [list source [file join $dir safe.tcl]] +set auto_index(tcl_wordBreakAfter) [list source [file join $dir word.tcl]] +set auto_index(tcl_wordBreakBefore) [list source [file join $dir word.tcl]] +set auto_index(tcl_endOfWord) [list source [file join $dir word.tcl]] +set auto_index(tcl_startOfNextWord) [list source [file join $dir word.tcl]] +set auto_index(tcl_startOfPreviousWord) [list source [file join $dir word.tcl]] +set auto_index(::tcl::tm::add) [list source [file join $dir tm.tcl]] +set auto_index(::tcl::tm::remove) [list source [file join $dir tm.tcl]] +set auto_index(::tcl::tm::list) [list source [file join $dir tm.tcl]] +set auto_index(::tcl::tm::Defaults) [list source [file join $dir tm.tcl]] +set auto_index(::tcl::tm::UnknownHandler) [list source [file join $dir tm.tcl]] +set auto_index(::tcl::tm::roots) [list source [file join $dir tm.tcl]] +set auto_index(::tcl::tm::path) [list source [file join $dir tm.tcl]] +if {[namespace exists ::tcl::unsupported]} { + set auto_index(timerate) {namespace import ::tcl::unsupported::timerate} +} diff --git a/lib/tcl8.6/tm.tcl b/lib/tcl8.6/tm.tcl new file mode 100644 index 0000000000000000000000000000000000000000..02007d536738204224dfe8a562e71eaa0434e584 --- /dev/null +++ b/lib/tcl8.6/tm.tcl @@ -0,0 +1,380 @@ +# -*- tcl -*- +# +# Searching for Tcl Modules. Defines a procedure, declares it as the primary +# command for finding packages, however also uses the former 'package unknown' +# command as a fallback. +# +# Locates all possible packages in a directory via a less restricted glob. The +# targeted directory is derived from the name of the requested package, i.e. +# the TM scan will look only at directories which can contain the requested +# package. It will register all packages it found in the directory so that +# future requests have a higher chance of being fulfilled by the ifneeded +# database without having to come to us again. +# +# We do not remember where we have been and simply rescan targeted directories +# when invoked again. The reasoning is this: +# +# - The only way we get back to the same directory is if someone is trying to +# [package require] something that wasn't there on the first scan. +# +# Either +# 1) It is there now: If we rescan, you get it; if not you don't. +# +# This covers the possibility that the application asked for a package +# late, and the package was actually added to the installation after the +# application was started. It should still be able to find it. +# +# 2) It still is not there: Either way, you don't get it, but the rescan +# takes time. This is however an error case and we don't care that much +# about it +# +# 3) It was there the first time; but for some reason a "package forget" has +# been run, and "package" doesn't know about it anymore. +# +# This can be an indication that the application wishes to reload some +# functionality. And should work as well. +# +# Note that this also strikes a balance between doing a glob targeting a +# single package, and thus most likely requiring multiple globs of the same +# directory when the application is asking for many packages, and trying to +# glob for _everything_ in all subdirectories when looking for a package, +# which comes with a heavy startup cost. +# +# We scan for regular packages only if no satisfying module was found. + +namespace eval ::tcl::tm { + # Default paths. None yet. + + variable paths {} + + # The regex pattern a file name has to match to make it a Tcl Module. + + set pkgpattern {^([_[:alpha:]][:_[:alnum:]]*)-([[:digit:]].*)[.]tm$} + + # Export the public API + + namespace export path + namespace ensemble create -command path -subcommands {add remove list} +} + +# ::tcl::tm::path implementations -- +# +# Public API to the module path. See specification. +# +# Arguments +# cmd - The subcommand to execute +# args - The paths to add/remove. Must not appear querying the +# path with 'list'. +# +# Results +# No result for subcommands 'add' and 'remove'. A list of paths for +# 'list'. +# +# Side effects +# The subcommands 'add' and 'remove' manipulate the list of paths to +# search for Tcl Modules. The subcommand 'list' has no side effects. + +proc ::tcl::tm::add {args} { + # PART OF THE ::tcl::tm::path ENSEMBLE + # + # The path is added at the head to the list of module paths. + # + # The command enforces the restriction that no path may be an ancestor + # directory of any other path on the list. If the new path violates this + # restriction an error will be raised. + # + # If the path is already present as is no error will be raised and no + # action will be taken. + + variable paths + + # We use a copy of the path as source during validation, and extend it as + # well. Because we not only have to detect if the new paths are bogus with + # respect to the existing paths, but also between themselves. Otherwise we + # can still add bogus paths, by specifying them in a single call. This + # makes the use of the new paths simpler as well, a trivial assignment of + # the collected paths to the official state var. + + set newpaths $paths + foreach p $args { + if {$p in $newpaths} { + # Ignore a path already on the list. + continue + } + + # Search for paths which are subdirectories of the new one. If there + # are any then the new path violates the restriction about ancestors. + + set pos [lsearch -glob $newpaths ${p}/*] + # Cannot use "in", we need the position for the message. + if {$pos >= 0} { + return -code error \ + "$p is ancestor of existing module path [lindex $newpaths $pos]." + } + + # Now look for existing paths which are ancestors of the new one. This + # reverse question forces us to loop over the existing paths, as each + # element is the pattern, not the new path :( + + foreach ep $newpaths { + if {[string match ${ep}/* $p]} { + return -code error \ + "$p is subdirectory of existing module path $ep." + } + } + + set newpaths [linsert $newpaths 0 $p] + } + + # The validation of the input is complete and successful, and everything + # in newpaths is either an old path, or added. We can now extend the + # official list of paths, a simple assignment is sufficient. + + set paths $newpaths + return +} + +proc ::tcl::tm::remove {args} { + # PART OF THE ::tcl::tm::path ENSEMBLE + # + # Removes the path from the list of module paths. The command is silently + # ignored if the path is not on the list. + + variable paths + + foreach p $args { + set pos [lsearch -exact $paths $p] + if {$pos >= 0} { + set paths [lreplace $paths $pos $pos] + } + } +} + +proc ::tcl::tm::list {} { + # PART OF THE ::tcl::tm::path ENSEMBLE + + variable paths + return $paths +} + +# ::tcl::tm::UnknownHandler -- +# +# Unknown handler for Tcl Modules, i.e. packages in module form. +# +# Arguments +# original - Original [package unknown] procedure. +# name - Name of desired package. +# version - Version of desired package. Can be the +# empty string. +# exact - Either -exact or omitted. +# +# Name, version, and exact are used to determine satisfaction. The +# original is called iff no satisfaction was achieved. The name is also +# used to compute the directory to target in the search. +# +# Results +# None. +# +# Side effects +# May populate the package ifneeded database with additional provide +# scripts. + +proc ::tcl::tm::UnknownHandler {original name args} { + # Import the list of paths to search for packages in module form. + # Import the pattern used to check package names in detail. + + variable paths + variable pkgpattern + + # Without paths to search we can do nothing. (Except falling back to the + # regular search). + + if {[llength $paths]} { + set pkgpath [string map {:: /} $name] + set pkgroot [file dirname $pkgpath] + if {$pkgroot eq "."} { + set pkgroot "" + } + + # We don't remember a copy of the paths while looping. Tcl Modules are + # unable to change the list while we are searching for them. This also + # simplifies the loop, as we cannot get additional directories while + # iterating over the list. A simple foreach is sufficient. + + set satisfied 0 + foreach path $paths { + if {![interp issafe] && ![file exists $path]} { + continue + } + set currentsearchpath [file join $path $pkgroot] + if {![interp issafe] && ![file exists $currentsearchpath]} { + continue + } + set strip [llength [file split $path]] + + # Get the module files out of the subdirectories. + # - Safe Base interpreters have a restricted "glob" command that + # works in this case. + # - The "catch" was essential when there was no safe glob and every + # call in a safe interp failed; it is retained only for corner + # cases in which the eventual call to glob returns an error. + + catch { + # We always look for _all_ possible modules in the current + # path, to get the max result out of the glob. + + foreach file [glob -nocomplain -directory $currentsearchpath *.tm] { + set pkgfilename [join [lrange [file split $file] $strip end] ::] + + if {![regexp -- $pkgpattern $pkgfilename --> pkgname pkgversion]} { + # Ignore everything not matching our pattern for + # package names. + continue + } + try { + package vcompare $pkgversion 0 + } on error {} { + # Ignore everything where the version part is not + # acceptable to "package vcompare". + continue + } + + if {([package ifneeded $pkgname $pkgversion] ne {}) + && (![interp issafe]) + } { + # There's already a provide script registered for + # this version of this package. Since all units of + # code claiming to be the same version of the same + # package ought to be identical, just stick with + # the one we already have. + # This does not apply to Safe Base interpreters because + # the token-to-directory mapping may have changed. + continue + } + + # We have found a candidate, generate a "provide script" + # for it, and remember it. Note that we are using ::list + # to do this; locally [list] means something else without + # the namespace specifier. + + # NOTE. When making changes to the format of the provide + # command generated below CHECK that the 'LOCATE' + # procedure in core file 'platform/shell.tcl' still + # understands it, or, if not, update its implementation + # appropriately. + # + # Right now LOCATE's implementation assumes that the path + # of the package file is the last element in the list. + + package ifneeded $pkgname $pkgversion \ + "[::list package provide $pkgname $pkgversion];[::list source -encoding utf-8 $file]" + + # We abort in this unknown handler only if we got a + # satisfying candidate for the requested package. + # Otherwise we still have to fallback to the regular + # package search to complete the processing. + + if {($pkgname eq $name) + && [package vsatisfies $pkgversion {*}$args]} { + set satisfied 1 + + # We do not abort the loop, and keep adding provide + # scripts for every candidate in the directory, just + # remember to not fall back to the regular search + # anymore. + } + } + } + } + + if {$satisfied} { + return + } + } + + # Fallback to previous command, if existing. See comment above about + # ::list... + + if {[llength $original]} { + uplevel 1 $original [::linsert $args 0 $name] + } +} + +# ::tcl::tm::Defaults -- +# +# Determines the default search paths. +# +# Arguments +# None +# +# Results +# None. +# +# Side effects +# May add paths to the list of defaults. + +proc ::tcl::tm::Defaults {} { + global env tcl_platform + + 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 + # something other than [::list] in this namespace. + roots [::list \ + [file dirname [info library]] \ + [file join [file dirname [file dirname $exe]] lib] \ + ] + + if {$tcl_platform(platform) eq "windows"} { + set sep ";" + } else { + set sep ":" + } + for {set n $minor} {$n >= 0} {incr n -1} { + foreach ev [::list \ + TCL${major}.${n}_TM_PATH \ + TCL${major}_${n}_TM_PATH \ + ] { + if {![info exists env($ev)]} continue + foreach p [split $env($ev) $sep] { + path add $p + } + } + } + return +} + +# ::tcl::tm::roots -- +# +# Public API to the module path. See specification. +# +# Arguments +# paths - List of 'root' paths to derive search paths from. +# +# Results +# No result. +# +# Side effects +# 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 + foreach pa $paths { + set p [file join $pa tcl$major] + for {set n $minor} {$n >= 0} {incr n -1} { + set px [file join $p ${major}.${n}] + if {![interp issafe]} {set px [file normalize $px]} + path add $px + } + set px [file join $p site-tcl] + if {![interp issafe]} {set px [file normalize $px]} + path add $px + } + return +} + +# Initialization. Set up the default paths, then insert the new handler into +# the chain. + +if {![interp issafe]} {::tcl::tm::Defaults} diff --git a/lib/tcl8.6/word.tcl b/lib/tcl8.6/word.tcl new file mode 100644 index 0000000000000000000000000000000000000000..a993918b44c590b0b1eba3d7ae64cebf195e644a --- /dev/null +++ b/lib/tcl8.6/word.tcl @@ -0,0 +1,154 @@ +# word.tcl -- +# +# This file defines various procedures for computing word boundaries in +# strings. This file is primarily needed so Tk text and entry widgets behave +# properly for different platforms. +# +# Copyright (c) 1996 Sun Microsystems, Inc. +# Copyright (c) 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. + +if {$::tcl_platform(platform) eq "windows"} { + # Windows style - any but a Unicode space char + if {![info exists ::tcl_wordchars]} { + set ::tcl_wordchars {\S} + } + if {![info exists ::tcl_nonwordchars]} { + set ::tcl_nonwordchars {\s} + } +} else { + # Motif style - any Unicode word char (number, letter, or underscore) + if {![info exists ::tcl_wordchars]} { + set ::tcl_wordchars {\w} + } + if {![info exists ::tcl_nonwordchars]} { + set ::tcl_nonwordchars {\W} + } +} + +# Arrange for caches of the real matcher REs to be kept, which enables the REs +# themselves to be cached for greater performance (and somewhat greater +# clarity too). + +namespace eval ::tcl { + variable WordBreakRE + array set WordBreakRE {} + + proc UpdateWordBreakREs args { + # Ignores the arguments + global tcl_wordchars tcl_nonwordchars + variable WordBreakRE + + # To keep the RE strings short... + set letter $tcl_wordchars + set space $tcl_nonwordchars + + set WordBreakRE(after) "$letter$space|$space$letter" + set WordBreakRE(before) "^.*($letter$space|$space$letter)" + set WordBreakRE(end) "$space*$letter+$space" + set WordBreakRE(next) "$letter*$space+$letter" + set WordBreakRE(previous) "$space*($letter+)$space*\$" + } + + # Initialize the cache + UpdateWordBreakREs + trace add variable ::tcl_wordchars write ::tcl::UpdateWordBreakREs + trace add variable ::tcl_nonwordchars write ::tcl::UpdateWordBreakREs +} + +# tcl_wordBreakAfter -- +# +# This procedure returns the index of the first word boundary after the +# starting point in the given string, or -1 if there are no more boundaries in +# the given string. The index returned refers to the first character of the +# pair that comprises a boundary. +# +# Arguments: +# str - String to search. +# start - Index into string specifying starting point. + +proc tcl_wordBreakAfter {str start} { + variable ::tcl::WordBreakRE + set result {-1 -1} + regexp -indices -start $start -- $WordBreakRE(after) $str result + return [lindex $result 1] +} + +# tcl_wordBreakBefore -- +# +# This procedure returns the index of the first word boundary before the +# starting point in the given string, or -1 if there are no more boundaries in +# the given string. The index returned refers to the second character of the +# pair that comprises a boundary. +# +# Arguments: +# str - String to search. +# start - Index into string specifying starting point. + +proc tcl_wordBreakBefore {str start} { + variable ::tcl::WordBreakRE + set result {-1 -1} + regexp -indices -- $WordBreakRE(before) [string range $str 0 $start] result + return [lindex $result 1] +} + +# tcl_endOfWord -- +# +# This procedure returns the index of the first end-of-word location after a +# starting index in the given string. An end-of-word location is defined to be +# the first whitespace character following the first non-whitespace character +# after the starting point. Returns -1 if there are no more words after the +# starting point. +# +# Arguments: +# str - String to search. +# start - Index into string specifying starting point. + +proc tcl_endOfWord {str start} { + variable ::tcl::WordBreakRE + set result {-1 -1} + regexp -indices -start $start -- $WordBreakRE(end) $str result + return [lindex $result 1] +} + +# tcl_startOfNextWord -- +# +# This procedure returns the index of the first start-of-word location after a +# starting index in the given string. A start-of-word location is defined to +# be a non-whitespace character following a whitespace character. Returns -1 +# if there are no more start-of-word locations after the starting point. +# +# Arguments: +# str - String to search. +# start - Index into string specifying starting point. + +proc tcl_startOfNextWord {str start} { + variable ::tcl::WordBreakRE + set result {-1 -1} + regexp -indices -start $start -- $WordBreakRE(next) $str result + return [lindex $result 1] +} + +# tcl_startOfPreviousWord -- +# +# This procedure returns the index of the first start-of-word location before +# a starting index in the given string. +# +# Arguments: +# str - String to search. +# start - Index into string specifying starting point. + +proc tcl_startOfPreviousWord {str start} { + variable ::tcl::WordBreakRE + set word {-1 -1} + if {$start > 0} { + regexp -indices -- $WordBreakRE(previous) [string range [string range $str 0 $start] 0 end-1] \ + result word + } + return [lindex $word 0] +} diff --git a/lib/tdbc1.1.7/libtdbc1.1.7.so b/lib/tdbc1.1.7/libtdbc1.1.7.so new file mode 100644 index 0000000000000000000000000000000000000000..d27d353f0364e7eacedd8cce7a8f5b9cf433cbfa Binary files /dev/null and b/lib/tdbc1.1.7/libtdbc1.1.7.so differ diff --git a/lib/tdbc1.1.7/libtdbcstub1.1.7.a b/lib/tdbc1.1.7/libtdbcstub1.1.7.a new file mode 100644 index 0000000000000000000000000000000000000000..8204788fb9a35e6beaf9867bdd965db17a11ddee Binary files /dev/null and b/lib/tdbc1.1.7/libtdbcstub1.1.7.a differ diff --git a/lib/tdbc1.1.7/pkgIndex.tcl b/lib/tdbc1.1.7/pkgIndex.tcl new file mode 100644 index 0000000000000000000000000000000000000000..7a600a589b18e8d1a5e4c8b548758840ac3b0fad --- /dev/null +++ b/lib/tdbc1.1.7/pkgIndex.tcl @@ -0,0 +1,26 @@ +# -*- tcl -*- +# Tcl package index file, version 1.1 +# +# Make sure that TDBC is running in a compatible version of Tcl, and +# that TclOO is available. + +if {![package vsatisfies [package provide Tcl] 8.6-]} { + return +} +apply {{dir} { + set libraryfile [file join $dir tdbc.tcl] + if {![file exists $libraryfile] && [info exists ::env(TDBC_LIBRARY)]} { + set libraryfile [file join $::env(TDBC_LIBRARY) tdbc.tcl] + } + if {[package vsatisfies [package provide Tcl] 9.0-]} { + package ifneeded tdbc 1.1.7 \ + "package require TclOO;\ + [list load [file join $dir libtcl9tdbc1.1.7.so] [string totitle tdbc]]\;\ + [list source $libraryfile]" + } else { + package ifneeded tdbc 1.1.7 \ + "package require TclOO;\ + [list load [file join $dir libtdbc1.1.7.so] [string totitle tdbc]]\;\ + [list source $libraryfile]" + } +}} $dir diff --git a/lib/tdbc1.1.7/tdbc.tcl b/lib/tdbc1.1.7/tdbc.tcl new file mode 100644 index 0000000000000000000000000000000000000000..f536131e80e1e126c31bd07c8591ec287af618d5 --- /dev/null +++ b/lib/tdbc1.1.7/tdbc.tcl @@ -0,0 +1,922 @@ +# tdbc.tcl -- +# +# Definitions of base classes from which TDBC drivers' connections, +# statements and result sets may inherit. +# +# Copyright (c) 2008 by Kevin B. Kenny +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# RCS: @(#) $Id$ +# +#------------------------------------------------------------------------------ + +package require TclOO + +namespace eval ::tdbc { + namespace export connection statement resultset + variable generalError [list TDBC GENERAL_ERROR HY000 {}] +} + +#------------------------------------------------------------------------------ +# +# tdbc::ParseConvenienceArgs -- +# +# Parse the convenience arguments to a TDBC 'execute', +# 'executewithdictionary', or 'foreach' call. +# +# Parameters: +# argv - Arguments to the call +# optsVar -- Name of a variable in caller's scope that will receive +# a dictionary of the supplied options +# +# Results: +# Returns any args remaining after parsing the options. +# +# Side effects: +# Sets the 'opts' dictionary to the options. +# +#------------------------------------------------------------------------------ + +proc tdbc::ParseConvenienceArgs {argv optsVar} { + + variable generalError + upvar 1 $optsVar opts + + set opts [dict create -as dicts] + set i 0 + + # Munch keyword options off the front of the command arguments + + foreach {key value} $argv { + if {[string index $key 0] eq {-}} { + switch -regexp -- $key { + -as? { + if {$value ne {dicts} && $value ne {lists}} { + set errorcode $generalError + lappend errorcode badVarType $value + return -code error \ + -errorcode $errorcode \ + "bad variable type \"$value\":\ + must be lists or dicts" + } + dict set opts -as $value + } + -c(?:o(?:l(?:u(?:m(?:n(?:s(?:v(?:a(?:r(?:i(?:a(?:b(?:le?)?)?)?)?)?)?)?)?)?)?)?)?) { + dict set opts -columnsvariable $value + } + -- { + incr i + break + } + default { + set errorcode $generalError + lappend errorcode badOption $key + return -code error \ + -errorcode $errorcode \ + "bad option \"$key\":\ + must be -as or -columnsvariable" + } + } + } else { + break + } + incr i 2 + } + + return [lrange $argv[set argv {}] $i end] + +} + + + +#------------------------------------------------------------------------------ +# +# tdbc::connection -- +# +# Class that represents a generic connection to a database. +# +#----------------------------------------------------------------------------- + +oo::class create ::tdbc::connection { + + # statementSeq is the sequence number of the last statement created. + # statementClass is the name of the class that implements the + # 'statement' API. + # primaryKeysStatement is the statement that queries primary keys + # foreignKeysStatement is the statement that queries foreign keys + + variable statementSeq primaryKeysStatement foreignKeysStatement + + # The base class constructor accepts no arguments. It sets up the + # machinery to do the bookkeeping to keep track of what statements + # are associated with the connection. The derived class constructor + # is expected to set the variable, 'statementClass' to the name + # of the class that represents statements, so that the 'prepare' + # method can invoke it. + + constructor {} { + set statementSeq 0 + namespace eval Stmt {} + } + + # The 'close' method is simply an alternative syntax for destroying + # the connection. + + method close {} { + my destroy + } + + # The 'prepare' method creates a new statement against the connection, + # giving its constructor the current statement and the SQL code to + # prepare. It uses the 'statementClass' variable set by the constructor + # to get the class to instantiate. + + method prepare {sqlcode} { + return [my statementCreate Stmt::[incr statementSeq] [self] $sqlcode] + } + + # The 'statementCreate' method delegates to the constructor + # of the class specified by the 'statementClass' variable. It's + # intended for drivers designed before tdbc 1.0b10. Current ones + # should forward this method to the constructor directly. + + method statementCreate {name instance sqlcode} { + my variable statementClass + return [$statementClass create $name $instance $sqlcode] + } + + # Derived classes are expected to implement the 'prepareCall' method, + # and have it call 'prepare' as needed (or do something else and + # install the resulting statement) + + # The 'statements' method lists the statements active against this + # connection. + + method statements {} { + info commands Stmt::* + } + + # The 'resultsets' method lists the result sets active against this + # connection. + + method resultsets {} { + set retval {} + foreach statement [my statements] { + foreach resultset [$statement resultsets] { + lappend retval $resultset + } + } + return $retval + } + + # The 'transaction' method executes a block of Tcl code as an + # ACID transaction against the database. + + method transaction {script} { + my begintransaction + set status [catch {uplevel 1 $script} result options] + if {$status in {0 2 3 4}} { + set status2 [catch {my commit} result2 options2] + if {$status2 == 1} { + set status 1 + set result $result2 + set options $options2 + } + } + switch -exact -- $status { + 0 { + # do nothing + } + 2 - 3 - 4 { + set options [dict merge {-level 1} $options[set options {}]] + dict incr options -level + } + default { + my rollback + } + } + return -options $options $result + } + + # The 'allrows' method prepares a statement, then executes it with + # a given set of substituents, returning a list of all the rows + # that the statement returns. Optionally, it stores the names of + # the columns in '-columnsvariable'. + # Usage: + # $db allrows ?-as lists|dicts? ?-columnsvariable varName? ?--? + # sql ?dictionary? + + method allrows args { + + variable ::tdbc::generalError + + # Grab keyword-value parameters + + set args [::tdbc::ParseConvenienceArgs $args[set args {}] opts] + + # Check postitional parameters + + set cmd [list [self] prepare] + if {[llength $args] == 1} { + set sqlcode [lindex $args 0] + } elseif {[llength $args] == 2} { + lassign $args sqlcode dict + } else { + set errorcode $generalError + lappend errorcode wrongNumArgs + return -code error -errorcode $errorcode \ + "wrong # args: should be [lrange [info level 0] 0 1]\ + ?-option value?... ?--? sqlcode ?dictionary?" + } + lappend cmd $sqlcode + + # Prepare the statement + + set stmt [uplevel 1 $cmd] + + # Delegate to the statement to accumulate the results + + set cmd [list $stmt allrows {*}$opts --] + if {[info exists dict]} { + lappend cmd $dict + } + set status [catch { + uplevel 1 $cmd + } result options] + + # Destroy the statement + + catch { + $stmt close + } + + return -options $options $result + } + + # The 'foreach' method prepares a statement, then executes it with + # a supplied set of substituents. For each row of the result, + # it sets a variable to the row and invokes a script in the caller's + # scope. + # + # Usage: + # $db foreach ?-as lists|dicts? ?-columnsVariable varName? ?--? + # varName sql ?dictionary? script + + method foreach args { + + variable ::tdbc::generalError + + # Grab keyword-value parameters + + set args [::tdbc::ParseConvenienceArgs $args[set args {}] opts] + + # Check postitional parameters + + set cmd [list [self] prepare] + if {[llength $args] == 3} { + lassign $args varname sqlcode script + } elseif {[llength $args] == 4} { + lassign $args varname sqlcode dict script + } else { + set errorcode $generalError + lappend errorcode wrongNumArgs + return -code error -errorcode $errorcode \ + "wrong # args: should be [lrange [info level 0] 0 1]\ + ?-option value?... ?--? varname sqlcode ?dictionary? script" + } + lappend cmd $sqlcode + + # Prepare the statement + + set stmt [uplevel 1 $cmd] + + # Delegate to the statement to iterate over the results + + set cmd [list $stmt foreach {*}$opts -- $varname] + if {[info exists dict]} { + lappend cmd $dict + } + lappend cmd $script + set status [catch { + uplevel 1 $cmd + } result options] + + # Destroy the statement + + catch { + $stmt close + } + + # Adjust return level in the case that the script [return]s + + if {$status == 2} { + set options [dict merge {-level 1} $options[set options {}]] + dict incr options -level + } + return -options $options $result + } + + # The 'BuildPrimaryKeysStatement' method builds a SQL statement to + # retrieve the primary keys from a database. (It executes once the + # first time the 'primaryKeys' method is executed, and retains the + # prepared statement for reuse.) + + method BuildPrimaryKeysStatement {} { + + # On some databases, CONSTRAINT_CATALOG is always NULL and + # JOINing to it fails. Check for this case and include that + # JOIN only if catalog names are supplied. + + set catalogClause {} + if {[lindex [set count [my allrows -as lists { + SELECT COUNT(*) + FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS + WHERE CONSTRAINT_CATALOG IS NOT NULL}]] 0 0] != 0} { + set catalogClause \ + {AND xtable.CONSTRAINT_CATALOG = xcolumn.CONSTRAINT_CATALOG} + } + set primaryKeysStatement [my prepare " + SELECT xtable.TABLE_SCHEMA AS \"tableSchema\", + xtable.TABLE_NAME AS \"tableName\", + xtable.CONSTRAINT_CATALOG AS \"constraintCatalog\", + xtable.CONSTRAINT_SCHEMA AS \"constraintSchema\", + xtable.CONSTRAINT_NAME AS \"constraintName\", + xcolumn.COLUMN_NAME AS \"columnName\", + xcolumn.ORDINAL_POSITION AS \"ordinalPosition\" + FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS xtable + INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE xcolumn + ON xtable.CONSTRAINT_SCHEMA = xcolumn.CONSTRAINT_SCHEMA + AND xtable.TABLE_NAME = xcolumn.TABLE_NAME + AND xtable.CONSTRAINT_NAME = xcolumn.CONSTRAINT_NAME + $catalogClause + WHERE xtable.TABLE_NAME = :tableName + AND xtable.CONSTRAINT_TYPE = 'PRIMARY KEY' + "] + } + + # The default implementation of the 'primarykeys' method uses the + # SQL INFORMATION_SCHEMA to retrieve primary key information. Databases + # that might not have INFORMATION_SCHEMA must overload this method. + + method primarykeys {tableName} { + if {![info exists primaryKeysStatement]} { + my BuildPrimaryKeysStatement + } + tailcall $primaryKeysStatement allrows [list tableName $tableName] + } + + # The 'BuildForeignKeysStatements' method builds a SQL statement to + # retrieve the foreign keys from a database. (It executes once the + # first time the 'foreignKeys' method is executed, and retains the + # prepared statements for reuse.) + + method BuildForeignKeysStatement {} { + + # On some databases, CONSTRAINT_CATALOG is always NULL and + # JOINing to it fails. Check for this case and include that + # JOIN only if catalog names are supplied. + + set catalogClause1 {} + set catalogClause2 {} + if {[lindex [set count [my allrows -as lists { + SELECT COUNT(*) + FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS + WHERE CONSTRAINT_CATALOG IS NOT NULL}]] 0 0] != 0} { + set catalogClause1 \ + {AND fkc.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG} + set catalogClause2 \ + {AND pkc.CONSTRAINT_CATALOG = rc.CONSTRAINT_CATALOG} + } + + foreach {exists1 clause1} { + 0 {} + 1 { AND pkc.TABLE_NAME = :primary} + } { + foreach {exists2 clause2} { + 0 {} + 1 { AND fkc.TABLE_NAME = :foreign} + } { + set stmt [my prepare " + SELECT rc.CONSTRAINT_CATALOG AS \"foreignConstraintCatalog\", + rc.CONSTRAINT_SCHEMA AS \"foreignConstraintSchema\", + rc.CONSTRAINT_NAME AS \"foreignConstraintName\", + rc.UNIQUE_CONSTRAINT_CATALOG + AS \"primaryConstraintCatalog\", + rc.UNIQUE_CONSTRAINT_SCHEMA AS \"primaryConstraintSchema\", + rc.UNIQUE_CONSTRAINT_NAME AS \"primaryConstraintName\", + rc.UPDATE_RULE AS \"updateAction\", + rc.DELETE_RULE AS \"deleteAction\", + pkc.TABLE_CATALOG AS \"primaryCatalog\", + pkc.TABLE_SCHEMA AS \"primarySchema\", + pkc.TABLE_NAME AS \"primaryTable\", + pkc.COLUMN_NAME AS \"primaryColumn\", + fkc.TABLE_CATALOG AS \"foreignCatalog\", + fkc.TABLE_SCHEMA AS \"foreignSchema\", + fkc.TABLE_NAME AS \"foreignTable\", + fkc.COLUMN_NAME AS \"foreignColumn\", + pkc.ORDINAL_POSITION AS \"ordinalPosition\" + FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc + INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE fkc + ON fkc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME + AND fkc.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA + $catalogClause1 + INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE pkc + ON pkc.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME + AND pkc.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA + $catalogClause2 + AND pkc.ORDINAL_POSITION = fkc.ORDINAL_POSITION + WHERE 1=1 + $clause1 + $clause2 + ORDER BY \"foreignConstraintCatalog\", \"foreignConstraintSchema\", \"foreignConstraintName\", \"ordinalPosition\" +"] + dict set foreignKeysStatement $exists1 $exists2 $stmt + } + } + } + + # The default implementation of the 'foreignkeys' method uses the + # SQL INFORMATION_SCHEMA to retrieve primary key information. Databases + # that might not have INFORMATION_SCHEMA must overload this method. + + method foreignkeys {args} { + + variable ::tdbc::generalError + + # Check arguments + + set argdict {} + if {[llength $args] % 2 != 0} { + set errorcode $generalError + lappend errorcode wrongNumArgs + return -code error -errorcode $errorcode \ + "wrong # args: should be [lrange [info level 0] 0 1]\ + ?-option value?..." + } + foreach {key value} $args { + if {$key ni {-primary -foreign}} { + set errorcode $generalError + lappend errorcode badOption + return -code error -errorcode $errorcode \ + "bad option \"$key\", must be -primary or -foreign" + } + set key [string range $key 1 end] + if {[dict exists $argdict $key]} { + set errorcode $generalError + lappend errorcode dupOption + return -code error -errorcode $errorcode \ + "duplicate option \"$key\" supplied" + } + dict set argdict $key $value + } + + # Build the statements that query foreign keys. There are four + # of them, one for each combination of whether -primary + # and -foreign is specified. + + if {![info exists foreignKeysStatement]} { + my BuildForeignKeysStatement + } + set stmt [dict get $foreignKeysStatement \ + [dict exists $argdict primary] \ + [dict exists $argdict foreign]] + tailcall $stmt allrows $argdict + } + + # Derived classes are expected to implement the 'begintransaction', + # 'commit', and 'rollback' methods. + + # Derived classes are expected to implement 'tables' and 'columns' method. + +} + +#------------------------------------------------------------------------------ +# +# Class: tdbc::statement +# +# Class that represents a SQL statement in a generic database +# +#------------------------------------------------------------------------------ + +oo::class create tdbc::statement { + + # resultSetSeq is the sequence number of the last result set created. + # resultSetClass is the name of the class that implements the 'resultset' + # API. + + variable resultSetClass resultSetSeq + + # The base class constructor accepts no arguments. It initializes + # the machinery for tracking the ownership of result sets. The derived + # constructor is expected to invoke the base constructor, and to + # set a variable 'resultSetClass' to the fully-qualified name of the + # class that represents result sets. + + constructor {} { + set resultSetSeq 0 + namespace eval ResultSet {} + } + + # The 'execute' method on a statement runs the statement with + # a particular set of substituted variables. It actually works + # by creating the result set object and letting that objects + # constructor do the work of running the statement. The creation + # is wrapped in an [uplevel] call because the substitution proces + # may need to access variables in the caller's scope. + + # WORKAROUND: Take out the '0 &&' from the next line when + # Bug 2649975 is fixed + if {0 && [package vsatisfies [package provide Tcl] 8.6]} { + method execute args { + tailcall my resultSetCreate \ + [namespace current]::ResultSet::[incr resultSetSeq] \ + [self] {*}$args + } + } else { + method execute args { + return \ + [uplevel 1 \ + [list \ + [self] resultSetCreate \ + [namespace current]::ResultSet::[incr resultSetSeq] \ + [self] {*}$args]] + } + } + + # The 'ResultSetCreate' method is expected to be a forward to the + # appropriate result set constructor. If it's missing, the driver must + # have been designed for tdbc 1.0b9 and earlier, and the 'resultSetClass' + # variable holds the class name. + + method resultSetCreate {name instance args} { + return [uplevel 1 [list $resultSetClass create \ + $name $instance {*}$args]] + } + + # The 'resultsets' method returns a list of result sets produced by + # the current statement + + method resultsets {} { + info commands ResultSet::* + } + + # The 'allrows' method executes a statement with a given set of + # substituents, and returns a list of all the rows that the statement + # returns. Optionally, it stores the names of columns in + # '-columnsvariable'. + # + # Usage: + # $statement allrows ?-as lists|dicts? ?-columnsvariable varName? ?--? + # ?dictionary? + + + method allrows args { + + variable ::tdbc::generalError + + # Grab keyword-value parameters + + set args [::tdbc::ParseConvenienceArgs $args[set args {}] opts] + + # Check postitional parameters + + set cmd [list [self] execute] + if {[llength $args] == 0} { + # do nothing + } elseif {[llength $args] == 1} { + lappend cmd [lindex $args 0] + } else { + set errorcode $generalError + lappend errorcode wrongNumArgs + return -code error -errorcode $errorcode \ + "wrong # args: should be [lrange [info level 0] 0 1]\ + ?-option value?... ?--? ?dictionary?" + } + + # Get the result set + + set resultSet [uplevel 1 $cmd] + + # Delegate to the result set's [allrows] method to accumulate + # the rows of the result. + + set cmd [list $resultSet allrows {*}$opts] + set status [catch { + uplevel 1 $cmd + } result options] + + # Destroy the result set + + catch { + rename $resultSet {} + } + + # Adjust return level in the case that the script [return]s + + if {$status == 2} { + set options [dict merge {-level 1} $options[set options {}]] + dict incr options -level + } + return -options $options $result + } + + # The 'foreach' method executes a statement with a given set of + # substituents. It runs the supplied script, substituting the supplied + # named variable. Optionally, it stores the names of columns in + # '-columnsvariable'. + # + # Usage: + # $statement foreach ?-as lists|dicts? ?-columnsvariable varName? ?--? + # variableName ?dictionary? script + + method foreach args { + + variable ::tdbc::generalError + + # Grab keyword-value parameters + + set args [::tdbc::ParseConvenienceArgs $args[set args {}] opts] + + # Check positional parameters + + set cmd [list [self] execute] + if {[llength $args] == 2} { + lassign $args varname script + } elseif {[llength $args] == 3} { + lassign $args varname dict script + lappend cmd $dict + } else { + set errorcode $generalError + lappend errorcode wrongNumArgs + return -code error -errorcode $errorcode \ + "wrong # args: should be [lrange [info level 0] 0 1]\ + ?-option value?... ?--? varName ?dictionary? script" + } + + # Get the result set + + set resultSet [uplevel 1 $cmd] + + # Delegate to the result set's [foreach] method to evaluate + # the script for each row of the result. + + set cmd [list $resultSet foreach {*}$opts -- $varname $script] + set status [catch { + uplevel 1 $cmd + } result options] + + # Destroy the result set + + catch { + rename $resultSet {} + } + + # Adjust return level in the case that the script [return]s + + if {$status == 2} { + set options [dict merge {-level 1} $options[set options {}]] + dict incr options -level + } + return -options $options $result + } + + # The 'close' method is syntactic sugar for invoking the destructor + + method close {} { + my destroy + } + + # Derived classes are expected to implement their own constructors, + # plus the following methods: + + # paramtype paramName ?direction? type ?scale ?precision?? + # Declares the type of a parameter in the statement + +} + +#------------------------------------------------------------------------------ +# +# Class: tdbc::resultset +# +# Class that represents a result set in a generic database. +# +#------------------------------------------------------------------------------ + +oo::class create tdbc::resultset { + + constructor {} { } + + # The 'allrows' method returns a list of all rows that a given + # result set returns. + + method allrows args { + + variable ::tdbc::generalError + + # Parse args + + set args [::tdbc::ParseConvenienceArgs $args[set args {}] opts] + if {[llength $args] != 0} { + set errorcode $generalError + lappend errorcode wrongNumArgs + return -code error -errorcode $errorcode \ + "wrong # args: should be [lrange [info level 0] 0 1]\ + ?-option value?... ?--? varName script" + } + + # Do -columnsvariable if requested + + if {[dict exists $opts -columnsvariable]} { + upvar 1 [dict get $opts -columnsvariable] columns + } + + # Assemble the results + + if {[dict get $opts -as] eq {lists}} { + set delegate nextlist + } else { + set delegate nextdict + } + set results [list] + while {1} { + set columns [my columns] + while {[my $delegate row]} { + lappend results $row + } + if {![my nextresults]} break + } + return $results + + } + + # The 'foreach' method runs a script on each row from a result set. + + method foreach args { + + variable ::tdbc::generalError + + # Grab keyword-value parameters + + set args [::tdbc::ParseConvenienceArgs $args[set args {}] opts] + + # Check positional parameters + + if {[llength $args] != 2} { + set errorcode $generalError + lappend errorcode wrongNumArgs + return -code error -errorcode $errorcode \ + "wrong # args: should be [lrange [info level 0] 0 1]\ + ?-option value?... ?--? varName script" + } + + # Do -columnsvariable if requested + + if {[dict exists $opts -columnsvariable]} { + upvar 1 [dict get $opts -columnsvariable] columns + } + + # Iterate over the groups of results + while {1} { + + # Export column names to caller + + set columns [my columns] + + # Iterate over the rows of one group of results + + upvar 1 [lindex $args 0] row + if {[dict get $opts -as] eq {lists}} { + set delegate nextlist + } else { + set delegate nextdict + } + while {[my $delegate row]} { + set status [catch { + uplevel 1 [lindex $args 1] + } result options] + switch -exact -- $status { + 0 - 4 { # OK or CONTINUE + } + 2 { # RETURN + set options \ + [dict merge {-level 1} $options[set options {}]] + dict incr options -level + return -options $options $result + } + 3 { # BREAK + set broken 1 + break + } + default { # ERROR or unknown status + return -options $options $result + } + } + } + + # Advance to the next group of results if there is one + + if {[info exists broken] || ![my nextresults]} { + break + } + } + + return + } + + + # The 'nextrow' method retrieves a row in the form of either + # a list or a dictionary. + + method nextrow {args} { + + variable ::tdbc::generalError + + set opts [dict create -as dicts] + set i 0 + + # Munch keyword options off the front of the command arguments + + foreach {key value} $args { + if {[string index $key 0] eq {-}} { + switch -regexp -- $key { + -as? { + dict set opts -as $value + } + -- { + incr i + break + } + default { + set errorcode $generalError + lappend errorcode badOption $key + return -code error -errorcode $errorcode \ + "bad option \"$key\":\ + must be -as or -columnsvariable" + } + } + } else { + break + } + incr i 2 + } + + set args [lrange $args $i end] + if {[llength $args] != 1} { + set errorcode $generalError + lappend errorcode wrongNumArgs + return -code error -errorcode $errorcode \ + "wrong # args: should be [lrange [info level 0] 0 1]\ + ?-option value?... ?--? varName" + } + upvar 1 [lindex $args 0] row + if {[dict get $opts -as] eq {lists}} { + set delegate nextlist + } else { + set delegate nextdict + } + return [my $delegate row] + } + + # Derived classes must override 'nextresults' if a single + # statement execution can yield multiple sets of results + + method nextresults {} { + return 0 + } + + # Derived classes must override 'outputparams' if statements can + # have output parameters. + + method outputparams {} { + return {} + } + + # The 'close' method is syntactic sugar for destroying the result set. + + method close {} { + my destroy + } + + # Derived classes are expected to implement the following methods: + + # constructor and destructor. + # Constructor accepts a statement and an optional + # a dictionary of substituted parameters and + # executes the statement against the database. If + # the dictionary is not supplied, then the default + # is to get params from variables in the caller's scope). + # columns + # -- Returns a list of the names of the columns in the result. + # nextdict variableName + # -- Stores the next row of the result set in the given variable + # in caller's scope, in the form of a dictionary that maps + # column names to values. + # nextlist variableName + # -- Stores the next row of the result set in the given variable + # in caller's scope, in the form of a list of cells. + # rowcount + # -- Returns a count of rows affected by the statement, or -1 + # if the count of rows has not been determined. + +} \ No newline at end of file diff --git a/lib/tdbc1.1.7/tdbcConfig.sh b/lib/tdbc1.1.7/tdbcConfig.sh new file mode 100644 index 0000000000000000000000000000000000000000..27438be4db40939ea422e358f9b5ae13d8729dc0 --- /dev/null +++ b/lib/tdbc1.1.7/tdbcConfig.sh @@ -0,0 +1,81 @@ +# tdbcConfig.sh -- +# +# This shell script (for sh) is generated automatically by TDBC's configure +# script. It will create shell variables for most of the configuration options +# discovered by the configure script. This script is intended to be included +# by the configure scripts for TDBC extensions so that they don't have to +# figure this all out for themselves. +# +# The information in this file is specific to a single platform. +# +# RCS: @(#) $Id$ + +# TDBC's version number +tdbc_VERSION=1.1.7 +TDBC_VERSION=1.1.7 + +# Name of the TDBC library - may be either a static or shared library +tdbc_LIB_FILE=libtdbc1.1.7.so +TDBC_LIB_FILE=libtdbc1.1.7.so + +# String to pass to the linker to pick up the TDBC library from its build dir +tdbc_BUILD_LIB_SPEC="-L/croot/tk_1748849386456/work/tcl8.6.14/unix/pkgs/tdbc1.1.7 -ltdbc1.1.7" +TDBC_BUILD_LIB_SPEC="-L/croot/tk_1748849386456/work/tcl8.6.14/unix/pkgs/tdbc1.1.7 -ltdbc1.1.7" + +# String to pass to the linker to pick up the TDBC library from its installed +# dir. +tdbc_LIB_SPEC="-L/home/aioz-nghiale/anaconda3/envs/testing_softzoo_pointe/lib/tdbc1.1.7 -ltdbc1.1.7" +TDBC_LIB_SPEC="-L/home/aioz-nghiale/anaconda3/envs/testing_softzoo_pointe/lib/tdbc1.1.7 -ltdbc1.1.7" + +# Name of the TBDC stub library +tdbc_STUB_LIB_FILE="libtdbcstub1.1.7.a" +TDBC_STUB_LIB_FILE="libtdbcstub1.1.7.a" + +# String to pass to the linker to pick up the TDBC stub library from its +# build directory +tdbc_BUILD_STUB_LIB_SPEC="-L/croot/tk_1748849386456/work/tcl8.6.14/unix/pkgs/tdbc1.1.7 -ltdbcstub1.1.7" +TDBC_BUILD_STUB_LIB_SPEC="-L/croot/tk_1748849386456/work/tcl8.6.14/unix/pkgs/tdbc1.1.7 -ltdbcstub1.1.7" + +# String to pass to the linker to pick up the TDBC stub library from its +# installed directory +tdbc_STUB_LIB_SPEC="-L/home/aioz-nghiale/anaconda3/envs/testing_softzoo_pointe/lib/tdbc1.1.7 -ltdbcstub1.1.7" +TDBC_STUB_LIB_SPEC="-L/home/aioz-nghiale/anaconda3/envs/testing_softzoo_pointe/lib/tdbc1.1.7 -ltdbcstub1.1.7" + +# Path name of the TDBC stub library in its build directory +tdbc_BUILD_STUB_LIB_PATH="/croot/tk_1748849386456/work/tcl8.6.14/unix/pkgs/tdbc1.1.7/libtdbcstub1.1.7.a" +TDBC_BUILD_STUB_LIB_PATH="/croot/tk_1748849386456/work/tcl8.6.14/unix/pkgs/tdbc1.1.7/libtdbcstub1.1.7.a" + +# Path name of the TDBC stub library in its installed directory +tdbc_STUB_LIB_PATH="/home/aioz-nghiale/anaconda3/envs/testing_softzoo_pointe/lib/tdbc1.1.7/libtdbcstub1.1.7.a" +TDBC_STUB_LIB_PATH="/home/aioz-nghiale/anaconda3/envs/testing_softzoo_pointe/lib/tdbc1.1.7/libtdbcstub1.1.7.a" + +# Location of the top-level source directories from which TDBC was built. +# This is the directory that contains doc/, generic/ and so on. If TDBC +# was compiled in a directory other than the source directory, this still +# points to the location of the sources, not the location where TDBC was +# compiled. +tdbc_SRC_DIR="/croot/tk_1748849386456/work/tcl8.6.14/pkgs/tdbc1.1.7" +TDBC_SRC_DIR="/croot/tk_1748849386456/work/tcl8.6.14/pkgs/tdbc1.1.7" + +# String to pass to the compiler so that an extension can find installed TDBC +# headers +tdbc_INCLUDE_SPEC="-I/home/aioz-nghiale/anaconda3/envs/testing_softzoo_pointe/include" +TDBC_INCLUDE_SPEC="-I/home/aioz-nghiale/anaconda3/envs/testing_softzoo_pointe/include" + +# String to pass to the compiler so that an extension can find TDBC headers +# in the source directory +tdbc_BUILD_INCLUDE_SPEC="-I/croot/tk_1748849386456/work/tcl8.6.14/pkgs/tdbc1.1.7/generic" +TDBC_BUILD_INCLUDE_SPEC="-I/croot/tk_1748849386456/work/tcl8.6.14/pkgs/tdbc1.1.7/generic" + +# Path name where .tcl files in the tdbc package appear at run time. +tdbc_LIBRARY_PATH="/home/aioz-nghiale/anaconda3/envs/testing_softzoo_pointe/lib/tdbc1.1.7" +TDBC_LIBRARY_PATH="/home/aioz-nghiale/anaconda3/envs/testing_softzoo_pointe/lib/tdbc1.1.7" + +# Path name where .tcl files in the tdbc package appear at build time. +tdbc_BUILD_LIBRARY_PATH="/croot/tk_1748849386456/work/tcl8.6.14/pkgs/tdbc1.1.7/library" +TDBC_BUILD_LIBRARY_PATH="/croot/tk_1748849386456/work/tcl8.6.14/pkgs/tdbc1.1.7/library" + +# Additional flags that must be passed to the C compiler to use tdbc +tdbc_CFLAGS= +TDBC_CFLAGS= + diff --git a/lib/tdbcmysql1.1.7/libtdbcmysql1.1.7.so b/lib/tdbcmysql1.1.7/libtdbcmysql1.1.7.so new file mode 100644 index 0000000000000000000000000000000000000000..01f56bd56b12e82d27e2ffaa93f1005e8543a063 Binary files /dev/null and b/lib/tdbcmysql1.1.7/libtdbcmysql1.1.7.so differ diff --git a/lib/tdbcmysql1.1.7/pkgIndex.tcl b/lib/tdbcmysql1.1.7/pkgIndex.tcl new file mode 100644 index 0000000000000000000000000000000000000000..65b62e249c967fd77b5e9fd2e7d2b68c417fc34a --- /dev/null +++ b/lib/tdbcmysql1.1.7/pkgIndex.tcl @@ -0,0 +1,14 @@ +# Index file to load the TDBC MySQL package. + +if {![package vsatisfies [package provide Tcl] 8.6-]} { + return +} +if {[package vsatisfies [package provide Tcl] 9.0-]} { + package ifneeded tdbc::mysql 1.1.7 \ + "[list source [file join $dir tdbcmysql.tcl]]\;\ + [list load [file join $dir libtcl9tdbcmysql1.1.7.so] [string totitle tdbcmysql]]" +} else { + package ifneeded tdbc::mysql 1.1.7 \ + "[list source [file join $dir tdbcmysql.tcl]]\;\ + [list load [file join $dir libtdbcmysql1.1.7.so] [string totitle tdbcmysql]]" +} diff --git a/lib/tdbcmysql1.1.7/tdbcmysql.tcl b/lib/tdbcmysql1.1.7/tdbcmysql.tcl new file mode 100644 index 0000000000000000000000000000000000000000..caa334c470df3aa0b983c92cea2235a13946a5ec --- /dev/null +++ b/lib/tdbcmysql1.1.7/tdbcmysql.tcl @@ -0,0 +1,193 @@ +# tdbcmysql.tcl -- +# +# Class definitions and Tcl-level methods for the tdbc::mysql bridge. +# +# Copyright (c) 2008 by Kevin B. Kenny +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# RCS: @(#) $Id: tdbcmysql.tcl,v 1.47 2008/02/27 02:08:27 kennykb Exp $ +# +#------------------------------------------------------------------------------ + +package require tdbc + +::namespace eval ::tdbc::mysql { + + namespace export connection datasources drivers + +} + +#------------------------------------------------------------------------------ +# +# tdbc::mysql::connection -- +# +# Class representing a connection to a database through MYSQL. +# +#------------------------------------------------------------------------------- + +::oo::class create ::tdbc::mysql::connection { + + superclass ::tdbc::connection + + # The constructor is written in C. It takes alternating keywords + # and values pairs as its argumenta. (See the manual page for the + # available options.) + + variable foreignKeysStatement + + # The 'statementCreate' method delegates to the constructor of the + # statement class + + forward statementCreate ::tdbc::mysql::statement create + + # The 'columns' method returns a dictionary describing the tables + # in the database + + method columns {table {pattern %}} { + + # To return correct lengths of CHARACTER and BINARY columns, + # we need to know the maximum lengths of characters in each + # collation. We cache this information only once, on the first + # call to 'columns'. + + if {[my NeedCollationInfo]} { + my SetCollationInfo {*}[my allrows -as lists { + SELECT coll.id, cs.maxlen + FROM INFORMATION_SCHEMA.COLLATIONS coll, + INFORMATION_SCHEMA.CHARACTER_SETS cs + WHERE cs.CHARACTER_SET_NAME = coll.CHARACTER_SET_NAME + ORDER BY coll.id DESC + }] + } + + return [my Columns $table $pattern] + } + + # The 'preparecall' method gives a portable interface to prepare + # calls to stored procedures. It delegates to 'prepare' to do the + # actual work. + + method preparecall {call} { + regexp {^[[:space:]]*(?:([A-Za-z_][A-Za-z_0-9]*)[[:space:]]*=)?(.*)} \ + $call -> varName rest + if {$varName eq {}} { + my prepare "CALL $rest" + } else { + my prepare \\{:$varName=$rest\\} + } + } + + # The 'init', 'begintransaction', 'commit, 'rollback', 'tables' + # 'NeedCollationInfo', 'SetCollationInfo', and 'Columns' methods + # are implemented in C. + + # The 'BuildForeignKeysStatements' method builds a SQL statement to + # retrieve the foreign keys from a database. (It executes once the + # first time the 'foreignKeys' method is executed, and retains the + # prepared statements for reuse.) It is slightly nonstandard because + # MYSQL doesn't name the PRIMARY constraints uniquely. + + method BuildForeignKeysStatement {} { + + foreach {exists1 clause1} { + 0 {} + 1 { AND fkc.REFERENCED_TABLE_NAME = :primary} + } { + foreach {exists2 clause2} { + 0 {} + 1 { AND fkc.TABLE_NAME = :foreign} + } { + set stmt [my prepare " + SELECT rc.CONSTRAINT_SCHEMA AS \"foreignConstraintSchema\", + rc.CONSTRAINT_NAME AS \"foreignConstraintName\", + rc.UPDATE_RULE AS \"updateAction\", + rc.DELETE_RULE AS \"deleteAction\", + fkc.REFERENCED_TABLE_SCHEMA AS \"primarySchema\", + fkc.REFERENCED_TABLE_NAME AS \"primaryTable\", + fkc.REFERENCED_COLUMN_NAME AS \"primaryColumn\", + fkc.TABLE_SCHEMA AS \"foreignSchema\", + fkc.TABLE_NAME AS \"foreignTable\", + fkc.COLUMN_NAME AS \"foreignColumn\", + fkc.ORDINAL_POSITION AS \"ordinalPosition\" + FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc + INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE fkc + ON fkc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME + AND fkc.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA + WHERE 1=1 + $clause1 + $clause2 +"] + dict set foreignKeysStatement $exists1 $exists2 $stmt + } + } + } +} + +#------------------------------------------------------------------------------ +# +# tdbc::mysql::statement -- +# +# The class 'tdbc::mysql::statement' models one statement against a +# database accessed through an MYSQL connection +# +#------------------------------------------------------------------------------ + +::oo::class create ::tdbc::mysql::statement { + + superclass ::tdbc::statement + + # The 'resultSetCreate' method forwards to the constructor of the + # result set. + + forward resultSetCreate ::tdbc::mysql::resultset create + + # Methods implemented in C: + # + # constructor connection SQLCode + # The constructor accepts the handle to the connection and the SQL code + # for the statement to prepare. It creates a subordinate namespace to + # hold the statement's active result sets, and then delegates to the + # 'init' method, written in C, to do the actual work of preparing the + # statement. + # params + # Returns descriptions of the parameters of a statement. + # paramtype paramname ?direction? type ?precision ?scale?? + # Declares the type of a parameter in the statement + +} + +#------------------------------------------------------------------------------ +# +# tdbc::mysql::resultset -- +# +# The class 'tdbc::mysql::resultset' models the result set that is +# produced by executing a statement against an MYSQL database. +# +#------------------------------------------------------------------------------ + +::oo::class create ::tdbc::mysql::resultset { + + superclass ::tdbc::resultset + + # Methods implemented in C include: + + # constructor statement ?dictionary? + # -- Executes the statement against the database, optionally providing + # a dictionary of substituted parameters (default is to get params + # from variables in the caller's scope). + # columns + # -- Returns a list of the names of the columns in the result. + # nextdict + # -- Stores the next row of the result set in the given variable in + # the caller's scope as a dictionary whose keys are + # column names and whose values are column values, or else + # as a list of cells. + # nextlist + # -- Stores the next row of the result set in the given variable in + # the caller's scope as a list of cells. + # rowcount + # -- Returns a count of rows affected by the statement, or -1 + # if the count of rows has not been determined. + +} diff --git a/lib/tdbcodbc1.1.7/libtdbcodbc1.1.7.so b/lib/tdbcodbc1.1.7/libtdbcodbc1.1.7.so new file mode 100644 index 0000000000000000000000000000000000000000..d993391c40cdc052d5c82ecaa7ae6e9fddef16da Binary files /dev/null and b/lib/tdbcodbc1.1.7/libtdbcodbc1.1.7.so differ diff --git a/lib/tdbcodbc1.1.7/pkgIndex.tcl b/lib/tdbcodbc1.1.7/pkgIndex.tcl new file mode 100644 index 0000000000000000000000000000000000000000..f5d50949d8f3d4e4fd6e79ecb038d6e4d62fbc5f --- /dev/null +++ b/lib/tdbcodbc1.1.7/pkgIndex.tcl @@ -0,0 +1,14 @@ +# Index file to load the TDBC ODBC package. + +if {![package vsatisfies [package provide Tcl] 8.6-]} { + return +} +if {[package vsatisfies [package provide Tcl] 9.0-]} { + package ifneeded tdbc::odbc 1.1.7 \ + "[list source [file join $dir tdbcodbc.tcl]]\;\ + [list load [file join $dir libtcl9tdbcodbc1.1.7.so] [string totitle tdbcodbc]]" +} else { + package ifneeded tdbc::odbc 1.1.7 \ + "[list source [file join $dir tdbcodbc.tcl]]\;\ + [list load [file join $dir libtdbcodbc1.1.7.so] [string totitle tdbcodbc]]" +} diff --git a/lib/tdbcodbc1.1.7/tdbcodbc.tcl b/lib/tdbcodbc1.1.7/tdbcodbc.tcl new file mode 100644 index 0000000000000000000000000000000000000000..7bc68ba32343029fdc90be290c857e2f420351c5 --- /dev/null +++ b/lib/tdbcodbc1.1.7/tdbcodbc.tcl @@ -0,0 +1,554 @@ +# tdbcodbc.tcl -- +# +# Class definitions and Tcl-level methods for the tdbc::odbc bridge. +# +# Copyright (c) 2008 by Kevin B. Kenny +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# RCS: @(#) $Id: tdbcodbc.tcl,v 1.47 2008/02/27 02:08:27 kennykb Exp $ +# +#------------------------------------------------------------------------------ + +package require tdbc + +::namespace eval ::tdbc::odbc { + + namespace export connection datasources drivers + + # Data types that are predefined in ODBC + + variable sqltypes [dict create \ + 1 char \ + 2 numeric \ + 3 decimal \ + 4 integer \ + 5 smallint \ + 6 float \ + 7 real \ + 8 double \ + 9 datetime \ + 12 varchar \ + 91 date \ + 92 time \ + 93 timestamp \ + -1 longvarchar \ + -2 binary \ + -3 varbinary \ + -4 longvarbinary \ + -5 bigint \ + -6 tinyint \ + -7 bit \ + -8 wchar \ + -9 wvarchar \ + -10 wlongvarchar \ + -11 guid] +} + +#------------------------------------------------------------------------------ +# +# tdbc::odbc::connection -- +# +# Class representing a connection to a database through ODBC. +# +#------------------------------------------------------------------------------- + +::oo::class create ::tdbc::odbc::connection { + + superclass ::tdbc::connection + + variable statementSeq typemap + + # The constructor is written in C. It takes the connection string + # as its argument It sets up a namespace to hold the statements + # associated with the connection, and then delegates to the 'init' + # method (written in C) to do the actual work of attaching to the + # database. When that comes back, it sets up a statement to query + # the support types, makes a dictionary to enumerate them, and + # calls back to set a flag if WVARCHAR is seen (If WVARCHAR is + # seen, the database supports Unicode.) + + # The 'statementCreate' method forwards to the constructor of the + # statement class + + forward statementCreate ::tdbc::odbc::statement create + + # The 'tables' method returns a dictionary describing the tables + # in the database + + method tables {{pattern %}} { + set stmt [::tdbc::odbc::tablesStatement create \ + Stmt::[incr statementSeq] [self] $pattern] + set status [catch { + set retval {} + $stmt foreach -as dicts row { + if {[dict exists $row TABLE_NAME]} { + dict set retval [dict get $row TABLE_NAME] $row + } + } + set retval + } result options] + catch {rename $stmt {}} + return -level 0 -options $options $result + } + + # The 'columns' method returns a dictionary describing the tables + # in the database + + method columns {table {pattern %}} { + # Make sure that the type map is initialized + my typemap + + # Query the columns from the database + + set stmt [::tdbc::odbc::columnsStatement create \ + Stmt::[incr statementSeq] [self] $table $pattern] + set status [catch { + set retval {} + $stmt foreach -as dicts origrow { + + # Map the type, precision, scale and nullable indicators + # to tdbc's notation + + set row {} + dict for {key value} $origrow { + dict set row [string tolower $key] $value + } + if {[dict exists $row column_name]} { + if {[dict exists $typemap \ + [dict get $row data_type]]} { + dict set row type \ + [dict get $typemap \ + [dict get $row data_type]] + } else { + dict set row type [dict get $row type_name] + } + if {[dict exists $row column_size]} { + dict set row precision \ + [dict get $row column_size] + } + if {[dict exists $row decimal_digits]} { + dict set row scale \ + [dict get $row decimal_digits] + } + if {![dict exists $row nullable]} { + dict set row nullable \ + [expr {!![string trim [dict get $row is_nullable]]}] + } + dict set retval [dict get $row column_name] $row + } + } + set retval + } result options] + catch {rename $stmt {}} + return -level 0 -options $options $result + } + + # The 'primarykeys' method returns a dictionary describing the primary + # keys of a table + + method primarykeys {tableName} { + set stmt [::tdbc::odbc::primarykeysStatement create \ + Stmt::[incr statementSeq] [self] $tableName] + set status [catch { + set retval {} + $stmt foreach -as dicts row { + foreach {odbcKey tdbcKey} { + TABLE_CAT tableCatalog + TABLE_SCHEM tableSchema + TABLE_NAME tableName + COLUMN_NAME columnName + KEY_SEQ ordinalPosition + PK_NAME constraintName + } { + if {[dict exists $row $odbcKey]} { + dict set row $tdbcKey [dict get $row $odbcKey] + dict unset row $odbcKey + } + } + lappend retval $row + } + set retval + } result options] + catch {rename $stmt {}} + return -level 0 -options $options $result + } + + # The 'foreignkeys' method returns a dictionary describing the foreign + # keys of a table + + method foreignkeys {args} { + set stmt [::tdbc::odbc::foreignkeysStatement create \ + Stmt::[incr statementSeq] [self] {*}$args] + set status [catch { + set fkseq 0 + set retval {} + $stmt foreach -as dicts row { + foreach {odbcKey tdbcKey} { + PKTABLE_CAT primaryCatalog + PKTABLE_SCHEM primarySchema + PKTABLE_NAME primaryTable + PKCOLUMN_NAME primaryColumn + FKTABLE_CAT foreignCatalog + FKTABLE_SCHEM foreignSchema + FKTABLE_NAME foreignTable + FKCOLUMN_NAME foreignColumn + UPDATE_RULE updateRule + DELETE_RULE deleteRule + DEFERRABILITY deferrable + KEY_SEQ ordinalPosition + FK_NAME foreignConstraintName + } { + if {[dict exists $row $odbcKey]} { + dict set row $tdbcKey [dict get $row $odbcKey] + dict unset row $odbcKey + } + } + # Horrible kludge: If the driver doesn't report FK_NAME, + # make one up. + if {![dict exists $row foreignConstraintName]} { + if {![dict exists $row ordinalPosition] + || [dict get $row ordinalPosition] == 1} { + set fkname ?[dict get $row foreignTable]?[incr fkseq] + } + dict set row foreignConstraintName $fkname + } + lappend retval $row + } + set retval + } result options] + catch {rename $stmt {}} + return -level 0 -options $options $result + } + + # The 'evaldirect' evaluates driver-native SQL code without preparing it, + # and returns a list of dicts (similar to '$connection allrows -as dicts'). + + method evaldirect {sqlStatement} { + set stmt [::tdbc::odbc::evaldirectStatement create \ + Stmt::[incr statementSeq] [self] $sqlStatement] + set status [catch { + $stmt allrows -as dicts + } result options] + catch {rename $stmt {}} + return -level 0 -options $options $result + } + + # The 'prepareCall' method gives a portable interface to prepare + # calls to stored procedures. It delegates to 'prepare' to do the + # actual work. + + method preparecall {call} { + + regexp {^[[:space:]]*(?:([A-Za-z_][A-Za-z_0-9]*)[[:space:]]*=)?(.*)} \ + $call -> varName rest + if {$varName eq {}} { + my prepare \\{CALL $rest\\} + } else { + my prepare \\{:$varName=CALL $rest\\} + } + + if 0 { + # Kevin thinks this is going to be + + if {![regexp -expanded { + ^\s* # leading whitespace + (?::([[:alpha:]_][[:alnum:]_]*)\s*=\s*) # possible variable name + (?:(?:([[:alpha:]_][[:alnum:]_]*)\s*[.]\s*)? # catalog + ([[:alpha:]_][[:alnum:]_]*)\s*[.]\s*)? # schema + ([[:alpha:]_][[:alnum:]_]*)\s* # procedure + (.*)$ # argument list + } $call -> varName catalog schema procedure arglist]} { + return -code error \ + -errorCode [list TDBC \ + SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION \ + 42000 ODBC -1] \ + "Syntax error in stored procedure call" + } else { + my PrepareCall $varName $catalog $schema $procedure $arglist + } + + # at least if making all parameters 'inout' doesn't work. + + } + + } + + # The 'typemap' method returns the type map + + method typemap {} { + if {![info exists typemap]} { + set typemap $::tdbc::odbc::sqltypes + set typesStmt [tdbc::odbc::typesStatement new [self]] + $typesStmt foreach row { + set typeNum [dict get $row DATA_TYPE] + if {![dict exists $typemap $typeNum]} { + dict set typemap $typeNum [string tolower \ + [dict get $row TYPE_NAME]] + } + switch -exact -- $typeNum { + -9 { + [self] HasWvarchar 1 + } + -5 { + [self] HasBigint 1 + } + } + } + rename $typesStmt {} + } + return $typemap + } + + # The 'begintransaction', 'commit' and 'rollback' methods are + # implemented in C. + +} + +#------------------------------------------------------------------------------- +# +# tdbc::odbc::statement -- +# +# The class 'tdbc::odbc::statement' models one statement against a +# database accessed through an ODBC connection +# +#------------------------------------------------------------------------------- + +::oo::class create ::tdbc::odbc::statement { + + superclass ::tdbc::statement + + # The constructor is implemented in C. It accepts the handle to + # the connection and the SQL code for the statement to prepare. + # It creates a subordinate namespace to hold the statement's + # active result sets, and then delegates to the 'init' method, + # written in C, to do the actual work of preparing the statement. + + # The 'resultSetCreate' method forwards to the result set constructor + + forward resultSetCreate ::tdbc::odbc::resultset create + + # The 'params' method describes the parameters to the statement + + method params {} { + set typemap [[my connection] typemap] + set result {} + foreach {name flags typeNum precision scale nullable} [my ParamList] { + set lst [dict create \ + name $name \ + direction [lindex {unknown in out inout} \ + [expr {($flags & 0x06) >> 1}]] \ + type [dict get $typemap $typeNum] \ + precision $precision \ + scale $scale] + if {$nullable in {0 1}} { + dict set list nullable $nullable + } + dict set result $name $lst + } + return $result + } + + # Methods implemented in C: + # init statement ?dictionary? + # Does the heavy lifting for the constructor + # connection + # Returns the connection handle to which this statement belongs + # paramtype paramname ?direction? type ?precision ?scale?? + # Declares the type of a parameter in the statement + +} + +#------------------------------------------------------------------------------ +# +# tdbc::odbc::tablesStatement -- +# +# The class 'tdbc::odbc::tablesStatement' represents the special +# statement that queries the tables in a database through an ODBC +# connection. +# +#------------------------------------------------------------------------------ + +oo::class create ::tdbc::odbc::tablesStatement { + + superclass ::tdbc::statement + + # The constructor is written in C. It accepts the handle to the + # connection and a pattern to match table names. It works in all + # ways like the constructor of the 'statement' class except that + # its 'init' method sets up to enumerate tables and not run a SQL + # query. + + # The 'resultSetCreate' method forwards to the result set constructor + + forward resultSetCreate ::tdbc::odbc::resultset create + +} + +#------------------------------------------------------------------------------ +# +# tdbc::odbc::columnsStatement -- +# +# The class 'tdbc::odbc::tablesStatement' represents the special +# statement that queries the columns of a table or view +# in a database through an ODBC connection. +# +#------------------------------------------------------------------------------ + +oo::class create ::tdbc::odbc::columnsStatement { + + superclass ::tdbc::statement + + # The constructor is written in C. It accepts the handle to the + # connection, a table name, and a pattern to match column + # names. It works in all ways like the constructor of the + # 'statement' class except that its 'init' method sets up to + # enumerate tables and not run a SQL query. + + # The 'resultSetCreate' class forwards to the constructor of the + # result set + + forward resultSetCreate ::tdbc::odbc::resultset create + +} + +#------------------------------------------------------------------------------ +# +# tdbc::odbc::primarykeysStatement -- +# +# The class 'tdbc::odbc::primarykeysStatement' represents the special +# statement that queries the primary keys on a table through an ODBC +# connection. +# +#------------------------------------------------------------------------------ + +oo::class create ::tdbc::odbc::primarykeysStatement { + + superclass ::tdbc::statement + + # The constructor is written in C. It accepts the handle to the + # connection and a table name. It works in all + # ways like the constructor of the 'statement' class except that + # its 'init' method sets up to enumerate primary keys and not run a SQL + # query. + + # The 'resultSetCreate' method forwards to the result set constructor + + forward resultSetCreate ::tdbc::odbc::resultset create + +} + +#------------------------------------------------------------------------------ +# +# tdbc::odbc::foreignkeysStatement -- +# +# The class 'tdbc::odbc::foreignkeysStatement' represents the special +# statement that queries the foreign keys on a table through an ODBC +# connection. +# +#------------------------------------------------------------------------------ + +oo::class create ::tdbc::odbc::foreignkeysStatement { + + superclass ::tdbc::statement + + # The constructor is written in C. It accepts the handle to the + # connection and the -primary and -foreign options. It works in all + # ways like the constructor of the 'statement' class except that + # its 'init' method sets up to enumerate foreign keys and not run a SQL + # query. + + # The 'resultSetCreate' method forwards to the result set constructor + + forward resultSetCreate ::tdbc::odbc::resultset create + +} + +#------------------------------------------------------------------------------ +# +# tdbc::odbc::evaldirectStatement -- +# +# The class 'tdbc::odbc::evaldirectStatement' provides a mechanism to +# execute driver-name SQL code through an ODBC connection. The SQL code +# is not prepared and no tokenization or variable substitution is done. +# +#------------------------------------------------------------------------------ + +oo::class create ::tdbc::odbc::evaldirectStatement { + + superclass ::tdbc::statement + + # The constructor is written in C. It accepts the handle to the + # connection and a SQL statement. It works in all + # ways like the constructor of the 'statement' class except that + # its 'init' method does not tokenize or prepare the SQL statement, and + # sets up to run the SQL query without performing variable substitution. + + # The 'resultSetCreate' method forwards to the result set constructor + + forward resultSetCreate ::tdbc::odbc::resultset create + +} + +#------------------------------------------------------------------------------ +# +# tdbc::odbc::typesStatement -- +# +# The class 'tdbc::odbc::typesStatement' represents the special +# statement that queries the types available in a database through +# an ODBC connection. +# +#------------------------------------------------------------------------------ + + +oo::class create ::tdbc::odbc::typesStatement { + + superclass ::tdbc::statement + + # The constructor is written in C. It accepts the handle to the + # connection, and (optionally) a data type number. It works in all + # ways like the constructor of the 'statement' class except that + # its 'init' method sets up to enumerate types and not run a SQL + # query. + + # The 'resultSetCreate' method forwards to the constructor of result sets + + forward resultSetCreate ::tdbc::odbc::resultset create + + # The C code contains a variant implementation of the 'init' method. + +} + +#------------------------------------------------------------------------------ +# +# tdbc::odbc::resultset -- +# +# The class 'tdbc::odbc::resultset' models the result set that is +# produced by executing a statement against an ODBC database. +# +#------------------------------------------------------------------------------ + +::oo::class create ::tdbc::odbc::resultset { + + superclass ::tdbc::resultset + + # Methods implemented in C include: + + # constructor statement ?dictionary? + # -- Executes the statement against the database, optionally providing + # a dictionary of substituted parameters (default is to get params + # from variables in the caller's scope). + # columns + # -- Returns a list of the names of the columns in the result. + # nextdict + # -- Stores the next row of the result set in the given variable in + # the caller's scope as a dictionary whose keys are + # column names and whose values are column values. + # nextlist + # -- Stores the next row of the result set in the given variable in + # the caller's scope as a list of cells. + # rowcount + # -- Returns a count of rows affected by the statement, or -1 + # if the count of rows has not been determined. + +} diff --git a/lib/tdbcpostgres1.1.7/libtdbcpostgres1.1.7.so b/lib/tdbcpostgres1.1.7/libtdbcpostgres1.1.7.so new file mode 100644 index 0000000000000000000000000000000000000000..6d6e63100cd022a80e072f030f60a30f3c12f27b Binary files /dev/null and b/lib/tdbcpostgres1.1.7/libtdbcpostgres1.1.7.so differ diff --git a/lib/tdbcpostgres1.1.7/pkgIndex.tcl b/lib/tdbcpostgres1.1.7/pkgIndex.tcl new file mode 100644 index 0000000000000000000000000000000000000000..dbf426c1ff1c9a7ef3c226d474761da10a1ebad6 --- /dev/null +++ b/lib/tdbcpostgres1.1.7/pkgIndex.tcl @@ -0,0 +1,14 @@ +# Index file to load the TDBC Postgres package. + +if {![package vsatisfies [package provide Tcl] 8.6-]} { + return +} +if {[package vsatisfies [package provide Tcl] 9.0-]} { + package ifneeded tdbc::postgres 1.1.7 \ + "[list source [file join $dir tdbcpostgres.tcl]]\;\ + [list load [file join $dir libtcl9tdbcpostgres1.1.7.so] [string totitle tdbcpostgres]]" +} else { + package ifneeded tdbc::postgres 1.1.7 \ + "[list source [file join $dir tdbcpostgres.tcl]]\;\ + [list load [file join $dir libtdbcpostgres1.1.7.so] [string totitle tdbcpostgres]]" +} diff --git a/lib/tdbcpostgres1.1.7/tdbcpostgres.tcl b/lib/tdbcpostgres1.1.7/tdbcpostgres.tcl new file mode 100644 index 0000000000000000000000000000000000000000..c5c4ef0f85aa0e08aabd7e901c3276eb5421ffca --- /dev/null +++ b/lib/tdbcpostgres1.1.7/tdbcpostgres.tcl @@ -0,0 +1,135 @@ +# tdbcpostgres.tcl -- +# +# Class definitions and Tcl-level methods for the tdbc::postgres bridge. +# +# Copyright (c) 2009 by Slawomir Cygan +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +#------------------------------------------------------------------------------ + +package require tdbc + +::namespace eval ::tdbc::mypostgres { + + namespace export connection datasources drivers + +} + +#------------------------------------------------------------------------------ +# +# tdbc::postgres::connection -- +# +# Class representing a connection to a Postgres database. +# +#------------------------------------------------------------------------------- + +::oo::class create ::tdbc::postgres::connection { + + superclass ::tdbc::connection + + # The constructor is written in C. It takes alternating keywords + # and values pairs as its arguments. (See the manual page for the + # available options.) + + # The 'statementCreate' method delegates to the constructor of the + # statement class + + forward statementCreate ::tdbc::postgres::statement create + + + # The 'prepareCall' method gives a portable interface to prepare + # calls to stored procedures. It delegates to 'prepare' to do the + # actual work. + + method preparecall {call} { + regexp {^[[:space:]]*(?:([A-Za-z_][A-Za-z_0-9]*)[[:space:]]*=)?(.*)} \ + $call -> varName rest + if {$varName eq {}} { + my prepare \\{$rest\\} + } else { + my prepare \\{:$varName=$rest\\} + } + } + + # The 'init', 'begintransaction', 'commit, 'rollback', 'tables' + # and 'columns' methods are implemented in C. + +} + +#------------------------------------------------------------------------------ +# +# tdbc::postgres::statement -- +# +# The class 'tdbc::postgres::statement' models one statement against a +# database accessed through a Postgres connection +# +#------------------------------------------------------------------------------ + +::oo::class create ::tdbc::postgres::statement { + + superclass ::tdbc::statement + + # The 'resultSetCreate' method forwards to the constructor of the + # result set. + + forward resultSetCreate ::tdbc::postgres::resultset create + + # Methods implemented in C: + # + # constructor connection SQLCode + # The constructor accepts the handle to the connection and the SQL code + # for the statement to prepare. It creates a subordinate namespace to + # hold the statement's active result sets, and then delegates to the + # 'init' method, written in C, to do the actual work of preparing the + # statement. + # params + # Returns descriptions of the parameters of a statement. + # paramtype paramname ?direction? type ?precision ?scale?? + # Declares the type of a parameter in the statement + +} + +#------------------------------------------------------------------------------ +# +# tdbc::postgres::resultset -- +# +# The class 'tdbc::postgres::resultset' models the result set that is +# produced by executing a statement against a Postgres database. +# +#------------------------------------------------------------------------------ + +::oo::class create ::tdbc::postgres::resultset { + + superclass ::tdbc::resultset + + # The 'nextresults' method is stubbed out; tdbcpostgres does not + # allow a single call to return multiple results. + + method nextresults {} { + while {[my nextdict rubbish]} {} + return 0 + } + + # Methods implemented in C include: + + # constructor statement ?dictionary? + # -- Executes the statement against the database, optionally providing + # a dictionary of substituted parameters (default is to get params + # from variables in the caller's scope). + # columns + # -- Returns a list of the names of the columns in the result. + # nextdict + # -- Stores the next row of the result set in the given variable in + # the caller's scope as a dictionary whose keys are + # column names and whose values are column values, or else + # as a list of cells. + # nextlist + # -- Stores the next row of the result set in the given variable in + # the caller's scope as a list of cells. + # rowcount + # -- Returns a count of rows affected by the statement, or -1 + # if the count of rows has not been determined. + +} diff --git a/lib/thread2.8.9/libthread2.8.9.so b/lib/thread2.8.9/libthread2.8.9.so new file mode 100644 index 0000000000000000000000000000000000000000..3f90710db8246cda01398bacac1ae472c5220c16 Binary files /dev/null and b/lib/thread2.8.9/libthread2.8.9.so differ diff --git a/lib/thread2.8.9/pkgIndex.tcl b/lib/thread2.8.9/pkgIndex.tcl new file mode 100644 index 0000000000000000000000000000000000000000..26d39bbf735cd1e36aefffe3d7fe7c1d105f2a62 --- /dev/null +++ b/lib/thread2.8.9/pkgIndex.tcl @@ -0,0 +1,68 @@ +# -*- tcl -*- +# Tcl package index file, version 1.1 +# + +if {![package vsatisfies [package provide Tcl] 8.4]} { + # Pre-8.4 Tcl interps we dont support at all. Bye! + # 9.0+ Tcl interps are only supported on 32-bit platforms. + if {![package vsatisfies [package provide Tcl] 9.0] + || ($::tcl_platform(pointerSize) != 4)} { + return + } +} + +# All Tcl 8.4+ interps can [load] Thread 2.8.9 +# +# For interps that are not thread-enabled, we still call [package ifneeded]. +# This is contrary to the usual convention, but is a good idea because we +# cannot imagine any other version of Thread that might succeed in a +# thread-disabled interp. There's nothing to gain by yielding to other +# competing callers of [package ifneeded Thread]. On the other hand, +# deferring the error has the advantage that a script calling +# [package require Thread] in a thread-disabled interp gets an error message +# about a thread-disabled interp, instead of the message +# "can't find package Thread". + +package ifneeded Thread 2.8.9 [list load [file join $dir libthread2.8.9.so] [string totitle thread]] + +# package Ttrace uses some support machinery. + +# In Tcl 8.4 interps we use some older interfaces +if {![package vsatisfies [package provide Tcl] 8.5]} { + package ifneeded Ttrace 2.8.9 " + [list proc thread_source {dir} { + if {[info exists ::env(TCL_THREAD_LIBRARY)] && + [file readable $::env(TCL_THREAD_LIBRARY)/ttrace.tcl]} { + source $::env(TCL_THREAD_LIBRARY)/ttrace.tcl + } elseif {[file readable [file join $dir .. lib ttrace.tcl]]} { + source [file join $dir .. lib ttrace.tcl] + } elseif {[file readable [file join $dir ttrace.tcl]]} { + source [file join $dir ttrace.tcl] + } + if {[namespace which ::ttrace::update] ne ""} { + ::ttrace::update + } + }] + [list thread_source $dir] + [list rename thread_source {}]" + return +} + +# In Tcl 8.5+ interps; use [::apply] + +package ifneeded Ttrace 2.8.9 [list ::apply {{dir} { + if {[info exists ::env(TCL_THREAD_LIBRARY)] && + [file readable $::env(TCL_THREAD_LIBRARY)/ttrace.tcl]} { + source $::env(TCL_THREAD_LIBRARY)/ttrace.tcl + } elseif {[file readable [file join $dir .. lib ttrace.tcl]]} { + source [file join $dir .. lib ttrace.tcl] + } elseif {[file readable [file join $dir ttrace.tcl]]} { + source [file join $dir ttrace.tcl] + } + if {[namespace which ::ttrace::update] ne ""} { + ::ttrace::update + } +}} $dir] + + + diff --git a/lib/thread2.8.9/ttrace.tcl b/lib/thread2.8.9/ttrace.tcl new file mode 100644 index 0000000000000000000000000000000000000000..5d5b9c7f7b090f8830b4715469b997501fb16588 --- /dev/null +++ b/lib/thread2.8.9/ttrace.tcl @@ -0,0 +1,942 @@ +# +# ttrace.tcl -- +# +# Copyright (C) 2003 Zoran Vasiljevic, Archiware GmbH. All Rights Reserved. +# +# See the file "license.terms" for information on usage and redistribution of +# this file, and for a DISCLAIMER OF ALL WARRANTIES. +# ---------------------------------------------------------------------------- +# +# User level commands: +# +# ttrace::eval top-level wrapper (ttrace-savvy eval) +# ttrace::enable activates registered Tcl command traces +# ttrace::disable terminates tracing of Tcl commands +# ttrace::isenabled returns true if ttrace is enabled +# ttrace::cleanup bring the interp to a pristine state +# ttrace::update update interp to the latest trace epoch +# ttrace::config setup some configuration options +# ttrace::getscript returns a script for initializing interps +# +# Commands used for/from trace callbacks: +# +# ttrace::atenable register callback to be done at trace enable +# ttrace::atdisable register callback to be done at trace disable +# ttrace::addtrace register user-defined tracer callback +# ttrace::addscript register user-defined script generator +# ttrace::addresolver register user-defined command resolver +# ttrace::addcleanup register user-defined cleanup procedures +# ttrace::addentry adds one entry into the named trace store +# ttrace::getentry returns the entry value from the named store +# ttrace::delentry removes the entry from the named store +# ttrace::getentries returns all entries from the named store +# ttrace::preload register procedures to be preloaded always +# +# +# Limitations: +# +# o. [namespace forget] is still not implemented +# o. [namespace origin cmd] breaks if cmd is not already defined +# +# I left this deliberately. I didn't want to override the [namespace] +# command in order to avoid potential slowdown. +# + +namespace eval ttrace { + + # Setup some compatibility wrappers + if {[info commands nsv_set] != ""} { + variable tvers 0 + variable mutex ns_mutex + variable elock [$mutex create traceepochmutex] + # Import the underlying API; faster than recomputing + interp alias {} [namespace current]::_array {} nsv_array + interp alias {} [namespace current]::_incr {} nsv_incr + interp alias {} [namespace current]::_lappend {} nsv_lappend + interp alias {} [namespace current]::_names {} nsv_names + interp alias {} [namespace current]::_set {} nsv_set + interp alias {} [namespace current]::_unset {} nsv_unset + } elseif {![catch { + variable tvers [package require Thread] + }]} { + variable mutex thread::mutex + variable elock [$mutex create] + # Import the underlying API; faster than recomputing + interp alias {} [namespace current]::_array {} tsv::array + interp alias {} [namespace current]::_incr {} tsv::incr + interp alias {} [namespace current]::_lappend {} tsv::lappend + interp alias {} [namespace current]::_names {} tsv::names + interp alias {} [namespace current]::_set {} tsv::set + interp alias {} [namespace current]::_unset {} tsv::unset + } else { + error "requires NaviServer/AOLserver or Tcl threading extension" + } + + # Keep in sync with the Thread package + package provide Ttrace 2.8.9 + + # Package variables + variable resolvers "" ; # List of registered resolvers + variable tracers "" ; # List of registered cmd tracers + variable scripts "" ; # List of registered script makers + variable enables "" ; # List of trace-enable callbacks + variable disables "" ; # List of trace-disable callbacks + variable preloads "" ; # List of procedure names to preload + variable enabled 0 ; # True if trace is enabled + variable config ; # Array with config options + + variable epoch -1 ; # The initialization epoch + variable cleancnt 0 ; # Counter of registered cleaners + + # Package private namespaces + namespace eval resolve "" ; # Commands for resolving commands + namespace eval trace "" ; # Commands registered for tracing + namespace eval enable "" ; # Commands invoked at trace enable + namespace eval disable "" ; # Commands invoked at trace disable + namespace eval script "" ; # Commands for generating scripts + + # Exported commands + namespace export unknown + + # Initialize ttrace shared state + if {[_array exists ttrace] == 0} { + _set ttrace lastepoch $epoch + _set ttrace epochlist "" + } + + # Initially, allow creation of epochs + set config(-doepochs) 1 + + proc eval {cmd args} { + enable + set code [catch {uplevel 1 [concat $cmd $args]} result] + disable + if {$code == 0} { + if {[llength [info commands ns_ictl]]} { + ns_ictl save [getscript] + } else { + thread::broadcast { + package require Ttrace + ttrace::update + } + } + } + return -code $code \ + -errorinfo $::errorInfo -errorcode $::errorCode $result + } + + proc config {args} { + variable config + if {[llength $args] == 0} { + array get config + } elseif {[llength $args] == 1} { + set opt [lindex $args 0] + set config($opt) + } else { + set opt [lindex $args 0] + set val [lindex $args 1] + set config($opt) $val + } + } + + proc enable {} { + variable config + variable tracers + variable enables + variable enabled + incr enabled 1 + if {$enabled > 1} { + return + } + if {$config(-doepochs) != 0} { + variable epoch [_newepoch] + } + set nsp [namespace current] + foreach enabler $enables { + enable::_$enabler + } + foreach trace $tracers { + if {[info commands $trace] != ""} { + trace add execution $trace leave ${nsp}::trace::_$trace + } + } + } + + proc disable {} { + variable enabled + variable tracers + variable disables + incr enabled -1 + if {$enabled > 0} { + return + } + set nsp [namespace current] + foreach disabler $disables { + disable::_$disabler + } + foreach trace $tracers { + if {[info commands $trace] != ""} { + trace remove execution $trace leave ${nsp}::trace::_$trace + } + } + } + + proc isenabled {} { + variable enabled + expr {$enabled > 0} + } + + proc update {{from -1}} { + if {$from < 0} { + variable epoch [_set ttrace lastepoch] + } else { + if {[lsearch [_set ttrace epochlist] $from] < 0} { + error "no such epoch: $from" + } + variable epoch $from + } + uplevel 1 [getscript] + } + + proc getscript {} { + variable preloads + variable epoch + variable scripts + append script [_serializensp] \n + append script "::namespace eval [namespace current] {" \n + append script "::namespace export unknown" \n + append script "_useepoch $epoch" \n + append script "}" \n + foreach cmd $preloads { + append script [_serializeproc $cmd] \n + } + foreach maker $scripts { + append script [script::_$maker] + } + return $script + } + + proc cleanup {args} { + foreach cmd [info commands resolve::cleaner_*] { + uplevel 1 $cmd $args + } + } + + proc preload {cmd} { + variable preloads + if {[lsearch $preloads $cmd] < 0} { + lappend preloads $cmd + } + } + + proc atenable {cmd arglist body} { + variable enables + if {[lsearch $enables $cmd] < 0} { + lappend enables $cmd + set cmd [namespace current]::enable::_$cmd + proc $cmd $arglist $body + return $cmd + } + } + + proc atdisable {cmd arglist body} { + variable disables + if {[lsearch $disables $cmd] < 0} { + lappend disables $cmd + set cmd [namespace current]::disable::_$cmd + proc $cmd $arglist $body + return $cmd + } + } + + proc addtrace {cmd arglist body} { + variable tracers + if {[lsearch $tracers $cmd] < 0} { + lappend tracers $cmd + set tracer [namespace current]::trace::_$cmd + proc $tracer $arglist $body + if {[isenabled]} { + trace add execution $cmd leave $tracer + } + return $tracer + } + } + + proc addscript {cmd body} { + variable scripts + if {[lsearch $scripts $cmd] < 0} { + lappend scripts $cmd + set cmd [namespace current]::script::_$cmd + proc $cmd args $body + return $cmd + } + } + + proc addresolver {cmd arglist body} { + variable resolvers + if {[lsearch $resolvers $cmd] < 0} { + lappend resolvers $cmd + set cmd [namespace current]::resolve::$cmd + proc $cmd $arglist $body + return $cmd + } + } + + proc addcleanup {body} { + variable cleancnt + set cmd [namespace current]::resolve::cleaner_[incr cleancnt] + proc $cmd args $body + return $cmd + } + + proc addentry {cmd var val} { + variable epoch + _set ${epoch}-$cmd $var $val + } + + proc delentry {cmd var} { + variable epoch + set ei $::errorInfo + set ec $::errorCode + catch {_unset ${epoch}-$cmd $var} + set ::errorInfo $ei + set ::errorCode $ec + } + + proc getentry {cmd var} { + variable epoch + set ei $::errorInfo + set ec $::errorCode + if {[catch {_set ${epoch}-$cmd $var} val]} { + set ::errorInfo $ei + set ::errorCode $ec + set val "" + } + return $val + } + + proc getentries {cmd {pattern *}} { + variable epoch + _array names ${epoch}-$cmd $pattern + } + + proc unknown {args} { + set cmd [lindex $args 0] + if {[uplevel 1 ttrace::_resolve [list $cmd]]} { + set c [catch {uplevel 1 $cmd [lrange $args 1 end]} r] + } else { + set c [catch {uplevel 1 ::tcl::unknown $args} r] + } + return -code $c -errorcode $::errorCode -errorinfo $::errorInfo $r + } + + proc _resolve {cmd} { + variable resolvers + foreach resolver $resolvers { + if {[uplevel 1 [info comm resolve::$resolver] [list $cmd]]} { + return 1 + } + } + return 0 + } + + proc _getthread {} { + if {[info commands ns_thread] == ""} { + thread::id + } else { + ns_thread getid + } + } + + proc _getthreads {} { + if {[info commands ns_thread] == ""} { + return [thread::names] + } else { + foreach entry [ns_info threads] { + lappend threads [lindex $entry 2] + } + return $threads + } + } + + proc _newepoch {} { + variable elock + variable mutex + $mutex lock $elock + set old [_set ttrace lastepoch] + set new [_incr ttrace lastepoch] + _lappend ttrace $new [_getthread] + if {$old >= 0} { + _copyepoch $old $new + _delepochs + } + _lappend ttrace epochlist $new + $mutex unlock $elock + return $new + } + + proc _copyepoch {old new} { + foreach var [_names $old-*] { + set cmd [lindex [split $var -] 1] + _array reset $new-$cmd [_array get $var] + } + } + + proc _delepochs {} { + set tlist [_getthreads] + set elist "" + foreach epoch [_set ttrace epochlist] { + if {[_dropepoch $epoch $tlist] == 0} { + lappend elist $epoch + } else { + _unset ttrace $epoch + } + } + _set ttrace epochlist $elist + } + + proc _dropepoch {epoch threads} { + set self [_getthread] + foreach tid [_set ttrace $epoch] { + if {$tid != $self && [lsearch $threads $tid] >= 0} { + lappend alive $tid + } + } + if {[info exists alive]} { + _set ttrace $epoch $alive + return 0 + } else { + foreach var [_names $epoch-*] { + _unset $var + } + return 1 + } + } + + proc _useepoch {epoch} { + if {$epoch >= 0} { + set tid [_getthread] + if {[lsearch [_set ttrace $epoch] $tid] == -1} { + _lappend ttrace $epoch $tid + } + } + } + + proc _serializeproc {cmd} { + set dargs [info args $cmd] + set pbody [info body $cmd] + set pargs "" + foreach arg $dargs { + if {![info default $cmd $arg def]} { + lappend pargs $arg + } else { + lappend pargs [list $arg $def] + } + } + set nsp [namespace qual $cmd] + if {$nsp == ""} { + set nsp "::" + } + append res [list ::namespace eval $nsp] " {" \n + append res [list ::proc [namespace tail $cmd] $pargs $pbody] \n + append res "}" \n + } + + proc _serializensp {{nsp ""} {result _}} { + upvar $result res + if {$nsp == ""} { + set nsp [namespace current] + } + append res [list ::namespace eval $nsp] " {" \n + foreach var [info vars ${nsp}::*] { + set vname [namespace tail $var] + if {[array exists $var] == 0} { + append res [list ::variable $vname [set $var]] \n + } else { + append res [list ::variable $vname] \n + append res [list ::array set $vname [array get $var]] \n + } + } + foreach cmd [info procs ${nsp}::*] { + append res [_serializeproc $cmd] \n + } + append res "}" \n + foreach nn [namespace children $nsp] { + _serializensp $nn res + } + return $res + } +} + +# +# The code below is ment to be run once during the application start. It +# provides implementation of tracing callbacks for some Tcl commands. Users +# can supply their own tracer implementations on-the-fly. +# +# The code below will create traces for the following Tcl commands: +# "namespace", "variable", "load", "proc" and "rename" +# +# Also, the Tcl object extension XOTcl 1.1.0 is handled and all XOTcl related +# things, like classes and objects are traced (many thanks to Gustaf Neumann +# from XOTcl for his kind help and support). +# + +eval { + + # + # Register the "load" trace. This will create the following key/value pair + # in the "load" store: + # + # --- key ---- --- value --- + # + # + # We normally need only the name_of_the_init_proc for being able to load + # the package in other interpreters, but we store the path to the image + # file as well. + # + + ttrace::addtrace load {cmdline code args} { + if {$code != 0} { + return + } + set image [lindex $cmdline 1] + set initp [lindex $cmdline 2] + if {$initp == ""} { + foreach pkg [info loaded] { + if {[lindex $pkg 0] == $image} { + set initp [lindex $pkg 1] + } + } + } + ttrace::addentry load $image $initp + } + + ttrace::addscript load { + append res "\n" + foreach entry [ttrace::getentries load] { + set initp [ttrace::getentry load $entry] + append res "::load {} $initp" \n + } + return $res + } + + # + # Register the "namespace" trace. This will create the following key/value + # entry in "namespace" store: + # + # --- key ---- --- value --- + # ::fully::qualified::namespace 1 + # + # It will also fill the "proc" store for procedures and commands imported + # in this namespace with following: + # + # --- key ---- --- value --- + # ::fully::qualified::proc [list "" ""] + # + # The is the name of the namespace where the command or procedure is + # imported from. + # + + ttrace::addtrace namespace {cmdline code args} { + if {$code != 0} { + return + } + set nop [lindex $cmdline 1] + set cns [uplevel 1 namespace current] + if {$cns == "::"} { + set cns "" + } + switch -glob $nop { + eva* { + set nsp [lindex $cmdline 2] + if {![string match "::*" $nsp]} { + set nsp ${cns}::$nsp + } + ttrace::addentry namespace $nsp 1 + } + imp* { + # - parse import arguments (skip opt "-force") + set opts [lrange $cmdline 2 end] + if {[string match "-fo*" [lindex $opts 0]]} { + set opts [lrange $cmdline 3 end] + } + # - register all imported procs and commands + foreach opt $opts { + if {![string match "::*" [::namespace qual $opt]]} { + set opt ${cns}::$opt + } + # - first import procs + foreach entry [ttrace::getentries proc $opt] { + set cmd ${cns}::[::namespace tail $entry] + set nsp [::namespace qual $entry] + set done($cmd) 1 + set entry [list 0 $nsp "" ""] + ttrace::addentry proc $cmd $entry + } + + # - then import commands + foreach entry [info commands $opt] { + set cmd ${cns}::[::namespace tail $entry] + set nsp [::namespace qual $entry] + if {[info exists done($cmd)] == 0} { + set entry [list 0 $nsp "" ""] + ttrace::addentry proc $cmd $entry + } + } + } + } + } + } + + ttrace::addscript namespace { + append res \n + foreach entry [ttrace::getentries namespace] { + append res "::namespace eval $entry {}" \n + } + return $res + } + + # + # Register the "variable" trace. This will create the following key/value + # entry in the "variable" store: + # + # --- key ---- --- value --- + # ::fully::qualified::variable 1 + # + # The variable value itself is ignored at the time of + # trace/collection. Instead, we take the real value at the time of script + # generation. + # + + ttrace::addtrace variable {cmdline code args} { + if {$code != 0} { + return + } + set opts [lrange $cmdline 1 end] + if {[llength $opts]} { + set cns [uplevel 1 namespace current] + if {$cns == "::"} { + set cns "" + } + foreach {var val} $opts { + if {![string match "::*" $var]} { + set var ${cns}::$var + } + ttrace::addentry variable $var 1 + } + } + } + + ttrace::addscript variable { + append res \n + foreach entry [ttrace::getentries variable] { + set cns [namespace qual $entry] + set var [namespace tail $entry] + append res "::namespace eval $cns {" \n + append res "::variable $var" + if {[array exists $entry]} { + append res "\n::array set $var [list [array get $entry]]" \n + } elseif {[info exists $entry]} { + append res " [list [set $entry]]" \n + } else { + append res \n + } + append res "}" \n + } + return $res + } + + + # + # Register the "rename" trace. It will create the following key/value pair + # in "rename" store: + # + # --- key ---- --- value --- + # ::fully::qualified::old ::fully::qualified::new + # + # The "new" value may be empty, for commands that have been deleted. In + # such cases we also remove any traced procedure definitions. + # + + ttrace::addtrace rename {cmdline code args} { + if {$code != 0} { + return + } + set cns [uplevel 1 namespace current] + if {$cns == "::"} { + set cns "" + } + set old [lindex $cmdline 1] + if {![string match "::*" $old]} { + set old ${cns}::$old + } + set new [lindex $cmdline 2] + if {$new != ""} { + if {![string match "::*" $new]} { + set new ${cns}::$new + } + ttrace::addentry rename $old $new + } else { + ttrace::delentry proc $old + } + } + + ttrace::addscript rename { + append res \n + foreach old [ttrace::getentries rename] { + set new [ttrace::getentry rename $old] + append res "::rename $old {$new}" \n + } + return $res + } + + # + # Register the "proc" trace. This will create the following key/value pair + # in the "proc" store: + # + # --- key ---- --- value --- + # ::fully::qualified::proc [list ] + # + # The chages anytime one (re)defines a proc. The is the + # namespace where the command was imported from. If empty, the + # and will hold the actual procedure definition. See the + # "namespace" tracer implementation also. + # + + ttrace::addtrace proc {cmdline code args} { + if {$code != 0} { + return + } + set cns [uplevel 1 namespace current] + if {$cns == "::"} { + set cns "" + } + set cmd [lindex $cmdline 1] + if {![string match "::*" $cmd]} { + set cmd ${cns}::$cmd + } + set dargs [info args $cmd] + set pbody [info body $cmd] + set pargs "" + foreach arg $dargs { + if {![info default $cmd $arg def]} { + lappend pargs $arg + } else { + lappend pargs [list $arg $def] + } + } + set pdef [ttrace::getentry proc $cmd] + if {$pdef == ""} { + set epoch -1 ; # never traced before + } else { + set epoch [lindex $pdef 0] + } + ttrace::addentry proc $cmd [list [incr epoch] "" $pargs $pbody] + } + + ttrace::addscript proc { + return { + if {[info command ::tcl::unknown] == ""} { + rename ::unknown ::tcl::unknown + namespace import -force ::ttrace::unknown + } + if {[info command ::tcl::info] == ""} { + rename ::info ::tcl::info + } + proc ::info args { + set cmd [lindex $args 0] + set hit [lsearch -glob {commands procs args default body} $cmd*] + if {$hit > 1} { + if {[catch {uplevel 1 ::tcl::info $args}]} { + uplevel 1 ttrace::_resolve [list [lindex $args 1]] + } + return [uplevel 1 ::tcl::info $args] + } + if {$hit == -1} { + return [uplevel 1 ::tcl::info $args] + } + set cns [uplevel 1 namespace current] + if {$cns == "::"} { + set cns "" + } + set pat [lindex $args 1] + if {![string match "::*" $pat]} { + set pat ${cns}::$pat + } + set fns [ttrace::getentries proc $pat] + if {[string match $cmd* commands]} { + set fns [concat $fns [ttrace::getentries xotcl $pat]] + } + foreach entry $fns { + if {$cns != [namespace qual $entry]} { + set lazy($entry) 1 + } else { + set lazy([namespace tail $entry]) 1 + } + } + foreach entry [uplevel 1 ::tcl::info $args] { + set lazy($entry) 1 + } + array names lazy + } + } + } + + # + # Register procedure resolver. This will try to resolve the command in the + # current namespace first, and if not found, in global namespace. It also + # handles commands imported from other namespaces. + # + + ttrace::addresolver resolveprocs {cmd {export 0}} { + set cns [uplevel 1 namespace current] + set name [namespace tail $cmd] + if {$cns == "::"} { + set cns "" + } + if {![string match "::*" $cmd]} { + set ncmd ${cns}::$cmd + set gcmd ::$cmd + } else { + set ncmd $cmd + set gcmd $cmd + } + set pdef [ttrace::getentry proc $ncmd] + if {$pdef == ""} { + set pdef [ttrace::getentry proc $gcmd] + if {$pdef == ""} { + return 0 + } + set cmd $gcmd + } else { + set cmd $ncmd + } + set epoch [lindex $pdef 0] + set pnsp [lindex $pdef 1] + if {$pnsp != ""} { + set nsp [namespace qual $cmd] + if {$nsp == ""} { + set nsp :: + } + set cmd ${pnsp}::$name + if {[resolveprocs $cmd 1] == 0 && [info commands $cmd] == ""} { + return 0 + } + namespace eval $nsp "namespace import -force $cmd" + } else { + uplevel 0 [list ::proc $cmd [lindex $pdef 2] [lindex $pdef 3]] + if {$export} { + set nsp [namespace qual $cmd] + if {$nsp == ""} { + set nsp :: + } + namespace eval $nsp "namespace export $name" + } + } + variable resolveproc + set resolveproc($cmd) $epoch + return 1 + } + + # + # For XOTcl, the entire item introspection/tracing is delegated to XOTcl + # itself. The xotcl store is filled with this: + # + # --- key ---- --- value --- + # ::fully::qualified::item + # + # The is the script used to generate the entire item (class, + # object). Note that we do not fill in this during code tracing. It is + # done during the script generation. In this step, only the placeholder is + # set. + # + # NOTE: we assume all XOTcl commands are imported in global namespace + # + + ttrace::atenable XOTclEnabler {args} { + if {[info commands ::xotcl::Class] == ""} { + return + } + if {[info commands ::xotcl::_creator] == ""} { + ::xotcl::Class create ::xotcl::_creator -instproc create {args} { + set result [next] + if {![string match ::xotcl::_* $result]} { + ttrace::addentry xotcl $result "" + } + return $result + } + } + ::xotcl::Class instmixin ::xotcl::_creator + } + + ttrace::atdisable XOTclDisabler {args} { + if { [info commands ::xotcl::Class] == "" + || [info commands ::xotcl::_creator] == ""} { + return + } + ::xotcl::Class instmixin "" + ::xotcl::_creator destroy + } + + set resolver [ttrace::addresolver resolveclasses {classname} { + set cns [uplevel 1 namespace current] + set script [ttrace::getentry xotcl $classname] + if {$script == ""} { + set name [namespace tail $classname] + if {$cns == "::"} { + set script [ttrace::getentry xotcl ::$name] + } else { + set script [ttrace::getentry xotcl ${cns}::$name] + if {$script == ""} { + set script [ttrace::getentry xotcl ::$name] + } + } + if {$script == ""} { + return 0 + } + } + uplevel 1 [list namespace eval $cns $script] + return 1 + }] + + ttrace::addscript xotcl [subst -nocommands { + if {![catch {Serializer new} ss]} { + foreach entry [ttrace::getentries xotcl] { + if {[ttrace::getentry xotcl \$entry] == ""} { + ttrace::addentry xotcl \$entry [\$ss serialize \$entry] + } + } + \$ss destroy + return {::xotcl::Class proc __unknown name {$resolver \$name}} + } + }] + + # + # Register callback to be called on cleanup. This will trash lazily loaded + # procs which have changed since. + # + + ttrace::addcleanup { + variable resolveproc + foreach cmd [array names resolveproc] { + set def [ttrace::getentry proc $cmd] + if {$def != ""} { + set new [lindex $def 0] + set old $resolveproc($cmd) + if {[info command $cmd] != "" && $new != $old} { + catch {rename $cmd ""} + } + } + } + } +} + +# EOF +return + +# Local Variables: +# mode: tcl +# fill-column: 78 +# tab-width: 8 +# indent-tabs-mode: nil +# End: diff --git a/lib/tk8.6/bgerror.tcl b/lib/tk8.6/bgerror.tcl new file mode 100644 index 0000000000000000000000000000000000000000..fe8dfe0410e2bc53a64dc89aa8b6c4f6542552f4 --- /dev/null +++ b/lib/tk8.6/bgerror.tcl @@ -0,0 +1,272 @@ +# bgerror.tcl -- +# +# Implementation of the bgerror procedure. It posts a dialog box with +# the error message and gives the user a chance to see a more detailed +# stack trace, and possible do something more interesting with that +# trace (like save it to a log). This is adapted from work done by +# Donal K. Fellows. +# +# Copyright (c) 1998-2000 by Ajuba Solutions. +# Copyright (c) 2007 by ActiveState Software Inc. +# Copyright (c) 2007 Daniel A. Steffen +# Copyright (c) 2009 Pat Thoyts + +namespace eval ::tk::dialog::error { + namespace import -force ::tk::msgcat::* + namespace export bgerror + option add *ErrorDialog.function.text [mc "Save To Log"] \ + widgetDefault + option add *ErrorDialog.function.command [namespace code SaveToLog] + option add *ErrorDialog*Label.font TkCaptionFont widgetDefault + if {[tk windowingsystem] eq "aqua"} { + option add *ErrorDialog*background systemAlertBackgroundActive \ + widgetDefault + option add *ErrorDialog*info.text.background \ + systemTextBackgroundColor widgetDefault + option add *ErrorDialog*Button.highlightBackground \ + systemAlertBackgroundActive widgetDefault + } +} + +proc ::tk::dialog::error::Return {which code} { + variable button + + .bgerrorDialog.$which state {active selected focus} + update idletasks + after 100 + set button $code +} + +proc ::tk::dialog::error::Details {} { + set w .bgerrorDialog + set caption [option get $w.function text {}] + set command [option get $w.function command {}] + if {($caption eq "") || ($command eq "")} { + grid forget $w.function + } + lappend command [$w.top.info.text get 1.0 end-1c] + $w.function configure -text $caption -command $command + grid $w.top.info - -sticky nsew -padx 3m -pady 3m +} + +proc ::tk::dialog::error::SaveToLog {text} { + if {$::tcl_platform(platform) eq "windows"} { + set allFiles *.* + } else { + set allFiles * + } + set types [list \ + [list [mc "Log Files"] .log] \ + [list [mc "Text Files"] .txt] \ + [list [mc "All Files"] $allFiles] \ + ] + set filename [tk_getSaveFile -title [mc "Select Log File"] \ + -filetypes $types -defaultextension .log -parent .bgerrorDialog] + if {$filename ne {}} { + set f [open $filename w] + puts -nonewline $f $text + close $f + } + return +} + +proc ::tk::dialog::error::Destroy {w} { + if {$w eq ".bgerrorDialog"} { + variable button + set button -1 + } +} + +proc ::tk::dialog::error::DeleteByProtocol {} { + variable button + set button 1 +} + +proc ::tk::dialog::error::ReturnInDetails w { + bind $w {}; # Remove this binding + $w invoke + return -code break +} + +# ::tk::dialog::error::bgerror -- +# +# This is the default version of bgerror. +# It tries to execute tkerror, if that fails it posts a dialog box +# containing the error message and gives the user a chance to ask +# to see a stack trace. +# +# Arguments: +# err - The error message. +# +proc ::tk::dialog::error::bgerror {err {flag 1}} { + global errorInfo + variable button + + set info $errorInfo + + set ret [catch {::tkerror $err} msg]; + if {$ret != 1} {return -code $ret $msg} + + # The application's tkerror either failed or was not found + # so we use the default dialog. But on Aqua we cannot display + # the dialog if the background error occurs in an idle task + # being processed inside of [NSView drawRect]. In that case + # we post the dialog as an after task instead. + set windowingsystem [tk windowingsystem] + if {$windowingsystem eq "aqua"} { + if $flag { + set errorInfo $info + after 500 [list bgerror "$err" 0] + return + } + } + + set ok [mc OK] + # Truncate the message if it is too wide (>maxLine characters) or + # too tall (>4 lines). Truncation occurs at the first point at + # which one of those conditions is met. + set displayedErr "" + set lines 0 + set maxLine 45 + foreach line [split $err \n] { + if {[string length $line] > $maxLine} { + append displayedErr "[string range $line 0 $maxLine-3]..." + break + } + if {$lines > 4} { + append displayedErr "..." + break + } else { + append displayedErr "${line}\n" + } + incr lines + } + + set title [mc "Application Error"] + set text [mc "Error: %1\$s" $displayedErr] + set buttons [list ok $ok dismiss [mc "Skip Messages"] \ + function [mc "Details >>"]] + + # 1. Create the top-level window and divide it into top + # and bottom parts. + + set dlg .bgerrorDialog + set bg [ttk::style lookup . -background] + destroy $dlg + toplevel $dlg -class ErrorDialog -background $bg + wm withdraw $dlg + wm title $dlg $title + wm iconname $dlg ErrorDialog + wm protocol $dlg WM_DELETE_WINDOW [namespace code DeleteByProtocol] + + if {$windowingsystem eq "aqua"} { + ::tk::unsupported::MacWindowStyle style $dlg moveableAlert {} + } elseif {$windowingsystem eq "x11"} { + wm attributes $dlg -type dialog + } + + ttk::frame $dlg.bot + ttk::frame $dlg.top + pack $dlg.bot -side bottom -fill both + pack $dlg.top -side top -fill both -expand 1 + + set W [ttk::frame $dlg.top.info] + text $W.text -setgrid true -height 10 -wrap char \ + -yscrollcommand [list $W.scroll set] + if {$windowingsystem ne "aqua"} { + $W.text configure -width 40 + } + + ttk::scrollbar $W.scroll -command [list $W.text yview] + pack $W.scroll -side right -fill y + pack $W.text -side left -expand yes -fill both + $W.text insert 0.0 "$err\n$info" + $W.text mark set insert 0.0 + bind $W.text {focus %W} + $W.text configure -state disabled + + # 2. Fill the top part with bitmap and message + + # Max-width of message is the width of the screen... + set wrapwidth [winfo screenwidth $dlg] + # ...minus the width of the icon, padding and a fudge factor for + # the window manager decorations and aesthetics. + set wrapwidth [expr {$wrapwidth-60-[winfo pixels $dlg 9m]}] + ttk::label $dlg.msg -justify left -text $text -wraplength $wrapwidth + ttk::label $dlg.bitmap -image ::tk::icons::error + + grid $dlg.bitmap $dlg.msg -in $dlg.top -row 0 -padx 3m -pady 3m + grid configure $dlg.bitmap -sticky ne + grid configure $dlg.msg -sticky nsw -padx {0 3m} + grid rowconfigure $dlg.top 1 -weight 1 + grid columnconfigure $dlg.top 1 -weight 1 + + # 3. Create a row of buttons at the bottom of the dialog. + + set i 0 + foreach {name caption} $buttons { + ttk::button $dlg.$name -text $caption -default normal \ + -command [namespace code [list set button $i]] + grid $dlg.$name -in $dlg.bot -column $i -row 0 -sticky ew -padx 10 + grid columnconfigure $dlg.bot $i -weight 1 + # We boost the size of some Mac buttons for l&f + if {$windowingsystem eq "aqua"} { + if {($name eq "ok") || ($name eq "dismiss")} { + grid columnconfigure $dlg.bot $i -minsize 90 + } + grid configure $dlg.$name -pady 7 + } + incr i + } + # The "OK" button is the default for this dialog. + $dlg.ok configure -default active + + bind $dlg [namespace code {Return ok 0}] + bind $dlg [namespace code {Return dismiss 1}] + bind $dlg [namespace code {Destroy %W}] + bind $dlg.function [namespace code {ReturnInDetails %W}] + $dlg.function configure -command [namespace code Details] + + # 6. Withdraw the window, then update all the geometry information + # so we know how big it wants to be, then center the window in the + # display (Motif style) and de-iconify it. + + ::tk::PlaceWindow $dlg + + # 7. Set a grab and claim the focus too. + + ::tk::SetFocusGrab $dlg $dlg.ok + + # 8. Ensure that we are topmost. + + raise $dlg + if {[tk windowingsystem] eq "win32"} { + # Place it topmost if we aren't at the top of the stacking + # order to ensure that it's seen + if {[lindex [wm stackorder .] end] ne "$dlg"} { + wm attributes $dlg -topmost 1 + } + } + + # 9. Wait for the user to respond, then restore the focus and + # return the index of the selected button. Restore the focus + # before deleting the window, since otherwise the window manager + # may take the focus away so we can't redirect it. Finally, + # restore any grab that was in effect. + + vwait [namespace which -variable button] + set copy $button; # Save a copy... + + ::tk::RestoreFocusGrab $dlg $dlg.ok destroy + + if {$copy == 1} { + return -code break + } +} + +namespace eval :: { + # Fool the indexer + proc bgerror err {} + rename bgerror {} + namespace import ::tk::dialog::error::bgerror +} diff --git a/lib/tk8.6/button.tcl b/lib/tk8.6/button.tcl new file mode 100644 index 0000000000000000000000000000000000000000..9b13607710a078ed34458ad8179f1d80c2a2d9c5 --- /dev/null +++ b/lib/tk8.6/button.tcl @@ -0,0 +1,782 @@ +# button.tcl -- +# +# This file defines the default bindings for Tk label, button, +# checkbutton, and radiobutton widgets and provides procedures +# that help in implementing those bindings. +# +# Copyright (c) 1992-1994 The Regents of the University of California. +# Copyright (c) 1994-1996 Sun Microsystems, Inc. +# Copyright (c) 2002 ActiveState Corporation. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# + +#------------------------------------------------------------------------- +# The code below creates the default class bindings for buttons. +#------------------------------------------------------------------------- + +if {[tk windowingsystem] eq "aqua"} { + + bind Radiobutton { + tk::ButtonEnter %W + } + bind Radiobutton <1> { + tk::ButtonDown %W + } + bind Radiobutton { + tk::ButtonUp %W + } + bind Checkbutton { + tk::ButtonEnter %W + } + bind Checkbutton <1> { + tk::ButtonDown %W + } + bind Checkbutton { + tk::ButtonUp %W + } + bind Checkbutton { + tk::ButtonLeave %W + } +} +if {"win32" eq [tk windowingsystem]} { + bind Checkbutton { + tk::CheckRadioInvoke %W select + } + bind Checkbutton { + tk::CheckRadioInvoke %W select + } + bind Checkbutton { + tk::CheckRadioInvoke %W deselect + } + bind Checkbutton <1> { + tk::CheckRadioDown %W + } + bind Checkbutton { + tk::ButtonUp %W + } + bind Checkbutton { + tk::CheckRadioEnter %W + } + bind Checkbutton { + tk::ButtonLeave %W + } + + bind Radiobutton <1> { + tk::CheckRadioDown %W + } + bind Radiobutton { + tk::ButtonUp %W + } + bind Radiobutton { + tk::CheckRadioEnter %W + } +} +if {"x11" eq [tk windowingsystem]} { + bind Checkbutton { + if {!$tk_strictMotif} { + tk::CheckInvoke %W + } + } + bind Radiobutton { + if {!$tk_strictMotif} { + tk::CheckRadioInvoke %W + } + } + bind Checkbutton <1> { + tk::CheckInvoke %W + } + bind Radiobutton <1> { + tk::CheckRadioInvoke %W + } + bind Checkbutton { + tk::CheckEnter %W + } + bind Radiobutton { + tk::ButtonEnter %W + } + bind Checkbutton { + tk::CheckLeave %W + } +} + +bind Button { + tk::ButtonInvoke %W +} +bind Checkbutton { + tk::CheckRadioInvoke %W +} +bind Radiobutton { + tk::CheckRadioInvoke %W +} +bind Button <> { + tk::ButtonInvoke %W +} +bind Checkbutton <> { + tk::CheckRadioInvoke %W +} +bind Radiobutton <> { + tk::CheckRadioInvoke %W +} + +bind Button {} +bind Button { + tk::ButtonEnter %W +} +bind Button { + tk::ButtonLeave %W +} +bind Button <1> { + tk::ButtonDown %W +} +bind Button { + tk::ButtonUp %W +} + +bind Checkbutton {} + +bind Radiobutton {} +bind Radiobutton { + tk::ButtonLeave %W +} + +if {"win32" eq [tk windowingsystem]} { + +######################### +# Windows implementation +######################### + +# ::tk::ButtonEnter -- +# The procedure below is invoked when the mouse pointer enters a +# button widget. It records the button we're in and changes the +# state of the button to active unless the button is disabled. +# +# Arguments: +# w - The name of the widget. + +proc ::tk::ButtonEnter w { + variable ::tk::Priv + if {[$w cget -state] ne "disabled"} { + + # If the mouse button is down, set the relief to sunken on entry. + # Overwise, if there's an -overrelief value, set the relief to that. + + set Priv($w,relief) [$w cget -relief] + if {$Priv(buttonWindow) eq $w} { + $w configure -relief sunken -state active + set Priv($w,prelief) sunken + } elseif {[set over [$w cget -overrelief]] ne ""} { + $w configure -relief $over + set Priv($w,prelief) $over + } + } + set Priv(window) $w +} + +# ::tk::ButtonLeave -- +# The procedure below is invoked when the mouse pointer leaves a +# button widget. It changes the state of the button back to inactive. +# Restore any modified relief too. +# +# Arguments: +# w - The name of the widget. + +proc ::tk::ButtonLeave w { + variable ::tk::Priv + if {[$w cget -state] ne "disabled"} { + $w configure -state normal + } + + # Restore the original button relief if it was changed by Tk. + # That is signaled by the existence of Priv($w,prelief). + + if {[info exists Priv($w,relief)]} { + if {[info exists Priv($w,prelief)] && \ + $Priv($w,prelief) eq [$w cget -relief]} { + $w configure -relief $Priv($w,relief) + } + unset -nocomplain Priv($w,relief) Priv($w,prelief) + } + + set Priv(window) "" +} + +# ::tk::ButtonDown -- +# The procedure below is invoked when the mouse button is pressed in +# a button widget. It records the fact that the mouse is in the button, +# saves the button's relief so it can be restored later, and changes +# the relief to sunken. +# +# Arguments: +# w - The name of the widget. + +proc ::tk::ButtonDown w { + variable ::tk::Priv + + # Only save the button's relief if it does not yet exist. If there + # is an overrelief setting, Priv($w,relief) will already have been set, + # and the current value of the -relief option will be incorrect. + + if {![info exists Priv($w,relief)]} { + set Priv($w,relief) [$w cget -relief] + } + + if {[$w cget -state] ne "disabled"} { + set Priv(buttonWindow) $w + $w configure -relief sunken -state active + set Priv($w,prelief) sunken + + # If this button has a repeatdelay set up, get it going with an after + after cancel $Priv(afterId) + set delay [$w cget -repeatdelay] + set Priv(repeated) 0 + if {$delay > 0} { + set Priv(afterId) [after $delay [list tk::ButtonAutoInvoke $w]] + } + } +} + +# ::tk::ButtonUp -- +# The procedure below is invoked when the mouse button is released +# in a button widget. It restores the button's relief and invokes +# the command as long as the mouse hasn't left the button. +# +# Arguments: +# w - The name of the widget. + +proc ::tk::ButtonUp w { + variable ::tk::Priv + if {$Priv(buttonWindow) eq $w} { + set Priv(buttonWindow) "" + + # Restore the button's relief if it was cached. + + if {[info exists Priv($w,relief)]} { + if {[info exists Priv($w,prelief)] && \ + $Priv($w,prelief) eq [$w cget -relief]} { + $w configure -relief $Priv($w,relief) + } + unset -nocomplain Priv($w,relief) Priv($w,prelief) + } + + # Clean up the after event from the auto-repeater + after cancel $Priv(afterId) + + if {$Priv(window) eq $w && [$w cget -state] ne "disabled"} { + $w configure -state normal + + # Only invoke the command if it wasn't already invoked by the + # auto-repeater functionality + if { $Priv(repeated) == 0 } { + uplevel #0 [list $w invoke] + } + } + } +} + +# ::tk::CheckRadioEnter -- +# The procedure below is invoked when the mouse pointer enters a +# checkbutton or radiobutton widget. It records the button we're in +# and changes the state of the button to active unless the button is +# disabled. +# +# Arguments: +# w - The name of the widget. + +proc ::tk::CheckRadioEnter w { + variable ::tk::Priv + if {[$w cget -state] ne "disabled"} { + if {$Priv(buttonWindow) eq $w} { + $w configure -state active + } + if {[set over [$w cget -overrelief]] ne ""} { + set Priv($w,relief) [$w cget -relief] + set Priv($w,prelief) $over + $w configure -relief $over + } + } + set Priv(window) $w +} + +# ::tk::CheckRadioDown -- +# The procedure below is invoked when the mouse button is pressed in +# a button widget. It records the fact that the mouse is in the button, +# saves the button's relief so it can be restored later, and changes +# the relief to sunken. +# +# Arguments: +# w - The name of the widget. + +proc ::tk::CheckRadioDown w { + variable ::tk::Priv + if {![info exists Priv($w,relief)]} { + set Priv($w,relief) [$w cget -relief] + } + if {[$w cget -state] ne "disabled"} { + set Priv(buttonWindow) $w + set Priv(repeated) 0 + $w configure -state active + } +} + +} + +if {"x11" eq [tk windowingsystem]} { + +##################### +# Unix implementation +##################### + +# ::tk::ButtonEnter -- +# The procedure below is invoked when the mouse pointer enters a +# button widget. It records the button we're in and changes the +# state of the button to active unless the button is disabled. +# +# Arguments: +# w - The name of the widget. + +proc ::tk::ButtonEnter {w} { + variable ::tk::Priv + if {[$w cget -state] ne "disabled"} { + # On unix the state is active just with mouse-over + $w configure -state active + + # If the mouse button is down, set the relief to sunken on entry. + # Overwise, if there's an -overrelief value, set the relief to that. + + set Priv($w,relief) [$w cget -relief] + if {$Priv(buttonWindow) eq $w} { + $w configure -relief sunken + set Priv($w,prelief) sunken + } elseif {[set over [$w cget -overrelief]] ne ""} { + $w configure -relief $over + set Priv($w,prelief) $over + } + } + set Priv(window) $w +} + +# ::tk::ButtonLeave -- +# The procedure below is invoked when the mouse pointer leaves a +# button widget. It changes the state of the button back to inactive. +# Restore any modified relief too. +# +# Arguments: +# w - The name of the widget. + +proc ::tk::ButtonLeave w { + variable ::tk::Priv + if {[$w cget -state] ne "disabled"} { + $w configure -state normal + } + + # Restore the original button relief if it was changed by Tk. + # That is signaled by the existence of Priv($w,prelief). + + if {[info exists Priv($w,relief)]} { + if {[info exists Priv($w,prelief)] && \ + $Priv($w,prelief) eq [$w cget -relief]} { + $w configure -relief $Priv($w,relief) + } + unset -nocomplain Priv($w,relief) Priv($w,prelief) + } + + set Priv(window) "" +} + +# ::tk::ButtonDown -- +# The procedure below is invoked when the mouse button is pressed in +# a button widget. It records the fact that the mouse is in the button, +# saves the button's relief so it can be restored later, and changes +# the relief to sunken. +# +# Arguments: +# w - The name of the widget. + +proc ::tk::ButtonDown w { + variable ::tk::Priv + + # Only save the button's relief if it does not yet exist. If there + # is an overrelief setting, Priv($w,relief) will already have been set, + # and the current value of the -relief option will be incorrect. + + if {![info exists Priv($w,relief)]} { + set Priv($w,relief) [$w cget -relief] + } + + if {[$w cget -state] ne "disabled"} { + set Priv(buttonWindow) $w + $w configure -relief sunken + set Priv($w,prelief) sunken + + # If this button has a repeatdelay set up, get it going with an after + after cancel $Priv(afterId) + set delay [$w cget -repeatdelay] + set Priv(repeated) 0 + if {$delay > 0} { + set Priv(afterId) [after $delay [list tk::ButtonAutoInvoke $w]] + } + } +} + +# ::tk::ButtonUp -- +# The procedure below is invoked when the mouse button is released +# in a button widget. It restores the button's relief and invokes +# the command as long as the mouse hasn't left the button. +# +# Arguments: +# w - The name of the widget. + +proc ::tk::ButtonUp w { + variable ::tk::Priv + if {$w eq $Priv(buttonWindow)} { + set Priv(buttonWindow) "" + + # Restore the button's relief if it was cached. + + if {[info exists Priv($w,relief)]} { + if {[info exists Priv($w,prelief)] && \ + $Priv($w,prelief) eq [$w cget -relief]} { + $w configure -relief $Priv($w,relief) + } + unset -nocomplain Priv($w,relief) Priv($w,prelief) + } + + # Clean up the after event from the auto-repeater + after cancel $Priv(afterId) + + if {$Priv(window) eq $w && [$w cget -state] ne "disabled"} { + # Only invoke the command if it wasn't already invoked by the + # auto-repeater functionality + if { $Priv(repeated) == 0 } { + uplevel #0 [list $w invoke] + } + } + } +} + +} + +if {[tk windowingsystem] eq "aqua"} { + +#################### +# Mac implementation +#################### + +# ::tk::ButtonEnter -- +# The procedure below is invoked when the mouse pointer enters a +# button widget. It records the button we're in and changes the +# state of the button to active unless the button is disabled. +# +# Arguments: +# w - The name of the widget. + +proc ::tk::ButtonEnter {w} { + variable ::tk::Priv + if {[$w cget -state] ne "disabled"} { + + # If there's an -overrelief value, set the relief to that. + + if {$Priv(buttonWindow) eq $w} { + $w configure -state active + } elseif {[set over [$w cget -overrelief]] ne ""} { + set Priv($w,relief) [$w cget -relief] + set Priv($w,prelief) $over + $w configure -relief $over + } + } + set Priv(window) $w +} + +# ::tk::ButtonLeave -- +# The procedure below is invoked when the mouse pointer leaves a +# button widget. It changes the state of the button back to +# inactive. If we're leaving the button window with a mouse button +# pressed (Priv(buttonWindow) == $w), restore the relief of the +# button too. +# +# Arguments: +# w - The name of the widget. + +proc ::tk::ButtonLeave w { + variable ::tk::Priv + if {$w eq $Priv(buttonWindow)} { + $w configure -state normal + } + + # Restore the original button relief if it was changed by Tk. + # That is signaled by the existence of Priv($w,prelief). + + if {[info exists Priv($w,relief)]} { + if {[info exists Priv($w,prelief)] && \ + $Priv($w,prelief) eq [$w cget -relief]} { + $w configure -relief $Priv($w,relief) + } + unset -nocomplain Priv($w,relief) Priv($w,prelief) + } + + set Priv(window) "" +} + +# ::tk::ButtonDown -- +# The procedure below is invoked when the mouse button is pressed in +# a button widget. It records the fact that the mouse is in the button, +# saves the button's relief so it can be restored later, and changes +# the relief to sunken. +# +# Arguments: +# w - The name of the widget. + +proc ::tk::ButtonDown w { + variable ::tk::Priv + + if {[$w cget -state] ne "disabled"} { + set Priv(buttonWindow) $w + $w configure -state active + + # If this button has a repeatdelay set up, get it going with an after + after cancel $Priv(afterId) + set Priv(repeated) 0 + if { ![catch {$w cget -repeatdelay} delay] } { + if {$delay > 0} { + set Priv(afterId) [after $delay [list tk::ButtonAutoInvoke $w]] + } + } + } +} + +# ::tk::ButtonUp -- +# The procedure below is invoked when the mouse button is released +# in a button widget. It restores the button's relief and invokes +# the command as long as the mouse hasn't left the button. +# +# Arguments: +# w - The name of the widget. + +proc ::tk::ButtonUp w { + variable ::tk::Priv + if {$Priv(buttonWindow) eq $w} { + set Priv(buttonWindow) "" + $w configure -state normal + + # Restore the button's relief if it was cached. + + if {[info exists Priv($w,relief)]} { + if {[info exists Priv($w,prelief)] && \ + $Priv($w,prelief) eq [$w cget -relief]} { + $w configure -relief $Priv($w,relief) + } + unset -nocomplain Priv($w,relief) Priv($w,prelief) + } + + # Clean up the after event from the auto-repeater + after cancel $Priv(afterId) + + if {$Priv(window) eq $w && [$w cget -state] ne "disabled"} { + # Only invoke the command if it wasn't already invoked by the + # auto-repeater functionality + if { $Priv(repeated) == 0 } { + uplevel #0 [list $w invoke] + } + } + } +} + +} + +################## +# Shared routines +################## + +# ::tk::ButtonInvoke -- +# The procedure below is called when a button is invoked through +# the keyboard. It simulate a press of the button via the mouse. +# +# Arguments: +# w - The name of the widget. + +proc ::tk::ButtonInvoke w { + if {[winfo exists $w] && [$w cget -state] ne "disabled"} { + set oldRelief [$w cget -relief] + set oldState [$w cget -state] + $w configure -state active -relief sunken + after 100 [list ::tk::ButtonInvokeEnd $w $oldState $oldRelief] + } +} + +# ::tk::ButtonInvokeEnd -- +# The procedure below is called after a button is invoked through +# the keyboard. It simulate a release of the button via the mouse. +# +# Arguments: +# w - The name of the widget. +# oldState - Old state to be set back. +# oldRelief - Old relief to be set back. + +proc ::tk::ButtonInvokeEnd {w oldState oldRelief} { + if {[winfo exists $w]} { + $w configure -state $oldState -relief $oldRelief + uplevel #0 [list $w invoke] + } +} + +# ::tk::ButtonAutoInvoke -- +# +# Invoke an auto-repeating button, and set it up to continue to repeat. +# +# Arguments: +# w button to invoke. +# +# Results: +# None. +# +# Side effects: +# May create an after event to call ::tk::ButtonAutoInvoke. + +proc ::tk::ButtonAutoInvoke {w} { + variable ::tk::Priv + after cancel $Priv(afterId) + set delay [$w cget -repeatinterval] + if {$Priv(window) eq $w} { + incr Priv(repeated) + uplevel #0 [list $w invoke] + } + if {$delay > 0} { + set Priv(afterId) [after $delay [list tk::ButtonAutoInvoke $w]] + } +} + +# ::tk::CheckRadioInvoke -- +# The procedure below is invoked when the mouse button is pressed in +# a checkbutton or radiobutton widget, or when the widget is invoked +# through the keyboard. It invokes the widget if it +# isn't disabled. +# +# Arguments: +# w - The name of the widget. +# cmd - The subcommand to invoke (one of invoke, select, or deselect). + +proc ::tk::CheckRadioInvoke {w {cmd invoke}} { + if {[$w cget -state] ne "disabled"} { + uplevel #0 [list $w $cmd] + } +} + +# Special versions of the handlers for checkbuttons on Unix that do the magic +# to make things work right when the checkbutton indicator is hidden; +# radiobuttons don't need this complexity. + +# ::tk::CheckInvoke -- +# The procedure below invokes the checkbutton, like ButtonInvoke, but handles +# what to do when the checkbutton indicator is missing. Only used on Unix. +# +# Arguments: +# w - The name of the widget. + +proc ::tk::CheckInvoke {w} { + variable ::tk::Priv + if {[$w cget -state] ne "disabled"} { + # Additional logic to switch the "selected" colors around if necessary + # (when we're indicator-less). + + if {![$w cget -indicatoron] && [info exist Priv($w,selectcolor)]} { + if {[$w cget -selectcolor] eq $Priv($w,aselectcolor)} { + $w configure -selectcolor $Priv($w,selectcolor) + } else { + $w configure -selectcolor $Priv($w,aselectcolor) + } + } + uplevel #0 [list $w invoke] + } +} + +# ::tk::CheckEnter -- +# The procedure below enters the checkbutton, like ButtonEnter, but handles +# what to do when the checkbutton indicator is missing. Only used on Unix. +# +# Arguments: +# w - The name of the widget. + +proc ::tk::CheckEnter {w} { + variable ::tk::Priv + if {[$w cget -state] ne "disabled"} { + # On unix the state is active just with mouse-over + $w configure -state active + + # If the mouse button is down, set the relief to sunken on entry. + # Overwise, if there's an -overrelief value, set the relief to that. + + set Priv($w,relief) [$w cget -relief] + if {$Priv(buttonWindow) eq $w} { + $w configure -relief sunken + set Priv($w,prelief) sunken + } elseif {[set over [$w cget -overrelief]] ne ""} { + $w configure -relief $over + set Priv($w,prelief) $over + } + + # Compute what the "selected and active" color should be. + + if {![$w cget -indicatoron] && [$w cget -selectcolor] ne ""} { + set Priv($w,selectcolor) [$w cget -selectcolor] + lassign [winfo rgb $w [$w cget -selectcolor]] r1 g1 b1 + lassign [winfo rgb $w [$w cget -activebackground]] r2 g2 b2 + set Priv($w,aselectcolor) \ + [format "#%04x%04x%04x" [expr {($r1+$r2)/2}] \ + [expr {($g1+$g2)/2}] [expr {($b1+$b2)/2}]] + # use uplevel to work with other var resolvers + if {[uplevel #0 [list set [$w cget -variable]]] + eq [$w cget -onvalue]} { + $w configure -selectcolor $Priv($w,aselectcolor) + } + } + } + set Priv(window) $w +} + +# ::tk::CheckLeave -- +# The procedure below leaves the checkbutton, like ButtonLeave, but handles +# what to do when the checkbutton indicator is missing. Only used on Unix. +# +# Arguments: +# w - The name of the widget. + +proc ::tk::CheckLeave {w} { + variable ::tk::Priv + if {[$w cget -state] ne "disabled"} { + $w configure -state normal + } + + # Restore the original button "selected" color; but only if the user + # has not changed it in the meantime. + + if {![$w cget -indicatoron] && [info exist Priv($w,selectcolor)]} { + if {[$w cget -selectcolor] eq $Priv($w,selectcolor) + || ([info exist Priv($w,aselectcolor)] && + [$w cget -selectcolor] eq $Priv($w,aselectcolor))} { + $w configure -selectcolor $Priv($w,selectcolor) + } + } + unset -nocomplain Priv($w,selectcolor) Priv($w,aselectcolor) + + # Restore the original button relief if it was changed by Tk. That is + # signaled by the existence of Priv($w,prelief). + + if {[info exists Priv($w,relief)]} { + if {[info exists Priv($w,prelief)] && \ + $Priv($w,prelief) eq [$w cget -relief]} { + $w configure -relief $Priv($w,relief) + } + unset -nocomplain Priv($w,relief) Priv($w,prelief) + } + + set Priv(window) "" +} + +return + +# Local Variables: +# mode: tcl +# fill-column: 78 +# End: diff --git a/lib/tk8.6/choosedir.tcl b/lib/tk8.6/choosedir.tcl new file mode 100644 index 0000000000000000000000000000000000000000..33a66b19b3d9a77b8d794c1eb95e86e8ecd01bc2 --- /dev/null +++ b/lib/tk8.6/choosedir.tcl @@ -0,0 +1,310 @@ +# choosedir.tcl -- +# +# Choose directory dialog implementation for Unix/Mac. +# +# Copyright (c) 1998-2000 by Scriptics Corporation. +# All rights reserved. + +# Make sure the tk::dialog namespace, in which all dialogs should live, exists +namespace eval ::tk::dialog {} +namespace eval ::tk::dialog::file {} + +# Make the chooseDir namespace inside the dialog namespace +namespace eval ::tk::dialog::file::chooseDir { + namespace import -force ::tk::msgcat::* +} + +# ::tk::dialog::file::chooseDir:: -- +# +# Implements the TK directory selection dialog. +# +# Arguments: +# args Options parsed by the procedure. +# +proc ::tk::dialog::file::chooseDir:: {args} { + variable ::tk::Priv + set dataName __tk_choosedir + upvar ::tk::dialog::file::$dataName data + Config $dataName $args + + if {$data(-parent) eq "."} { + set w .$dataName + } else { + set w $data(-parent).$dataName + } + + # (re)create the dialog box if necessary + # + if {![winfo exists $w]} { + ::tk::dialog::file::Create $w TkChooseDir + } elseif {[winfo class $w] ne "TkChooseDir"} { + destroy $w + ::tk::dialog::file::Create $w TkChooseDir + } else { + set data(dirMenuBtn) $w.contents.f1.menu + set data(dirMenu) $w.contents.f1.menu.menu + set data(upBtn) $w.contents.f1.up + set data(icons) $w.contents.icons + set data(ent) $w.contents.f2.ent + set data(okBtn) $w.contents.f2.ok + set data(cancelBtn) $w.contents.f2.cancel + set data(hiddenBtn) $w.contents.f2.hidden + } + if {$::tk::dialog::file::showHiddenBtn} { + $data(hiddenBtn) configure -state normal + grid $data(hiddenBtn) + } else { + $data(hiddenBtn) configure -state disabled + grid remove $data(hiddenBtn) + } + + # When using -mustexist, manage the OK button state for validity + $data(okBtn) configure -state normal + if {$data(-mustexist)} { + $data(ent) configure -validate key \ + -validatecommand [list ::tk::dialog::file::chooseDir::IsOK? $w %P] + } else { + $data(ent) configure -validate none + } + + # Dialog boxes should be transient with respect to their parent, + # so that they will always stay on top of their parent window. However, + # some window managers will create the window as withdrawn if the parent + # window is withdrawn or iconified. Combined with the grab we put on the + # window, this can hang the entire application. Therefore we only make + # the dialog transient if the parent is viewable. + + if {[winfo viewable [winfo toplevel $data(-parent)]] } { + wm transient $w $data(-parent) + } + + trace add variable data(selectPath) write \ + [list ::tk::dialog::file::SetPath $w] + $data(dirMenuBtn) configure \ + -textvariable ::tk::dialog::file::${dataName}(selectPath) + + set data(filter) "*" + set data(previousEntryText) "" + ::tk::dialog::file::UpdateWhenIdle $w + + # Withdraw the window, then update all the geometry information + # so we know how big it wants to be, then center the window in the + # display (Motif style) and de-iconify it. + + ::tk::PlaceWindow $w widget $data(-parent) + wm title $w $data(-title) + + # Set a grab and claim the focus too. + + ::tk::SetFocusGrab $w $data(ent) + $data(ent) delete 0 end + $data(ent) insert 0 $data(selectPath) + $data(ent) selection range 0 end + $data(ent) icursor end + + # Wait for the user to respond, then restore the focus and + # return the index of the selected button. Restore the focus + # before deleting the window, since otherwise the window manager + # may take the focus away so we can't redirect it. Finally, + # restore any grab that was in effect. + + vwait ::tk::Priv(selectFilePath) + + ::tk::RestoreFocusGrab $w $data(ent) withdraw + + # Cleanup traces on selectPath variable + # + + foreach trace [trace info variable data(selectPath)] { + trace remove variable data(selectPath) [lindex $trace 0] [lindex $trace 1] + } + if {[winfo exists $data(dirMenuBtn)]} { + $data(dirMenuBtn) configure -textvariable {} + } + + # Return value to user + # + + return $Priv(selectFilePath) +} + +# ::tk::dialog::file::chooseDir::Config -- +# +# Configures the Tk choosedir dialog according to the argument list +# +proc ::tk::dialog::file::chooseDir::Config {dataName argList} { + upvar ::tk::dialog::file::$dataName data + + # 0: Delete all variable that were set on data(selectPath) the + # last time the file dialog is used. The traces may cause troubles + # if the dialog is now used with a different -parent option. + # + foreach trace [trace info variable data(selectPath)] { + trace remove variable data(selectPath) [lindex $trace 0] [lindex $trace 1] + } + + # 1: the configuration specs + # + set specs { + {-mustexist "" "" 0} + {-initialdir "" "" ""} + {-parent "" "" "."} + {-title "" "" ""} + } + + # 2: default values depending on the type of the dialog + # + if {![info exists data(selectPath)]} { + # first time the dialog has been popped up + set data(selectPath) [pwd] + } + + # 3: parse the arguments + # + tclParseConfigSpec ::tk::dialog::file::$dataName $specs "" $argList + + if {$data(-title) eq ""} { + set data(-title) "[mc "Choose Directory"]" + } + + # Stub out the -multiple value for the dialog; it doesn't make sense for + # choose directory dialogs, but we have to have something there because we + # share so much code with the file dialogs. + set data(-multiple) 0 + + # 4: set the default directory and selection according to the -initial + # settings + # + if {$data(-initialdir) ne ""} { + # Ensure that initialdir is an absolute path name. + if {[file isdirectory $data(-initialdir)]} { + set old [pwd] + cd $data(-initialdir) + set data(selectPath) [pwd] + cd $old + } else { + set data(selectPath) [pwd] + } + } + + if {![winfo exists $data(-parent)]} { + return -code error -errorcode [list TK LOOKUP WINDOW $data(-parent)] \ + "bad window path name \"$data(-parent)\"" + } +} + +# Gets called when user presses Return in the "Selection" entry or presses OK. +# +proc ::tk::dialog::file::chooseDir::OkCmd {w} { + upvar ::tk::dialog::file::[winfo name $w] data + + # This is the brains behind selecting non-existant directories. Here's + # the flowchart: + # 1. If the icon list has a selection, join it with the current dir, + # and return that value. + # 1a. If the icon list does not have a selection ... + # 2. If the entry is empty, do nothing. + # 3. If the entry contains an invalid directory, then... + # 3a. If the value is the same as last time through here, end dialog. + # 3b. If the value is different than last time, save it and return. + # 4. If entry contains a valid directory, then... + # 4a. If the value is the same as the current directory, end dialog. + # 4b. If the value is different from the current directory, change to + # that directory. + + set selection [$data(icons) selection get] + if {[llength $selection] != 0} { + set iconText [$data(icons) get [lindex $selection 0]] + set iconText [file join $data(selectPath) $iconText] + Done $w $iconText + } else { + set text [$data(ent) get] + if {$text eq ""} { + return + } + set text [file join {*}[file split [string trim $text]]] + if {![file exists $text] || ![file isdirectory $text]} { + # Entry contains an invalid directory. If it's the same as the + # last time they came through here, reset the saved value and end + # the dialog. Otherwise, save the value (so we can do this test + # next time). + if {$text eq $data(previousEntryText)} { + set data(previousEntryText) "" + Done $w $text + } else { + set data(previousEntryText) $text + } + } else { + # Entry contains a valid directory. If it is the same as the + # current directory, end the dialog. Otherwise, change to that + # directory. + if {$text eq $data(selectPath)} { + Done $w $text + } else { + set data(selectPath) $text + } + } + } + return +} + +# Change state of OK button to match -mustexist correctness of entry +# +proc ::tk::dialog::file::chooseDir::IsOK? {w text} { + upvar ::tk::dialog::file::[winfo name $w] data + + set ok [file isdirectory $text] + $data(okBtn) configure -state [expr {$ok ? "normal" : "disabled"}] + + # always return 1 + return 1 +} + +proc ::tk::dialog::file::chooseDir::DblClick {w} { + upvar ::tk::dialog::file::[winfo name $w] data + set selection [$data(icons) selection get] + if {[llength $selection] != 0} { + set filenameFragment [$data(icons) get [lindex $selection 0]] + set file $data(selectPath) + if {[file isdirectory $file]} { + ::tk::dialog::file::ListInvoke $w [list $filenameFragment] + return + } + } +} + +# Gets called when user browses the IconList widget (dragging mouse, arrow +# keys, etc) +# +proc ::tk::dialog::file::chooseDir::ListBrowse {w text} { + upvar ::tk::dialog::file::[winfo name $w] data + + if {$text eq ""} { + return + } + + set file [::tk::dialog::file::JoinFile $data(selectPath) $text] + $data(ent) delete 0 end + $data(ent) insert 0 $file +} + +# ::tk::dialog::file::chooseDir::Done -- +# +# Gets called when user has input a valid filename. Pops up a +# dialog box to confirm selection when necessary. Sets the +# Priv(selectFilePath) variable, which will break the "vwait" +# loop in tk_chooseDirectory and return the selected filename to the +# script that calls tk_getOpenFile or tk_getSaveFile +# +proc ::tk::dialog::file::chooseDir::Done {w {selectFilePath ""}} { + upvar ::tk::dialog::file::[winfo name $w] data + variable ::tk::Priv + + if {$selectFilePath eq ""} { + set selectFilePath $data(selectPath) + } + if {$data(-mustexist) && ![file isdirectory $selectFilePath]} { + return + } + set Priv(selectFilePath) $selectFilePath +} diff --git a/lib/tk8.6/clrpick.tcl b/lib/tk8.6/clrpick.tcl new file mode 100644 index 0000000000000000000000000000000000000000..cc38005c74fa4a9067520b85e4460ec0e4a71bb0 --- /dev/null +++ b/lib/tk8.6/clrpick.tcl @@ -0,0 +1,694 @@ +# clrpick.tcl -- +# +# Color selection dialog for platforms that do not support a +# standard color selection dialog. +# +# Copyright (c) 1996 Sun Microsystems, Inc. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# ToDo: +# +# (1): Find out how many free colors are left in the colormap and +# don't allocate too many colors. +# (2): Implement HSV color selection. +# + +# Make sure namespaces exist +namespace eval ::tk {} +namespace eval ::tk::dialog {} +namespace eval ::tk::dialog::color { + namespace import ::tk::msgcat::* +} + +# ::tk::dialog::color:: -- +# +# Create a color dialog and let the user choose a color. This function +# should not be called directly. It is called by the tk_chooseColor +# function when a native color selector widget does not exist +# +proc ::tk::dialog::color:: {args} { + variable ::tk::Priv + set dataName __tk__color + upvar ::tk::dialog::color::$dataName data + set w .$dataName + + # The lines variables track the start and end indices of the line + # elements in the colorbar canvases. + set data(lines,red,start) 0 + set data(lines,red,last) -1 + set data(lines,green,start) 0 + set data(lines,green,last) -1 + set data(lines,blue,start) 0 + set data(lines,blue,last) -1 + + # This is the actual number of lines that are drawn in each color strip. + # Note that the bars may be of any width. + # However, NUM_COLORBARS must be a number that evenly divides 256. + # Such as 256, 128, 64, etc. + set data(NUM_COLORBARS) 16 + + # BARS_WIDTH is the number of pixels wide the color bar portion of the + # canvas is. This number must be a multiple of NUM_COLORBARS + set data(BARS_WIDTH) 160 + + # PLGN_WIDTH is the number of pixels wide of the triangular selection + # polygon. This also results in the definition of the padding on the + # left and right sides which is half of PLGN_WIDTH. Make this number even. + set data(PLGN_HEIGHT) 10 + + # PLGN_HEIGHT is the height of the selection polygon and the height of the + # selection rectangle at the bottom of the color bar. No restrictions. + set data(PLGN_WIDTH) 10 + + Config $dataName $args + InitValues $dataName + + set sc [winfo screen $data(-parent)] + set winExists [winfo exists $w] + if {!$winExists || $sc ne [winfo screen $w]} { + if {$winExists} { + destroy $w + } + toplevel $w -class TkColorDialog -screen $sc + if {[tk windowingsystem] eq "x11"} {wm attributes $w -type dialog} + BuildDialog $w + } + + # Dialog boxes should be transient with respect to their parent, + # so that they will always stay on top of their parent window. However, + # some window managers will create the window as withdrawn if the parent + # window is withdrawn or iconified. Combined with the grab we put on the + # window, this can hang the entire application. Therefore we only make + # the dialog transient if the parent is viewable. + + if {[winfo viewable [winfo toplevel $data(-parent)]] } { + wm transient $w $data(-parent) + } + + # 5. Withdraw the window, then update all the geometry information + # so we know how big it wants to be, then center the window in the + # display (Motif style) and de-iconify it. + + ::tk::PlaceWindow $w widget $data(-parent) + wm title $w $data(-title) + + # 6. Set a grab and claim the focus too. + + ::tk::SetFocusGrab $w $data(okBtn) + + # 7. Wait for the user to respond, then restore the focus and + # return the index of the selected button. Restore the focus + # before deleting the window, since otherwise the window manager + # may take the focus away so we can't redirect it. Finally, + # restore any grab that was in effect. + + vwait ::tk::Priv(selectColor) + set result $Priv(selectColor) + ::tk::RestoreFocusGrab $w $data(okBtn) + unset data + + return $result +} + +# ::tk::dialog::color::InitValues -- +# +# Get called during initialization or when user resets NUM_COLORBARS +# +proc ::tk::dialog::color::InitValues {dataName} { + upvar ::tk::dialog::color::$dataName data + + # IntensityIncr is the difference in color intensity between a colorbar + # and its neighbors. + set data(intensityIncr) [expr {256 / $data(NUM_COLORBARS)}] + + # ColorbarWidth is the width of each colorbar + set data(colorbarWidth) [expr {$data(BARS_WIDTH) / $data(NUM_COLORBARS)}] + + # Indent is the width of the space at the left and right side of the + # colorbar. It is always half the selector polygon width, because the + # polygon extends into the space. + set data(indent) [expr {$data(PLGN_WIDTH) / 2}] + + set data(colorPad) 2 + set data(selPad) [expr {$data(PLGN_WIDTH) / 2}] + + # + # minX is the x coordinate of the first colorbar + # + set data(minX) $data(indent) + + # + # maxX is the x coordinate of the last colorbar + # + set data(maxX) [expr {$data(BARS_WIDTH) + $data(indent)-1}] + + # + # canvasWidth is the width of the entire canvas, including the indents + # + set data(canvasWidth) [expr {$data(BARS_WIDTH) + $data(PLGN_WIDTH)}] + + # Set the initial color, specified by -initialcolor, or the + # color chosen by the user the last time. + set data(selection) $data(-initialcolor) + set data(finalColor) $data(-initialcolor) + set rgb [winfo rgb . $data(selection)] + + set data(red,intensity) [expr {[lindex $rgb 0]/0x100}] + set data(green,intensity) [expr {[lindex $rgb 1]/0x100}] + set data(blue,intensity) [expr {[lindex $rgb 2]/0x100}] +} + +# ::tk::dialog::color::Config -- +# +# Parses the command line arguments to tk_chooseColor +# +proc ::tk::dialog::color::Config {dataName argList} { + variable ::tk::Priv + upvar ::tk::dialog::color::$dataName data + + # 1: the configuration specs + # + if {[info exists Priv(selectColor)] && $Priv(selectColor) ne ""} { + set defaultColor $Priv(selectColor) + } else { + set defaultColor [. cget -background] + } + + set specs [list \ + [list -initialcolor "" "" $defaultColor] \ + [list -parent "" "" "."] \ + [list -title "" "" [mc "Color"]] \ + ] + + # 2: parse the arguments + # + tclParseConfigSpec ::tk::dialog::color::$dataName $specs "" $argList + + if {$data(-title) eq ""} { + set data(-title) " " + } + if {[catch {winfo rgb . $data(-initialcolor)} err]} { + return -code error -errorcode [list TK LOOKUP COLOR $data(-initialcolor)] \ + $err + } + + if {![winfo exists $data(-parent)]} { + return -code error -errorcode [list TK LOOKUP WINDOW $data(-parent)] \ + "bad window path name \"$data(-parent)\"" + } +} + +# ::tk::dialog::color::BuildDialog -- +# +# Build the dialog. +# +proc ::tk::dialog::color::BuildDialog {w} { + upvar ::tk::dialog::color::[winfo name $w] data + + # TopFrame contains the color strips and the color selection + # + set topFrame [frame $w.top -relief raised -bd 1] + + # StripsFrame contains the colorstrips and the individual RGB entries + set stripsFrame [frame $topFrame.colorStrip] + + set maxWidth [::tk::mcmaxamp &Red &Green &Blue] + set maxWidth [expr {$maxWidth<6 ? 6 : $maxWidth}] + set colorList { + red "&Red" + green "&Green" + blue "&Blue" + } + foreach {color l} $colorList { + # each f frame contains an [R|G|B] entry and the equiv. color strip. + set f [frame $stripsFrame.$color] + + # The box frame contains the label and entry widget for an [R|G|B] + set box [frame $f.box] + + ::tk::AmpWidget label $box.label -text "[mc $l]:" \ + -width $maxWidth -anchor ne + bind $box.label <> [list focus $box.entry] + + entry $box.entry -textvariable \ + ::tk::dialog::color::[winfo name $w]($color,intensity) \ + -width 4 + pack $box.label -side left -fill y -padx 2 -pady 3 + pack $box.entry -side left -anchor n -pady 0 + pack $box -side left -fill both + + set height [expr { + [winfo reqheight $box.entry] - + 2*([$box.entry cget -highlightthickness] + [$box.entry cget -bd]) + }] + + canvas $f.color -height $height \ + -width $data(BARS_WIDTH) -relief sunken -bd 2 + canvas $f.sel -height $data(PLGN_HEIGHT) \ + -width $data(canvasWidth) -highlightthickness 0 + pack $f.color -expand yes -fill both + pack $f.sel -expand yes -fill both + + pack $f -side top -fill x -padx 0 -pady 2 + + set data($color,entry) $box.entry + set data($color,col) $f.color + set data($color,sel) $f.sel + + bind $data($color,col) \ + [list tk::dialog::color::DrawColorScale $w $color 1] + bind $data($color,col) \ + [list tk::dialog::color::EnterColorBar $w $color] + bind $data($color,col) \ + [list tk::dialog::color::LeaveColorBar $w $color] + + bind $data($color,sel) \ + [list tk::dialog::color::EnterColorBar $w $color] + bind $data($color,sel) \ + [list tk::dialog::color::LeaveColorBar $w $color] + + bind $box.entry [list tk::dialog::color::HandleRGBEntry $w] + } + + pack $stripsFrame -side left -fill both -padx 4 -pady 10 + + # The selFrame contains a frame that demonstrates the currently + # selected color + # + set selFrame [frame $topFrame.sel] + set lab [::tk::AmpWidget label $selFrame.lab \ + -text [mc "&Selection:"] -anchor sw] + set ent [entry $selFrame.ent \ + -textvariable ::tk::dialog::color::[winfo name $w](selection) \ + -width 16] + set f1 [frame $selFrame.f1 -relief sunken -bd 2] + set data(finalCanvas) [frame $f1.demo -bd 0 -width 100 -height 70] + + pack $lab $ent -side top -fill x -padx 4 -pady 2 + pack $f1 -expand yes -anchor nw -fill both -padx 6 -pady 10 + pack $data(finalCanvas) -expand yes -fill both + + bind $ent [list tk::dialog::color::HandleSelEntry $w] + + pack $selFrame -side left -fill none -anchor nw + pack $topFrame -side top -expand yes -fill both -anchor nw + + # the botFrame frame contains the buttons + # + set botFrame [frame $w.bot -relief raised -bd 1] + + ::tk::AmpWidget button $botFrame.ok -text [mc "&OK"] \ + -command [list tk::dialog::color::OkCmd $w] + ::tk::AmpWidget button $botFrame.cancel -text [mc "&Cancel"] \ + -command [list tk::dialog::color::CancelCmd $w] + + set data(okBtn) $botFrame.ok + set data(cancelBtn) $botFrame.cancel + + grid x $botFrame.ok x $botFrame.cancel x -sticky ew + grid configure $botFrame.ok $botFrame.cancel -padx 10 -pady 10 + grid columnconfigure $botFrame {0 4} -weight 1 -uniform space + grid columnconfigure $botFrame {1 3} -weight 1 -uniform button + grid columnconfigure $botFrame 2 -weight 2 -uniform space + pack $botFrame -side bottom -fill x + + # Accelerator bindings + bind $lab <> [list focus $ent] + bind $w [list tk::ButtonInvoke $data(cancelBtn)] + bind $w [list tk::AltKeyInDialog $w %A] + + wm protocol $w WM_DELETE_WINDOW [list tk::dialog::color::CancelCmd $w] +} + +# ::tk::dialog::color::SetRGBValue -- +# +# Sets the current selection of the dialog box +# +proc ::tk::dialog::color::SetRGBValue {w color} { + upvar ::tk::dialog::color::[winfo name $w] data + + set data(red,intensity) [lindex $color 0] + set data(green,intensity) [lindex $color 1] + set data(blue,intensity) [lindex $color 2] + + RedrawColorBars $w all + + # Now compute the new x value of each colorbars pointer polygon + foreach color {red green blue} { + set x [RgbToX $w $data($color,intensity)] + MoveSelector $w $data($color,sel) $color $x 0 + } +} + +# ::tk::dialog::color::XToRgb -- +# +# Converts a screen coordinate to intensity +# +proc ::tk::dialog::color::XToRgb {w x} { + upvar ::tk::dialog::color::[winfo name $w] data + + set x [expr {($x * $data(intensityIncr))/ $data(colorbarWidth)}] + if {$x > 255} { + set x 255 + } + return $x +} + +# ::tk::dialog::color::RgbToX +# +# Converts an intensity to screen coordinate. +# +proc ::tk::dialog::color::RgbToX {w color} { + upvar ::tk::dialog::color::[winfo name $w] data + + return [expr {($color * $data(colorbarWidth)/ $data(intensityIncr))}] +} + +# ::tk::dialog::color::DrawColorScale -- +# +# Draw color scale is called whenever the size of one of the color +# scale canvases is changed. +# +proc ::tk::dialog::color::DrawColorScale {w c {create 0}} { + upvar ::tk::dialog::color::[winfo name $w] data + + # col: color bar canvas + # sel: selector canvas + set col $data($c,col) + set sel $data($c,sel) + + # First handle the case that we are creating everything for the first time. + if {$create} { + # First remove all the lines that already exist. + if { $data(lines,$c,last) > $data(lines,$c,start)} { + for {set i $data(lines,$c,start)} \ + {$i <= $data(lines,$c,last)} {incr i} { + $sel delete $i + } + } + # Delete the selector if it exists + if {[info exists data($c,index)]} { + $sel delete $data($c,index) + } + + # Draw the selection polygons + CreateSelector $w $sel $c + $sel bind $data($c,index) \ + [list tk::dialog::color::StartMove $w $sel $c %x $data(selPad) 1] + $sel bind $data($c,index) \ + [list tk::dialog::color::MoveSelector $w $sel $c %x $data(selPad)] + $sel bind $data($c,index) \ + [list tk::dialog::color::ReleaseMouse $w $sel $c %x $data(selPad)] + + set height [winfo height $col] + # Create an invisible region under the colorstrip to catch mouse clicks + # that aren't on the selector. + set data($c,clickRegion) [$sel create rectangle 0 0 \ + $data(canvasWidth) $height -fill {} -outline {}] + + bind $col \ + [list tk::dialog::color::StartMove $w $sel $c %x $data(colorPad)] + bind $col \ + [list tk::dialog::color::MoveSelector $w $sel $c %x $data(colorPad)] + bind $col \ + [list tk::dialog::color::ReleaseMouse $w $sel $c %x $data(colorPad)] + + $sel bind $data($c,clickRegion) \ + [list tk::dialog::color::StartMove $w $sel $c %x $data(selPad)] + $sel bind $data($c,clickRegion) \ + [list tk::dialog::color::MoveSelector $w $sel $c %x $data(selPad)] + $sel bind $data($c,clickRegion) \ + [list tk::dialog::color::ReleaseMouse $w $sel $c %x $data(selPad)] + } else { + # l is the canvas index of the first colorbar. + set l $data(lines,$c,start) + } + + # Draw the color bars. + set highlightW [expr {[$col cget -highlightthickness] + [$col cget -bd]}] + for {set i 0} { $i < $data(NUM_COLORBARS)} { incr i} { + set intensity [expr {$i * $data(intensityIncr)}] + set startx [expr {$i * $data(colorbarWidth) + $highlightW}] + if {$c eq "red"} { + set color [format "#%02x%02x%02x" \ + $intensity $data(green,intensity) $data(blue,intensity)] + } elseif {$c eq "green"} { + set color [format "#%02x%02x%02x" \ + $data(red,intensity) $intensity $data(blue,intensity)] + } else { + set color [format "#%02x%02x%02x" \ + $data(red,intensity) $data(green,intensity) $intensity] + } + + if {$create} { + set index [$col create rect $startx $highlightW \ + [expr {$startx +$data(colorbarWidth)}] \ + [expr {[winfo height $col] + $highlightW}] \ + -fill $color -outline $color] + } else { + $col itemconfigure $l -fill $color -outline $color + incr l + } + } + $sel raise $data($c,index) + + if {$create} { + set data(lines,$c,last) $index + set data(lines,$c,start) [expr {$index - $data(NUM_COLORBARS) + 1}] + } + + RedrawFinalColor $w +} + +# ::tk::dialog::color::CreateSelector -- +# +# Creates and draws the selector polygon at the position +# $data($c,intensity). +# +proc ::tk::dialog::color::CreateSelector {w sel c } { + upvar ::tk::dialog::color::[winfo name $w] data + set data($c,index) [$sel create polygon \ + 0 $data(PLGN_HEIGHT) \ + $data(PLGN_WIDTH) $data(PLGN_HEIGHT) \ + $data(indent) 0] + set data($c,x) [RgbToX $w $data($c,intensity)] + $sel move $data($c,index) $data($c,x) 0 +} + +# ::tk::dialog::color::RedrawFinalColor +# +# Combines the intensities of the three colors into the final color +# +proc ::tk::dialog::color::RedrawFinalColor {w} { + upvar ::tk::dialog::color::[winfo name $w] data + + set color [format "#%02x%02x%02x" $data(red,intensity) \ + $data(green,intensity) $data(blue,intensity)] + + $data(finalCanvas) configure -bg $color + set data(finalColor) $color + set data(selection) $color + set data(finalRGB) [list \ + $data(red,intensity) \ + $data(green,intensity) \ + $data(blue,intensity)] +} + +# ::tk::dialog::color::RedrawColorBars -- +# +# Only redraws the colors on the color strips that were not manipulated. +# Params: color of colorstrip that changed. If color is not [red|green|blue] +# Then all colorstrips will be updated +# +proc ::tk::dialog::color::RedrawColorBars {w colorChanged} { + upvar ::tk::dialog::color::[winfo name $w] data + + switch $colorChanged { + red { + DrawColorScale $w green + DrawColorScale $w blue + } + green { + DrawColorScale $w red + DrawColorScale $w blue + } + blue { + DrawColorScale $w red + DrawColorScale $w green + } + default { + DrawColorScale $w red + DrawColorScale $w green + DrawColorScale $w blue + } + } + RedrawFinalColor $w +} + +#---------------------------------------------------------------------- +# Event handlers +#---------------------------------------------------------------------- + +# ::tk::dialog::color::StartMove -- +# +# Handles a mousedown button event over the selector polygon. +# Adds the bindings for moving the mouse while the button is +# pressed. Sets the binding for the button-release event. +# +# Params: sel is the selector canvas window, color is the color of the strip. +# +proc ::tk::dialog::color::StartMove {w sel color x delta {dontMove 0}} { + upvar ::tk::dialog::color::[winfo name $w] data + + if {!$dontMove} { + MoveSelector $w $sel $color $x $delta + } +} + +# ::tk::dialog::color::MoveSelector -- +# +# Moves the polygon selector so that its middle point has the same +# x value as the specified x. If x is outside the bounds [0,255], +# the selector is set to the closest endpoint. +# +# Params: sel is the selector canvas, c is [red|green|blue] +# x is a x-coordinate. +# +proc ::tk::dialog::color::MoveSelector {w sel color x delta} { + upvar ::tk::dialog::color::[winfo name $w] data + + incr x -$delta + + if { $x < 0 } { + set x 0 + } elseif { $x > $data(BARS_WIDTH)} { + set x $data(BARS_WIDTH) + } + set diff [expr {$x - $data($color,x)}] + $sel move $data($color,index) $diff 0 + set data($color,x) [expr {$data($color,x) + $diff}] + + # Return the x value that it was actually set at + return $x +} + +# ::tk::dialog::color::ReleaseMouse +# +# Removes mouse tracking bindings, updates the colorbars. +# +# Params: sel is the selector canvas, color is the color of the strip, +# x is the x-coord of the mouse. +# +proc ::tk::dialog::color::ReleaseMouse {w sel color x delta} { + upvar ::tk::dialog::color::[winfo name $w] data + + set x [MoveSelector $w $sel $color $x $delta] + + # Determine exactly what color we are looking at. + set data($color,intensity) [XToRgb $w $x] + + RedrawColorBars $w $color +} + +# ::tk::dialog::color::ResizeColorbars -- +# +# Completely redraws the colorbars, including resizing the +# colorstrips +# +proc ::tk::dialog::color::ResizeColorBars {w} { + upvar ::tk::dialog::color::[winfo name $w] data + + if { + ($data(BARS_WIDTH) < $data(NUM_COLORBARS)) || + (($data(BARS_WIDTH) % $data(NUM_COLORBARS)) != 0) + } then { + set data(BARS_WIDTH) $data(NUM_COLORBARS) + } + InitValues [winfo name $w] + foreach color {red green blue} { + $data($color,col) configure -width $data(canvasWidth) + DrawColorScale $w $color 1 + } +} + +# ::tk::dialog::color::HandleSelEntry -- +# +# Handles the return keypress event in the "Selection:" entry +# +proc ::tk::dialog::color::HandleSelEntry {w} { + upvar ::tk::dialog::color::[winfo name $w] data + + set text [string trim $data(selection)] + # Check to make sure that the color is valid + if {[catch {set color [winfo rgb . $text]} ]} { + set data(selection) $data(finalColor) + return + } + + set R [expr {[lindex $color 0]/0x100}] + set G [expr {[lindex $color 1]/0x100}] + set B [expr {[lindex $color 2]/0x100}] + + SetRGBValue $w "$R $G $B" + set data(selection) $text +} + +# ::tk::dialog::color::HandleRGBEntry -- +# +# Handles the return keypress event in the R, G or B entry +# +proc ::tk::dialog::color::HandleRGBEntry {w} { + upvar ::tk::dialog::color::[winfo name $w] data + + foreach c {red green blue} { + if {[catch { + set data($c,intensity) [expr {int($data($c,intensity))}] + }]} { + set data($c,intensity) 0 + } + + if {$data($c,intensity) < 0} { + set data($c,intensity) 0 + } + if {$data($c,intensity) > 255} { + set data($c,intensity) 255 + } + } + + SetRGBValue $w "$data(red,intensity) \ + $data(green,intensity) $data(blue,intensity)" +} + +# mouse cursor enters a color bar +# +proc ::tk::dialog::color::EnterColorBar {w color} { + upvar ::tk::dialog::color::[winfo name $w] data + + $data($color,sel) itemconfigure $data($color,index) -fill red +} + +# mouse leaves enters a color bar +# +proc ::tk::dialog::color::LeaveColorBar {w color} { + upvar ::tk::dialog::color::[winfo name $w] data + + $data($color,sel) itemconfigure $data($color,index) -fill black +} + +# user hits OK button +# +proc ::tk::dialog::color::OkCmd {w} { + variable ::tk::Priv + upvar ::tk::dialog::color::[winfo name $w] data + + set Priv(selectColor) $data(finalColor) +} + +# user hits Cancel button or destroys window +# +proc ::tk::dialog::color::CancelCmd {w} { + variable ::tk::Priv + set Priv(selectColor) "" +} diff --git a/lib/tk8.6/comdlg.tcl b/lib/tk8.6/comdlg.tcl new file mode 100644 index 0000000000000000000000000000000000000000..b4d89785423050c49045b453893ca51316072ac5 --- /dev/null +++ b/lib/tk8.6/comdlg.tcl @@ -0,0 +1,322 @@ +# comdlg.tcl -- +# +# Some functions needed for the common dialog boxes. Probably need to go +# in a different file. +# +# Copyright (c) 1996 Sun Microsystems, Inc. +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# + +# tclParseConfigSpec -- +# +# Parses a list of "-option value" pairs. If all options and +# values are legal, the values are stored in +# $data($option). Otherwise an error message is returned. When +# an error happens, the data() array may have been partially +# modified, but all the modified members of the data(0 array are +# guaranteed to have valid values. This is different than +# Tk_ConfigureWidget() which does not modify the value of a +# widget record if any error occurs. +# +# Arguments: +# +# w = widget record to modify. Must be the pathname of a widget. +# +# specs = { +# {-commandlineswitch resourceName ResourceClass defaultValue verifier} +# {....} +# } +# +# flags = a list of flags. Currently supported flags are: +# DONTSETDEFAULTS = skip default values setting +# +# argList = The list of "-option value" pairs. +# +proc tclParseConfigSpec {w specs flags argList} { + upvar #0 $w data + + # 1: Put the specs in associative arrays for faster access + # + foreach spec $specs { + if {[llength $spec] < 4} { + return -code error -errorcode {TK VALUE CONFIG_SPEC} \ + "\"spec\" should contain 5 or 4 elements" + } + set cmdsw [lindex $spec 0] + set cmd($cmdsw) "" + set rname($cmdsw) [lindex $spec 1] + set rclass($cmdsw) [lindex $spec 2] + set def($cmdsw) [lindex $spec 3] + set verproc($cmdsw) [lindex $spec 4] + } + + if {[llength $argList] & 1} { + set cmdsw [lindex $argList end] + if {![info exists cmd($cmdsw)]} { + return -code error -errorcode [list TK LOOKUP OPTION $cmdsw] \ + "bad option \"$cmdsw\": must be [tclListValidFlags cmd]" + } + return -code error -errorcode {TK VALUE_MISSING} \ + "value for \"$cmdsw\" missing" + } + + # 2: set the default values + # + if {"DONTSETDEFAULTS" ni $flags} { + foreach cmdsw [array names cmd] { + set data($cmdsw) $def($cmdsw) + } + } + + # 3: parse the argument list + # + foreach {cmdsw value} $argList { + if {![info exists cmd($cmdsw)]} { + return -code error -errorcode [list TK LOOKUP OPTION $cmdsw] \ + "bad option \"$cmdsw\": must be [tclListValidFlags cmd]" + } + set data($cmdsw) $value + } + + # Done! +} + +proc tclListValidFlags {v} { + upvar $v cmd + + set len [llength [array names cmd]] + set i 1 + set separator "" + set errormsg "" + foreach cmdsw [lsort [array names cmd]] { + append errormsg "$separator$cmdsw" + incr i + if {$i == $len} { + set separator ", or " + } else { + set separator ", " + } + } + return $errormsg +} + +#---------------------------------------------------------------------- +# +# Focus Group +# +# Focus groups are used to handle the user's focusing actions inside a +# toplevel. +# +# One example of using focus groups is: when the user focuses on an +# entry, the text in the entry is highlighted and the cursor is put to +# the end of the text. When the user changes focus to another widget, +# the text in the previously focused entry is validated. +# +#---------------------------------------------------------------------- + + +# ::tk::FocusGroup_Create -- +# +# Create a focus group. All the widgets in a focus group must be +# within the same focus toplevel. Each toplevel can have only +# one focus group, which is identified by the name of the +# toplevel widget. +# +proc ::tk::FocusGroup_Create {t} { + variable ::tk::Priv + if {[winfo toplevel $t] ne $t} { + return -code error -errorcode [list TK LOOKUP TOPLEVEL $t] \ + "$t is not a toplevel window" + } + if {![info exists Priv(fg,$t)]} { + set Priv(fg,$t) 1 + set Priv(focus,$t) "" + bind $t [list tk::FocusGroup_In $t %W %d] + bind $t [list tk::FocusGroup_Out $t %W %d] + bind $t [list tk::FocusGroup_Destroy $t %W] + } +} + +# ::tk::FocusGroup_BindIn -- +# +# Add a widget into the "FocusIn" list of the focus group. The $cmd will be +# called when the widget is focused on by the user. +# +proc ::tk::FocusGroup_BindIn {t w cmd} { + variable FocusIn + variable ::tk::Priv + if {![info exists Priv(fg,$t)]} { + return -code error -errorcode [list TK LOOKUP FOCUS_GROUP $t] \ + "focus group \"$t\" doesn't exist" + } + set FocusIn($t,$w) $cmd +} + + +# ::tk::FocusGroup_BindOut -- +# +# Add a widget into the "FocusOut" list of the focus group. The +# $cmd will be called when the widget loses the focus (User +# types Tab or click on another widget). +# +proc ::tk::FocusGroup_BindOut {t w cmd} { + variable FocusOut + variable ::tk::Priv + if {![info exists Priv(fg,$t)]} { + return -code error -errorcode [list TK LOOKUP FOCUS_GROUP $t] \ + "focus group \"$t\" doesn't exist" + } + set FocusOut($t,$w) $cmd +} + +# ::tk::FocusGroup_Destroy -- +# +# Cleans up when members of the focus group is deleted, or when the +# toplevel itself gets deleted. +# +proc ::tk::FocusGroup_Destroy {t w} { + variable FocusIn + variable FocusOut + variable ::tk::Priv + + if {$t eq $w} { + unset Priv(fg,$t) + unset Priv(focus,$t) + + foreach name [array names FocusIn $t,*] { + unset FocusIn($name) + } + foreach name [array names FocusOut $t,*] { + unset FocusOut($name) + } + } else { + if {[info exists Priv(focus,$t)] && ($Priv(focus,$t) eq $w)} { + set Priv(focus,$t) "" + } + unset -nocomplain FocusIn($t,$w) FocusOut($t,$w) + } +} + +# ::tk::FocusGroup_In -- +# +# Handles the event. Calls the FocusIn command for the newly +# focused widget in the focus group. +# +proc ::tk::FocusGroup_In {t w detail} { + variable FocusIn + variable ::tk::Priv + + if {$detail ne "NotifyNonlinear" && $detail ne "NotifyNonlinearVirtual"} { + # This is caused by mouse moving out&in of the window *or* + # ordinary keypresses some window managers (ie: CDE [Bug: 2960]). + return + } + if {![info exists FocusIn($t,$w)]} { + set FocusIn($t,$w) "" + return + } + if {![info exists Priv(focus,$t)]} { + return + } + if {$Priv(focus,$t) eq $w} { + # This is already in focus + # + return + } else { + set Priv(focus,$t) $w + eval $FocusIn($t,$w) + } +} + +# ::tk::FocusGroup_Out -- +# +# Handles the event. Checks if this is really a lose +# focus event, not one generated by the mouse moving out of the +# toplevel window. Calls the FocusOut command for the widget +# who loses its focus. +# +proc ::tk::FocusGroup_Out {t w detail} { + variable FocusOut + variable ::tk::Priv + + if {$detail ne "NotifyNonlinear" && $detail ne "NotifyNonlinearVirtual"} { + # This is caused by mouse moving out of the window + return + } + if {![info exists Priv(focus,$t)]} { + return + } + if {![info exists FocusOut($t,$w)]} { + return + } else { + eval $FocusOut($t,$w) + set Priv(focus,$t) "" + } +} + +# ::tk::FDGetFileTypes -- +# +# Process the string given by the -filetypes option of the file +# dialogs. Similar to the C function TkGetFileFilters() on the Mac +# and Windows platform. +# +proc ::tk::FDGetFileTypes {string} { + foreach t $string { + if {[llength $t] < 2 || [llength $t] > 3} { + return -code error -errorcode {TK VALUE FILE_TYPE} \ + "bad file type \"$t\", should be \"typeName {extension ?extensions ...?} ?{macType ?macTypes ...?}?\"" + } + lappend fileTypes([lindex $t 0]) {*}[lindex $t 1] + } + + set types {} + foreach t $string { + set label [lindex $t 0] + set exts {} + + if {[info exists hasDoneType($label)]} { + continue + } + + # Validate each macType. This is to agree with the + # behaviour of TkGetFileFilters(). This list may be + # empty. + foreach macType [lindex $t 2] { + if {[string length $macType] != 4} { + return -code error -errorcode {TK VALUE MAC_TYPE} \ + "bad Macintosh file type \"$macType\"" + } + } + + set name "$label \(" + set sep "" + set doAppend 1 + foreach ext $fileTypes($label) { + if {$ext eq ""} { + continue + } + regsub {^[.]} $ext "*." ext + if {![info exists hasGotExt($label,$ext)]} { + if {$doAppend} { + if {[string length $sep] && [string length $name]>40} { + set doAppend 0 + append name $sep... + } else { + append name $sep$ext + } + } + lappend exts $ext + set hasGotExt($label,$ext) 1 + } + set sep "," + } + append name "\)" + lappend types [list $name $exts] + + set hasDoneType($label) 1 + } + + return $types +} diff --git a/lib/tk8.6/console.tcl b/lib/tk8.6/console.tcl new file mode 100644 index 0000000000000000000000000000000000000000..d73b2f5c81b883d391367d8c4059ad9d04e4541a --- /dev/null +++ b/lib/tk8.6/console.tcl @@ -0,0 +1,1154 @@ +# console.tcl -- +# +# This code constructs the console window for an application. It +# can be used by non-unix systems that do not have built-in support +# for shells. +# +# Copyright (c) 1995-1997 Sun Microsystems, Inc. +# Copyright (c) 1998-2000 Ajuba Solutions. +# Copyright (c) 2007-2008 Daniel A. Steffen +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# + +# TODO: history - remember partially written command + +namespace eval ::tk::console { + variable blinkTime 500 ; # msecs to blink braced range for + variable blinkRange 1 ; # enable blinking of the entire braced range + variable magicKeys 1 ; # enable brace matching and proc/var recognition + variable maxLines 600 ; # maximum # of lines buffered in console + variable showMatches 1 ; # show multiple expand matches + variable useFontchooser [llength [info command ::tk::fontchooser]] + variable inPlugin [info exists embed_args] + variable defaultPrompt ; # default prompt if tcl_prompt1 isn't used + + if {$inPlugin} { + set defaultPrompt {subst {[history nextid] % }} + } else { + set defaultPrompt {subst {([file tail [pwd]]) [history nextid] % }} + } +} + +# simple compat function for tkcon code added for this console +interp alias {} EvalAttached {} consoleinterp eval + +# ::tk::ConsoleInit -- +# This procedure constructs and configures the console windows. +# +# Arguments: +# None. + +proc ::tk::ConsoleInit {} { + if {![consoleinterp eval {set tcl_interactive}]} { + wm withdraw . + } + + if {[tk windowingsystem] eq "aqua"} { + set mod "Cmd" + } else { + set mod "Ctrl" + } + + if {[catch {menu .menubar} err]} { + bgerror "INIT: $err" + } + AmpMenuArgs .menubar add cascade -label [mc &File] -menu .menubar.file + AmpMenuArgs .menubar add cascade -label [mc &Edit] -menu .menubar.edit + + menu .menubar.file -tearoff 0 + AmpMenuArgs .menubar.file add command -label [mc "&Source..."] \ + -command {tk::ConsoleSource} + AmpMenuArgs .menubar.file add command -label [mc "&Hide Console"] \ + -command {wm withdraw .} + AmpMenuArgs .menubar.file add command -label [mc "&Clear Console"] \ + -command {.console delete 1.0 "promptEnd linestart"} + if {[tk windowingsystem] ne "aqua"} { + AmpMenuArgs .menubar.file add command -label [mc E&xit] -command {exit} + } + + menu .menubar.edit -tearoff 0 + AmpMenuArgs .menubar.edit add command -label [mc Cu&t] -accel "$mod+X"\ + -command {event generate .console <>} + AmpMenuArgs .menubar.edit add command -label [mc &Copy] -accel "$mod+C"\ + -command {event generate .console <>} + AmpMenuArgs .menubar.edit add command -label [mc P&aste] -accel "$mod+V"\ + -command {event generate .console <>} + + if {[tk windowingsystem] ne "win32"} { + AmpMenuArgs .menubar.edit add command -label [mc Cl&ear] \ + -command {event generate .console <>} + } else { + AmpMenuArgs .menubar.edit add command -label [mc &Delete] \ + -command {event generate .console <>} -accel "Del" + + AmpMenuArgs .menubar add cascade -label [mc &Help] -menu .menubar.help + menu .menubar.help -tearoff 0 + AmpMenuArgs .menubar.help add command -label [mc &About...] \ + -command tk::ConsoleAbout + } + + AmpMenuArgs .menubar.edit add separator + if {$::tk::console::useFontchooser} { + if {[tk windowingsystem] eq "aqua"} { + .menubar.edit add command -label tk_choose_font_marker + set index [.menubar.edit index tk_choose_font_marker] + .menubar.edit entryconfigure $index \ + -label [mc "Show Fonts"]\ + -accelerator "$mod-T"\ + -command [list ::tk::console::FontchooserToggle] + bind Console <> \ + [list ::tk::console::FontchooserVisibility $index] + ::tk::console::FontchooserVisibility $index + } else { + AmpMenuArgs .menubar.edit add command -label [mc "&Font..."] \ + -command [list ::tk::console::FontchooserToggle] + } + bind Console [list ::tk::console::FontchooserFocus %W 1] + bind Console [list ::tk::console::FontchooserFocus %W 0] + } + AmpMenuArgs .menubar.edit add command -label [mc "&Increase Font Size"] \ + -accel "$mod++" -command {event generate .console <>} + AmpMenuArgs .menubar.edit add command -label [mc "&Decrease Font Size"] \ + -accel "$mod+-" -command {event generate .console <>} + AmpMenuArgs .menubar.edit add command -label [mc "Fit To Screen Width"] \ + -command {event generate .console <>} + + if {[tk windowingsystem] eq "aqua"} { + .menubar add cascade -label [mc Window] -menu [menu .menubar.window] + .menubar add cascade -label [mc Help] -menu [menu .menubar.help] + } + + . configure -menu .menubar + + # See if we can find a better font than the TkFixedFont + catch {font create TkConsoleFont {*}[font configure TkFixedFont]} + set families [font families] + switch -exact -- [tk windowingsystem] { + aqua { set preferred {Monaco 10} } + win32 { set preferred {ProFontWindows 8 Consolas 8} } + default { set preferred {} } + } + foreach {family size} $preferred { + if {$family in $families} { + font configure TkConsoleFont -family $family -size $size + break + } + } + + # Provide the right border for the text widget (platform dependent). + ::ttk::style layout ConsoleFrame { + Entry.field -sticky news -border 1 -children { + ConsoleFrame.padding -sticky news + } + } + ::ttk::frame .consoleframe -style ConsoleFrame + + set con [text .console -yscrollcommand [list .sb set] -setgrid true \ + -borderwidth 0 -highlightthickness 0 -font TkConsoleFont] + if {[tk windowingsystem] eq "aqua"} { + scrollbar .sb -command [list $con yview] + } else { + ::ttk::scrollbar .sb -command [list $con yview] + } + pack .sb -in .consoleframe -fill both -side right -padx 1 -pady 1 + pack $con -in .consoleframe -fill both -expand 1 -side left -padx 1 -pady 1 + pack .consoleframe -fill both -expand 1 -side left + + ConsoleBind $con + + $con tag configure stderr -foreground red + $con tag configure stdin -foreground blue + $con tag configure prompt -foreground \#8F4433 + $con tag configure proc -foreground \#008800 + $con tag configure var -background \#FFC0D0 + $con tag raise sel + $con tag configure blink -background \#FFFF00 + $con tag configure find -background \#FFFF00 + + focus $con + + # Avoid listing this console in [winfo interps] + if {[info command ::send] eq "::send"} {rename ::send {}} + + wm protocol . WM_DELETE_WINDOW { wm withdraw . } + wm title . [mc "Console"] + flush stdout + $con mark set output [$con index "end - 1 char"] + tk::TextSetCursor $con end + $con mark set promptEnd insert + $con mark gravity promptEnd left + + # A variant of ConsolePrompt to avoid a 'puts' call + set w $con + set temp [$w index "end - 1 char"] + $w mark set output end + if {![consoleinterp eval "info exists tcl_prompt1"]} { + set string [EvalAttached $::tk::console::defaultPrompt] + $w insert output $string stdout + } + $w mark set output $temp + ::tk::TextSetCursor $w end + $w mark set promptEnd insert + $w mark gravity promptEnd left + + if {[tk windowingsystem] ne "aqua"} { + # Subtle work-around to erase the '% ' that tclMain.c prints out + after idle [subst -nocommand { + if {[$con get 1.0 output] eq "% "} { $con delete 1.0 output } + }] + } +} + +# ::tk::ConsoleSource -- +# +# Prompts the user for a file to source in the main interpreter. +# +# Arguments: +# None. + +proc ::tk::ConsoleSource {} { + set filename [tk_getOpenFile -defaultextension .tcl -parent . \ + -title [mc "Select a file to source"] \ + -filetypes [list \ + [list [mc "Tcl Scripts"] .tcl] \ + [list [mc "All Files"] *]]] + if {$filename ne ""} { + set cmd [list source $filename] + if {[catch {consoleinterp eval $cmd} result]} { + ConsoleOutput stderr "$result\n" + } + } +} + +# ::tk::ConsoleInvoke -- +# Processes the command line input. If the command is complete it +# is evaled in the main interpreter. Otherwise, the continuation +# prompt is added and more input may be added. +# +# Arguments: +# None. + +proc ::tk::ConsoleInvoke {args} { + set ranges [.console tag ranges input] + set cmd "" + if {[llength $ranges]} { + set pos 0 + while {[lindex $ranges $pos] ne ""} { + set start [lindex $ranges $pos] + set end [lindex $ranges [incr pos]] + append cmd [.console get $start $end] + incr pos + } + } + if {$cmd eq ""} { + ConsolePrompt + } elseif {[info complete $cmd]} { + .console mark set output end + .console tag delete input + set result [consoleinterp record $cmd] + if {$result ne ""} { + puts $result + } + ConsoleHistory reset + ConsolePrompt + } else { + ConsolePrompt partial + } + .console yview -pickplace insert +} + +# ::tk::ConsoleHistory -- +# This procedure implements command line history for the +# console. In general is evals the history command in the +# main interpreter to obtain the history. The variable +# ::tk::HistNum is used to store the current location in the history. +# +# Arguments: +# cmd - Which action to take: prev, next, reset. + +set ::tk::HistNum 1 +proc ::tk::ConsoleHistory {cmd} { + variable HistNum + + switch $cmd { + prev { + incr HistNum -1 + if {$HistNum == 0} { + set cmd {history event [expr {[history nextid] -1}]} + } else { + set cmd "history event $HistNum" + } + if {[catch {consoleinterp eval $cmd} cmd]} { + incr HistNum + return + } + .console delete promptEnd end + .console insert promptEnd $cmd {input stdin} + .console see end + } + next { + incr HistNum + if {$HistNum == 0} { + set cmd {history event [expr {[history nextid] -1}]} + } elseif {$HistNum > 0} { + set cmd "" + set HistNum 1 + } else { + set cmd "history event $HistNum" + } + if {$cmd ne ""} { + catch {consoleinterp eval $cmd} cmd + } + .console delete promptEnd end + .console insert promptEnd $cmd {input stdin} + .console see end + } + reset { + set HistNum 1 + } + } +} + +# ::tk::ConsolePrompt -- +# This procedure draws the prompt. If tcl_prompt1 or tcl_prompt2 +# exists in the main interpreter it will be called to generate the +# prompt. Otherwise, a hard coded default prompt is printed. +# +# Arguments: +# partial - Flag to specify which prompt to print. + +proc ::tk::ConsolePrompt {{partial normal}} { + set w .console + if {$partial eq "normal"} { + set temp [$w index "end - 1 char"] + $w mark set output end + if {[consoleinterp eval "info exists tcl_prompt1"]} { + consoleinterp eval "eval \[set tcl_prompt1\]" + } else { + puts -nonewline [EvalAttached $::tk::console::defaultPrompt] + } + } else { + set temp [$w index output] + $w mark set output end + if {[consoleinterp eval "info exists tcl_prompt2"]} { + consoleinterp eval "eval \[set tcl_prompt2\]" + } else { + puts -nonewline "> " + } + } + flush stdout + $w mark set output $temp + ::tk::TextSetCursor $w end + $w mark set promptEnd insert + $w mark gravity promptEnd left + ::tk::console::ConstrainBuffer $w $::tk::console::maxLines + $w see end +} + +# Copy selected text from the console +proc ::tk::console::Copy {w} { + if {![catch {set data [$w get sel.first sel.last]}]} { + clipboard clear -displayof $w + clipboard append -displayof $w $data + } +} +# Copies selected text. If the selection is within the current active edit +# region then it will be cut, if not it is only copied. +proc ::tk::console::Cut {w} { + if {![catch {set data [$w get sel.first sel.last]}]} { + clipboard clear -displayof $w + clipboard append -displayof $w $data + if {[$w compare sel.first >= output]} { + $w delete sel.first sel.last + } + } +} +# Paste text from the clipboard +proc ::tk::console::Paste {w} { + catch { + set clip [::tk::GetSelection $w CLIPBOARD] + set list [split $clip \n\r] + tk::ConsoleInsert $w [lindex $list 0] + foreach x [lrange $list 1 end] { + $w mark set insert {end - 1c} + tk::ConsoleInsert $w "\n" + tk::ConsoleInvoke + tk::ConsoleInsert $w $x + } + } +} + +# Fit TkConsoleFont to window width +proc ::tk::console::FitScreenWidth {w} { + set width [winfo screenwidth $w] + set cwidth [$w cget -width] + set s -50 + set fit 0 + array set fi [font configure TkConsoleFont] + while {$s < 0} { + set fi(-size) $s + set f [font create {*}[array get fi]] + set c [font measure $f "eM"] + font delete $f + if {$c * $cwidth < 1.667 * $width} { + font configure TkConsoleFont -size $s + break + } + incr s 2 + } +} + +# ::tk::ConsoleBind -- +# This procedure first ensures that the default bindings for the Text +# class have been defined. Then certain bindings are overridden for +# the class. +# +# Arguments: +# None. + +proc ::tk::ConsoleBind {w} { + bindtags $w [list $w Console PostConsole [winfo toplevel $w] all] + + ## Get all Text bindings into Console + foreach ev [bind Text] { + bind Console $ev [bind Text $ev] + } + ## We really didn't want the newline insertion... + bind Console {} + ## ...or any Control-v binding (would block <>) + bind Console {} + + # For the moment, transpose isn't enabled until the console + # gets and overhaul of how it handles input -- hobbs + bind Console {} + + # Ignore all Alt, Meta, and Control keypresses unless explicitly bound. + # Otherwise, if a widget binding for one of these is defined, the + # class binding will also fire and insert the character + # which is wrong. + + bind Console {# nothing } + bind Console {# nothing} + bind Console {# nothing} + if {[tk windowingsystem] eq "aqua"} { + bind Console {# nothing} + bind Console {# nothing} + } + + foreach {ev key} { + <> + <> + <> + <> + + <> + <> + <> + <> + <> + <> + <> + <> + <> + + <> + <> + <> + <> + <> + <> + <> + } { + event add $ev $key + bind Console $key {} + } + if {[tk windowingsystem] eq "aqua"} { + foreach {ev key} { + <> + <> + } { + event add $ev $key + bind Console $key {} + } + if {$::tk::console::useFontchooser} { + bind Console [list ::tk::console::FontchooserToggle] + } + } + bind Console <> { + if {[%W compare insert > promptEnd]} { + ::tk::console::Expand %W + } + } + bind Console <> { + if {[%W compare insert > promptEnd]} { + ::tk::console::Expand %W path + } + } + bind Console <> { + if {[%W compare insert > promptEnd]} { + ::tk::console::Expand %W proc + } + } + bind Console <> { + if {[%W compare insert > promptEnd]} { + ::tk::console::Expand %W var + } + } + bind Console <> { + %W mark set insert {end - 1c} + tk::ConsoleInsert %W "\n" + tk::ConsoleInvoke + break + } + bind Console { + if {{} ne [%W tag nextrange sel 1.0 end] \ + && [%W compare sel.first >= promptEnd]} { + %W delete sel.first sel.last + } elseif {[%W compare insert >= promptEnd]} { + %W delete insert + %W see insert + } + } + bind Console { + if {{} ne [%W tag nextrange sel 1.0 end] \ + && [%W compare sel.first >= promptEnd]} { + %W delete sel.first sel.last + } elseif {[%W compare insert != 1.0] && \ + [%W compare insert > promptEnd]} { + %W delete insert-1c + %W see insert + } + } + bind Console [bind Console ] + + bind Console <> { + if {[%W compare insert < promptEnd]} { + tk::TextSetCursor %W {insert linestart} + } else { + tk::TextSetCursor %W promptEnd + } + } + bind Console <> { + tk::TextSetCursor %W {insert lineend} + } + bind Console { + if {[%W compare insert < promptEnd]} { + break + } + %W delete insert + } + bind Console <> { + if {[%W compare insert < promptEnd]} { + break + } + if {[%W compare insert == {insert lineend}]} { + %W delete insert + } else { + %W delete insert {insert lineend} + } + } + bind Console <> { + ## Clear console display + %W delete 1.0 "promptEnd linestart" + } + bind Console <> { + ## Clear command line (Unix shell staple) + %W delete promptEnd end + } + bind Console { + if {[%W compare insert >= promptEnd]} { + %W delete insert {insert wordend} + } + } + bind Console { + if {[%W compare {insert -1c wordstart} >= promptEnd]} { + %W delete {insert -1c wordstart} insert + } + } + bind Console { + if {[%W compare insert >= promptEnd]} { + %W delete insert {insert wordend} + } + } + bind Console { + if {[%W compare {insert -1c wordstart} >= promptEnd]} { + %W delete {insert -1c wordstart} insert + } + } + bind Console { + if {[%W compare insert >= promptEnd]} { + %W delete insert {insert wordend} + } + } + bind Console <> { + tk::ConsoleHistory prev + } + bind Console <> { + tk::ConsoleHistory next + } + bind Console { + catch {tk::ConsoleInsert %W [::tk::GetSelection %W PRIMARY]} + } + bind Console { + tk::ConsoleInsert %W %A + } + bind Console { + destroy {*}[winfo children .] + source -encoding utf-8 [file join $tk_library console.tcl] + } + if {[tk windowingsystem] eq "aqua"} { + bind Console { + exit + } + } + bind Console <> { ::tk::console::Cut %W } + bind Console <> { ::tk::console::Copy %W } + bind Console <> { ::tk::console::Paste %W } + + bind Console <> { + set size [font configure TkConsoleFont -size] + if {$size < 0} {set sign -1} else {set sign 1} + set size [expr {(abs($size) + 1) * $sign}] + font configure TkConsoleFont -size $size + if {$::tk::console::useFontchooser} { + tk fontchooser configure -font TkConsoleFont + } + } + bind Console <> { + set size [font configure TkConsoleFont -size] + if {abs($size) < 2} { return } + if {$size < 0} {set sign -1} else {set sign 1} + set size [expr {(abs($size) - 1) * $sign}] + font configure TkConsoleFont -size $size + if {$::tk::console::useFontchooser} { + tk fontchooser configure -font TkConsoleFont + } + } + bind Console <> { + ::tk::console::FitScreenWidth %W + } + + ## + ## Bindings for doing special things based on certain keys + ## + bind PostConsole { + if {"\\" ne [%W get insert-2c]} { + ::tk::console::MatchPair %W \( \) promptEnd + } + } + bind PostConsole { + if {"\\" ne [%W get insert-2c]} { + ::tk::console::MatchPair %W \[ \] promptEnd + } + } + bind PostConsole { + if {"\\" ne [%W get insert-2c]} { + ::tk::console::MatchPair %W \{ \} promptEnd + } + } + bind PostConsole { + if {"\\" ne [%W get insert-2c]} { + ::tk::console::MatchQuote %W promptEnd + } + } + + bind PostConsole { + if {"%A" ne ""} { + ::tk::console::TagProc %W + } + } +} + +# ::tk::ConsoleInsert -- +# Insert a string into a text at the point of the insertion cursor. +# If there is a selection in the text, and it covers the point of the +# insertion cursor, then delete the selection before inserting. Insertion +# is restricted to the prompt area. +# +# Arguments: +# w - The text window in which to insert the string +# s - The string to insert (usually just a single character) + +proc ::tk::ConsoleInsert {w s} { + if {$s eq ""} { + return + } + catch { + if {[$w compare sel.first <= insert] \ + && [$w compare sel.last >= insert]} { + $w tag remove sel sel.first promptEnd + $w delete sel.first sel.last + } + } + if {[$w compare insert < promptEnd]} { + $w mark set insert end + } + $w insert insert $s {input stdin} + $w see insert +} + +# ::tk::ConsoleOutput -- +# +# This routine is called directly by ConsolePutsCmd to cause a string +# to be displayed in the console. +# +# Arguments: +# dest - The output tag to be used: either "stderr" or "stdout". +# string - The string to be displayed. + +proc ::tk::ConsoleOutput {dest string} { + set w .console + $w insert output $string $dest + ::tk::console::ConstrainBuffer $w $::tk::console::maxLines + $w see insert +} + +# ::tk::ConsoleExit -- +# +# This routine is called by ConsoleEventProc when the main window of +# the application is destroyed. Don't call exit - that probably already +# happened. Just delete our window. +# +# Arguments: +# None. + +proc ::tk::ConsoleExit {} { + destroy . +} + +# ::tk::ConsoleAbout -- +# +# This routine displays an About box to show Tcl/Tk version info. +# +# Arguments: +# None. + +proc ::tk::ConsoleAbout {} { + tk_messageBox -type ok -message "[mc {Tcl for Windows}] + +Tcl $::tcl_patchLevel +Tk $::tk_patchLevel" +} + +# ::tk::console::Fontchooser* -- +# Let the user select the console font (TIP 324). + +proc ::tk::console::FontchooserToggle {} { + if {[tk fontchooser configure -visible]} { + tk fontchooser hide + } else { + tk fontchooser show + } +} +proc ::tk::console::FontchooserVisibility {index} { + if {[tk fontchooser configure -visible]} { + .menubar.edit entryconfigure $index -label [::tk::msgcat::mc "Hide Fonts"] + } else { + .menubar.edit entryconfigure $index -label [::tk::msgcat::mc "Show Fonts"] + } +} +proc ::tk::console::FontchooserFocus {w isFocusIn} { + if {$isFocusIn} { + tk fontchooser configure -parent $w -font TkConsoleFont \ + -command [namespace code [list FontchooserApply]] + } else { + tk fontchooser configure -parent $w -font {} -command {} + } +} +proc ::tk::console::FontchooserApply {font args} { + catch {font configure TkConsoleFont {*}[font actual $font]} +} + +# ::tk::console::TagProc -- +# +# Tags a procedure in the console if it's recognized +# This procedure is not perfect. However, making it perfect wastes +# too much CPU time... +# +# Arguments: +# w - console text widget + +proc ::tk::console::TagProc w { + if {!$::tk::console::magicKeys} { + return + } + set exp "\[^\\\\\]\[\[ \t\n\r\;{}\"\$\]" + set i [$w search -backwards -regexp $exp insert-1c promptEnd-1c] + if {$i eq ""} { + set i promptEnd + } else { + append i +2c + } + regsub -all "\[\[\\\\\\?\\*\]" [$w get $i "insert-1c wordend"] {\\\0} c + if {[llength [EvalAttached [list info commands $c]]]} { + $w tag add proc $i "insert-1c wordend" + } else { + $w tag remove proc $i "insert-1c wordend" + } + if {[llength [EvalAttached [list info vars $c]]]} { + $w tag add var $i "insert-1c wordend" + } else { + $w tag remove var $i "insert-1c wordend" + } +} + +# ::tk::console::MatchPair -- +# +# Blinks a matching pair of characters +# c2 is assumed to be at the text index 'insert'. +# This proc is really loopy and took me an hour to figure out given +# all possible combinations with escaping except for escaped \'s. +# It doesn't take into account possible commenting... Oh well. If +# anyone has something better, I'd like to see/use it. This is really +# only efficient for small contexts. +# +# Arguments: +# w - console text widget +# c1 - first char of pair +# c2 - second char of pair +# +# Calls: ::tk::console::Blink + +proc ::tk::console::MatchPair {w c1 c2 {lim 1.0}} { + if {!$::tk::console::magicKeys} { + return + } + if {{} ne [set ix [$w search -back $c1 insert $lim]]} { + while { + [string match {\\} [$w get $ix-1c]] && + [set ix [$w search -back $c1 $ix-1c $lim]] ne {} + } {} + set i1 insert-1c + while {$ix ne {}} { + set i0 $ix + set j 0 + while {[set i0 [$w search $c2 $i0 $i1]] ne {}} { + append i0 +1c + if {[string match {\\} [$w get $i0-2c]]} { + continue + } + incr j + } + if {!$j} { + break + } + set i1 $ix + while {$j && [set ix [$w search -back $c1 $ix $lim]] ne {}} { + if {[string match {\\} [$w get $ix-1c]]} { + continue + } + incr j -1 + } + } + if {[string match {} $ix]} { + set ix [$w index $lim] + } + } else { + set ix [$w index $lim] + } + if {$::tk::console::blinkRange} { + Blink $w $ix [$w index insert] + } else { + Blink $w $ix $ix+1c [$w index insert-1c] [$w index insert] + } +} + +# ::tk::console::MatchQuote -- +# +# Blinks between matching quotes. +# Blinks just the quote if it's unmatched, otherwise blinks quoted string +# The quote to match is assumed to be at the text index 'insert'. +# +# Arguments: +# w - console text widget +# +# Calls: ::tk::console::Blink + +proc ::tk::console::MatchQuote {w {lim 1.0}} { + if {!$::tk::console::magicKeys} { + return + } + set i insert-1c + set j 0 + while {[set i [$w search -back \" $i $lim]] ne {}} { + if {[string match {\\} [$w get $i-1c]]} { + continue + } + if {!$j} { + set i0 $i + } + incr j + } + if {$j&1} { + if {$::tk::console::blinkRange} { + Blink $w $i0 [$w index insert] + } else { + Blink $w $i0 $i0+1c [$w index insert-1c] [$w index insert] + } + } else { + Blink $w [$w index insert-1c] [$w index insert] + } +} + +# ::tk::console::Blink -- +# +# Blinks between n index pairs for a specified duration. +# +# Arguments: +# w - console text widget +# i1 - start index to blink region +# i2 - end index of blink region +# dur - duration in usecs to blink for +# +# Outputs: +# blinks selected characters in $w + +proc ::tk::console::Blink {w args} { + eval [list $w tag add blink] $args + after $::tk::console::blinkTime [list $w] tag remove blink $args +} + +# ::tk::console::ConstrainBuffer -- +# +# This limits the amount of data in the text widget +# Called by Prompt and ConsoleOutput +# +# Arguments: +# w - console text widget +# size - # of lines to constrain to +# +# Outputs: +# may delete data in console widget + +proc ::tk::console::ConstrainBuffer {w size} { + if {[$w index end] > $size} { + $w delete 1.0 [expr {int([$w index end])-$size}].0 + } +} + +# ::tk::console::Expand -- +# +# Arguments: +# ARGS: w - text widget in which to expand str +# type - type of expansion (path / proc / variable) +# +# Calls: ::tk::console::Expand(Pathname|Procname|Variable) +# +# Outputs: The string to match is expanded to the longest possible match. +# If ::tk::console::showMatches is non-zero and the longest match +# equaled the string to expand, then all possible matches are +# output to stdout. Triggers bell if no matches are found. +# +# Returns: number of matches found + +proc ::tk::console::Expand {w {type ""}} { + set exp "\[^\\\\\]\[\[ \t\n\r\\\{\"\\\\\$\]" + set tmp [$w search -backwards -regexp $exp insert-1c promptEnd-1c] + if {$tmp eq ""} { + set tmp promptEnd + } else { + append tmp +2c + } + if {[$w compare $tmp >= insert]} { + return + } + set str [$w get $tmp insert] + switch -glob $type { + path* { + set res [ExpandPathname $str] + } + proc* { + set res [ExpandProcname $str] + } + var* { + set res [ExpandVariable $str] + } + default { + set res {} + foreach t {Pathname Procname Variable} { + if {![catch {Expand$t $str} res] && ($res ne "")} { + break + } + } + } + } + set len [llength $res] + if {$len} { + set repl [lindex $res 0] + $w delete $tmp insert + $w insert $tmp $repl {input stdin} + if {($len > 1) && ($::tk::console::showMatches) && ($repl eq $str)} { + puts stdout [lsort [lreplace $res 0 0]] + } + } else { + bell + } + return [incr len -1] +} + +# ::tk::console::ExpandPathname -- +# +# Expand a file pathname based on $str +# This is based on UNIX file name conventions +# +# Arguments: +# str - partial file pathname to expand +# +# Calls: ::tk::console::ExpandBestMatch +# +# Returns: list containing longest unique match followed by all the +# possible further matches + +proc ::tk::console::ExpandPathname str { + set pwd [EvalAttached pwd] + if {[catch {EvalAttached [list cd [file dirname $str]]} err opt]} { + return -options $opt $err + } + set dir [file tail $str] + ## Check to see if it was known to be a directory and keep the trailing + ## slash if so (file tail cuts it off) + if {[string match */ $str]} { + append dir / + } + if {[catch {lsort [EvalAttached [list glob $dir*]]} m]} { + set match {} + } else { + if {[llength $m] > 1} { + if { $::tcl_platform(platform) eq "windows" } { + ## Windows is screwy because it's case insensitive + set tmp [ExpandBestMatch [string tolower $m] \ + [string tolower $dir]] + ## Don't change case if we haven't changed the word + if {[string length $dir]==[string length $tmp]} { + set tmp $dir + } + } else { + set tmp [ExpandBestMatch $m $dir] + } + if {[string match ?*/* $str]} { + set tmp [file dirname $str]/$tmp + } elseif {[string match /* $str]} { + set tmp /$tmp + } + regsub -all { } $tmp {\\ } tmp + set match [linsert $m 0 $tmp] + } else { + ## This may look goofy, but it handles spaces in path names + eval append match $m + if {[file isdir $match]} { + append match / + } + if {[string match ?*/* $str]} { + set match [file dirname $str]/$match + } elseif {[string match /* $str]} { + set match /$match + } + regsub -all { } $match {\\ } match + ## Why is this one needed and the ones below aren't!! + set match [list $match] + } + } + EvalAttached [list cd $pwd] + return $match +} + +# ::tk::console::ExpandProcname -- +# +# Expand a tcl proc name based on $str +# +# Arguments: +# str - partial proc name to expand +# +# Calls: ::tk::console::ExpandBestMatch +# +# Returns: list containing longest unique match followed by all the +# possible further matches + +proc ::tk::console::ExpandProcname str { + set match [EvalAttached [list info commands $str*]] + if {[llength $match] == 0} { + set ns [EvalAttached \ + "namespace children \[namespace current\] [list $str*]"] + if {[llength $ns]==1} { + set match [EvalAttached [list info commands ${ns}::*]] + } else { + set match $ns + } + } + if {[llength $match] > 1} { + regsub -all { } [ExpandBestMatch $match $str] {\\ } str + set match [linsert $match 0 $str] + } else { + regsub -all { } $match {\\ } match + } + return $match +} + +# ::tk::console::ExpandVariable -- +# +# Expand a tcl variable name based on $str +# +# Arguments: +# str - partial tcl var name to expand +# +# Calls: ::tk::console::ExpandBestMatch +# +# Returns: list containing longest unique match followed by all the +# possible further matches + +proc ::tk::console::ExpandVariable str { + if {[regexp {([^\(]*)\((.*)} $str -> ary str]} { + ## Looks like they're trying to expand an array. + set match [EvalAttached [list array names $ary $str*]] + if {[llength $match] > 1} { + set vars $ary\([ExpandBestMatch $match $str] + foreach var $match { + lappend vars $ary\($var\) + } + return $vars + } elseif {[llength $match] == 1} { + set match $ary\($match\) + } + ## Space transformation avoided for array names. + } else { + set match [EvalAttached [list info vars $str*]] + if {[llength $match] > 1} { + regsub -all { } [ExpandBestMatch $match $str] {\\ } str + set match [linsert $match 0 $str] + } else { + regsub -all { } $match {\\ } match + } + } + return $match +} + +# ::tk::console::ExpandBestMatch -- +# +# Finds the best unique match in a list of names. +# The extra $e in this argument allows us to limit the innermost loop a little +# further. This improves speed as $l becomes large or $e becomes long. +# +# Arguments: +# l - list to find best unique match in +# e - currently best known unique match +# +# Returns: longest unique match in the list + +proc ::tk::console::ExpandBestMatch {l {e {}}} { + set ec [lindex $l 0] + if {[llength $l]>1} { + set e [expr {[string length $e] - 1}] + set ei [expr {[string length $ec] - 1}] + foreach l $l { + while {$ei>=$e && [string first $ec $l]} { + set ec [string range $ec 0 [incr ei -1]] + } + } + } + return $ec +} + +# now initialize the console +::tk::ConsoleInit diff --git a/lib/tk8.6/dialog.tcl b/lib/tk8.6/dialog.tcl new file mode 100644 index 0000000000000000000000000000000000000000..a099d90036028ddee2782e505b728c3a7aea7a12 --- /dev/null +++ b/lib/tk8.6/dialog.tcl @@ -0,0 +1,175 @@ +# dialog.tcl -- +# +# This file defines the procedure tk_dialog, which creates a dialog +# box containing a bitmap, a message, and one or more buttons. +# +# Copyright (c) 1992-1993 The Regents of the University of California. +# Copyright (c) 1994-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. +# + +# +# ::tk_dialog: +# +# This procedure displays a dialog box, waits for a button in the dialog +# to be invoked, then returns the index of the selected button. If the +# dialog somehow gets destroyed, -1 is returned. +# +# Arguments: +# w - Window to use for dialog top-level. +# title - Title to display in dialog's decorative frame. +# text - Message to display in dialog. +# bitmap - Bitmap to display in dialog (empty string means none). +# default - Index of button that is to display the default ring +# (-1 means none). +# args - One or more strings to display in buttons across the +# bottom of the dialog box. + +proc ::tk_dialog {w title text bitmap default args} { + variable ::tk::Priv + + # Check that $default was properly given + if {[string is integer -strict $default]} { + if {$default >= [llength $args]} { + return -code error -errorcode {TK DIALOG BAD_DEFAULT} \ + "default button index greater than number of buttons\ + specified for tk_dialog" + } + } elseif {"" eq $default} { + set default -1 + } else { + set default [lsearch -exact $args $default] + } + + set windowingsystem [tk windowingsystem] + + # 1. Create the top-level window and divide it into top + # and bottom parts. + + destroy $w + toplevel $w -class Dialog + wm title $w $title + wm iconname $w Dialog + wm protocol $w WM_DELETE_WINDOW { } + + # Dialog boxes should be transient with respect to their parent, + # so that they will always stay on top of their parent window. However, + # some window managers will create the window as withdrawn if the parent + # window is withdrawn or iconified. Combined with the grab we put on the + # window, this can hang the entire application. Therefore we only make + # the dialog transient if the parent is viewable. + # + if {[winfo viewable [winfo toplevel [winfo parent $w]]] } { + wm transient $w [winfo toplevel [winfo parent $w]] + } + + if {$windowingsystem eq "aqua"} { + ::tk::unsupported::MacWindowStyle style $w moveableModal {} + } elseif {$windowingsystem eq "x11"} { + wm attributes $w -type dialog + } + + frame $w.bot + frame $w.top + if {$windowingsystem eq "x11"} { + $w.bot configure -relief raised -bd 1 + $w.top configure -relief raised -bd 1 + } + pack $w.bot -side bottom -fill both + pack $w.top -side top -fill both -expand 1 + grid anchor $w.bot center + + # 2. Fill the top part with bitmap and message (use the option + # database for -wraplength and -font so that they can be + # overridden by the caller). + + option add *Dialog.msg.wrapLength 3i widgetDefault + option add *Dialog.msg.font TkCaptionFont widgetDefault + + label $w.msg -justify left -text $text + pack $w.msg -in $w.top -side right -expand 1 -fill both -padx 3m -pady 3m + if {$bitmap ne ""} { + if {$windowingsystem eq "aqua" && $bitmap eq "error"} { + set bitmap "stop" + } + label $w.bitmap -bitmap $bitmap + pack $w.bitmap -in $w.top -side left -padx 3m -pady 3m + } + + # 3. Create a row of buttons at the bottom of the dialog. + + set i 0 + foreach but $args { + button $w.button$i -text $but -command [list set ::tk::Priv(button) $i] + if {$i == $default} { + $w.button$i configure -default active + } else { + $w.button$i configure -default normal + } + grid $w.button$i -in $w.bot -column $i -row 0 -sticky ew \ + -padx 10 -pady 4 + grid columnconfigure $w.bot $i + # We boost the size of some Mac buttons for l&f + if {$windowingsystem eq "aqua"} { + set tmp [string tolower $but] + if {$tmp eq "ok" || $tmp eq "cancel"} { + grid columnconfigure $w.bot $i -minsize 90 + } + grid configure $w.button$i -pady 7 + } + incr i + } + + # 4. Create a binding for on the dialog if there is a + # default button. + # Convention also dictates that if the keyboard focus moves among the + # the buttons that the binding affects the button with the focus. + + if {$default >= 0} { + bind $w [list $w.button$default invoke] + } + bind $w <> [list bind $w {[tk_focusPrev %W] invoke}] + bind $w <> [list bind $w {[tk_focusNext %W] invoke}] + + # 5. Create a binding for the window that sets the + # button variable to -1; this is needed in case something happens + # that destroys the window, such as its parent window being destroyed. + + bind $w {set ::tk::Priv(button) -1} + + # 6. Withdraw the window, then update all the geometry information + # so we know how big it wants to be, then center the window in the + # display (Motif style) and de-iconify it. + + ::tk::PlaceWindow $w + tkwait visibility $w + + # 7. Set a grab and claim the focus too. + + if {$default >= 0} { + set focus $w.button$default + } else { + set focus $w + } + tk::SetFocusGrab $w $focus + + # 8. Wait for the user to respond, then restore the focus and + # return the index of the selected button. Restore the focus + # before deleting the window, since otherwise the window manager + # may take the focus away so we can't redirect it. Finally, + # restore any grab that was in effect. + + vwait ::tk::Priv(button) + + catch { + # It's possible that the window has already been destroyed, + # hence this "catch". Delete the Destroy handler so that + # Priv(button) doesn't get reset by it. + + bind $w {} + } + tk::RestoreFocusGrab $w $focus + return $Priv(button) +} diff --git a/lib/tk8.6/entry.tcl b/lib/tk8.6/entry.tcl new file mode 100644 index 0000000000000000000000000000000000000000..5cb5ab912d75742d697f877dda44bdf016265f17 --- /dev/null +++ b/lib/tk8.6/entry.tcl @@ -0,0 +1,699 @@ +# entry.tcl -- +# +# This file defines the default bindings for Tk entry widgets and provides +# procedures that help in implementing those bindings. +# +# Copyright (c) 1992-1994 The Regents of the University of California. +# Copyright (c) 1994-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. +# + +#------------------------------------------------------------------------- +# Elements of tk::Priv that are used in this file: +# +# afterId - If non-null, it means that auto-scanning is underway +# and it gives the "after" id for the next auto-scan +# command to be executed. +# mouseMoved - Non-zero means the mouse has moved a significant +# amount since the button went down (so, for example, +# start dragging out a selection). +# pressX - X-coordinate at which the mouse button was pressed. +# selectMode - The style of selection currently underway: +# char, word, or line. +# x, y - Last known mouse coordinates for scanning +# and auto-scanning. +# data - Used for Cut and Copy +#------------------------------------------------------------------------- + +#------------------------------------------------------------------------- +# The code below creates the default class bindings for entries. +#------------------------------------------------------------------------- +bind Entry <> { + if {![catch {tk::EntryGetSelection %W} tk::Priv(data)]} { + clipboard clear -displayof %W + clipboard append -displayof %W $tk::Priv(data) + %W delete sel.first sel.last + unset tk::Priv(data) + } +} +bind Entry <> { + if {![catch {tk::EntryGetSelection %W} tk::Priv(data)]} { + clipboard clear -displayof %W + clipboard append -displayof %W $tk::Priv(data) + unset tk::Priv(data) + } +} +bind Entry <> { + catch { + if {[tk windowingsystem] ne "x11"} { + catch { + %W delete sel.first sel.last + } + } + %W insert insert [::tk::GetSelection %W CLIPBOARD] + tk::EntrySeeInsert %W + } +} +bind Entry <> { + # ignore if there is no selection + catch {%W delete sel.first sel.last} +} +bind Entry <> { + if {$tk_strictMotif || ![info exists tk::Priv(mouseMoved)] + || !$tk::Priv(mouseMoved)} { + tk::EntryPaste %W %x + } +} + +bind Entry <> { + %W selection range 0 end + %W icursor end +} + +# Standard Motif bindings: + +bind Entry { + tk::EntryButton1 %W %x + %W selection clear +} +bind Entry { + set tk::Priv(x) %x + tk::EntryMouseSelect %W %x +} +bind Entry { + set tk::Priv(selectMode) word + tk::EntryMouseSelect %W %x + catch {%W icursor sel.last} +} +bind Entry { + set tk::Priv(selectMode) line + tk::EntryMouseSelect %W %x + catch {%W icursor sel.last} +} +bind Entry { + set tk::Priv(selectMode) char + %W selection adjust @%x +} +bind Entry { + set tk::Priv(selectMode) word + tk::EntryMouseSelect %W %x +} +bind Entry { + set tk::Priv(selectMode) line + tk::EntryMouseSelect %W %x +} +bind Entry { + set tk::Priv(x) %x + tk::EntryAutoScan %W +} +bind Entry { + tk::CancelRepeat +} +bind Entry { + tk::CancelRepeat +} +bind Entry { + %W icursor @%x +} + +bind Entry <> { + tk::EntrySetCursor %W [expr {[%W index insert]-1}] +} +bind Entry <> { + tk::EntrySetCursor %W [expr {[%W index insert]+1}] +} +bind Entry <> { + tk::EntryKeySelect %W [expr {[%W index insert]-1}] + tk::EntrySeeInsert %W +} +bind Entry <> { + tk::EntryKeySelect %W [expr {[%W index insert]+1}] + tk::EntrySeeInsert %W +} +bind Entry <> { + tk::EntrySetCursor %W [tk::EntryPreviousWord %W insert] +} +bind Entry <> { + tk::EntrySetCursor %W [tk::EntryNextWord %W insert] +} +bind Entry <> { + tk::EntryKeySelect %W [tk::EntryPreviousWord %W insert] + tk::EntrySeeInsert %W +} +bind Entry <> { + tk::EntryKeySelect %W [tk::EntryNextWord %W insert] + tk::EntrySeeInsert %W +} +bind Entry <> { + tk::EntrySetCursor %W 0 +} +bind Entry <> { + tk::EntryKeySelect %W 0 + tk::EntrySeeInsert %W +} +bind Entry <> { + tk::EntrySetCursor %W end +} +bind Entry <> { + tk::EntryKeySelect %W end + tk::EntrySeeInsert %W +} + +bind Entry { + if {[%W selection present]} { + %W delete sel.first sel.last + } else { + %W delete insert + } +} +bind Entry { + tk::EntryBackspace %W +} + +bind Entry { + %W selection from insert +} +bind Entry { + tk::ListboxBeginSelect %W [%W index active] +} +bind Listbox { + tk::ListboxBeginExtend %W [%W index active] +} +bind Listbox { + tk::ListboxBeginExtend %W [%W index active] +} +bind Listbox { + tk::ListboxCancel %W +} +bind Listbox <> { + tk::ListboxSelectAll %W +} +bind Listbox <> { + if {[%W cget -selectmode] ne "browse"} { + %W selection clear 0 end + tk::FireListboxSelectEvent %W + } +} + +# Additional Tk bindings that aren't part of the Motif look and feel: + +bind Listbox <2> { + %W scan mark %x %y +} +bind Listbox { + %W scan dragto %x %y +} + +# The MouseWheel will typically only fire on Windows and Mac OS X. +# However, someone could use the "event generate" command to produce +# one on other platforms. + +if {[tk windowingsystem] eq "aqua"} { + bind Listbox { + %W yview scroll [expr {-(%D)}] units + } + bind Listbox { + %W yview scroll [expr {-10 * (%D)}] units + } + bind Listbox { + %W xview scroll [expr {-(%D)}] units + } + bind Listbox { + %W xview scroll [expr {-10 * (%D)}] units + } +} else { + bind Listbox { + if {%D >= 0} { + %W yview scroll [expr {-%D/30}] units + } else { + %W yview scroll [expr {(29-%D)/30}] units + } + } + bind Listbox { + if {%D >= 0} { + %W xview scroll [expr {-%D/30}] units + } else { + %W xview scroll [expr {(29-%D)/30}] units + } + } +} + +if {[tk windowingsystem] eq "x11"} { + # Support for mousewheels on Linux/Unix commonly comes through mapping + # the wheel to the extended buttons. If you have a mousewheel, find + # Linux configuration info at: + # https://linuxreviews.org/HOWTO_change_the_mouse_speed_in_X + bind Listbox <4> { + if {!$tk_strictMotif} { + %W yview scroll -5 units + } + } + bind Listbox { + if {!$tk_strictMotif} { + %W xview scroll -5 units + } + } + bind Listbox <5> { + if {!$tk_strictMotif} { + %W yview scroll 5 units + } + } + bind Listbox { + if {!$tk_strictMotif} { + %W xview scroll 5 units + } + } +} + +# ::tk::ListboxBeginSelect -- +# +# This procedure is typically invoked on button-1 presses. It begins +# the process of making a selection in the listbox. Its exact behavior +# depends on the selection mode currently in effect for the listbox; +# see the Motif documentation for details. +# +# Arguments: +# w - The listbox widget. +# el - The element for the selection operation (typically the +# one under the pointer). Must be in numerical form. + +proc ::tk::ListboxBeginSelect {w el {focus 1}} { + variable ::tk::Priv + if {[$w cget -selectmode] eq "multiple"} { + if {[$w selection includes $el]} { + $w selection clear $el + } else { + $w selection set $el + } + } else { + $w selection clear 0 end + $w selection set $el + $w selection anchor $el + set Priv(listboxSelection) {} + set Priv(listboxPrev) $el + } + tk::FireListboxSelectEvent $w + # check existence as ListboxSelect may destroy us + if {$focus && [winfo exists $w] && [$w cget -state] eq "normal"} { + focus $w + } +} + +# ::tk::ListboxMotion -- +# +# This procedure is called to process mouse motion events while +# button 1 is down. It may move or extend the selection, depending +# on the listbox's selection mode. +# +# Arguments: +# w - The listbox widget. +# el - The element under the pointer (must be a number). + +proc ::tk::ListboxMotion {w el} { + variable ::tk::Priv + if {$el == $Priv(listboxPrev)} { + return + } + set anchor [$w index anchor] + switch [$w cget -selectmode] { + browse { + $w selection clear 0 end + $w selection set $el + set Priv(listboxPrev) $el + tk::FireListboxSelectEvent $w + } + extended { + set i $Priv(listboxPrev) + if {$i < 0} { + set i $el + $w selection set $el + } + if {[$w selection includes anchor]} { + $w selection clear $i $el + $w selection set anchor $el + } else { + $w selection clear $i $el + $w selection clear anchor $el + } + if {![info exists Priv(listboxSelection)]} { + set Priv(listboxSelection) [$w curselection] + } + while {($i < $el) && ($i < $anchor)} { + if {$i in $Priv(listboxSelection)} { + $w selection set $i + } + incr i + } + while {($i > $el) && ($i > $anchor)} { + if {$i in $Priv(listboxSelection)} { + $w selection set $i + } + incr i -1 + } + set Priv(listboxPrev) $el + tk::FireListboxSelectEvent $w + } + } +} + +# ::tk::ListboxBeginExtend -- +# +# This procedure is typically invoked on shift-button-1 presses. It +# begins the process of extending a selection in the listbox. Its +# exact behavior depends on the selection mode currently in effect +# for the listbox; see the Motif documentation for details. +# +# Arguments: +# w - The listbox widget. +# el - The element for the selection operation (typically the +# one under the pointer). Must be in numerical form. + +proc ::tk::ListboxBeginExtend {w el} { + if {[$w cget -selectmode] eq "extended"} { + if {[$w selection includes anchor]} { + ListboxMotion $w $el + } else { + # No selection yet; simulate the begin-select operation. + ListboxBeginSelect $w $el + } + } +} + +# ::tk::ListboxBeginToggle -- +# +# This procedure is typically invoked on control-button-1 presses. It +# begins the process of toggling a selection in the listbox. Its +# exact behavior depends on the selection mode currently in effect +# for the listbox; see the Motif documentation for details. +# +# Arguments: +# w - The listbox widget. +# el - The element for the selection operation (typically the +# one under the pointer). Must be in numerical form. + +proc ::tk::ListboxBeginToggle {w el} { + variable ::tk::Priv + if {[$w cget -selectmode] eq "extended"} { + set Priv(listboxSelection) [$w curselection] + set Priv(listboxPrev) $el + $w selection anchor $el + if {[$w selection includes $el]} { + $w selection clear $el + } else { + $w selection set $el + } + tk::FireListboxSelectEvent $w + } +} + +# ::tk::ListboxAutoScan -- +# This procedure is invoked when the mouse leaves an entry window +# with button 1 down. It scrolls the window up, down, left, or +# right, depending on where the mouse left the window, and reschedules +# itself as an "after" command so that the window continues to scroll until +# the mouse moves back into the window or the mouse button is released. +# +# Arguments: +# w - The entry window. + +proc ::tk::ListboxAutoScan {w} { + variable ::tk::Priv + if {![winfo exists $w]} return + set x $Priv(x) + set y $Priv(y) + if {$y >= [winfo height $w]} { + $w yview scroll 1 units + } elseif {$y < 0} { + $w yview scroll -1 units + } elseif {$x >= [winfo width $w]} { + $w xview scroll 2 units + } elseif {$x < 0} { + $w xview scroll -2 units + } else { + return + } + ListboxMotion $w [$w index @$x,$y] + set Priv(afterId) [after 50 [list tk::ListboxAutoScan $w]] +} + +# ::tk::ListboxUpDown -- +# +# Moves the location cursor (active element) up or down by one element, +# and changes the selection if we're in browse or extended selection +# mode. +# +# Arguments: +# w - The listbox widget. +# amount - +1 to move down one item, -1 to move back one item. + +proc ::tk::ListboxUpDown {w amount} { + variable ::tk::Priv + $w activate [expr {[$w index active] + $amount}] + $w see active + switch [$w cget -selectmode] { + browse { + $w selection clear 0 end + $w selection set active + tk::FireListboxSelectEvent $w + } + extended { + $w selection clear 0 end + $w selection set active + $w selection anchor active + set Priv(listboxPrev) [$w index active] + set Priv(listboxSelection) {} + tk::FireListboxSelectEvent $w + } + } +} + +# ::tk::ListboxExtendUpDown -- +# +# Does nothing unless we're in extended selection mode; in this +# case it moves the location cursor (active element) up or down by +# one element, and extends the selection to that point. +# +# Arguments: +# w - The listbox widget. +# amount - +1 to move down one item, -1 to move back one item. + +proc ::tk::ListboxExtendUpDown {w amount} { + variable ::tk::Priv + if {[$w cget -selectmode] ne "extended"} { + return + } + set active [$w index active] + if {![info exists Priv(listboxSelection)]} { + $w selection set $active + set Priv(listboxSelection) [$w curselection] + } + $w activate [expr {$active + $amount}] + $w see active + ListboxMotion $w [$w index active] +} + +# ::tk::ListboxDataExtend +# +# This procedure is called for key-presses such as Shift-KEndData. +# If the selection mode isn't multiple or extend then it does nothing. +# Otherwise it moves the active element to el and, if we're in +# extended mode, extends the selection to that point. +# +# Arguments: +# w - The listbox widget. +# el - An integer element number. + +proc ::tk::ListboxDataExtend {w el} { + set mode [$w cget -selectmode] + if {$mode eq "extended"} { + $w activate $el + $w see $el + if {[$w selection includes anchor]} { + ListboxMotion $w $el + } + } elseif {$mode eq "multiple"} { + $w activate $el + $w see $el + } +} + +# ::tk::ListboxCancel +# +# This procedure is invoked to cancel an extended selection in +# progress. If there is an extended selection in progress, it +# restores all of the items between the active one and the anchor +# to their previous selection state. +# +# Arguments: +# w - The listbox widget. + +proc ::tk::ListboxCancel w { + variable ::tk::Priv + if {[$w cget -selectmode] ne "extended"} { + return + } + set first [$w index anchor] + set last $Priv(listboxPrev) + if {$last eq ""} { + # Not actually doing any selection right now + return + } + if {$first > $last} { + set tmp $first + set first $last + set last $tmp + } + $w selection clear $first $last + while {$first <= $last} { + if {$first in $Priv(listboxSelection)} { + $w selection set $first + } + incr first + } + tk::FireListboxSelectEvent $w +} + +# ::tk::ListboxSelectAll +# +# This procedure is invoked to handle the "select all" operation. +# For single and browse mode, it just selects the active element. +# Otherwise it selects everything in the widget. +# +# Arguments: +# w - The listbox widget. + +proc ::tk::ListboxSelectAll w { + set mode [$w cget -selectmode] + if {$mode eq "single" || $mode eq "browse"} { + $w selection clear 0 end + $w selection set active + } else { + $w selection set 0 end + } + tk::FireListboxSelectEvent $w +} + +# ::tk::FireListboxSelectEvent +# +# Fire the <> event if the listbox is not in disabled +# state. +# +# Arguments: +# w - The listbox widget. + +proc ::tk::FireListboxSelectEvent w { + if {[$w cget -state] eq "normal"} { + event generate $w <> + } +} diff --git a/lib/tk8.6/megawidget.tcl b/lib/tk8.6/megawidget.tcl new file mode 100644 index 0000000000000000000000000000000000000000..ec9f469adfe50c03d8ffa4449809704a892f64cc --- /dev/null +++ b/lib/tk8.6/megawidget.tcl @@ -0,0 +1,297 @@ +# megawidget.tcl +# +# Basic megawidget support classes. Experimental for any use other than +# the ::tk::IconList megawdget, which is itself only designed for use in +# the Unix file dialogs. +# +# Copyright (c) 2009-2010 Donal K. Fellows +# +# See the file "license.terms" for information on usage and redistribution of +# this file, and for a DISCLAIMER OF ALL WARRANTIES. +# + +package require Tk + +::oo::class create ::tk::Megawidget { + superclass ::oo::class + method unknown {w args} { + if {[string match .* $w]} { + [self] create $w {*}$args + return $w + } + next $w {*}$args + } + unexport new unknown + self method create {name superclasses body} { + next $name [list \ + superclass ::tk::MegawidgetClass {*}$superclasses]\;$body + } +} + +::oo::class create ::tk::MegawidgetClass { + variable w hull options IdleCallbacks + constructor args { + # Extract the "widget name" from the object name + set w [namespace tail [self]] + + # Configure things + tclParseConfigSpec [my varname options] [my GetSpecs] "" $args + + # Move the object out of the way of the hull widget + rename [self] _tmp + + # Make the hull widget(s) + my CreateHull + bind $hull [list [namespace which my] destroy] + + # Rename things into their final places + rename ::$w theWidget + rename [self] ::$w + + # Make the contents + my Create + } + destructor { + foreach {name cb} [array get IdleCallbacks] { + after cancel $cb + unset IdleCallbacks($name) + } + if {[winfo exists $w]} { + bind $hull {} + destroy $w + } + } + + #################################################################### + # + # MegawidgetClass::configure -- + # + # Implementation of 'configure' for megawidgets. Emulates the operation + # of the standard Tk configure method fairly closely, which makes things + # substantially more complex than they otherwise would be. + # + # This method assumes that the 'GetSpecs' method returns a description + # of all the specifications of the options (i.e., as Tk returns except + # with the actual values removed). It also assumes that the 'options' + # array in the class holds all options; it is up to subclasses to set + # traces on that array if they want to respond to configuration changes. + # + # TODO: allow unambiguous abbreviations. + # + method configure args { + # Configure behaves differently depending on the number of arguments + set argc [llength $args] + if {$argc == 0} { + return [lmap spec [my GetSpecs] { + lappend spec $options([lindex $spec 0]) + }] + } elseif {$argc == 1} { + set opt [lindex $args 0] + if {[info exists options($opt)]} { + set spec [lsearch -inline -index 0 -exact [my GetSpecs] $opt] + return [linsert $spec end $options($opt)] + } + } elseif {$argc == 2} { + # Special case for where we're setting a single option. This + # avoids some of the costly operations. We still do the [array + # get] as this gives a sufficiently-consistent trace. + set opt [lindex $args 0] + if {[dict exists [array get options] $opt]} { + # Actually set the new value of the option. Use a catch to + # allow a megawidget user to throw an error from a write trace + # on the options array to reject invalid values. + try { + array set options $args + } on error {ret info} { + # Rethrow the error to get a clean stack trace + return -code error -errorcode [dict get $info -errorcode] $ret + } + return + } + } elseif {$argc % 2 == 0} { + # Check that all specified options exist. Any unknown option will + # cause the merged dictionary to be bigger than the options array + set merge [dict merge [array get options] $args] + if {[dict size $merge] == [array size options]} { + # Actually set the new values of the options. Use a catch to + # allow a megawidget user to throw an error from a write trace + # on the options array to reject invalid values + try { + array set options $args + } on error {ret info} { + # Rethrow the error to get a clean stack trace + return -code error -errorcode [dict get $info -errorcode] $ret + } + return + } + # Due to the order of the merge, the unknown options will be at + # the end of the dict. This makes the first unknown option easy to + # find. + set opt [lindex [dict keys $merge] [array size options]] + } else { + set opt [lindex $args end] + return -code error -errorcode [list TK VALUE_MISSING] \ + "value for \"$opt\" missing" + } + return -code error -errorcode [list TK LOOKUP OPTION $opt] \ + "bad option \"$opt\": must be [tclListValidFlags options]" + } + + #################################################################### + # + # MegawidgetClass::cget -- + # + # Implementation of 'cget' for megawidgets. Emulates the operation of + # the standard Tk cget method fairly closely. + # + # This method assumes that the 'options' array in the class holds all + # options; it is up to subclasses to set traces on that array if they + # want to respond to configuration reads. + # + # TODO: allow unambiguous abbreviations. + # + method cget option { + return $options($option) + } + + #################################################################### + # + # MegawidgetClass::TraceOption -- + # + # Sets up the tracing of an element of the options variable. + # + method TraceOption {option method args} { + set callback [list my $method {*}$args] + trace add variable options($option) write [namespace code $callback] + } + + #################################################################### + # + # MegawidgetClass::GetSpecs -- + # + # Return a list of descriptions of options supported by this + # megawidget. Each option is described by the 4-tuple list, consisting + # of the name of the option, the "option database" name, the "option + # database" class-name, and the default value of the option. These are + # the same values returned by calling the configure method of a widget, + # except without the current values of the options. + # + method GetSpecs {} { + return { + {-takefocus takeFocus TakeFocus {}} + } + } + + #################################################################### + # + # MegawidgetClass::CreateHull -- + # + # Creates the real main widget of the megawidget. This is often a frame + # or toplevel widget, but isn't always (lightweight megawidgets might + # use a content widget directly). + # + # The name of the hull widget is given by the 'w' instance variable. The + # name should be written into the 'hull' instance variable. The command + # created by this method will be renamed. + # + method CreateHull {} { + return -code error -errorcode {TCL OO ABSTRACT_METHOD} \ + "method must be overridden" + } + + #################################################################### + # + # MegawidgetClass::Create -- + # + # Creates the content of the megawidget. The name of the widget to + # create the content in will be in the 'hull' instance variable. + # + method Create {} { + return -code error -errorcode {TCL OO ABSTRACT_METHOD} \ + "method must be overridden" + } + + #################################################################### + # + # MegawidgetClass::WhenIdle -- + # + # Arrange for a method to be called on the current instance when Tk is + # idle. Only one such method call per method will be queued; subsequent + # queuing actions before the callback fires will be silently ignored. + # The additional args will be passed to the callback, and the callbacks + # will be properly cancelled if the widget is destroyed. + # + method WhenIdle {method args} { + if {![info exists IdleCallbacks($method)]} { + set IdleCallbacks($method) [after idle [list \ + [namespace which my] DoWhenIdle $method $args]] + } + } + method DoWhenIdle {method arguments} { + unset IdleCallbacks($method) + tailcall my $method {*}$arguments + } +} + +#################################################################### +# +# tk::SimpleWidget -- +# +# Simple megawidget class that makes it easy create widgets that behave +# like a ttk widget. It creates the hull as a ttk::frame and maps the +# state manipulation methods of the overall megawidget to the equivalent +# operations on the ttk::frame. +# +::tk::Megawidget create ::tk::SimpleWidget {} { + variable w hull options + method GetSpecs {} { + return { + {-cursor cursor Cursor {}} + {-takefocus takeFocus TakeFocus {}} + } + } + method CreateHull {} { + set hull [::ttk::frame $w -cursor $options(-cursor)] + my TraceOption -cursor UpdateCursorOption + } + method UpdateCursorOption args { + $hull configure -cursor $options(-cursor) + } + # Not fixed names, so can't forward + method state args { + tailcall $hull state {*}$args + } + method instate args { + tailcall $hull instate {*}$args + } +} + +#################################################################### +# +# tk::FocusableWidget -- +# +# Simple megawidget class that makes a ttk-like widget that has a focus +# ring. +# +::tk::Megawidget create ::tk::FocusableWidget ::tk::SimpleWidget { + variable w hull options + method GetSpecs {} { + return { + {-cursor cursor Cursor {}} + {-takefocus takeFocus TakeFocus ::ttk::takefocus} + } + } + method CreateHull {} { + ttk::frame $w + set hull [ttk::entry $w.cHull -takefocus 0 -cursor $options(-cursor)] + pack $hull -expand yes -fill both -ipadx 2 -ipady 2 + my TraceOption -cursor UpdateCursorOption + } +} + +return + +# Local Variables: +# mode: tcl +# fill-column: 78 +# End: diff --git a/lib/tk8.6/menu.tcl b/lib/tk8.6/menu.tcl new file mode 100644 index 0000000000000000000000000000000000000000..f6ec29ad0d7c99ca21e0a9cd11e5ca90169739d3 --- /dev/null +++ b/lib/tk8.6/menu.tcl @@ -0,0 +1,1387 @@ +# menu.tcl -- +# +# This file defines the default bindings for Tk menus and menubuttons. +# It also implements keyboard traversal of menus and implements a few +# other utility procedures related to menus. +# +# Copyright (c) 1992-1994 The Regents of the University of California. +# Copyright (c) 1994-1997 Sun Microsystems, Inc. +# Copyright (c) 1998-1999 Scriptics Corporation. +# Copyright (c) 2007 Daniel A. Steffen +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# + +#------------------------------------------------------------------------- +# Elements of tk::Priv that are used in this file: +# +# cursor - Saves the -cursor option for the posted menubutton. +# focus - Saves the focus during a menu selection operation. +# Focus gets restored here when the menu is unposted. +# grabGlobal - Used in conjunction with tk::Priv(oldGrab): if +# tk::Priv(oldGrab) is non-empty, then tk::Priv(grabGlobal) +# contains either an empty string or "-global" to +# indicate whether the old grab was a local one or +# a global one. +# inMenubutton - The name of the menubutton widget containing +# the mouse, or an empty string if the mouse is +# not over any menubutton. +# menuBar - The name of the menubar that is the root +# of the cascade hierarchy which is currently +# posted. This is null when there is no menu currently +# being pulled down from a menu bar. +# oldGrab - Window that had the grab before a menu was posted. +# Used to restore the grab state after the menu +# is unposted. Empty string means there was no +# grab previously set. +# popup - If a menu has been popped up via tk_popup, this +# gives the name of the menu. Otherwise this +# value is empty. +# postedMb - Name of the menubutton whose menu is currently +# posted, or an empty string if nothing is posted +# A grab is set on this widget. +# relief - Used to save the original relief of the current +# menubutton. +# window - When the mouse is over a menu, this holds the +# name of the menu; it's cleared when the mouse +# leaves the menu. +# tearoff - Whether the last menu posted was a tearoff or not. +# This is true always for unix, for tearoffs for Mac +# and Windows. +# activeMenu - This is the last active menu for use +# with the <> virtual event. +# activeItem - This is the last active menu item for +# use with the <> virtual event. +#------------------------------------------------------------------------- + +#------------------------------------------------------------------------- +# Overall note: +# This file is tricky because there are five different ways that menus +# can be used: +# +# 1. As a pulldown from a menubutton. In this style, the variable +# tk::Priv(postedMb) identifies the posted menubutton. +# 2. As a torn-off menu copied from some other menu. In this style +# tk::Priv(postedMb) is empty, and menu's type is "tearoff". +# 3. As an option menu, triggered from an option menubutton. In this +# style tk::Priv(postedMb) identifies the posted menubutton. +# 4. As a popup menu. In this style tk::Priv(postedMb) is empty and +# the top-level menu's type is "normal". +# 5. As a pulldown from a menubar. The variable tk::Priv(menubar) has +# the owning menubar, and the menu itself is of type "normal". +# +# The various binding procedures use the state described above to +# distinguish the various cases and take different actions in each +# case. +#------------------------------------------------------------------------- + +#------------------------------------------------------------------------- +# The code below creates the default class bindings for menus +# and menubuttons. +#------------------------------------------------------------------------- + +bind Menubutton {} +bind Menubutton { + tk::MbEnter %W +} +bind Menubutton { + tk::MbLeave %W +} +bind Menubutton { + if {$tk::Priv(inMenubutton) ne ""} { + tk::MbPost $tk::Priv(inMenubutton) %X %Y + } +} +bind Menubutton { + tk::MbMotion %W up %X %Y +} +bind Menubutton { + tk::MbMotion %W down %X %Y +} +bind Menubutton { + tk::MbButtonUp %W +} +bind Menubutton { + tk::MbPost %W + tk::MenuFirstEntry [%W cget -menu] +} +bind Menubutton <> { + tk::MbPost %W + tk::MenuFirstEntry [%W cget -menu] +} + +# Must set focus when mouse enters a menu, in order to allow +# mixed-mode processing using both the mouse and the keyboard. +# Don't set the focus if the event comes from a grab release, +# though: such an event can happen after as part of unposting +# a cascaded chain of menus, after the focus has already been +# restored to wherever it was before menu selection started. + +bind Menu {} + +bind Menu { + set tk::Priv(window) %W + if {[%W cget -type] eq "tearoff"} { + if {"%m" ne "NotifyUngrab"} { + if {[tk windowingsystem] eq "x11"} { + tk_menuSetFocus %W + } + } + } + tk::MenuMotion %W %x %y %s +} + +bind Menu { + tk::MenuLeave %W %X %Y %s +} +bind Menu { + tk::MenuMotion %W %x %y %s +} +bind Menu